Skip to content

Commit e5403bb

Browse files
authored
ci: merge main to release (#9029)
2 parents ec34cf7 + 18cea10 commit e5403bb

File tree

15 files changed

+347
-155
lines changed

15 files changed

+347
-155
lines changed

dev/coverage-action/package-lock.json

Lines changed: 196 additions & 130 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dev/coverage-action/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"license": "BSD-3-Clause",
88
"dependencies": {
99
"@actions/core": "1.11.1",
10-
"@actions/github": "6.0.0",
10+
"@actions/github": "6.0.1",
1111
"lodash": "4.17.21",
1212
"luxon": "3.6.1"
1313
}

ietf/api/tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1161,7 +1161,7 @@ def test_related_email_list(self):
11611161
self.assertEqual(r.headers["Content-Type"], "application/json")
11621162
result = json.loads(r.content)
11631163
self.assertCountEqual(result.keys(), ["addresses"])
1164-
self.assertCountEqual(result["addresses"], joe.person.email_set.exclude(address='[email protected]').values_list("address", flat=True))
1164+
self.assertCountEqual(result["addresses"], joe.person.email_set.values_list("address", flat=True))
11651165
# non-ascii
11661166
non_ascii_url = urlreverse("ietf.api.views.related_email_list", kwargs={'email': 'jò[email protected]'})
11671167
r = self.client.get(non_ascii_url, headers={"X-Api-Key": "valid-token"})

ietf/api/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ def _http_err(code, text):
618618
return JsonResponse({"addresses": []})
619619
return JsonResponse(
620620
{
621-
"addresses": list(person.email_set.exclude(address=email).values_list("address", flat=True)),
621+
"addresses": list(person.email_set.values_list("address", flat=True)),
622622
}
623623
)
624624
return HttpResponse(status=405)

ietf/doc/forms.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2013-2020, All Rights Reserved
1+
# Copyright The IETF Trust 2013-2025, All Rights Reserved
22
# -*- coding: utf-8 -*-
33

44

@@ -9,7 +9,7 @@
99
from django.core.validators import validate_email
1010

1111
from ietf.doc.fields import SearchableDocumentField, SearchableDocumentsField
12-
from ietf.doc.models import RelatedDocument, DocExtResource
12+
from ietf.doc.models import RelatedDocument, DocExtResource, State
1313
from ietf.iesg.models import TelechatDate
1414
from ietf.iesg.utils import telechat_page_count
1515
from ietf.person.fields import SearchablePersonField, SearchablePersonsField
@@ -61,7 +61,7 @@ class DocAuthorChangeBasisForm(forms.Form):
6161
basis = forms.CharField(max_length=255,
6262
label='Reason for change',
6363
help_text='What is the source or reasoning for the changes to the author list?')
64-
64+
6565
class AdForm(forms.Form):
6666
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type='area').order_by('name'),
6767
label="Shepherding AD", empty_label="(None)", required=True)
@@ -288,3 +288,10 @@ def clean_name_fragment(self):
288288
if any(c in name_fragment for c in disallowed_characters):
289289
raise ValidationError(f"The following characters are disallowed: {', '.join(disallowed_characters)}")
290290
return name_fragment
291+
292+
293+
class ChangeStatementStateForm(forms.Form):
294+
state = forms.ModelChoiceField(
295+
State.objects.filter(used=True, type="statement"),
296+
empty_label=None,
297+
)

ietf/doc/tests_statement.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2023, All Rights Reserved
1+
# Copyright The IETF Trust 2023-2025, All Rights Reserved
22

33
import debug # pyflakes:ignore
44

@@ -372,3 +372,36 @@ def test_submit_non_markdown_formats(self):
372372
self.assertEqual(r.status_code, 200)
373373
q = PyQuery(r.content)
374374
self.assertTrue("Unexpected content" in q("#id_statement_file").next().text())
375+
376+
def test_change_statement_state(self):
377+
statement = StatementFactory() # starts in "active" state
378+
active_state = State.objects.get(type_id="statement", slug="active")
379+
replaced_state = State.objects.get(type_id="statement", slug="replaced")
380+
url = urlreverse(
381+
"ietf.doc.views_statement.change_statement_state",
382+
kwargs={"name": statement.name},
383+
)
384+
385+
events_before = statement.docevent_set.count()
386+
login_testing_unauthorized(self, "secretary", url)
387+
388+
r = self.client.get(url)
389+
self.assertEqual(r.status_code,200)
390+
391+
r = self.client.post(url, {"state": active_state.pk}, follow=True)
392+
self.assertContains(r, "State not changed", status_code=200)
393+
statement = Document.objects.get(pk=statement.pk) # bust the state cache
394+
self.assertEqual(statement.get_state(), active_state)
395+
396+
r = self.client.post(url, {"state": replaced_state.pk}, follow=True)
397+
self.assertContains(r, "State changed to", status_code=200)
398+
statement = Document.objects.get(pk=statement.pk) # bust the state cache
399+
self.assertEqual(statement.get_state(), replaced_state)
400+
401+
events_after = statement.docevent_set.count()
402+
self.assertEqual(events_after, events_before + 1)
403+
event = statement.docevent_set.first()
404+
self.assertEqual(event.type, "changed_state")
405+
self.assertEqual(
406+
event.desc, "Statement State changed to <b>Replaced</b> from Active"
407+
)

