Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions ietf/doc/mails.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,61 @@ def email_stream_changed(request, doc, old_stream, new_stream, text=""):
dict(text=text,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
cc=cc)

def email_wg_call_for_adoption_issued(request, doc, cfa_duration_weeks=None):
if cfa_duration_weeks is None:
cfa_duration_weeks=2
(to, cc) = gather_address_lists("doc_wg_call_for_adoption_issued", doc=doc)
frm = request.user.person.formatted_email()

end_date = date_today(DEADLINE_TZINFO) + datetime.timedelta(days=7 * cfa_duration_weeks)

subject = f"Call for adoption: {doc.name}-{doc.rev} (Ends {end_date})"

send_mail(
request,
to,
frm,
subject,
"doc/mail/wg_call_for_adoption_issued.txt",
dict(
doc=doc,
subject=subject,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
end_date=end_date,
cfa_duration_weeks=cfa_duration_weeks,
wg_list=doc.group.list_email,
),
cc=cc,
)


def email_wg_last_call_issued(request, doc, wglc_duration_weeks=None):
if wglc_duration_weeks is None:
wglc_duration_weeks = 2
(to, cc) = gather_address_lists("doc_wg_last_call_issued", doc=doc)
frm = request.user.person.formatted_email()


end_date = date_today(DEADLINE_TZINFO) + datetime.timedelta(days=7 * wglc_duration_weeks)
subject = f"WG Last Call: {doc.name}-{doc.rev} (Ends {end_date})"

send_mail(
request,
to,
frm,
subject,
"doc/mail/wg_last_call_issued.txt",
dict(
doc=doc,
subject=subject,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
end_date=end_date,
wglc_duration_weeks=wglc_duration_weeks,
wg_list=doc.group.list_email,
),
cc=cc,
)

def email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state):
extra=extra_automation_headers(doc)
Expand Down
205 changes: 200 additions & 5 deletions ietf/doc/tests_draft.py
Original file line number Diff line number Diff line change
Expand Up @@ -1707,11 +1707,12 @@ def test_adopt_document(self):
self.assertEqual(draft.group, chair_role.group)
self.assertEqual(draft.stream_id, stream_state_type_slug[type_id][13:]) # trim off "draft-stream-"
self.assertEqual(draft.docevent_set.count() - events_before, 5)
self.assertEqual(len(outbox), 1)
self.assertTrue("Call For Adoption" in outbox[-1]["Subject"])
self.assertTrue(f"{chair_role.group.acronym}-chairs@" in outbox[-1]['To'])
self.assertTrue(f"{draft.name}@" in outbox[-1]['To'])
self.assertTrue(f"{chair_role.group.acronym}@" in outbox[-1]['To'])
self.assertEqual(len(outbox), 2)
self.assertTrue("Call For Adoption" in outbox[0]["Subject"])
self.assertTrue(f"{chair_role.group.acronym}-chairs@" in outbox[0]['To'])
self.assertTrue(f"{draft.name}@" in outbox[0]['To'])
self.assertTrue(f"{chair_role.group.acronym}@" in outbox[0]['To'])
# contents of outbox[1] are tested elsewhere

# adopt
empty_outbox()
Expand Down Expand Up @@ -2001,6 +2002,200 @@ def test_set_state(self):
self.assertTrue("mars-chairs@ietf.org" in outbox[0].as_string())
self.assertTrue("marsdelegate@ietf.org" in outbox[0].as_string())

def test_wg_call_for_adoption_issued(self):
role = RoleFactory(
name_id="chair",
group__acronym="mars",
group__list_email="mars-wg@ietf.org",
person__user__username="marschairman",
person__name="WG Cháir Man",
)
# First test the usual workflow through the manage adoption view
draft = IndividualDraftFactory()
url = urlreverse(
"ietf.doc.views_draft.adopt_draft", kwargs=dict(name=draft.name)
)
login_testing_unauthorized(self, "marschairman", url)
empty_outbox()
call_issued = State.objects.get(type="draft-stream-ietf", slug="c-adopt")
r = self.client.post(
url,
dict(
comment="some comment",
group=role.group.pk,
newstate=call_issued.pk,
weeks="10",
),
)
self.assertEqual(r.status_code, 302)
self.assertEqual(len(outbox), 2)
self.assertIn("mars-wg@ietf.org", outbox[1]["To"])
self.assertIn("Call for adoption", outbox[1]["Subject"])
body = get_payload_text(outbox[1])
self.assertIn("disclosure obligations", body)
self.assertIn("starts a 10-week", body)
# Test not entering a duration on the form
draft = IndividualDraftFactory()
url = urlreverse(
"ietf.doc.views_draft.adopt_draft", kwargs=dict(name=draft.name)
)
empty_outbox()
call_issued = State.objects.get(type="draft-stream-ietf", slug="c-adopt")
r = self.client.post(
url,
dict(
comment="some comment",
group=role.group.pk,
newstate=call_issued.pk,
),
)
self.assertEqual(r.status_code, 302)
self.assertEqual(len(outbox), 2)
self.assertIn("mars-wg@ietf.org", outbox[1]["To"])
self.assertIn("Call for adoption", outbox[1]["Subject"])
body = get_payload_text(outbox[1])
self.assertIn("disclosure obligations", body)
self.assertIn("starts a 2-week", body)

