chore: feat/dark-mode <- main (#6103)
* chore: Remove unused "rendertest" stuff (#6015) * fix: restore ability to create status change documents (#5963) * fix: restore ability to create status change documents Fixes #5962 * chore: address review comment * fix: Provide human-friendly status in submission status API response (#6011) Co-authored-by: nectostr <bastinda96@gmail.com> * fix: Make name/email lookups case-insensitive (#5972) (#6007) * fix: Make name/email lookups case-insensitive (#5972) Use icontains so that looking up name or email is case insensitive Added a test Fixes: 5972 * fix: Use __iexact not __icontains * fix: Clarify no-action-needed (#5918) (#6020) When a draft is submitted for manual processing, clarify that no action is needed; the Secretariat has the next steps. Fixes: #5918 * fix: Fix menu hover issue (#6019) * fix: Fix menu hover issue Fixes #5702 * Fix leftmenu hover issue * fix: Server error from api_get_session_materials() (#6025) Fixes #5877 * fix: Clarify Questionnaire label (#4688) (#6017) When filtering nominees, `Questionnaire` implies `Accepted == yes` so fix the dropdown test tosay that. Fixes: #4688 * chore: Merge from @martinthomson's rfc-txt-html (#6023) * fix:no history entry when changing RFC Editor note for doc (#6021) * fix:no history entry when changing RFC Editor note for doc * fix:no history entry when changing RFC Editor note for doc --------- Co-authored-by: Priyanka Narkar <priyankanarkar@dhcp-91f8.meeting.ietf.org> * fix: avoid deprecation warning on view_list() for objs without CommunityList Fixes #5942 * fix: return 404 for non-existing revisions (#6014) * fix: return 404 for non-existing revisions Links to non-existing revisions to docs should return 404 * fix: change rfc/rev and search behaviour * refactor: fix tab level * fix: return 404 for rfc revision for bibtex * fix: provide date for revisions in bibtex output (#6029) * fix: provide date for revisions in bibtex output * refactor: change walrus to if's * fix: specify particular revision for events * fix: review refactoring issue fixes #5447 * fix: Remove automatically suggested document for document that is already has review request (fixes #3211) (#5425) * Added check that if there is already review request for the document in question, ignore the automatic suggestion for that document. Fixes #3211. * fix: dont block on open requests for a previous version. Add tests --------- Co-authored-by: Nicolas Giard <github@ngpixel.com> Co-authored-by: Robert Sparks <rjsparks@nostrum.com> * feat: IAB statements (#5940) * feat: support iab and iesg statements. Import iab statements. (#5895) * feat: infrastructure for statements doctype * chore: basic test framework * feat: basic statement document view * feat: show replaced statements * chore: black * fix: state help for statements * fix: cleanout non-relevant email expansions * feat: import iab statements, provide group statements tab * fix: guard against running import twice * feat: build redirect csv for iab statements * fix: set document state on import * feat: show published date on main doc view * feat: handle pdf statements * feat: create new and update statements * chore: copyright block updates * chore: remove flakes * chore: black * feat: add edit/new buttons for the secretariat * fix: address PR #5895 review comments * fix: pin pydantic until inflect catches up (#5901) (#5902) * chore: re-un-pin pydantic * feat: include submitter in email about submitted slides (#6033) * feat: include submitter in email about submitted slides fixes #6031 * chore: remove unintended whitespace change * chore(dev): update .vscode/settings.json with new taskExplorer settings * fix: Add editorial stream to proceedings (#6027) * fix: Add editorial stream to proceedings Fixes #5717 * fix: Move editorial stream after the irtf in proceedings * fix: Add editorial stream to meeting materials (#6047) Fixes #6042 * fix: Shows requested reviews for doc fixes (#6022) * Fix: Shows requested reviews for doc * Changed template includes to only give required variables to them. * feat: allow openId to choose an unactive email if there are none active (#6041) * feat: allow openId to choose an unactive email if there are no active ones * chore: correct typo * chore: rename unactive to inactive * fix: Make review table more responsive (#6053) * fix: Improve layout of review table * Progress * Progress * Final changes * Fix tests * Remove fluff * Undo commits * ci: add --validate-html-harder to tests * ci: add --validate-html-harder to build.yml workflow * fix: Set colspan to actual number of columns (#6069) * fix: Clean up view_feedback_pending (#6070) - Remove "Unclassified" column header, which caused misalignment in the table body. - Show the message author - previously displayed as `(None)`. * docs: Update LICENSE year * fix: Remove IESG state edit button when state is 'dead' (#6051) (#6065) * fix: Correctly order "last call requested" column in the IESG dashboard (#6079) * ci: update dev sandbox init script to start memcached * feat: Reclassify nomcom feedback (#6002) * fix: Clean up view_feedback_pending - Remove "Unclassified" column header, which caused misalignment in the table body. - Show the message author - previously displayed as `(None)`. * feat: Reclassify nomcom feedback (#4669) - There's a new `Chair/Advisor Tasks` menu item `Reclassify feedback`. - I overloaded `view_feedback*` URLs with a `?reclassify` parameter. - This adds a checkbox to each feedback message, and a `Reclassify` button at the bottom of each feedback page. - "Reclassifying" basically de-classifies the feedback, and punts it back to the "Pending emails" view for reclassification. - If a feedback has been applied to multiple nominees, declassifying it from one nominee removes it from all. * fix: Remove unused local variables * fix: Fix some missing and mis-nested html * test: Add tests for reclassifying feedback * refactor: Substantial redesign of feedback reclassification - Break out reclassify_feedback* as their own URLs and views, and revert changes to view_feedback*.html. - Replace checkboxes with a Reclassify button on each message. * fix: Remember to clear the feedback associations when reclassifying * feat: Add an 'Overcome by events' feedback type * refactor: When invoking reclassification from a view-feedback page, load the corresponding reclassify-feedback page * fix: De-conflict migration with 0004_statements Also change the coding style to match, and add a reverse migration. * fix: Fix a test case to account for new feedback type * fix: 842e730 broke the Back button * refactor: Reclassify feedback directly instead of putting it back in the work queue * fix: Adjust tests to new workflow * refactor: Further refine reclassification to avoid redirects * refactor: Impose a FeedbackTypeName ordering Also add FeedbackTypeName.legend field, rather than synthesizing it every time we classify or reclassify feedback. In the reclassification forms, only show the relevant feedback types. * refactor: Merge reclassify_feedback_* back into view_feedback_* This means the "Reclassify" button is always present, but eliminates some complexity. * refactor: Add filter(used=True) on FeedbackTypeName querysets * refactor: Add the new FeedbackTypeName to the reclassification success message * fix: Secure reclassification against rogue nomcom members * fix: Print decoded key and fully clean up test nomcom (#6094) * fix: Delete Person records when deleting a test nomcom * fix: Decode test nomcom private key before printing * test: Use correct time zone for test_statement_doc_view (#6064) * chore(deps): update all npm dependencies for playwright (#6061) Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> * chore(deps): update all npm dependencies for dev/diff (#6062) Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> * chore(deps): update all npm dependencies for dev/coverage-action (#6063) Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> * fix: Hash cache key for default memcached cache (#6089) * feat: Show docs that an AD hasn't balloted on that need ballots to progress (#6075) * fix(doc): Unify help texts for document states (#6060) * Fix IESG State help text link (only) * Intermediate checkpoint * Correct URL filtering of state descriptions * Unify help texts for document states * Remove redundant load static from template --------- Co-authored-by: Robert Sparks <rjsparks@nostrum.com> * ci: fix sandbox start.sh memcached user * fix: refactor how settings handles cache definitions (#6099) * fix: refactor how settings handles cache definitions * chore: more english-speaker readable expression * fix: Cast cache key to str before calling encode (#6100) --------- Co-authored-by: Robert Sparks <rjsparks@nostrum.com> Co-authored-by: Liubov Kurafeeva <liubov.kurafeeva@gmail.com> Co-authored-by: nectostr <bastinda96@gmail.com> Co-authored-by: Rich Salz <rsalz@akamai.com> Co-authored-by: PriyankaN <priyanka@amsl.com> Co-authored-by: Priyanka Narkar <priyankanarkar@dhcp-91f8.meeting.ietf.org> Co-authored-by: Ali <alireza83@gmail.com> Co-authored-by: Roman Beltiukov <maybe.hello.world@gmail.com> Co-authored-by: Tero Kivinen <kivinen@iki.fi> Co-authored-by: Nicolas Giard <github@ngpixel.com> Co-authored-by: Kesara Rathnayake <kesara@fq.nz> Co-authored-by: Jennifer Richards <jennifer@staff.ietf.org> Co-authored-by: Paul Selkirk <paul@painless-security.com> Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Co-authored-by: Jim Fenton <fenton@bluepopcorn.net>
This commit is contained in:
parent
1d22e39a52
commit
57f23f5198
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -137,9 +137,9 @@ jobs:
|
||||||
echo "Running tests..."
|
echo "Running tests..."
|
||||||
if [[ "x${{ github.event.inputs.ignoreLowerCoverage }}" == "xtrue" ]]; then
|
if [[ "x${{ github.event.inputs.ignoreLowerCoverage }}" == "xtrue" ]]; then
|
||||||
echo "Lower coverage failures will be ignored."
|
echo "Lower coverage failures will be ignored."
|
||||||
./ietf/manage.py test --settings=settings_postgrestest --ignore-lower-coverage
|
./ietf/manage.py test --validate-html-harder --settings=settings_postgrestest --ignore-lower-coverage
|
||||||
else
|
else
|
||||||
./ietf/manage.py test --settings=settings_postgrestest
|
./ietf/manage.py test --validate-html-harder --settings=settings_postgrestest
|
||||||
fi
|
fi
|
||||||
coverage xml
|
coverage xml
|
||||||
|
|
||||||
|
|
2
.github/workflows/ci-run-tests.yml
vendored
2
.github/workflows/ci-run-tests.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "Running tests..."
|
echo "Running tests..."
|
||||||
./ietf/manage.py test --settings=settings_postgrestest
|
./ietf/manage.py test --validate-html-harder --settings=settings_postgrestest
|
||||||
coverage xml
|
coverage xml
|
||||||
|
|
||||||
- name: Upload Coverage Results to Codecov
|
- name: Upload Coverage Results to Codecov
|
||||||
|
|
115
.vscode/settings.json
vendored
115
.vscode/settings.json
vendored
|
@ -1,59 +1,60 @@
|
||||||
{
|
{
|
||||||
"taskExplorer.exclude": [
|
"taskExplorer.exclude": [
|
||||||
"**/.vscode-test/**",
|
"**/.vscode-test/**",
|
||||||
"**/bin/**",
|
"**/bin/**",
|
||||||
"**/build/**",
|
"**/build/**",
|
||||||
"**/CompiledOutput/**",
|
"**/CompiledOutput/**",
|
||||||
"**/dist/**",
|
"**/dist/**",
|
||||||
"**/doc/**",
|
"**/doc/**",
|
||||||
"**/ext/**",
|
"**/ext/**",
|
||||||
"**/out/**",
|
"**/out/**",
|
||||||
"**/output/**",
|
"**/output/**",
|
||||||
"**/packages/**",
|
"**/packages/**",
|
||||||
"**/release/**",
|
"**/release/**",
|
||||||
"**/releases/**",
|
"**/releases/**",
|
||||||
"**/samples/**",
|
"**/samples/**",
|
||||||
"**/sdks/**",
|
"**/sdks/**",
|
||||||
"**/static/**",
|
"**/static/**",
|
||||||
"**/target/**",
|
"**/target/**",
|
||||||
"**/test/**",
|
"**/test/**",
|
||||||
"**/third_party/**",
|
"**/third_party/**",
|
||||||
"**/vendor/**",
|
"**/vendor/**",
|
||||||
"**/work/**",
|
"**/work/**",
|
||||||
"/workspace/bootstrap/nuget/MyGet.ps1"
|
"/workspace/bootstrap/nuget/MyGet.ps1"
|
||||||
],
|
],
|
||||||
"taskExplorer.enableAnt": false,
|
"taskExplorer.enabledTasks": {
|
||||||
"taskExplorer.enableAppPublisher": false,
|
"ant": false,
|
||||||
"taskExplorer.enablePipenv": false,
|
"bash": false,
|
||||||
"taskExplorer.enableBash": false,
|
"batch": false,
|
||||||
"taskExplorer.enableBatch": false,
|
"composer": false,
|
||||||
"taskExplorer.enableGradle": false,
|
"gradle": false,
|
||||||
"taskExplorer.enableGrunt": false,
|
"grunt": false,
|
||||||
"taskExplorer.enableGulp": false,
|
"gulp": false,
|
||||||
"taskExplorer.enablePerl": false,
|
"make": false,
|
||||||
"taskExplorer.enableMake": false,
|
"maven": false,
|
||||||
"taskExplorer.enableMaven": false,
|
"npm": false,
|
||||||
"taskExplorer.enableNsis": false,
|
"perl": false,
|
||||||
"taskExplorer.enableNpm": false,
|
"pipenv": false,
|
||||||
"taskExplorer.enablePowershell": false,
|
"powershell": false,
|
||||||
"taskExplorer.enablePython": false,
|
"python": false,
|
||||||
"taskExplorer.enableRuby": false,
|
"ruby": false,
|
||||||
"taskExplorer.enableTsc": false,
|
"tsc": false
|
||||||
"taskExplorer.enableWorkspace": true,
|
},
|
||||||
"taskExplorer.enableExplorerView": false,
|
"taskExplorer.enableExplorerView": false,
|
||||||
"taskExplorer.enableSideBar": true,
|
"taskExplorer.enableSideBar": true,
|
||||||
"search.exclude": {
|
"taskExplorer.showLastTasks": false,
|
||||||
"**/.yarn": true,
|
"search.exclude": {
|
||||||
"**/.pnp.*": true
|
"**/.yarn": true,
|
||||||
},
|
"**/.pnp.*": true
|
||||||
"eslint.nodePath": ".yarn/sdks",
|
},
|
||||||
"eslint.validate": [
|
"eslint.nodePath": ".yarn/sdks",
|
||||||
"javascript",
|
"eslint.validate": [
|
||||||
"javascriptreact",
|
"javascript",
|
||||||
"vue"
|
"javascriptreact",
|
||||||
],
|
"vue"
|
||||||
"python.linting.pylintArgs": ["--load-plugins", "pylint_django"],
|
],
|
||||||
"python.testing.pytestEnabled": false,
|
"python.linting.pylintArgs": ["--load-plugins", "pylint_django"],
|
||||||
"python.testing.unittestEnabled": false,
|
"python.testing.pytestEnabled": false,
|
||||||
"python.linting.enabled": true
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.linting.enabled": true
|
||||||
}
|
}
|
||||||
|
|
4
LICENSE
4
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
BSD 3-Clause License
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) 2008-2022, The IETF Trust
|
Copyright (c) 2008-2023, The IETF Trust
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
@ -26,4 +26,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
1535
dev/coverage-action/package-lock.json
generated
1535
dev/coverage-action/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -14,11 +14,11 @@
|
||||||
"luxon": "3.3.0"
|
"luxon": "3.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "8.43.0",
|
"eslint": "8.45.0",
|
||||||
"eslint-config-standard": "17.1.0",
|
"eslint-config-standard": "17.1.0",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
"eslint-plugin-node": "11.1.0",
|
"eslint-plugin-node": "11.1.0",
|
||||||
"eslint-plugin-promise": "6.1.1",
|
"eslint-plugin-promise": "6.1.1",
|
||||||
"npm-check-updates": "16.10.13"
|
"npm-check-updates": "16.10.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,9 @@ if [ -n "$PGHOST" ]; then
|
||||||
psql -U django -h $PGHOST -d datatracker -v ON_ERROR_STOP=1 -c '\x' -c 'ALTER USER django set search_path=datatracker,public;'
|
psql -U django -h $PGHOST -d datatracker -v ON_ERROR_STOP=1 -c '\x' -c 'ALTER USER django set search_path=datatracker,public;'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Starting memcached..."
|
||||||
|
/usr/bin/memcached -d -u root
|
||||||
|
|
||||||
echo "Running Datatracker checks..."
|
echo "Running Datatracker checks..."
|
||||||
./ietf/manage.py check
|
./ietf/manage.py check
|
||||||
|
|
||||||
|
|
14
dev/diff/package-lock.json
generated
14
dev/diff/package-lock.json
generated
|
@ -16,7 +16,7 @@
|
||||||
"listr2": "^6.6.0",
|
"listr2": "^6.6.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"luxon": "^3.3.0",
|
"luxon": "^3.3.0",
|
||||||
"pretty-bytes": "^6.1.0",
|
"pretty-bytes": "^6.1.1",
|
||||||
"tar": "^6.1.15",
|
"tar": "^6.1.15",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
|
@ -981,9 +981,9 @@
|
||||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
|
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
|
||||||
},
|
},
|
||||||
"node_modules/pretty-bytes": {
|
"node_modules/pretty-bytes": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
|
||||||
"integrity": "sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ==",
|
"integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.13.1 || >=16.0.0"
|
"node": "^14.13.1 || >=16.0.0"
|
||||||
},
|
},
|
||||||
|
@ -2032,9 +2032,9 @@
|
||||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
|
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
|
||||||
},
|
},
|
||||||
"pretty-bytes": {
|
"pretty-bytes": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
|
||||||
"integrity": "sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ=="
|
"integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="
|
||||||
},
|
},
|
||||||
"pump": {
|
"pump": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"listr2": "^6.6.0",
|
"listr2": "^6.6.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"luxon": "^3.3.0",
|
"luxon": "^3.3.0",
|
||||||
"pretty-bytes": "^6.1.0",
|
"pretty-bytes": "^6.1.1",
|
||||||
"tar": "^6.1.15",
|
"tar": "^6.1.15",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -40,7 +40,7 @@ INTERNAL_IPS = [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] + ['127.0.0.
|
||||||
# 'ietf.context_processors.sql_debug',
|
# 'ietf.context_processors.sql_debug',
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
DOCUMENT_PATH_PATTERN = '/assets/ietf-ftp/{doc.type_id}/'
|
DOCUMENT_PATH_PATTERN = '/assets/ietfdata/doc/{doc.type_id}/'
|
||||||
INTERNET_DRAFT_PATH = '/assets/ietf-ftp/internet-drafts/'
|
INTERNET_DRAFT_PATH = '/assets/ietf-ftp/internet-drafts/'
|
||||||
RFC_PATH = '/assets/ietf-ftp/rfc/'
|
RFC_PATH = '/assets/ietf-ftp/rfc/'
|
||||||
CHARTER_PATH = '/assets/ietf-ftp/charter/'
|
CHARTER_PATH = '/assets/ietf-ftp/charter/'
|
||||||
|
|
|
@ -725,6 +725,15 @@ class CustomApiTests(TestCase):
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
jsondata = r.json()
|
jsondata = r.json()
|
||||||
self.assertEqual(jsondata['success'], True)
|
self.assertEqual(jsondata['success'], True)
|
||||||
|
|
||||||
|
def test_api_get_session_matherials_no_agenda_meeting_url(self):
|
||||||
|
meeting = MeetingFactory(type_id='ietf')
|
||||||
|
session = SessionFactory(meeting=meeting)
|
||||||
|
url = urlreverse('ietf.meeting.views.api_get_session_materials', kwargs={'session_id': session.pk})
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DirectAuthApiTests(TestCase):
|
class DirectAuthApiTests(TestCase):
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ def view_list(request, username=None):
|
||||||
docs = docs_tracked_by_community_list(clist)
|
docs = docs_tracked_by_community_list(clist)
|
||||||
docs, meta = prepare_document_table(request, docs, request.GET)
|
docs, meta = prepare_document_table(request, docs, request.GET)
|
||||||
|
|
||||||
subscribed = request.user.is_authenticated and EmailSubscription.objects.filter(community_list=clist, email__person__user=request.user)
|
subscribed = request.user.is_authenticated and (EmailSubscription.objects.none() if clist.pk is None else EmailSubscription.objects.filter(community_list=clist, email__person__user=request.user))
|
||||||
|
|
||||||
return render(request, 'community/view_list.html', {
|
return render(request, 'community/view_list.html', {
|
||||||
'clist': clist,
|
'clist': clist,
|
||||||
|
|
|
@ -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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ from ietf.utils.text import xslugify
|
||||||
from ietf.utils.timezone import date_today
|
from ietf.utils.timezone import date_today
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def draft_name_generator(type_id,group,n):
|
def draft_name_generator(type_id,group,n):
|
||||||
return '%s-%s-%s-%s%d'%(
|
return '%s-%s-%s-%s%d'%(
|
||||||
type_id,
|
type_id,
|
||||||
|
@ -577,4 +576,31 @@ class EditorialRfcFactory(RgDraftFactory):
|
||||||
def reset_canonical_name(obj, create, extracted, **kwargs):
|
def reset_canonical_name(obj, create, extracted, **kwargs):
|
||||||
if hasattr(obj, '_canonical_name'):
|
if hasattr(obj, '_canonical_name'):
|
||||||
del obj._canonical_name
|
del obj._canonical_name
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
class StatementFactory(BaseDocumentFactory):
|
||||||
|
type_id = "statement"
|
||||||
|
title = factory.Faker("sentence")
|
||||||
|
group = factory.SubFactory("ietf.group.factories.GroupFactory", acronym="iab")
|
||||||
|
|
||||||
|
name = factory.LazyAttribute(
|
||||||
|
lambda o: "statement-%s-%s" % (xslugify(o.group.acronym), xslugify(o.title))
|
||||||
|
)
|
||||||
|
uploaded_filename = factory.LazyAttribute(lambda o: f"{o.name}-{o.rev}.md")
|
||||||
|
|
||||||
|
published_statement_event = factory.RelatedFactory(
|
||||||
|
"ietf.doc.factories.DocEventFactory",
|
||||||
|
"doc",
|
||||||
|
type="published_statement",
|
||||||
|
time=timezone.now() - datetime.timedelta(days=1),
|
||||||
|
)
|
||||||
|
|
||||||
|
@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="statement", slug="active"))
|
||||||
|
|
320
ietf/doc/management/commands/import_iab_statements.py
Normal file
320
ietf/doc/management/commands/import_iab_statements.py
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
# Copyright The IETF Trust 2023, All Rights Reserved
|
||||||
|
|
||||||
|
import debug # pyflakes:ignore
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import datetime
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from ietf.doc.models import Document, DocAlias, DocEvent, State
|
||||||
|
from ietf.utils.text import xslugify
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Performs a one-time import of IAB statements"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
if Document.objects.filter(type="statement", group__acronym="iab").exists():
|
||||||
|
print("IAB statement documents already exist - exiting")
|
||||||
|
exit(-1)
|
||||||
|
tmpdir = tempfile.mkdtemp()
|
||||||
|
process = subprocess.Popen(
|
||||||
|
["git", "clone", "https://github.com/kesara/iab-scraper.git", tmpdir],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
if not Path(tmpdir).joinpath("iab_minutes", "2022-12-14.md").exists():
|
||||||
|
print("Git clone of the iab-scraper directory did not go as expected")
|
||||||
|
print("stdout:", stdout)
|
||||||
|
print("stderr:", stderr)
|
||||||
|
print(f"Clean up {tmpdir} manually")
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
spreadsheet_rows = load_spreadsheet()
|
||||||
|
with open("iab_statement_redirects.csv", "w") as redirect_file:
|
||||||
|
redirect_writer = csv.writer(redirect_file)
|
||||||
|
for index, (file_fix, date_string, title, url, _) in enumerate(
|
||||||
|
spreadsheet_rows
|
||||||
|
):
|
||||||
|
name = url.split("/")[6].lower()
|
||||||
|
if name.startswith("iabs"):
|
||||||
|
name = name[5:]
|
||||||
|
elif name.startswith("iab"):
|
||||||
|
name = name[4:]
|
||||||
|
if index == 1:
|
||||||
|
name += "-archive" # https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/archive/
|
||||||
|
if index == 100:
|
||||||
|
name = (
|
||||||
|
"2010-" + name
|
||||||
|
) # https://www.iab.org/documents/correspondence-reports-documents/docs2010/iab-statement-on-the-rpki/
|
||||||
|
if index == 152:
|
||||||
|
name = (
|
||||||
|
"2018-" + name
|
||||||
|
) # https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-the-rpki/
|
||||||
|
docname = f"statement-iab-{xslugify(name)}"
|
||||||
|
ext = None
|
||||||
|
base_sourcename = (
|
||||||
|
f"{date_string}-{file_fix}" if file_fix != "" else date_string
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
Path(tmpdir)
|
||||||
|
.joinpath("iab_statements", f"{base_sourcename}.md")
|
||||||
|
.exists()
|
||||||
|
):
|
||||||
|
ext = "md"
|
||||||
|
elif (
|
||||||
|
Path(tmpdir)
|
||||||
|
.joinpath("iab_statements", f"{base_sourcename}.pdf")
|
||||||
|
.exists()
|
||||||
|
):
|
||||||
|
ext = "pdf"
|
||||||
|
if ext is None:
|
||||||
|
debug.show(
|
||||||
|
'f"Could not find {Path(tmpdir).joinpath("iab_statements", f"{base_path}.md")}"'
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
filename = f"{docname}-00.{ext}"
|
||||||
|
# Create Document
|
||||||
|
doc = Document.objects.create(
|
||||||
|
name=docname,
|
||||||
|
type_id="statement",
|
||||||
|
title=title,
|
||||||
|
group_id=7, # The IAB group
|
||||||
|
rev="00",
|
||||||
|
uploaded_filename=filename,
|
||||||
|
)
|
||||||
|
doc.set_state(State.objects.get(type_id="statement", slug="active"))
|
||||||
|
DocAlias.objects.create(name=doc.name).docs.add(doc)
|
||||||
|
year, month, day = [int(part) for part in date_string.split("-")]
|
||||||
|
e1 = DocEvent.objects.create(
|
||||||
|
time=datetime.datetime(
|
||||||
|
year, month, day, 12, 00, tzinfo=datetime.timezone.utc
|
||||||
|
),
|
||||||
|
type="published_statement",
|
||||||
|
doc=doc,
|
||||||
|
rev="00",
|
||||||
|
by_id=1,
|
||||||
|
desc="Statement published (note: The 1200Z time of day is inaccurate - the actual time of day is not known)",
|
||||||
|
)
|
||||||
|
e2 = DocEvent.objects.create(
|
||||||
|
type="added_comment",
|
||||||
|
doc=doc,
|
||||||
|
rev="00",
|
||||||
|
by_id=1, # The "(System)" person
|
||||||
|
desc="Statement moved into datatracker from iab wordpress website",
|
||||||
|
)
|
||||||
|
doc.save_with_history([e1, e2])
|
||||||
|
|
||||||
|
# Put file in place
|
||||||
|
source = Path(tmpdir).joinpath(
|
||||||
|
"iab_statements", f"{base_sourcename}.{ext}"
|
||||||
|
)
|
||||||
|
dest = Path(settings.DOCUMENT_PATH_PATTERN.format(doc=doc)).joinpath(
|
||||||
|
filename
|
||||||
|
)
|
||||||
|
if dest.exists():
|
||||||
|
print(f"WARNING: {dest} already exists - not overwriting it.")
|
||||||
|
else:
|
||||||
|
os.makedirs(dest.parent, exist_ok=True)
|
||||||
|
shutil.copy(source, dest)
|
||||||
|
|
||||||
|
redirect_writer.writerow(
|
||||||
|
[
|
||||||
|
url,
|
||||||
|
f"https://datatracker.ietf.org/doc/{docname}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
shutil.rmtree(tmpdir)
|
||||||
|
|
||||||
|
|
||||||
|
def load_spreadsheet():
|
||||||
|
csv_dump = '''2002-03-01,IAB RFC Publication Process Description(txt) March 2003,https://www.iab.org/documents/correspondence-reports-documents/docs2003/iab-rfc-publication-process/,deprecated
|
||||||
|
2015-01-27,IAB Statement on Identifiers and Unicode 7.0.0 (archive),https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/archive/,deprecated
|
||||||
|
2010-02-05,Response to the EC’s RFI on Forums and Consortiums,https://www.iab.org/documents/correspondence-reports-documents/docs2010/response-to-the-ecs-rfi-on-forums-and-consortiums/,https://www.iab.org/wp-content/IAB-uploads/2011/03/2010-02-05-IAB-Response-Euro-ICT-Questionnaire.pdf
|
||||||
|
2011-03-30,IAB responds to NTIA Request for Comments on the IANA Functions,https://www.iab.org/documents/correspondence-reports-documents/2011-2/iab-responds-to-ntia-request-for-comments-on-the-iana-functions/,https://www.iab.org/wp-content/IAB-uploads/2011/04/2011-03-30-iab-iana-noi-response.pdf
|
||||||
|
2011-07-28,IAB's response to the NTIA FNOI on IANA,https://www.iab.org/documents/correspondence-reports-documents/2011-2/iabs-response-to-the-ntia-fnoi-on-iana/,https://www.iab.org/wp-content/IAB-uploads/2011/07/IANA-IAB-FNOI-2011.pdf
|
||||||
|
2011-12-16,"Questionnaire in support of the ICANN bid for the IANA function [Dec 16, 2011]",https://www.iab.org/documents/correspondence-reports-documents/2011-2/questionnaire-in-support-of-the-icann-bid-for-the-iana-function/,https://www.iab.org/wp-content/IAB-uploads/2011/12/IAB-Past-Performance-Questionnaire.pdf
|
||||||
|
2012-04-03,IETF Oversight of the IANA Protocol Parameter Function,https://www.iab.org/documents/correspondence-reports-documents/2012-2/ietf-oversight-of-the-iana-protocol-parameter-function/,https://www.iab.org/wp-content/IAB-uploads/2012/04/IETF-IANA-Oversight.pdf
|
||||||
|
2012-04-29,IETF and IAB comment on OMB Circular A-119,https://www.iab.org/documents/correspondence-reports-documents/2012-2/ietf-and-iab-comment-on-omb-circular-a-119/,https://www.iab.org/wp-content/IAB-uploads/2012/04/OMB-119.pdf
|
||||||
|
2012-05-24,IAB submits updated ICANN performance evaluation,https://www.iab.org/documents/correspondence-reports-documents/2012-2/iab-submits-updated-icann-performance-evaluation/,https://www.iab.org/wp-content/IAB-uploads/2012/05/IAB-Past-Performance-Questionnaire-FINAL.pdf
|
||||||
|
2013-07-02,Open letter to the European Commission and the European Parliament in the matter of the Transatlantic Trade and Investment Partnership (TTIP),https://www.iab.org/documents/correspondence-reports-documents/2013-2/open-letter-to-the-ec/,https://www.iab.org/wp-content/IAB-uploads/2013/07/TTIP_market_driven_standards_EU_letter.pdf
|
||||||
|
2013-05-10,Comments In the matter of Transatlantic Trade and Investment Partnership (TTIP) (USTR-2013-0019),https://www.iab.org/documents/correspondence-reports-documents/2013-2/comments-in-the-matter-of-transatlantic-trade-and-investment-partnership-ttip-ustr-2013-0019/,https://www.iab.org/wp-content/IAB-uploads/2013/07/TTIP_market_driven_standards_FINAL.pdf
|
||||||
|
2013-10-23,IAB Comments on Recommendation for Random Number Generation Using Deterministic Random Bit Generators,https://www.iab.org/documents/correspondence-reports-documents/2013-2/nist-sp-800-90a/,https://www.iab.org/wp-content/IAB-uploads/2013/10/IAB-NIST-FINAL.pdf
|
||||||
|
2014-04-07,IAB Comments on NISTIR 7977,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-comments-on-nistir-7977/,https://www.iab.org/wp-content/IAB-uploads/2014/04/IAB-NIST7977-20140407.pdf
|
||||||
|
2014-04-29,Comments to ICANN on the Transition of NTIA’s Stewardship of the IANA Functions,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-response-to-icann-iana-transition-proposal/,https://www.iab.org/wp-content/IAB-uploads/2014/04/iab-response-to-20140408-20140428a.pdf
|
||||||
|
2016-05-27,"IAB Comments to US NTIA Request for Comments, ""The Benefits, Challenges, and Potential Roles for the Government in Fostering the Advancement of the Internet of Things""",https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-comments-to-ntia-request-for-comments-the-benefits-challenges-and-potential-roles-for-the-government/,https://www.iab.org/wp-content/IAB-uploads/2016/05/ntia-iot-20160525.pdf
|
||||||
|
2016-05-24,"IAB Chair Testifies before the United States Senate Committee on Commerce, Science, and Transportation on ""Examining the Multistakeholder Plan for Transitioning the Internet Assigned Number Authority""",https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-chair-statement-before-us-senate-committee-on-iana-transition/,https://www.iab.org/wp-content/IAB-uploads/2016/05/sullivan-to-senate-commerce-20160524.pdf
|
||||||
|
2018-07-16,IAB Response to NTIA Notice of Inquiry on International Internet Policy Priorities,https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-response-to-ntia-notice-of-inquiry-on-international-internet-policy-priorities-response/,https://www.iab.org/wp-content/IAB-uploads/2018/07/IAB-response-to-the-2018-NTIA-Notice-of-Inquiry.pdf
|
||||||
|
2018-09-09,Internet Architecture Board Comments on the Australian Assistance and Access Bill 2018,https://www.iab.org/documents/correspondence-reports-documents/2018-2/internet-architecture-board-comments-on-the-australian-assistance-and-access-bill-2018/,https://www.iab.org/wp-content/IAB-uploads/2018/09/IAB-Comments-on-Australian-Assistance-and-Access-Bill-2018.pdf
|
||||||
|
2023-03-03,IAB Response to the Office of the High Commissioner for Human Rights Call for Input on “The relationship between human rights and technical standard-setting processes for new and emerging digital technologies”,https://www.iab.org/documents/correspondence-reports-documents/2023-2/iab-response-to-the-ohchr-call-for-input-on-the-relationship-between-human-rights-and-technical-standard/,https://www.iab.org/wp-content/IAB-uploads/2023/03/IAB-Response-to-OHCHR-consultation.pdf
|
||||||
|
1998-12-09,"IAB Request to IANA for Delegating IPv6 Address Space, Mail Message, December 1998",https://www.iab.org/documents/correspondence-reports-documents/docs98/iab-request-to-iana-for-delegating-ipv6-address-space-mail-message-december-1998/,
|
||||||
|
1998-12-18,"1998 Statements on Cryptography, Mail Message, December 1998.",https://www.iab.org/documents/correspondence-reports-documents/docs98/1998-statements-on-cryptography/,
|
||||||
|
1999-02-22,Correspondence between Bradner and Dyson on Protocol Parameter Parameters,https://www.iab.org/documents/correspondence-reports-documents/docs99/correspondence-between-bradner-and-dyson-on-protocol-parameter-parameters/,
|
||||||
|
1999-08-13,Comment on ICANN ASO membership,https://www.iab.org/documents/correspondence-reports-documents/docs99/comment-on-icann-aso-membership/,
|
||||||
|
1999-10-19,Ad Hoc Group on Numbering,https://www.iab.org/documents/correspondence-reports-documents/docs99/ad-hoc-group-on-numbering/,
|
||||||
|
2000-05-01,"IAB Statement on Infrastructure Domain and Subdomains, May 2000.",https://www.iab.org/documents/correspondence-reports-documents/docs2000/iab-statement-on-infrastructure-domain-and-subdomains-may-2000/,
|
||||||
|
2002-05-01,"IETF and ITU-T Cooperation Arrangements, May 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/ietf-and-itu-t-cooperation-arrangements-may-2002/,
|
||||||
|
2002-05-03,"IAB replyto ENUM liaison statement, May 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/enum-response/,
|
||||||
|
2002-05-24,"Interim Approval for Internet Telephone Numbering System (ENUM) Provisioning, 24 May 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/enum-pr/,
|
||||||
|
2002-06-01,"IAB response to ICANN Evolution and Reform, June 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/icann-response/,
|
||||||
|
2002-09-01,"IAB response to ICANN Evolution and Reform Committee's Second Interim Report, September 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/icann-response-2/,
|
||||||
|
2002-10-01,"IAB response to ICANN Evolution and Reform Committee's Final Implementation Report, October 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/icann-response-3/,
|
||||||
|
2002-12-10,"IAB Response to RIRs request regarding 6bone address entries in ip6.arpa, December 2002",https://www.iab.org/documents/correspondence-reports-documents/docs2002/3ffe/,
|
||||||
|
2003-01-03,"IETF Notice of Withdrawal from the Protocol Support Organization, January 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/icann-pso-notice/,
|
||||||
|
2003-01-25,"IAB Response to Verisign GRS IDN Announcement, January 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/icann-vgrs-response/,
|
||||||
|
2003-07-10,"Note: Unified Notification Protocol Considerations, July 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-07-10-iab-notification/,
|
||||||
|
2003-08-01,Open Architectural Issues in the Development of the Internet,https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-08-architectural-issues/,
|
||||||
|
2003-08-28,RFC Document editing/ queueing suggestion,https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-08-28-klensin-rfc-editor/,
|
||||||
|
2003-09-02,"IAB Chair's announcement of an Advisory Committee, September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-02-adv-committee/,
|
||||||
|
2003-09-19,"IAB Commentary: Architectural Concerns on the Use of DNS Wildcards, September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-20-dns-wildcards/,
|
||||||
|
2003-09-24,"IAB to ICANN: IAB input related to the .cs code in ISO 3166, 24 September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-25-icann-cs-code/,
|
||||||
|
2003-09-24,"IAB to ISO: IAB comment on stability of ISO 3166 and other infrastructure standards, 24 September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-25-iso-cs-code/,
|
||||||
|
2003-09-25,"Correspondance to ISO concerning .cs code, and advice to ICANN, 25 September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-25-icann-cs-code-2/,
|
||||||
|
2003-09-25,"Correspondance to ISO concerning .cs code, and advice to ICANN, 25 September 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-25-iso-cs-code-2/,
|
||||||
|
2003-09-26,ISO Codes,https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-09-23-isocodes/,
|
||||||
|
2003-10-02,"IESG to IAB: Checking data for validity before usage in a protocol, 2 October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-02-iesg-dns-validity-check-query/,
|
||||||
|
2003-10-14,"Survey of Current Security Work in the IETF, October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-14-security-survey/,
|
||||||
|
2003-10-17,"IAB to ICANN SESAC:Wildcard entries in DNS domains, 17 October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-17-crocker-wildcards-2/,
|
||||||
|
2003-10-17,"IAB note to Steve Crocker, Chair, ICANN Security and Stability Advisory Committee, October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-17-crocker-wildcards/,
|
||||||
|
2003-10-18,"IAB concerns against permanent deployment of edge-based port filtering, October 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-10-18-edge-filters/,
|
||||||
|
2003-11-08,"IAB Response to IESG architectural query: Checking data for validity before usage in protocol, November 2003",https://www.iab.org/documents/correspondence-reports-documents/docs2003/2003-11-08-iesg-dns-validity-check-query-response/,
|
||||||
|
2004-01-19,"Number Resource Organisation (NRO) formation, 19 January 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-01-19-nro/,
|
||||||
|
2004-01-22,"IAB to RIPE NCC:ENUM Administration, 22 January 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-01-22-enum-subcodes/,
|
||||||
|
2004-02-09,"IAB to IANA: Instructions to IANA -Delegation of 2.0.0.2.ip6.arpa, 9 February, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-02-09-6to4-rev-delegation/,
|
||||||
|
2004-02-26,The IETF -- what is it?,https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-02-26-ietf-defn/,
|
||||||
|
2004-04-15,"IAB to ICANN: Validity checks for names, 15 April, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-04-15-icann-dns-validity-check/,
|
||||||
|
2004-05-07,"IAB to IANA: IPv6 Allocation Policy , 7 May, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-05-07-iana-v6alloc/,
|
||||||
|
2004-05-24,"IAB to IANA: Instructions to IANA -Delegation of 3.f.f.e.ip6.arpa, 24 May, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-05-24-3ffe-rev-delegation/,
|
||||||
|
2004-05-27,"IAB to ICANN:Concerns regarding IANA Report, 27 May, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-05-27-iana-report/,
|
||||||
|
2004-07-16,"Upcoming clarifications to RIPE NCC instructions for e164.arpa operation, 16 July 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-07-15-enum-instructions/,
|
||||||
|
2004-07-16,"IANA Delegation Requests, 16 July 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-07-16-iana-delegation/,
|
||||||
|
2004-08-06,OMA-IETF Standardization Collaboration,https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-08-draft-iab-oma-liaison-00/,
|
||||||
|
2004-08-12,"IAB to RIPE NCC:Notice of revision of instructions concerning the ENUM Registry, 12 August, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-08-12-enum-instructions/,
|
||||||
|
2004-08-12,"Response to your letter of August 4, 12 August 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-08-12-icann-wildcard/,
|
||||||
|
2004-09-27,"IAB to ICANN:Report of Concerns over IANA Performance , 27 September, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-09-27-iana-concerns/,
|
||||||
|
2004-09-27,"IAB Report of IETF IANA Functions , 27 September 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-09-27-iana-report/,
|
||||||
|
2004-11-03,"IAB to IESG:Comments on Teredo , 3 November, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-11-03-teredo-comments/,
|
||||||
|
2004-11-12,"IAB to ICANN:Response to ICANN Request for assistance with new TLD Policy , 12 November, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-11-12-icann-new-tld-policy/,
|
||||||
|
2004-11-29,"The IETF and IPv6 Address Allocation , 29 November 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-11-29-ipv6-allocs/,
|
||||||
|
2004-12-15,"IAB Comment to Internet AD:Comments on IANA Considerations in IPv6 ULA draft, 15 December, 2004",https://www.iab.org/documents/correspondence-reports-documents/docs2004/2004-12-15-ipv6-ula-iana-considerations/,
|
||||||
|
2005-02-16,"IAB review of Structure of the IETF Administrative Support Activity, 16 February 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-02-16-iasa/,
|
||||||
|
2005-08-26,"SiteFinder returns, 26 August 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-08-26-ssac-note/,
|
||||||
|
2005-09-01,"Re: SiteFinder returns, 1 September 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-09-01-ssac-response/,
|
||||||
|
2005-10-14,"IAB to ICANN: IAB comments on ICANN IDN Guidelines, 14 October 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-10-14-idn-guidelines/,
|
||||||
|
2005-11-07,"IAB to ICANN – Nameserver change for e164.arpa, 7 November 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-11-07-nameserver-change/,
|
||||||
|
2005-11-22,"IETF to ICANN – IANA structural status, 22 November 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-11-22-iana-structure/,
|
||||||
|
2005-11-29,"IAB to IANA – Teredo prefix assignment, 29 November 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-11-29-teredo-prefix/,
|
||||||
|
2005-12-22,"IAB to ICANN – dot arpa TLD management, 22 December 2005",https://www.iab.org/documents/correspondence-reports-documents/docs2005/2005-12-22-dot-arpa/,
|
||||||
|
2006-03-06,"IAB Position on the IETF IANA Technical Parameter Function, 6 March 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/iab-iana-position/,
|
||||||
|
2006-03-28,"IAB to ICANN – Name server changes for ip6.arpa, 28 March 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-03-28-nameserver-change/,
|
||||||
|
2006-04-20,"IAB to IANA – Administrative contact information change for arpa, 20 April 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-04-20-update-to-administrative-contact-information-for-arpa-iana/,
|
||||||
|
2006-04-20,"IAB to ITU TSB – FYI re contact info changes for e164.arpa, 20 April 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-04-20-update-to-contact-information-for-e164-arpa-hill/,
|
||||||
|
2006-04-20,"IAB to IANA – Contact information changes for e164.arpa, 20 April 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-04-20-update-to-contact-information-for-e164-arpa-iana/,
|
||||||
|
2006-05-15,"IAB to IANA – Request to IANA for status update on deployment of DNSSEC on IANA managed zones, 15 May 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-05-15-iab-request-to-iana-to-sign-dnssec-zones/,
|
||||||
|
2006-06-07,"The IAB announces the mailing list for the discussion of the independent submissions process, 7 June 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-06-07-independent-submissions/,
|
||||||
|
2006-06-19,"Procedural issues with liaison on nextsteps, 19 June 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-06-16-response-to-idn-liaison-issues/,
|
||||||
|
2006-10-12,"The IAB sends a note to the Registry Services Technical Evaluation Panel on the use of wildcards in the .travel TLD, 12 October 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-10-12-rstep-note/,
|
||||||
|
2006-10-19,"The IAB sends a note to the OIF Technical Committee Chair on IETF Protocol Extensions, 19 October 2006",https://www.iab.org/documents/correspondence-reports-documents/docs2006/2006-10-19-oifnote/,
|
||||||
|
2007-05-21,"The IAB responds to ITU Consultation on Resolution 102, 21 May 2007",https://www.iab.org/documents/correspondence-reports-documents/docs2007/2007-05-21-itu-resolution-102/,
|
||||||
|
2007-07-05,"Correspondence from the RIPE NCC regarding deployment of DNSSEC in the E164.ARPA zone, 5 July 2007",https://www.iab.org/documents/correspondence-reports-documents/docs2007/2007-07-05-ripe-ncc-dnssec-e164/,
|
||||||
|
2007-07-24,"Correspondence from the IAB to the ITU-TSB Director regarding deployment of DNSSEC in the E164.ARPA zone, 24 July 2007",https://www.iab.org/documents/correspondence-reports-documents/docs2007/2007-07-24-iab-itu-dnssec-e164/,
|
||||||
|
2007-10-10,"Follow-up work on NAT-PT, 10 October 2007",https://www.iab.org/documents/correspondence-reports-documents/docs2007/follow-up-work-on-nat-pt/,
|
||||||
|
2008-02-15,"Correspondence from the IAB to the National Telecommunications and Information Administration, US Department of Commerce regarding the ICANN/DoC Joint Project Agreement, 15 February 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-02-15-midterm-view-icann-doc-jpa/,
|
||||||
|
2008-03-07,"The IAB’s response to ICANN’s solicitation on DNS stability, 7 March 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-03-07-icann-new-gtlds/,
|
||||||
|
2008-06-04,Proposed RFC Editor Structure,https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-06-04-rfc-editor-model/,
|
||||||
|
2008-08-16,"The IAB’s response to Geoff Huston’s request concerning 32-bit AS numbers, 16 August 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-08-16-32bit-as-huston/,
|
||||||
|
2008-09-05,Proposed RFC Editor Structure,https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-09-05-rfc-editor-model/,
|
||||||
|
2008-11-18,"The IAB’s correspondence with NTIA on DNSSEC deployment at the root, 18 November 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-11-18-dnssec-deployment-at-the-root/,
|
||||||
|
2008-12-04,"IAB correspondence with Geoff Huston on TAs, IANA, RIRs et al.., 4 December 2008",https://www.iab.org/documents/correspondence-reports-documents/docs2008/2008-12-04-huston-tas-iana-rirs/,
|
||||||
|
2009-06-02,"IAB correspondence with IANA on the Signing of .ARPA, 2 June 2009",https://www.iab.org/documents/correspondence-reports-documents/docs2009/2009-06-02-roseman-signing-by-iana-of-arpa/,
|
||||||
|
2009-10-14,"IAB correspondence with ICANN on their “Scaling the Root” study., 14 October 2009",https://www.iab.org/documents/correspondence-reports-documents/docs2009/2009-10-14-icann-scaling-the-root/,
|
||||||
|
2010-01-27,IAB statement on the RPKI,https://www.iab.org/documents/correspondence-reports-documents/docs2010/iab-statement-on-the-rpki/,
|
||||||
|
2010-07-30,Transition of IN-ADDR.ARPA generation,https://www.iab.org/documents/correspondence-reports-documents/docs2010/transition-of-in-addr-arpa-generation/,
|
||||||
|
2011-06-22,Response to ARIN's request for guidance regarding Draft Policy ARIN-2011-5,https://www.iab.org/documents/correspondence-reports-documents/2011-2/response-to-arins-request-for-guidance-regarding-draft-policy-arin-2011-5/,
|
||||||
|
2011-07-25,"IAB Response to ""Some IESG Thoughts on Liaisons""",https://www.iab.org/documents/correspondence-reports-documents/2011-2/iab-response-to-some-iesg-thoughts-on-liaisons/,
|
||||||
|
2011-09-16,Letter to the European Commission on Global Interoperability in Emergency Services,https://www.iab.org/documents/correspondence-reports-documents/2011-2/letter-to-the-european-commission-on-global-interoperability-in-emergency-services/,
|
||||||
|
2012-02-08,"IAB Statement: ""The interpretation of rules in the ICANN gTLD Applicant Guidebook""",https://www.iab.org/documents/correspondence-reports-documents/2012-2/iab-statement-the-interpretation-of-rules-in-the-icann-gtld-applicant-guidebook/,
|
||||||
|
2012-03-26,"Response to ICANN questions concerning ""The interpretation of rules in the ICANN gTLD Applicant Guidebook""",https://www.iab.org/documents/correspondence-reports-documents/2012-2/response-to-icann-questions-concerning-the-interpretation-of-rules-in-the-icann-gtld-applicant-guidebook/,
|
||||||
|
2012-03-30,IAB Member Roles in Evaluating New Work Proposals,https://www.iab.org/documents/correspondence-reports-documents/2012-2/iab-member-roles-in-evaluating-new-work-proposals/,
|
||||||
|
2012-08-29,Leading Global Standards Organizations Endorse ‘OpenStand’ Principles that Drive Innovation and Borderless Commerce,https://www.iab.org/documents/correspondence-reports-documents/2012-2/leading-global-standards-organizations-endorse-%e2%80%98openstand/,
|
||||||
|
2013-03-28,IAB Response to RSSAC restructure document (28 March 2013),https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-response-to-rssac-restructure-document-28-march-2013/,
|
||||||
|
2013-05-28,Consultation on Root Zone KSK Rollover from the IAB,https://www.iab.org/documents/correspondence-reports-documents/2013-2/consultation-on-root-zone-ksk-rollover-from-the-iab/,
|
||||||
|
2013-07-10,IAB Statement: Dotless Domains Considered Harmful,https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/,
|
||||||
|
2013-07-16,IAB Response to ICANN Consultation on the Source of Policies & User Instructions for Internet Number Resource Requests,https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-response-to-iana-policies-user-instructions-25jun13/,
|
||||||
|
2013-10-03,Statement from the IAB on the Strengths of the OpenStand Principles,https://www.iab.org/documents/correspondence-reports-documents/2013-2/statement-from-openstand-on-the-strengths-of-the-openstand-principles/,
|
||||||
|
2013-10-07,Montevideo Statement on the Future of Internet Cooperation,https://www.iab.org/documents/correspondence-reports-documents/2013-2/montevideo-statement-on-the-future-of-internet-cooperation/,
|
||||||
|
2013-11-27,IAB Statement on draft-farrell-perpass-attack-00,https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-on-draft-farrell-perpass-attack-00/,
|
||||||
|
2014-01-23,IAB Comments Regarding the IRTF CFRG chair,https://www.iab.org/documents/correspondence-reports-documents/2014-2/0123-iab-comments-regarding-the-irtf-cfrg-chair/,
|
||||||
|
2014-02-14,"Statement from the I* Leaders Coordination Meeting, Santa Monica, 14 February 2014",https://www.iab.org/documents/correspondence-reports-documents/2014-2/statement-from-the-i-leaders-coordination-meeting-santa-monica-14-february-2014/,
|
||||||
|
2014-03-11,Re: Guiding the Evolution of the IANA Protocol Parameter Registries,https://www.iab.org/documents/correspondence-reports-documents/2014-2/re-guiding-the-evolution-of-the-iana-protocol-parameter-registries/,
|
||||||
|
2014-03-14,Internet Technical Leaders Welcome IANA Globalization Progress,https://www.iab.org/documents/correspondence-reports-documents/2014-2/internet-technical-leaders-welcome-iana-globalization-progress/,
|
||||||
|
2014-05-13,I* Post-NETmundial Meeting Statement,https://www.iab.org/documents/correspondence-reports-documents/2014-2/i-post-netmundial-meeting-statement/,
|
||||||
|
2014-06-05,Comments on ICANN Board Member Compensation from the IAB,https://www.iab.org/documents/correspondence-reports-documents/2014-2/comments-on-icann-board-member-compensation/,
|
||||||
|
2014-11-13,IAB Statement on Internet Confidentiality,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-statement-on-internet-confidentiality/,
|
||||||
|
2014-12-04,IAB statement on the NETmundial Initiative,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-statement-on-the-netmundial-initiative/,
|
||||||
|
2014-12-17,IAB Comments on CSTD Report Mapping International Internet Public Policy Issues,https://www.iab.org/documents/correspondence-reports-documents/2014-2/iab-comments-on-cstd-report-mapping-international-public-policy-issues/,
|
||||||
|
2015-02-11,IAB liaison to ICANN Root Server System Advisory Committee (RSSAC),https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-liaison-to-icann-root-server-system-advisory-council-rssac/,
|
||||||
|
2015-02-11,IAB Statement on Identifiers and Unicode 7.0.0,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-identifiers-and-unicode-7-0-0/,
|
||||||
|
2015-03-02,IAB Statement on Liaison Compensation,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-liaison-compensation/,
|
||||||
|
2015-04-09,IAB Comments on The HTTPS-Only Standard,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-the-https-only-standard/,
|
||||||
|
2015-06-03,IAB comments on CCWG-Accountability Draft Report,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-ccwg-accountability-draft-report/,
|
||||||
|
2015-06-12,IAB Statement on the Trade in Security Technologies,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-statement-on-the-trade-in-security-technologies/,
|
||||||
|
2015-06-24,"IAB Correspondence to U.S. Bureau of Industry and Security, re RIN 0694-AG49",https://www.iab.org/documents/correspondence-reports-documents/2015-2/rin-0694-ag49/,
|
||||||
|
2015-09-07,Internet Architecture Board comments on the ICG Proposal,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-icg-proposal/,
|
||||||
|
2015-09-09,IAB comments on the CCWG accountability 2d draft report,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-ccwg-accountability/,
|
||||||
|
2015-10-07,IAB Comments to FCC on Rules regarding Authorization of Radiofrequency Equipment,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-fcc-15-92/,
|
||||||
|
2015-12-16,IAB comments on the CCWG accountability 3d draft report,https://www.iab.org/documents/correspondence-reports-documents/2015-2/iab-comments-on-the-ccwg-accountability-3d-draft-report/,
|
||||||
|
2016-01-13,"Comments from the Internet Architecture Board (IAB) on ""Registration Data Access Protocol (RDAP) Operational Profile for gTLD Registries and Registrars""",https://www.iab.org/documents/correspondence-reports-documents/2016-2/comments-from-the-internet-architecture-board-iab-on-registration-data-access-protocol-rdap-operational-profile-for-gtld-registries-and-registrars/,
|
||||||
|
2016-05-04,IAB comments on Draft New ICANN Bylaws,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-comments-on-draft-new-icann-bylaws/,
|
||||||
|
2016-05-11,IAB Comments on Proposed Changes to Internet Society Bylaws,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-comments-on-proposed-changes-to-internet-society-bylaws/,
|
||||||
|
2016-07-17,Comments from the IAB on LGRs for second level,https://www.iab.org/documents/correspondence-reports-documents/2016-2/comments-from-the-iab-on-lgrs-for-second-level/,
|
||||||
|
2016-09-01,IAB statement on IANA Intellectual Property Rights,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-statement-on-iana-intellectual-property-rights/,
|
||||||
|
2016-09-14,IAB Statement on the IANA Stewardship Transition,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-statement-on-the-iana-stewardship-transition/,
|
||||||
|
2016-11-07,IAB Statement on IPv6,https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-statement-on-ipv6/,
|
||||||
|
2016-12-07,"IAB comment on ""Revised Proposed Implementation of GNSO Thick Whois Consensus Policy Requiring Consistent Labeling and Display of RDDS (Whois) Output for All gTLDs""",https://www.iab.org/documents/correspondence-reports-documents/2016-2/iab-comment-on-revised-proposed-implementation-of-gnso-thick-whois-consensus-policy-requiring-consistent-labeling-and-display-of-rdds-whois-output-for-all-gtlds/,
|
||||||
|
2017-01-04,IAB comments on Identifier Technology Health Indicators: Definition,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-comments-on-identifier-technology-health-indicators-definition/,
|
||||||
|
2017-02-01,IAB Statement on OCSP Stapling,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-statement-on-ocsp-stapling/,
|
||||||
|
2017-02-16,Follow up on barriers to entry blog post,https://www.iab.org/documents/correspondence-reports-documents/2017-2/follow-up-on-barriers-to-entry-blog-post/,
|
||||||
|
2017-03-02,IAB Comments to United States NTIA on the Green Paper: Fostering the Advancement of the Internet of Things,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-comments-to-ntia-on-fostering-the-advancement-of-iot/,
|
||||||
|
2017-03-30,Internet Architecture Board statement on the registration of special use names in the ARPA domain,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-statement-on-the-registration-of-special-use-names-in-the-arpa-domain/,
|
||||||
|
2017-05-01,Comments from the IAB on IDN Implementation Guidelines,https://www.iab.org/documents/correspondence-reports-documents/2017-2/comments-from-the-iab-on-idn-implementation-guidelines/,
|
||||||
|
2017-07-31,IAB Response to FCC-17-89,https://www.iab.org/documents/correspondence-reports-documents/2017-2/iab-response-to-fcc-17-89/,
|
||||||
|
2018-03-15,IAB Statement on Identifiers and Unicode,https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-identifiers-and-unicode/,
|
||||||
|
2018-04-03,IAB Statement on the RPKI,https://www.iab.org/documents/correspondence-reports-documents/2018-2/iab-statement-on-the-rpki/,
|
||||||
|
2019-05-02,Revised Operating Instructions for e164.arpa (ENUM),https://www.iab.org/documents/correspondence-reports-documents/2019-2/revised-operating-instructions-for-e164-arpa-enum/,
|
||||||
|
2019-06-26,Comments on Evolving the Governance of the Root Server System,https://www.iab.org/documents/correspondence-reports-documents/2019-2/comments-on-evolving-the-governance-of-the-root-server-system/,
|
||||||
|
2019-09-04,Avoiding Unintended Harm to Internet Infrastructure,https://www.iab.org/documents/correspondence-reports-documents/2019-2/avoiding-unintended-harm-to-internet-infrastructure/,
|
||||||
|
2020-07-01,"IAB correspondence with the National Telecommunications and Information Administration (NTIA) on DNSSEC deployment for the Root Zone [Docket No. 100603240-0240-01], 1 July 2010",https://www.iab.org/documents/correspondence-reports-documents/docs2010/2010-07-01-alexander-dnssec-deployment-for-the-root-zone/,
|
||||||
|
2020-09-29,IAB Comments on the Draft Final Report on the new gTLD Subsequent Procedures Policy Development Process,https://www.iab.org/documents/correspondence-reports-documents/2020-2/iab-comments-on-new-gtld-subsequent-procedures/,
|
||||||
|
2021-07-14,IAB Statement on Inclusive Language in IAB Stream Documents,https://www.iab.org/documents/correspondence-reports-documents/2021-2/iab-statement-on-inclusive-language-in-iab-stream-documents/,
|
||||||
|
2022-04-08,IAB comment on Mandated Browser Root Certificates in the European Union’s eIDAS Regulation on the Internet,https://www.iab.org/documents/correspondence-reports-documents/2022-2/iab-comment-on-mandated-browser-root-certificates-in-the-european-unions-eidas-regulation-on-the-internet/,
|
||||||
|
2022-04-08,"IAB Comments on A Notice by the Federal Communications Commission on Secure Internet Routing, issued 03/11/2022",https://www.iab.org/documents/correspondence-reports-documents/2022-2/iab-comments-on-a-notice-by-the-federal-communications-commission-on-secure-internet-routing-issued-03-11-2022/,
|
||||||
|
2022-07-08,IAB Statement to OSTP on Privacy-Enhancing Technologies,https://www.iab.org/documents/correspondence-reports-documents/2022-2/iab-statement-to-ostp-on-privacy-enhancing-technologies/,
|
||||||
|
2022-11-21,IAB Comments on a notice by the Federal Trade Commission on “Trade Regulation Rule on Commercial Surveillance and Data Security” (16 CFR Part 464),https://www.iab.org/documents/correspondence-reports-documents/2022-2/iab-comments-on-a-notice-by-the-federal-trade-commission-on-trade-regulation-rule-on-commercial-surveillance-and-data-security-16-cfr-part-464/,
|
||||||
|
'''
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
date_count = defaultdict(lambda: 0)
|
||||||
|
with io.StringIO(csv_dump) as csv_file:
|
||||||
|
reader = csv.reader(csv_file)
|
||||||
|
for row in reader:
|
||||||
|
date = row[0]
|
||||||
|
if date_count[date] == 0:
|
||||||
|
row.insert(0, "")
|
||||||
|
else:
|
||||||
|
row.insert(0, date_count[date])
|
||||||
|
date_count[date] += 1
|
||||||
|
rows.append(row)
|
||||||
|
return rows
|
86
ietf/doc/migrations/0005_alter_docevent_type.py
Normal file
86
ietf/doc/migrations/0005_alter_docevent_type.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# Copyright The IETF Trust 2023, All Rights Reserved
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("doc", "0004_alter_dochistory_ad_alter_dochistory_shepherd_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="docevent",
|
||||||
|
name="type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("new_revision", "Added new revision"),
|
||||||
|
("new_submission", "Uploaded new revision"),
|
||||||
|
("changed_document", "Changed document metadata"),
|
||||||
|
("added_comment", "Added comment"),
|
||||||
|
("added_message", "Added message"),
|
||||||
|
("edited_authors", "Edited the documents author list"),
|
||||||
|
("deleted", "Deleted document"),
|
||||||
|
("changed_state", "Changed state"),
|
||||||
|
("changed_stream", "Changed document stream"),
|
||||||
|
("expired_document", "Expired document"),
|
||||||
|
("extended_expiry", "Extended expiry of document"),
|
||||||
|
("requested_resurrect", "Requested resurrect"),
|
||||||
|
("completed_resurrect", "Completed resurrect"),
|
||||||
|
("changed_consensus", "Changed consensus"),
|
||||||
|
("published_rfc", "Published RFC"),
|
||||||
|
(
|
||||||
|
"added_suggested_replaces",
|
||||||
|
"Added suggested replacement relationships",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"reviewed_suggested_replaces",
|
||||||
|
"Reviewed suggested replacement relationships",
|
||||||
|
),
|
||||||
|
("changed_action_holders", "Changed action holders for document"),
|
||||||
|
("changed_group", "Changed group"),
|
||||||
|
("changed_protocol_writeup", "Changed protocol writeup"),
|
||||||
|
("changed_charter_milestone", "Changed charter milestone"),
|
||||||
|
("initial_review", "Set initial review time"),
|
||||||
|
("changed_review_announcement", "Changed WG Review text"),
|
||||||
|
("changed_action_announcement", "Changed WG Action text"),
|
||||||
|
("started_iesg_process", "Started IESG process on document"),
|
||||||
|
("created_ballot", "Created ballot"),
|
||||||
|
("closed_ballot", "Closed ballot"),
|
||||||
|
("sent_ballot_announcement", "Sent ballot announcement"),
|
||||||
|
("changed_ballot_position", "Changed ballot position"),
|
||||||
|
("changed_ballot_approval_text", "Changed ballot approval text"),
|
||||||
|
("changed_ballot_writeup_text", "Changed ballot writeup text"),
|
||||||
|
("changed_rfc_editor_note_text", "Changed RFC Editor Note text"),
|
||||||
|
("changed_last_call_text", "Changed last call text"),
|
||||||
|
("requested_last_call", "Requested last call"),
|
||||||
|
("sent_last_call", "Sent last call"),
|
||||||
|
("scheduled_for_telechat", "Scheduled for telechat"),
|
||||||
|
("iesg_approved", "IESG approved document (no problem)"),
|
||||||
|
("iesg_disapproved", "IESG disapproved document (do not publish)"),
|
||||||
|
("approved_in_minute", "Approved in minute"),
|
||||||
|
("iana_review", "IANA review comment"),
|
||||||
|
("rfc_in_iana_registry", "RFC is in IANA registry"),
|
||||||
|
(
|
||||||
|
"rfc_editor_received_announcement",
|
||||||
|
"Announcement was received by RFC Editor",
|
||||||
|
),
|
||||||
|
("requested_publication", "Publication at RFC Editor requested"),
|
||||||
|
(
|
||||||
|
"sync_from_rfc_editor",
|
||||||
|
"Received updated information from RFC Editor",
|
||||||
|
),
|
||||||
|
("requested_review", "Requested review"),
|
||||||
|
("assigned_review_request", "Assigned review request"),
|
||||||
|
("closed_review_request", "Closed review request"),
|
||||||
|
("closed_review_assignment", "Closed review assignment"),
|
||||||
|
("downref_approved", "Downref approved"),
|
||||||
|
("posted_related_ipr", "Posted related IPR"),
|
||||||
|
("removed_related_ipr", "Removed related IPR"),
|
||||||
|
("changed_editors", "Changed BOF Request editors"),
|
||||||
|
("published_statement", "Published statement"),
|
||||||
|
],
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
43
ietf/doc/migrations/0006_statements.py
Normal file
43
ietf/doc/migrations/0006_statements.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Copyright The IETF Trust 2023, All Rights Reserved
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def forward(apps, schema_editor):
|
||||||
|
StateType = apps.get_model("doc", "StateType")
|
||||||
|
State = apps.get_model("doc", "State")
|
||||||
|
|
||||||
|
StateType.objects.create(slug="statement", label="Statement State")
|
||||||
|
State.objects.create(
|
||||||
|
slug="active",
|
||||||
|
type_id="statement",
|
||||||
|
name="Active",
|
||||||
|
order=0,
|
||||||
|
desc="The statement is active",
|
||||||
|
)
|
||||||
|
State.objects.create(
|
||||||
|
slug="replaced",
|
||||||
|
type_id="statement",
|
||||||
|
name="Replaced",
|
||||||
|
order=0,
|
||||||
|
desc="The statement has been replaced",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def reverse(apps, schema_editor):
|
||||||
|
StateType = apps.get_model("doc", "StateType")
|
||||||
|
State = apps.get_model("doc", "State")
|
||||||
|
|
||||||
|
State.objects.filter(type_id="statement").delete()
|
||||||
|
StateType.objects.filter(slug="statement").delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("doc", "0005_alter_docevent_type"),
|
||||||
|
("name", "0004_statements"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forward, reverse),
|
||||||
|
]
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright The IETF Trust 2010-2020, All Rights Reserved
|
# Copyright The IETF Trust 2010-2023, All Rights Reserved
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ class DocumentInfo(models.Model):
|
||||||
self._cached_file_path = settings.CONFLICT_REVIEW_PATH
|
self._cached_file_path = settings.CONFLICT_REVIEW_PATH
|
||||||
elif self.type_id == "statchg":
|
elif self.type_id == "statchg":
|
||||||
self._cached_file_path = settings.STATUS_CHANGE_PATH
|
self._cached_file_path = settings.STATUS_CHANGE_PATH
|
||||||
elif self.type_id == "bofreq":
|
elif self.type_id == "bofreq": # TODO: This is probably unneeded, as is the separate path setting
|
||||||
self._cached_file_path = settings.BOFREQ_PATH
|
self._cached_file_path = settings.BOFREQ_PATH
|
||||||
else:
|
else:
|
||||||
self._cached_file_path = settings.DOCUMENT_PATH_PATTERN.format(doc=self)
|
self._cached_file_path = settings.DOCUMENT_PATH_PATTERN.format(doc=self)
|
||||||
|
@ -186,7 +186,7 @@ class DocumentInfo(models.Model):
|
||||||
elif self.type_id == 'review':
|
elif self.type_id == 'review':
|
||||||
# TODO: This will be wrong if a review is updated on the same day it was created (or updated more than once on the same day)
|
# TODO: This will be wrong if a review is updated on the same day it was created (or updated more than once on the same day)
|
||||||
self._cached_base_name = "%s.txt" % self.name
|
self._cached_base_name = "%s.txt" % self.name
|
||||||
elif self.type_id == 'bofreq':
|
elif self.type_id in ['bofreq', 'statement']:
|
||||||
self._cached_base_name = "%s-%s.md" % (self.name, self.rev)
|
self._cached_base_name = "%s-%s.md" % (self.name, self.rev)
|
||||||
else:
|
else:
|
||||||
if self.rev:
|
if self.rev:
|
||||||
|
@ -1290,7 +1290,11 @@ EVENT_TYPES = [
|
||||||
("removed_related_ipr", "Removed related IPR"),
|
("removed_related_ipr", "Removed related IPR"),
|
||||||
|
|
||||||
# Bofreq Editor events
|
# Bofreq Editor events
|
||||||
("changed_editors", "Changed BOF Request editors")
|
("changed_editors", "Changed BOF Request editors"),
|
||||||
|
|
||||||
|
# Statement events
|
||||||
|
("published_statement", "Published statement"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
class DocEvent(models.Model):
|
class DocEvent(models.Model):
|
||||||
|
|
|
@ -261,6 +261,17 @@ class SearchTests(TestCase):
|
||||||
parsed = urlparse(r["Location"])
|
parsed = urlparse(r["Location"])
|
||||||
self.assertEqual(parsed.path, urlreverse('ietf.doc.views_search.search'))
|
self.assertEqual(parsed.path, urlreverse('ietf.doc.views_search.search'))
|
||||||
self.assertEqual(parse_qs(parsed.query)["name"][0], "draft-ietf-doesnotexist-42")
|
self.assertEqual(parse_qs(parsed.query)["name"][0], "draft-ietf-doesnotexist-42")
|
||||||
|
|
||||||
|
def test_search_rfc(self):
|
||||||
|
rfc = WgRfcFactory(name="rfc0000")
|
||||||
|
|
||||||
|
# search for existing RFC should redirect directly to the RFC page
|
||||||
|
r = self.client.get(urlreverse('ietf.doc.views_search.search_for_name', kwargs=dict(name=rfc.name)))
|
||||||
|
self.assertRedirects(r, f'/doc/{rfc.name}/', status_code=302, target_status_code=200)
|
||||||
|
|
||||||
|
# search for existing RFC with revision number should redirect to the RFC page
|
||||||
|
r = self.client.get(urlreverse('ietf.doc.views_search.search_for_name', kwargs=dict(name=rfc.name + "-99")), follow=True)
|
||||||
|
self.assertRedirects(r, f'/doc/{rfc.name}/', status_code=302, target_status_code=200)
|
||||||
|
|
||||||
def test_frontpage(self):
|
def test_frontpage(self):
|
||||||
r = self.client.get("/")
|
r = self.client.get("/")
|
||||||
|
@ -1617,6 +1628,10 @@ class DocTestCase(TestCase):
|
||||||
CharterFactory(name='charter-ietf-mars')
|
CharterFactory(name='charter-ietf-mars')
|
||||||
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name="charter-ietf-mars")))
|
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name="charter-ietf-mars")))
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
def test_incorrect_rfc_url(self):
|
||||||
|
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name="rfc8989", rev="00")))
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
def test_document_conflict_review(self):
|
def test_document_conflict_review(self):
|
||||||
ConflictReviewFactory(name='conflict-review-imaginary-irtf-submission')
|
ConflictReviewFactory(name='conflict-review-imaginary-irtf-submission')
|
||||||
|
@ -1993,6 +2008,12 @@ class DocTestCase(TestCase):
|
||||||
#
|
#
|
||||||
self.assertNotIn('day', entry)
|
self.assertNotIn('day', entry)
|
||||||
|
|
||||||
|
# test for incorrect case - revision for RFC
|
||||||
|
rfc = WgRfcFactory(name="rfc0000")
|
||||||
|
url = urlreverse('ietf.doc.views_doc.document_bibtex', kwargs=dict(name=rfc.name, rev='00'))
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
april1 = IndividualRfcFactory.create(
|
april1 = IndividualRfcFactory.create(
|
||||||
stream_id = 'ise',
|
stream_id = 'ise',
|
||||||
states = [('draft','rfc'),('draft-iesg','pub')],
|
states = [('draft','rfc'),('draft-iesg','pub')],
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright The IETF Trust 2011-2020, All Rights Reserved
|
# Copyright The IETF Trust 2011-2023, All Rights Reserved
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
@ -311,6 +311,24 @@ class ChangeStateTests(TestCase):
|
||||||
# action holders
|
# action holders
|
||||||
self.assertCountEqual(draft.action_holders.all(), [ad])
|
self.assertCountEqual(draft.action_holders.all(), [ad])
|
||||||
|
|
||||||
|
def test_iesg_state_edit_button(self):
|
||||||
|
ad = Person.objects.get(user__username="ad")
|
||||||
|
draft = WgDraftFactory(ad=ad,states=[('draft','active'),('draft-iesg','ad-eval')])
|
||||||
|
|
||||||
|
url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name))
|
||||||
|
self.client.login(username="ad", password="ad+password")
|
||||||
|
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertIn("Edit", q('tr:contains("IESG state")').text())
|
||||||
|
|
||||||
|
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="dead"))
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertNotIn("Edit", q('tr:contains("IESG state")').text())
|
||||||
|
|
||||||
|
|
||||||
class EditInfoTests(TestCase):
|
class EditInfoTests(TestCase):
|
||||||
def test_edit_info(self):
|
def test_edit_info(self):
|
||||||
|
|
359
ietf/doc/tests_statement.py
Normal file
359
ietf/doc/tests_statement.py
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
# Copyright The IETF Trust 2023, All Rights Reserved
|
||||||
|
|
||||||
|
import debug # pyflakes:ignore
|
||||||
|
|
||||||
|
from pyquery import PyQuery
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.urls import reverse as urlreverse
|
||||||
|
|
||||||
|
from ietf.doc.factories import StatementFactory, DocEventFactory
|
||||||
|
from ietf.doc.models import Document, DocAlias, State, NewRevisionDocEvent
|
||||||
|
from ietf.group.models import Group
|
||||||
|
from ietf.person.factories import PersonFactory
|
||||||
|
from ietf.utils.mail import outbox, empty_outbox
|
||||||
|
from ietf.utils.test_utils import (
|
||||||
|
TestCase,
|
||||||
|
reload_db_objects,
|
||||||
|
login_testing_unauthorized,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class StatementsTestCase(TestCase):
|
||||||
|
settings_temp_path_overrides = TestCase.settings_temp_path_overrides + [
|
||||||
|
"DOCUMENT_PATH_PATTERN"
|
||||||
|
]
|
||||||
|
|
||||||
|
def extract_content(self, response):
|
||||||
|
if not hasattr(response, "_cached_extraction"):
|
||||||
|
response._cached_extraction = list(response.streaming_content)[0].decode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
return response._cached_extraction
|
||||||
|
|
||||||
|
def write_statement_markdown_file(self, statement):
|
||||||
|
(
|
||||||
|
Path(settings.DOCUMENT_PATH_PATTERN.format(doc=statement))
|
||||||
|
/ ("%s-%s.md" % (statement.name, statement.rev))
|
||||||
|
).write_text(
|
||||||
|
"""# This is a test statement.
|
||||||
|
Version: {statement.rev}
|
||||||
|
|
||||||
|
## A section
|
||||||
|
|
||||||
|
This test section has some text.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
def write_statement_pdf_file(self, statement):
|
||||||
|
(
|
||||||
|
Path(settings.DOCUMENT_PATH_PATTERN.format(doc=statement))
|
||||||
|
/ ("%s-%s.pdf" % (statement.name, statement.rev))
|
||||||
|
).write_text(
|
||||||
|
f"{statement.rev} This is not valid PDF, but the test does not need it to be"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_statement_doc_view(self):
|
||||||
|
doc = StatementFactory()
|
||||||
|
self.write_statement_markdown_file(doc)
|
||||||
|
url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name))
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
q = PyQuery(response.content)
|
||||||
|
self.assertEqual(q("#statement-state").text(), "Active")
|
||||||
|
self.assertEqual(q("#statement-type").text(), "IAB Statement")
|
||||||
|
self.assertIn("has some text", q(".card-body").text())
|
||||||
|
published = doc.docevent_set.filter(type="published_statement").last().time
|
||||||
|
self.assertIn(
|
||||||
|
published.astimezone(ZoneInfo(settings.TIME_ZONE)).date().isoformat(),
|
||||||
|
q("#published").text(),
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.set_state(State.objects.get(type_id="statement", slug="replaced"))
|
||||||
|
doc2 = StatementFactory()
|
||||||
|
doc2.relateddocument_set.create(
|
||||||
|
relationship_id="replaces", target=doc.docalias.first()
|
||||||
|
)
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
q = PyQuery(response.content)
|
||||||
|
self.assertEqual(q("#statement-state").text(), "Replaced")
|
||||||
|
self.assertEqual(q("#statement-type").text(), "Replaced IAB Statement")
|
||||||
|
self.assertEqual(q("#statement-type").next().text(), f"Replaced by {doc2.name}")
|
||||||
|
|
||||||
|
url = urlreverse(
|
||||||
|
"ietf.doc.views_doc.document_main", kwargs=dict(name=doc2.name)
|
||||||
|
)
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
q = PyQuery(response.content)
|
||||||
|
self.assertEqual(q("#statement-type").text(), "IAB Statement")
|
||||||
|
self.assertEqual(q("#statement-type").next().text(), f"Replaces {doc.name}")
|
||||||
|
|
||||||
|
def test_serve_pdf(self):
|
||||||
|
url = urlreverse(
|
||||||
|
"ietf.doc.views_statement.serve_pdf",
|
||||||
|
kwargs=dict(name="statement-does-not-exist"),
|
||||||
|
)
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
doc = StatementFactory()
|
||||||
|
url = urlreverse(
|
||||||
|
"ietf.doc.views_statement.serve_pdf", kwargs=dict(name=doc.name)
|
||||||
|
)
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 404) # File not found
|
||||||
|
|
||||||
|
self.write_statement_pdf_file(doc)
|
||||||
|
doc.rev = "01"
|
||||||
|
e = DocEventFactory(type="published_statement", doc=doc, rev=doc.rev)
|
||||||
|
doc.save_with_history([e])
|
||||||
|
self.write_statement_pdf_file(doc)
|
||||||
|
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.get("Content-Type"), "application/pdf")
|
||||||
|
self.assertTrue(
|
||||||
|
self.extract_content(r).startswith(doc.rev)
|
||||||
|
) # relies on test doc not actually being pdf
|
||||||
|
|
||||||
|
url = urlreverse(
|
||||||
|
"ietf.doc.views_statement.serve_pdf", kwargs=dict(name=doc.name, rev="00")
|
||||||
|
)
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(self.extract_content(r).startswith("00 "))
|
||||||
|
url = urlreverse(
|
||||||
|
"ietf.doc.views_statement.serve_pdf", kwargs=dict(name=doc.name, rev="01")
|
||||||
|
)
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(self.extract_content(r).startswith("01 "))
|
||||||
|
|
||||||
|
def test_submit(self):
|
||||||
|
doc = StatementFactory()
|
||||||
|
url = urlreverse("ietf.doc.views_statement.submit", kwargs=dict(name=doc.name))
|
||||||
|
|
||||||
|
rev = doc.rev
|
||||||
|
r = self.client.post(
|
||||||
|
url, {"statement_submission": "enter", "statement_content": "# oiwefrase"}
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
doc = reload_db_objects(doc)
|
||||||
|
self.assertEqual(rev, doc.rev)
|
||||||
|
|
||||||
|
nobody = PersonFactory()
|
||||||
|
self.client.login(
|
||||||
|
username=nobody.user.username, password=nobody.user.username + "+password"
|
||||||
|
)
|
||||||
|
r = self.client.post(
|
||||||
|
url, {"statement_submission": "enter", "statement_content": "# oiwefrase"}
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
doc = reload_db_objects(doc)
|
||||||
|
self.assertEqual(rev, doc.rev)
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
for username in ["secretary"]: # There is potential for expanding this list
|
||||||
|
self.client.login(username=username, password=username + "+password")
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
file = SimpleUploadedFile(
|
||||||
|
"random.pdf",
|
||||||
|
b"not valid pdf",
|
||||||
|
content_type="application/pdf",
|
||||||
|
)
|
||||||
|
for postdict in [
|
||||||
|
{
|
||||||
|
"statement_submission": "enter",
|
||||||
|
"statement_content": f"# {username}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"statement_submission": "upload",
|
||||||
|
"statement_file": file,
|
||||||
|
},
|
||||||
|
]:
|
||||||
|
docevent_count = doc.docevent_set.count()
|
||||||
|
empty_outbox()
|
||||||
|
r = self.client.post(url, postdict)
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
doc = reload_db_objects(doc)
|
||||||
|
self.assertEqual("%02d" % (int(rev) + 1), doc.rev)
|
||||||
|
if postdict["statement_submission"] == "enter":
|
||||||
|
self.assertEqual(f"# {username}", doc.text())
|
||||||
|
else:
|
||||||
|
self.assertEqual("not valid pdf", doc.text())
|
||||||
|
self.assertEqual(docevent_count + 1, doc.docevent_set.count())
|
||||||
|
self.assertEqual(0, len(outbox))
|
||||||
|
rev = doc.rev
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
def test_start_new_statement(self):
|
||||||
|
url = urlreverse("ietf.doc.views_statement.new_statement")
|
||||||
|
login_testing_unauthorized(self, "secretary", url)
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertContains(
|
||||||
|
r,
|
||||||
|
"Replace this with the content of the statement in markdown source",
|
||||||
|
status_code=200,
|
||||||
|
)
|
||||||
|
group = Group.objects.get(acronym="iab")
|
||||||
|
r = self.client.post(
|
||||||
|
url,
|
||||||
|
dict(
|
||||||
|
group=group.pk,
|
||||||
|
title="default",
|
||||||
|
statement_submission="enter",
|
||||||
|
statement_content=render_to_string(
|
||||||
|
"doc/statement/statement_template.md", {"settings": settings}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertContains(r, "The example content may not be saved.", status_code=200)
|
||||||
|
|
||||||
|
file = SimpleUploadedFile(
|
||||||
|
"random.pdf",
|
||||||
|
b"not valid pdf",
|
||||||
|
content_type="application/pdf",
|
||||||
|
)
|
||||||
|
group = Group.objects.get(acronym="iab")
|
||||||
|
for postdict in [
|
||||||
|
dict(
|
||||||
|
group=group.pk,
|
||||||
|
title="title one",
|
||||||
|
statement_submission="enter",
|
||||||
|
statement_content="some stuff",
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
group=group.pk,
|
||||||
|
title="title two",
|
||||||
|
statement_submission="upload",
|
||||||
|
statement_file=file,
|
||||||
|
),
|
||||||
|
]:
|
||||||
|
empty_outbox()
|
||||||
|
r = self.client.post(url, postdict)
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
name = f"statement-{group.acronym}-{postdict['title']}".replace(
|
||||||
|
" ", "-"
|
||||||
|
) # cheap slugification
|
||||||
|
statement = Document.objects.filter(
|
||||||
|
name=name, type_id="statement"
|
||||||
|
).first()
|
||||||
|
self.assertIsNotNone(statement)
|
||||||
|
self.assertIsNotNone(DocAlias.objects.filter(name=name).first())
|
||||||
|
self.assertEqual(statement.title, postdict["title"])
|
||||||
|
self.assertEqual(statement.rev, "00")
|
||||||
|
self.assertEqual(statement.get_state_slug(), "active")
|
||||||
|
self.assertEqual(
|
||||||
|
statement.latest_event(NewRevisionDocEvent).rev, "00"
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(statement.latest_event(type="published_statement"))
|
||||||
|
if postdict["statement_submission"] == "enter":
|
||||||
|
self.assertEqual(statement.text_or_error(), "some stuff")
|
||||||
|
else:
|
||||||
|
self.assertTrue(statement.uploaded_filename.endswith("pdf"))
|
||||||
|
self.assertEqual(len(outbox), 0)
|
||||||
|
|
||||||
|
existing_statement = StatementFactory()
|
||||||
|
for postdict in [
|
||||||
|
dict(
|
||||||
|
group=group.pk,
|
||||||
|
title="",
|
||||||
|
statement_submission="enter",
|
||||||
|
statement_content="some stuff",
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
group=group.pk,
|
||||||
|
title="a title",
|
||||||
|
statement_submission="enter",
|
||||||
|
statement_content="",
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
group=group.pk,
|
||||||
|
title=existing_statement.title,
|
||||||
|
statement_submission="enter",
|
||||||
|
statement_content="some stuff",
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
group=group.pk,
|
||||||
|
title="森川",
|
||||||
|
statement_submission="enter",
|
||||||
|
statement_content="some stuff",
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
group=group.pk,
|
||||||
|
title="a title",
|
||||||
|
statement_submission="",
|
||||||
|
statement_content="some stuff",
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
group="",
|
||||||
|
title="a title",
|
||||||
|
statement_submission="enter",
|
||||||
|
statement_content="some stuff",
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
group=0,
|
||||||
|
title="a title",
|
||||||
|
statement_submission="enter",
|
||||||
|
statement_content="some stuff",
|
||||||
|
),
|
||||||
|
]:
|
||||||
|
r = self.client.post(url, postdict)
|
||||||
|
self.assertEqual(r.status_code, 200, f"Wrong status_code for {postdict}")
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertTrue(
|
||||||
|
q("form div.is-invalid"), f"Expected an error for {postdict}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_submit_non_markdown_formats(self):
|
||||||
|
doc = StatementFactory()
|
||||||
|
|
||||||
|
file = SimpleUploadedFile(
|
||||||
|
"random.pdf",
|
||||||
|
b"01 This is not valid PDF, but the test does not need it to be",
|
||||||
|
content_type="application/pdf",
|
||||||
|
)
|
||||||
|
|
||||||
|
url = urlreverse("ietf.doc.views_statement.submit", kwargs=dict(name=doc.name))
|
||||||
|
login_testing_unauthorized(self, "secretary", url)
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
"statement_submission": "upload",
|
||||||
|
"statement_file": file,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
self.assertEqual(
|
||||||
|
r["Location"],
|
||||||
|
urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name)),
|
||||||
|
)
|
||||||
|
|
||||||
|
doc = reload_db_objects(doc)
|
||||||
|
self.assertEqual(doc.rev, "01")
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertEqual(
|
||||||
|
q("#id_statement_content").text().strip(),
|
||||||
|
"The current revision of this statement is in pdf format",
|
||||||
|
)
|
||||||
|
|
||||||
|
file = SimpleUploadedFile(
|
||||||
|
"random.mp4", b"29ucdvn2o09hano5", content_type="video/mp4"
|
||||||
|
)
|
||||||
|
r = self.client.post(
|
||||||
|
url, {"statement_submission": "upload", "statement_file": file}
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertTrue("Unexpected content" in q("#id_statement_file").next().text())
|
|
@ -14,7 +14,7 @@ from textwrap import wrap
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse as urlreverse
|
from django.urls import reverse as urlreverse
|
||||||
|
|
||||||
from ietf.doc.factories import DocumentFactory, IndividualRfcFactory, WgRfcFactory
|
from ietf.doc.factories import DocumentFactory, IndividualRfcFactory, WgRfcFactory, DocEventFactory
|
||||||
from ietf.doc.models import ( Document, DocAlias, State, DocEvent,
|
from ietf.doc.models import ( Document, DocAlias, State, DocEvent,
|
||||||
BallotPositionDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent )
|
BallotPositionDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent )
|
||||||
from ietf.doc.utils import create_ballot_if_not_open
|
from ietf.doc.utils import create_ballot_if_not_open
|
||||||
|
@ -86,6 +86,16 @@ class StatusChangeTests(TestCase):
|
||||||
status_change = Document.objects.get(name='status-change-imaginary-new2')
|
status_change = Document.objects.get(name='status-change-imaginary-new2')
|
||||||
self.assertIsNone(status_change.ad)
|
self.assertIsNone(status_change.ad)
|
||||||
|
|
||||||
|
# Verify that the right thing happens if a control along the way uppercases RFC
|
||||||
|
r = self.client.post(url,dict(
|
||||||
|
document_name="imaginary-new3",title="A new imaginary status change",
|
||||||
|
create_in_state=state_strpk,notify='ipu@ietf.org',new_relation_row_blah="RFC9999",
|
||||||
|
statchg_relation_row_blah="tois")
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
status_change = Document.objects.get(name='status-change-imaginary-new3')
|
||||||
|
self.assertTrue(status_change.relateddocument_set.filter(relationship__slug='tois',target__name='rfc9999'))
|
||||||
|
|
||||||
|
|
||||||
def test_change_state(self):
|
def test_change_state(self):
|
||||||
|
|
||||||
|
@ -289,7 +299,19 @@ class StatusChangeTests(TestCase):
|
||||||
self.assertEqual(r.status_code,200)
|
self.assertEqual(r.status_code,200)
|
||||||
self.assertContains(r, 'RFC9999 from Proposed Standard to Internet Standard')
|
self.assertContains(r, 'RFC9999 from Proposed Standard to Internet Standard')
|
||||||
self.assertContains(r, 'RFC9998 from Informational to Historic')
|
self.assertContains(r, 'RFC9998 from Informational to Historic')
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertEqual(len(q("button[name='send_last_call_request']")), 1)
|
||||||
|
|
||||||
|
# Make sure request LC isn't offered with no responsible AD.
|
||||||
|
doc.ad = None
|
||||||
|
doc.save_with_history([DocEventFactory(doc=doc)])
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code,200)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertEqual(len(q("button[name='send_last_call_request']")), 0)
|
||||||
|
doc.ad = Person.objects.get(name='Ad No2')
|
||||||
|
doc.save_with_history([DocEventFactory(doc=doc)])
|
||||||
|
|
||||||
# request last call
|
# request last call
|
||||||
messages_before = len(outbox)
|
messages_before = len(outbox)
|
||||||
r = self.client.post(url,dict(last_call_text='stuff',send_last_call_request='Save+and+Request+Last+Call'))
|
r = self.client.post(url,dict(last_call_text='stuff',send_last_call_request='Save+and+Request+Last+Call'))
|
||||||
|
|
|
@ -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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
||||||
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
|
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
|
||||||
|
@ -37,7 +37,7 @@ from django.conf import settings
|
||||||
from django.urls import include
|
from django.urls import include
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
|
||||||
from ietf.doc import views_search, views_draft, views_ballot, views_status_change, views_doc, views_downref, views_stats, views_help, views_bofreq
|
from ietf.doc import views_search, views_draft, views_ballot, views_status_change, views_doc, views_downref, views_stats, views_help, views_bofreq, views_statement
|
||||||
from ietf.utils.urls import url
|
from ietf.utils.urls import url
|
||||||
|
|
||||||
session_patterns = [
|
session_patterns = [
|
||||||
|
@ -57,6 +57,7 @@ urlpatterns = [
|
||||||
url(r'^start-rfc-status-change/(?:%(name)s/)?$' % settings.URL_REGEXPS, views_status_change.start_rfc_status_change),
|
url(r'^start-rfc-status-change/(?:%(name)s/)?$' % settings.URL_REGEXPS, views_status_change.start_rfc_status_change),
|
||||||
url(r'^bof-requests/?$', views_bofreq.bof_requests),
|
url(r'^bof-requests/?$', views_bofreq.bof_requests),
|
||||||
url(r'^bof-requests/new/$', views_bofreq.new_bof_request),
|
url(r'^bof-requests/new/$', views_bofreq.new_bof_request),
|
||||||
|
url(r'^statement/new/$', views_statement.new_statement),
|
||||||
url(r'^iesg/?$', views_search.drafts_in_iesg_process),
|
url(r'^iesg/?$', views_search.drafts_in_iesg_process),
|
||||||
url(r'^email-aliases/?$', views_doc.email_aliases),
|
url(r'^email-aliases/?$', views_doc.email_aliases),
|
||||||
url(r'^downref/?$', views_downref.downref_registry),
|
url(r'^downref/?$', views_downref.downref_registry),
|
||||||
|
@ -169,6 +170,7 @@ urlpatterns = [
|
||||||
|
|
||||||
url(r'^%(charter)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_charter')),
|
url(r'^%(charter)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_charter')),
|
||||||
url(r'^%(bofreq)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_bofreq')),
|
url(r'^%(bofreq)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_bofreq')),
|
||||||
|
url(r'^%(statement)s/' % settings.URL_REGEXPS, include('ietf.doc.urls_statement')),
|
||||||
url(r'^%(name)s/conflict-review/' % settings.URL_REGEXPS, include('ietf.doc.urls_conflict_review')),
|
url(r'^%(name)s/conflict-review/' % settings.URL_REGEXPS, include('ietf.doc.urls_conflict_review')),
|
||||||
url(r'^%(name)s/status-change/' % settings.URL_REGEXPS, include('ietf.doc.urls_status_change')),
|
url(r'^%(name)s/status-change/' % settings.URL_REGEXPS, include('ietf.doc.urls_status_change')),
|
||||||
url(r'^%(name)s/material/' % settings.URL_REGEXPS, include('ietf.doc.urls_material')),
|
url(r'^%(name)s/material/' % settings.URL_REGEXPS, include('ietf.doc.urls_material')),
|
||||||
|
|
10
ietf/doc/urls_statement.py
Normal file
10
ietf/doc/urls_statement.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Copyright The IETF Trust 2023, All Rights Reserved
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from ietf.doc import views_statement
|
||||||
|
from ietf.utils.urls import url
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r"^(?:%(rev)s/)?pdf/$" % settings.URL_REGEXPS, views_statement.serve_pdf),
|
||||||
|
url(r"^submit/$", views_statement.submit),
|
||||||
|
]
|
|
@ -721,7 +721,7 @@ def ballot_rfceditornote(request, name):
|
||||||
e = WriteupDocEvent(doc=doc, rev=doc.rev, by=login)
|
e = WriteupDocEvent(doc=doc, rev=doc.rev, by=login)
|
||||||
e.by = login
|
e.by = login
|
||||||
e.type = "changed_rfc_editor_note_text"
|
e.type = "changed_rfc_editor_note_text"
|
||||||
e.desc = "RFC Editor Note was changed"
|
e.desc = f"RFC Editor Note was changed to \n{t}"
|
||||||
e.text = t.rstrip()
|
e.text = t.rstrip()
|
||||||
e.save()
|
e.save()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright The IETF Trust 2009-2022, All Rights Reserved
|
# Copyright The IETF Trust 2009-2023, All Rights Reserved
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Parts Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
|
# Parts Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||||
|
@ -79,7 +79,7 @@ from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||||
from ietf.meeting.models import Session
|
from ietf.meeting.models import Session
|
||||||
from ietf.meeting.utils import group_sessions, get_upcoming_manageable_sessions, sort_sessions, add_event_info_to_session_qs
|
from ietf.meeting.utils import group_sessions, get_upcoming_manageable_sessions, sort_sessions, add_event_info_to_session_qs
|
||||||
from ietf.review.models import ReviewAssignment
|
from ietf.review.models import ReviewAssignment
|
||||||
from ietf.review.utils import can_request_review_of_doc, review_assignments_to_list_for_docs
|
from ietf.review.utils import can_request_review_of_doc, review_assignments_to_list_for_docs, review_requests_to_list_for_docs
|
||||||
from ietf.review.utils import no_review_from_teams_on_doc
|
from ietf.review.utils import no_review_from_teams_on_doc
|
||||||
from ietf.utils import markup_txt, log, markdown
|
from ietf.utils import markup_txt, log, markdown
|
||||||
from ietf.utils.draft import PlaintextDraft
|
from ietf.utils.draft import PlaintextDraft
|
||||||
|
@ -191,6 +191,9 @@ def interesting_doc_relations(doc):
|
||||||
return interesting_relations_that, interesting_relations_that_doc
|
return interesting_relations_that, interesting_relations_that_doc
|
||||||
|
|
||||||
def document_main(request, name, rev=None, document_html=False):
|
def document_main(request, name, rev=None, document_html=False):
|
||||||
|
if name.startswith("rfc") and rev is not None:
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
doc = get_object_or_404(Document.objects.select_related(), docalias__name=name)
|
doc = get_object_or_404(Document.objects.select_related(), docalias__name=name)
|
||||||
|
|
||||||
# take care of possible redirections
|
# take care of possible redirections
|
||||||
|
@ -498,6 +501,7 @@ def document_main(request, name, rev=None, document_html=False):
|
||||||
started_iesg_process = doc.latest_event(type="started_iesg_process")
|
started_iesg_process = doc.latest_event(type="started_iesg_process")
|
||||||
|
|
||||||
review_assignments = review_assignments_to_list_for_docs([doc]).get(doc.name, [])
|
review_assignments = review_assignments_to_list_for_docs([doc]).get(doc.name, [])
|
||||||
|
review_requests = review_requests_to_list_for_docs([doc]).get(doc.name, [])
|
||||||
no_review_from_teams = no_review_from_teams_on_doc(doc, rev or doc.rev)
|
no_review_from_teams = no_review_from_teams_on_doc(doc, rev or doc.rev)
|
||||||
|
|
||||||
exp_comment = doc.latest_event(IanaExpertDocEvent,type="comment")
|
exp_comment = doc.latest_event(IanaExpertDocEvent,type="comment")
|
||||||
|
@ -512,11 +516,13 @@ def document_main(request, name, rev=None, document_html=False):
|
||||||
# Do not show the Auth48 URL in the "Additional URLs" section
|
# Do not show the Auth48 URL in the "Additional URLs" section
|
||||||
additional_urls = doc.documenturl_set.exclude(tag_id='auth48')
|
additional_urls = doc.documenturl_set.exclude(tag_id='auth48')
|
||||||
|
|
||||||
# Stream description passing test
|
# Stream description and name passing test
|
||||||
if doc.stream != None:
|
if doc.stream != None:
|
||||||
stream_desc = doc.stream.desc
|
stream_desc = doc.stream.desc
|
||||||
|
stream = "draft-stream-" + doc.stream.slug
|
||||||
else:
|
else:
|
||||||
stream_desc = "(None)"
|
stream_desc = "(None)"
|
||||||
|
stream = "(None)"
|
||||||
|
|
||||||
html = None
|
html = None
|
||||||
js = None
|
js = None
|
||||||
|
@ -554,6 +560,7 @@ def document_main(request, name, rev=None, document_html=False):
|
||||||
split_content=split_content,
|
split_content=split_content,
|
||||||
revisions=simple_diff_revisions if document_html else revisions,
|
revisions=simple_diff_revisions if document_html else revisions,
|
||||||
snapshot=snapshot,
|
snapshot=snapshot,
|
||||||
|
stream=stream,
|
||||||
stream_desc=stream_desc,
|
stream_desc=stream_desc,
|
||||||
latest_revision=latest_revision,
|
latest_revision=latest_revision,
|
||||||
latest_rev=latest_rev,
|
latest_rev=latest_rev,
|
||||||
|
@ -614,6 +621,7 @@ def document_main(request, name, rev=None, document_html=False):
|
||||||
actions=actions,
|
actions=actions,
|
||||||
presentations=presentations,
|
presentations=presentations,
|
||||||
review_assignments=review_assignments,
|
review_assignments=review_assignments,
|
||||||
|
review_requests=review_requests,
|
||||||
no_review_from_teams=no_review_from_teams,
|
no_review_from_teams=no_review_from_teams,
|
||||||
due_date=due_date,
|
due_date=due_date,
|
||||||
diff_revisions=diff_revisions
|
diff_revisions=diff_revisions
|
||||||
|
@ -843,7 +851,40 @@ def document_main(request, name, rev=None, document_html=False):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if doc.type_id == "statement":
|
||||||
|
if doc.uploaded_filename:
|
||||||
|
basename = doc.uploaded_filename.split(".")[0] # strip extension
|
||||||
|
else:
|
||||||
|
basename = f"{doc.name}-{doc.rev}"
|
||||||
|
variants = set([match.name.split(".")[1] for match in Path(doc.get_file_path()).glob(f"{basename}.*")])
|
||||||
|
inlineable = any([ext in variants for ext in ["md", "txt"]])
|
||||||
|
if inlineable:
|
||||||
|
content = markdown.markdown(doc.text_or_error())
|
||||||
|
else:
|
||||||
|
content = "No format available to display inline"
|
||||||
|
if "pdf" in variants:
|
||||||
|
pdf_url = urlreverse(
|
||||||
|
"ietf.doc.views_statement.serve_pdf",
|
||||||
|
kwargs=dict(name=doc.name, rev=doc.rev),
|
||||||
|
)
|
||||||
|
content += f" - Download [pdf]({pdf_url})"
|
||||||
|
content = markdown.markdown(content)
|
||||||
|
can_manage = has_role(request.user,["Secretariat"]) # Add IAB or IESG as appropriate
|
||||||
|
interesting_relations_that, interesting_relations_that_doc = interesting_doc_relations(doc)
|
||||||
|
published = doc.latest_event(type="published_statement").time
|
||||||
|
|
||||||
|
return render(request, "doc/document_statement.html",
|
||||||
|
dict(doc=doc,
|
||||||
|
top=top,
|
||||||
|
revisions=revisions,
|
||||||
|
latest_rev=latest_rev,
|
||||||
|
published=published,
|
||||||
|
content=content,
|
||||||
|
snapshot=snapshot,
|
||||||
|
replaces=interesting_relations_that_doc.filter(relationship="replaces"),
|
||||||
|
replaced_by=interesting_relations_that.filter(relationship="replaces"),
|
||||||
|
can_manage=can_manage,
|
||||||
|
))
|
||||||
|
|
||||||
raise Http404("Document not found: %s" % (name + ("-%s"%rev if rev else "")))
|
raise Http404("Document not found: %s" % (name + ("-%s"%rev if rev else "")))
|
||||||
|
|
||||||
|
@ -1081,6 +1122,9 @@ def document_history(request, name):
|
||||||
|
|
||||||
|
|
||||||
def document_bibtex(request, name, rev=None):
|
def document_bibtex(request, name, rev=None):
|
||||||
|
if name.startswith('rfc') and rev is not None:
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
# Make sure URL_REGEXPS did not grab too much for the rev number
|
# Make sure URL_REGEXPS did not grab too much for the rev number
|
||||||
if rev != None and len(rev) != 2:
|
if rev != None and len(rev) != 2:
|
||||||
mo = re.search(r"^(?P<m>[0-9]{1,2})-(?P<n>[0-9]{2})$", rev)
|
mo = re.search(r"^(?P<m>[0-9]{1,2})-(?P<n>[0-9]{2})$", rev)
|
||||||
|
@ -1113,6 +1157,11 @@ def document_bibtex(request, name, rev=None):
|
||||||
else:
|
else:
|
||||||
doi = None
|
doi = None
|
||||||
|
|
||||||
|
if doc.is_dochistory():
|
||||||
|
latest_event = doc.latest_event(type='new_revision', rev=rev)
|
||||||
|
if latest_event:
|
||||||
|
doc.pub_date = latest_event.time
|
||||||
|
|
||||||
return render(request, "doc/document_bibtex.bib",
|
return render(request, "doc/document_bibtex.bib",
|
||||||
dict(doc=doc,
|
dict(doc=doc,
|
||||||
replaced_by=replaced_by,
|
replaced_by=replaced_by,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# Copyright The IETF Trust 2013-2023, All Rights Reserved
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
|
||||||
|
@ -5,11 +7,13 @@ from ietf.doc.models import State, StateType, IESG_SUBSTATE_TAGS
|
||||||
from ietf.name.models import DocRelationshipName, DocTagName
|
from ietf.name.models import DocRelationshipName, DocTagName
|
||||||
from ietf.doc.utils import get_tags_for_stream_id
|
from ietf.doc.utils import get_tags_for_stream_id
|
||||||
|
|
||||||
def state_help(request, type):
|
def state_help(request, type=None):
|
||||||
slug, title = {
|
slug, title = {
|
||||||
"draft-iesg": ("draft-iesg", "IESG States for Internet-Drafts"),
|
"draft-iesg": ("draft-iesg", "IESG States for Internet-Drafts"),
|
||||||
"draft-rfceditor": ("draft-rfceditor", "RFC Editor States for Internet-Drafts"),
|
"draft-rfceditor": ("draft-rfceditor", "RFC Editor States for Internet-Drafts"),
|
||||||
"draft-iana-action": ("draft-iana-action", "IANA Action States for Internet-Drafts"),
|
"draft-iana-action": ("draft-iana-action", "IANA Action States for Internet-Drafts"),
|
||||||
|
"draft-iana-review": ("draft-iana-review", "IANA Review States for Internet-Drafts"),
|
||||||
|
"draft-iana-experts": ("draft-iana-experts", "IANA Expert Review States for Internet-Drafts"),
|
||||||
"draft-stream-ietf": ("draft-stream-ietf", "IETF Stream States for Internet-Drafts"),
|
"draft-stream-ietf": ("draft-stream-ietf", "IETF Stream States for Internet-Drafts"),
|
||||||
"draft-stream-irtf": ("draft-stream-irtf", "IRTF Stream States for Internet-Drafts"),
|
"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-ise": ("draft-stream-ise", "ISE Stream States for Internet-Drafts"),
|
||||||
|
@ -19,10 +23,11 @@ def state_help(request, type):
|
||||||
"status-change": ("statchg", "RFC Status Change States"),
|
"status-change": ("statchg", "RFC Status Change States"),
|
||||||
"bofreq": ("bofreq", "BOF Request States"),
|
"bofreq": ("bofreq", "BOF Request States"),
|
||||||
"procmaterials": ("procmaterials", "Proceedings Materials States"),
|
"procmaterials": ("procmaterials", "Proceedings Materials States"),
|
||||||
|
"statement": {"statement", "Statement States"}
|
||||||
}.get(type, (None, None))
|
}.get(type, (None, None))
|
||||||
state_type = get_object_or_404(StateType, slug=slug)
|
state_type = get_object_or_404(StateType, slug=slug)
|
||||||
|
|
||||||
states = State.objects.filter(type=state_type).order_by("order")
|
states = State.objects.filter(used=True, type=state_type).order_by("order")
|
||||||
|
|
||||||
has_next_states = False
|
has_next_states = False
|
||||||
for state in states:
|
for state in states:
|
||||||
|
|
|
@ -389,9 +389,6 @@ def reject_reviewer_assignment(request, name, assignment_id):
|
||||||
state=review_assignment.state,
|
state=review_assignment.state,
|
||||||
)
|
)
|
||||||
|
|
||||||
policy = get_reviewer_queue_policy(review_assignment.review_request.team)
|
|
||||||
policy.return_reviewer_to_rotation_top(review_assignment.reviewer.person, form.cleaned_data['wants_to_be_next'])
|
|
||||||
|
|
||||||
msg = render_to_string("review/reviewer_assignment_rejected.txt", {
|
msg = render_to_string("review/reviewer_assignment_rejected.txt", {
|
||||||
"by": request.user.person,
|
"by": request.user.person,
|
||||||
"message_to_secretary": form.cleaned_data.get("message_to_secretary"),
|
"message_to_secretary": form.cleaned_data.get("message_to_secretary"),
|
||||||
|
@ -441,7 +438,7 @@ def withdraw_reviewer_assignment(request, name, assignment_id):
|
||||||
)
|
)
|
||||||
|
|
||||||
policy = get_reviewer_queue_policy(review_assignment.review_request.team)
|
policy = get_reviewer_queue_policy(review_assignment.review_request.team)
|
||||||
policy.return_reviewer_to_rotation_top(review_assignment.reviewer.person, True)
|
policy.set_wants_to_be_next(review_assignment.reviewer.person)
|
||||||
|
|
||||||
msg = "Review assignment withdrawn by %s"%request.user.person
|
msg = "Review assignment withdrawn by %s"%request.user.person
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ from ietf.doc.models import ( Document, DocHistory, DocAlias, State,
|
||||||
IESG_BALLOT_ACTIVE_STATES, IESG_STATCHG_CONFLREV_ACTIVE_STATES,
|
IESG_BALLOT_ACTIVE_STATES, IESG_STATCHG_CONFLREV_ACTIVE_STATES,
|
||||||
IESG_CHARTER_ACTIVE_STATES )
|
IESG_CHARTER_ACTIVE_STATES )
|
||||||
from ietf.doc.fields import select2_id_doc_name_json
|
from ietf.doc.fields import select2_id_doc_name_json
|
||||||
from ietf.doc.utils import get_search_cache_key, augment_events_with_revision
|
from ietf.doc.utils import get_search_cache_key, augment_events_with_revision, needed_ballot_positions
|
||||||
from ietf.group.models import Group
|
from ietf.group.models import Group
|
||||||
from ietf.idindex.index import active_drafts_index_by_group
|
from ietf.idindex.index import active_drafts_index_by_group
|
||||||
from ietf.name.models import DocTagName, DocTypeName, StreamName
|
from ietf.name.models import DocTagName, DocTypeName, StreamName
|
||||||
|
@ -290,8 +290,8 @@ def search_for_name(request, name):
|
||||||
redirect_to = find_unique(rev_split.group(1))
|
redirect_to = find_unique(rev_split.group(1))
|
||||||
if redirect_to:
|
if redirect_to:
|
||||||
rev = rev_split.group(2)
|
rev = rev_split.group(2)
|
||||||
# check if we can redirect directly to the rev
|
# check if we can redirect directly to the rev if it's draft, if rfc - always redirect to main page
|
||||||
if DocHistory.objects.filter(doc__docalias__name=redirect_to, rev=rev).exists():
|
if not redirect_to.startswith('rfc') and DocHistory.objects.filter(doc__docalias__name=redirect_to, rev=rev).exists():
|
||||||
return cached_redirect(cache_key, urlreverse("ietf.doc.views_doc.document_main", kwargs={ "name": redirect_to, "rev": rev }))
|
return cached_redirect(cache_key, urlreverse("ietf.doc.views_doc.document_main", kwargs={ "name": redirect_to, "rev": rev }))
|
||||||
else:
|
else:
|
||||||
return cached_redirect(cache_key, urlreverse("ietf.doc.views_doc.document_main", kwargs={ "name": redirect_to }))
|
return cached_redirect(cache_key, urlreverse("ietf.doc.views_doc.document_main", kwargs={ "name": redirect_to }))
|
||||||
|
@ -497,6 +497,7 @@ def ad_workload(request):
|
||||||
[
|
[
|
||||||
("Publication Requested Internet-Draft", False),
|
("Publication Requested Internet-Draft", False),
|
||||||
("AD Evaluation Internet-Draft", False),
|
("AD Evaluation Internet-Draft", False),
|
||||||
|
("Last Call Requested Internet-Draft", True),
|
||||||
("In Last Call Internet-Draft", True),
|
("In Last Call Internet-Draft", True),
|
||||||
("Waiting for Writeup Internet-Draft", False),
|
("Waiting for Writeup Internet-Draft", False),
|
||||||
("IESG Evaluation - Defer Internet-Draft", False),
|
("IESG Evaluation - Defer Internet-Draft", False),
|
||||||
|
@ -532,6 +533,7 @@ def ad_workload(request):
|
||||||
[
|
[
|
||||||
("Publication Requested Status Change", False),
|
("Publication Requested Status Change", False),
|
||||||
("AD Evaluation Status Change", False),
|
("AD Evaluation Status Change", False),
|
||||||
|
("Last Call Requested Status Change", True),
|
||||||
("In Last Call Status Change", True),
|
("In Last Call Status Change", True),
|
||||||
("Waiting for Writeup Status Change", False),
|
("Waiting for Writeup Status Change", False),
|
||||||
("IESG Evaluation Status Change", True),
|
("IESG Evaluation Status Change", True),
|
||||||
|
@ -705,18 +707,20 @@ def docs_for_ad(request, name):
|
||||||
|
|
||||||
for d in results:
|
for d in results:
|
||||||
d.search_heading = ad_dashboard_group(d)
|
d.search_heading = ad_dashboard_group(d)
|
||||||
#
|
|
||||||
# Additional content showing docs with blocking positions by this ad
|
# Additional content showing docs with blocking positions by this AD,
|
||||||
|
# and docs that the AD hasn't balloted on that are lacking ballot positions to progress
|
||||||
blocked_docs = []
|
blocked_docs = []
|
||||||
|
not_balloted_docs = []
|
||||||
if ad in get_active_ads():
|
if ad in get_active_ads():
|
||||||
possible_docs = Document.objects.filter(Q(states__type="draft-iesg",
|
iesg_docs = Document.objects.filter(Q(states__type="draft-iesg",
|
||||||
states__slug__in=IESG_BALLOT_ACTIVE_STATES) |
|
states__slug__in=IESG_BALLOT_ACTIVE_STATES) |
|
||||||
Q(states__type="charter",
|
Q(states__type="charter",
|
||||||
states__slug__in=IESG_CHARTER_ACTIVE_STATES) |
|
states__slug__in=IESG_CHARTER_ACTIVE_STATES) |
|
||||||
Q(states__type__in=("statchg", "conflrev"),
|
Q(states__type__in=("statchg", "conflrev"),
|
||||||
states__slug__in=IESG_STATCHG_CONFLREV_ACTIVE_STATES),
|
states__slug__in=IESG_STATCHG_CONFLREV_ACTIVE_STATES)).distinct()
|
||||||
docevent__ballotpositiondocevent__pos__blocking=True,
|
possible_docs = iesg_docs.filter(docevent__ballotpositiondocevent__pos__blocking=True,
|
||||||
docevent__ballotpositiondocevent__balloter=ad).distinct()
|
docevent__ballotpositiondocevent__balloter=ad)
|
||||||
for doc in possible_docs:
|
for doc in possible_docs:
|
||||||
ballot = doc.active_ballot()
|
ballot = doc.active_ballot()
|
||||||
if not ballot:
|
if not ballot:
|
||||||
|
@ -737,12 +741,26 @@ def docs_for_ad(request, name):
|
||||||
if blocked_docs:
|
if blocked_docs:
|
||||||
blocked_docs.sort(key=lambda d: min(p.time for p in d.blocking_positions if p.balloter==ad), reverse=True)
|
blocked_docs.sort(key=lambda d: min(p.time for p in d.blocking_positions if p.balloter==ad), reverse=True)
|
||||||
|
|
||||||
for d in blocked_docs:
|
possible_docs = iesg_docs.exclude(
|
||||||
if d.get_base_name() == 'charter-ietf-shmoo-01-04.txt':
|
Q(docevent__ballotpositiondocevent__balloter=ad)
|
||||||
print('Is in list')
|
)
|
||||||
|
for doc in possible_docs:
|
||||||
|
ballot = doc.active_ballot()
|
||||||
|
if (
|
||||||
|
not ballot
|
||||||
|
or doc.get_state_slug("draft") == "repl"
|
||||||
|
or (doc.telechat_date() and doc.telechat_date() > timezone.now().date())
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
iesg_ballot_summary = needed_ballot_positions(
|
||||||
|
doc, list(ballot.active_balloter_positions().values())
|
||||||
|
)
|
||||||
|
if re.search(r"\bNeeds\s+\d+", iesg_ballot_summary):
|
||||||
|
not_balloted_docs.append(doc)
|
||||||
|
|
||||||
return render(request, 'doc/drafts_for_ad.html', {
|
return render(request, 'doc/drafts_for_ad.html', {
|
||||||
'form':form, 'docs':results, 'meta':meta, 'ad_name': ad.plain_name(), 'blocked_docs': blocked_docs
|
'form':form, 'docs':results, 'meta':meta, 'ad_name': ad.plain_name(), 'blocked_docs': blocked_docs, 'not_balloted_docs': not_balloted_docs
|
||||||
})
|
})
|
||||||
def drafts_in_last_call(request):
|
def drafts_in_last_call(request):
|
||||||
lc_state = State.objects.get(type="draft-iesg", slug="lc").pk
|
lc_state = State.objects.get(type="draft-iesg", slug="lc").pk
|
||||||
|
|
274
ietf/doc/views_statement.py
Normal file
274
ietf/doc/views_statement.py
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
# Copyright The IETF Trust 2023, All Rights Reserved
|
||||||
|
|
||||||
|
import debug # pyflakes: ignore
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import FileResponse, Http404
|
||||||
|
from django.views.decorators.cache import cache_control
|
||||||
|
from django.shortcuts import get_object_or_404, render, redirect
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from ietf.utils import markdown
|
||||||
|
from django.utils.html import escape
|
||||||
|
|
||||||
|
from ietf.doc.models import Document, DocAlias, DocEvent, NewRevisionDocEvent, State
|
||||||
|
from ietf.group.models import Group
|
||||||
|
from ietf.ietfauth.utils import role_required
|
||||||
|
from ietf.utils.text import xslugify
|
||||||
|
from ietf.utils.textupload import get_cleaned_text_file_content
|
||||||
|
|
||||||
|
CONST_PDF_REV_NOTICE = "The current revision of this statement is in pdf format"
|
||||||
|
|
||||||
|
|
||||||
|
@cache_control(max_age=3600)
|
||||||
|
def serve_pdf(self, name, rev=None):
|
||||||
|
doc = get_object_or_404(Document, name=name)
|
||||||
|
if rev is None:
|
||||||
|
rev = doc.rev
|
||||||
|
p = Path(doc.get_file_path()).joinpath(f"{doc.name}-{rev}.pdf")
|
||||||
|
if not p.exists():
|
||||||
|
raise Http404
|
||||||
|
else:
|
||||||
|
return FileResponse(p.open(mode="rb"), content_type="application/pdf")
|
||||||
|
|
||||||
|
|
||||||
|
class StatementUploadForm(forms.Form):
|
||||||
|
ACTIONS = [
|
||||||
|
("enter", "Enter content directly"),
|
||||||
|
("upload", "Upload content from file"),
|
||||||
|
]
|
||||||
|
statement_submission = forms.ChoiceField(choices=ACTIONS, widget=forms.RadioSelect)
|
||||||
|
statement_file = forms.FileField(
|
||||||
|
label="Markdown or PDF source file to upload", required=False
|
||||||
|
)
|
||||||
|
statement_content = forms.CharField(
|
||||||
|
widget=forms.Textarea(attrs={"rows": 30}), required=False, strip=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
def require_field(f):
|
||||||
|
if not self.cleaned_data.get(f):
|
||||||
|
self.add_error(f, forms.ValidationError("You must fill in this field."))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
submission_method = self.cleaned_data.get("statement_submission")
|
||||||
|
markdown_content = ""
|
||||||
|
if submission_method == "enter":
|
||||||
|
if require_field("statement_content"):
|
||||||
|
markdown_content = self.cleaned_data["statement_content"].replace(
|
||||||
|
"\r", ""
|
||||||
|
)
|
||||||
|
default_content = render_to_string(
|
||||||
|
"doc/statement/statement_template.md", {}
|
||||||
|
)
|
||||||
|
if markdown_content == default_content:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
"The example content may not be saved. Edit it to contain the next revision statement content."
|
||||||
|
)
|
||||||
|
if markdown_content == CONST_PDF_REV_NOTICE:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
"Not proceeding with the text noting that the current version is pdf. Did you mean to upload a new PDF?"
|
||||||
|
)
|
||||||
|
elif submission_method == "upload":
|
||||||
|
if require_field("statement_file"):
|
||||||
|
content_type = self.cleaned_data["statement_file"].content_type
|
||||||
|
acceptable_types = (
|
||||||
|
"application/pdf",
|
||||||
|
) + settings.DOC_TEXT_FILE_VALID_UPLOAD_MIME_TYPES
|
||||||
|
if not content_type.startswith(
|
||||||
|
acceptable_types
|
||||||
|
): # dances around decoration of types with encoding etc.
|
||||||
|
self.add_error(
|
||||||
|
"statement_file",
|
||||||
|
forms.ValidationError(
|
||||||
|
f"Unexpected content type: Expected one of {', '.join(acceptable_types)}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
elif content_type != "application/pdf":
|
||||||
|
markdown_content = get_cleaned_text_file_content(
|
||||||
|
self.cleaned_data["statement_file"]
|
||||||
|
)
|
||||||
|
if markdown_content != "":
|
||||||
|
try:
|
||||||
|
_ = markdown.markdown(markdown_content)
|
||||||
|
except Exception as e:
|
||||||
|
raise forms.ValidationError(f"Markdown processing failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
@role_required("Secretariat")
|
||||||
|
def submit(request, name):
|
||||||
|
statement = get_object_or_404(Document, type="statement", name=name)
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = StatementUploadForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
statement_submission = form.cleaned_data["statement_submission"]
|
||||||
|
writing_pdf = (
|
||||||
|
statement_submission == "upload"
|
||||||
|
and form.cleaned_data["statement_file"].content_type
|
||||||
|
== "application/pdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
statement.rev = "%02d" % (int(statement.rev) + 1)
|
||||||
|
statement.uploaded_filename = (
|
||||||
|
f"{statement.name}-{statement.rev}.{'pdf' if writing_pdf else 'md'}"
|
||||||
|
)
|
||||||
|
e = NewRevisionDocEvent.objects.create(
|
||||||
|
type="new_revision",
|
||||||
|
doc=statement,
|
||||||
|
by=request.user.person,
|
||||||
|
rev=statement.rev,
|
||||||
|
desc="New revision available",
|
||||||
|
)
|
||||||
|
statement.save_with_history([e])
|
||||||
|
markdown_content = ""
|
||||||
|
if statement_submission == "upload":
|
||||||
|
if not writing_pdf:
|
||||||
|
markdown_content = get_cleaned_text_file_content(
|
||||||
|
form.cleaned_data["statement_file"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
markdown_content = form.cleaned_data["statement_content"]
|
||||||
|
with Path(statement.get_file_name()).open(
|
||||||
|
mode="wb" if writing_pdf else "w"
|
||||||
|
) as destination:
|
||||||
|
if writing_pdf:
|
||||||
|
for chunk in form.cleaned_data["statement_file"].chunks():
|
||||||
|
destination.write(chunk)
|
||||||
|
else:
|
||||||
|
destination.write(markdown_content)
|
||||||
|
return redirect("ietf.doc.views_doc.document_main", name=statement.name)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if statement.uploaded_filename.endswith("pdf"):
|
||||||
|
text = CONST_PDF_REV_NOTICE
|
||||||
|
else:
|
||||||
|
text = statement.text_or_error()
|
||||||
|
init = {
|
||||||
|
"statement_content": text,
|
||||||
|
"statement_submission": "enter",
|
||||||
|
}
|
||||||
|
form = StatementUploadForm(initial=init)
|
||||||
|
return render(
|
||||||
|
request, "doc/statement/upload_content.html", {"form": form, "doc": statement}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NewStatementForm(StatementUploadForm):
|
||||||
|
group = forms.ModelChoiceField(
|
||||||
|
queryset=Group.objects.filter(acronym__in=["iab", "iesg"])
|
||||||
|
)
|
||||||
|
title = forms.CharField(max_length=255)
|
||||||
|
field_order = [
|
||||||
|
"group",
|
||||||
|
"title",
|
||||||
|
"statement_submission",
|
||||||
|
"statement_file",
|
||||||
|
"statement_content",
|
||||||
|
]
|
||||||
|
|
||||||
|
def name_from_title_and_group(self, title, group):
|
||||||
|
title_slug = xslugify(title)
|
||||||
|
if title_slug.startswith(f"{group.acronym}-"):
|
||||||
|
title_slug = title_slug[len(f"{group.acronym}-") :]
|
||||||
|
name = f"statement-{group.acronym}-{title_slug[:240]}"
|
||||||
|
return name.replace("_", "-")
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if all([field in self.cleaned_data for field in ["title", "group"]]):
|
||||||
|
title = self.cleaned_data["title"]
|
||||||
|
group = self.cleaned_data["group"]
|
||||||
|
name = self.name_from_title_and_group(title, group)
|
||||||
|
if name == self.name_from_title_and_group("", group):
|
||||||
|
self.add_error(
|
||||||
|
"title",
|
||||||
|
forms.ValidationError(
|
||||||
|
"The filename derived from this title is empty. Please include a few descriptive words using ascii or numeric characters"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if Document.objects.filter(name=name).exists():
|
||||||
|
self.add_error(
|
||||||
|
"title",
|
||||||
|
forms.ValidationError(
|
||||||
|
"This title produces a filename already used by an existing statement"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return super().clean()
|
||||||
|
|
||||||
|
|
||||||
|
@role_required("Secretariat")
|
||||||
|
def new_statement(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
form = NewStatementForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
statement_submission = form.cleaned_data["statement_submission"]
|
||||||
|
writing_pdf = (
|
||||||
|
statement_submission == "upload"
|
||||||
|
and form.cleaned_data["statement_file"].content_type
|
||||||
|
== "application/pdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
group = form.cleaned_data["group"]
|
||||||
|
title = form.cleaned_data["title"]
|
||||||
|
name = form.name_from_title_and_group(title, group)
|
||||||
|
statement = Document.objects.create(
|
||||||
|
type_id="statement",
|
||||||
|
group=group,
|
||||||
|
name=name,
|
||||||
|
title=title,
|
||||||
|
abstract="",
|
||||||
|
rev="00",
|
||||||
|
uploaded_filename=f"{name}-00.{'pdf' if writing_pdf else 'md'}",
|
||||||
|
)
|
||||||
|
statement.set_state(State.objects.get(type_id="statement", slug="active"))
|
||||||
|
e1 = NewRevisionDocEvent.objects.create(
|
||||||
|
type="new_revision",
|
||||||
|
doc=statement,
|
||||||
|
by=request.user.person,
|
||||||
|
rev=statement.rev,
|
||||||
|
desc="New revision available",
|
||||||
|
time=statement.time,
|
||||||
|
)
|
||||||
|
e2 = DocEvent.objects.create(
|
||||||
|
type="published_statement",
|
||||||
|
doc=statement,
|
||||||
|
rev=statement.rev,
|
||||||
|
by=request.user.person,
|
||||||
|
desc="Statement published",
|
||||||
|
)
|
||||||
|
statement.save_with_history([e1, e2])
|
||||||
|
alias = DocAlias.objects.create(name=name)
|
||||||
|
alias.docs.set([statement])
|
||||||
|
markdown_content = ""
|
||||||
|
if statement_submission == "upload":
|
||||||
|
if not writing_pdf:
|
||||||
|
markdown_content = get_cleaned_text_file_content(
|
||||||
|
form.cleaned_data["statement_file"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
markdown_content = form.cleaned_data["statement_content"]
|
||||||
|
with Path(statement.get_file_name()).open(
|
||||||
|
mode="wb" if writing_pdf else "w"
|
||||||
|
) as destination:
|
||||||
|
if writing_pdf:
|
||||||
|
for chunk in form.cleaned_data["statement_file"].chunks():
|
||||||
|
destination.write(chunk)
|
||||||
|
else:
|
||||||
|
destination.write(markdown_content)
|
||||||
|
return redirect("ietf.doc.views_doc.document_main", name=statement.name)
|
||||||
|
|
||||||
|
else:
|
||||||
|
init = {
|
||||||
|
"statement_content": escape(
|
||||||
|
render_to_string(
|
||||||
|
"doc/statement/statement_template.md", {"settings": settings}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"statement_submission": "enter",
|
||||||
|
}
|
||||||
|
form = NewStatementForm(initial=init)
|
||||||
|
return render(request, "doc/statement/new_statement.html", {"form": form})
|
|
@ -418,7 +418,7 @@ def clean_helper(form, formtype):
|
||||||
rfc_fields = {}
|
rfc_fields = {}
|
||||||
status_fields={}
|
status_fields={}
|
||||||
for k in sorted(form.data.keys()):
|
for k in sorted(form.data.keys()):
|
||||||
v = form.data[k]
|
v = form.data[k].lower()
|
||||||
if k.startswith('new_relation_row'):
|
if k.startswith('new_relation_row'):
|
||||||
if re.match(r'\d{1,4}',v):
|
if re.match(r'\d{1,4}',v):
|
||||||
v = 'rfc'+v
|
v = 'rfc'+v
|
||||||
|
@ -685,7 +685,7 @@ def last_call(request, name):
|
||||||
form = LastCallTextForm(initial=dict(last_call_text=escape(last_call_event.text)))
|
form = LastCallTextForm(initial=dict(last_call_text=escape(last_call_event.text)))
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST:
|
if "save_last_call_text" in request.POST or ("send_last_call_request" in request.POST and status_change.ad is not None):
|
||||||
form = LastCallTextForm(request.POST)
|
form = LastCallTextForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
events = []
|
events = []
|
||||||
|
|
|
@ -283,12 +283,6 @@ class GroupPagesTests(TestCase):
|
||||||
self.assertContains(r, milestone.desc)
|
self.assertContains(r, milestone.desc)
|
||||||
self.assertContains(r, milestone.docs.all()[0].name)
|
self.assertContains(r, milestone.docs.all()[0].name)
|
||||||
|
|
||||||
def test_about_rendertest(self):
|
|
||||||
group = CharterFactory().group
|
|
||||||
url = urlreverse('ietf.group.views.group_about_rendertest', kwargs=dict(acronym=group.acronym))
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code,200)
|
|
||||||
|
|
||||||
|
|
||||||
def test_group_about(self):
|
def test_group_about(self):
|
||||||
|
|
||||||
|
|
|
@ -101,8 +101,20 @@ class ReviewTests(TestCase):
|
||||||
|
|
||||||
self.assertEqual(list(suggested_review_requests_for_team(team)), [])
|
self.assertEqual(list(suggested_review_requests_for_team(team)), [])
|
||||||
|
|
||||||
|
# blocked by an already existing request (don't suggest it again)
|
||||||
|
review_req.state_id = "requested"
|
||||||
|
review_req.save()
|
||||||
|
self.assertEqual(list(suggested_review_requests_for_team(team)), [])
|
||||||
|
|
||||||
|
# ... but not for a previous version
|
||||||
|
review_req.requested_rev = prev_rev
|
||||||
|
review_req.save()
|
||||||
|
self.assertEqual(len(suggested_review_requests_for_team(team)), 1)
|
||||||
|
|
||||||
|
|
||||||
# blocked by completion
|
# blocked by completion
|
||||||
review_req.state = ReviewRequestStateName.objects.get(slug="assigned")
|
review_req.state = ReviewRequestStateName.objects.get(slug="assigned")
|
||||||
|
review_req.requested_rev = ""
|
||||||
review_req.save()
|
review_req.save()
|
||||||
assignment.state = ReviewAssignmentStateName.objects.get(slug="completed")
|
assignment.state = ReviewAssignmentStateName.objects.get(slug="completed")
|
||||||
assignment.reviewed_rev = review_req.doc.rev
|
assignment.reviewed_rev = review_req.doc.rev
|
||||||
|
@ -116,6 +128,7 @@ class ReviewTests(TestCase):
|
||||||
|
|
||||||
self.assertEqual(len(suggested_review_requests_for_team(team)), 1)
|
self.assertEqual(len(suggested_review_requests_for_team(team)), 1)
|
||||||
|
|
||||||
|
|
||||||
def test_suggested_review_requests_on_lc_and_telechat(self):
|
def test_suggested_review_requests_on_lc_and_telechat(self):
|
||||||
review_req = ReviewRequestFactory(state_id='assigned')
|
review_req = ReviewRequestFactory(state_id='assigned')
|
||||||
doc = review_req.doc
|
doc = review_req.doc
|
||||||
|
@ -199,13 +212,13 @@ class ReviewTests(TestCase):
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
# review team members can see reason for being unavailable
|
# review team members can see reason for being unavailable
|
||||||
self.assertContains(r, "Availability")
|
self.assertContains(r, "Available")
|
||||||
|
|
||||||
self.client.login(username="secretary", password="secretary+password")
|
self.client.login(username="secretary", password="secretary+password")
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
# secretariat can see reason for being unavailable
|
# secretariat can see reason for being unavailable
|
||||||
self.assertContains(r, "Availability")
|
self.assertContains(r, "Available")
|
||||||
|
|
||||||
# add one closed review with no response and see it is visible
|
# add one closed review with no response and see it is visible
|
||||||
review_req2 = ReviewRequestFactory(state_id='completed',team=team)
|
review_req2 = ReviewRequestFactory(state_id='completed',team=team)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright The IETF Trust 2013-2020, All Rights Reserved
|
# Copyright The IETF Trust 2013-2023, All Rights Reserved
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import include
|
from django.urls import include
|
||||||
|
@ -20,7 +20,6 @@ info_detail_urls = [
|
||||||
url(r'^documents/subscription/$', community_views.subscription),
|
url(r'^documents/subscription/$', community_views.subscription),
|
||||||
url(r'^charter/$', views.group_about),
|
url(r'^charter/$', views.group_about),
|
||||||
url(r'^about/$', views.group_about),
|
url(r'^about/$', views.group_about),
|
||||||
url(r'^about/rendertest/$', views.group_about_rendertest),
|
|
||||||
url(r'^about/status/$', views.group_about_status),
|
url(r'^about/status/$', views.group_about_status),
|
||||||
url(r'^about/status/edit/$', views.group_about_status_edit),
|
url(r'^about/status/edit/$', views.group_about_status_edit),
|
||||||
url(r'^about/status/meeting/(?P<num>\d+)/$', views.group_about_status_meeting),
|
url(r'^about/status/meeting/(?P<num>\d+)/$', views.group_about_status_meeting),
|
||||||
|
@ -48,6 +47,7 @@ info_detail_urls = [
|
||||||
url(r'^secretarysettings/$', views.change_review_secretary_settings),
|
url(r'^secretarysettings/$', views.change_review_secretary_settings),
|
||||||
url(r'^reset_next_reviewer/$', views.reset_next_reviewer),
|
url(r'^reset_next_reviewer/$', views.reset_next_reviewer),
|
||||||
url(r'^email-aliases/$', RedirectView.as_view(pattern_name=views.email,permanent=False),name='ietf.group.urls_info_details.redirect.email'),
|
url(r'^email-aliases/$', RedirectView.as_view(pattern_name=views.email,permanent=False),name='ietf.group.urls_info_details.redirect.email'),
|
||||||
|
url(r'^statements/$', views.statements),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright The IETF Trust 2012-2021, All Rights Reserved
|
# Copyright The IETF Trust 2012-2023, All Rights Reserved
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
@ -233,6 +233,8 @@ def construct_group_menu_context(request, group, selected, group_type, others):
|
||||||
|
|
||||||
if group.features.has_meetings:
|
if group.features.has_meetings:
|
||||||
entries.append(("Meetings", urlreverse("ietf.group.views.meetings", kwargs=kwargs)))
|
entries.append(("Meetings", urlreverse("ietf.group.views.meetings", kwargs=kwargs)))
|
||||||
|
if group.acronym in ["iab", "iesg"]:
|
||||||
|
entries.append(("Statements", urlreverse("ietf.group.views.statements", kwargs=kwargs)))
|
||||||
entries.append(("History", urlreverse("ietf.group.views.history", kwargs=kwargs)))
|
entries.append(("History", urlreverse("ietf.group.views.history", kwargs=kwargs)))
|
||||||
entries.append(("Photos", urlreverse("ietf.group.views.group_photos", kwargs=kwargs)))
|
entries.append(("Photos", urlreverse("ietf.group.views.group_photos", kwargs=kwargs)))
|
||||||
entries.append(("Email expansions", urlreverse("ietf.group.views.email", kwargs=kwargs)))
|
entries.append(("Email expansions", urlreverse("ietf.group.views.email", kwargs=kwargs)))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright The IETF Trust 2009-2022, All Rights Reserved
|
# Copyright The IETF Trust 2009-2023, All Rights Reserved
|
||||||
#
|
#
|
||||||
# Portion Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
# Portion Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||||
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
|
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
|
||||||
|
@ -48,7 +48,7 @@ from simple_history.utils import update_change_reason
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Q, Count
|
from django.db.models import Q, Count, OuterRef, Subquery
|
||||||
from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonResponse
|
from django.http import HttpResponse, HttpResponseRedirect, Http404, JsonResponse
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
@ -61,7 +61,7 @@ import debug # pyflakes:ignore
|
||||||
|
|
||||||
from ietf.community.models import CommunityList, EmailSubscription
|
from ietf.community.models import CommunityList, EmailSubscription
|
||||||
from ietf.community.utils import docs_tracked_by_community_list
|
from ietf.community.utils import docs_tracked_by_community_list
|
||||||
from ietf.doc.models import DocTagName, State, DocAlias, RelatedDocument, Document
|
from ietf.doc.models import DocTagName, State, DocAlias, RelatedDocument, Document, DocEvent
|
||||||
from ietf.doc.templatetags.ietf_filters import clean_whitespace
|
from ietf.doc.templatetags.ietf_filters import clean_whitespace
|
||||||
from ietf.doc.utils import get_chartering_type, get_tags_for_stream_id
|
from ietf.doc.utils import get_chartering_type, get_tags_for_stream_id
|
||||||
from ietf.doc.utils_charter import charter_name_for_group, replace_charter_of_replaced_group
|
from ietf.doc.utils_charter import charter_name_for_group, replace_charter_of_replaced_group
|
||||||
|
@ -604,17 +604,6 @@ def all_status(request):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def group_about_rendertest(request, acronym, group_type=None):
|
|
||||||
group = get_group_or_404(acronym, group_type)
|
|
||||||
charter = None
|
|
||||||
if group.charter:
|
|
||||||
charter = get_charter_text(group)
|
|
||||||
try:
|
|
||||||
rendered = markdown.markdown(charter)
|
|
||||||
except Exception as e:
|
|
||||||
rendered = f'Markdown rendering failed: {e}'
|
|
||||||
return render(request, 'group/group_about_rendertest.html', {'group':group, 'charter':charter, 'rendered':rendered})
|
|
||||||
|
|
||||||
def group_about_status(request, acronym, group_type=None):
|
def group_about_status(request, acronym, group_type=None):
|
||||||
group = get_group_or_404(acronym, group_type)
|
group = get_group_or_404(acronym, group_type)
|
||||||
status_update = group.latest_event(type='status_update')
|
status_update = group.latest_event(type='status_update')
|
||||||
|
@ -2093,7 +2082,32 @@ def reset_next_reviewer(request, acronym, group_type=None):
|
||||||
|
|
||||||
return render(request, 'group/reset_next_reviewer.html', { 'group':group, 'form': form,})
|
return render(request, 'group/reset_next_reviewer.html', { 'group':group, 'form': form,})
|
||||||
|
|
||||||
|
def statements(request, acronym, group_type=None):
|
||||||
|
if not acronym in ["iab", "iesg"]:
|
||||||
|
raise Http404
|
||||||
|
group = get_group_or_404(acronym, group_type)
|
||||||
|
statements = group.document_set.filter(type_id="statement").annotate(
|
||||||
|
published=Subquery(
|
||||||
|
DocEvent.objects.filter(
|
||||||
|
doc=OuterRef("pk"),
|
||||||
|
type="published_statement"
|
||||||
|
).order_by("-time").values("time")[:1]
|
||||||
|
)
|
||||||
|
).order_by("-published")
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"group/statements.html",
|
||||||
|
construct_group_menu_context(
|
||||||
|
request,
|
||||||
|
group,
|
||||||
|
"statements",
|
||||||
|
group_type,
|
||||||
|
{
|
||||||
|
"group": group,
|
||||||
|
"statements": statements,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -223,7 +223,7 @@ def is_bofreq_editor(user, doc):
|
||||||
def openid_userinfo(claims, user):
|
def openid_userinfo(claims, user):
|
||||||
# Populate claims dict.
|
# Populate claims dict.
|
||||||
person = get_object_or_404(Person, user=user)
|
person = get_object_or_404(Person, user=user)
|
||||||
email = person.email()
|
email = person.email_allowing_inactive()
|
||||||
if person.photo:
|
if person.photo:
|
||||||
photo_url = person.cdn_photo_url()
|
photo_url = person.cdn_photo_url()
|
||||||
else:
|
else:
|
||||||
|
|
31
ietf/mailtrigger/migrations/0002_slidesubmitter.py
Normal file
31
ietf/mailtrigger/migrations/0002_slidesubmitter.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# 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")
|
||||||
|
r = Recipient.objects.create(
|
||||||
|
slug="slides_proposer",
|
||||||
|
desc="Person who proposed slides",
|
||||||
|
template="{{ proposer.email }}"
|
||||||
|
)
|
||||||
|
mt = MailTrigger.objects.get(slug="slides_proposed")
|
||||||
|
mt.cc.add(r)
|
||||||
|
|
||||||
|
def reverse(apps, schema_editor):
|
||||||
|
MailTrigger = apps.get_model("mailtrigger", "MailTrigger")
|
||||||
|
Recipient = apps.get_model("mailtrigger", "Recipient")
|
||||||
|
mt = MailTrigger.objects.get(slug="slides_proposed")
|
||||||
|
r = Recipient.objects.get(slug="slides_proposer")
|
||||||
|
mt.cc.remove(r)
|
||||||
|
r.delete()
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("mailtrigger", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forward, reverse)
|
||||||
|
]
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright The IETF Trust 2015-2019, All Rights Reserved
|
# Copyright The IETF Trust 2015-2023, All Rights Reserved
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ def gather_relevant_expansions(**kwargs):
|
||||||
|
|
||||||
relevant.add('doc_state_edited')
|
relevant.add('doc_state_edited')
|
||||||
|
|
||||||
if not doc.type_id in ['bofreq',]:
|
if not doc.type_id in ['bofreq', 'statement']:
|
||||||
relevant.update(['doc_telechat_details_changed','ballot_deferred','iesg_ballot_saved'])
|
relevant.update(['doc_telechat_details_changed','ballot_deferred','iesg_ballot_saved'])
|
||||||
|
|
||||||
if doc.type_id in ['draft','statchg']:
|
if doc.type_id in ['draft','statchg']:
|
||||||
|
|
|
@ -648,6 +648,20 @@ class MeetingTests(BaseMeetingTestCase):
|
||||||
self.assertFalse(row.find("a:contains(\"Bad Slideshow\")"))
|
self.assertFalse(row.find("a:contains(\"Bad Slideshow\")"))
|
||||||
|
|
||||||
# test with no meeting number in url
|
# test with no meeting number in url
|
||||||
|
# Add various group sessions
|
||||||
|
groups = []
|
||||||
|
parent_groups = [
|
||||||
|
GroupFactory.create(type_id="area", acronym="gen"),
|
||||||
|
GroupFactory.create(acronym="iab"),
|
||||||
|
GroupFactory.create(acronym="irtf"),
|
||||||
|
]
|
||||||
|
for parent in parent_groups:
|
||||||
|
groups.append(GroupFactory.create(parent=parent))
|
||||||
|
for acronym in ["rsab", "edu"]:
|
||||||
|
groups.append(GroupFactory.create(acronym=acronym))
|
||||||
|
for group in groups:
|
||||||
|
SessionFactory(meeting=meeting, group=group)
|
||||||
|
self.write_materials_files(meeting, session)
|
||||||
url = urlreverse("ietf.meeting.views.materials", kwargs=dict())
|
url = urlreverse("ietf.meeting.views.materials", kwargs=dict())
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
@ -657,6 +671,10 @@ class MeetingTests(BaseMeetingTestCase):
|
||||||
self.assertTrue(row.find('a:contains("Minutes")'))
|
self.assertTrue(row.find('a:contains("Minutes")'))
|
||||||
self.assertTrue(row.find('a:contains("Slideshow")'))
|
self.assertTrue(row.find('a:contains("Slideshow")'))
|
||||||
self.assertFalse(row.find("a:contains(\"Bad Slideshow\")"))
|
self.assertFalse(row.find("a:contains(\"Bad Slideshow\")"))
|
||||||
|
# test for different sections
|
||||||
|
sections = ["plenaries", "gen", "iab", "editorial", "irtf", "training"]
|
||||||
|
for section in sections:
|
||||||
|
self.assertEqual(len(q(f"#{section}")), 1, f"{section} section should exists in proceedings")
|
||||||
|
|
||||||
# test with a loggged-in wg chair
|
# test with a loggged-in wg chair
|
||||||
self.client.login(username="marschairman", password="marschairman+password")
|
self.client.login(username="marschairman", password="marschairman+password")
|
||||||
|
@ -7632,6 +7650,13 @@ class ProceedingsTests(BaseMeetingTestCase):
|
||||||
'Correct title and link for each ProceedingsMaterial should appear in the correct order'
|
'Correct title and link for each ProceedingsMaterial should appear in the correct order'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _assertGroupSessions(self, response, meeting):
|
||||||
|
"""Checks that group/sessions are present"""
|
||||||
|
pq = PyQuery(response.content)
|
||||||
|
sections = ["plenaries", "gen", "iab", "editorial", "irtf", "training"]
|
||||||
|
for section in sections:
|
||||||
|
self.assertEqual(len(pq(f"#{section}")), 1, f"{section} section should exists in proceedings")
|
||||||
|
|
||||||
def test_proceedings(self):
|
def test_proceedings(self):
|
||||||
"""Proceedings should be displayed correctly
|
"""Proceedings should be displayed correctly
|
||||||
|
|
||||||
|
@ -7645,6 +7670,20 @@ class ProceedingsTests(BaseMeetingTestCase):
|
||||||
SessionPresentationFactory(document__type_id='recording',session=session)
|
SessionPresentationFactory(document__type_id='recording',session=session)
|
||||||
SessionPresentationFactory(document__type_id='recording',session=session,document__title="Audio recording for tests")
|
SessionPresentationFactory(document__type_id='recording',session=session,document__title="Audio recording for tests")
|
||||||
|
|
||||||
|
# Add various group sessions
|
||||||
|
groups = []
|
||||||
|
parent_groups = [
|
||||||
|
GroupFactory.create(type_id="area", acronym="gen"),
|
||||||
|
GroupFactory.create(acronym="iab"),
|
||||||
|
GroupFactory.create(acronym="irtf"),
|
||||||
|
]
|
||||||
|
for parent in parent_groups:
|
||||||
|
groups.append(GroupFactory.create(parent=parent))
|
||||||
|
for acronym in ["rsab", "edu"]:
|
||||||
|
groups.append(GroupFactory.create(acronym=acronym))
|
||||||
|
for group in groups:
|
||||||
|
SessionFactory(meeting=meeting, group=group)
|
||||||
|
|
||||||
self.write_materials_files(meeting, session)
|
self.write_materials_files(meeting, session)
|
||||||
self._create_proceedings_materials(meeting)
|
self._create_proceedings_materials(meeting)
|
||||||
|
|
||||||
|
@ -7691,6 +7730,7 @@ class ProceedingsTests(BaseMeetingTestCase):
|
||||||
# configurable contents
|
# configurable contents
|
||||||
self._assertMeetingHostsDisplayed(r, meeting)
|
self._assertMeetingHostsDisplayed(r, meeting)
|
||||||
self._assertProceedingsMaterialsDisplayed(r, meeting)
|
self._assertProceedingsMaterialsDisplayed(r, meeting)
|
||||||
|
self._assertGroupSessions(r, meeting)
|
||||||
|
|
||||||
def test_named_session(self):
|
def test_named_session(self):
|
||||||
"""Session with a name should appear separately in the proceedings"""
|
"""Session with a name should appear separately in the proceedings"""
|
||||||
|
|
|
@ -159,18 +159,19 @@ def materials(request, num=None):
|
||||||
irtf = sessions.filter(group__parent__acronym = 'irtf')
|
irtf = sessions.filter(group__parent__acronym = 'irtf')
|
||||||
training = sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['regular', 'other', ])
|
training = sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['regular', 'other', ])
|
||||||
iab = sessions.filter(group__parent__acronym = 'iab')
|
iab = sessions.filter(group__parent__acronym = 'iab')
|
||||||
|
editorial = sessions.filter(group__acronym__in=['rsab','rswg'])
|
||||||
|
|
||||||
session_pks = [s.pk for ss in [plenaries, ietf, irtf, training, iab] for s in ss]
|
session_pks = [s.pk for ss in [plenaries, ietf, irtf, training, iab, editorial] for s in ss]
|
||||||
other = sessions.filter(type__in=['regular'], group__type__features__has_meetings=True).exclude(pk__in=session_pks)
|
other = sessions.filter(type__in=['regular'], group__type__features__has_meetings=True).exclude(pk__in=session_pks)
|
||||||
|
|
||||||
for topic in [plenaries, ietf, training, irtf, iab]:
|
for topic in [plenaries, ietf, training, irtf, iab, editorial]:
|
||||||
for event in topic:
|
for event in topic:
|
||||||
date_list = []
|
date_list = []
|
||||||
for slide_event in event.all_meeting_slides(): date_list.append(slide_event.time)
|
for slide_event in event.all_meeting_slides(): date_list.append(slide_event.time)
|
||||||
for agenda_event in event.all_meeting_agendas(): date_list.append(agenda_event.time)
|
for agenda_event in event.all_meeting_agendas(): date_list.append(agenda_event.time)
|
||||||
if date_list: setattr(event, 'last_update', sorted(date_list, reverse=True)[0])
|
if date_list: setattr(event, 'last_update', sorted(date_list, reverse=True)[0])
|
||||||
|
|
||||||
for session_list in [plenaries, ietf, training, irtf, iab, other]:
|
for session_list in [plenaries, ietf, training, irtf, iab, editorial, other]:
|
||||||
for session in session_list:
|
for session in session_list:
|
||||||
session.past_cutoff_date = past_cutoff_date
|
session.past_cutoff_date = past_cutoff_date
|
||||||
|
|
||||||
|
@ -183,6 +184,7 @@ def materials(request, num=None):
|
||||||
irtf, _ = organize_proceedings_sessions(irtf)
|
irtf, _ = organize_proceedings_sessions(irtf)
|
||||||
training, _ = organize_proceedings_sessions(training)
|
training, _ = organize_proceedings_sessions(training)
|
||||||
iab, _ = organize_proceedings_sessions(iab)
|
iab, _ = organize_proceedings_sessions(iab)
|
||||||
|
editorial, _ = organize_proceedings_sessions(editorial)
|
||||||
other, _ = organize_proceedings_sessions(other)
|
other, _ = organize_proceedings_sessions(other)
|
||||||
|
|
||||||
ietf_areas = []
|
ietf_areas = []
|
||||||
|
@ -202,6 +204,7 @@ def materials(request, num=None):
|
||||||
'training': training,
|
'training': training,
|
||||||
'irtf': irtf,
|
'irtf': irtf,
|
||||||
'iab': iab,
|
'iab': iab,
|
||||||
|
'editorial': editorial,
|
||||||
'other': other,
|
'other': other,
|
||||||
'cut_off_date': cut_off_date,
|
'cut_off_date': cut_off_date,
|
||||||
'cor_cut_off_date': cor_cut_off_date,
|
'cor_cut_off_date': cor_cut_off_date,
|
||||||
|
@ -1704,9 +1707,11 @@ def api_get_session_materials (request, session_id=None):
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
pass # no action available if it's past cutoff
|
pass # no action available if it's past cutoff
|
||||||
|
|
||||||
|
agenda = session.agenda()
|
||||||
|
agenda_url = agenda.get_href() if agenda is not None else None
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
"url": session.agenda().get_href(),
|
"url": agenda_url,
|
||||||
"slides": {
|
"slides": {
|
||||||
"decks": list(map(agenda_extract_slide, session.slides())),
|
"decks": list(map(agenda_extract_slide, session.slides())),
|
||||||
"actions": slides_actions,
|
"actions": slides_actions,
|
||||||
|
@ -2885,7 +2890,7 @@ def propose_session_slides(request, session_id, num):
|
||||||
submission.filename = filename
|
submission.filename = filename
|
||||||
submission.save()
|
submission.save()
|
||||||
|
|
||||||
(to, cc) = gather_address_lists('slides_proposed', group=session.group).as_strings()
|
(to, cc) = gather_address_lists('slides_proposed', group=session.group, proposer=request.user.person).as_strings()
|
||||||
msg_txt = render_to_string("meeting/slides_proposed.txt", {
|
msg_txt = render_to_string("meeting/slides_proposed.txt", {
|
||||||
"to": to,
|
"to": to,
|
||||||
"cc": cc,
|
"cc": cc,
|
||||||
|
@ -3777,6 +3782,10 @@ def proceedings(request, num=None):
|
||||||
sessions.filter(group__parent__acronym = 'iab')
|
sessions.filter(group__parent__acronym = 'iab')
|
||||||
.exclude(current_status='notmeet')
|
.exclude(current_status='notmeet')
|
||||||
)
|
)
|
||||||
|
editorial, _ = organize_proceedings_sessions(
|
||||||
|
sessions.filter(group__acronym__in=['rsab','rswg'])
|
||||||
|
.exclude(current_status='notmeet')
|
||||||
|
)
|
||||||
|
|
||||||
ietf = sessions.filter(group__parent__type__slug = 'area').exclude(group__acronym='edu').order_by('group__parent__acronym', 'group__acronym')
|
ietf = sessions.filter(group__parent__type__slug = 'area').exclude(group__acronym='edu').order_by('group__parent__acronym', 'group__acronym')
|
||||||
ietf_areas = []
|
ietf_areas = []
|
||||||
|
@ -3796,6 +3805,7 @@ def proceedings(request, num=None):
|
||||||
'training': training,
|
'training': training,
|
||||||
'irtf': irtf,
|
'irtf': irtf,
|
||||||
'iab': iab,
|
'iab': iab,
|
||||||
|
'editorial': editorial,
|
||||||
'ietf_areas': ietf_areas,
|
'ietf_areas': ietf_areas,
|
||||||
'cut_off_date': cut_off_date,
|
'cut_off_date': cut_off_date,
|
||||||
'cor_cut_off_date': cor_cut_off_date,
|
'cor_cut_off_date': cor_cut_off_date,
|
||||||
|
|
|
@ -2539,6 +2539,32 @@
|
||||||
"model": "doc.state",
|
"model": "doc.state",
|
||||||
"pk": 174
|
"pk": 174
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"desc": "The statement is active",
|
||||||
|
"name": "Active",
|
||||||
|
"next_states": [],
|
||||||
|
"order": 0,
|
||||||
|
"slug": "active",
|
||||||
|
"type": "statement",
|
||||||
|
"used": true
|
||||||
|
},
|
||||||
|
"model": "doc.state",
|
||||||
|
"pk": 175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"desc": "The statement has been replaced",
|
||||||
|
"name": "Replaced",
|
||||||
|
"next_states": [],
|
||||||
|
"order": 0,
|
||||||
|
"slug": "replaced",
|
||||||
|
"type": "statement",
|
||||||
|
"used": true
|
||||||
|
},
|
||||||
|
"model": "doc.state",
|
||||||
|
"pk": 176
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"label": "State"
|
"label": "State"
|
||||||
|
@ -2742,6 +2768,13 @@
|
||||||
"model": "doc.statetype",
|
"model": "doc.statetype",
|
||||||
"pk": "statchg"
|
"pk": "statchg"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"label": "Statement State"
|
||||||
|
},
|
||||||
|
"model": "doc.statetype",
|
||||||
|
"pk": "statement"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"about_page": "ietf.group.views.group_about",
|
"about_page": "ietf.group.views.group_about",
|
||||||
|
@ -5557,7 +5590,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"cc": [],
|
"cc": [
|
||||||
|
"slides_proposer"
|
||||||
|
],
|
||||||
"desc": "Recipients when slides are proposed for a given session",
|
"desc": "Recipients when slides are proposed for a given session",
|
||||||
"to": [
|
"to": [
|
||||||
"group_chairs",
|
"group_chairs",
|
||||||
|
@ -6348,6 +6383,14 @@
|
||||||
"model": "mailtrigger.recipient",
|
"model": "mailtrigger.recipient",
|
||||||
"pk": "session_requests"
|
"pk": "session_requests"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"desc": "Person who proposed slides",
|
||||||
|
"template": "{{ proposer.email }}"
|
||||||
|
},
|
||||||
|
"model": "mailtrigger.recipient",
|
||||||
|
"pk": "slides_proposer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "The managers of any related streams",
|
"desc": "The managers of any related streams",
|
||||||
|
@ -10593,6 +10636,17 @@
|
||||||
"model": "name.doctypename",
|
"model": "name.doctypename",
|
||||||
"pk": "statchg"
|
"pk": "statchg"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"desc": "",
|
||||||
|
"name": "Statement",
|
||||||
|
"order": 0,
|
||||||
|
"prefix": "statement",
|
||||||
|
"used": true
|
||||||
|
},
|
||||||
|
"model": "name.doctypename",
|
||||||
|
"pk": "statement"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "",
|
"desc": "",
|
||||||
|
@ -11059,8 +11113,9 @@
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "",
|
"desc": "",
|
||||||
|
"legend": "C",
|
||||||
"name": "Comment",
|
"name": "Comment",
|
||||||
"order": 0,
|
"order": 1,
|
||||||
"used": true
|
"used": true
|
||||||
},
|
},
|
||||||
"model": "name.feedbacktypename",
|
"model": "name.feedbacktypename",
|
||||||
|
@ -11069,8 +11124,9 @@
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "",
|
"desc": "",
|
||||||
|
"legend": "J",
|
||||||
"name": "Junk",
|
"name": "Junk",
|
||||||
"order": 0,
|
"order": 5,
|
||||||
"used": true
|
"used": true
|
||||||
},
|
},
|
||||||
"model": "name.feedbacktypename",
|
"model": "name.feedbacktypename",
|
||||||
|
@ -11079,8 +11135,9 @@
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "",
|
"desc": "",
|
||||||
|
"legend": "N",
|
||||||
"name": "Nomination",
|
"name": "Nomination",
|
||||||
"order": 0,
|
"order": 2,
|
||||||
"used": true
|
"used": true
|
||||||
},
|
},
|
||||||
"model": "name.feedbacktypename",
|
"model": "name.feedbacktypename",
|
||||||
|
@ -11089,8 +11146,20 @@
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "",
|
"desc": "",
|
||||||
|
"legend": "O",
|
||||||
|
"name": "Overcome by events",
|
||||||
|
"order": 4,
|
||||||
|
"used": true
|
||||||
|
},
|
||||||
|
"model": "name.feedbacktypename",
|
||||||
|
"pk": "obe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"desc": "",
|
||||||
|
"legend": "Q",
|
||||||
"name": "Questionnaire response",
|
"name": "Questionnaire response",
|
||||||
"order": 0,
|
"order": 3,
|
||||||
"used": true
|
"used": true
|
||||||
},
|
},
|
||||||
"model": "name.feedbacktypename",
|
"model": "name.feedbacktypename",
|
||||||
|
@ -11099,8 +11168,9 @@
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "",
|
"desc": "",
|
||||||
|
"legend": "R",
|
||||||
"name": "Read",
|
"name": "Read",
|
||||||
"order": 0,
|
"order": 6,
|
||||||
"used": true
|
"used": true
|
||||||
},
|
},
|
||||||
"model": "name.feedbacktypename",
|
"model": "name.feedbacktypename",
|
||||||
|
@ -13180,7 +13250,7 @@
|
||||||
"desc": "Flipchars",
|
"desc": "Flipchars",
|
||||||
"name": "Flipcharts",
|
"name": "Flipcharts",
|
||||||
"order": 0,
|
"order": 0,
|
||||||
"used": true
|
"used": false
|
||||||
},
|
},
|
||||||
"model": "name.roomresourcename",
|
"model": "name.roomresourcename",
|
||||||
"pk": "flipcharts"
|
"pk": "flipcharts"
|
||||||
|
@ -13230,7 +13300,7 @@
|
||||||
"desc": "Experimental Room Setup (U-Shape and classroom, subject to availability)",
|
"desc": "Experimental Room Setup (U-Shape and classroom, subject to availability)",
|
||||||
"name": "Experimental Room Setup (U-Shape and classroom)",
|
"name": "Experimental Room Setup (U-Shape and classroom)",
|
||||||
"order": 0,
|
"order": 0,
|
||||||
"used": true
|
"used": false
|
||||||
},
|
},
|
||||||
"model": "name.roomresourcename",
|
"model": "name.roomresourcename",
|
||||||
"pk": "u-shape"
|
"pk": "u-shape"
|
||||||
|
@ -16385,7 +16455,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "xym",
|
"command": "xym",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2023-05-14T07:09:32.713Z",
|
"time": "2023-07-17T07:09:47.664Z",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "xym 0.7.0"
|
"version": "xym 0.7.0"
|
||||||
},
|
},
|
||||||
|
@ -16396,7 +16466,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "pyang",
|
"command": "pyang",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2023-05-14T07:09:33.045Z",
|
"time": "2023-07-17T07:09:48.075Z",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "pyang 2.5.3"
|
"version": "pyang 2.5.3"
|
||||||
},
|
},
|
||||||
|
@ -16407,7 +16477,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "yanglint",
|
"command": "yanglint",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2023-05-14T07:09:33.065Z",
|
"time": "2023-07-17T07:09:48.104Z",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "yanglint SO 1.9.2"
|
"version": "yanglint SO 1.9.2"
|
||||||
},
|
},
|
||||||
|
@ -16418,9 +16488,9 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "xml2rfc",
|
"command": "xml2rfc",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2023-05-14T07:09:33.970Z",
|
"time": "2023-07-17T07:09:49.075Z",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "xml2rfc 3.17.1"
|
"version": "xml2rfc 3.17.4"
|
||||||
},
|
},
|
||||||
"model": "utils.versioninfo",
|
"model": "utils.versioninfo",
|
||||||
"pk": 4
|
"pk": 4
|
||||||
|
|
21
ietf/name/migrations/0004_statements.py
Normal file
21
ietf/name/migrations/0004_statements.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Copyright The IETF Trust 2023, All Rights Reserved
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
def forward(apps, schema_editor):
|
||||||
|
DocTypeName = apps.get_model("name", "DocTypeName")
|
||||||
|
DocTypeName.objects.create(slug="statement", name="Statement", prefix="statement", desc="", used=True)
|
||||||
|
|
||||||
|
|
||||||
|
def reverse(apps, schema_editor):
|
||||||
|
DocTypeName = apps.get_model("name", "DocTypeName")
|
||||||
|
DocTypeName.objects.filter(slug="statement").delete()
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("name", "0003_populate_telechatagendasectionname"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forward, reverse),
|
||||||
|
]
|
20
ietf/name/migrations/0005_feedbacktypename_schema.py
Normal file
20
ietf/name/migrations/0005_feedbacktypename_schema.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Copyright The IETF Trust 2023, All Rights Reserved
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("name", "0004_statements"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="FeedbackTypeName",
|
||||||
|
name="legend",
|
||||||
|
field=models.CharField(
|
||||||
|
default="",
|
||||||
|
help_text="One-character legend for feedback classification form",
|
||||||
|
max_length=1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
36
ietf/name/migrations/0006_feedbacktypename_data.py
Normal file
36
ietf/name/migrations/0006_feedbacktypename_data.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Copyright The IETF Trust 2023, All Rights Reserved
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
def forward(apps, schema_editor):
|
||||||
|
FeedbackTypeName = apps.get_model("name", "FeedbackTypeName")
|
||||||
|
FeedbackTypeName.objects.create(slug="obe", name="Overcome by events")
|
||||||
|
for slug, legend, order in (
|
||||||
|
('comment', 'C', 1),
|
||||||
|
('nomina', 'N', 2),
|
||||||
|
('questio', 'Q', 3),
|
||||||
|
('obe', 'O', 4),
|
||||||
|
('junk', 'J', 5),
|
||||||
|
('read', 'R', 6),
|
||||||
|
):
|
||||||
|
ft = FeedbackTypeName.objects.get(slug=slug)
|
||||||
|
ft.legend = legend
|
||||||
|
ft.order = order
|
||||||
|
ft.save()
|
||||||
|
|
||||||
|
def reverse(apps, schema_editor):
|
||||||
|
FeedbackTypeName = apps.get_model("name", "FeedbackTypeName")
|
||||||
|
FeedbackTypeName.objects.filter(slug="obe").delete()
|
||||||
|
for ft in FeedbackTypeName.objects.all():
|
||||||
|
ft.legend = ""
|
||||||
|
ft.order = 0
|
||||||
|
ft.save()
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("name", "0005_feedbacktypename_schema"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forward, reverse),
|
||||||
|
]
|
|
@ -94,6 +94,7 @@ class NomineePositionStateName(NameModel):
|
||||||
"""Status of a candidate for a position: None, Accepted, Declined"""
|
"""Status of a candidate for a position: None, Accepted, Declined"""
|
||||||
class FeedbackTypeName(NameModel):
|
class FeedbackTypeName(NameModel):
|
||||||
"""Type of feedback: questionnaires, nominations, comments"""
|
"""Type of feedback: questionnaires, nominations, comments"""
|
||||||
|
legend = models.CharField(max_length=1, default="", help_text="One-character legend for feedback classification form")
|
||||||
class DBTemplateTypeName(NameModel):
|
class DBTemplateTypeName(NameModel):
|
||||||
"""reStructuredText, Plain, Django"""
|
"""reStructuredText, Plain, Django"""
|
||||||
class DraftSubmissionStateName(NameModel):
|
class DraftSubmissionStateName(NameModel):
|
||||||
|
|
|
@ -653,7 +653,7 @@ class PrivateKeyForm(forms.Form):
|
||||||
|
|
||||||
class PendingFeedbackForm(forms.ModelForm):
|
class PendingFeedbackForm(forms.ModelForm):
|
||||||
|
|
||||||
type = forms.ModelChoiceField(queryset=FeedbackTypeName.objects.all().order_by('pk'), widget=forms.RadioSelect, empty_label='Unclassified', required=False)
|
type = forms.ModelChoiceField(queryset=FeedbackTypeName.objects.all(), widget=forms.RadioSelect, empty_label='Unclassified', required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Feedback
|
model = Feedback
|
||||||
|
|
|
@ -11,7 +11,7 @@ import debug # pyflakes:ignore
|
||||||
from ietf.nomcom.factories import nomcom_kwargs_for_year, NomComFactory, NomineePositionFactory, key
|
from ietf.nomcom.factories import nomcom_kwargs_for_year, NomComFactory, NomineePositionFactory, key
|
||||||
from ietf.person.factories import EmailFactory
|
from ietf.person.factories import EmailFactory
|
||||||
from ietf.group.models import Group
|
from ietf.group.models import Group
|
||||||
from ietf.person.models import User
|
from ietf.person.models import Person, User
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = ("Create (or delete) a nomcom for test and development purposes.")
|
help = ("Create (or delete) a nomcom for test and development purposes.")
|
||||||
|
@ -27,7 +27,9 @@ class Command(BaseCommand):
|
||||||
if opt_delete:
|
if opt_delete:
|
||||||
if Group.objects.filter(acronym='nomcom7437').exists():
|
if Group.objects.filter(acronym='nomcom7437').exists():
|
||||||
Group.objects.filter(acronym='nomcom7437').delete()
|
Group.objects.filter(acronym='nomcom7437').delete()
|
||||||
User.objects.filter(username__in=['testchair','testmember','testcandidate']).delete()
|
users_to_delete = ['testchair','testmember','testcandidate']
|
||||||
|
Person.objects.filter(user__username__in=users_to_delete).delete()
|
||||||
|
User.objects.filter(username__in=users_to_delete).delete()
|
||||||
self.stdout.write("Deleted test group 'nomcom7437' and its related objects.")
|
self.stdout.write("Deleted test group 'nomcom7437' and its related objects.")
|
||||||
else:
|
else:
|
||||||
self.stderr.write("test nomcom 'nomcom7437' does not exist; nothing to do.\n")
|
self.stderr.write("test nomcom 'nomcom7437' does not exist; nothing to do.\n")
|
||||||
|
@ -57,6 +59,6 @@ class Command(BaseCommand):
|
||||||
position__nomcom=nc, position__name='Test Area Director', position__is_iesg_position=True,
|
position__nomcom=nc, position__name='Test Area Director', position__is_iesg_position=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.stdout.write("%s\n" % key)
|
self.stdout.write("%s\n" % key.decode())
|
||||||
self.stdout.write("Nomcom 7437 created. The private key can also be found at any time\nin ietf/nomcom/factories.py. Note that it is NOT a secure key.\n")
|
self.stdout.write("Nomcom 7437 created. The private key can also be found at any time\nin ietf/nomcom/factories.py. Note that it is NOT a secure key.\n")
|
||||||
|
|
||||||
|
|
|
@ -1450,7 +1450,7 @@ class FeedbackIndexTests(TestCase):
|
||||||
self.assertEqual(response.status_code,200)
|
self.assertEqual(response.status_code,200)
|
||||||
q = PyQuery(response.content)
|
q = PyQuery(response.content)
|
||||||
r = q('tfoot').eq(0).find('td').contents()
|
r = q('tfoot').eq(0).find('td').contents()
|
||||||
self.assertEqual([a.strip() for a in r], ['1', '1', '1'])
|
self.assertEqual([a.strip() for a in r], ['1', '1', '1', '0'])
|
||||||
|
|
||||||
class FeedbackLastSeenTests(TestCase):
|
class FeedbackLastSeenTests(TestCase):
|
||||||
|
|
||||||
|
@ -2863,3 +2863,92 @@ class VolunteerDecoratorUnitTests(TestCase):
|
||||||
self.assertEqual(v.qualifications,'path_2')
|
self.assertEqual(v.qualifications,'path_2')
|
||||||
if v.person == author_person:
|
if v.person == author_person:
|
||||||
self.assertEqual(v.qualifications,'path_3')
|
self.assertEqual(v.qualifications,'path_3')
|
||||||
|
|
||||||
|
class ReclassifyFeedbackTests(TestCase):
|
||||||
|
"""Tests for feedback reclassification"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
setup_test_public_keys_dir(self)
|
||||||
|
nomcom_test_data()
|
||||||
|
self.nc = NomComFactory.create(**nomcom_kwargs_for_year())
|
||||||
|
self.chair = self.nc.group.role_set.filter(name='chair').first().person
|
||||||
|
self.member = self.nc.group.role_set.filter(name='member').first().person
|
||||||
|
self.nominee = self.nc.nominee_set.order_by('pk').first()
|
||||||
|
self.position = self.nc.position_set.first()
|
||||||
|
self.topic = self.nc.topic_set.first()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
teardown_test_public_keys_dir(self)
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
|
def test_reclassify_feedback_nominee(self):
|
||||||
|
fb = FeedbackFactory.create(nomcom=self.nc,type_id='comment')
|
||||||
|
fb.positions.add(self.position)
|
||||||
|
fb.nominees.add(self.nominee)
|
||||||
|
fb.save()
|
||||||
|
self.assertEqual(Feedback.objects.comments().count(), 1)
|
||||||
|
|
||||||
|
url = reverse('ietf.nomcom.views.view_feedback_nominee', kwargs={'year':self.nc.year(), 'nominee_id':self.nominee.id})
|
||||||
|
login_testing_unauthorized(self,self.member.user.username,url)
|
||||||
|
provide_private_key_to_test_client(self)
|
||||||
|
response = self.client.post(url, {'feedback_id': fb.id, 'type': 'obe'})
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
self.client.logout()
|
||||||
|
self.client.login(username=self.chair.user.username, password=self.chair.user.username + "+password")
|
||||||
|
provide_private_key_to_test_client(self)
|
||||||
|
|
||||||
|
response = self.client.post(url, {'feedback_id': fb.id, 'type': 'obe'})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
fb = Feedback.objects.get(id=fb.id)
|
||||||
|
self.assertEqual(fb.type_id,'obe')
|
||||||
|
self.assertEqual(Feedback.objects.comments().count(), 0)
|
||||||
|
self.assertEqual(Feedback.objects.filter(type='obe').count(), 1)
|
||||||
|
|
||||||
|
def test_reclassify_feedback_topic(self):
|
||||||
|
fb = FeedbackFactory.create(nomcom=self.nc,type_id='comment')
|
||||||
|
fb.topics.add(self.topic)
|
||||||
|
fb.save()
|
||||||
|
self.assertEqual(Feedback.objects.comments().count(), 1)
|
||||||
|
|
||||||
|
url = reverse('ietf.nomcom.views.view_feedback_topic', kwargs={'year':self.nc.year(), 'topic_id':self.topic.id})
|
||||||
|
login_testing_unauthorized(self,self.member.user.username,url)
|
||||||
|
provide_private_key_to_test_client(self)
|
||||||
|
response = self.client.post(url, {'feedback_id': fb.id, 'type': 'unclassified'})
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
self.client.logout()
|
||||||
|
self.client.login(username=self.chair.user.username, password=self.chair.user.username + "+password")
|
||||||
|
provide_private_key_to_test_client(self)
|
||||||
|
|
||||||
|
response = self.client.post(url, {'feedback_id': fb.id, 'type': 'unclassified'})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
fb = Feedback.objects.get(id=fb.id)
|
||||||
|
self.assertEqual(fb.type_id,None)
|
||||||
|
self.assertEqual(Feedback.objects.comments().count(), 0)
|
||||||
|
self.assertEqual(Feedback.objects.filter(type=None).count(), 1)
|
||||||
|
|
||||||
|
def test_reclassify_feedback_unrelated(self):
|
||||||
|
fb = FeedbackFactory(nomcom=self.nc, type_id='read')
|
||||||
|
self.assertEqual(Feedback.objects.filter(type='read').count(), 1)
|
||||||
|
|
||||||
|
url = reverse('ietf.nomcom.views.view_feedback_unrelated', kwargs={'year':self.nc.year()})
|
||||||
|
login_testing_unauthorized(self,self.member.user.username,url)
|
||||||
|
provide_private_key_to_test_client(self)
|
||||||
|
response = self.client.post(url, {'feedback_id': fb.id, 'type': 'junk'})
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
self.client.logout()
|
||||||
|
self.client.login(username=self.chair.user.username, password=self.chair.user.username + "+password")
|
||||||
|
provide_private_key_to_test_client(self)
|
||||||
|
|
||||||
|
response = self.client.post(url, {'feedback_id': fb.id, 'type': 'junk'})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
fb = Feedback.objects.get(id=fb.id)
|
||||||
|
self.assertEqual(fb.type_id, 'junk')
|
||||||
|
self.assertEqual(Feedback.objects.filter(type='read').count(), 0)
|
||||||
|
self.assertEqual(Feedback.objects.filter(type='junk').count(), 1)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# Copyright The IETF Trust 2012-2020, All Rights Reserved
|
# Copyright The IETF Trust 2012-2023, All Rights Reserved
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict, Counter
|
from collections import Counter
|
||||||
import csv
|
import csv
|
||||||
import hmac
|
import hmac
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||||
from django.forms.models import modelformset_factory, inlineformset_factory
|
from django.forms.models import modelformset_factory, inlineformset_factory
|
||||||
from django.http import Http404, HttpResponseRedirect, HttpResponse
|
from django.http import Http404, HttpResponseRedirect, HttpResponse, HttpResponseForbidden
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -236,7 +236,7 @@ def private_index(request, year):
|
||||||
'position__id':p.pk,
|
'position__id':p.pk,
|
||||||
'position': p,
|
'position': p,
|
||||||
} for p in positions]
|
} for p in positions]
|
||||||
states = list(NomineePositionStateName.objects.values('slug', 'name')) + [{'slug': questionnaire_state, 'name': 'Questionnaire'}]
|
states = [{'slug': questionnaire_state, 'name': 'Accepted and sent Questionnaire'}] + list(NomineePositionStateName.objects.values('slug', 'name'))
|
||||||
positions = set([ n.position for n in all_nominee_positions.order_by('position__name') ])
|
positions = set([ n.position for n in all_nominee_positions.order_by('position__name') ])
|
||||||
for s in stats:
|
for s in stats:
|
||||||
for state in states:
|
for state in states:
|
||||||
|
@ -767,7 +767,6 @@ def process_nomination_status(request, year, nominee_position_id, state, date, h
|
||||||
'selected': 'feedback',
|
'selected': 'feedback',
|
||||||
'form': form })
|
'form': form })
|
||||||
|
|
||||||
|
|
||||||
@role_required("Nomcom")
|
@role_required("Nomcom")
|
||||||
@nomcom_private_key_required
|
@nomcom_private_key_required
|
||||||
def view_feedback(request, year):
|
def view_feedback(request, year):
|
||||||
|
@ -775,7 +774,7 @@ def view_feedback(request, year):
|
||||||
nominees = Nominee.objects.get_by_nomcom(nomcom).not_duplicated().distinct()
|
nominees = Nominee.objects.get_by_nomcom(nomcom).not_duplicated().distinct()
|
||||||
independent_feedback_types = []
|
independent_feedback_types = []
|
||||||
nominee_feedback_types = []
|
nominee_feedback_types = []
|
||||||
for ft in FeedbackTypeName.objects.all():
|
for ft in FeedbackTypeName.objects.filter(used=True):
|
||||||
if ft.slug in settings.NOMINEE_FEEDBACK_TYPES:
|
if ft.slug in settings.NOMINEE_FEEDBACK_TYPES:
|
||||||
nominee_feedback_types.append(ft)
|
nominee_feedback_types.append(ft)
|
||||||
else:
|
else:
|
||||||
|
@ -838,7 +837,8 @@ def view_feedback(request, year):
|
||||||
'topics_feedback': topics_feedback,
|
'topics_feedback': topics_feedback,
|
||||||
'independent_feedback': independent_feedback,
|
'independent_feedback': independent_feedback,
|
||||||
'nominees_feedback': nominees_feedback,
|
'nominees_feedback': nominees_feedback,
|
||||||
'nomcom': nomcom})
|
'nomcom': nomcom,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||||
|
@ -924,23 +924,13 @@ def view_feedback_pending(request, year):
|
||||||
formset = FeedbackFormSet(queryset=feedback_page.object_list)
|
formset = FeedbackFormSet(queryset=feedback_page.object_list)
|
||||||
for form in formset.forms:
|
for form in formset.forms:
|
||||||
form.set_nomcom(nomcom, request.user)
|
form.set_nomcom(nomcom, request.user)
|
||||||
type_dict = OrderedDict()
|
|
||||||
for t in FeedbackTypeName.objects.all().order_by('pk'):
|
|
||||||
rest = t.name
|
|
||||||
slug = rest[0]
|
|
||||||
rest = rest[1:]
|
|
||||||
while slug in type_dict and rest:
|
|
||||||
slug = rest[0]
|
|
||||||
rest = rest[1]
|
|
||||||
type_dict[slug] = t
|
|
||||||
return render(request, 'nomcom/view_feedback_pending.html',
|
return render(request, 'nomcom/view_feedback_pending.html',
|
||||||
{'year': year,
|
{'year': year,
|
||||||
'selected': 'feedback_pending',
|
'selected': 'feedback_pending',
|
||||||
'formset': formset,
|
'formset': formset,
|
||||||
'extra_step': extra_step,
|
'extra_step': extra_step,
|
||||||
'type_dict': type_dict,
|
|
||||||
'extra_ids': extra_ids,
|
'extra_ids': extra_ids,
|
||||||
'types': FeedbackTypeName.objects.all().order_by('pk'),
|
'types': FeedbackTypeName.objects.filter(used=True),
|
||||||
'nomcom': nomcom,
|
'nomcom': nomcom,
|
||||||
'is_chair_task' : True,
|
'is_chair_task' : True,
|
||||||
'page': feedback_page,
|
'page': feedback_page,
|
||||||
|
@ -951,22 +941,59 @@ def view_feedback_pending(request, year):
|
||||||
@nomcom_private_key_required
|
@nomcom_private_key_required
|
||||||
def view_feedback_unrelated(request, year):
|
def view_feedback_unrelated(request, year):
|
||||||
nomcom = get_nomcom_by_year(year)
|
nomcom = get_nomcom_by_year(year)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if not nomcom.group.has_role(request.user, ['chair','advisor']):
|
||||||
|
return HttpResponseForbidden('Restricted to roles: Nomcom Chair, Nomcom Advisor')
|
||||||
|
feedback_id = request.POST.get('feedback_id', None)
|
||||||
|
feedback = get_object_or_404(Feedback, id=feedback_id)
|
||||||
|
type = request.POST.get('type', None)
|
||||||
|
if type:
|
||||||
|
if type == 'unclassified':
|
||||||
|
feedback.type = None
|
||||||
|
messages.success(request, 'The selected feedback has been de-classified. Please reclassify it in the Pending emails tab.')
|
||||||
|
else:
|
||||||
|
feedback.type = FeedbackTypeName.objects.get(slug=type)
|
||||||
|
messages.success(request, f'The selected feedback has been reclassified as {feedback.type.name}.')
|
||||||
|
feedback.save()
|
||||||
|
else:
|
||||||
|
return render(request, 'nomcom/view_feedback_unrelated.html',
|
||||||
|
{'year': year,
|
||||||
|
'nomcom': nomcom,
|
||||||
|
'feedback_types': FeedbackTypeName.objects.filter(used=True).exclude(slug__in=settings.NOMINEE_FEEDBACK_TYPES),
|
||||||
|
'reclassify_feedback': feedback,
|
||||||
|
'is_chair_task' : True,
|
||||||
|
})
|
||||||
|
|
||||||
feedback_types = []
|
feedback_types = []
|
||||||
for ft in FeedbackTypeName.objects.exclude(slug__in=settings.NOMINEE_FEEDBACK_TYPES):
|
for ft in FeedbackTypeName.objects.filter(used=True).exclude(slug__in=settings.NOMINEE_FEEDBACK_TYPES):
|
||||||
feedback_types.append({'ft': ft,
|
feedback_types.append({'ft': ft,
|
||||||
'feedback': ft.feedback_set.get_by_nomcom(nomcom)})
|
'feedback': ft.feedback_set.get_by_nomcom(nomcom)})
|
||||||
|
|
||||||
return render(request, 'nomcom/view_feedback_unrelated.html',
|
return render(request, 'nomcom/view_feedback_unrelated.html',
|
||||||
{'year': year,
|
{'year': year,
|
||||||
'selected': 'view_feedback',
|
|
||||||
'feedback_types': feedback_types,
|
'feedback_types': feedback_types,
|
||||||
'nomcom': nomcom})
|
'nomcom': nomcom,
|
||||||
|
})
|
||||||
|
|
||||||
@role_required("Nomcom")
|
@role_required("Nomcom")
|
||||||
@nomcom_private_key_required
|
@nomcom_private_key_required
|
||||||
def view_feedback_topic(request, year, topic_id):
|
def view_feedback_topic(request, year, topic_id):
|
||||||
nomcom = get_nomcom_by_year(year)
|
# At present, the only feedback type for topics is 'comment'.
|
||||||
|
# Reclassifying from 'comment' to 'comment' is a no-op,
|
||||||
|
# so the only meaningful action is to de-classify it.
|
||||||
|
if request.method == 'POST':
|
||||||
|
nomcom = get_nomcom_by_year(year)
|
||||||
|
if not nomcom.group.has_role(request.user, ['chair','advisor']):
|
||||||
|
return HttpResponseForbidden('Restricted to roles: Nomcom Chair, Nomcom Advisor')
|
||||||
|
feedback_id = request.POST.get('feedback_id', None)
|
||||||
|
feedback = get_object_or_404(Feedback, id=feedback_id)
|
||||||
|
feedback.type = None
|
||||||
|
feedback.topics.clear()
|
||||||
|
feedback.save()
|
||||||
|
messages.success(request, 'The selected feedback has been de-classified. Please reclassify it in the Pending emails tab.')
|
||||||
|
|
||||||
topic = get_object_or_404(Topic, id=topic_id)
|
topic = get_object_or_404(Topic, id=topic_id)
|
||||||
|
nomcom = get_nomcom_by_year(year)
|
||||||
feedback_types = FeedbackTypeName.objects.filter(slug__in=['comment',])
|
feedback_types = FeedbackTypeName.objects.filter(slug__in=['comment',])
|
||||||
|
|
||||||
last_seen = TopicFeedbackLastSeen.objects.filter(reviewer=request.user.person,topic=topic).first()
|
last_seen = TopicFeedbackLastSeen.objects.filter(reviewer=request.user.person,topic=topic).first()
|
||||||
|
@ -978,18 +1005,42 @@ def view_feedback_topic(request, year, topic_id):
|
||||||
|
|
||||||
return render(request, 'nomcom/view_feedback_topic.html',
|
return render(request, 'nomcom/view_feedback_topic.html',
|
||||||
{'year': year,
|
{'year': year,
|
||||||
'selected': 'view_feedback',
|
|
||||||
'topic': topic,
|
'topic': topic,
|
||||||
'feedback_types': feedback_types,
|
'feedback_types': feedback_types,
|
||||||
'last_seen_time' : last_seen_time,
|
'last_seen_time' : last_seen_time,
|
||||||
'nomcom': nomcom})
|
'nomcom': nomcom,
|
||||||
|
})
|
||||||
|
|
||||||
@role_required("Nomcom")
|
@role_required("Nomcom")
|
||||||
@nomcom_private_key_required
|
@nomcom_private_key_required
|
||||||
def view_feedback_nominee(request, year, nominee_id):
|
def view_feedback_nominee(request, year, nominee_id):
|
||||||
nomcom = get_nomcom_by_year(year)
|
nomcom = get_nomcom_by_year(year)
|
||||||
nominee = get_object_or_404(Nominee, id=nominee_id)
|
nominee = get_object_or_404(Nominee, id=nominee_id)
|
||||||
feedback_types = FeedbackTypeName.objects.filter(slug__in=settings.NOMINEE_FEEDBACK_TYPES)
|
feedback_types = FeedbackTypeName.objects.filter(used=True, slug__in=settings.NOMINEE_FEEDBACK_TYPES)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if not nomcom.group.has_role(request.user, ['chair','advisor']):
|
||||||
|
return HttpResponseForbidden('Restricted to roles: Nomcom Chair, Nomcom Advisor')
|
||||||
|
feedback_id = request.POST.get('feedback_id', None)
|
||||||
|
feedback = get_object_or_404(Feedback, id=feedback_id)
|
||||||
|
type = request.POST.get('type', None)
|
||||||
|
if type:
|
||||||
|
if type == 'unclassified':
|
||||||
|
feedback.type = None
|
||||||
|
feedback.nominees.clear()
|
||||||
|
messages.success(request, 'The selected feedback has been de-classified. Please reclassify it in the Pending emails tab.')
|
||||||
|
else:
|
||||||
|
feedback.type = FeedbackTypeName.objects.get(slug=type)
|
||||||
|
messages.success(request, f'The selected feedback has been reclassified as {feedback.type.name}.')
|
||||||
|
feedback.save()
|
||||||
|
else:
|
||||||
|
return render(request, 'nomcom/view_feedback_nominee.html',
|
||||||
|
{'year': year,
|
||||||
|
'nomcom': nomcom,
|
||||||
|
'feedback_types': feedback_types,
|
||||||
|
'reclassify_feedback': feedback,
|
||||||
|
'is_chair_task': True,
|
||||||
|
})
|
||||||
|
|
||||||
last_seen = FeedbackLastSeen.objects.filter(reviewer=request.user.person,nominee=nominee).first()
|
last_seen = FeedbackLastSeen.objects.filter(reviewer=request.user.person,nominee=nominee).first()
|
||||||
last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.timezone.utc)
|
last_seen_time = (last_seen and last_seen.time) or datetime.datetime(year=1, month=1, day=1, tzinfo=datetime.timezone.utc)
|
||||||
|
@ -1000,11 +1051,11 @@ def view_feedback_nominee(request, year, nominee_id):
|
||||||
|
|
||||||
return render(request, 'nomcom/view_feedback_nominee.html',
|
return render(request, 'nomcom/view_feedback_nominee.html',
|
||||||
{'year': year,
|
{'year': year,
|
||||||
'selected': 'view_feedback',
|
|
||||||
'nominee': nominee,
|
'nominee': nominee,
|
||||||
'feedback_types': feedback_types,
|
'feedback_types': feedback_types,
|
||||||
'last_seen_time' : last_seen_time,
|
'last_seen_time' : last_seen_time,
|
||||||
'nomcom': nomcom})
|
'nomcom': nomcom,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||||
|
|
|
@ -145,6 +145,14 @@ class Person(models.Model):
|
||||||
e = self.email_set.filter(active=True).order_by("-time").first()
|
e = self.email_set.filter(active=True).order_by("-time").first()
|
||||||
self._cached_email = e
|
self._cached_email = e
|
||||||
return self._cached_email
|
return self._cached_email
|
||||||
|
def email_allowing_inactive(self):
|
||||||
|
if not hasattr(self, "_cached_email_allowing_inactive"):
|
||||||
|
e = self.email()
|
||||||
|
if not e:
|
||||||
|
e = self.email_set.order_by("-time").first()
|
||||||
|
log.assertion(statement="e is not None", note=f"Person {self.pk} has no Email objects")
|
||||||
|
self._cached_email_allowing_inactive = e
|
||||||
|
return self._cached_email_allowing_inactive
|
||||||
def email_address(self):
|
def email_address(self):
|
||||||
e = self.email()
|
e = self.email()
|
||||||
if e:
|
if e:
|
||||||
|
|
|
@ -112,6 +112,14 @@ class PersonTests(TestCase):
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertContains(r, person.name, status_code=200)
|
self.assertContains(r, person.name, status_code=200)
|
||||||
|
|
||||||
|
def test_case_insensitive(self):
|
||||||
|
# Case insensitive seach
|
||||||
|
person = PersonFactory(name="Test Person")
|
||||||
|
url = urlreverse("ietf.person.views.profile", kwargs={ "email_or_name": "test person"})
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertContains(r, person.name, status_code=200)
|
||||||
|
self.assertNotIn('More than one person', r.content.decode())
|
||||||
|
|
||||||
def test_person_profile_duplicates(self):
|
def test_person_profile_duplicates(self):
|
||||||
# same Person name and email - should not show on the profile as multiple Person records
|
# same Person name and email - should not show on the profile as multiple Person records
|
||||||
person = PersonFactory(name="bazquux@example.com", user__email="bazquux@example.com")
|
person = PersonFactory(name="bazquux@example.com", user__email="bazquux@example.com")
|
||||||
|
|
|
@ -69,11 +69,11 @@ def ajax_select2_search(request, model_name):
|
||||||
|
|
||||||
|
|
||||||
def profile(request, email_or_name):
|
def profile(request, email_or_name):
|
||||||
aliases = Alias.objects.filter(name=email_or_name)
|
aliases = Alias.objects.filter(name__iexact=email_or_name)
|
||||||
persons = set(a.person for a in aliases)
|
persons = set(a.person for a in aliases)
|
||||||
|
|
||||||
if '@' in email_or_name:
|
if '@' in email_or_name:
|
||||||
emails = Email.objects.filter(address=email_or_name)
|
emails = Email.objects.filter(address__iexact=email_or_name)
|
||||||
persons.update(e.person for e in emails)
|
persons.update(e.person for e in emails)
|
||||||
|
|
||||||
persons = [p for p in persons if p and p.id]
|
persons = [p for p in persons if p and p.id]
|
||||||
|
|
|
@ -84,7 +84,7 @@ class AbstractReviewerQueuePolicy:
|
||||||
rotation_list = self._filter_unavailable_reviewers(rotation_list)
|
rotation_list = self._filter_unavailable_reviewers(rotation_list)
|
||||||
return rotation_list
|
return rotation_list
|
||||||
|
|
||||||
def return_reviewer_to_rotation_top(self, reviewer_person, wants_to_be_next):
|
def set_wants_to_be_next(self, reviewer_person):
|
||||||
"""
|
"""
|
||||||
Return a reviewer to the top of the rotation, e.g. because they rejected a review,
|
Return a reviewer to the top of the rotation, e.g. because they rejected a review,
|
||||||
and should retroactively not have been rotated over.
|
and should retroactively not have been rotated over.
|
||||||
|
@ -475,14 +475,13 @@ class RotateAlphabeticallyReviewerQueuePolicy(AbstractReviewerQueuePolicy):
|
||||||
|
|
||||||
return reviewers[next_reviewer_index:] + reviewers[:next_reviewer_index]
|
return reviewers[next_reviewer_index:] + reviewers[:next_reviewer_index]
|
||||||
|
|
||||||
def return_reviewer_to_rotation_top(self, reviewer_person, wants_to_be_next):
|
def set_wants_to_be_next(self, reviewer_person):
|
||||||
# As RotateAlphabetically does not keep a full rotation list,
|
# As RotateAlphabetically does not keep a full rotation list,
|
||||||
# returning someone to a particular order is complex.
|
# returning someone to a particular order is complex.
|
||||||
# Instead, the "assign me next" flag is set.
|
# Instead, the "assign me next" flag is set.
|
||||||
if wants_to_be_next:
|
settings = self._reviewer_settings_for(reviewer_person)
|
||||||
settings = self._reviewer_settings_for(reviewer_person)
|
settings.request_assignment_next = True
|
||||||
settings.request_assignment_next = wants_to_be_next
|
settings.save()
|
||||||
settings.save()
|
|
||||||
|
|
||||||
def _update_skip_next(self, rotation_pks, assignee_person):
|
def _update_skip_next(self, rotation_pks, assignee_person):
|
||||||
"""Decrement skip_next for all users skipped
|
"""Decrement skip_next for all users skipped
|
||||||
|
@ -570,14 +569,13 @@ class LeastRecentlyUsedReviewerQueuePolicy(AbstractReviewerQueuePolicy):
|
||||||
rotation_list += reviewers_with_assignment
|
rotation_list += reviewers_with_assignment
|
||||||
return rotation_list
|
return rotation_list
|
||||||
|
|
||||||
def return_reviewer_to_rotation_top(self, reviewer_person, wants_to_be_next):
|
def set_wants_to_be_next(self, reviewer_person):
|
||||||
# Reviewer rotation for this policy ignores rejected/withdrawn
|
# Reviewer rotation for this policy ignores rejected/withdrawn
|
||||||
# reviews, so it automatically adjusts the position of someone
|
# reviews, so it automatically adjusts the position of someone
|
||||||
# who rejected a review and no further action is needed.
|
# who rejected a review and no further action is needed.
|
||||||
if wants_to_be_next:
|
settings = self._reviewer_settings_for(reviewer_person)
|
||||||
settings = self._reviewer_settings_for(reviewer_person)
|
settings.request_assignment_next = True
|
||||||
settings.request_assignment_next = wants_to_be_next
|
settings.save()
|
||||||
settings.save()
|
|
||||||
|
|
||||||
|
|
||||||
QUEUE_POLICY_NAME_MAPPING = {
|
QUEUE_POLICY_NAME_MAPPING = {
|
||||||
|
|
|
@ -115,7 +115,7 @@ class _Wrapper(TestCase):
|
||||||
return (ReviewerSettings.objects.filter(team=self.team, person=person).first()
|
return (ReviewerSettings.objects.filter(team=self.team, person=person).first()
|
||||||
or ReviewerSettings(team=self.team, person=person))
|
or ReviewerSettings(team=self.team, person=person))
|
||||||
|
|
||||||
def test_return_reviewer_to_rotation_top(self):
|
def test_set_wants_to_be_next(self):
|
||||||
# Subclass must implement this
|
# Subclass must implement this
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -507,11 +507,9 @@ class RotateAlphabeticallyReviewerQueuePolicyTest(_Wrapper.ReviewerQueuePolicyTe
|
||||||
rotation = self.policy.default_reviewer_rotation_list()
|
rotation = self.policy.default_reviewer_rotation_list()
|
||||||
self.assertEqual(rotation, available_reviewers[2:] + available_reviewers[:1])
|
self.assertEqual(rotation, available_reviewers[2:] + available_reviewers[:1])
|
||||||
|
|
||||||
def test_return_reviewer_to_rotation_top(self):
|
def test_set_wants_to_be_next(self):
|
||||||
reviewer = self.append_reviewer()
|
reviewer = self.append_reviewer()
|
||||||
self.policy.return_reviewer_to_rotation_top(reviewer, False)
|
self.policy.set_wants_to_be_next(reviewer)
|
||||||
self.assertFalse(self.reviewer_settings_for(reviewer).request_assignment_next)
|
|
||||||
self.policy.return_reviewer_to_rotation_top(reviewer, True)
|
|
||||||
self.assertTrue(self.reviewer_settings_for(reviewer).request_assignment_next)
|
self.assertTrue(self.reviewer_settings_for(reviewer).request_assignment_next)
|
||||||
|
|
||||||
def test_update_policy_state_for_assignment(self):
|
def test_update_policy_state_for_assignment(self):
|
||||||
|
@ -725,11 +723,9 @@ class LeastRecentlyUsedReviewerQueuePolicyTest(_Wrapper.ReviewerQueuePolicyTestC
|
||||||
self.assertEqual(self.policy.default_reviewer_rotation_list(),
|
self.assertEqual(self.policy.default_reviewer_rotation_list(),
|
||||||
available_reviewers[2:] + [first_reviewer, second_reviewer])
|
available_reviewers[2:] + [first_reviewer, second_reviewer])
|
||||||
|
|
||||||
def test_return_reviewer_to_rotation_top(self):
|
def test_set_wants_to_be_next(self):
|
||||||
reviewer = self.append_reviewer()
|
reviewer = self.append_reviewer()
|
||||||
self.policy.return_reviewer_to_rotation_top(reviewer, False)
|
self.policy.set_wants_to_be_next(reviewer)
|
||||||
self.assertFalse(self.reviewer_settings_for(reviewer).request_assignment_next)
|
|
||||||
self.policy.return_reviewer_to_rotation_top(reviewer, True)
|
|
||||||
self.assertTrue(self.reviewer_settings_for(reviewer).request_assignment_next)
|
self.assertTrue(self.reviewer_settings_for(reviewer).request_assignment_next)
|
||||||
|
|
||||||
def test_assign_reviewer_updates_skip_next_without_add_skip(self):
|
def test_assign_reviewer_updates_skip_next_without_add_skip(self):
|
||||||
|
|
|
@ -79,6 +79,11 @@ def review_assignments_to_list_for_docs(docs):
|
||||||
|
|
||||||
return extract_revision_ordered_review_assignments_for_documents_and_replaced(assignment_qs, doc_names)
|
return extract_revision_ordered_review_assignments_for_documents_and_replaced(assignment_qs, doc_names)
|
||||||
|
|
||||||
|
def review_requests_to_list_for_docs(docs):
|
||||||
|
review_requests_qs = ReviewRequest.objects.filter(Q(state_id='requested'))
|
||||||
|
doc_names = [d.name for d in docs]
|
||||||
|
return extract_revision_ordered_review_requests_for_documents_and_replaced(review_requests_qs, doc_names)
|
||||||
|
|
||||||
def augment_review_requests_with_events(review_reqs):
|
def augment_review_requests_with_events(review_reqs):
|
||||||
req_dict = { r.pk: r for r in review_reqs }
|
req_dict = { r.pk: r for r in review_reqs }
|
||||||
for e in ReviewRequestDocEvent.objects.filter(review_request__in=review_reqs, type__in=["assigned_review_request", "closed_review_request"]).order_by("time"):
|
for e in ReviewRequestDocEvent.objects.filter(review_request__in=review_reqs, type__in=["assigned_review_request", "closed_review_request"]).order_by("time"):
|
||||||
|
@ -589,10 +594,12 @@ def suggested_review_requests_for_team(team):
|
||||||
and existing.reviewassignment_set.filter(state_id__in=("assigned", "accepted")).exists()
|
and existing.reviewassignment_set.filter(state_id__in=("assigned", "accepted")).exists()
|
||||||
and (not existing.requested_rev or existing.requested_rev == request.doc.rev))
|
and (not existing.requested_rev or existing.requested_rev == request.doc.rev))
|
||||||
request_closed = existing.state_id not in ('requested','assigned')
|
request_closed = existing.state_id not in ('requested','assigned')
|
||||||
|
# Is there a review request for this document already in system
|
||||||
|
requested = existing.state_id in ('requested') and (not existing.requested_rev or existing.requested_rev == request.doc.rev)
|
||||||
# at least one assignment was completed for the requested version or the current doc version if no specific version was requested:
|
# at least one assignment was completed for the requested version or the current doc version if no specific version was requested:
|
||||||
some_assignment_completed = existing.reviewassignment_set.filter(reviewed_rev=existing.requested_rev or existing.doc.rev, state_id='completed').exists()
|
some_assignment_completed = existing.reviewassignment_set.filter(reviewed_rev=existing.requested_rev or existing.doc.rev, state_id='completed').exists()
|
||||||
|
|
||||||
return any([no_review_document, no_review_rev, pending, request_closed, some_assignment_completed])
|
return any([no_review_document, no_review_rev, pending, request_closed, requested, some_assignment_completed])
|
||||||
|
|
||||||
res = [r for r in requests.values()
|
res = [r for r in requests.values()
|
||||||
if not any(blocks(e, r) for e in existing_requests[r.doc_id])]
|
if not any(blocks(e, r) for e in existing_requests[r.doc_id])]
|
||||||
|
|
161
ietf/settings.py
161
ietf/settings.py
|
@ -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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import datetime
|
import datetime
|
||||||
import warnings
|
import warnings
|
||||||
|
from hashlib import sha384
|
||||||
from typing import Any, Dict, List, Tuple # pyflakes:ignore
|
from typing import Any, Dict, List, Tuple # pyflakes:ignore
|
||||||
|
|
||||||
warnings.simplefilter("always", DeprecationWarning)
|
warnings.simplefilter("always", DeprecationWarning)
|
||||||
|
@ -657,6 +658,7 @@ URL_REGEXPS = {
|
||||||
"acronym": r"(?P<acronym>[-a-z0-9]+)",
|
"acronym": r"(?P<acronym>[-a-z0-9]+)",
|
||||||
"bofreq": r"(?P<name>bofreq-[-a-z0-9]+)",
|
"bofreq": r"(?P<name>bofreq-[-a-z0-9]+)",
|
||||||
"charter": r"(?P<name>charter-[-a-z0-9]+)",
|
"charter": r"(?P<name>charter-[-a-z0-9]+)",
|
||||||
|
"statement": r"(?P<name>statement-[-a-z0-9]+)",
|
||||||
"date": r"(?P<date>\d{4}-\d{2}-\d{2})",
|
"date": r"(?P<date>\d{4}-\d{2}-\d{2})",
|
||||||
"name": r"(?P<name>[A-Za-z0-9._+-]+?)",
|
"name": r"(?P<name>[A-Za-z0-9._+-]+?)",
|
||||||
"document": r"(?P<document>[a-z][-a-z0-9]+)", # regular document names
|
"document": r"(?P<document>[a-z][-a-z0-9]+)", # regular document names
|
||||||
|
@ -668,7 +670,6 @@ URL_REGEXPS = {
|
||||||
# Override this in settings_local.py if needed
|
# Override this in settings_local.py if needed
|
||||||
# *_PATH variables ends with a slash/ .
|
# *_PATH variables ends with a slash/ .
|
||||||
|
|
||||||
#DOCUMENT_PATH_PATTERN = '/a/www/ietf-ftp/{doc.type_id}/'
|
|
||||||
DOCUMENT_PATH_PATTERN = '/a/ietfdata/doc/{doc.type_id}/'
|
DOCUMENT_PATH_PATTERN = '/a/ietfdata/doc/{doc.type_id}/'
|
||||||
INTERNET_DRAFT_PATH = '/a/ietfdata/doc/draft/repository'
|
INTERNET_DRAFT_PATH = '/a/ietfdata/doc/draft/repository'
|
||||||
INTERNET_DRAFT_PDF_PATH = '/a/www/ietf-datatracker/pdf/'
|
INTERNET_DRAFT_PDF_PATH = '/a/www/ietf-datatracker/pdf/'
|
||||||
|
@ -725,44 +726,6 @@ DOC_ACTION_HOLDER_AGE_LIMIT_DAYS = 20
|
||||||
CACHE_MIDDLEWARE_SECONDS = 300
|
CACHE_MIDDLEWARE_SECONDS = 300
|
||||||
CACHE_MIDDLEWARE_KEY_PREFIX = ''
|
CACHE_MIDDLEWARE_KEY_PREFIX = ''
|
||||||
|
|
||||||
# The default with no CACHES setting is 'django.core.cache.backends.locmem.LocMemCache'
|
|
||||||
# This setting is possibly overridden further down, after the import of settings_local
|
|
||||||
CACHES = {
|
|
||||||
'default': {
|
|
||||||
'BACKEND': 'ietf.utils.cache.LenientMemcacheCache',
|
|
||||||
'LOCATION': '127.0.0.1:11211',
|
|
||||||
'VERSION': __version__,
|
|
||||||
'KEY_PREFIX': 'ietf:dt',
|
|
||||||
},
|
|
||||||
'sessions': {
|
|
||||||
'BACKEND': 'ietf.utils.cache.LenientMemcacheCache',
|
|
||||||
'LOCATION': '127.0.0.1:11211',
|
|
||||||
# No release-specific VERSION setting.
|
|
||||||
'KEY_PREFIX': 'ietf:dt',
|
|
||||||
},
|
|
||||||
'htmlized': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
|
||||||
'LOCATION': '/a/cache/datatracker/htmlized',
|
|
||||||
'OPTIONS': {
|
|
||||||
'MAX_ENTRIES': 100000, # 100,000
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'pdfized': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
|
||||||
'LOCATION': '/a/cache/datatracker/pdfized',
|
|
||||||
'OPTIONS': {
|
|
||||||
'MAX_ENTRIES': 100000, # 100,000
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'slowpages': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
|
||||||
'LOCATION': '/a/cache/datatracker/slowpages',
|
|
||||||
'OPTIONS': {
|
|
||||||
'MAX_ENTRIES': 5000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
HTMLIZER_VERSION = 1
|
HTMLIZER_VERSION = 1
|
||||||
HTMLIZER_URL_PREFIX = "/doc/html"
|
HTMLIZER_URL_PREFIX = "/doc/html"
|
||||||
HTMLIZER_CACHE_TIME = 60*60*24*14 # 14 days
|
HTMLIZER_CACHE_TIME = 60*60*24*14 # 14 days
|
||||||
|
@ -802,7 +765,7 @@ NOMCOM_PUBLIC_KEYS_DIR = '/a/www/nomcom/public_keys/'
|
||||||
NOMCOM_FROM_EMAIL = 'nomcom-chair-{year}@ietf.org'
|
NOMCOM_FROM_EMAIL = 'nomcom-chair-{year}@ietf.org'
|
||||||
OPENSSL_COMMAND = '/usr/bin/openssl'
|
OPENSSL_COMMAND = '/usr/bin/openssl'
|
||||||
DAYS_TO_EXPIRE_NOMINATION_LINK = ''
|
DAYS_TO_EXPIRE_NOMINATION_LINK = ''
|
||||||
NOMINEE_FEEDBACK_TYPES = ['comment', 'questio', 'nomina']
|
NOMINEE_FEEDBACK_TYPES = ['comment', 'questio', 'nomina', 'obe']
|
||||||
|
|
||||||
# SlideSubmission settings
|
# SlideSubmission settings
|
||||||
SLIDE_STAGING_PATH = '/a/www/www6s/staging/'
|
SLIDE_STAGING_PATH = '/a/www/www6s/staging/'
|
||||||
|
@ -1228,6 +1191,84 @@ else:
|
||||||
MIDDLEWARE += DEV_MIDDLEWARE
|
MIDDLEWARE += DEV_MIDDLEWARE
|
||||||
TEMPLATES[0]['OPTIONS']['context_processors'] += DEV_TEMPLATE_CONTEXT_PROCESSORS
|
TEMPLATES[0]['OPTIONS']['context_processors'] += DEV_TEMPLATE_CONTEXT_PROCESSORS
|
||||||
|
|
||||||
|
if 'CACHES' not in locals():
|
||||||
|
if SERVER_MODE == 'production':
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'ietf.utils.cache.LenientMemcacheCache',
|
||||||
|
'LOCATION': '127.0.0.1:11211',
|
||||||
|
'VERSION': __version__,
|
||||||
|
'KEY_PREFIX': 'ietf:dt',
|
||||||
|
'KEY_FUNCTION': lambda key, key_prefix, version: (
|
||||||
|
f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'sessions': {
|
||||||
|
'BACKEND': 'ietf.utils.cache.LenientMemcacheCache',
|
||||||
|
'LOCATION': '127.0.0.1:11211',
|
||||||
|
# No release-specific VERSION setting.
|
||||||
|
'KEY_PREFIX': 'ietf:dt',
|
||||||
|
},
|
||||||
|
'htmlized': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||||
|
'LOCATION': '/a/cache/datatracker/htmlized',
|
||||||
|
'OPTIONS': {
|
||||||
|
'MAX_ENTRIES': 100000, # 100,000
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'pdfized': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||||
|
'LOCATION': '/a/cache/datatracker/pdfized',
|
||||||
|
'OPTIONS': {
|
||||||
|
'MAX_ENTRIES': 100000, # 100,000
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'slowpages': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||||
|
'LOCATION': '/a/cache/datatracker/slowpages',
|
||||||
|
'OPTIONS': {
|
||||||
|
'MAX_ENTRIES': 5000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||||
|
#'BACKEND': 'ietf.utils.cache.LenientMemcacheCache',
|
||||||
|
#'LOCATION': '127.0.0.1:11211',
|
||||||
|
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||||
|
'VERSION': __version__,
|
||||||
|
'KEY_PREFIX': 'ietf:dt',
|
||||||
|
},
|
||||||
|
'sessions': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||||
|
},
|
||||||
|
'htmlized': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||||
|
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||||
|
'LOCATION': '/var/cache/datatracker/htmlized',
|
||||||
|
'OPTIONS': {
|
||||||
|
'MAX_ENTRIES': 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'pdfized': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||||
|
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||||
|
'LOCATION': '/var/cache/datatracker/pdfized',
|
||||||
|
'OPTIONS': {
|
||||||
|
'MAX_ENTRIES': 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'slowpages': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||||
|
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||||
|
'LOCATION': '/var/cache/datatracker/',
|
||||||
|
'OPTIONS': {
|
||||||
|
'MAX_ENTRIES': 5000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
# We provide a secret key only for test and development modes. It's
|
# We provide a secret key only for test and development modes. It's
|
||||||
# absolutely vital that django fails to start in production mode unless a
|
# absolutely vital that django fails to start in production mode unless a
|
||||||
|
@ -1238,44 +1279,6 @@ if SERVER_MODE != 'production':
|
||||||
loaders = TEMPLATES[0]['OPTIONS']['loaders']
|
loaders = TEMPLATES[0]['OPTIONS']['loaders']
|
||||||
loaders = tuple(l for e in loaders for l in (e[1] if isinstance(e, tuple) and "cached.Loader" in e[0] else (e,)))
|
loaders = tuple(l for e in loaders for l in (e[1] if isinstance(e, tuple) and "cached.Loader" in e[0] else (e,)))
|
||||||
TEMPLATES[0]['OPTIONS']['loaders'] = loaders
|
TEMPLATES[0]['OPTIONS']['loaders'] = loaders
|
||||||
|
|
||||||
CACHES = {
|
|
||||||
'default': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
|
||||||
#'BACKEND': 'ietf.utils.cache.LenientMemcacheCache',
|
|
||||||
#'LOCATION': '127.0.0.1:11211',
|
|
||||||
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
|
||||||
'VERSION': __version__,
|
|
||||||
'KEY_PREFIX': 'ietf:dt',
|
|
||||||
},
|
|
||||||
'sessions': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
|
||||||
},
|
|
||||||
'htmlized': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
|
||||||
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
|
||||||
'LOCATION': '/var/cache/datatracker/htmlized',
|
|
||||||
'OPTIONS': {
|
|
||||||
'MAX_ENTRIES': 1000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'pdfized': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
|
||||||
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
|
||||||
'LOCATION': '/var/cache/datatracker/pdfized',
|
|
||||||
'OPTIONS': {
|
|
||||||
'MAX_ENTRIES': 1000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'slowpages': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
|
||||||
#'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
|
||||||
'LOCATION': '/var/cache/datatracker/',
|
|
||||||
'OPTIONS': {
|
|
||||||
'MAX_ENTRIES': 5000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
SESSION_ENGINE = "django.contrib.sessions.backends.db"
|
SESSION_ENGINE = "django.contrib.sessions.backends.db"
|
||||||
|
|
||||||
if 'SECRET_KEY' not in locals():
|
if 'SECRET_KEY' not in locals():
|
||||||
|
|
|
@ -424,8 +424,8 @@ sup, sub {
|
||||||
/* Authors */
|
/* Authors */
|
||||||
address, address.vcard {
|
address, address.vcard {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
// margin: 2em 0 var(--line) 3ch;
|
// margin: var(--line) 0 var(--line) 3ch
|
||||||
margin-top: 2em;
|
margin-top: var(--line);
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
margin-bottom: var(--line);
|
margin-bottom: var(--line);
|
||||||
margin-left: 3ch;
|
margin-left: 3ch;
|
||||||
|
|
|
@ -88,6 +88,34 @@ html {
|
||||||
scroll-padding-top: 60px;
|
scroll-padding-top: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make submenus open on hover.
|
||||||
|
@include media-breakpoint-up(lg) {
|
||||||
|
.dropdown-menu>li>ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu>li:hover>ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
.leftmenu .nav>li>ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftmenu .nav>li:hover>ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(.dropdown-menu, .leftmenu .nav) .dropdown-menu {
|
||||||
|
top: 0;
|
||||||
|
left: 100%;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
// Make textareas in forms use a monospace font
|
// Make textareas in forms use a monospace font
|
||||||
textarea.form-control {
|
textarea.form-control {
|
||||||
font-family: $font-family-code;
|
font-family: $font-family-code;
|
||||||
|
|
|
@ -93,18 +93,6 @@ $(document)
|
||||||
|
|
||||||
$(document)
|
$(document)
|
||||||
.ready(function () {
|
.ready(function () {
|
||||||
|
|
||||||
function dropdown_hover(e) {
|
|
||||||
var navbar = $(this)
|
|
||||||
.closest(".navbar");
|
|
||||||
if (navbar.length === 0 || navbar.find(".navbar-toggler")
|
|
||||||
.is(":hidden")) {
|
|
||||||
$(this)
|
|
||||||
.children(".dropdown-toggle")
|
|
||||||
.dropdown(e.type == "mouseenter" ? "show" : "hide");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load data for the menu
|
// load data for the menu
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: $(document.body)
|
url: $(document.body)
|
||||||
|
@ -140,9 +128,6 @@ $(document)
|
||||||
}
|
}
|
||||||
attachTo.append(menu.join(""));
|
attachTo.append(menu.join(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
$("ul.nav li.dropdown, ul.nav li.dropend")
|
|
||||||
.on("mouseenter mouseleave", dropdown_hover);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
29
ietf/static/js/upload_statement.js
Normal file
29
ietf/static/js/upload_statement.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
$(document)
|
||||||
|
.ready(function () {
|
||||||
|
var form = $("form.upload-content");
|
||||||
|
// review submission selection
|
||||||
|
form.find("[name=statement_submission]")
|
||||||
|
.on("click change", function () {
|
||||||
|
var val = form.find("[name=statement_submission]:checked")
|
||||||
|
.val();
|
||||||
|
|
||||||
|
var shouldBeVisible = {
|
||||||
|
enter: ['[name="statement_content"]'],
|
||||||
|
upload: ['[name="statement_file"]'],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var v in shouldBeVisible) {
|
||||||
|
for (var i in shouldBeVisible[v]) {
|
||||||
|
var selector = shouldBeVisible[v][i];
|
||||||
|
var row = form.find(selector);
|
||||||
|
if (!row.is(".row"))
|
||||||
|
row = row.closest(".row");
|
||||||
|
if ($.inArray(selector, shouldBeVisible[val]) != -1)
|
||||||
|
row.show();
|
||||||
|
else
|
||||||
|
row.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.trigger("change");
|
||||||
|
});
|
|
@ -2878,7 +2878,7 @@ class ApiSubmissionTests(BaseSubmitTestCase):
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
r.json(),
|
r.json(),
|
||||||
{'id': str(s.pk), 'state': 'validating'},
|
{'id': str(s.pk), 'state': 'validating', 'state_desc': s.state.name},
|
||||||
)
|
)
|
||||||
|
|
||||||
s.state_id = 'uploaded'
|
s.state_id = 'uploaded'
|
||||||
|
@ -2887,7 +2887,7 @@ class ApiSubmissionTests(BaseSubmitTestCase):
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
r.json(),
|
r.json(),
|
||||||
{'id': str(s.pk), 'state': 'uploaded'},
|
{'id': str(s.pk), 'state': 'uploaded', 'state_desc': s.state.name},
|
||||||
)
|
)
|
||||||
|
|
||||||
# try an invalid one
|
# try an invalid one
|
||||||
|
|
|
@ -183,6 +183,7 @@ def api_submission_status(request, submission_id):
|
||||||
{
|
{
|
||||||
'id': str(submission.pk),
|
'id': str(submission.pk),
|
||||||
'state': submission.state.slug,
|
'state': submission.state.slug,
|
||||||
|
'state_desc': submission.state.name,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
<a href="#"
|
<a href="#"
|
||||||
class="nav-link dropdown-toggle"
|
class="nav-link dropdown-toggle"
|
||||||
role="button"
|
role="button"
|
||||||
data-bs-auto-close="false"
|
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false">
|
aria-expanded="false">
|
||||||
Groups
|
Groups
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{# Copyright The IETF Trust 2016-2020, All Rights Reserved #}
|
{# Copyright The IETF Trust 2016-2023, All Rights Reserved #}
|
||||||
{% load origin %}
|
{% load origin %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load ietf_filters %}
|
{% load ietf_filters %}
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
{% if doc.stream %}
|
{% if doc.stream %}
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
{% if doc.stream.slug != "legacy" %}
|
{% if doc.stream.slug != "legacy" %}
|
||||||
<a href="{% url "ietf.help.views.state" doc=doc.type.slug type=doc.stream.slug %}">
|
<a href="{% url "ietf.doc.views_help.state_help" type=stream %}">
|
||||||
{% if doc.stream_id == 'ietf' %}
|
{% if doc.stream_id == 'ietf' %}
|
||||||
WG
|
WG
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -274,12 +274,12 @@
|
||||||
IESG
|
IESG
|
||||||
</th>
|
</th>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<a href="{% url "ietf.help.views.state" doc=doc.type.slug type="iesg" %}">
|
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iesg" %}">
|
||||||
IESG state
|
IESG state
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<td class="edit">
|
<td class="edit">
|
||||||
{% if iesg_state.slug != 'idexists' and can_edit %}
|
{% if iesg_state.slug != 'idexists' and iesg_state.slug != 'dead' and can_edit %}
|
||||||
<a class="btn btn-primary btn-sm"
|
<a class="btn btn-primary btn-sm"
|
||||||
href="{% url 'ietf.doc.views_draft.change_state' name=doc.name %}">
|
href="{% url 'ietf.doc.views_draft.change_state' name=doc.name %}">
|
||||||
Edit
|
Edit
|
||||||
|
@ -468,7 +468,7 @@
|
||||||
IANA
|
IANA
|
||||||
</th>
|
</th>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<a href="{% url "ietf.help.views.state" doc=doc.type.slug type="iana-review" %}">
|
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iana-review" %}">
|
||||||
IANA review state
|
IANA review state
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
|
@ -497,7 +497,7 @@
|
||||||
{% if not can_edit_iana_state and not iana_review_state %}IANA{% endif %}
|
{% if not can_edit_iana_state and not iana_review_state %}IANA{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<a href="{% url "ietf.help.views.state" doc=doc.type.slug type="iana-action" %}">
|
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iana-action" %}">
|
||||||
IANA action state
|
IANA action state
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
|
@ -526,7 +526,7 @@
|
||||||
{% if not can_edit_iana_state and not iana_review_state and not iana_action_state %}IANA{% endif %}
|
{% if not can_edit_iana_state and not iana_review_state and not iana_action_state %}IANA{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<a href="{% url "ietf.help.views.state" doc=doc.type.slug type="iana-experts" %}">
|
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iana-experts" %}">
|
||||||
IANA expert review state
|
IANA expert review state
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
|
@ -587,7 +587,7 @@
|
||||||
RFC Editor
|
RFC Editor
|
||||||
</th>
|
</th>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
<a href="{% url "ietf.help.views.state" doc=doc.type.slug type="rfceditor" %}">
|
<a href="{% url "ietf.doc.views_help.state_help" type="draft-rfceditor" %}">
|
||||||
RFC Editor state
|
RFC Editor state
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
|
|
|
@ -339,7 +339,10 @@ href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">E
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% for review_assignment in review_assignments %}
|
{% for review_assignment in review_assignments %}
|
||||||
{% include "doc/review_assignment_summary.html" with current_doc_name=doc.name current_rev=doc.rev %}
|
{% 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 %}
|
{% endfor %}
|
||||||
{% if no_review_from_teams %}
|
{% if no_review_from_teams %}
|
||||||
{% for team in no_review_from_teams %}
|
{% for team in no_review_from_teams %}
|
||||||
|
|
134
ietf/templates/doc/document_statement.html
Normal file
134
ietf/templates/doc/document_statement.html
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{# Copyright The IETF Trust 2023, All Rights Reserved #}
|
||||||
|
{% load origin %}
|
||||||
|
{% load static %}
|
||||||
|
{% load ietf_filters %}
|
||||||
|
{% load person_filters textfilters %}
|
||||||
|
{% block title %}{{ doc.title }}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
{% origin %}
|
||||||
|
{{ top|safe }}
|
||||||
|
{% include "doc/revisions_list.html" %}
|
||||||
|
<div id="timeline"></div>
|
||||||
|
{% if doc.rev != latest_rev %}
|
||||||
|
<div class="alert alert-warning my-3">The information below is for an older version of this statement.</div>
|
||||||
|
{% endif %}
|
||||||
|
<table class="table table-sm table-borderless">
|
||||||
|
<tbody class="meta border-top">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Document</th>
|
||||||
|
<th scope="row">Type</th>
|
||||||
|
<td class="edit"></td>
|
||||||
|
<td>
|
||||||
|
<span id="statement-type">{% if doc.get_state.slug != "active" %}{{doc.get_state.name}} {% endif %}{% if doc.group %}{{doc.group.acronym|upper}} {%endif%}Statement</span>
|
||||||
|
{% if snapshot %}<span class="badge rounded-pill bg-warning">Snapshot</span>{% endif %}
|
||||||
|
{% if replaced_by %}<div>Replaced by {{ replaced_by|urlize_related_source_list:False|join:", " }}</div>{% endif %}
|
||||||
|
{% if replaces %}<div>Replaces {{ replaces|urlize_related_target_list:False|join:", " }}</div>{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<th scope="row">Title</th>
|
||||||
|
<td class="edit"></td>
|
||||||
|
<th scope="row" >{{ doc.title }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<th scope="row">Published</th>
|
||||||
|
<td class="edit"></td>
|
||||||
|
<td id="published">{{ published|date:"Y-m-d" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<th scope="row">Metadata last updated</th>
|
||||||
|
<td class="edit"></td>
|
||||||
|
<td>{{ doc.time|date:"Y-m-d" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<th scope="row">
|
||||||
|
<a href="{% url 'ietf.doc.views_help.state_help' type='statement' %}">State</a>
|
||||||
|
</th>
|
||||||
|
<td class="edit"></td>
|
||||||
|
<td id="statement-state">
|
||||||
|
{% if doc.get_state %}
|
||||||
|
<span title="{{ doc.get_state.desc }}" class="{% if doc.get_state.name|slugify == 'active' %}text-success{% else %}text-danger{% endif %}">{{ doc.get_state.name }}</span>
|
||||||
|
{% else %}
|
||||||
|
No document state
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% with doc.docextresource_set.all as resources %}
|
||||||
|
{% if resources or can_manage %}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<th scope="row">Additional resources</th>
|
||||||
|
<td class="edit">
|
||||||
|
{% if can_manage %}
|
||||||
|
<a class="btn btn-primary btn-sm"
|
||||||
|
href="{% url 'ietf.doc.views_draft.edit_doc_extresources' name=doc.name %}">Edit</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if resources %}
|
||||||
|
{% for resource in resources|dictsort:"display_name" %}
|
||||||
|
{% if resource.name.type.slug == 'url' or resource.name.type.slug == 'email' %}
|
||||||
|
<a href="{{ resource.value }}" title="{{ resource.name.name }}">
|
||||||
|
{% firstof resource.display_name resource.name.name %}
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
{# Maybe make how a resource displays itself a method on the class so templates aren't doing this switching #}
|
||||||
|
{% else %}
|
||||||
|
<span title="{{ resource.name.name }}">{% firstof resource.display_name resource.name.name %}: {{ resource.value|escape }}</span>
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<th scope="row">
|
||||||
|
Send notices to
|
||||||
|
</th>
|
||||||
|
<td class="edit">
|
||||||
|
{% if not snapshot %}
|
||||||
|
{% if can_manage %}
|
||||||
|
{% doc_edit_button 'ietf.doc.views_doc.edit_notify' name=doc.name %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ doc.notify|default:'<span class="text-muted">(None)</span>' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% if not snapshot %}
|
||||||
|
{% if request.user|has_role:"Secretariat" %}
|
||||||
|
<p id="change-statement">
|
||||||
|
<a class="btn btn-primary"
|
||||||
|
href="{% url 'ietf.doc.views_statement.submit' name=doc.name %}">
|
||||||
|
Edit or upload revised statement text
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="card mt-5">
|
||||||
|
<div class="card-header">
|
||||||
|
{{ doc.name }}-{{ doc.rev }}
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-break">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
<script src="{% static 'ietf/js/d3.js' %}">
|
||||||
|
</script>
|
||||||
|
<script src="{% static 'ietf/js/document_timeline.js' %}">
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -52,8 +52,35 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<h2 class="mt-4">Documents for {{ ad_name }}</h2>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if not_balloted_docs %}
|
||||||
|
<h2 class="mt-4">Missing ballot positions for {{ ad_name }}</h2>
|
||||||
|
<table class="table table-sm table-striped tablesorter">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" data-sort="document">Document</th>
|
||||||
|
<th scope="col" data-sort="status">Status</th>
|
||||||
|
<th scope="col" class="d-none d-sm-table-cell" data-sort="responsible">Responsible AD</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for doc in not_balloted_docs %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ doc.displayname_with_link }}</td>
|
||||||
|
{% include "doc/search/status_columns.html" %}
|
||||||
|
<td class="d-none d-sm-table-cell">
|
||||||
|
{% if doc.ad %}
|
||||||
|
{% person_link doc.ad %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-body-secondary">(None)</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
<h2 class="mt-4">Documents for {{ ad_name }}</h2>
|
||||||
{% include "doc/search/search_results.html" with start_table=True end_table=True %}
|
{% include "doc/search/search_results.html" with start_table=True end_table=True %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js %}
|
{% block js %}
|
||||||
|
|
|
@ -19,10 +19,9 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="text-body-secondary"
|
<a class="text-body-secondary"
|
||||||
href="{% url 'ietf.doc.views_review.review_request' review_assignment.review_request.doc.name review_assignment.review_request.pk %}">
|
href="{% url 'ietf.doc.views_review.review_request' review_assignment.review_request.doc.name review_assignment.review_request.pk %}">
|
||||||
{{ review_assignment.review_request.team.acronym|upper }} {{ review_assignment.review_request.type.name }} Review</a>
|
{{ review_assignment.review_request.team.acronym|upper }} {{ review_assignment.review_request.type.name }} Review due {{ review_assignment.review_request.deadline|date:"Y-m-d" }}</a>
|
||||||
<span class="badge rounded-pill text-bg-secondary ms-1">
|
<span class="badge rounded-pill ms-1 bg-secondary">
|
||||||
<i class="bi bi-hourglass-split"></i>
|
Incomplete
|
||||||
Incomplete, due {{ review_assignment.review_request.deadline|date:"Y-m-d" }}
|
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
9
ietf/templates/doc/review_request_summary.html
Normal file
9
ietf/templates/doc/review_request_summary.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<div class="review-request-summary">
|
||||||
|
<a class="text-body-secondary"
|
||||||
|
href="{% url 'ietf.doc.views_review.review_request' review_request.doc.name review_request.pk %}">
|
||||||
|
{{ review_request.team.acronym|upper }} {{ review_request.type.name }} Review due {{ review_request.deadline|date:"Y-m-d" }}
|
||||||
|
</a>
|
||||||
|
<span class="badge rounded-pill ms-1 bg-secondary">
|
||||||
|
Requested
|
||||||
|
</span>
|
||||||
|
</div>
|
|
@ -3,9 +3,7 @@
|
||||||
{% origin %}
|
{% origin %}
|
||||||
{% load ietf_filters ballot_icon person_filters %}
|
{% load ietf_filters ballot_icon person_filters %}
|
||||||
<td class="status">
|
<td class="status">
|
||||||
{% if doc.ballot %}
|
<div class="float-end ms-1 mb-1" id="ballot-icon-{{ doc.name }}">{% ballot_icon doc %}</div>
|
||||||
<div class="float-end ms-1 mb-1" id="ballot-icon-{{ doc.name }}">{% ballot_icon doc %}</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if not doc.get_state_slug == "rfc" %}
|
{% if not doc.get_state_slug == "rfc" %}
|
||||||
{% if '::' in doc.friendly_state %}
|
{% if '::' in doc.friendly_state %}
|
||||||
{{ doc.friendly_state|safe }}
|
{{ doc.friendly_state|safe }}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||||
{% load origin %}
|
{% load origin static ietf_filters textfilters %}
|
||||||
{% load static %}
|
|
||||||
{% block title %}{{ title }}{% endblock %}
|
{% block title %}{{ title }}{% endblock %}
|
||||||
{% block pagehead %}
|
{% block pagehead %}
|
||||||
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
|
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
|
||||||
|
@ -27,7 +26,7 @@
|
||||||
{% for state in states %}
|
{% for state in states %}
|
||||||
<tr id="{{ state.slug|default:"idexists" }}">
|
<tr id="{{ state.slug|default:"idexists" }}">
|
||||||
<th scope="row">{{ state.name }}</th>
|
<th scope="row">{{ state.name }}</th>
|
||||||
<td>{{ state.desc|safe|linebreaksbr }}</td>
|
<td>{{ state.desc|urlize_ietf_docs|linkify }}</td>
|
||||||
{% if has_next_states %}
|
{% if has_next_states %}
|
||||||
<td>
|
<td>
|
||||||
{% for s in state.next_states.all %}
|
{% for s in state.next_states.all %}
|
||||||
|
|
18
ietf/templates/doc/statement/new_statement.html
Normal file
18
ietf/templates/doc/statement/new_statement.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{# Copyright The IETF Trust 2023, All Rights Reserved #}
|
||||||
|
{% load origin django_bootstrap5 static textfilters %}
|
||||||
|
{% block title %}Start a new Statement{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
{% origin %}
|
||||||
|
<h1>Start a new Statement</h1>
|
||||||
|
<form class="upload-content form-horizontal"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form layout="horizontal" %}
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
<script src="{% static 'ietf/js/upload_statement.js' %}"></script>
|
||||||
|
{% endblock %}
|
1
ietf/templates/doc/statement/statement_template.md
Normal file
1
ietf/templates/doc/statement/statement_template.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Replace this with the content of the statement in markdown source
|
23
ietf/templates/doc/statement/upload_content.html
Normal file
23
ietf/templates/doc/statement/upload_content.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{# Copyright The IETF Trust 2023, All Rights Reserved #}
|
||||||
|
{% load origin django_bootstrap5 static %}
|
||||||
|
{% block title %}Upload new revision: {{ doc.name }}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
{% origin %}
|
||||||
|
<h1>
|
||||||
|
Upload New Revision
|
||||||
|
<br>
|
||||||
|
<small class="text-muted">{{ doc.name }}</small>
|
||||||
|
</h1>
|
||||||
|
<form class="upload-content form-horizontal mt-3"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form layout="horizontal" %}
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
<a class="btn btn-secondary float-end" href="{{ doc.get_absolute_url }}">Back</a>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
<script src="{% static 'ietf/js/upload_statement.js' %}"></script>
|
||||||
|
{% endblock %}
|
|
@ -11,6 +11,11 @@
|
||||||
<br>
|
<br>
|
||||||
<small class="text-body-secondary">{{ doc }}</small>
|
<small class="text-body-secondary">{{ doc }}</small>
|
||||||
</h1>
|
</h1>
|
||||||
|
{% if doc.ad is None %}
|
||||||
|
<div class="alert alert-warning my-3">
|
||||||
|
A responsible AD must be set before last call can be requested.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<form class="my-3 edit-last-call-text" method="post">
|
<form class="my-3 edit-last-call-text" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form last_call_form %}
|
{% bootstrap_form last_call_form %}
|
||||||
|
@ -18,15 +23,17 @@
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
name="save_last_call_text"
|
name="save_last_call_text"
|
||||||
value="Save Last Call Text">Save text</button>
|
value="Save Last Call Text">Save text</button>
|
||||||
<button type="submit"
|
{% if doc.ad is not None %}
|
||||||
class="btn btn-warning"
|
<button type="submit"
|
||||||
name="send_last_call_request"
|
class="btn btn-warning"
|
||||||
value="Save and Request Last Call">
|
name="send_last_call_request"
|
||||||
Save text & request last call
|
value="Save and Request Last Call">
|
||||||
</button>
|
Save text & request last call
|
||||||
{% if user|has_role:"Secretariat" %}
|
</button>
|
||||||
<a class="btn btn-warning"
|
{% if user|has_role:"Secretariat" %}
|
||||||
href="{% url 'ietf.doc.views_ballot.make_last_call' name=doc.name %}">Issue last call</a>
|
<a class="btn btn-warning"
|
||||||
|
href="{% url 'ietf.doc.views_ballot.make_last_call' name=doc.name %}">Issue last call</a>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
{% extends "group/group_base.html" %}
|
|
||||||
{# Copyright The IETF Trust 2021, All Rights Reserved #}
|
|
||||||
{% load origin %}
|
|
||||||
{% load ietf_filters %}
|
|
||||||
{% block group_content %}
|
|
||||||
{% if charter %}
|
|
||||||
{% comment %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 h2 text-center bg-info border-end">Current about page rendering</div>
|
|
||||||
<div class="col-md-6 h2 text-center bg-info">Markdown rendering</div>
|
|
||||||
</div>
|
|
||||||
<div class="row border-bottom text-center">
|
|
||||||
<div class="col-md-6 border-end"> </div>
|
|
||||||
<div class="col-md-6"><input type="checkbox" class="form-check-input" name="widthconstraint"> Constrain width</input></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 border-end">{{charter|linebreaks}}</div>
|
|
||||||
<div class="col-md-6 rightcontent">{{rendered|sanitize|safe}}</div>
|
|
||||||
</div>
|
|
||||||
{% endcomment %}
|
|
||||||
<div class="my-3 row">
|
|
||||||
<div class="col-md-6 border-end">
|
|
||||||
<div class="h2 text-center bg-info">Current about page rendering</div>
|
|
||||||
<div class="border-bottom text-center"> </div>
|
|
||||||
<div>{{ charter|linebreaks }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 rightpanel">
|
|
||||||
<div class="h2 text-center bg-info">Markdown rendering</div>
|
|
||||||
<div class="border-bottom text-center">
|
|
||||||
<label class="form-label" for="widthconstraint">Constrain width</label>
|
|
||||||
<input type="checkbox" class="form-check-input" name="widthconstraint" id="widthconstraint">
|
|
||||||
</div>
|
|
||||||
<div class="rightcontent">{{ rendered|sanitize|safe }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div>Group has no charter document</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block js %}
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('input[name=widthconstraint]').on("change", function() {
|
|
||||||
if ($(this).is(':checked')) {
|
|
||||||
$('.rightcontent').css('max-width','700px')
|
|
||||||
} else {
|
|
||||||
$('.rightcontent').css('max-width','')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('input[name=widthconstraint').prop('checked', false);
|
|
||||||
$('.rightcontent').css('max-width','')
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -24,7 +24,7 @@
|
||||||
rotation with the next reviewer in the rotation at the top. Rows with darker backgrounds have the following meaning:
|
rotation with the next reviewer in the rotation at the top. Rows with darker backgrounds have the following meaning:
|
||||||
</p>
|
</p>
|
||||||
<div class="reviewer-overview">
|
<div class="reviewer-overview">
|
||||||
<p class="alert alert-secondary my-3">
|
<p class="alert alert-info my-3">
|
||||||
Has already been assigned a document within the given interval.
|
Has already been assigned a document within the given interval.
|
||||||
</p>
|
</p>
|
||||||
<p class="alert alert-warning my-3">
|
<p class="alert alert-warning my-3">
|
||||||
|
@ -44,89 +44,102 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if reviewers %}
|
{% if reviewers %}
|
||||||
<table class="table reviewer-overview tablesorter">
|
<table class="table table-sm table-striped reviewer-overview tablesorter">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" data-sort="num">Next</th>
|
<th scope="col" data-sort="next">Next</th>
|
||||||
<th scope="col" data-sort="reviewer">Reviewer</th>
|
<th scope="col" data-sort="reviewer">Reviewer</th>
|
||||||
<th scope="col" data-sort="assigned">Recent history</th>
|
<th scope="col">
|
||||||
<th scope="col" data-sort="num">Days since completed</th>
|
<div class="row">
|
||||||
<th scope="col" data-sort="settings">Settings</th>
|
<div class="col-md-2 me-1">Assigned</div>
|
||||||
|
<div class="col-md-2 me-1 d-none d-lg-block">Deadline</div>
|
||||||
|
<div class="col-md-2 me-1">State</div>
|
||||||
|
<div class="col-md-1 me-1 d-none d-lg-block">Days for review</div>
|
||||||
|
<div class="col">Document</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="num">Days since review</th>
|
||||||
|
<th scope="col" class="d-none d-xl-table-cell w-25">Settings</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for person in reviewers %}
|
{% for person in reviewers %}
|
||||||
<tr {% if person.completely_unavailable %}class="table-danger" title="Is not available to do reviews at this time." {% elif person.busy %}class="table-secondary" title="Has already been assigned a document within the given interval." {% elif person.settings.skip_next %}class="table-warning" title="Will be skipped the next time at the top of rotation." {% endif %}>
|
<tr {% if person.completely_unavailable %}class="table-danger" title="Is not available to do reviews at this time." {% elif person.busy %}class="table-info" title="Has already been assigned a document within the given interval." {% elif person.settings.skip_next %}class="table-warning" title="Will be skipped the next time at the top of rotation." {% endif %}>
|
||||||
<td>{{ forloop.counter }}</td>
|
<td>{{ forloop.counter }}</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{% person_link person %}
|
{% person_link person with_email=False %}
|
||||||
{% if person.settings_url %}
|
|
||||||
<a href="{{ person.settings_url }}" class="btn btn-primary btn-small float-end"
|
<div class="text-nowrap">
|
||||||
title="{{ person.settings.expertise }}">Edit
|
{% if person.settings_url %}
|
||||||
</a>
|
<a href="{{ person.settings_url }}" class="btn btn-primary btn-sm"
|
||||||
{% endif %}
|
aria-label="Change settings for {{ person.name }}">
|
||||||
|
<i class="bi bi-gear"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary btn-sm d-xl-none"
|
||||||
|
aria-label="View settings for {{ person.name }}"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#modal{{ forloop.counter }}">
|
||||||
|
<i class="bi bi-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{% if person.latest_reqs %}
|
{% if person.latest_reqs %}
|
||||||
<table class="table table-sm table-borderless">
|
{% for assn_pk, req_pk, doc_name, reviewed_rev, assigned_time, deadline, state, assignment_to_closure_days in person.latest_reqs %}
|
||||||
<thead>
|
<div class="row {% if not forloop.last %}border-bottom mb-1 pb-1{% endif %}">
|
||||||
<tr>
|
<div class="col-md-2 me-1">
|
||||||
<th scope="col">Assigned</th>
|
{{ assigned_time|date|split:"-"|join:"-<wbr>" }}
|
||||||
<th scope="col">Deadline</th>
|
</div>
|
||||||
<th scope="col">State</th>
|
<div class="col-md-2 me-1 d-none d-lg-block">
|
||||||
<th scope="col">Review time</th>
|
<a href="{% url 'ietf.doc.views_review.review_request' name=doc_name request_id=req_pk %}">{{ deadline|date|split:"-"|join:"-<wbr>" }}</a>
|
||||||
<th scope="col">Document</th>
|
</div>
|
||||||
</tr>
|
<div class="col-md-2 me-1">
|
||||||
</thead>
|
<span class="badge rounded-pill bg-{% if state.slug == 'completed' or state.slug == 'part-completed' %}success{% elif state.slug == 'no-response' %}danger{% elif state.slug == 'overtaken' %}warning{% elif state.slug == 'requested' or state.slug == 'accepted' %}primary{% else %}secondary{% endif %} text-wrap">{{ state.name }}</span>
|
||||||
<tbody>
|
</div>
|
||||||
{% for assn_pk, req_pk, doc_name, reviewed_rev, assigned_time, deadline, state, assignment_to_closure_days in person.latest_reqs %}
|
<div class="col-md-1 me-1 text-end d-none d-lg-block">
|
||||||
<tr>
|
{% if assignment_to_closure_days != None %}
|
||||||
<td>
|
{{ assignment_to_closure_days }}
|
||||||
{{ assigned_time|date }}
|
{% endif %}
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<div class="col">
|
||||||
<a href="{% url 'ietf.doc.views_review.review_request' name=doc_name request_id=req_pk %}">{{ deadline|date }}</a>
|
{{ doc_name }}{% if reviewed_rev %}-{{ reviewed_rev }}{% endif %}
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
{{ state.name|badgeify }}
|
{% endfor %}
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if assignment_to_closure_days != None %}
|
|
||||||
{{ assignment_to_closure_days }} day{{ assignment_to_closure_days|pluralize }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="text-nowrap">
|
|
||||||
{{ doc_name }}
|
|
||||||
{% if reviewed_rev %}-{{ reviewed_rev }}{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
|
<td class="text-end">
|
||||||
{% if person.days_since_completed_review != 9999 %}
|
{% if person.days_since_completed_review != 9999 %}
|
||||||
{{ person.days_since_completed_review }}
|
{{ person.days_since_completed_review }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
|
||||||
{% if person.settings.min_interval %}
|
<td class="d-none d-xl-table-cell w-25">
|
||||||
<b>{{ person.settings.get_min_interval_display }}</b>
|
{% include "review/unavailable_table.html" with person=person unavailable_periods=person.unavailable_periods %}
|
||||||
<br>
|
<div class="modal modal-xl fade" id="modal{{ forloop.counter }}" tabindex="-1" aria-labelledby="modallabel{{ forloop.counter }}" aria-hidden="true">
|
||||||
{% endif %}
|
<div class="modal-dialog">
|
||||||
{% if person.settings.skip_next %}
|
<div class="modal-content">
|
||||||
<b>Skip:</b> {{ person.settings.skip_next }}
|
<div class="modal-header">
|
||||||
<br>
|
<h1 class="modal-title fs-5" id="modallabel{{ forloop.counter }}">Reviewer settings</h1>
|
||||||
{% endif %}
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
{% if person.settings.filter_re %}
|
</div>
|
||||||
<b>Filter:</b> <code title="{{ person.settings.filter_re }}">{{ person.settings.filter_re|truncatechars:15 }}</code>
|
<div class="modal-body">
|
||||||
<br>
|
{% include "review/unavailable_table.html" with person=person unavailable_periods=person.unavailable_periods %}
|
||||||
{% endif %}
|
</div>
|
||||||
{% if person.unavailable_periods %}
|
<div class="modal-footer">
|
||||||
{% include "review/unavailable_table.html" with unavailable_periods=person.unavailable_periods %}
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
{% endif %}
|
<button type="button" class="btn btn-primary">Save changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
40
ietf/templates/group/statements.html
Normal file
40
ietf/templates/group/statements.html
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{% extends "group/group_base.html" %}
|
||||||
|
{# Copyright The IETF Trust 2023, All Rights Reserved #}
|
||||||
|
{% load origin %}
|
||||||
|
{% load ietf_filters person_filters textfilters %}
|
||||||
|
{% load static %}
|
||||||
|
{% block pagehead %}
|
||||||
|
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block group_content %}
|
||||||
|
{% origin %}
|
||||||
|
<h2 class="my-3">{{group.acronym|upper}} Statements</h2>
|
||||||
|
{% if request.user|has_role:"Secretariat" %}
|
||||||
|
<div class="buttonlist">
|
||||||
|
<a id="start_button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
href="{% url 'ietf.doc.views_statement.new_statement' %}">
|
||||||
|
Start New Statement
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<table class="my-3 table table-sm table-striped tablesorter">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-1" scope="col" data-sort="date">Date</th>
|
||||||
|
<th scope="col" data-sort="statement">Statement</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for statement in statements %}
|
||||||
|
<tr>
|
||||||
|
<td title="{{ statement.published|date:'Y-m-d H:i:s O' }}">{{ statement.published|date:"Y-m-d" }}</td>
|
||||||
|
<td><a href="{% url 'ietf.doc.views_doc.document_main' name=statement.name %}">{{statement.title}}</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
<script src="{% static "ietf/js/list.js" %}"></script>
|
||||||
|
{% endblock %}
|
|
@ -186,10 +186,10 @@
|
||||||
<table class="table table-sm table-striped tablesorter">
|
<table class="table table-sm table-striped tablesorter">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">
|
<th scope="col" data-sort="setting">
|
||||||
Setting
|
Setting
|
||||||
</th>
|
</th>
|
||||||
<th scope="col">
|
<th scope="col" data-sort="value">
|
||||||
Value
|
Value
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -208,7 +208,7 @@
|
||||||
Skip next assignments
|
Skip next assignments
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
{{ t.reviewer_settings.skip_next }}
|
{{ t.reviewer_settings.skip_next|yesno }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -190,6 +190,42 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<!-- Editorial Sessions -->
|
||||||
|
{% if editorial %}
|
||||||
|
<h2 class="mt-4" id="editorial">Editorial Stream</h2>
|
||||||
|
<table class="table table-sm table-striped tablesorter">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" data-sort="group">
|
||||||
|
Group
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="agenda">
|
||||||
|
Agenda
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="minutes">
|
||||||
|
Minutes
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="slides">
|
||||||
|
Slides
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="drafts">
|
||||||
|
Internet-Drafts
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="updated">
|
||||||
|
Updated
|
||||||
|
</th>
|
||||||
|
{% if user|has_role:"Secretariat" or user_groups %}
|
||||||
|
<th scope="col"></th>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for entry in editorial %}
|
||||||
|
{% include "meeting/group_materials.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
{% if other %}
|
{% if other %}
|
||||||
<h2 class="mt-4" id="other">
|
<h2 class="mt-4" id="other">
|
||||||
Other <small>Miscellaneous other sessions</small>
|
Other <small>Miscellaneous other sessions</small>
|
||||||
|
|
|
@ -206,10 +206,40 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<!-- Editorial Sessions -->
|
||||||
|
{% if editorial %}
|
||||||
|
<h2 class="mt-5" id="editorial">Editorial Stream</h2>
|
||||||
|
<table class="table table-sm table-striped tablesorter">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" data-sort="group">
|
||||||
|
Group
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="artifacts">
|
||||||
|
Artifacts
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="recordings">
|
||||||
|
Recordings
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="slides">
|
||||||
|
Slides
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sort="drafts">
|
||||||
|
Internet-Drafts
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for entry in editorial %}
|
||||||
|
{% include "meeting/group_proceedings.html" with entry=entry meeting=meeting show_agenda=True only %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endcache %}
|
{% endcache %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script src="{% static "ietf/js/list.js" %}">
|
<script src="{% static "ietf/js/list.js" %}">
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -144,7 +144,7 @@
|
||||||
{% if not forloop.first %}</tbody>{% endif %}
|
{% if not forloop.first %}</tbody>{% endif %}
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="table-warning" colspan="8">{{ session.current_status_name|capfirst }}</th>
|
<th scope="col" class="table-warning" colspan="7">{{ session.current_status_name|capfirst }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
103
ietf/templates/nomcom/reclassify_feedback_item.html
Normal file
103
ietf/templates/nomcom/reclassify_feedback_item.html
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
{# Copyright The IETF Trust 2023, All Rights Reserved #}
|
||||||
|
{% load nomcom_tags textfilters %}
|
||||||
|
<h2 class="mt-3">Reclassify feedback item</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Code</th>
|
||||||
|
<th scope="col">Explanation</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">U</th>
|
||||||
|
<td>Unclassified</td>
|
||||||
|
</tr>
|
||||||
|
{% for ft in feedback_types %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{ ft.legend }}</th>
|
||||||
|
<td>{{ ft.name }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="table table-sm table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Date</th>
|
||||||
|
<th scope="col" class="text-center" title="Unclassified">U</th>
|
||||||
|
{% for ft in feedback_types %}
|
||||||
|
<th scope="col" class="text-center" title="{{ ft.name }}">{{ ft.legend }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
<th scope="col">Author</th>
|
||||||
|
<th scope="col">Subject</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<!-- [html-validate-disable-block input-missing-label -- labelled via aria-label] -->
|
||||||
|
<td>{{ reclassify_feedback.time|date:"r" }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<input type="radio"
|
||||||
|
class="form-check-input"
|
||||||
|
name="type"
|
||||||
|
value="unclassified"
|
||||||
|
id="unclassified"
|
||||||
|
aria-label="Unclassified"
|
||||||
|
title="Unclassified">
|
||||||
|
</td>
|
||||||
|
{% for ft in feedback_types %}
|
||||||
|
<td class="text-center">
|
||||||
|
<input type="radio"
|
||||||
|
class="form-check-input"
|
||||||
|
name="type"
|
||||||
|
value="{{ ft.slug }}"
|
||||||
|
id="{{ ft.name|slugify }}"
|
||||||
|
aria-label="{{ ft.name }}"
|
||||||
|
{% if reclassify_feedback.type == t %}checked{% endif %}
|
||||||
|
title="{{ ft.name }}">
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
<td>{{ reclassify_feedback.author }}</td>
|
||||||
|
<td>{{ reclassify_feedback.subject }}</td>
|
||||||
|
<td>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#modal{{ reclassify_feedback.id }}">
|
||||||
|
View
|
||||||
|
</button>
|
||||||
|
<div class="modal fade"
|
||||||
|
id="modal{{ reclassify_feedback.id }}"
|
||||||
|
tabindex="-1"
|
||||||
|
role="dialog"
|
||||||
|
aria-labelledby="label{{ reclassify_feedback.id }}"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<p class="h5 modal-title" id="label{{ reclassify_feedback.id }}">{{ reclassify_feedback.subject }}</p>
|
||||||
|
<button type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<pre>{% decrypt reclassify_feedback.comments request year 1 %}</pre>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<input type="hidden" name="feedback_id" value="{{ reclassify_feedback.id }}">
|
||||||
|
<button class="btn btn-primary" type="submit">Classify</button>
|
||||||
|
</form>
|
|
@ -1,10 +1,13 @@
|
||||||
{% extends "nomcom/nomcom_private_base.html" %}
|
{% extends "nomcom/nomcom_private_base.html" %}
|
||||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
{# Copyright The IETF Trust 2015-2023, All Rights Reserved #}
|
||||||
{% load origin %}
|
{% load origin %}
|
||||||
{% load nomcom_tags textfilters %}
|
{% load nomcom_tags textfilters %}
|
||||||
{% block subtitle %}- View feedback about {{ nominee.email.person.name }}{% endblock %}
|
{% block subtitle %}- View feedback about {{ nominee.email.person.name }}{% endblock %}
|
||||||
{% block nomcom_content %}
|
{% block nomcom_content %}
|
||||||
{% origin %}
|
{% origin %}
|
||||||
|
{% if reclassify_feedback %}
|
||||||
|
{% include "nomcom/reclassify_feedback_item.html" %}
|
||||||
|
{% else %}
|
||||||
<h2>Feedback about {{ nominee }}</h2>
|
<h2>Feedback about {{ nominee }}</h2>
|
||||||
<ul class="nav nav-tabs my-3" role="tablist">
|
<ul class="nav nav-tabs my-3" role="tablist">
|
||||||
{% for ft in feedback_types %}
|
{% for ft in feedback_types %}
|
||||||
|
@ -81,6 +84,19 @@
|
||||||
<dd class="col-sm-10 pasted">
|
<dd class="col-sm-10 pasted">
|
||||||
<pre>{% decrypt feedback.comments request year 1 %}</pre>
|
<pre>{% decrypt feedback.comments request year 1 %}</pre>
|
||||||
</dd>
|
</dd>
|
||||||
|
{% if user|is_chair_or_advisor:year %}
|
||||||
|
<dt class="col-sm-2">
|
||||||
|
<form id="reclassify-{{ feedback.id }}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="feedback_id" value="{{ feedback.id }}">
|
||||||
|
<button class="btn btn-warning btn-sm" type="submit">
|
||||||
|
Reclassify
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
</dd>
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
{% if not forloop.last %}<hr>{% endif %}
|
{% if not forloop.last %}<hr>{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -90,6 +106,7 @@
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-secondary"
|
<a class="btn btn-secondary"
|
||||||
href="{% url 'ietf.nomcom.views.view_feedback' year %}">Back</a>
|
href="{% url 'ietf.nomcom.views.view_feedback' year %}">Back</a>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "nomcom/nomcom_private_base.html" %}
|
{% extends "nomcom/nomcom_private_base.html" %}
|
||||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
{# Copyright The IETF Trust 2015-2023, All Rights Reserved #}
|
||||||
{% load origin %}
|
{% load origin %}
|
||||||
{% load django_bootstrap5 %}
|
{% load django_bootstrap5 %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
@ -73,13 +73,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
{% for t in types %}
|
||||||
<th scope="row">U</th>
|
|
||||||
<td>Unclassified</td>
|
|
||||||
</tr>
|
|
||||||
{% for legend, t in type_dict.items %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ legend }}</th>
|
<th scope="row">{{ t.legend }}</th>
|
||||||
<td>{{ t.name }}</td>
|
<td>{{ t.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -89,8 +85,7 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Date</th>
|
<th scope="col">Date</th>
|
||||||
<th scope="col" class="text-center" title="Unclassified">U</th>
|
{% for t in types %}<th scope="col" class="text-center" title="{{ t.name }}">{{ t.legend }}</th>{% endfor %}
|
||||||
{% for legend, t in type_dict.items %}<th scope="col" class="text-center" title="{{ t.name }}">{{ legend }}</th>{% endfor %}
|
|
||||||
<th scope="col">Author</th>
|
<th scope="col">Author</th>
|
||||||
<th scope="col">Subject</th>
|
<th scope="col">Subject</th>
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
|
@ -118,7 +113,7 @@
|
||||||
title="{{ choice.1 }}">
|
title="{{ choice.1 }}">
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<td>{% person_link form.instance.author %}</td>
|
<td>{{ form.instance.author }}</td>
|
||||||
<td>{{ form.instance.subject }}</td>
|
<td>{{ form.instance.subject }}</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
{% extends "nomcom/nomcom_private_base.html" %}
|
{% extends "nomcom/nomcom_private_base.html" %}
|
||||||
{# Copyright The IETF Trust 2017, All Rights Reserved #}
|
{# Copyright The IETF Trust 2017-2023, All Rights Reserved #}
|
||||||
{% load origin %}
|
{% load origin %}
|
||||||
{% load nomcom_tags textfilters %}
|
{% load nomcom_tags textfilters %}
|
||||||
{% block subtitle %}- View feedback about {{ topic.subject }}{% endblock %}
|
{% block subtitle %}- View feedback about {{ topic.subject }}{% endblock %}
|
||||||
{% block nomcom_content %}
|
{% block nomcom_content %}
|
||||||
{% origin %}
|
{% origin %}
|
||||||
|
{% if reclassify_feedback %}
|
||||||
|
{% include "nomcom/reclassify_feedback_item.html" %}
|
||||||
|
{% else %}
|
||||||
<h2>Feedback about {{ topic.subject }}</h2>
|
<h2>Feedback about {{ topic.subject }}</h2>
|
||||||
<ul class="nav nav-tabs my-3" role="tablist">
|
<ul class="nav nav-tabs my-3" role="tablist">
|
||||||
{% for ft in feedback_types %}
|
{% for ft in feedback_types %}
|
||||||
|
@ -44,6 +47,19 @@
|
||||||
<dd class="col-sm-10 pasted">
|
<dd class="col-sm-10 pasted">
|
||||||
<pre>{% decrypt feedback.comments request year 1 %}</pre>
|
<pre>{% decrypt feedback.comments request year 1 %}</pre>
|
||||||
</dd>
|
</dd>
|
||||||
|
{% if user|is_chair_or_advisor:year %}
|
||||||
|
<dt class="col-sm-2">
|
||||||
|
<form id="reclassify-{{ feedback.id }}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="feedback_id" value="{{ feedback.id }}">
|
||||||
|
<button class="btn btn-warning btn-sm" type="submit">
|
||||||
|
Reclassify
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
</dd>
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
{% if not forloop.last %}<hr>{% endif %}
|
{% if not forloop.last %}<hr>{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -53,6 +69,7 @@
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-secondary"
|
<a class="btn btn-secondary"
|
||||||
href="{% url 'ietf.nomcom.views.view_feedback' year %}">Back</a>
|
href="{% url 'ietf.nomcom.views.view_feedback' year %}">Back</a>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
{% extends "nomcom/nomcom_private_base.html" %}
|
{% extends "nomcom/nomcom_private_base.html" %}
|
||||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
{# Copyright The IETF Trust 2015-2023, All Rights Reserved #}
|
||||||
{% load origin %}
|
{% load origin %}
|
||||||
{% load nomcom_tags textfilters %}
|
{% load nomcom_tags textfilters %}
|
||||||
{% block subtitle %}- View unrelated feedback{% endblock %}
|
{% block subtitle %}- View unrelated feedback{% endblock %}
|
||||||
{% block nomcom_content %}
|
{% block nomcom_content %}
|
||||||
{% origin %}
|
{% origin %}
|
||||||
|
{% if reclassify_feedback %}
|
||||||
|
{% include "nomcom/reclassify_feedback_item.html" %}
|
||||||
|
{% else %}
|
||||||
<h2>Feedback not related to nominees</h2>
|
<h2>Feedback not related to nominees</h2>
|
||||||
<ul role="tablist" class="nav nav-tabs my-3">
|
<ul role="tablist" class="nav nav-tabs my-3">
|
||||||
{% for ft in feedback_types %}
|
{% for ft in feedback_types %}
|
||||||
|
@ -45,6 +48,19 @@
|
||||||
<dd class="col-sm-10 pasted">
|
<dd class="col-sm-10 pasted">
|
||||||
<pre>{% decrypt feedback.comments request year 1 %}</pre>
|
<pre>{% decrypt feedback.comments request year 1 %}</pre>
|
||||||
</dd>
|
</dd>
|
||||||
|
{% if user|is_chair_or_advisor:year %}
|
||||||
|
<dt class="col-sm-2">
|
||||||
|
<form id="reclassify-{{ feedback.id }}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="feedback_id" value="{{ feedback.id }}">
|
||||||
|
<button class="btn btn-warning btn-sm" type="submit">
|
||||||
|
Reclassify
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
</dd>
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,6 +70,7 @@
|
||||||
<a class="btn btn-secondary"
|
<a class="btn btn-secondary"
|
||||||
href="{% url 'ietf.nomcom.views.view_feedback' year %}">Back</a>
|
href="{% url 'ietf.nomcom.views.view_feedback' year %}">Back</a>
|
||||||
</p>
|
</p>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,25 +1,54 @@
|
||||||
<table class="mt-2 mb-0 table table-sm table-borderless">
|
{% load origin %}
|
||||||
|
{% origin %}
|
||||||
|
<table class="m-0 p-0 table table-sm table-borderless">
|
||||||
|
<thead class="d-nones">
|
||||||
|
<tr>
|
||||||
|
<th scope="row" class="p-0 col-3"></th>
|
||||||
|
<th scope="row" class="p-0 col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
{% if person %}
|
||||||
|
{% if person.settings.min_interval %}
|
||||||
|
<tr>
|
||||||
|
<th class="bg-transparent p-0 pe-1" scope="row">Can review:</th>
|
||||||
|
<td class="bg-transparent p-0">{{ person.settings.get_min_interval_display }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if person.settings.skip_next %}
|
||||||
|
<tr>
|
||||||
|
<th class="bg-transparent p-0 pe-1" scope="row">Skip next:</th>
|
||||||
|
<td class="bg-transparent p-0">{{ person.settings.skip_next|yesno }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if person.settings.filter_re %}
|
||||||
|
<tr>
|
||||||
|
<th class="bg-transparent p-0 pe-1" scope="row">Filter:</th>
|
||||||
|
<td class="bg-transparent p-0"><code title="{{ person.settings.filter_re }}">{{ person.settings.filter_re }}</code></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% for p in unavailable_periods %}
|
{% for p in unavailable_periods %}
|
||||||
<tr class="unavailable-period-{{ p.state }}">
|
<tr class="unavailable-period-{{ p.state }}">
|
||||||
<th scope="row">Period:</th>
|
<th class="bg-transparent p-0 pe-1" scope="row">Period:</th>
|
||||||
<td>{{ p.state }}</td>
|
<td class="bg-transparent p-0">{{ p.state }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="unavailable-period-{{ p.state }}">
|
<tr class="unavailable-period-{{ p.state }}">
|
||||||
<th scope="row">Dates:</th>
|
<th class="bg-transparent p-0 pe-1" scope="row">Dates:</th>
|
||||||
<td>
|
<td class="bg-transparent p-0">
|
||||||
{% if p.start_date or p.end_date %}{{ p.start_date|default:"∞" }} -{% endif %}
|
{% if p.start_date or p.end_date %}{{ p.start_date|default:"∞" }}-{% endif %}{{ p.end_date|default:"∞" }}
|
||||||
{{ p.end_date|default:"∞" }}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="unavailable-period-{{ p.state }}">
|
<tr class="unavailable-period-{{ p.state }}">
|
||||||
<th scope="row">Availability:</th>
|
<th class="bg-transparent p-0 pe-1" scope="row">Available:</th>
|
||||||
<td>{{ p.get_availability_display }}</td>
|
<td class="bg-transparent p-0">{{ p.get_availability_display }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if p.reason %}
|
||||||
<tr class="unavailable-period-{{ p.state }}">
|
<tr class="unavailable-period-{{ p.state }}">
|
||||||
<th scope="row">Reason:</th>
|
<th class="bg-transparent p-0 pe-1" scope="row">Reason:</th>
|
||||||
<td>{{ p.reason }}</td>
|
<td class="bg-transparent p-0">{{ p.reason }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
|
@ -2,6 +2,8 @@
|
||||||
Hi,
|
Hi,
|
||||||
|
|
||||||
Manual posting has been requested for the following Internet-Draft.
|
Manual posting has been requested for the following Internet-Draft.
|
||||||
|
The IETF Secretariat will handle this request; no further action is
|
||||||
|
required at this time.
|
||||||
|
|
||||||
{% if errors %}The problems found during automated submission were: {% for err in errors.values %}
|
{% if errors %}The problems found during automated submission were: {% for err in errors.values %}
|
||||||
- {{ err }}{% endfor %}
|
- {{ err }}{% endfor %}
|
||||||
|
|
|
@ -152,6 +152,7 @@
|
||||||
"ietf/static/js/upcoming.js",
|
"ietf/static/js/upcoming.js",
|
||||||
"ietf/static/js/upload-material.js",
|
"ietf/static/js/upload-material.js",
|
||||||
"ietf/static/js/upload_bofreq.js",
|
"ietf/static/js/upload_bofreq.js",
|
||||||
|
"ietf/static/js/upload_statement.js",
|
||||||
"ietf/static/js/zxcvbn.js"
|
"ietf/static/js/zxcvbn.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
1513
playwright/package-lock.json
generated
1513
playwright/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -7,14 +7,14 @@
|
||||||
"test:debug": "playwright test --debug"
|
"test:debug": "playwright test --debug"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.35.1",
|
"@playwright/test": "1.36.1",
|
||||||
"eslint": "8.43.0",
|
"eslint": "8.45.0",
|
||||||
"eslint-config-standard": "17.1.0",
|
"eslint-config-standard": "17.1.0",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
"eslint-plugin-n": "16.0.1",
|
"eslint-plugin-n": "16.0.1",
|
||||||
"eslint-plugin-node": "11.1.0",
|
"eslint-plugin-node": "11.1.0",
|
||||||
"eslint-plugin-promise": "6.1.1",
|
"eslint-plugin-promise": "6.1.1",
|
||||||
"npm-check-updates": "16.10.13"
|
"npm-check-updates": "16.10.16"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faker-js/faker": "8.0.2",
|
"@faker-js/faker": "8.0.2",
|
||||||
|
|
Loading…
Reference in a new issue