ci: Merge pull request #6868 from ietf-tools/main
ci: merge main to release
This commit is contained in:
commit
9e0d93716f
|
@ -33,23 +33,16 @@
|
|||
"oderwat.indent-rainbow",
|
||||
"redhat.vscode-yaml",
|
||||
"spmeesseman.vscode-taskexplorer",
|
||||
"visualstudioexptteam.vscodeintellicode"
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"ms-python.pylint"
|
||||
],
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "zsh",
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.languageServer": "Default",
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
|
||||
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
|
||||
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
|
||||
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
|
||||
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
|
||||
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
|
||||
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
|
||||
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
|
||||
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
|
||||
"python.testing.pytestArgs": [
|
||||
"ietf"
|
||||
],
|
||||
|
|
|
@ -245,7 +245,7 @@ async function main () {
|
|||
name: `dt-app-${branch}`,
|
||||
Hostname: `dt-app-${branch}`,
|
||||
Env: [
|
||||
`LETSENCRYPT_HOST=${hostname}`,
|
||||
// `LETSENCRYPT_HOST=${hostname}`,
|
||||
`VIRTUAL_HOST=${hostname}`,
|
||||
`VIRTUAL_PORT=8000`,
|
||||
`PGHOST=dt-db-${branch}`
|
||||
|
|
|
@ -38,8 +38,5 @@ echo "Running Datatracker checks..."
|
|||
echo "Running Datatracker migrations..."
|
||||
/usr/local/bin/python ./ietf/manage.py migrate --settings=settings_local
|
||||
|
||||
echo "Syncing with the rfc-index"
|
||||
./ietf/bin/rfc-editor-index-updates -d 1969-01-01
|
||||
|
||||
echo "Starting Datatracker..."
|
||||
./ietf/manage.py runserver 0.0.0.0:8000 --settings=settings_local
|
||||
|
|
|
@ -634,6 +634,9 @@ class DocumentInfo(models.Model):
|
|||
)
|
||||
except AssertionError:
|
||||
pdf = None
|
||||
except Exception as e:
|
||||
log.log('weasyprint failed:'+str(e))
|
||||
raise
|
||||
if pdf:
|
||||
cache.set(cache_key, pdf, settings.PDFIZER_CACHE_TIME)
|
||||
return pdf
|
||||
|
@ -649,7 +652,7 @@ class DocumentInfo(models.Model):
|
|||
source__states__slug="active",
|
||||
)
|
||||
| models.Q(source__type__slug="rfc")
|
||||
)
|
||||
).distinct()
|
||||
|
||||
def referenced_by_rfcs(self):
|
||||
"""Get refs to this doc from RFCs"""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2012-2020, All Rights Reserved
|
||||
# Copyright The IETF Trust 2012-2023, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
@ -31,6 +31,8 @@ from django.utils.text import slugify
|
|||
|
||||
from tastypie.test import ResourceTestCaseMixin
|
||||
|
||||
from weasyprint.urls import URLFetchingError
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import ( Document, DocRelationshipName, RelatedDocument, State,
|
||||
|
@ -40,7 +42,7 @@ from ietf.doc.factories import ( DocumentFactory, DocEventFactory, CharterFactor
|
|||
ConflictReviewFactory, WgDraftFactory, IndividualDraftFactory, WgRfcFactory,
|
||||
IndividualRfcFactory, StateDocEventFactory, BallotPositionDocEventFactory,
|
||||
BallotDocEventFactory, DocumentAuthorFactory, NewRevisionDocEventFactory,
|
||||
StatusChangeFactory, DocExtResourceFactory, RgDraftFactory)
|
||||
StatusChangeFactory, DocExtResourceFactory, RgDraftFactory, BcpFactory)
|
||||
from ietf.doc.forms import NotifyForm
|
||||
from ietf.doc.fields import SearchableDocumentsField
|
||||
from ietf.doc.utils import create_ballot_if_not_open, uppercase_std_abbreviated_name
|
||||
|
@ -156,6 +158,23 @@ class SearchTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r, draft.title)
|
||||
|
||||
def test_search_became_rfc(self):
|
||||
draft = WgDraftFactory()
|
||||
rfc = WgRfcFactory()
|
||||
draft.set_state(State.objects.get(type="draft", slug="rfc"))
|
||||
draft.relateddocument_set.create(relationship_id="became_rfc", target=rfc)
|
||||
base_url = urlreverse('ietf.doc.views_search.search')
|
||||
|
||||
# find by RFC
|
||||
r = self.client.get(base_url + f"?rfcs=on&name={rfc.name}")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r, rfc.title)
|
||||
|
||||
# find by draft
|
||||
r = self.client.get(base_url + f"?activedrafts=on&rfcs=on&name={draft.name}")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r, rfc.title)
|
||||
|
||||
def test_search_for_name(self):
|
||||
draft = WgDraftFactory(name='draft-ietf-mars-test',group=GroupFactory(acronym='mars',parent=Group.objects.get(acronym='farfut')),authors=[PersonFactory()],ad=PersonFactory())
|
||||
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="pub-req"))
|
||||
|
@ -1948,6 +1967,12 @@ class DocTestCase(TestCase):
|
|||
|
||||
@override_settings(RFC_EDITOR_INFO_BASE_URL='https://www.rfc-editor.ietf.org/info/')
|
||||
def test_document_bibtex(self):
|
||||
|
||||
for factory in [CharterFactory, BcpFactory, StatusChangeFactory, ConflictReviewFactory]: # Should be extended to all other doc types
|
||||
doc = factory()
|
||||
url = urlreverse("ietf.doc.views_doc.document_bibtex", kwargs=dict(name=doc.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
rfc = WgRfcFactory.create(
|
||||
time=datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo(settings.TIME_ZONE))
|
||||
)
|
||||
|
@ -2844,6 +2869,12 @@ class PdfizedTests(TestCase):
|
|||
self.should_succeed(dict(name=draft.name,rev=f'{r:02d}',ext=ext))
|
||||
self.should_404(dict(name=draft.name,rev='02'))
|
||||
|
||||
with mock.patch('ietf.doc.models.DocumentInfo.pdfized', side_effect=URLFetchingError):
|
||||
url = urlreverse(self.view, kwargs=dict(name=rfc.name))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r, "Error while rendering PDF")
|
||||
|
||||
class NotifyValidationTests(TestCase):
|
||||
def test_notify_validation(self):
|
||||
valid_values = [
|
||||
|
|
|
@ -1483,6 +1483,42 @@ class SubmitToIesgTests(TestCase):
|
|||
self.assertTrue("aread@" in outbox[-1]['To'])
|
||||
self.assertTrue("iesg-secretary@" in outbox[-1]['Cc'])
|
||||
|
||||
def test_confirm_submission_no_doc_ad(self):
|
||||
url = urlreverse('ietf.doc.views_draft.to_iesg', kwargs=dict(name=self.docname))
|
||||
self.client.login(username="marschairman", password="marschairman+password")
|
||||
|
||||
doc = Document.objects.get(name=self.docname)
|
||||
RoleFactory(name_id='ad', group=doc.group, person=doc.ad)
|
||||
e = DocEvent(type="changed_document", by=doc.ad, doc=doc, rev=doc.rev, desc="Remove doc AD")
|
||||
e.save()
|
||||
doc.ad = None
|
||||
doc.save_with_history([e])
|
||||
|
||||
docevents_pre = set(doc.docevent_set.all())
|
||||
mailbox_before = len(outbox)
|
||||
|
||||
r = self.client.post(url, dict(confirm="1"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
doc = Document.objects.get(name=self.docname)
|
||||
self.assertTrue(doc.get_state('draft-iesg').slug=='pub-req')
|
||||
self.assertTrue(doc.get_state('draft-stream-ietf').slug=='sub-pub')
|
||||
|
||||
self.assertCountEqual(doc.action_holders.all(), [doc.ad])
|
||||
|
||||
new_docevents = set(doc.docevent_set.all()) - docevents_pre
|
||||
self.assertEqual(len(new_docevents), 5)
|
||||
new_docevent_type_count = Counter([e.type for e in new_docevents])
|
||||
self.assertEqual(new_docevent_type_count['changed_state'],2)
|
||||
self.assertEqual(new_docevent_type_count['started_iesg_process'],1)
|
||||
self.assertEqual(new_docevent_type_count['changed_action_holders'], 1)
|
||||
self.assertEqual(new_docevent_type_count['changed_document'], 1)
|
||||
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("Publication has been requested" in outbox[-1]['Subject'])
|
||||
self.assertTrue("aread@" in outbox[-1]['To'])
|
||||
self.assertTrue("iesg-secretary@" in outbox[-1]['Cc'])
|
||||
|
||||
|
||||
|
||||
class RequestPublicationTests(TestCase):
|
||||
|
|
|
@ -380,6 +380,25 @@ class ReviewTests(TestCase):
|
|||
reviewer_label = q("option[value=\"{}\"]".format(reviewer_email.address)).text().lower()
|
||||
self.assertIn("rejected review of document before", reviewer_label)
|
||||
|
||||
def test_assign_reviewer_after_withdraw(self):
|
||||
doc = WgDraftFactory()
|
||||
review_team = ReviewTeamFactory()
|
||||
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer')
|
||||
RoleFactory(group=review_team,person__user__username='reviewsecretary',name_id='secr')
|
||||
review_req = ReviewRequestFactory(team=review_team,doc=doc)
|
||||
reviewer = rev_role.person.email_set.first()
|
||||
ReviewAssignmentFactory(review_request=review_req, state_id='withdrawn', reviewer=reviewer)
|
||||
req_url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
|
||||
assign_url = urlreverse('ietf.doc.views_review.assign_reviewer', kwargs={ "name": doc.name, "request_id": review_req.pk })
|
||||
|
||||
login_testing_unauthorized(self, "reviewsecretary", assign_url)
|
||||
r = self.client.post(assign_url, { "action": "assign", "reviewer": reviewer.pk })
|
||||
self.assertRedirects(r, req_url)
|
||||
review_req = reload_db_objects(review_req)
|
||||
assignment = review_req.reviewassignment_set.last()
|
||||
self.assertEqual(assignment.state, ReviewAssignmentStateName.objects.get(slug='assigned'))
|
||||
self.assertEqual(review_req.state, ReviewRequestStateName.objects.get(slug='assigned'))
|
||||
|
||||
def test_previously_reviewed_replaced_doc(self):
|
||||
review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
|
||||
rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',person__name='Some Reviewer',name_id='reviewer')
|
||||
|
|
|
@ -51,7 +51,6 @@ from django.conf import settings
|
|||
from django import forms
|
||||
from django.contrib.staticfiles import finders
|
||||
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import ( Document, DocHistory, DocEvent, BallotDocEvent, BallotType,
|
||||
|
@ -1064,7 +1063,10 @@ def document_pdfized(request, name, rev=None, ext=None):
|
|||
if not os.path.exists(doc.get_file_name()):
|
||||
raise Http404("File not found: %s" % doc.get_file_name())
|
||||
|
||||
pdf = doc.pdfized()
|
||||
try:
|
||||
pdf = doc.pdfized()
|
||||
except Exception:
|
||||
return render(request, "doc/weasyprint_failed.html")
|
||||
if pdf:
|
||||
return HttpResponse(pdf,content_type='application/pdf')
|
||||
else:
|
||||
|
@ -1262,6 +1264,9 @@ def document_bibtex(request, name, rev=None):
|
|||
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
|
||||
if doc.type_id not in ["rfc", "draft"]:
|
||||
raise Http404()
|
||||
|
||||
doi = None
|
||||
draft_became_rfc = None
|
||||
replaced_by = None
|
||||
|
@ -2185,13 +2190,31 @@ def idnits2_state(request, name, rev=None):
|
|||
if doc.type_id == "rfc":
|
||||
draft = doc.came_from_draft()
|
||||
if draft:
|
||||
zero_revision = NewRevisionDocEvent.objects.filter(doc=draft,rev='00').first()
|
||||
zero_revision = NewRevisionDocEvent.objects.filter(
|
||||
doc=draft, rev="00"
|
||||
).first()
|
||||
else:
|
||||
zero_revision = NewRevisionDocEvent.objects.filter(doc=doc,rev='00').first()
|
||||
zero_revision = NewRevisionDocEvent.objects.filter(doc=doc, rev="00").first()
|
||||
if zero_revision:
|
||||
doc.created = zero_revision.time
|
||||
else:
|
||||
doc.created = doc.docevent_set.order_by('-time').first().time
|
||||
if doc.type_id == "draft":
|
||||
if doc.became_rfc():
|
||||
interesting_event = (
|
||||
doc.became_rfc()
|
||||
.docevent_set.filter(type="published_rfc")
|
||||
.order_by("-time")
|
||||
.first()
|
||||
)
|
||||
else:
|
||||
interesting_event = doc.docevent_set.order_by(
|
||||
"-time"
|
||||
).first() # Is taking the most _recent_ instead of the oldest event correct?
|
||||
else: # doc.type_id == "rfc"
|
||||
interesting_event = (
|
||||
doc.docevent_set.filter(type="published_rfc").order_by("-time").first()
|
||||
)
|
||||
doc.created = interesting_event.time
|
||||
if doc.std_level:
|
||||
doc.deststatus = doc.std_level.name
|
||||
elif doc.intended_std_level:
|
||||
|
@ -2199,8 +2222,16 @@ def idnits2_state(request, name, rev=None):
|
|||
else:
|
||||
text = doc.text()
|
||||
if text:
|
||||
parsed_draft = PlaintextDraft(text=doc.text(), source=name, name_from_source=False)
|
||||
parsed_draft = PlaintextDraft(
|
||||
text=doc.text(), source=name, name_from_source=False
|
||||
)
|
||||
doc.deststatus = parsed_draft.get_status()
|
||||
else:
|
||||
doc.deststatus="Unknown"
|
||||
return render(request, 'doc/idnits2-state.txt', context={'doc':doc}, content_type='text/plain;charset=utf-8')
|
||||
doc.deststatus = "Unknown"
|
||||
return render(
|
||||
request,
|
||||
"doc/idnits2-state.txt",
|
||||
context={"doc": doc},
|
||||
content_type="text/plain;charset=utf-8",
|
||||
)
|
||||
|
||||
|
|
|
@ -560,22 +560,19 @@ def to_iesg(request,name):
|
|||
if request.method == 'POST':
|
||||
|
||||
if request.POST.get("confirm", ""):
|
||||
|
||||
by = request.user.person
|
||||
|
||||
events = []
|
||||
|
||||
changes = []
|
||||
def doc_event(type, by, doc, desc):
|
||||
return DocEvent.objects.create(type=type, by=by, doc=doc, rev=doc.rev, desc=desc)
|
||||
|
||||
if doc.get_state_slug("draft-iesg") == "idexists":
|
||||
e = DocEvent()
|
||||
e.type = "started_iesg_process"
|
||||
e.by = by
|
||||
e.doc = doc
|
||||
e.rev = doc.rev
|
||||
e.desc = "Document is now in IESG state <b>%s</b>" % target_state['iesg'].name
|
||||
e.save()
|
||||
events.append(e)
|
||||
events.append(doc_event("started_iesg_process", by, doc, f"Document is now in IESG state <b>{target_state['iesg'].name}</b>"))
|
||||
|
||||
# do this first, so AD becomes action holder
|
||||
if not doc.ad == ad :
|
||||
doc.ad = ad
|
||||
events.append(doc_event("changed_document", by, doc, f"Responsible AD changed to {doc.ad}"))
|
||||
|
||||
for state_type in ['draft-iesg','draft-stream-ietf']:
|
||||
prev_state=doc.get_state(state_type)
|
||||
|
@ -587,25 +584,14 @@ def to_iesg(request,name):
|
|||
events.append(e)
|
||||
events.append(add_state_change_event(doc=doc,by=by,prev_state=prev_state,new_state=new_state))
|
||||
|
||||
if not doc.ad == ad :
|
||||
doc.ad = ad
|
||||
changes.append("Responsible AD changed to %s" % doc.ad)
|
||||
|
||||
if not doc.notify == notify :
|
||||
doc.notify = notify
|
||||
changes.append("State Change Notice email list changed to %s" % doc.notify)
|
||||
events.append(doc_event("changed_document", by, doc, f"State Change Notice email list changed to {doc.notify}"))
|
||||
|
||||
# Get the last available writeup
|
||||
previous_writeup = doc.latest_event(WriteupDocEvent,type="changed_protocol_writeup")
|
||||
if previous_writeup != None:
|
||||
changes.append(previous_writeup.text)
|
||||
|
||||
for c in changes:
|
||||
e = DocEvent(doc=doc, rev=doc.rev, by=by)
|
||||
e.desc = c
|
||||
e.type = "changed_document"
|
||||
e.save()
|
||||
events.append(e)
|
||||
events.append(doc_event("changed_document", by, doc, previous_writeup.text))
|
||||
|
||||
doc.save_with_history(events)
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Copyright The IETF Trust 2013-2023, All Rights Reserved
|
||||
|
||||
import debug # pyflakes: ignore
|
||||
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.http import Http404
|
||||
|
||||
|
@ -18,6 +20,7 @@ def state_help(request, type=None):
|
|||
"draft-stream-irtf": ("draft-stream-irtf", "IRTF Stream States for Internet-Drafts"),
|
||||
"draft-stream-ise": ("draft-stream-ise", "ISE Stream States for Internet-Drafts"),
|
||||
"draft-stream-iab": ("draft-stream-iab", "IAB Stream States for Internet-Drafts"),
|
||||
"draft-stream-editorial": ("draft-stream-editorial", "Editorial Stream States for Internet-Drafts"),
|
||||
"charter": ("charter", "Charter States"),
|
||||
"conflict-review": ("conflrev", "Conflict Review States"),
|
||||
"status-change": ("statchg", "RFC Status Change States"),
|
||||
|
|
|
@ -211,6 +211,9 @@ def retrieve_search_results(form, all_types=False):
|
|||
Q(targets_related__source__title__icontains=singlespace, targets_related__relationship_id="contains"),
|
||||
])
|
||||
|
||||
if query["rfcs"]:
|
||||
queries.extend([Q(targets_related__source__name__icontains=look_for, targets_related__relationship_id="became_rfc")])
|
||||
|
||||
combined_query = reduce(operator.or_, queries)
|
||||
docs = docs.filter(combined_query).distinct()
|
||||
|
||||
|
@ -468,11 +471,11 @@ def ad_workload(request):
|
|||
state = doc_state(doc)
|
||||
|
||||
state_events = doc.docevent_set.filter(
|
||||
Q(type="started_iesg_process")
|
||||
| Q(type="changed_state")
|
||||
| Q(type="published_rfc")
|
||||
| Q(type="closed_ballot"),
|
||||
).order_by("-time")
|
||||
type__in=["started_iesg_process", "changed_state", "closed_ballot"]
|
||||
)
|
||||
if doc.became_rfc():
|
||||
state_events = state_events | doc.became_rfc().docevent_set.filter(type="published_rfc")
|
||||
state_events = state_events.order_by("-time")
|
||||
|
||||
# compute state history for drafts
|
||||
last = now
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2009-2020, All Rights Reserved
|
||||
# Copyright The IETF Trust 2009-2023, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import io
|
||||
|
@ -6106,21 +6106,21 @@ class MaterialsTests(TestCase):
|
|||
|
||||
test_file = BytesIO(b'this is some text for a test')
|
||||
test_file.name = "not_really.json"
|
||||
r = self.client.post(url,dict(file=test_file))
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(q('form .is-invalid'))
|
||||
|
||||
test_file = BytesIO(b'this is some text for a test'*1510000)
|
||||
test_file.name = "not_really.pdf"
|
||||
r = self.client.post(url,dict(file=test_file))
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(q('form .is-invalid'))
|
||||
|
||||
test_file = BytesIO(b'<html><frameset><frame src="foo.html"></frame><frame src="bar.html"></frame></frameset></html>')
|
||||
test_file.name = "not_really.html"
|
||||
r = self.client.post(url,dict(file=test_file))
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(q('form .is-invalid'))
|
||||
|
@ -6128,7 +6128,7 @@ class MaterialsTests(TestCase):
|
|||
# Test html sanitization
|
||||
test_file = BytesIO(b'<html><head><title>Title</title></head><body><h1>Title</h1><section>Some text</section></body></html>')
|
||||
test_file.name = "some.html"
|
||||
r = self.client.post(url,dict(file=test_file))
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document
|
||||
self.assertEqual(doc.rev,'00')
|
||||
|
@ -6140,7 +6140,7 @@ class MaterialsTests(TestCase):
|
|||
# txt upload
|
||||
test_file = BytesIO(b'This is some text for a test, with the word\nvirtual at the beginning of a line.')
|
||||
test_file.name = "some.txt"
|
||||
r = self.client.post(url,dict(file=test_file,apply_to_all=False))
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file,apply_to_all=False))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document
|
||||
self.assertEqual(doc.rev,'01')
|
||||
|
@ -6152,7 +6152,7 @@ class MaterialsTests(TestCase):
|
|||
self.assertIn('Revise', str(q("Title")))
|
||||
test_file = BytesIO(b'this is some different text for a test')
|
||||
test_file.name = "also_some.txt"
|
||||
r = self.client.post(url,dict(file=test_file,apply_to_all=True))
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file,apply_to_all=True))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = Document.objects.get(pk=doc.pk)
|
||||
self.assertEqual(doc.rev,'02')
|
||||
|
@ -6161,7 +6161,7 @@ class MaterialsTests(TestCase):
|
|||
# Test bad encoding
|
||||
test_file = BytesIO('<html><h1>Title</h1><section>Some\x93text</section></html>'.encode('latin1'))
|
||||
test_file.name = "some.html"
|
||||
r = self.client.post(url,dict(file=test_file))
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file))
|
||||
self.assertContains(r, 'Could not identify the file encoding')
|
||||
doc = Document.objects.get(pk=doc.pk)
|
||||
self.assertEqual(doc.rev,'02')
|
||||
|
@ -6191,7 +6191,7 @@ class MaterialsTests(TestCase):
|
|||
|
||||
test_file = BytesIO(b'this is some text for a test')
|
||||
test_file.name = "not_really.txt"
|
||||
r = self.client.post(url,dict(file=test_file,apply_to_all=False))
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file,apply_to_all=False))
|
||||
self.assertEqual(r.status_code, 410)
|
||||
|
||||
@override_settings(MEETING_MATERIALS_SERVE_LOCALLY=True)
|
||||
|
@ -6211,7 +6211,7 @@ class MaterialsTests(TestCase):
|
|||
self.assertFalse(session.sessionpresentation_set.filter(document__type_id=doctype))
|
||||
test_file = BytesIO(b'this is some text for a test')
|
||||
test_file.name = "not_really.txt"
|
||||
r = self.client.post(url,dict(file=test_file))
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document
|
||||
self.assertEqual(doc.rev,'00')
|
||||
|
@ -6223,6 +6223,46 @@ class MaterialsTests(TestCase):
|
|||
self.requests_mock.get(f'{session.notes_url()}/info', text=json.dumps({'title': 'title', 'updatetime': '2021-12-01T17:11:00z'}))
|
||||
self.crawl_materials(url=url, top=top)
|
||||
|
||||
def test_enter_agenda(self):
|
||||
session = SessionFactory(meeting__type_id='ietf')
|
||||
url = urlreverse('ietf.meeting.views.upload_session_agenda',kwargs={'num':session.meeting.number,'session_id':session.id})
|
||||
redirect_url = urlreverse('ietf.meeting.views.session_details', kwargs={'num':session.meeting.number,'acronym':session.group.acronym})
|
||||
login_testing_unauthorized(self,"secretary",url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertIn('Upload', str(q("Title")))
|
||||
self.assertFalse(session.sessionpresentation_set.exists())
|
||||
|
||||
test_text = 'Enter agenda from scratch'
|
||||
r = self.client.post(url,dict(submission_method="enter",content=test_text))
|
||||
self.assertRedirects(r, redirect_url)
|
||||
doc = session.sessionpresentation_set.filter(document__type_id='agenda').first().document
|
||||
self.assertEqual(doc.rev,'00')
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertIn('Revise', str(q("Title")))
|
||||
|
||||
test_file = BytesIO(b'Upload after enter')
|
||||
test_file.name = "some.txt"
|
||||
r = self.client.post(url,dict(submission_method="upload",file=test_file))
|
||||
self.assertRedirects(r, redirect_url)
|
||||
doc = Document.objects.get(pk=doc.pk)
|
||||
self.assertEqual(doc.rev,'01')
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertIn('Revise', str(q("Title")))
|
||||
|
||||
test_text = 'Enter after upload'
|
||||
r = self.client.post(url,dict(submission_method="enter",content=test_text))
|
||||
self.assertRedirects(r, redirect_url)
|
||||
doc = Document.objects.get(pk=doc.pk)
|
||||
self.assertEqual(doc.rev,'02')
|
||||
|
||||
def test_upload_slides(self):
|
||||
|
||||
session1 = SessionFactory(meeting__type_id='ietf')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2007-2022, All Rights Reserved
|
||||
# Copyright The IETF Trust 2007-2023, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
@ -2662,6 +2662,40 @@ def upload_session_minutes(request, session_id, num):
|
|||
})
|
||||
|
||||
|
||||
class UploadOrEnterAgendaForm(UploadAgendaForm):
|
||||
ACTIONS = [
|
||||
("upload", "Upload agenda"),
|
||||
("enter", "Enter agenda"),
|
||||
]
|
||||
submission_method = forms.ChoiceField(choices=ACTIONS, widget=forms.RadioSelect)
|
||||
|
||||
content = forms.CharField(widget=forms.Textarea, required=False, strip=False, label="Agenda text")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["file"].required=False
|
||||
self.order_fields(["submission_method", "file", "content"])
|
||||
|
||||
def clean_content(self):
|
||||
return self.cleaned_data["content"].replace("\r", "")
|
||||
|
||||
def clean_file(self):
|
||||
submission_method = self.cleaned_data.get("submission_method")
|
||||
if submission_method == "upload":
|
||||
return super().clean_file()
|
||||
return None
|
||||
|
||||
def clean(self):
|
||||
def require_field(f):
|
||||
if not self.cleaned_data.get(f):
|
||||
self.add_error(f, ValidationError("You must fill in this field."))
|
||||
|
||||
submission_method = self.cleaned_data.get("submission_method")
|
||||
if submission_method == "upload":
|
||||
require_field("file")
|
||||
elif submission_method == "enter":
|
||||
require_field("content")
|
||||
|
||||
def upload_session_agenda(request, session_id, num):
|
||||
# num is redundant, but we're dragging it along an artifact of where we are in the current URL structure
|
||||
session = get_object_or_404(Session,pk=session_id)
|
||||
|
@ -2680,10 +2714,23 @@ def upload_session_agenda(request, session_id, num):
|
|||
agenda_sp = session.sessionpresentation_set.filter(document__type='agenda').first()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = UploadAgendaForm(show_apply_to_all_checkbox,request.POST,request.FILES)
|
||||
form = UploadOrEnterAgendaForm(show_apply_to_all_checkbox,request.POST,request.FILES)
|
||||
if form.is_valid():
|
||||
file = request.FILES['file']
|
||||
_, ext = os.path.splitext(file.name)
|
||||
submission_method = form.cleaned_data['submission_method']
|
||||
if submission_method == "upload":
|
||||
file = request.FILES['file']
|
||||
_, ext = os.path.splitext(file.name)
|
||||
else:
|
||||
if agenda_sp:
|
||||
doc = agenda_sp.document
|
||||
_, ext = os.path.splitext(doc.uploaded_filename)
|
||||
else:
|
||||
ext = ".md"
|
||||
fd, name = tempfile.mkstemp(suffix=ext, text=True)
|
||||
os.close(fd)
|
||||
with open(name, "w") as file:
|
||||
file.write(form.cleaned_data['content'])
|
||||
file = open(name, "rb")
|
||||
apply_to_all = session.type.slug == 'regular'
|
||||
if show_apply_to_all_checkbox:
|
||||
apply_to_all = form.cleaned_data['apply_to_all']
|
||||
|
@ -2738,7 +2785,11 @@ def upload_session_agenda(request, session_id, num):
|
|||
doc.uploaded_filename = filename
|
||||
e = NewRevisionDocEvent.objects.create(doc=doc,by=request.user.person,type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev)
|
||||
# The way this function builds the filename it will never trigger the file delete in handle_file_upload.
|
||||
save_error = handle_upload_file(file, filename, session.meeting, 'agenda', request=request, encoding=form.file_encoding[file.name])
|
||||
try:
|
||||
encoding=form.file_encoding[file.name]
|
||||
except AttributeError:
|
||||
encoding=None
|
||||
save_error = handle_upload_file(file, filename, session.meeting, 'agenda', request=request, encoding=encoding)
|
||||
if save_error:
|
||||
form.add_error(None, save_error)
|
||||
else:
|
||||
|
@ -2746,7 +2797,11 @@ def upload_session_agenda(request, session_id, num):
|
|||
messages.success(request, f'Successfully uploaded agenda as revision {doc.rev}.')
|
||||
return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym)
|
||||
else:
|
||||
form = UploadAgendaForm(show_apply_to_all_checkbox, initial={'apply_to_all':session.type_id=='regular'})
|
||||
initial={'apply_to_all':session.type_id=='regular', 'submission_method':'upload'}
|
||||
if agenda_sp:
|
||||
doc = agenda_sp.document
|
||||
initial['content'] = doc.text()
|
||||
form = UploadOrEnterAgendaForm(show_apply_to_all_checkbox, initial=initial)
|
||||
|
||||
return render(request, "meeting/upload_session_agenda.html",
|
||||
{'session': session,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2016-2020, All Rights Reserved
|
||||
# Copyright The IETF Trust 2016-2023, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
@ -392,7 +392,9 @@ def assign_review_request_to_reviewer(request, review_req, reviewer, add_skip=Fa
|
|||
# cannot reference reviewassignment_set relation until pk exists
|
||||
if review_req.pk is not None:
|
||||
reviewassignment_set = review_req.reviewassignment_set.filter(reviewer=reviewer)
|
||||
if reviewassignment_set.exists() and not reviewassignment_set.filter(state_id='rejected').exists():
|
||||
if (reviewassignment_set.exists() and not
|
||||
(reviewassignment_set.filter(state_id='rejected').exists() or
|
||||
reviewassignment_set.filter(state_id='withdrawn').exists())):
|
||||
return
|
||||
|
||||
# Note that assigning a review no longer unassigns other reviews
|
||||
|
|
28
ietf/static/js/upload-session-agenda.js
Normal file
28
ietf/static/js/upload-session-agenda.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
$(document)
|
||||
.ready(function () {
|
||||
var form = $("form.my-3");
|
||||
|
||||
// review submission selection
|
||||
form.find("[name=submission_method]")
|
||||
.on("click change", function () {
|
||||
var val = form.find("[name=submission_method]:checked")
|
||||
.val();
|
||||
|
||||
var shouldBeVisible = {
|
||||
upload: ['[name="file"]'],
|
||||
enter: ['[name="content"]']
|
||||
};
|
||||
|
||||
for (var v in shouldBeVisible) {
|
||||
for (var i in shouldBeVisible[v]) {
|
||||
var selector = shouldBeVisible[v][i];
|
||||
var row = form.find(selector).parent();
|
||||
if ($.inArray(selector, shouldBeVisible[val]) != -1)
|
||||
row.show();
|
||||
else
|
||||
row.hide();
|
||||
}
|
||||
}
|
||||
})
|
||||
.trigger("change");
|
||||
});
|
17
ietf/templates/doc/weasyprint_failed.html
Normal file
17
ietf/templates/doc/weasyprint_failed.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{# Copyright The IETF Trust 2023, All Rights Reserved #}
|
||||
{% extends "base.html" %}
|
||||
{% load origin %}
|
||||
{% block title %}Error while rendering PDF{% endblock %}
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Error while rendering PDF</h1>
|
||||
<p>
|
||||
An error was encountered while trying to render your document as PDF.
|
||||
In case this was a temporary error, you may want to try again in a
|
||||
little while.
|
||||
</p>
|
||||
<p>
|
||||
A failure report with details about what happened has been sent to the
|
||||
server administrators.
|
||||
</p>
|
||||
{% endblock %}
|
|
@ -16,6 +16,11 @@
|
|||
{% endwith %}
|
||||
{% else %}
|
||||
<div class="badge rounded-pill text-bg-secondary">{{ s.current_status_name }}</div>
|
||||
{% if s.current_status == "canceled" %}
|
||||
{% with timeslot=s.official_timeslotassignment.timeslot %}
|
||||
<span class="text-decoration-line-through text-secondary session-time date me-3" data-start-utc="{{ timeslot.time|utc|date:'Y-m-d' }}" data-end-utc="{{ timeslot.end_time|utc|date:'Y-m-d' }}"></span>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if show_request and s.meeting.type_id == 'ietf' %}
|
||||
{% if can_edit %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{# Copyright The IETF Trust 2015-2023, All Rights Reserved #}
|
||||
{% load origin static django_bootstrap5 tz %}
|
||||
{% block title %}
|
||||
{% if agenda_sp %}
|
||||
|
@ -29,6 +29,9 @@
|
|||
<form enctype="multipart/form-data" method="post" class="my-3">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
<button type="submit" class="btn btn-primary">Upload</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
<script src="{% static 'ietf/js/upload-session-agenda.js' %}"></script>
|
||||
{% endblock %}
|
|
@ -152,6 +152,7 @@
|
|||
"ietf/static/js/timezone.js",
|
||||
"ietf/static/js/upcoming.js",
|
||||
"ietf/static/js/upload-material.js",
|
||||
"ietf/static/js/upload-session-agenda.js",
|
||||
"ietf/static/js/upload_bofreq.js",
|
||||
"ietf/static/js/upload_statement.js",
|
||||
"ietf/static/js/zxcvbn.js"
|
||||
|
|
Loading…
Reference in a new issue