# Test the less usual workflow of issuing a call for adoption
# of a document that's already in the ietf stream
draft = WgDraftFactory(group=role.group)
url = urlreverse(
"ietf.doc.views_draft.change_stream_state",
kwargs=dict(name=draft.name, state_type="draft-stream-ietf"),
)
old_state = draft.get_state("draft-stream-%s" % draft.stream_id)
new_state = State.objects.get(
used=True, type="draft-stream-%s" % draft.stream_id, slug="c-adopt"
)
self.assertNotEqual(old_state, new_state)
empty_outbox()
r = self.client.post(
url,
dict(
new_state=new_state.pk,
comment="some comment",
weeks="10",
tags=[
t.pk
for t in draft.tags.filter(
slug__in=get_tags_for_stream_id(draft.stream_id)
)
],
),
)
self.assertEqual(r.status_code, 302)
self.assertEqual(len(outbox), 2)
self.assertIn("mars-wg@ietf.org", outbox[1]["To"])
self.assertIn("Call for adoption", outbox[1]["Subject"])
body = get_payload_text(outbox[1])
self.assertIn("disclosure obligations", body)
self.assertIn("starts a 10-week", body)
draft = WgDraftFactory(group=role.group)
url = urlreverse(
"ietf.doc.views_draft.change_stream_state",
kwargs=dict(name=draft.name, state_type="draft-stream-ietf"),
)
old_state = draft.get_state("draft-stream-%s" % draft.stream_id)
new_state = State.objects.get(
used=True, type="draft-stream-%s" % draft.stream_id, slug="c-adopt"
)
self.assertNotEqual(old_state, new_state)
empty_outbox()
r = self.client.post(
url,
dict(
new_state=new_state.pk,
comment="some comment",
tags=[
t.pk
for t in draft.tags.filter(
slug__in=get_tags_for_stream_id(draft.stream_id)
)
],
),
)
self.assertEqual(r.status_code, 302)
self.assertEqual(len(outbox), 2)
self.assertIn("mars-wg@ietf.org", outbox[1]["To"])
self.assertIn("Call for adoption", outbox[1]["Subject"])
body = get_payload_text(outbox[1])
self.assertIn("disclosure obligations", body)
self.assertIn("starts a 2-week", body)

def test_wg_last_call_issued(self):
role = RoleFactory(
name_id="chair",
group__acronym="mars",
group__list_email="mars-wg@ietf.org",
person__user__username="marschairman",
person__name="WG Cháir Man",
)
draft = WgDraftFactory(group=role.group)
url = urlreverse(
"ietf.doc.views_draft.change_stream_state",
kwargs=dict(name=draft.name, state_type="draft-stream-ietf"),
)
login_testing_unauthorized(self, "marschairman", url)
old_state = draft.get_state("draft-stream-%s" % draft.stream_id)
new_state = State.objects.get(
used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-lc"
)
self.assertNotEqual(old_state, new_state)
empty_outbox()
r = self.client.post(
url,
dict(
new_state=new_state.pk,
comment="some comment",
weeks="10",
tags=[
t.pk
for t in draft.tags.filter(
slug__in=get_tags_for_stream_id(draft.stream_id)
)
],
),
)
self.assertEqual(r.status_code, 302)
self.assertEqual(len(outbox), 2)
self.assertIn("mars-wg@ietf.org", outbox[1]["To"])
self.assertIn("WG Last Call", outbox[1]["Subject"])
body = get_payload_text(outbox[1])
self.assertIn("disclosure obligations", body)
self.assertIn("starts a 10-week", body)
draft = WgDraftFactory(group=role.group)
url = urlreverse(
"ietf.doc.views_draft.change_stream_state",
kwargs=dict(name=draft.name, state_type="draft-stream-ietf"),
)
old_state = draft.get_state("draft-stream-%s" % draft.stream_id)
new_state = State.objects.get(
used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-lc"
)
self.assertNotEqual(old_state, new_state)
empty_outbox()
r = self.client.post(
url,
dict(
new_state=new_state.pk,
comment="some comment",
tags=[
t.pk
for t in draft.tags.filter(
slug__in=get_tags_for_stream_id(draft.stream_id)
)
],
),
)
self.assertEqual(r.status_code, 302)
self.assertEqual(len(outbox), 2)
self.assertIn("mars-wg@ietf.org", outbox[1]["To"])
self.assertIn("WG Last Call", outbox[1]["Subject"])
body = get_payload_text(outbox[1])
self.assertIn("disclosure obligations", body)
self.assertIn("starts a 2-week", body)