ietf/doc/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2009-2023, All Rights Reserved
1+
# Copyright The IETF Trust 2009-2025, All Rights Reserved
22
# -*- coding: utf-8 -*-
33
# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
44
# All rights reserved. Contact: Pasi Eronen <[email protected]>
@@ -145,6 +145,7 @@
145145
url(r'^%(name)s/edit/adopt/$' % settings.URL_REGEXPS, views_draft.adopt_draft),
146146
url(r'^%(name)s/edit/release/$' % settings.URL_REGEXPS, views_draft.release_draft),
147147
url(r'^%(name)s/edit/state/(?P<state_type>draft-stream-[a-z]+)/$' % settings.URL_REGEXPS, views_draft.change_stream_state),
148+
url(r'^%(name)s/edit/state/statement/$' % settings.URL_REGEXPS, views_statement.change_statement_state),
148149

149150
url(r'^%(name)s/edit/clearballot/(?P<ballot_type_slug>[\w-]+)/$' % settings.URL_REGEXPS, views_ballot.clear_ballot),
150151
url(r'^%(name)s/edit/deferballot/$' % settings.URL_REGEXPS, views_ballot.defer_ballot),

ietf/doc/views_statement.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
# Copyright The IETF Trust 2023, All Rights Reserved
1+
# Copyright The IETF Trust 2023-2025, All Rights Reserved
2+
from django.contrib import messages
23

34
import debug # pyflakes: ignore
45

56
from pathlib import Path
67

78
from django import forms
89
from django.conf import settings
9-
from django.http import FileResponse, Http404
10+
from django.http import FileResponse, Http404, HttpResponseRedirect
1011
from django.views.decorators.cache import cache_control
1112
from django.shortcuts import get_object_or_404, render, redirect
1213
from django.template.loader import render_to_string
14+
15+
from ietf.doc.forms import ChangeStatementStateForm
16+
from ietf.doc.utils import add_state_change_event
1317
from ietf.utils import markdown
1418
from django.utils.html import escape
1519

@@ -278,3 +282,40 @@ def new_statement(request):
278282
}
279283
form = NewStatementForm(initial=init)
280284
return render(request, "doc/statement/new_statement.html", {"form": form})
285+
286+
287+
@role_required("Secretariat")
288+
def change_statement_state(request, name):
289+
"""Change state of a statement Document"""
290+
statement = get_object_or_404(
291+
Document.objects.filter(type_id="statement"),
292+
name=name,
293+
)
294+
if request.method == "POST":
295+
form = ChangeStatementStateForm(request.POST)
296+
if form.is_valid():
297+
new_state = form.cleaned_data["state"]
298+
prev_state = statement.get_state()
299+
if new_state == prev_state:
300+
messages.info(request, f"State not changed, remains {prev_state}.")
301+
else:
302+
statement.set_state(new_state)
303+
e = add_state_change_event(
304+
statement,
305+
request.user.person,
306+
prev_state,
307+
new_state,
308+
)
309+
statement.save_with_history([e])
310+
messages.success(request, f"State changed to {new_state}.")
311+
return HttpResponseRedirect(statement.get_absolute_url())
312+
else:
313+
form = ChangeStatementStateForm(initial={"state": statement.get_state()})
314+
return render(
315+
request,
316+
"doc/statement/change_statement_state.html",
317+
{
318+
"form": form,
319+
"statement": statement,
320+
},
321+
)

ietf/iesg/tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ def test_telechat_agenda_content_view(self):
759759
urlreverse("ietf.iesg.views.telechat_agenda_content_view", kwargs={"section": section})
760760
)
761761
self.assertContains(r, content, status_code=200)
762-
self.assertEqual(r.get("Content-Type", None), "text/plain")
762+
self.assertEqual(r.get("Content-Type", None), "text/plain; charset=utf-8")
763763

764764
def test_telechat_agenda_content_view_permissions(self):
765765
for section in TelechatAgendaSectionName.objects.filter(used=True).values_list("slug", flat=True):

ietf/iesg/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,4 +610,4 @@ def telechat_agenda_content_manage(request):
610610
@role_required("Secretariat", "IAB Chair", "Area Director")
611611
def telechat_agenda_content_view(request, section):
612612
content = get_object_or_404(TelechatAgendaContent, section__slug=section, section__used=True)
613-
return HttpResponse(content=content.text, content_type="text/plain")
613+
return HttpResponse(content=content.text, content_type="text/plain; charset=utf-8")

0 commit comments

Comments
 (0)