def test_pubreq_validation(self):
role = RoleFactory(name_id='chair',group__acronym='mars',group__list_email='mars-wg@ietf.org',person__user__username='marschairman',person__name='WG Cháir Man')
RoleFactory(name_id='delegate',group=role.group,person__user__email='marsdelegate@ietf.org')
Expand Down
17 changes: 16 additions & 1 deletion ietf/doc/views_draft.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
IanaExpertDocEvent, IESG_SUBSTATE_TAGS)
from ietf.doc.mails import ( email_pulled_from_rfc_queue, email_resurrect_requested,
email_resurrection_completed, email_state_changed, email_stream_changed,
email_wg_call_for_adoption_issued, email_wg_last_call_issued,
email_stream_state_changed, email_stream_tags_changed, extra_automation_headers,
generate_publication_request, email_adopted, email_intended_status_changed,
email_iesg_processing_document, email_ad_approved_doc,
Expand Down Expand Up @@ -1568,8 +1569,15 @@ def adopt_draft(request, name):

update_reminder(doc, "stream-s", e, due_date)

# The following call name is very misleading - the view allows
# setting states that are _not_ the adopted state.
email_adopted(request, doc, prev_state, new_state, by, comment)

# Currently only the IETF stream uses the c-adopt state - guard against other
# streams starting to use it asthe IPR rules for those streams will be different.
if doc.stream_id == "ietf" and new_state.slug == "c-adopt":
email_wg_call_for_adoption_issued(request, doc, cfa_duration_weeks=form.cleaned_data["weeks"])

# comment
if comment:
e = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=by)
Expand Down Expand Up @@ -1754,13 +1762,20 @@ def change_stream_state(request, name, state_type):
events.append(e)

due_date = None
if form.cleaned_data["weeks"] != None:
if form.cleaned_data["weeks"] is not None:
due_date = datetime_today(DEADLINE_TZINFO) + datetime.timedelta(weeks=form.cleaned_data["weeks"])

update_reminder(doc, "stream-s", e, due_date)

email_stream_state_changed(request, doc, prev_state, new_state, by, comment)

if doc.stream_id == "ietf":
if new_state.slug == "c-adopt":
email_wg_call_for_adoption_issued(request, doc, cfa_duration_weeks=form.cleaned_data["weeks"])

if new_state.slug == "wg-lc":
email_wg_last_call_issued(request, doc, wglc_duration_weeks=form.cleaned_data["weeks"])

# tags
existing_tags = set(doc.tags.all())
new_tags = set(form.cleaned_data["tags"])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright The IETF Trust 2023, All Rights Reserved

from django.db import migrations


def forward(apps, schema_editor):
MailTrigger = apps.get_model("mailtrigger", "MailTrigger")
Recipient = apps.get_model("mailtrigger", "Recipient")
recipients = list(
Recipient.objects.filter(
slug__in=(
"doc_group_mail_list",
"doc_authors",
"doc_group_chairs",
"doc_shepherd",
)
)
)
call_for_adoption = MailTrigger.objects.create(
slug="doc_wg_call_for_adoption_issued",
desc="Recipients when a working group call for adoption is issued",
)
call_for_adoption.to.add(*recipients)
wg_last_call = MailTrigger.objects.create(
slug="doc_wg_last_call_issued",
desc="Recipients when a working group last call is issued",
)
wg_last_call.to.add(*recipients)


def reverse(apps, schema_editor):
MailTrigger = apps.get_model("mailtrigger", "MailTrigger")
MailTrigger.objects.filter(
slug_in=("doc_wg_call_for_adoption_issued", "doc_wg_last_call_issued")
).delete()


class Migration(migrations.Migration):
dependencies = [
("mailtrigger", "0005_rfc_recipients"),
]

operations = [migrations.RunPython(forward, reverse)]
Loading
Loading