Merge remote-tracking branch 'upstream/main' into feat/postgres

This commit is contained in:
Robert Sparks 2022-12-12 09:54:49 -06:00
commit 15569771ff
No known key found for this signature in database
GPG key ID: 6E2A6A5775F91318
117 changed files with 2610 additions and 1269 deletions

View file

@ -1,4 +1,5 @@
name: Build and Release
run-name: ${{ github.event.inputs.publish == 'true' && '[Prod]' || '[Dev]' }} Build ${{ github.run_number }} of branch ${{ github.ref_name }} by @${{ github.actor }}
on:
push:
@ -453,7 +454,7 @@ jobs:
npm ci
cd ../..
echo "Start Deploy..."
node ./dev/deploy-to-container/cli.js --branch ${{ github.ref_name }} --domain dev.ietf.org
node ./dev/deploy-to-container/cli.js --branch ${{ github.ref_name }} --domain dev.ietf.org --appversion ${{ env.PKG_VERSION }} --commit ${{ github.sha }} --ghrunid ${{ github.run_id }}
- name: Cleanup old docker resources
env:

View file

@ -0,0 +1,61 @@
# GITHUB ACTIONS - WORKFLOW
# RSync the assets in the shared assets volume
name: Nightly Dev Shared Assets Sync
# Controls when the workflow will run
on:
# Run every night
schedule:
- cron: '0 1 * * *'
# Run on app-rsync-extras.sh changes
push:
branches:
- main
paths:
- 'docker/scripts/app-rsync-extras.sh'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
name: Build Docker Image
runs-on: ubuntu-latest
if: ${{ github.event_name != 'schedule' }}
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Build & Push
uses: docker/build-push-action@v3
with:
context: .
file: dev/shared-assets-sync/Dockerfile
push: true
tags: ghcr.io/ietf-tools/datatracker-rsync-assets:latest
sync:
name: Run assets rsync
if: ${{ always() }}
runs-on: dev-server
needs: [build]
steps:
- name: Run rsync
env:
DEBIAN_FRONTEND: noninteractive
run: |
docker pull ghcr.io/ietf-tools/datatracker-rsync-assets:latest
docker run --rm -v dt-assets:/assets ghcr.io/ietf-tools/datatracker-rsync-assets:latest
docker image prune -a -f

177
.pnp.cjs generated
View file

@ -42,6 +42,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@fullcalendar/luxon2", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\
["@fullcalendar/timegrid", "npm:5.11.3"],\
["@fullcalendar/vue3", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\
["@parcel/optimizer-data-url", "npm:2.8.0"],\
["@parcel/transformer-inline-string", "npm:2.8.0"],\
["@parcel/transformer-sass", "npm:2.8.0"],\
["@popperjs/core", "npm:2.11.6"],\
["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.0.2"],\
@ -52,7 +54,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["browser-fs-access", "npm:0.31.1"],\
["browserlist", "npm:1.0.1"],\
["c8", "npm:7.12.0"],\
["caniuse-lite", "npm:1.0.30001434"],\
["caniuse-lite", "npm:1.0.30001435"],\
["d3", "npm:7.6.1"],\
["eslint", "npm:8.28.0"],\
["eslint-config-standard", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:17.0.0"],\
@ -63,7 +65,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["eslint-plugin-promise", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:6.1.1"],\
["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.8.0"],\
["file-saver", "npm:2.0.5"],\
["highcharts", "npm:10.3.1"],\
["highcharts", "npm:10.3.2"],\
["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:7.10.0"],\
["jquery", "npm:3.6.1"],\
["jquery-migrate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.4.0"],\
@ -72,14 +74,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["list.js", "npm:2.3.1"],\
["lodash", "npm:4.17.21"],\
["lodash-es", "npm:4.17.21"],\
["luxon", "npm:3.1.0"],\
["luxon", "npm:3.1.1"],\
["moment", "npm:2.29.4"],\
["moment-timezone", "npm:0.5.39"],\
["ms", "npm:2.1.3"],\
["murmurhash-js", "npm:1.0.0"],\
["naive-ui", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.34.2"],\
["parcel", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.8.0"],\
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26"],\
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27"],\
["pinia-plugin-persist", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:1.0.0"],\
["pug", "npm:3.0.2"],\
["sass", "npm:1.56.1"],\
@ -337,7 +339,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@fullcalendar/luxon2", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\
["@fullcalendar/common", "npm:5.11.3"],\
["@types/luxon", null],\
["luxon", "npm:3.1.0"],\
["luxon", "npm:3.1.1"],\
["tslib", "npm:2.4.0"]\
],\
"packagePeers": [\
@ -1130,12 +1132,25 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@parcel/source-map", "npm:2.1.1"],\
["@parcel/utils", "npm:2.8.0"],\
["browserslist", "npm:4.20.3"],\
["lightningcss", "npm:1.16.1"],\
["lightningcss", "npm:1.17.1"],\
["nullthrows", "npm:1.1.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["@parcel/optimizer-data-url", [\
["npm:2.8.0", {\
"packageLocation": "./.yarn/cache/@parcel-optimizer-data-url-npm-2.8.0-89a39d906e-998fb94cee.zip/node_modules/@parcel/optimizer-data-url/",\
"packageDependencies": [\
["@parcel/optimizer-data-url", "npm:2.8.0"],\
["@parcel/plugin", "npm:2.8.0"],\
["@parcel/utils", "npm:2.8.0"],\
["isbinaryfile", "npm:4.0.10"],\
["mime", "npm:2.6.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["@parcel/optimizer-htmlnano", [\
["npm:2.8.0", {\
"packageLocation": "./.yarn/cache/@parcel-optimizer-htmlnano-npm-2.8.0-d2ead43d0c-68010e586b.zip/node_modules/@parcel/optimizer-htmlnano/",\
@ -1480,7 +1495,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@parcel/source-map", "npm:2.1.1"],\
["@parcel/utils", "npm:2.8.0"],\
["browserslist", "npm:4.20.3"],\
["lightningcss", "npm:1.16.1"],\
["lightningcss", "npm:1.17.1"],\
["nullthrows", "npm:1.1.1"]\
],\
"linkType": "HARD"\
@ -1529,6 +1544,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD"\
}]\
]],\
["@parcel/transformer-inline-string", [\
["npm:2.8.0", {\
"packageLocation": "./.yarn/cache/@parcel-transformer-inline-string-npm-2.8.0-5fce2c90b8-e40616c55b.zip/node_modules/@parcel/transformer-inline-string/",\
"packageDependencies": [\
["@parcel/transformer-inline-string", "npm:2.8.0"],\
["@parcel/plugin", "npm:2.8.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["@parcel/transformer-js", [\
["npm:2.8.0", {\
"packageLocation": "./.yarn/unplugged/@parcel-transformer-js-virtual-0a5c0b53bd/node_modules/@parcel/transformer-js/",\
@ -2690,10 +2715,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "HARD"\
}],\
["npm:1.0.30001434", {\
"packageLocation": "./.yarn/cache/caniuse-lite-npm-1.0.30001434-9c6ea57daf-7c9d2641e8.zip/node_modules/caniuse-lite/",\
["npm:1.0.30001435", {\
"packageLocation": "./.yarn/cache/caniuse-lite-npm-1.0.30001435-7cebb35f0a-ec88b9c37f.zip/node_modules/caniuse-lite/",\
"packageDependencies": [\
["caniuse-lite", "npm:1.0.30001434"]\
["caniuse-lite", "npm:1.0.30001435"]\
],\
"linkType": "HARD"\
}]\
@ -4910,10 +4935,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["highcharts", [\
["npm:10.3.1", {\
"packageLocation": "./.yarn/cache/highcharts-npm-10.3.1-e67a887ff6-8a1cf9a363.zip/node_modules/highcharts/",\
["npm:10.3.2", {\
"packageLocation": "./.yarn/cache/highcharts-npm-10.3.2-1672942f09-43cb42b24c.zip/node_modules/highcharts/",\
"packageDependencies": [\
["highcharts", "npm:10.3.1"]\
["highcharts", "npm:10.3.2"]\
],\
"linkType": "HARD"\
}]\
@ -5456,6 +5481,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD"\
}]\
]],\
["isbinaryfile", [\
["npm:4.0.10", {\
"packageLocation": "./.yarn/cache/isbinaryfile-npm-4.0.10-91d1251522-a6b28db7e2.zip/node_modules/isbinaryfile/",\
"packageDependencies": [\
["isbinaryfile", "npm:4.0.10"]\
],\
"linkType": "HARD"\
}]\
]],\
["isexe", [\
["npm:2.0.0", {\
"packageLocation": "./.yarn/cache/isexe-npm-2.0.0-b58870bd2e-26bf6c5480.zip/node_modules/isexe/",\
@ -5716,91 +5750,91 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["lightningcss", [\
["npm:1.16.1", {\
"packageLocation": "./.yarn/cache/lightningcss-npm-1.16.1-dc51de6ab1-78ec1fa158.zip/node_modules/lightningcss/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/cache/lightningcss-npm-1.17.1-7428f2d516-0bf9d5c932.zip/node_modules/lightningcss/",\
"packageDependencies": [\
["lightningcss", "npm:1.16.1"],\
["lightningcss", "npm:1.17.1"],\
["detect-libc", "npm:1.0.3"],\
["lightningcss-darwin-arm64", "npm:1.16.1"],\
["lightningcss-darwin-x64", "npm:1.16.1"],\
["lightningcss-linux-arm-gnueabihf", "npm:1.16.1"],\
["lightningcss-linux-arm64-gnu", "npm:1.16.1"],\
["lightningcss-linux-arm64-musl", "npm:1.16.1"],\
["lightningcss-linux-x64-gnu", "npm:1.16.1"],\
["lightningcss-linux-x64-musl", "npm:1.16.1"],\
["lightningcss-win32-x64-msvc", "npm:1.16.1"]\
["lightningcss-darwin-arm64", "npm:1.17.1"],\
["lightningcss-darwin-x64", "npm:1.17.1"],\
["lightningcss-linux-arm-gnueabihf", "npm:1.17.1"],\
["lightningcss-linux-arm64-gnu", "npm:1.17.1"],\
["lightningcss-linux-arm64-musl", "npm:1.17.1"],\
["lightningcss-linux-x64-gnu", "npm:1.17.1"],\
["lightningcss-linux-x64-musl", "npm:1.17.1"],\
["lightningcss-win32-x64-msvc", "npm:1.17.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["lightningcss-darwin-arm64", [\
["npm:1.16.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-darwin-arm64-npm-1.16.1-0a412810bd/node_modules/lightningcss-darwin-arm64/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-darwin-arm64-npm-1.17.1-a84f0d052c/node_modules/lightningcss-darwin-arm64/",\
"packageDependencies": [\
["lightningcss-darwin-arm64", "npm:1.16.1"]\
["lightningcss-darwin-arm64", "npm:1.17.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["lightningcss-darwin-x64", [\
["npm:1.16.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-darwin-x64-npm-1.16.1-3f7b1b3519/node_modules/lightningcss-darwin-x64/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-darwin-x64-npm-1.17.1-131957b733/node_modules/lightningcss-darwin-x64/",\
"packageDependencies": [\
["lightningcss-darwin-x64", "npm:1.16.1"]\
["lightningcss-darwin-x64", "npm:1.17.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["lightningcss-linux-arm-gnueabihf", [\
["npm:1.16.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-arm-gnueabihf-npm-1.16.1-628363ec64/node_modules/lightningcss-linux-arm-gnueabihf/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-arm-gnueabihf-npm-1.17.1-bbf7f4f213/node_modules/lightningcss-linux-arm-gnueabihf/",\
"packageDependencies": [\
["lightningcss-linux-arm-gnueabihf", "npm:1.16.1"]\
["lightningcss-linux-arm-gnueabihf", "npm:1.17.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["lightningcss-linux-arm64-gnu", [\
["npm:1.16.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-arm64-gnu-npm-1.16.1-3ca4dc231b/node_modules/lightningcss-linux-arm64-gnu/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-arm64-gnu-npm-1.17.1-5b0e0aecb4/node_modules/lightningcss-linux-arm64-gnu/",\
"packageDependencies": [\
["lightningcss-linux-arm64-gnu", "npm:1.16.1"]\
["lightningcss-linux-arm64-gnu", "npm:1.17.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["lightningcss-linux-arm64-musl", [\
["npm:1.16.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-arm64-musl-npm-1.16.1-94c93845ed/node_modules/lightningcss-linux-arm64-musl/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-arm64-musl-npm-1.17.1-4da73a58bf/node_modules/lightningcss-linux-arm64-musl/",\
"packageDependencies": [\
["lightningcss-linux-arm64-musl", "npm:1.16.1"]\
["lightningcss-linux-arm64-musl", "npm:1.17.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["lightningcss-linux-x64-gnu", [\
["npm:1.16.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-x64-gnu-npm-1.16.1-04529113fe/node_modules/lightningcss-linux-x64-gnu/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-x64-gnu-npm-1.17.1-39d6988913/node_modules/lightningcss-linux-x64-gnu/",\
"packageDependencies": [\
["lightningcss-linux-x64-gnu", "npm:1.16.1"]\
["lightningcss-linux-x64-gnu", "npm:1.17.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["lightningcss-linux-x64-musl", [\
["npm:1.16.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-x64-musl-npm-1.16.1-01c07ec3c0/node_modules/lightningcss-linux-x64-musl/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-linux-x64-musl-npm-1.17.1-84311b8bf8/node_modules/lightningcss-linux-x64-musl/",\
"packageDependencies": [\
["lightningcss-linux-x64-musl", "npm:1.16.1"]\
["lightningcss-linux-x64-musl", "npm:1.17.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["lightningcss-win32-x64-msvc", [\
["npm:1.16.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-win32-x64-msvc-npm-1.16.1-40af4d14b2/node_modules/lightningcss-win32-x64-msvc/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/unplugged/lightningcss-win32-x64-msvc-npm-1.17.1-849d8d151b/node_modules/lightningcss-win32-x64-msvc/",\
"packageDependencies": [\
["lightningcss-win32-x64-msvc", "npm:1.16.1"]\
["lightningcss-win32-x64-msvc", "npm:1.17.1"]\
],\
"linkType": "HARD"\
}]\
@ -5918,10 +5952,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["luxon", [\
["npm:3.1.0", {\
"packageLocation": "./.yarn/cache/luxon-npm-3.1.0-16e2508500-f8a850b759.zip/node_modules/luxon/",\
["npm:3.1.1", {\
"packageLocation": "./.yarn/cache/luxon-npm-3.1.1-64fe977c1d-388fb35d3c.zip/node_modules/luxon/",\
"packageDependencies": [\
["luxon", "npm:3.1.0"]\
["luxon", "npm:3.1.1"]\
],\
"linkType": "HARD"\
}]\
@ -5987,6 +6021,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["mime", "npm:1.6.0"]\
],\
"linkType": "HARD"\
}],\
["npm:2.6.0", {\
"packageLocation": "./.yarn/cache/mime-npm-2.6.0-88b89d8de0-1497ba7b9f.zip/node_modules/mime/",\
"packageDependencies": [\
["mime", "npm:2.6.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["minimatch", [\
@ -6659,17 +6700,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["pinia", [\
["npm:2.0.26", {\
"packageLocation": "./.yarn/cache/pinia-npm-2.0.26-0d96417fac-0d38cc0efc.zip/node_modules/pinia/",\
["npm:2.0.27", {\
"packageLocation": "./.yarn/cache/pinia-npm-2.0.27-3e0154e702-29c862ea43.zip/node_modules/pinia/",\
"packageDependencies": [\
["pinia", "npm:2.0.26"]\
["pinia", "npm:2.0.27"]\
],\
"linkType": "SOFT"\
}],\
["virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26", {\
"packageLocation": "./.yarn/__virtual__/pinia-virtual-3c74e5a139/0/cache/pinia-npm-2.0.26-0d96417fac-0d38cc0efc.zip/node_modules/pinia/",\
["virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27", {\
"packageLocation": "./.yarn/__virtual__/pinia-virtual-0b7bfddb52/0/cache/pinia-npm-2.0.27-3e0154e702-29c862ea43.zip/node_modules/pinia/",\
"packageDependencies": [\
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26"],\
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27"],\
["@types/typescript", null],\
["@types/vue", null],\
["@types/vue__composition-api", null],\
@ -6677,7 +6718,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@vue/devtools-api", "npm:6.4.5"],\
["typescript", null],\
["vue", "npm:3.2.45"],\
["vue-demi", "virtual:3c74e5a1392a9d26efc27d5867a5220b1ab24b8bfb7c76fe2dac826f7e9d478b9c8eb69cc87331bb2ba20521466999017a02e9dca572946a787e2b4314602fca#npm:0.13.1"]\
["vue-demi", "virtual:0b7bfddb52b3cb488814806546397e52c62caef1815758033c8eac7ce386779ac52132e251ad567a19dde858cd2ed318ab2b52e9e258efd261b951e0d2160c16#npm:0.13.1"]\
],\
"packagePeers": [\
"@types/typescript",\
@ -6706,7 +6747,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@types/vue", null],\
["@types/vue__composition-api", null],\
["@vue/composition-api", null],\
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26"],\
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27"],\
["vue", "npm:3.2.45"],\
["vue-demi", "virtual:f56fcf19bbebc2ada1b28955da8cc216b1e9a569a1a7337d2d1926c1ebd1bc7a5bd91aedae1d05c15c8562f33caf7c59bd3020a667340f6bdc6a7b13fc2ba847#npm:0.12.5"]\
],\
@ -7232,6 +7273,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@fullcalendar/luxon2", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\
["@fullcalendar/timegrid", "npm:5.11.3"],\
["@fullcalendar/vue3", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.11.3"],\
["@parcel/optimizer-data-url", "npm:2.8.0"],\
["@parcel/transformer-inline-string", "npm:2.8.0"],\
["@parcel/transformer-sass", "npm:2.8.0"],\
["@popperjs/core", "npm:2.11.6"],\
["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.0.2"],\
@ -7242,7 +7285,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["browser-fs-access", "npm:0.31.1"],\
["browserlist", "npm:1.0.1"],\
["c8", "npm:7.12.0"],\
["caniuse-lite", "npm:1.0.30001434"],\
["caniuse-lite", "npm:1.0.30001435"],\
["d3", "npm:7.6.1"],\
["eslint", "npm:8.28.0"],\
["eslint-config-standard", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:17.0.0"],\
@ -7253,7 +7296,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["eslint-plugin-promise", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:6.1.1"],\
["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.8.0"],\
["file-saver", "npm:2.0.5"],\
["highcharts", "npm:10.3.1"],\
["highcharts", "npm:10.3.2"],\
["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:7.10.0"],\
["jquery", "npm:3.6.1"],\
["jquery-migrate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.4.0"],\
@ -7262,14 +7305,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["list.js", "npm:2.3.1"],\
["lodash", "npm:4.17.21"],\
["lodash-es", "npm:4.17.21"],\
["luxon", "npm:3.1.0"],\
["luxon", "npm:3.1.1"],\
["moment", "npm:2.29.4"],\
["moment-timezone", "npm:0.5.39"],\
["ms", "npm:2.1.3"],\
["murmurhash-js", "npm:1.0.0"],\
["naive-ui", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.34.2"],\
["parcel", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.8.0"],\
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.26"],\
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.0.27"],\
["pinia-plugin-persist", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:1.0.0"],\
["pug", "npm:3.0.2"],\
["sass", "npm:1.56.1"],\
@ -8159,16 +8202,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "SOFT"\
}],\
["npm:0.13.1", {\
"packageLocation": "./.yarn/unplugged/vue-demi-virtual-cfef5ecd67/node_modules/vue-demi/",\
"packageLocation": "./.yarn/unplugged/vue-demi-virtual-615072ef29/node_modules/vue-demi/",\
"packageDependencies": [\
["vue-demi", "npm:0.13.1"]\
],\
"linkType": "SOFT"\
}],\
["virtual:3c74e5a1392a9d26efc27d5867a5220b1ab24b8bfb7c76fe2dac826f7e9d478b9c8eb69cc87331bb2ba20521466999017a02e9dca572946a787e2b4314602fca#npm:0.13.1", {\
"packageLocation": "./.yarn/unplugged/vue-demi-virtual-cfef5ecd67/node_modules/vue-demi/",\
["virtual:0b7bfddb52b3cb488814806546397e52c62caef1815758033c8eac7ce386779ac52132e251ad567a19dde858cd2ed318ab2b52e9e258efd261b951e0d2160c16#npm:0.13.1", {\
"packageLocation": "./.yarn/unplugged/vue-demi-virtual-615072ef29/node_modules/vue-demi/",\
"packageDependencies": [\
["vue-demi", "virtual:3c74e5a1392a9d26efc27d5867a5220b1ab24b8bfb7c76fe2dac826f7e9d478b9c8eb69cc87331bb2ba20521466999017a02e9dca572946a787e2b4314602fca#npm:0.13.1"],\
["vue-demi", "virtual:0b7bfddb52b3cb488814806546397e52c62caef1815758033c8eac7ce386779ac52132e251ad567a19dde858cd2ed318ab2b52e9e258efd261b951e0d2160c16#npm:0.13.1"],\
["@types/vue", null],\
["@types/vue__composition-api", null],\
["@vue/composition-api", null],\

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -235,6 +235,12 @@ async function main () {
`VIRTUAL_HOST=${hostname}`,
`VIRTUAL_PORT=8000`
],
Labels: {
appversion: `${argv.appversion}` ?? '0.0.0',
commit: `${argv.commit}` ?? 'unknown',
ghrunid: `${argv.ghrunid}` ?? '0',
hostname
},
HostConfig: {
Binds: [
'dt-assets:/assets'

View file

@ -0,0 +1,9 @@
FROM alpine:3
RUN apk add --no-cache wget rsync
COPY docker/scripts/app-rsync-extras.sh /workspace/app-rsync-extras.sh
RUN chmod +x /workspace/app-rsync-extras.sh
WORKDIR /workspace
CMD ["sh", "app-rsync-extras.sh"]

26
dev/tests/debug.sh Normal file
View file

@ -0,0 +1,26 @@
#!/bin/bash
# This script recreate the same environment used during tests on GitHub Actions
# and drops you into a terminal at the point where the actual tests would be run.
#
# Refer to https://github.com/ietf-tools/datatracker/blob/main/.github/workflows/build.yml#L141-L155
# for the commands to run next.
#
# Simply type "exit" + ENTER to exit and shutdown this test environment.
echo "Fetching latest images..."
docker pull ghcr.io/ietf-tools/datatracker-app-base:latest
docker pull ghcr.io/ietf-tools/datatracker-db:latest
echo "Starting containers..."
docker compose -f docker-compose.debug.yml -p dtdebug up -d
echo "Copying working directory into container..."
docker compose -p dtdebug cp ../../. app:/__w/datatracker/datatracker/
echo "Run prepare script..."
docker compose -p dtdebug exec app chmod +x ./dev/tests/prepare.sh
docker compose -p dtdebug exec app sh ./dev/tests/prepare.sh
docker compose -p dtdebug exec app /usr/local/bin/wait-for db:3306 -- echo "DB ready"
echo "================================================================="
echo "Launching zsh terminal:"
docker compose -p dtdebug exec app /bin/zsh
echo "Shutting down containers..."
docker compose -p dtdebug down -v

View file

@ -0,0 +1,33 @@
# This docker-compose replicates the test workflow happening on GitHub during a PR / build check.
# To be used from the debug.sh script.
version: '3.8'
services:
app:
image: ghcr.io/ietf-tools/datatracker-app-base:latest
command: -f /dev/null
working_dir: /__w/datatracker/datatracker
entrypoint: tail
hostname: app
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
CI: 'true'
GITHUB_ACTIONS: 'true'
HOME: /github/home
db:
image: ghcr.io/ietf-tools/datatracker-db:latest
restart: unless-stopped
volumes:
- mariadb-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: RkTkDPFnKpko
MYSQL_DATABASE: ietf_utf8
MYSQL_USER: django
MYSQL_PASSWORD: RkTkDPFnKpko
CI: 'true'
GITHUB_ACTIONS: 'true'
volumes:
mariadb-data:

View file

@ -54,7 +54,7 @@ SUBMIT_YANG_RFC_MODEL_DIR = '/assets/ietf-ftp/yang/rfcmod/'
# Set INTERNAL_IPS for use within Docker. See https://knasmueller.net/fix-djangos-debug-toolbar-not-showing-inside-docker
import socket
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS = [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips]
INTERNAL_IPS = [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] + ['127.0.0.1']
# DEV_TEMPLATE_CONTEXT_PROCESSORS = [
# 'ietf.context_processors.sql_debug',

View file

@ -42,7 +42,6 @@ cat << EOF > "$EXCLUDE"
*.diff
*.doc
*.exe
*.html
*.mib
*.new
*.p7s

View file

@ -80,7 +80,7 @@ class PersonalInformationExportView(DetailView, JsonExportMixin):
person = get_object_or_404(self.model, user=request.user)
expand = ['searchrule', 'documentauthor', 'ad_document_set', 'ad_dochistory_set', 'docevent',
'ballotpositiondocevent', 'deletedevent', 'email_set', 'groupevent', 'role', 'rolehistory', 'iprdisclosurebase',
'iprevent', 'liaisonstatementevent', 'whitelisted', 'schedule', 'constraint', 'schedulingevent', 'message',
'iprevent', 'liaisonstatementevent', 'allowlisted', 'schedule', 'constraint', 'schedulingevent', 'message',
'sendqueue', 'nominee', 'topicfeedbacklastseen', 'alias', 'email', 'apikeys', 'personevent',
'reviewersettings', 'reviewsecretarysettings', 'unavailableperiod', 'reviewwish',
'nextreviewerinteam', 'reviewrequest', 'meetingregistration', 'submissionevent', 'preapproval',

View file

@ -166,7 +166,7 @@ Thank you,</field>
Thank you for accepting your nomination for the position of $position.
Please remember to complete and return the questionnaire for this position at your earliest opportunity.
The questionaire is repeated below for your convenience.
The questionnaire is repeated below for your convenience.
--------</field>
<field to="group.group" name="group" rel="ManyToOneRel"><None></None></field>

View file

@ -15,7 +15,7 @@
<dd>{{ template.type.name }}
{% if template.type.slug == "rst" %}
<p class="help-block">This template uses the syntax of reStructuredText. Get a quick reference at <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">http://docutils.sourceforge.net/docs/user/rst/quickref.html</a>.</p>
<p class="help-block">You can do variable interpolation with $varialbe if the template allows any variable.</p>
<p class="help-block">You can do variable interpolation with $variable if the template allows any variable.</p>
{% endif %}
{% if template.type.slug == "django" %}
<p class="help-block">This template uses the syntax of the default django template framework. Get more info at <a href="https://docs.djangoproject.com/en/dev/topics/templates/">https://docs.djangoproject.com/en/dev/topics/templates/</a>.</p>

View file

@ -15,7 +15,7 @@
<dd>{{ template.type.name }}
{% if template.type.slug == "rst" %}
<p class="help-block">This template uses the syntax of reStructuredText. Get a quick reference at <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">http://docutils.sourceforge.net/docs/user/rst/quickref.html</a>.</p>
<p class="help-block">You can do variable interpolation with $varialbe if the template allows any variable.</p>
<p class="help-block">You can do variable interpolation with $variable if the template allows any variable.</p>
{% endif %}
{% if template.type.slug == "django" %}
<p class="help-block">This template uses the syntax of the default django template framework. Get more info at <a href="https://docs.djangoproject.com/en/dev/topics/templates/">https://docs.djangoproject.com/en/dev/topics/templates/</a>.</p>

View file

@ -75,7 +75,13 @@ class AdForm(forms.Form):
self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())]
class NotifyForm(forms.Form):
notify = forms.CharField(max_length=255, help_text="List of email addresses to receive state notifications, separated by comma.", label="Notification list", required=False)
notify = forms.CharField(
widget=forms.Textarea,
max_length=1023,
help_text="List of email addresses to receive state notifications, separated by comma.",
label="Notification list",
required=False,
)
def clean_notify(self):
addrspecs = [x.strip() for x in self.cleaned_data["notify"].split(',')]

View file

@ -0,0 +1,23 @@
# Generated by Django 2.2.28 on 2022-12-05 17:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('doc', '0047_tzaware_deletedevents'),
]
operations = [
migrations.AlterField(
model_name='dochistory',
name='notify',
field=models.TextField(blank=True, max_length=1023),
),
migrations.AlterField(
model_name='document',
name='notify',
field=models.TextField(blank=True, max_length=1023),
),
]

View file

@ -8,6 +8,8 @@ import io
import os
import rfc2html
from pathlib import Path
from lxml import etree
from typing import Optional, TYPE_CHECKING
from weasyprint import HTML as wpHTML
@ -21,6 +23,7 @@ from django.conf import settings
from django.utils import timezone
from django.utils.encoding import force_text
from django.utils.html import mark_safe # type:ignore
from django.contrib.staticfiles import finders
import debug # pyflakes:ignore
@ -108,7 +111,7 @@ class DocumentInfo(models.Model):
ad = ForeignKey(Person, verbose_name="area director", related_name='ad_%(class)s_set', blank=True, null=True)
shepherd = ForeignKey(Email, related_name='shepherd_%(class)s_set', blank=True, null=True)
expires = models.DateTimeField(blank=True, null=True)
notify = models.CharField(max_length=255, blank=True)
notify = models.TextField(max_length=1023, blank=True)
external_url = models.URLField(blank=True)
uploaded_filename = models.TextField(blank=True)
note = models.TextField(blank=True)
@ -541,6 +544,49 @@ class DocumentInfo(models.Model):
def text_or_error(self):
return self.text() or "Error; cannot read '%s'"%self.get_base_name()
def html_body(self, classes=""):
if self.get_state_slug() == "rfc":
try:
html = Path(
os.path.join(settings.RFC_PATH, self.canonical_name() + ".html")
).read_text()
except (IOError, UnicodeDecodeError):
return None
else:
try:
html = Path(
os.path.join(
settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR,
self.name + "-" + self.rev + ".html",
)
).read_text()
except (IOError, UnicodeDecodeError):
return None
# If HTML was generated by rfc2html, do not return it. Caller
# will use htmlize() to use a more current rfc2html to
# generate an HTMLized version. TODO: There should be a
# better way to determine how an HTML format was generated.
if html.startswith("<pre>"):
return None
# get body
etree_html = etree.HTML(html)
if etree_html is None:
return None
body = etree_html.xpath("//body")[0]
body.tag = "div"
if classes:
body.attrib["class"] = classes
# remove things
for tag in ["script"]:
for t in body.xpath(f"//{tag}"):
t.getparent().remove(t)
html = etree.tostring(body, encoding=str, method="html")
return html
def htmlized(self):
name = self.get_base_name()
text = self.text()
@ -566,17 +612,29 @@ class DocumentInfo(models.Model):
def pdfized(self):
name = self.get_base_name()
text = self.text()
cache = caches['pdfized']
cache_key = name.split('.')[0]
text = self.html_body(classes="rfchtml")
stylesheets = [finders.find("ietf/css/document_html_referenced.css")]
if text:
stylesheets.append(finders.find("ietf/css/document_html_txt.css"))
else:
text = self.htmlized()
stylesheets.append(io.BytesIO(b"body { font-size: 9.2pt; }"))
cache = caches["pdfized"]
cache_key = name.split(".")[0]
try:
pdf = cache.get(cache_key)
except EOFError:
pdf = None
if not pdf:
html = rfc2html.markup(text, path=settings.PDFIZER_URL_PREFIX)
try:
pdf = wpHTML(string=html.replace('\xad','')).write_pdf(stylesheets=[io.BytesIO(b'html { font-size: 94%;}')])
pdf = wpHTML(
string=text, base_url=settings.IDTRACKER_BASE_URL
).write_pdf(
stylesheets=stylesheets,
presentational_hints=True,
optimize_size=("fonts", "images"),
)
except AssertionError:
pdf = None
if pdf:

View file

@ -260,8 +260,8 @@ def urlize_ietf_docs(string, autoescape=None):
urlize_ietf_docs = stringfilter(urlize_ietf_docs)
@register.filter(name='urlize_related_source_list', is_safe=True, needs_autoescape=True)
def urlize_related_source_list(related, autoescape=None):
@register.filter(name='urlize_related_source_list', is_safe=True, document_html=False)
def urlize_related_source_list(related, document_html=False):
"""Convert a list of RelatedDocuments into list of links using the source document's canonical name"""
links = []
names = set()
@ -273,8 +273,7 @@ def urlize_related_source_list(related, autoescape=None):
continue
names.add(name)
titles.add(title)
url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=name))
if autoescape:
url = urlreverse('ietf.doc.views_doc.document_main' if document_html is False else 'ietf.doc.views_doc.document_html', kwargs=dict(name=name))
name = escape(name)
title = escape(title)
links.append(mark_safe(
@ -284,15 +283,14 @@ def urlize_related_source_list(related, autoescape=None):
))
return links
@register.filter(name='urlize_related_target_list', is_safe=True, needs_autoescape=True)
def urlize_related_target_list(related, autoescape=None):
@register.filter(name='urlize_related_target_list', is_safe=True, document_html=False)
def urlize_related_target_list(related, document_html=False):
"""Convert a list of RelatedDocuments into list of links using the target document's canonical name"""
links = []
for rel in related:
name=rel.target.document.canonical_name()
title = rel.target.document.title
url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=name))
if autoescape:
url = urlreverse('ietf.doc.views_doc.document_main' if document_html is False else 'ietf.doc.views_doc.document_html', kwargs=dict(name=name))
name = escape(name)
title = escape(title)
links.append(mark_safe(
@ -554,6 +552,19 @@ def consensus(doc):
else:
return "Unknown"
@register.filter
def std_level_to_label_format(doc):
"""Returns valid Bootstrap classes to label a status level badge."""
if doc.is_rfc():
if doc.related_that("obs"):
return "obs"
else:
return doc.std_level_id
else:
return "draft"
@register.filter
def pos_to_label_format(text):
"""Returns valid Bootstrap classes to label a ballot position."""

View file

@ -489,7 +489,7 @@ Table of Contents
1. Introduction
This document describes how to make the Martian networks work. The
methods used in Earth do not directly translate to the efficent
methods used in Earth do not directly translate to the efficient
networks on Mars, as the topographical differences caused by planets.
For example the avian carriers, cannot be used in the Mars, thus
RFC1149 ([RFC1149]) cannot be used in Mars.
@ -730,13 +730,13 @@ Man Expires September 22, 2015 [Page 3]
r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=draft.name)))
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Versions:")
self.assertContains(r, "Select version")
self.assertContains(r, "Deimos street")
q = PyQuery(r.content)
self.assertEqual(q('title').text(), 'draft-ietf-mars-test-01')
self.assertEqual(len(q('.rfcmarkup pre')), 4)
self.assertEqual(len(q('.rfcmarkup span.h1')), 2)
self.assertEqual(len(q('.rfcmarkup a[href]')), 41)
self.assertEqual(len(q('.rfcmarkup pre')), 3)
self.assertEqual(len(q('.rfcmarkup span.h1, .rfcmarkup h1')), 2)
self.assertEqual(len(q('.rfcmarkup a[href]')), 28)
r = self.client.get(urlreverse("ietf.doc.views_doc.document_html", kwargs=dict(name=draft.name, rev=draft.rev)))
self.assertEqual(r.status_code, 200)

View file

@ -448,8 +448,8 @@ class EditCharterTests(TestCase):
# Regenerate does not save!
self.assertEqual(charter.notify,newlist)
q = PyQuery(r.content)
formlist = q('form input[name=notify]')[0].value
self.assertEqual(formlist, None)
formlist = q('form textarea[name=notify]')[0].value.strip()
self.assertEqual(formlist, "")
def test_edit_ad(self):

View file

@ -105,7 +105,7 @@ class ConflictReviewTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('form input[name=notify]')),1)
self.assertEqual(len(q('form textarea[name=notify]')), 1)
self.assertEqual(len(q('form select[name=ad]')),0)
# successfully starts a review, and notifies the secretariat
@ -179,8 +179,8 @@ class ConflictReviewTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('form input[name=notify]')),1)
self.assertEqual(doc.notify,q('form input[name=notify]')[0].value)
self.assertEqual(len(q('form textarea[name=notify]')), 1)
self.assertEqual(doc.notify, q('form textarea[name=notify]')[0].value.strip())
# change notice list
newlist = '"Foo Bar" <foo@bar.baz.com>'
@ -197,7 +197,7 @@ class ConflictReviewTests(TestCase):
# Regenerate does not save!
self.assertEqual(doc.notify,newlist)
q = PyQuery(r.content)
self.assertEqual(None,q('form input[name=notify]')[0].value)
self.assertEqual("", q('form textarea[name=notify]')[0].value.strip())
def test_edit_ad(self):
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')

View file

@ -456,7 +456,7 @@ class EditInfoTests(TestCase):
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('form select[name=intended_std_level]')), 1)
self.assertEqual(None,q('form input[name=notify]')[0].value)
self.assertEqual("", q('form textarea[name=notify]')[0].value.strip())
# add
events_before = draft.docevent_set.count()
@ -947,7 +947,7 @@ class IndividualInfoFormsTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertEqual(len(q('form input[name=notify]')),1)
self.assertEqual(len(q('form textarea[name=notify]')), 1)
# Provide a list
r = self.client.post(url,dict(notify="TJ2APh2P@ietf.org",save_addresses="1"))
@ -962,7 +962,7 @@ class IndividualInfoFormsTests(TestCase):
# Regenerate does not save!
self.assertEqual(doc.notify,'TJ2APh2P@ietf.org')
q = PyQuery(r.content)
self.assertEqual(None,q('form input[name=notify]')[0].value)
self.assertEqual("", q('form textarea[name=notify]')[0].value.strip())
def test_doc_change_intended_status(self):
url = urlreverse('ietf.doc.views_draft.change_intention', kwargs=dict(name=self.docname))

View file

@ -147,8 +147,8 @@ class StatusChangeTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('form input[name=notify]')),1)
self.assertEqual(doc.notify,q('form input[name=notify]')[0].value)
self.assertEqual(len(q('form textarea[name=notify]')), 1)
self.assertEqual(doc.notify, q('form textarea[name=notify]')[0].value.strip())
# change notice list
newlist = '"Foo Bar" <foo@bar.baz.com>'
@ -169,8 +169,8 @@ class StatusChangeTests(TestCase):
# Regenerate does not save!
self.assertEqual(doc.notify,newlist)
q = PyQuery(r.content)
formlist = q('form input[name=notify]')[0].value
self.assertEqual(None,formlist)
formlist = q('form textarea[name=notify]')[0].value.strip()
self.assertEqual("", formlist)
def test_edit_title(self):
doc = Document.objects.get(name='status-change-imaginary-mid-review')

View file

@ -995,41 +995,6 @@ def get_search_cache_key(params):
key = "doc:document:search:" + hashlib.sha512(json.dumps(kwargs, sort_keys=True).encode('utf-8')).hexdigest()
return key
def label_wrap(label, items, joiner=',', max=50):
lines = []
if not items:
return lines
line = '%s: %s' % (label, items[0])
for item in items[1:]:
if len(line)+len(joiner+' ')+len(item) > max:
lines.append(line+joiner)
line = ' '*(len(label)+len(': ')) + item
else:
line += joiner+' '+item
if line:
lines.append(line)
return lines
def join_justified(left, right, width=72):
count = max(len(left), len(right))
left = left + ['']*(count-len(left))
right = right + ['']*(count-len(right))
lines = []
i = 0
while True:
l = left[i]
r = right[i]
if len(l)+1+len(r) > width:
left = left + ['']
right = right[:i] + [''] + right[i:]
r = right[i]
count += 1
lines.append( l + ' ' + r.rjust(width-len(l)-1) )
i += 1
if i >= count:
break
return lines
def build_file_urls(doc):
if isinstance(doc,Document) and doc.get_state_slug() == "rfc":
name = doc.canonical_name()
@ -1069,135 +1034,6 @@ def build_file_urls(doc):
return file_urls, found_types
def build_doc_supermeta_block(doc):
items = []
items.append(f'[<a href="{ settings.IDTRACKER_BASE_URL }" title="Document search and retrieval page">Search</a>]')
file_urls, found_types = build_file_urls(doc)
file_urls = [('txt',url) if label=='plain text' else (label,url) for label,url in file_urls]
if file_urls:
file_labels = {
'txt' : 'Plaintext version of this document',
'xml' : 'XML source for this document',
'pdf' : 'PDF version of this document',
'html' : 'HTML version of this document, from XML2RFC',
'bibtex' : 'BibTex entry for this document',
}
parts=[]
for label,url in file_urls:
if 'htmlized' not in label:
file_label=file_labels.get(label,'')
title_attribute = f' title="{file_label}"' if file_label else ''
partstring = f'<a href="{url}"{title_attribute}>{label}</a>'
parts.append(partstring)
items.append('[' + '|'.join(parts) + ']')
items.append(f'[<a href="{ urlreverse("ietf.doc.views_doc.document_main",kwargs=dict(name=doc.canonical_name())) }" title="Datatracker information for this document">Tracker</a>]')
if doc.group.acronym != 'none':
items.append(f'[<a href="{urlreverse("ietf.group.views.group_home",kwargs=dict(acronym=doc.group.acronym))}" title="The working group handling this document">WG</a>]')
items.append(f'[<a href="mailto:{doc.name}@ietf.org?subject={doc.name}" title="Send email to the document authors">Email</a>]')
if doc.rev != "00":
items.append(f'[<a href="{settings.RFCDIFF_BASE_URL}?difftype=--hwdiff&amp;url2={doc.name}-{doc.rev}.txt" title="Inline diff (wdiff)">Diff1</a>]')
items.append(f'[<a href="{settings.RFCDIFF_BASE_URL}?url2={doc.name}-{doc.rev}.txt" title="Side-by-side diff">Diff2</a>]')
items.append(f'[<a href="{settings.IDNITS_BASE_URL}?url={settings.IETF_ID_ARCHIVE_URL}{doc.name}-{doc.rev}.txt" title="Run an idnits check of this document">Nits</a>]')
return ' '.join(items)
def build_doc_meta_block(doc, path):
def add_markup(path, doc, lines):
is_hst = doc.is_dochistory()
rev = doc.rev
if is_hst:
doc = doc.doc
name = doc.name
rfcnum = doc.rfc_number()
errata_url = settings.RFC_EDITOR_ERRATA_URL.format(rfc_number=rfcnum) if not is_hst else ""
ipr_url = "%s?submit=draft&amp;id=%s" % (urlreverse('ietf.ipr.views.search'), name)
for i, line in enumerate(lines):
# add draft links
line = re.sub(r'\b(draft-[-a-z0-9]+)\b', r'<a href="%s/\g<1>">\g<1></a>'%(path, ), line)
# add rfcXXXX to RFC links
line = re.sub(r' (rfc[0-9]+)\b', r' <a href="%s/\g<1>">\g<1></a>'%(path, ), line)
# add XXXX to RFC links
line = re.sub(r' ([0-9]{3,5})\b', r' <a href="%s/rfc\g<1>">\g<1></a>'%(path, ), line)
# add draft revision links
line = re.sub(r' ([0-9]{2})\b', r' <a href="%s/%s-\g<1>">\g<1></a>'%(path, name, ), line)
if rfcnum:
# add errata link
line = re.sub(r'Errata exist', r'<a class="text-warning" href="%s">Errata exist</a>'%(errata_url, ), line)
if is_hst or not rfcnum:
# make current draft rev bold
line = re.sub(r'>(%s)<'%rev, r'><b>\g<1></b><', line)
line = re.sub(r'IPR declarations', r'<a class="text-warning" href="%s">IPR declarations</a>'%(ipr_url, ), line)
line = line.replace(r'[txt]', r'[<a href="%s">txt</a>]' % doc.get_href())
lines[i] = line
return lines
#
now = timezone.now()
draft_state = doc.get_state('draft')
block = ''
meta = {}
if doc.type_id == 'draft':
revisions = []
ipr = doc.related_ipr()
if ipr:
meta['ipr'] = [ "IPR declarations" ]
if doc.is_rfc() and not doc.is_dochistory():
if not doc.name.startswith('rfc'):
meta['from'] = [ "%s-%s"%(doc.name, doc.rev) ]
meta['errata'] = [ "Errata exist" ] if doc.tags.filter(slug='errata').exists() else []
meta['obsoletedby'] = [ document.rfc_number() for alias in doc.related_that('obs') for document in alias.docs.all() ]
meta['obsoletedby'].sort()
meta['updatedby'] = [ document.rfc_number() for alias in doc.related_that('updates') for document in alias.docs.all() ]
meta['updatedby'].sort()
meta['stdstatus'] = [ doc.std_level.name ]
else:
dd = doc.doc if doc.is_dochistory() else doc
revisions += [ '(%s)%s'%(d.name, ' '*(2-((len(d.name)-1)%3))) for d in dd.replaces() ]
revisions += doc.revisions()
if doc.is_dochistory() and doc.doc.is_rfc():
revisions += [ doc.doc.canonical_name() ]
else:
revisions += [ d.name for d in doc.replaced_by() ]
meta['versions'] = revisions
if not doc.is_dochistory and draft_state.slug == 'active' and now > doc.expires:
# Active past expiration date
meta['active'] = [ 'Document is active' ]
meta['state' ] = [ doc.friendly_state() ]
intended_std = doc.intended_std_level if doc.intended_std_level else None
if intended_std:
if intended_std.slug in ['ps', 'ds', 'std']:
meta['stdstatus'] = [ "Standards Track" ]
else:
meta['stdstatus'] = [ intended_std.name ]
elif doc.type_id == 'charter':
meta['versions'] = doc.revisions()
#
# Add markup to items that needs it.
if 'versions' in meta:
meta['versions'] = label_wrap('Versions', meta['versions'], joiner="")
for label in ['Obsoleted by', 'Updated by', 'From' ]:
item = label.replace(' ','').lower()
if item in meta and meta[item]:
meta[item] = label_wrap(label, meta[item])
#
left = []
right = []
#right = [ '[txt]']
for item in [ 'from', 'versions', 'obsoletedby', 'updatedby', ]:
if item in meta and meta[item]:
left += meta[item]
for item in ['stdstatus', 'active', 'state', 'ipr', 'errata', ]:
if item in meta and meta[item]:
right += meta[item]
lines = join_justified(left, right)
block = '\n'.join(add_markup(path, doc, lines))
#
return block
def augment_docs_and_user_with_user_info(docs, user):
"""Add attribute to each document with whether the document is tracked
or has a review wish by the user or not, and the review teams the user is on."""
@ -1362,5 +1198,9 @@ def bibxml_for_draft(doc, rev=None):
else:
doc.date = doc.time.astimezone(tzinfo).date() # Even if this may be incorrect, what would be better?
return render_to_string('doc/bibxml.xml', {'name':doc.name, 'doc': doc, 'doc_bibtype':'I-D'})
name = doc.name if isinstance(doc, Document) else doc.doc.name
if name.startswith('rfc'): # bibxml3 does not speak of RFCs
raise Http404()
return render_to_string('doc/bibxml.xml', {'name':name, 'doc':doc, 'doc_bibtype':'I-D'})

View file

@ -370,13 +370,25 @@ def approve_conflict_review(request, name):
))
class SimpleStartReviewForm(forms.Form):
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
notify = forms.CharField(
widget=forms.Textarea,
max_length=1023,
label="Notice emails",
help_text="Separate email addresses with commas.",
required=False,
)
class StartReviewForm(forms.Form):
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'),
label="Shepherding AD", empty_label="(None)", required=True)
create_in_state = forms.ModelChoiceField(State.objects.filter(used=True, type="conflrev", slug__in=("needshep", "adrev")), empty_label=None, required=False)
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
notify = forms.CharField(
widget=forms.Textarea,
max_length=1023,
label="Notice emails",
help_text="Separate email addresses with commas.",
required=False,
)
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
def __init__(self, *args, **kwargs):

View file

@ -49,6 +49,7 @@ from django.template.loader import render_to_string
from django.urls import reverse as urlreverse
from django.conf import settings
from django import forms
from django.contrib.staticfiles import finders
import debug # pyflakes:ignore
@ -61,9 +62,9 @@ from ietf.doc.utils import (add_links_in_new_revision_events, augment_events_wit
can_adopt_draft, can_unadopt_draft, get_chartering_type, get_tags_for_stream_id,
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
get_initial_notify, make_notify_changed_event, make_rev_history, default_consensus,
add_events_message_info, get_unicode_document_content, build_doc_meta_block,
add_events_message_info, get_unicode_document_content,
augment_docs_and_user_with_user_info, irsg_needed_ballot_positions, add_action_holder_change_event,
build_doc_supermeta_block, build_file_urls, update_documentauthors, fuzzy_find_documents,
build_file_urls, update_documentauthors, fuzzy_find_documents,
bibxml_for_draft)
from ietf.doc.utils_bofreq import bofreq_editors, bofreq_responsible
from ietf.group.models import Role, Group
@ -140,12 +141,12 @@ def interesting_doc_relations(doc):
return interesting_relations_that, interesting_relations_that_doc
def document_main(request, name, rev=None):
def document_main(request, name, rev=None, document_html=False):
doc = get_object_or_404(Document.objects.select_related(), docalias__name=name)
# take care of possible redirections
aliases = DocAlias.objects.filter(docs=doc).values_list("name", flat=True)
if rev==None and doc.type_id == "draft" and not name.startswith("rfc"):
if document_html is False and rev==None and doc.type_id == "draft" and not name.startswith("rfc"):
for a in aliases:
if a.startswith("rfc"):
return redirect("ietf.doc.views_doc.document_main", name=a)
@ -169,7 +170,7 @@ def document_main(request, name, rev=None):
doc = h
break
if not snapshot:
if not snapshot and document_html is False:
return redirect('ietf.doc.views_doc.document_main', name=name)
if doc.type_id == "charter":
@ -184,7 +185,6 @@ def document_main(request, name, rev=None):
top = render_document_top(request, doc, "status", name)
telechat = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
if telechat and (not telechat.telechat_date or telechat.telechat_date < date_today(settings.TIME_ZONE)):
telechat = None
@ -446,8 +446,22 @@ def document_main(request, name, rev=None):
else:
stream_desc = "(None)"
return render(request, "doc/document_draft.html",
html = None
js = None
css = None
if document_html:
html = doc.html_body()
if request.COOKIES.get("pagedeps") == "inline":
js = Path(finders.find("ietf/js/document_html.js")).read_text()
css = Path(finders.find("ietf/css/document_html_inline.css")).read_text()
if html:
css += Path(finders.find("ietf/css/document_html_txt.css")).read_text()
return render(request, "doc/document_draft.html" if document_html is False else "doc/document_html.html",
dict(doc=doc,
document_html=document_html,
css=css,
js=js,
html=html,
group=group,
top=top,
name=name,
@ -787,7 +801,6 @@ def document_raw_id(request, name, rev=None, ext=None):
except:
raise Http404
def document_html(request, name, rev=None):
found = fuzzy_find_documents(name, rev)
num_found = found.documents.count()
@ -811,30 +824,7 @@ def document_html(request, name, rev=None):
if not os.path.exists(doc.get_file_name()):
raise Http404("File not found: %s" % doc.get_file_name())
if doc.type_id in ['draft',]:
doc.supermeta = build_doc_supermeta_block(doc)
doc.meta = build_doc_meta_block(doc, settings.HTMLIZER_URL_PREFIX)
doccolor = 'bgwhite' # Unknown
if doc.type_id=='draft':
if doc.is_rfc():
if doc.related_that('obs'):
doccolor = 'bgbrown'
else:
doccolor = {
'ps' : 'bgblue',
'exp' : 'bgyellow',
'inf' : 'bgorange',
'ds' : 'bgcyan',
'hist' : 'bggrey',
'std' : 'bggreen',
'bcp' : 'bgmagenta',
'unkn' : 'bgwhite',
}.get(doc.std_level_id, 'bgwhite')
else:
doccolor = 'bgred' # Draft
return render(request, "doc/document_html.html", {"doc":doc, "doccolor":doccolor })
return document_main(request, name, rev=rev, document_html=True)
def document_pdfized(request, name, rev=None, ext=None):

View file

@ -515,7 +515,13 @@ class EditInfoForm(forms.Form):
area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active"), empty_label="(None - individual submission)", required=False, label="Assigned to area")
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'), label="Responsible AD", empty_label="(None)", required=True)
create_in_state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg", slug__in=("pub-req", "watching")), empty_label=None, required=False)
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
notify = forms.CharField(
widget=forms.Textarea,
max_length=1023,
label="Notice emails",
help_text="Separate email addresses with commas.",
required=False,
)
note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False, strip=False)
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
returning_item = forms.BooleanField(required=False)

View file

@ -468,7 +468,13 @@ class StartStatusChangeForm(forms.Form):
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'),
label="Shepherding AD", empty_label="(None)", required=False)
create_in_state = forms.ModelChoiceField(State.objects.filter(type="statchg", slug__in=("needshep", "adrev")), empty_label=None, required=False)
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
notify = forms.CharField(
widget=forms.Textarea,
max_length=1023,
label="Notice emails",
help_text="Separate email addresses with commas.",
required=False,
)
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
relations={} # type: Dict[str, str]

View file

@ -133,15 +133,15 @@ class Group(GroupInfo):
role_names = [role_names]
return user.is_authenticated and self.role_set.filter(name__in=role_names, person__user=user).exists()
def is_decendant_of(self, sought_parent):
def is_descendant_of(self, sought_parent):
parent = self.parent
decendants = [ self, ]
while (parent != None) and (parent not in decendants):
decendants = [ parent ] + decendants
descendants = [ self, ]
while (parent != None) and (parent not in descendants):
descendants = [ parent ] + descendants
if parent.acronym == sought_parent:
return True
parent = parent.parent
log.assertion('parent not in decendants')
log.assertion('parent not in descendants')
return False
def get_chair(self):

View file

@ -1937,12 +1937,12 @@ class GroupParentLoopTests(TestCase):
import signal
def timeout_handler(signum, frame):
raise Exception("Infinite loop in parent links is not handeled properly.")
raise Exception("Infinite loop in parent links is not handled properly.")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(1) # One second
try:
test2.is_decendant_of("ietf")
test2.is_descendant_of("ietf")
except AssertionError:
pass
except Exception:

View file

@ -18,7 +18,7 @@ from django_password_strength.widgets import PasswordStrengthInput, PasswordConf
import debug # pyflakes:ignore
from ietf.person.models import Person, Email
from ietf.mailinglists.models import Whitelisted
from ietf.mailinglists.models import Allowlisted
from ietf.utils.text import isascii
class RegistrationForm(forms.Form):
@ -203,9 +203,9 @@ class ResetPasswordForm(forms.Form):
class TestEmailForm(forms.Form):
email = forms.EmailField(required=False)
class WhitelistForm(forms.ModelForm):
class AllowlistForm(forms.ModelForm):
class Meta:
model = Whitelisted
model = Allowlisted
exclude = ['by', 'time' ]

View file

@ -194,7 +194,7 @@ class IetfAuthTests(TestCase):
self.assertTrue(self.username_in_htpasswd_file(email))
def test_create_whitelisted_account(self):
def test_create_allowlisted_account(self):
email = "new-account@example.com"
# add allowlist entry
@ -202,13 +202,13 @@ class IetfAuthTests(TestCase):
self.assertEqual(r.status_code, 302)
self.assertEqual(urlsplit(r["Location"])[2], urlreverse(ietf.ietfauth.views.profile))
r = self.client.get(urlreverse(ietf.ietfauth.views.add_account_whitelist))
r = self.client.get(urlreverse(ietf.ietfauth.views.add_account_allowlist))
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Add a whitelist entry")
self.assertContains(r, "Add an allowlist entry")
r = self.client.post(urlreverse(ietf.ietfauth.views.add_account_whitelist), {"email": email})
r = self.client.post(urlreverse(ietf.ietfauth.views.add_account_allowlist), {"email": email})
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Whitelist entry creation successful")
self.assertContains(r, "Allowlist entry creation successful")
# log out
r = self.client.get(urlreverse('django.contrib.auth.views.logout'))

View file

@ -24,5 +24,5 @@ urlpatterns = [
url(r'^review/$', views.review_overview),
url(r'^testemail/$', views.test_email),
url(r'^username/$', views.change_username),
url(r'^whitelist/add/?$', views.add_account_whitelist),
url(r'^allowlist/add/?$', views.add_account_allowlist),
]

View file

@ -62,13 +62,11 @@ import debug # pyflakes:ignore
from ietf.group.models import Role, Group
from ietf.ietfauth.forms import ( RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm,
WhitelistForm, ChangePasswordForm, get_person_form, RoleEmailForm,
AllowlistForm, ChangePasswordForm, get_person_form, RoleEmailForm,
NewEmailForm, ChangeUsernameForm, PersonPasswordForm)
from ietf.ietfauth.htpasswd import update_htpasswd_file
from ietf.ietfauth.utils import role_required, has_role
from ietf.mailinglists.models import Whitelisted
# needed if we revert to higher barrier for account creation
#from ietf.mailinglists.models import Subscribed, Whitelisted
from ietf.mailinglists.models import Allowlisted
from ietf.name.models import ExtResourceName
from ietf.nomcom.models import NomCom
from ietf.person.models import Person, Email, Alias, PersonalApiKey, PERSON_API_KEY_VALUES
@ -128,7 +126,7 @@ def create_account(request):
# The following is what to revert to should that lowered barrier prove problematic
# existing = Subscribed.objects.filter(email=to_email).first()
# ok_to_create = ( Whitelisted.objects.filter(email=to_email).exists()
# ok_to_create = ( Allowlisted.objects.filter(email=to_email).exists()
# or existing and (existing.time + TimeDelta(seconds=settings.LIST_ACCOUNT_DELAY)) < DateTime.now() )
# if ok_to_create:
# send_account_creation_email(request, to_email)
@ -522,19 +520,19 @@ def test_email(request):
return r
@role_required('Secretariat')
def add_account_whitelist(request):
def add_account_allowlist(request):
success = False
if request.method == 'POST':
form = WhitelistForm(request.POST)
form = AllowlistForm(request.POST)
if form.is_valid():
email = form.cleaned_data['email']
entry = Whitelisted(email=email, by=request.user.person)
entry = Allowlisted(email=email, by=request.user.person)
entry.save()
success = True
else:
form = WhitelistForm()
form = AllowlistForm()
return render(request, 'ietfauth/whitelist_form.html', {
return render(request, 'ietfauth/allowlist_form.html', {
'form': form,
'success': success,
})

View file

@ -134,7 +134,7 @@ class IprTests(TestCase):
def test_search(self):
WgDraftFactory() # The test matching the prefix "draft" needs more than one thing to find
draft = WgDraftFactory()
ipr = HolderIprDisclosureFactory(docs=[draft,],patent_info='Number: US12345\nTitle: A method of transfering bits\nInventor: A. Nonymous\nDate: 2000-01-01')
ipr = HolderIprDisclosureFactory(docs=[draft,],patent_info='Number: US12345\nTitle: A method of transferring bits\nInventor: A. Nonymous\nDate: 2000-01-01')
url = urlreverse("ietf.ipr.views.search")
@ -262,7 +262,7 @@ class IprTests(TestCase):
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
"patent_title": "A method of transfering bits",
"patent_title": "A method of transferring bits",
"patent_date": "2000-01-01",
"has_patent_pending": False,
"licensing": "royalty-free",
@ -277,7 +277,7 @@ class IprTests(TestCase):
ipr = iprs[0]
self.assertEqual(ipr.holder_legal_name, "Test Legal")
self.assertEqual(ipr.state.slug, 'pending')
for item in ['SE12345678901','A method of transfering bits','2000-01-01']:
for item in ['SE12345678901','A method of transferring bits','2000-01-01']:
self.assertIn(item, ipr.get_child().patent_info)
self.assertTrue(isinstance(ipr.get_child(),HolderIprDisclosure))
self.assertEqual(len(outbox),1)
@ -318,7 +318,7 @@ class IprTests(TestCase):
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
"patent_title": "A method of transfering bits",
"patent_title": "A method of transferring bits",
"patent_date": "2000-01-01",
"has_patent_pending": False,
"licensing": "royalty-free",
@ -332,7 +332,7 @@ class IprTests(TestCase):
ipr = iprs[0]
self.assertEqual(ipr.holder_legal_name, "Test Legal")
self.assertEqual(ipr.state.slug, "pending")
for item in ['SE12345678901','A method of transfering bits','2000-01-01' ]:
for item in ['SE12345678901','A method of transferring bits','2000-01-01' ]:
self.assertIn(item, ipr.get_child().patent_info)
self.assertTrue(isinstance(ipr.get_child(),ThirdPartyIprDisclosure))
self.assertEqual(len(outbox),1)
@ -368,7 +368,7 @@ class IprTests(TestCase):
"patent_date": "2000-01-01",
"patent_inventor": "A. Nonymous",
"patent_number": "SE12345678901",
"patent_title": "A method of transfering bits",
"patent_title": "A method of transferring bits",
"submitter_email": "test@holder.com",
"submitter_name": "Test Holder",
"updates": [],
@ -414,7 +414,7 @@ class IprTests(TestCase):
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
"patent_title": "A method of transfering bits",
"patent_title": "A method of transferring bits",
"patent_date": "2000-01-01",
"has_patent_pending": False,
"licensing": "royalty-free",
@ -450,7 +450,7 @@ class IprTests(TestCase):
"iprdocrel_set-0-revisions": '00',
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
"patent_title": "A method of transfering bits",
"patent_title": "A method of transferring bits",
"patent_date": "2000-01-01",
"has_patent_pending": False,
"licensing": "royalty-free",

View file

@ -471,7 +471,6 @@ class IncomingLiaisonForm(LiaisonModelForm):
class OutgoingLiaisonForm(LiaisonModelForm):
from_contact = SearchableEmailField(only_users=True)
approved = forms.BooleanField(label="Obtained prior approval", required=False)
class Meta:
@ -501,6 +500,7 @@ class OutgoingLiaisonForm(LiaisonModelForm):
self.fields['from_groups'].initial = [flat_choices[0][0]]
if has_role(self.user, "Secretariat"):
self.fields['from_contact'] = SearchableEmailField(only_users=True) # secretariat can edit this field!
return
if self.person.role_set.filter(name='liaiman',group__state='active'):
@ -509,8 +509,10 @@ class OutgoingLiaisonForm(LiaisonModelForm):
email = self.person.role_set.filter(name__in=('ad','chair'),group__state='active').first().email.address
else:
email = self.person.email_address()
# Non-secretariat user cannot change the from_contact field. Fill in its value.
self.fields['from_contact'].disabled = True
self.fields['from_contact'].initial = email
self.fields['from_contact'].widget.attrs['readonly'] = True
def set_to_fields(self):
'''Set to_groups and to_contacts options and initial value based on user

View file

@ -2,7 +2,7 @@
from django.contrib import admin
from ietf.mailinglists.models import List, Subscribed, Whitelisted
from ietf.mailinglists.models import List, Subscribed, Allowlisted
class ListAdmin(admin.ModelAdmin):
@ -18,6 +18,6 @@ class SubscribedAdmin(admin.ModelAdmin):
admin.site.register(Subscribed, SubscribedAdmin)
class WhitelistedAdmin(admin.ModelAdmin):
class AllowlistedAdmin(admin.ModelAdmin):
list_display = ('id', 'time', 'email', 'by')
admin.site.register(Whitelisted, WhitelistedAdmin)
admin.site.register(Allowlisted, AllowlistedAdmin)

View file

@ -0,0 +1,22 @@
# Generated by Django 2.2.28 on 2022-12-05 14:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('person', '0029_use_timezone_now_for_person_models'),
('mailinglists', '0002_auto_20190703_1344'),
]
operations = [
migrations.RenameModel(
old_name='Whitelisted',
new_name='Allowlisted',
),
migrations.AlterModelOptions(
name='allowlisted',
options={'verbose_name_plural': 'Allowlisted'},
),
]

View file

@ -28,12 +28,12 @@ class Subscribed(models.Model):
class Meta:
verbose_name_plural = "Subscribed"
class Whitelisted(models.Model):
class Allowlisted(models.Model):
time = models.DateTimeField(auto_now_add=True)
email = models.CharField("Email address", max_length=64, validators=[validate_email])
by = ForeignKey(Person)
def __str__(self):
return "<Whitelisted: %s at %s>" % (self.email, self.time)
return "<Allowlisted: %s at %s>" % (self.email, self.time)
class Meta:
verbose_name_plural = "Whitelisted"
verbose_name_plural = "Allowlisted"

View file

@ -11,17 +11,17 @@ from tastypie.cache import SimpleCache
from ietf import api
from ietf.api import ToOneField # pyflakes:ignore
from ietf.mailinglists.models import Whitelisted, List, Subscribed
from ietf.mailinglists.models import Allowlisted, List, Subscribed
from ietf.person.resources import PersonResource
class WhitelistedResource(ModelResource):
class AllowlistedResource(ModelResource):
by = ToOneField(PersonResource, 'by')
class Meta:
queryset = Whitelisted.objects.all()
queryset = Allowlisted.objects.all()
serializer = api.Serializer()
cache = SimpleCache()
#resource_name = 'whitelisted'
#resource_name = 'allowlisted'
ordering = ['id', ]
filtering = {
"id": ALL,
@ -29,7 +29,7 @@ class WhitelistedResource(ModelResource):
"email": ALL,
"by": ALL_WITH_RELATIONS,
}
api.mailinglists.register(WhitelistedResource())
api.mailinglists.register(AllowlistedResource())
class ListResource(ModelResource):
class Meta:

View file

@ -223,10 +223,10 @@ class InterimMeetingModelForm(forms.ModelForm):
class InterimSessionModelForm(forms.ModelForm):
date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, label='Date', required=False)
time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=True, help_text="Local time")
time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=True, help_text="Start time in meeting time zone")
time.widget.attrs['placeholder'] = "HH:MM"
requested_duration = CustomDurationField(required=True)
end_time = forms.TimeField(required=False, help_text="Local time")
end_time = forms.TimeField(required=False, help_text="End time in meeting time zone")
end_time.widget.attrs['placeholder'] = "HH:MM"
remote_participation = forms.ChoiceField(choices=(), required=False)
remote_instructions = forms.CharField(
@ -258,8 +258,8 @@ class InterimSessionModelForm(forms.ModelForm):
self.is_edit = bool(self.instance.pk)
# setup fields that aren't intrinsic to the Session object
if self.is_edit:
self.initial['date'] = self.instance.official_timeslotassignment().timeslot.time
self.initial['time'] = self.instance.official_timeslotassignment().timeslot.time
self.initial['date'] = self.instance.official_timeslotassignment().timeslot.local_start_time().date()
self.initial['time'] = self.instance.official_timeslotassignment().timeslot.local_start_time().time()
if self.instance.agenda():
doc = self.instance.agenda()
content = doc.text_or_error()

View file

@ -256,6 +256,10 @@ class AgendaFilterOrganizer(AgendaKeywordTool):
self.special_filters = None
if self._use_legacy_keywords():
self.extra_labels += ('Plenary',) # need this when not using session purpose
self.manual_extra_labels = set()
def add_extra_filter(self, kw):
self.manual_extra_labels.add(kw)
def get_non_area_keywords(self):
"""Get list of any 'non-area' (aka 'special') keywords
@ -436,13 +440,13 @@ class AgendaFilterOrganizer(AgendaKeywordTool):
def _extra_filters(self):
"""Get list of filters corresponding to self.extra_labels"""
item_source = self.assignments or self.sessions or []
candidates = set(self.extra_labels)
candidates = set(self.extra_labels).union(self.manual_extra_labels)
return self._filter_column(
label=None,
keyword=None,
children=[
self._filter_entry(label=label, keyword=xslugify(label), toggled_by=[], is_bof=False)
for label in candidates if any(
for label in candidates if label in self.manual_extra_labels or any(
# Keep only those that will affect at least one session
[label.lower() in item.filter_keywords for item in item_source]
)]
@ -1056,7 +1060,6 @@ def sessions_post_save(request, forms):
by=request.user.person,
)
if ('date' in form.changed_data) or ('time' in form.changed_data):
update_interim_session_assignment(form)
if 'agenda' in form.changed_data:
form.save_agenda()
@ -1136,6 +1139,8 @@ def update_interim_session_assignment(form):
"""Helper function to create / update timeslot assigned to interim session
form is an InterimSessionModelForm
Only updates timeslot time (a datetime) and duration
"""
session = form.instance
meeting = session.meeting
@ -1144,6 +1149,7 @@ def update_interim_session_assignment(form):
)
if session.official_timeslotassignment():
slot = session.official_timeslotassignment().timeslot
if slot.time != time or slot.duration != session.requested_duration:
slot.time = time
slot.duration = session.requested_duration
slot.save()

View file

@ -39,7 +39,7 @@ from ietf.name.models import (
)
from ietf.person.models import Person
from ietf.utils.decorators import memoize
from ietf.utils.history import find_history_replacements_active_at
from ietf.utils.history import find_history_replacements_active_at, find_history_active_at
from ietf.utils.storage import NoLocationMigrationFileSystemStorage
from ietf.utils.text import xslugify
from ietf.utils.timezone import datetime_from_date, date_today
@ -409,18 +409,36 @@ class Meeting(models.Model):
def uses_notes(self):
return self.date>=datetime.date(2020,7,6)
def groups_at_the_time(self):
def meeting_start(self):
"""Meeting-local midnight at the start of the meeting date"""
return self.tz().localize(datetime.datetime.combine(self.date, datetime.time()))
def _groups_at_the_time(self):
"""Get dict mapping Group PK to appropriate Group or GroupHistory at meeting time
Known issue: only looks up Groups and their *current* parents when called. If a Group's
parent was different at meeting time, that parent will not be in the cache. Use
group_at_the_time() to look up values - that will fill in missing groups for you.
"""
if not hasattr(self,'cached_groups_at_the_time'):
all_group_pks = set(self.session_set.values_list('group__pk', flat=True))
all_group_pks.update(self.session_set.values_list('group__parent__pk', flat=True))
all_group_pks.discard(None)
# meeting_time is meeting-local midnight at the start of the meeting date
meeting_start = self.tz().localize(
datetime.datetime.combine(self.date, datetime.time())
self.cached_groups_at_the_time = find_history_replacements_active_at(
Group.objects.filter(pk__in=all_group_pks),
self.meeting_start(),
)
self.cached_groups_at_the_time = find_history_replacements_active_at(Group.objects.filter(pk__in=all_group_pks), meeting_start)
return self.cached_groups_at_the_time
def group_at_the_time(self, group):
# MUST call self._groups_at_the_time() before assuming cached_groups_at_the_time exists
gatt = self._groups_at_the_time()
if group.pk in gatt:
return gatt[group.pk]
# Cache miss - look up the missing historical group and add it to the cache.
new_item = find_history_active_at(group, self.meeting_start()) or group # fall back to original if no history
self.cached_groups_at_the_time[group.pk] = new_item
return new_item
class Meta:
ordering = ["-date", "-id"]
@ -1301,11 +1319,12 @@ class Session(models.Model):
return urljoin(settings.IETF_NOTES_URL, self.notes_id())
def group_at_the_time(self):
return self.meeting.groups_at_the_time()[self.group.pk]
return self.meeting.group_at_the_time(self.group)
def group_parent_at_the_time(self):
if self.group_at_the_time().parent:
return self.meeting.groups_at_the_time()[self.group_at_the_time().parent.pk]
return self.meeting.group_at_the_time(self.group_at_the_time().parent)
class SchedulingEvent(models.Model):
session = ForeignKey(Session)

View file

@ -6,6 +6,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings, RequestFactory
from ietf.group.factories import GroupFactory
from ietf.meeting.factories import SessionFactory
from ietf.meeting.forms import (FileUploadForm, ApplyToAllFileUploadForm, InterimSessionModelForm,
InterimMeetingModelForm)
from ietf.person.factories import PersonFactory
@ -123,6 +124,15 @@ class InterimSessionModelFormTests(TestCase):
self.assertNotIn('meetecho', choice_vals)
self.assertIn('manual', choice_vals)
def test_edits_in_meeting_time_zone(self):
# use a time zone that never has a UTC offset of 0, even with DST
session = SessionFactory(meeting__type_id='interim', meeting__time_zone='America/Halifax')
form = InterimSessionModelForm(instance=session)
self.assertEqual(
form.initial['time'].strftime('%H:%M'),
session.official_timeslotassignment().timeslot.local_start_time().strftime('%H:%M'),
)
class InterimMeetingModelFormTests(TestCase):
def test_enforces_authroles(self):

View file

@ -590,26 +590,26 @@ class InterimTests(TestCase):
mock_form.changed_data = []
mock_form.requires_approval = True
mock_form.cleaned_data = {'remote_participation': None}
mock_form.cleaned_data = {'date': date_today(), 'time': datetime.time(1, 23), 'remote_participation': None}
sessions_post_save(RequestFactory().post('/some/url'), [mock_form])
self.assertTrue(mock_create_method.called)
self.assertCountEqual(mock_create_method.call_args[0][0], [])
mock_create_method.reset_mock()
mock_form.cleaned_data = {'remote_participation': 'manual'}
mock_form.cleaned_data['remote_participation'] = 'manual'
sessions_post_save(RequestFactory().post('/some/url'), [mock_form])
self.assertTrue(mock_create_method.called)
self.assertCountEqual(mock_create_method.call_args[0][0], [])
mock_create_method.reset_mock()
mock_form.cleaned_data = {'remote_participation': 'meetecho'}
mock_form.cleaned_data['remote_participation'] = 'meetecho'
sessions_post_save(RequestFactory().post('/some/url'), [mock_form])
self.assertTrue(mock_create_method.called)
self.assertCountEqual(mock_create_method.call_args[0][0], [session])
# Check that an exception does not percolate through sessions_post_save
mock_create_method.side_effect = RuntimeError('some error')
mock_form.cleaned_data = {'remote_participation': 'meetecho'}
mock_form.cleaned_data['remote_participation'] = 'meetecho'
# create mock request with session / message storage
request = RequestFactory().post('/some/url')
setattr(request, 'session', 'session')

View file

@ -1076,9 +1076,9 @@ class InterimTests(IetfSeleniumTestCase):
unexpected.add(entry.text)
advance_month()
self.assertEqual(seen, visible_meetings, "Expected calendar entries not shown.")
self.assertEqual(not_visible, set(), "Hidden calendar entries for expected interim meetings.")
self.assertEqual(unexpected, set(), "Unexpected calendar entries visible")
self.assertCountEqual(seen, visible_meetings, "Expected calendar entries not shown.")
self.assertCountEqual(not_visible, set(), "Hidden calendar entries for expected interim meetings.")
self.assertCountEqual(unexpected, set(), "Unexpected calendar entries visible")
def do_upcoming_view_filter_test(self, querystring, visible_meetings=()):
self.login()
@ -1152,102 +1152,85 @@ class InterimTests(IetfSeleniumTestCase):
ietf_meetings = set(self.all_ietf_meetings())
self.do_upcoming_view_filter_test('', ietf_meetings.union(self.displayed_interims()))
def test_upcoming_view_show_ietf_meetings(self):
self.do_upcoming_view_filter_test('?show=ietf-meetings', self.all_ietf_meetings())
def test_upcoming_view_filter_show_group(self):
# Show none
ietf_meetings = set(self.all_ietf_meetings())
self.do_upcoming_view_filter_test('?show=', ietf_meetings)
self.do_upcoming_view_filter_test('?show=')
# Show one
self.do_upcoming_view_filter_test('?show=mars',
ietf_meetings.union(
self.displayed_interims(groups=['mars'])
))
self.do_upcoming_view_filter_test('?show=mars', self.displayed_interims(groups=['mars']))
# Show two
self.do_upcoming_view_filter_test('?show=mars,ames',
ietf_meetings.union(
self.displayed_interims(groups=['mars', 'ames'])
))
self.do_upcoming_view_filter_test('?show=mars,ames',self.displayed_interims(groups=['mars', 'ames']))
# Show two plus ietf-meetings
self.do_upcoming_view_filter_test(
'?show=ietf-meetings,mars,ames',
set(self.all_ietf_meetings()).union(self.displayed_interims(groups=['mars', 'ames']))
)
def test_upcoming_view_filter_show_area(self):
mars = Group.objects.get(acronym='mars')
area = mars.parent
ietf_meetings = set(self.all_ietf_meetings())
self.do_upcoming_view_filter_test('?show=%s' % area.acronym,
ietf_meetings.union(
self.displayed_interims(groups=['mars', 'ames'])
))
self.do_upcoming_view_filter_test('?show=%s' % area.acronym, self.displayed_interims(groups=['mars', 'ames']))
def test_upcoming_view_filter_show_type(self):
ietf_meetings = set(self.all_ietf_meetings())
self.do_upcoming_view_filter_test('?show=plenary',
ietf_meetings.union(
self.displayed_interims(groups=['sg'])
))
self.do_upcoming_view_filter_test('?show=plenary', self.displayed_interims(groups=['sg']))
def test_upcoming_view_filter_hide_group(self):
mars = Group.objects.get(acronym='mars')
area = mars.parent
# Without anything shown, should see only ietf meetings
ietf_meetings = set(self.all_ietf_meetings())
self.do_upcoming_view_filter_test('?hide=mars', ietf_meetings)
self.do_upcoming_view_filter_test('?hide=mars')
# With group shown
self.do_upcoming_view_filter_test('?show=ames,mars&hide=mars',
ietf_meetings.union(
self.displayed_interims(groups=['ames'])
))
self.do_upcoming_view_filter_test('?show=ames,mars&hide=mars', self.displayed_interims(groups=['ames']))
# With area shown
self.do_upcoming_view_filter_test('?show=%s&hide=mars' % area.acronym,
ietf_meetings.union(
self.displayed_interims(groups=['ames'])
))
self.do_upcoming_view_filter_test('?show=%s&hide=mars' % area.acronym, self.displayed_interims(groups=['ames']))
# With type shown
self.do_upcoming_view_filter_test('?show=plenary&hide=sg',
ietf_meetings)
self.do_upcoming_view_filter_test('?show=plenary&hide=sg')
def test_upcoming_view_filter_hide_area(self):
mars = Group.objects.get(acronym='mars')
area = mars.parent
# Without anything shown, should see only ietf meetings
ietf_meetings = set(self.all_ietf_meetings())
self.do_upcoming_view_filter_test('?hide=%s' % area.acronym, ietf_meetings)
# Without anything shown, should see nothing
self.do_upcoming_view_filter_test('?hide=%s' % area.acronym)
# With area shown
self.do_upcoming_view_filter_test('?show=%s&hide=%s' % (area.acronym, area.acronym),
ietf_meetings)
self.do_upcoming_view_filter_test('?show=%s&hide=%s' % (area.acronym, area.acronym))
# With group shown
self.do_upcoming_view_filter_test('?show=mars&hide=%s' % area.acronym, ietf_meetings)
self.do_upcoming_view_filter_test('?show=mars&hide=%s' % area.acronym)
# With type shown
self.do_upcoming_view_filter_test('?show=regular&hide=%s' % area.acronym, ietf_meetings)
self.do_upcoming_view_filter_test('?show=regular&hide=%s' % area.acronym)
# with IETF meetings shown
self.do_upcoming_view_filter_test('?show=ietf-meetings,hide=%s' % area.acronym, self.all_ietf_meetings())
def test_upcoming_view_filter_hide_type(self):
mars = Group.objects.get(acronym='mars')
area = mars.parent
# Without anything shown, should see only ietf meetings
ietf_meetings = set(self.all_ietf_meetings())
self.do_upcoming_view_filter_test('?hide=regular', ietf_meetings)
# Without anything shown, should see nothing
self.do_upcoming_view_filter_test('?hide=regular')
# With group shown
self.do_upcoming_view_filter_test('?show=mars&hide=regular', ietf_meetings)
self.do_upcoming_view_filter_test('?show=mars&hide=regular')
# With type shown
self.do_upcoming_view_filter_test('?show=plenary,regular&hide=%s' % area.acronym,
ietf_meetings.union(
self.do_upcoming_view_filter_test(
'?show=plenary,regular&hide=regular',
self.displayed_interims(groups=['sg'])
))
)
# With interim-meetings shown
self.do_upcoming_view_filter_test('?show=plenary,regular&hide=regular', self.displayed_interims(groups=['sg']))
def test_upcoming_view_filter_whitespace(self):
"""Whitespace in filter lists should be ignored"""
meetings = set(self.all_ietf_meetings())
meetings.update(self.displayed_interims(groups=['mars']))
self.do_upcoming_view_filter_test('?show=mars , ames &hide= ames', meetings)
self.do_upcoming_view_filter_test('?show=mars , ames &hide= ames', self.displayed_interims(groups=['mars']))
def test_upcoming_view_time_zone_selection(self):
def _assert_interim_tz_correct(sessions, tz):

View file

@ -5,9 +5,11 @@ import datetime
from mock import patch
from ietf.group.factories import GroupFactory, GroupHistoryFactory
from ietf.meeting.factories import MeetingFactory, SessionFactory, AttendedFactory
from ietf.stats.factories import MeetingRegistrationFactory
from ietf.utils.test_utils import TestCase
from ietf.utils.timezone import date_today, datetime_today
class MeetingTests(TestCase):
@ -104,6 +106,14 @@ class MeetingTests(TestCase):
vtz = meeting.vtimezone()
self.assertIsNone(vtz)
def test_group_at_the_time(self):
m = MeetingFactory(type_id='ietf', date=date_today() - datetime.timedelta(days=10))
cached_groups = GroupFactory.create_batch(2)
m.cached_groups_at_the_time = {g.pk: g for g in cached_groups} # fake the cache
uncached_group_hist = GroupHistoryFactory(time=datetime_today() - datetime.timedelta(days=30))
self.assertEqual(m.group_at_the_time(uncached_group_hist.group), uncached_group_hist)
self.assertIn(uncached_group_hist.group.pk, m.cached_groups_at_the_time)
class SessionTests(TestCase):
def test_chat_archive_url_with_jabber(self):

View file

@ -4507,8 +4507,16 @@ class InterimTests(TestCase):
# Just a quick check of functionality - details tested by test_js.InterimTests
make_meeting_test_data(create_interims=True)
url = urlreverse("ietf.meeting.views.upcoming_ical")
r = self.client.get(url + '?show=mars')
r = self.client.get(url + '?show=mars')
self.assertEqual(r.status_code, 200)
assert_ical_response_is_valid(self, r,
expected_event_summaries=[
'mars - Martian Special Interest Group',
],
expected_event_count=1)
r = self.client.get(url + '?show=mars,ietf-meetings')
self.assertEqual(r.status_code, 200)
assert_ical_response_is_valid(self, r,
expected_event_summaries=[
@ -5374,12 +5382,11 @@ class InterimTests(TestCase):
length_before = len(outbox)
form_initial = r.context['form'].initial
formset_initial = r.context['formset'].forms[0].initial
new_time = formset_initial['time'] + datetime.timedelta(hours=1)
data = {'group':group.pk,
'meeting_type':'single',
'session_set-0-id':meeting.session_set.first().id,
'session_set-0-date':formset_initial['date'].strftime('%Y-%m-%d'),
'session_set-0-time':new_time.strftime('%H:%M'),
'session_set-0-time':'12:34',
'session_set-0-requested_duration': '00:30',
'session_set-0-remote_instructions':formset_initial['remote_instructions'],
#'session_set-0-agenda':formset_initial['agenda'],
@ -5392,7 +5399,10 @@ class InterimTests(TestCase):
self.assertEqual(len(outbox),length_before)
session = meeting.session_set.first()
timeslot = session.official_timeslotassignment().timeslot
self.assertEqual(timeslot.time,new_time)
self.assertEqual(
timeslot.time,
meeting.tz().localize(datetime.datetime.combine(formset_initial['date'], datetime.time(12, 34))),
)
def test_interim_request_edit(self):
'''Edit request. Send notice of change'''
@ -5412,13 +5422,12 @@ class InterimTests(TestCase):
length_before = len(outbox)
form_initial = r.context['form'].initial
formset_initial = r.context['formset'].forms[0].initial
new_time = formset_initial['time'] + datetime.timedelta(hours=1)
new_duration = formset_initial['requested_duration'] + datetime.timedelta(hours=1)
data = {'group':group.pk,
'meeting_type':'single',
'session_set-0-id':meeting.session_set.first().id,
'session_set-0-date':formset_initial['date'].strftime('%Y-%m-%d'),
'session_set-0-time':new_time.strftime('%H:%M'),
'session_set-0-time': '12:34',
'session_set-0-requested_duration':self.strfdelta(new_duration, '{hours}:{minutes}'),
'session_set-0-remote_instructions':formset_initial['remote_instructions'],
#'session_set-0-agenda':formset_initial['agenda'],
@ -5432,7 +5441,10 @@ class InterimTests(TestCase):
self.assertIn('CHANGED', outbox[-1]['Subject'])
session = meeting.session_set.first()
timeslot = session.official_timeslotassignment().timeslot
self.assertEqual(timeslot.time,new_time)
self.assertEqual(
timeslot.time,
meeting.tz().localize(datetime.datetime.combine(formset_initial['date'], datetime.time(12, 34))),
)
self.assertEqual(timeslot.duration,new_duration)
def strfdelta(self, tdelta, fmt):

View file

@ -3456,6 +3456,10 @@ def upcoming(request):
# Set up for agenda filtering - only one filter_category here
AgendaKeywordTagger(sessions=interim_sessions).apply()
filter_organizer = AgendaFilterOrganizer(sessions=interim_sessions, single_category=True)
# Allow filtering to show only IETF Meetings. This adds a button labeled "IETF Meetings" to the
# "Other" column of the filter UI. When enabled, this adds the keyword "ietf-meetings" to the "show"
# filter list. The IETF meetings are explicitly labeled with this keyword in upcoming.html.
filter_organizer.add_extra_filter('IETF Meetings')
entries = list(ietf_meetings)
entries.extend(list(interim_sessions))
@ -3544,9 +3548,14 @@ def upcoming_ical(request):
a.session = sessions.get(a.session_id) or a.session
a.session.ical_status = ical_session_status(a)
# handle IETFs separately
# Handle IETFs separately. Manually apply the 'ietf-meetings' filter.
if filter_params is None or (
'ietf-meetings' in filter_params['show'] and 'ietf-meetings' not in filter_params['hide']
):
ietfs = [m for m in meetings if m.type_id == 'ietf']
preprocess_meeting_important_dates(ietfs)
else:
ietfs = []
meeting_vtz = {meeting.vtimezone() for meeting in meetings}
meeting_vtz.discard(None)

View file

@ -385,7 +385,7 @@
},
{
"fields": {
"desc": "The document is in the RFC editor Queue (as confirmed by http://www.rfc-editor.org/queue.html).",
"desc": "The document is in the RFC editor Queue (as confirmed by https://www.rfc-editor.org/queue.html).",
"name": "RFC Ed Queue",
"next_states": [
7
@ -2585,7 +2585,7 @@
},
{
"fields": {
"label": "Liason Statement State"
"label": "Liaison Statement State"
},
"model": "doc.statetype",
"pk": "liaison"
@ -3844,7 +3844,7 @@
{
"fields": {
"cc": [],
"desc": "Recipients for message to adminstrators when a charter state edit needs followon administrative action",
"desc": "Recipients for message to administrators when a charter state edit needs follow-on administrative action",
"to": [
"iesg_secretary"
]
@ -5909,7 +5909,7 @@
},
{
"fields": {
"desc": "The set of people who can approve this liasion statemetns",
"desc": "The set of people who can approve this liaison statements",
"template": "{{liaison.approver_emails|join:\", \"}}"
},
"model": "mailtrigger.recipient",

View file

@ -175,7 +175,7 @@ class MergeNomineeForm(forms.Form):
secondary_emails = self.cleaned_data.get("secondary_emails")
if primary_email and secondary_emails:
if primary_email in secondary_emails:
msg = "Primary and secondary email address must be differents"
msg = "Primary and secondary email address must be different"
self._errors["primary_email"] = self.error_class([msg])
return self.cleaned_data

View file

@ -65,7 +65,10 @@ class PersonFactory(factory.django.DjangoModelFactory):
user = factory.SubFactory(UserFactory)
name = factory.LazyAttribute(lambda p: normalize_name('%s %s'%(p.user.first_name, p.user.last_name)))
ascii = factory.LazyAttribute(lambda p: force_text(unidecode_name(p.name)))
# Some i18n names, e.g., "शिला के.सी." have a dot at the end that is also part of the ASCII, e.g., "Shilaa Kesii."
# That trailing dot breaks extract_authors(). Avoid this issue by stripping the dot from the ASCII.
# Some others have a trailing semicolon (e.g., "உயிரோவியம் தங்கராஐ;") - strip those, too.
ascii = factory.LazyAttribute(lambda p: force_text(unidecode_name(p.name)).rstrip(".;"))
class Params:
with_bio = factory.Trait(biography = "\n\n".join(fake.paragraphs())) # type: ignore

View file

@ -84,7 +84,7 @@ class Command(BaseCommand):
email.origin = email.person.user.username if email.person.user_id else ('script: %s deactivation' % options['reason'])
email.save()
PersonEvent.objects.create(person=email.person, type='email_address_deactivated',
desc="Deactivated the email addres <%s>. Reason: %s" % (email.address, options['reason']) )
desc="Deactivated the email address <%s>. Reason: %s" % (email.address, options['reason']) )
else:
if email is None:
not_found.append(a)

View file

@ -5,7 +5,7 @@
<input type="radio" name="wg_action_status" value="2"> External Review NOT APPROVED;
<blockquote>
<input type="radio" name="wg_action_status_sub" value="1"> The Secretariat will wait for instructions from <select name="note_draft_by"></select><br>
<input type="radio" name="wg_action_status_sub" value="2"> The IESG decides the document needs more thime in INTERNAL REVIEW. The Secreatriat will put it back on the agenda for the next teleconference in the same category.<br>
<input type="radio" name="wg_action_status_sub" value="2"> The IESG decides the document needs more time in INTERNAL REVIEW. The Secretariat will put it back on the agenda for the next teleconference in the same category.<br>
<input type="radio" name="wg_action_status_sub" value="3"> The IESG has made changes since the charter was seen in INTERNAL REVIEW, and decides to send it back to INTERNAL REVIEW the charter again.
</blockquote>
{% endif %}

View file

@ -18,7 +18,7 @@ warnings.filterwarnings("ignore", message="The logout\(\) view is superseded by"
warnings.filterwarnings("ignore", message="Report.file_reporters will no longer be available in Coverage.py 4.2", module="coverage.report")
warnings.filterwarnings("ignore", message="{% load staticfiles %} is deprecated")
warnings.filterwarnings("ignore", message="Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated", module="bleach")
warnings.filterwarnings("ignore", message="HTTPResponse.getheader\(\) is deprecated", module='selenium.webdriver')
try:
import syslog
syslog.openlog(str("datatracker"), syslog.LOG_PID, syslog.LOG_USER)
@ -542,7 +542,7 @@ INTERNAL_IPS = (
# no slash at end
IDTRACKER_BASE_URL = "https://datatracker.ietf.org"
RFCDIFF_BASE_URL = "https://www.ietf.org/rfcdiff"
RFCDIFF_BASE_URL = "https://author-tools.ietf.org/iddiff"
IDNITS_BASE_URL = "https://author-tools.ietf.org/api/idnits"
IDNITS_SERVICE_URL = "https://author-tools.ietf.org/idnits"
@ -610,7 +610,7 @@ TEST_TEMPLATE_IGNORE = [
"500.html" # isn't loaded by regular loader, but checked by test_500_page()
]
TEST_COVERAGE_MASTER_FILE = os.path.join(BASE_DIR, "../release-coverage.json")
TEST_COVERAGE_MAIN_FILE = os.path.join(BASE_DIR, "../release-coverage.json")
TEST_COVERAGE_LATEST_FILE = os.path.join(BASE_DIR, "../latest-coverage.json")
TEST_CODE_COVERAGE_CHECKER = None
@ -680,7 +680,7 @@ INTERNET_ALL_DRAFTS_ARCHIVE_DIR = '/a/ietfdata/doc/draft/archive'
MEETING_RECORDINGS_DIR = '/a/www/audio'
DERIVED_DIR = '/a/ietfdata/derived'
DOCUMENT_FORMAT_WHITELIST = ["txt", "ps", "pdf", "xml", "html", ]
DOCUMENT_FORMAT_ALLOWLIST = ["txt", "ps", "pdf", "xml", "html", ]
# Mailing list info URL for lists hosted on the IETF servers
MAILING_LIST_INFO_URL = "https://www.ietf.org/mailman/listinfo/%(list_addr)s"

View file

@ -0,0 +1,316 @@
@use "sass:map";
// FIXME: It's not clear why these three variables remain unset by bs5, but just
// set them to placeholder values so the CSS embedded in the HTML validates.
$btn-font-family: inherit !default;
$nav-link-font-weight: inherit !default;
$tooltip-margin: inherit !default;
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/root";
// Layout & components
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
// @import "bootstrap/scss/images";
@import "bootstrap/scss/containers";
@import "bootstrap/scss/grid";
// @import "bootstrap/scss/tables";
@import "bootstrap/scss/forms";
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/transitions";
// @import "bootstrap/scss/dropdown";
@import "bootstrap/scss/button-group";
@import "bootstrap/scss/nav";
@import "bootstrap/scss/navbar";
// @import "bootstrap/scss/card";
// @import "bootstrap/scss/accordion";
// @import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/pagination";
@import "bootstrap/scss/badge";
@import "bootstrap/scss/alert";
// @import "bootstrap/scss/progress";
// @import "bootstrap/scss/list-group";
// @import "bootstrap/scss/close";
// @import "bootstrap/scss/toasts";
// @import "bootstrap/scss/modal";
@import "bootstrap/scss/tooltip";
// @import "bootstrap/scss/popover";
// @import "bootstrap/scss/carousel";
// @import "bootstrap/scss/spinners";
// @import "bootstrap/scss/offcanvas";
// @import "bootstrap/scss/placeholders";
// Helpers
@import "bootstrap/scss/helpers";
// Utilities
@import "bootstrap/scss/utilities/api";
:root {
--doc-ptsize-max: 16pt;
}
.overscroll-none {
overscroll-behavior: none;
}
.no-scrollbar {
scrollbar-width: none;
}
.sidebar-toggle[aria-expanded="true"] {
display: none;
}
.sidebar-toggle[aria-expanded="false"] {
display: inherit;
}
@media screen {
@include media-breakpoint-down(md) {
body {
padding-top: 60px;
}
html {
scroll-padding-top: 60px;
}
}
@include media-breakpoint-down(sm) {
body {
padding-top: 70px;
}
html {
scroll-padding-top: 70px;
}
}
}
.rfcmarkup,
.rfchtml {
font-family: var(--bs-font-monospace);
caption {
padding: 0;
color: var(--bs-body-color);
}
code {
font-size: 1em;
color: inherit;
}
@media screen {
@include media-breakpoint-only(xs) {
font-size: min(7pt, var(--doc-ptsize-max));
}
@include media-breakpoint-up(sm) {
font-size: min(9.5pt, var(--doc-ptsize-max));
}
@include media-breakpoint-up(md) {
font-size: min(9.5pt, var(--doc-ptsize-max));
}
@include media-breakpoint-up(lg) {
font-size: min(11pt, var(--doc-ptsize-max));
}
@include media-breakpoint-up(xl) {
font-size: min(13pt, var(--doc-ptsize-max));
}
@include media-breakpoint-up(xxl) {
font-size: min(16pt, var(--doc-ptsize-max));
}
.grey,
hr {
opacity: $hr-opacity;
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
font-size: 1em;
}
pre,
code {
font-size: 1em;
}
pre {
width: 72ch;
margin: 0;
padding: 0;
}
.bcp14 {
font-weight: bold;
// color: $gray-700;
}
}
.rfcmarkup {
h1,
h2,
h3,
h4,
h5,
h6 {
white-space: pre;
display: inline;
}
}
tbody.meta tr {
td:first-child,
th:first-child,
td.edit {
display: none;
}
}
.sidebar {
height: 100vh;
.toplink,
#name-table-of-contents {
display: none;
}
th,
td {
display: block;
padding: 0;
}
td {
margin-bottom: map.get($spacers, 3);
}
}
// Add some padding when there are multiple buttons in a line that can wrap
.buttonlist .btn {
margin-bottom: map.get($spacers, 1);
}
// Make revision numbers pagination items fixed-width
.revision-list {
.page-item {
width: 2.2rem;
}
.page-item.rfc {
width: 6.6rem;
}
}
#docinfo {
max-height: 70vh;
z-index: -1;
}
.badge-obs {
color: white;
background-color: $orange-800;
}
.badge-ps {
color: black;
background-color: $blue-300;
}
.badge-exp {
color: black;
background-color: $yellow-200;
}
.badge-inf {
color: white;
background-color: $orange;
}
.badge-ds {
color: black;
background-color: $cyan-200;
}
.badge-hist {
color: white;
background-color: $gray-700;
}
.badge-std {
color: black;
background-color: $teal-200;
}
.badge-bcp {
color: white;
background-color: $pink-500;
}
.badge-unkn {
color: black;
background-color: $gray-300;
}
.badge-draft {
color: white;
background-color: $danger;
}
#toc-nav {
width: inherit;
overscroll-behavior-y: none; // Prevent overscrolling from scrolling the main content
}
@media print {
@page {
size: letter;
margin: .75in;
}
body {
margin: 0;
padding: 0;
font-size: 10pt;
}
pre {
page-break-inside: avoid;
}
a:link,
a:visited {
// color: inherit;
// text-decoration: none;
}
.newpage {
page-break-before: always !important;
}
.noprint {
display: none;
}
}

View file

@ -0,0 +1,6 @@
@import "document_html";
// Make the bootstrap icons available via data-url.
$bootstrap-icons-font-src: url(data-url:npm:bootstrap-icons/font/fonts/bootstrap-icons.woff2) format("woff2"),
url(data-url:npm:bootstrap-icons/font/fonts/bootstrap-icons.woff) format("woff");
@import "bootstrap-icons/font/bootstrap-icons";

View file

@ -0,0 +1,6 @@
@import "document_html";
// Make the bootstrap icons available.
$bootstrap-icons-font-src: url("npm:bootstrap-icons/font/fonts/bootstrap-icons.woff2") format("woff2"),
url("npm:bootstrap-icons/font/fonts/bootstrap-icons.woff") format("woff");
@import "bootstrap-icons/font/bootstrap-icons";

View file

@ -0,0 +1,381 @@
:root {
--line: 1.2em;
--block: 0 0 0 3ch;
--paragraph: var(--line) 0 var(--line) 3ch;
}
// WeasyPrint can't handle CSS variables in multi-attribute properties, so work
// around that
// https://github.com/Kozea/WeasyPrint/issues/1219
@mixin margin-paragraph {
margin-top: var(--line);
margin-right: 0;
margin-bottom: var(--line);
margin-left: 3ch;
}
@mixin margin-line {
margin-top: var(--line);
margin-right: 0;
margin-bottom: var(--line);
margin-left: 0;
}
@mixin margin-block {
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
margin-left: 3ch;
}
.rfchtml {
// body {
// color: black;
// font-family: monospace;
// font-size: 1em;
line-height: var(--line);
width: 72ch;
// margin: var(--line) 3ch;
margin-top: var(--line);
margin-right: 3ch;
margin-bottom: var(--line);
margin-left: 3ch;
h1, h2, h3, h4, h5 {
font-weight: bold;
font-size: inherit;
line-height: inherit;
// margin: var(--line) 0;
@include margin-line;
}
.section-number {
margin-right: 1ch;
}
p {
// margin: var(--paragraph);
@include margin-paragraph;
}
aside, ol, dl {
// margin: var(--block);
@include margin-block;
}
figure {
margin: 0;
}
blockquote {
// margin: var(--paragraph);
@include margin-paragraph;
border-left: 2px solid darkgrey;
}
// Don't wrap boilerplate URLs; makes it look more like text version.
:is(#status-of-memo, #copyright) a[href] {
white-space: nowrap;
}
/* Header junk */
#external-metadata {
display: none !important; /* metadata.min.js is evil because it produces unstyleable goop */
}
#identifiers dt:is(.label-stream, .label-rfc, .label-std, .label-bcp, .label-internet-draft,
.label-workgroup, .label-intended-status, .label-obsoletes, .label-updates,
.label-published, .label-expires, .label-category, .label-issn,
.label-authors), .ears {
display: none;
}
#identifiers {
margin: 0;
display: grid;
grid-template-columns: 47ch 24ch;
grid-auto-rows: auto;
gap: 0 1ch;
}
#identifiers dt {
margin: 0 1ch 0 0;
float: revert;
display: inline-block;
}
#identifiers dd {
grid-column: 1;
margin: 0;
width: 47ch;
/* HAXX: this gets around the lack of text-content-trim support */
display: flex;
flex-wrap: wrap;
}
#identifiers dd::before {
margin: 0 1ch 0 0;
}
#identifiers dd.rfc::before {
content: "Request for Comments:";
}
#identifiers dd.std::before {
content: "STD:";
}
#identifiers dd.bcp::before {
content: "BCP:";
}
#identifiers dd.internet-draft::before {
content: "Internet-Draft:";
}
#identifiers dd.workgroup::before {
content: "Workgroup:";
}
#identifiers dd.intended-status::before {
content: "Intended Status:";
}
#identifiers dd.obsoletes::before {
content: "Obsoletes:";
margin: 0 0 0 -10ch;
}
#identifiers dd.obsoletes {
padding-left: 10ch;
width: 37ch;
}
#identifiers dd.updates::before {
content: "Updates:";
margin: 0 0 0 -8ch;
}
#identifiers dd.updates {
padding-left: 8ch;
width: 39ch;
}
#identifiers dd:is(.updates, .obsoletes) a {
margin: 0 0 0 1ch;
}
#identifiers dd:is(.updates, .obsoletes) a:last-of-type {
margin: 0 1ch;
}
#identifiers dd.published::before {
content: "Published:";
}
#identifiers dd.expires::before {
content: "Expires:";
}
#identifiers dd.category::before {
content: "Category:";
}
#identifiers dd.issn::before {
content: "ISSN:";
}
#identifiers dd.authors {
grid-area: 1 / 2 / 100 / 3;
width: 24ch;
text-align: right;
display: block;
}
#title {
clear: left;
text-align: center;
margin-top: 2em;
}
#rfcnum {
display: none;
}
.toplink {
display: none;
}
nav.toc ul {
list-style: none;
margin: 0;
padding: 0;
}
nav.toc ul > li {
padding-left: 2ch;
}
nav.toc > ul > li {
padding-left: 3ch;
}
nav.toc ul > li > p {
margin: 0;
}
/* Lists */
ol, ul {
padding: 0;
}
ol {
margin: 0 0 0 6ch; /* todo: deal with lists that have >= 10 items */
}
ol ol, ul ol {
margin: 0 0 0 3ch;
}
ul {
margin: 0 0 0 4ch;
list-style-type: '*';
}
ul ul {
list-style-type: '-';
}
ul ul, ol ul {
margin-left: 1ch;
}
ul ul ul {
list-style-type: 'o';
}
li {
// margin: var(--line) 0;
@include margin-line;
padding: 0 0 0 2ch;
}
.compact li {
margin: 0;
}
li p:first-child, dd p:first-child {
margin: 0;
}
dt {
float: left;
clear: left;
margin: 0 2ch 0 0;
break-after: avoid;
}
dd {
// margin: var(--paragraph);
@include margin-paragraph;
break-before: avoid;
}
dl.compact dt, dl.compact dd {
margin-top: 0;
margin-bottom: 0;
}
dl.references dt {
margin-right: 1ch;
}
dl.references dd {
margin-left: 11ch;
}
dd.break {
display: none;
}
/* Figures, tables */
pre {
// margin: var(--line) 0;
@include margin-line;
}
div.artwork, div.sourcecode {
display: flex;
flex-wrap: nowrap;
align-items: end;
}
div.artwork.alignCenter, div.sourcecode.alignCenter {
justify-content: center;
}
div.artwork.alignRight, div.sourcecode.alignRight {
justify-content: end;
}
div.artwork::before, div.sourcecode::before {
flex: 0 1 3ch;
content: "";
}
div.artwork.alignRight::before, div.sourcecode.alignRight::before {
flex-grow: 1;
}
div.artwork pre, div.sourcecode pre {
flex: 0 0 content;
margin: 0;
max-width: 72ch;
overflow: auto;
}
div.artwork .pilcrow, div.sourcecode .pilcrow {
flex: 0 0 1ch;
}
figcaption, table caption {
text-align: center;
margin-top: var(--line);
}
table {
--half-line: calc(var(--line) / 2 - 1px);
caption-side: bottom;
// margin: var(--line) 0 var(--half-line) 3ch;
margin-top: var(--line);
margin-right: 0;
margin-bottom: var(--half-line);
margin-left: 3ch;
border-collapse: collapse;
}
table.center {
margin-left: auto; /* todo: add 3ch */
margin-right: auto;
}
table caption {
margin-top: calc(var(--half-line) + var(--line));
}
thead, tfoot {
border-top-style: double;
border-bottom-style: double;
}
td, th {
border: 1px solid black;
// padding: var(--half-line) 1ch;
padding-top: var(--half-line);
padding-right: 1ch;
padding-bottom: var(--half-line);
padding-left: 1ch;
}
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
/* Links */
a.selfRef, a.pilcrow {
color: black;
text-decoration: none;
}
a.relref, a.xref {
hyphens: none;
}
a.relref, a.xref.cite {
white-space: nowrap;
}
.pilcrow {
display: inline-block;
margin-right: -1ch;
opacity: 0.01;
text-decoration: none;
}
:hover > .pilcrow {
opacity: 0.2;
}
* > .pilcrow[href]:hover {
opacity: 0.6;
}
/* sup, sub */
sup, sub {
line-height: 1.1;
}
/* Authors */
address {
font-style: normal;
// margin: 2em 0 var(--line) 3ch;
margin-top: 2em;
margin-right: 0;
margin-bottom: var(--line);
margin-left: 3ch;
}
h2 + address {
margin-top: 1em;
}
address .tel, address .email {
// margin: var(--line) 0 0;
margin-top: var(--line);
margin-right: 0;
margin-bottom: 0;
margin-left: 0;
}
address .tel + .email {
margin: 0;
}
}

View file

@ -168,8 +168,9 @@ table tbody.meta {
}
}
// Try and hyphenate table headings
th {
// Try and hyphenate table headings and other things
th,
.hyphenate {
hyphens: auto;
}
@ -208,6 +209,17 @@ th {
max-width: 300px;
}
// Make revision numbers pagination items fixed-width
.revision-list {
.page-item {
width: 2.2rem;
}
.page-item.rfc {
width: 6.6rem;
}
}
// Style the photo cards
.photo {
width: 12em;

View file

@ -3,21 +3,3 @@
@import "bootstrap/scss/mixins";
@import "select2/src/scss/core";
@import "select2-bootstrap-5-theme/src/include-all";
// Propagate readonly property from input to select2 instrumentation, based on
// https://stackoverflow.com/questions/41807096/select2-make-it-readonly-not-disabled-from-js/55001516#55001516
select[readonly].select2-hidden-accessible + .select2-container {
pointer-events: none;
touch-action: none;
.select2-selection {
background: $form-select-disabled-bg;
color: $form-select-disabled-color;
border-color: $form-select-disabled-border-color;
box-shadow: none;
}
.select2-selection__arrow, .select2-selection__clear {
display: none;
}
}

View file

@ -1 +1,76 @@
<svg width="765" height="990" style="display:inline" xmlns="http://www.w3.org/2000/svg"><g style="display:inline"><text xml:space="preserve" style="font-style:normal;font-variant:normal;font-weight:400;font-stretch:normal;line-height:0%;font-family:'Arial Black';-inkscape-font-specification:'Arial Black,';text-align:start;letter-spacing:0;word-spacing:0;writing-mode:lr-tb;text-anchor:start;fill:#fff;fill-opacity:1;stroke:#000;stroke-width:1.908;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" x="250.217" y="565.473" transform="translate(0 -62.362)"><tspan x="250.217" y="565.473" style="font-size:102.5px;line-height:1.25">R</tspan></text><path style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m134.869 273.355-44.033 44.031 44.033 44.034 44.032-44.034-44.032-44.03m519.185-.001-44.032 44.031 44.032 44.034 44.031-44.034-44.03-44.03m-311.778-.001-44.032 44.031 44.032 44.034 44.032-44.034-44.032-44.03m103.693-.001-44.032 44.031 44.032 44.034 44.032-44.034-44.032-44.03m103.693-.001-44.032 44.031 44.032 44.034 44.033-44.034-44.033-44.03M290.8 325.407l-44.032 44.032 44.032 44.032 44.032-44.032-44.032-44.032m-52.217-52.052-44.033 44.031 44.033 44.034 44.033-44.034-44.033-44.03m155.911 52.051-44.032 44.032 44.032 44.032 44.032-44.032-44.032-44.032m103.693 0-44.032 44.032 44.032 44.032 44.032-44.032-44.032-44.032M290.8 221.827l-44.032 44.03 44.032 44.034 44.032-44.033-44.032-44.031m103.694 0-44.032 44.03 44.032 44.034 44.032-44.033-44.032-44.031m103.693 0-44.032 44.03 44.032 44.034 44.032-44.033-44.032-44.031" transform="translate(0 -62.362)"/><path style="fill:#fccf34;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M98.431 312.254h90.633l49.886 49.886 51.52-51.52 51.807 51.807 104.12-104.12 52.06 52.059 51.52-51.52 53.948 53.949H692.4v10.248h-92.79l-49.903-49.901-51.79 51.79-51.79-51.79-103.58 103.581-51.79-51.792-51.52 51.522-54.22-54.22H97.89l.54-9.98" transform="translate(0 -62.362)"/><path style="fill:none;stroke:#0c0a08;stroke-width:1.90799999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" d="M98.431 312.254h90.633l49.886 49.886 51.52-51.52 51.807 51.807 104.12-104.12 52.06 52.059 51.52-51.52 53.948 53.949H692.4v10.248h-92.79l-49.903-49.901-51.79 51.79-51.79-51.79-103.58 103.581-51.79-51.792-51.52 51.522-54.22-54.22H97.89l.54-9.98z" transform="translate(0 -62.362)"/><path style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M79.997 327.492h21.04v-19.421h-21.04v19.421zm606.244 0h21.04v-19.421h-21.04v19.421zM98.468 565.473h25.715v-73.378H98.468v73.378zm341.277-54.979v-18.399h71.55v18.399h-22.919v54.979h-25.714v-54.98h-22.917m200.334-18.398h53.58v15.494h-27.867v13.34h26.145v15.495h-26.145v29.049H640.08v-73.378" transform="translate(0 -62.362)"/><path style="fill:#fff;fill-opacity:1;stroke:#000;stroke-width:1.90800011;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" d="m446.283 169.434-29.062 29.06 29.062 29.062 29.06-29.062-29.06-29.06m-111.179 7.485-29.061 29.06 29.06 29.062 29.062-29.062-29.061-29.06m-148.231 44.032-29.061 29.06 29.061 29.063 29.061-29.062-29.061-29.06m37.408 171.686-29.061 29.06 29.061 29.063 29.061-29.062-29.06-29.06m222.358-14.971-29.061 29.06 29.06 29.062 29.062-29.062-29.061-29.06m155.211-22.201-29.061 29.06 29.061 29.062 29.061-29.062-29.061-29.06M224.145 184.184l-14.531 14.53 14.53 14.531 14.531-14.53-14.53-14.531m52.217-22.027-14.53 14.53 14.53 14.531 14.53-14.531-14.53-14.53m14.751-44.252-14.531 14.53 14.53 14.531 14.531-14.53-14.53-14.531m74.192 29.501-14.531 14.53 14.531 14.53 14.53-14.53-14.53-14.53m118.444-14.751-14.531 14.53 14.53 14.531 14.532-14.53-14.531-14.53m29.501 29.501-14.53 14.53 14.53 14.531 14.53-14.531-14.53-14.53m51.477 22.027-14.531 14.53 14.53 14.531 14.531-14.53-14.53-14.531M201.855 369.897l-14.531 14.53 14.53 14.531 14.531-14.53-14.53-14.531m89.615 59.823-14.531 14.53 14.531 14.53L306 444.25l-14.53-14.53m103.693 0-14.53 14.53 14.53 14.53 14.53-14.53-14.53-14.53m140.42-37.302-14.531 14.53 14.53 14.531 14.531-14.53-14.53-14.53m-36.726 37.301-14.531 14.53 14.53 14.53 14.531-14.53-14.53-14.53m-140.94-37.082-29.06 29.06 29.06 29.063 29.062-29.062-29.062-29.06m243.488-171.688-14.531 14.53 14.53 14.532 14.531-14.531-14.53-14.53m-28.713-73.546-14.531 14.53 14.53 14.53 14.531-14.53-14.53-14.53" transform="translate(0 -62.362)"/></g></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="627.284"
height="449.19662"
style="display:inline"
version="1.1"
id="svg303"
sodipodi:docname="irtf-logo.svg"
inkscape:version="1.2.1 (9c6d41e4, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs307" />
<sodipodi:namedview
id="namedview305"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="1.0373737"
inkscape:cx="304.1334"
inkscape:cy="446.80136"
inkscape:window-width="1797"
inkscape:window-height="1083"
inkscape:window-x="951"
inkscape:window-y="445"
inkscape:window-maximized="0"
inkscape:current-layer="svg303" />
<g
style="display:inline"
id="g301"
transform="translate(-79.997,-54.868397)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:400;font-stretch:normal;line-height:0%;font-family:'Arial Black';-inkscape-font-specification:'Arial Black,';text-align:start;letter-spacing:0;word-spacing:0;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.908;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="250.217"
y="565.47302"
transform="translate(0,-62.362)"
id="text289"><tspan
x="250.217"
y="565.47302"
style="font-size:102.5px;line-height:1.25"
id="tspan287">R</tspan></text>
<path
style="fill:#bdbcbc;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 134.869,273.355 -44.033,44.031 44.033,44.034 44.032,-44.034 -44.032,-44.03 m 519.185,-10e-4 -44.032,44.031 44.032,44.034 44.031,-44.034 -44.03,-44.03 m -311.778,-10e-4 -44.032,44.031 44.032,44.034 44.032,-44.034 -44.032,-44.03 m 103.693,-10e-4 -44.032,44.031 44.032,44.034 44.032,-44.034 -44.032,-44.03 m 103.693,-10e-4 -44.032,44.031 44.032,44.034 44.033,-44.034 -44.033,-44.03 M 290.8,325.407 246.768,369.439 290.8,413.471 334.832,369.439 290.8,325.407 m -52.217,-52.052 -44.033,44.031 44.033,44.034 44.033,-44.034 -44.033,-44.03 m 155.911,52.051 -44.032,44.032 44.032,44.032 44.032,-44.032 -44.032,-44.032 m 103.693,0 -44.032,44.032 44.032,44.032 44.032,-44.032 -44.032,-44.032 M 290.8,221.827 246.768,265.857 290.8,309.891 334.832,265.858 290.8,221.827 m 103.694,0 -44.032,44.03 44.032,44.034 44.032,-44.033 -44.032,-44.031 m 103.693,0 -44.032,44.03 44.032,44.034 44.032,-44.033 -44.032,-44.031"
transform="translate(0,-62.362)"
id="path291" />
<path
style="fill:#fccf34;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 98.431,312.254 h 90.633 l 49.886,49.886 51.52,-51.52 51.807,51.807 104.12,-104.12 52.06,52.059 51.52,-51.52 53.948,53.949 H 692.4 v 10.248 h -92.79 l -49.903,-49.901 -51.79,51.79 -51.79,-51.79 -103.58,103.581 -51.79,-51.792 -51.52,51.522 -54.22,-54.22 H 97.89 l 0.54,-9.98"
transform="translate(0,-62.362)"
id="path293" />
<path
style="fill:none;stroke:#0c0a08;stroke-width:1.908;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 98.431,312.254 h 90.633 l 49.886,49.886 51.52,-51.52 51.807,51.807 104.12,-104.12 52.06,52.059 51.52,-51.52 53.948,53.949 H 692.4 v 10.248 h -92.79 l -49.903,-49.901 -51.79,51.79 -51.79,-51.79 -103.58,103.581 -51.79,-51.792 -51.52,51.522 -54.22,-54.22 H 97.89 l 0.54,-9.98 z"
transform="translate(0,-62.362)"
id="path295" />
<path
style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 79.997,327.492 h 21.04 v -19.421 h -21.04 z m 606.244,0 h 21.04 v -19.421 h -21.04 z M 98.468,565.473 h 25.715 V 492.095 H 98.468 Z m 341.277,-54.979 v -18.399 h 71.55 v 18.399 h -22.919 v 54.979 h -25.714 v -54.98 h -22.917 m 200.334,-18.398 h 53.58 v 15.494 h -27.867 v 13.34 h 26.145 v 15.495 h -26.145 v 29.049 H 640.08 v -73.378"
transform="translate(0,-62.362)"
id="path297" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.908;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 446.283,169.434 -29.062,29.06 29.062,29.062 29.06,-29.062 -29.06,-29.06 m -111.179,7.485 -29.061,29.06 29.06,29.062 29.062,-29.062 -29.061,-29.06 m -148.231,44.032 -29.061,29.06 29.061,29.063 29.061,-29.062 -29.061,-29.06 m 37.408,171.686 -29.061,29.06 29.061,29.063 29.061,-29.062 -29.06,-29.06 m 222.358,-14.971 -29.061,29.06 29.06,29.062 29.062,-29.062 -29.061,-29.06 m 155.211,-22.201 -29.061,29.06 29.061,29.062 29.061,-29.062 -29.061,-29.06 m -377.706,-171.283 -14.531,14.53 14.53,14.531 14.531,-14.53 -14.53,-14.531 m 52.217,-22.027 -14.53,14.53 14.53,14.531 14.53,-14.531 -14.53,-14.53 m 14.751,-44.252 -14.531,14.53 14.53,14.531 14.531,-14.53 -14.53,-14.531 m 74.192,29.501 -14.531,14.53 14.531,14.53 14.53,-14.53 -14.53,-14.53 m 118.444,-14.751 -14.531,14.53 14.53,14.531 14.532,-14.53 -14.531,-14.53 m 29.501,29.501 -14.53,14.53 14.53,14.531 14.53,-14.531 -14.53,-14.53 m 51.477,22.027 -14.531,14.53 14.53,14.531 14.531,-14.53 -14.53,-14.531 m -362.872,185.713 -14.531,14.53 14.53,14.531 14.531,-14.53 -14.53,-14.531 m 89.615,59.823 -14.531,14.53 14.531,14.53 14.53,-14.53 -14.53,-14.53 m 103.693,0 -14.53,14.53 14.53,14.53 14.53,-14.53 -14.53,-14.53 m 140.42,-37.302 -14.531,14.53 14.53,14.531 14.531,-14.53 -14.53,-14.53 m -36.726,37.301 -14.531,14.53 14.53,14.53 14.531,-14.53 -14.53,-14.53 m -140.94,-37.082 -29.06,29.06 29.06,29.063 29.062,-29.062 -29.062,-29.06 m 243.488,-171.688 -14.531,14.53 14.53,14.532 14.531,-14.531 -14.53,-14.53 m -28.713,-73.546 -14.531,14.53 14.53,14.53 14.531,-14.53 -14.53,-14.53"
transform="translate(0,-62.362)"
id="path299" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -0,0 +1,71 @@
import {
Tooltip as Tooltip,
// Button as Button,
// Collapse as Collapse,
// ScrollSpy as ScrollSpy,
Tab as Tab
} from "bootstrap";
import Cookies from "js-cookie";
import { populate_nav } from "./nav.js";
const cookies = Cookies.withAttributes({ sameSite: "strict" });
document.addEventListener("DOMContentLoaded", function (event) {
// handle point size slider
const cookie = "doc-ptsize-max";
function change_ptsize(ptsize) {
document.documentElement.style.setProperty(`--${cookie}`,
`${ptsize}pt`);
cookies.set(cookie, ptsize);
}
document.getElementById("ptsize")
.oninput = function () { change_ptsize(this.value) };
const ptsize = cookies.get(cookie);
change_ptsize(ptsize ? Math.min(Math.max(7, ptsize), 16) : 12);
// Use the Bootstrap tooltip plugin for all elements with a title attribute
const tt_triggers = document.querySelectorAll(
"[title]:not([title=''])");
[...tt_triggers].map(tt_el => {
const tooltip = Tooltip.getOrCreateInstance(tt_el);
tt_el.addEventListener("click", el => {
tooltip.hide();
tt_el.blur();
});
});
// Set up a nav pane
const toc_pane = document.getElementById("toc-nav");
populate_nav(toc_pane,
`#content h2, #content h3, #content h4, #content h5, #content h6
#content .h1, #content .h2, #content .h3, #content .h4, #content .h5, #content .h6`,
["py-0"]);
// activate pref buttons selected by pref cookies
document.querySelectorAll(".btn-check")
.forEach(btn => {
const id = btn.id.replace("-radio", "");
if (cookies.get(btn.name) == id) {
btn.checked = true;
}
btn.addEventListener("click", el => {
cookies.set(btn.name, id);
window.location.reload();
});
});
// activate tab selected in prefs
let defpane;
try {
defpane = Tab.getOrCreateInstance(
`#${cookies.get("deftab")}-tab`);
} catch (err) {
defpane = Tab.getOrCreateInstance("#docinfo-tab");
};
defpane.show();
document.activeElement.blur();
});

View file

@ -315,7 +315,7 @@ function draw_graph(data, group) {
})
];
// // See https://github.com/d3/d3-force/blob/master/README.md#simulation_tick
// // See https://github.com/d3/d3-force/blob/main/README.md#simulation_tick
// for (let i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) /
// Math.log(1 - simulation.alphaDecay())); i <
// n; ++i) {

View file

@ -24,7 +24,7 @@ if (!process.env.BUILD_DEPLOY) {
import Cookies from "js-cookie";
import debounce from "lodash/debounce";
import { populate_nav } from "./nav.js";
// setup CSRF protection using jQuery
function csrfSafeMethod(method) {
@ -154,9 +154,9 @@ $(document)
$(function () {
const contentElement = $('#content.ietf-auto-nav');
if (contentElement.length > 0) {
const heading_selector = "h2:not([style='display:none']):not(.navskip), h3:not([style='display:none']):not(.navskip), h4:not([style='display:none']):not(.navskip), h5:not([style='display:none']):not(.navskip), h6:not([style='display:none']):not(.navskip), .nav-heading:not([style='display:none']):not(.navskip)";
const headings = contentElement
.find("h1:visible, h2:visible, h3:visible, h4:visible, h5:visible, h6:visible, .nav-heading:visible")
.not(".navskip");
.find(heading_selector);
const contents = (headings.length > 0) &&
($(headings)
@ -178,8 +178,6 @@ $(function () {
if (pageTooTall || haveExtraNav) {
// console.log("Enabling nav.");
let n = 0;
let last_level;
contentElement
.attr("data-bs-offset", 0)
@ -197,48 +195,7 @@ $(function () {
.children()
.last();
contentElement
.find("h1:visible, h2:visible, h3:visible, h4:visible, h5:visible, h6:visible, .nav-heading:visible")
.not(".navskip")
.each(function () {
// Some headings have line breaks in them - only use first line in that case.
const frag = $(this)
.html()
.split("<br")
.shift();
const text = $.parseHTML(frag)
.map(x => $(x)
.text())
.join(" ");
if (text === undefined || text === "") {
// Nothing to do for empty headings.
return;
}
let id = $(this)
.attr("id");
if (id === undefined) {
id = `autoid-${++n}`;
$(this)
.attr("id", id);
}
const level = parseInt(this.nodeName.substring(1)) - 1;
if (!last_level) {
last_level = level;
}
if (level > last_level) {
last_level = level;
} else
while (level < last_level) {
last_level--;
}
$(nav)
.append(`<a class="nav-link" href="#${id}">${text}</a>`);
});
populate_nav(nav[0], heading_selector);
if (haveExtraNav) {
$('#righthand-panel').append('<div id="righthand-extra" class="w-100 py-3"></div>');
@ -246,18 +203,6 @@ $(function () {
extraNav.remove();
}
$(document)
// Chrome apparently wants this debounced to something >10ms,
// otherwise the main view doesn't scroll?
.on("scroll", debounce(function () {
const item = $('#righthand-nav')
.find(".active")
.last();
if (item.length) {
item[0].scrollIntoView({ block: "center", behavior: "smooth" });
}
}, 100));
// offset the scrollspy to account for the menu bar
const contentOffset = contentElement ? contentElement.offset().top : 0;

99
ietf/static/js/nav.js Normal file
View file

@ -0,0 +1,99 @@
import debounce from "lodash/debounce";
function make_nav() {
const nav = document.createElement("nav");
nav.classList.add("nav-pills", "ps-3", "flex-column");
return nav;
}
function get_level(el) {
let h;
if (el.tagName.match(/^h\d/i)) {
h = el.tagName
} else {
el.classList.forEach(cl => {
if (cl.match(/^h\d/i)) {
h = cl;
return;
}
});
}
return h.charAt(h.length - 1);
}
export function populate_nav(nav, heading_selector, classes) {
// Extract section headings from document
const headings = document.querySelectorAll(heading_selector);
const min_level = Math.min(...Array.from(headings)
.map(get_level));
let nav_stack = [nav];
let cur_level = 0;
let n = 0;
headings.forEach(el => {
const level = get_level(el) - min_level;
if (level < cur_level) {
while (level < cur_level) {
let nav = nav_stack.pop();
cur_level--;
nav_stack[level].appendChild(nav);
}
} else {
while (level > cur_level) {
nav_stack.push(make_nav());
cur_level++;
}
}
const link = document.createElement("a");
link.classList.add("nav-link", "ps-1", "d-flex", "hyphenate",
classes);
if (!el.id) {
el.id = `autoid-${++n}`;
}
link.href = `#${el.id}`;
const words = el.innerText.split(/\s+/);
let nr = "";
if (words[0].includes(".")) {
nr = words.shift();
} else if (words.length > 1 && words[1].includes(".")) {
nr = words.shift() + " " + words.shift();
nr = nr.replace(/\s*Appendix\s*/, "");
}
if (nr) {
const number = document.createElement("div");
number.classList.add("pe-1");
number.textContent = nr;
link.appendChild(number);
}
const text = document.createElement("div");
text.classList.add("text-break");
text.textContent = words.join(" ");
link.appendChild(text);
nav_stack[level].appendChild(link);
});
for (var i = nav_stack.length - 1; i > 0; i--) {
nav_stack[i - 1].appendChild(nav_stack[i]);
}
// Chrome apparently wants this debounced to something >10ms,
// otherwise the main view doesn't scroll?
document.addEventListener("scroll", debounce(function () {
const items = nav.querySelectorAll(".active");
const item = [...items].pop();
if (item) {
item.scrollIntoView({
block: "center",
behavior: "smooth"
});
}
}, 100));
}

View file

@ -345,7 +345,7 @@ def document_stats(request, stats_type=None):
basename, ext = t
ext = ext.lower()
if not any(ext==whitelisted_ext for whitelisted_ext in settings.DOCUMENT_FORMAT_WHITELIST):
if not any(ext==allowlisted_ext for allowlisted_ext in settings.DOCUMENT_FORMAT_ALLOWLIST):
continue
canonical_name = doc_names_with_missing_types.get(basename)

View file

@ -338,7 +338,7 @@ class SubmissionBaseUploadForm(forms.Form):
if group:
return group
else:
raise forms.ValidationError('Draft names starting with draft-%s- are restricted, please pick a differen name' % ntype)
raise forms.ValidationError('Draft names starting with draft-%s- are restricted, please pick a different name' % ntype)
return None

View file

@ -2,7 +2,7 @@
Network Working Group %(initials)s %(surname)s
Network Working Group %(firstpagename)37s
Internet-Draft Test Centre Inc.
Intended status: Informational %(month)s %(year)s
Expires: %(expiration)s
@ -10,7 +10,6 @@ Expires: %(expiration)s
%(title)s
%(name)s
Abstract
This document describes how to test tests.

View file

@ -107,6 +107,10 @@ def submission_file(name_in_doc, name_in_post, group, templatename, author=None,
if year is None:
year = _today.strftime("%Y")
# extract_authors() cuts the author line off at the first space past 80 characters
# very long factory-generated names can hence be truncated, causing a failure
# ietf/submit/test_submission.txt was changed so that 37-character names and shorter will work
# this may need further adjustment if longer names still cause failures
submission_text = template % dict(
date=_today.strftime("%d %B %Y"),
expiration=(_today + datetime.timedelta(days=100)).strftime("%d %B, %Y"),
@ -119,6 +123,7 @@ def submission_file(name_in_doc, name_in_post, group, templatename, author=None,
asciiAuthor=author.ascii,
initials=author.initials(),
surname=author.ascii_parts()[3] if ascii else author.name_parts()[3],
firstpagename=f"{author.initials()} {author.ascii_parts()[3] if ascii else author.name_parts()[3]}",
asciiSurname=author.ascii_parts()[3],
email=email,
title=title,

View file

@ -567,7 +567,7 @@ def post_approved_draft(url, name):
"Authorization": "Basic %s" % force_str(base64.encodebytes(smart_bytes("%s:%s" % (username, password)))).replace("\n", ""),
}
log("Posting RFC-Editor notifcation of approved draft '%s' to '%s'" % (name, url))
log("Posting RFC-Editor notification of approved draft '%s' to '%s'" % (name, url))
text = error = ""
try:

View file

@ -186,8 +186,8 @@
</li>
<li>
<a class="dropdown-item {% if flavor != 'top' %} text-wrap link-primary{% endif %}"
href="{% url 'ietf.ietfauth.views.add_account_whitelist' %}">
Account whitelist
href="{% url 'ietf.ietfauth.views.add_account_allowlist' %}">
Account allowlist
</a>
</li>
{% endif %}

View file

@ -2,7 +2,7 @@
{% load origin %}
{% load static %}
{% origin %}
{% if debug %}
{% if settings.DEBUG %}
{% if sql_debug %}
{% load debug_filters %}
{{ sql_queries|length }} queries ({{ sql_queries|timesum }}s)

View file

@ -26,7 +26,7 @@
{% block content %}
{% origin %}
{{ top|safe }}
{% include "doc/revisions_list.html" %}
{% include "doc/revisions_list.html" with document_html=document_html %}
<div id="timeline"></div>
{% if doc.rev != latest_rev %}
<div class="alert alert-warning my-3">The information below is for an old version of the document.</div>
@ -38,363 +38,7 @@
{% endif %}
{% 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>
{% if doc.get_state_slug == "rfc" and not snapshot %}
<span class="text-success">RFC - {{ doc.std_level }}</span>
{% if published %}
({{ doc.pub_date|date:"F Y" }})
{% else %}
(Publication date unknown)
{% endif %}
{% if has_verified_errata %}
<a class="badge rounded-pill bg-danger text-decoration-none text-light"
href="https://www.rfc-editor.org/errata_search.php?rfc={{ doc.rfc_number }}" title="Click to view errata." rel="nofollow">
Errata
</a>
{% elif has_errata %}
<a class="badge rounded-pill bg-warning text-decoration-none text-light"
href="https://www.rfc-editor.org/errata_search.php?rfc={{ doc.rfc_number }}" title="Click to view errata." rel="nofollow">
Errata
</a>
{% endif %}
{% if obsoleted_by %}<div>Obsoleted by {{ obsoleted_by|urlize_related_source_list|join:", " }}</div>{% endif %}
{% if updated_by %}<div>Updated by {{ updated_by|urlize_related_source_list|join:", " }}</div>{% endif %}
{% if obsoletes %}<div>Obsoletes {{ obsoletes|urlize_related_target_list|join:", " }}</div>{% endif %}
{% if updates %}<div>Updates {{ updates|urlize_related_target_list|join:", " }}</div>{% endif %}
{% if status_changes %}
<div>Status changed by {{ status_changes|urlize_related_source_list|join:", " }}</div>
{% endif %}
{% if proposed_status_changes %}
<div>Proposed status changed by {{ proposed_status_changes|urlize_related_source_list|join:", " }}</div>
{% endif %}
{% if rfc_aliases %}<div>Also known as {{ rfc_aliases|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if draft_name %}
<div>
Was
<a href="{% url 'ietf.doc.views_doc.document_main' name=draft_name %}">{{ draft_name }}</a>
{% if submission %}({{ submission|safe }}){% endif %}
</div>
{% endif %}
{% else %}
{% if snapshot and doc.doc.get_state_slug == 'rfc' %}
<span>This is an older version of an Internet-Draft that was ultimately published as an RFC.</span>
{% elif snapshot and doc.rev != latest_rev %}
<span>This is an older version of an Internet-Draft whose latest revision is {{ doc.doc.get_state }}</span>
{% else %}
<span class="{% if doc.get_state_slug == 'active' %}text-success{% elif doc.get_state_slug == 'expired' %}text-danger{% endif %}">{% if snapshot and doc.rev == latest_rev %}{{ doc.doc.get_state }}{% else %}{{ doc.get_state }}{% endif %} Internet-Draft</span>
{% if submission %}({{ submission|safe }}){% endif %}
{% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %}
{% endif %}
{% endif %}
</td>
</tr>
<tr>
<td></td>
<th scope="row">Author{{ doc.authors|pluralize }}</th>
<td class="edit">
{% if can_edit_authors %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_doc.edit_authors' name=doc.name %}">Edit</a>
{% endif %}
</td>
<td>
{# Implementation that uses the current primary email for each author #}
{% for author in doc.authors %}
{% person_link author %}{% if not forloop.last %},{% endif %}
{% endfor %}
</td>
</tr>
<tr>
<td></td>
<th scope="row">Last updated</th>
<td class="edit"></td>
<td>
{{ doc.time|date:"Y-m-d" }}
{% if latest_revision and latest_revision.time|date:"Y-m-d" != doc.time|date:"Y-m-d" %}
<span class="text-muted">(Latest revision {{ latest_revision.time|date:"Y-m-d" }})</span>
{% endif %}
</td>
</tr>
{% if replaces or can_edit_stream_info %}
<tr>
<td></td>
<th scope="row">Replaces</th>
<td class="edit">
{% if can_edit_stream_info and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.replaces' name=doc.name %}">Edit</a>
{% endif %}
</td>
<td>
{% if replaces %}
{{ replaces|urlize_related_target_list|join:", " }}
{% else %}
<span class="text-muted">(None)</span>
{% endif %}
</td>
</tr>
{% endif %}
{% if replaced_by %}
<tr>
<td></td>
<th scope="row">
Replaced by
</th>
<td class="edit">
</td>
<td>
{{ replaced_by|urlize_related_source_list|join:", " }}
</td>
</tr>
{% endif %}
{% if can_view_possibly_replaces %}
{% if possibly_replaces %}
<tr>
<td></td>
<th scope="row">
Possibly Replaces
</th>
<td class="edit">
{% if can_edit_replaces and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">
Edit
</a>
{% endif %}
</td>
<td>
{{ possibly_replaces|urlize_related_target_list|join:", " }}
</td>
</tr>
{% endif %}
{% if possibly_replaced_by %}
<tr>
<td></td>
<th scope="row">
Possibly Replaced By
</th>
<td class="edit">
{% if can_edit_replaces and not snapshot %}
{% comment %}<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">Edit</a>{% endcomment %}
{% endif %}
</td>
<td>
{{ possibly_replaced_by|urlize_related_source_list|join:", " }}
</td>
</tr>
{% endif %}
{% endif %}
<tr>
<td></td>
<th scope="row">
Stream
</th>
<td class="edit">
{% if can_change_stream and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.change_stream' name=doc.name %}">
Edit
</a>
{% endif %}
</td>
<td {% if stream_desc == "(None)" %}class="text-muted"{%endif%}>
{{ stream_desc }}
</td>
</tr>
{% if doc.get_state_slug != "rfc" and not snapshot %}
<tr>
<td></td>
<th scope="row">
Intended RFC status
</th>
<td class="edit">
{% if can_edit_stream_info and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.change_intention' name=doc.name %}">
Edit
</a>
{% endif %}
</td>
<td>
{% if doc.intended_std_level %}
{{ doc.intended_std_level }}
{% else %}
<span class="text-muted">
(None)
</span>
{% endif %}
</td>
</tr>
{% endif %}
<tr>
<td></td>
<th scope="row">
Formats
</th>
<td class="edit">
</td>
<td>
{% if doc.get_state_slug != "active" and doc.get_state_slug != "rfc" %}
<div class="badge rounded-pill bg-warning float-end">
Expired &amp; archived
</div>
{% endif %}
{% include "doc/document_format_buttons.html" %}
</td>
</tr>
{% for check in doc.submission.latest_checks %}
{% if check.passed != None and check.symbol.strip %}
<tr>
<td></td>
<th scope="row">
{{ check.checker|title }}
</th>
<td class="edit">
</td>
<td>
{% if check.errors or check.warnings %}
<span class="checker-warning"
data-bs-toggle="modal"
data-bs-target="#check-{{ check.pk }}"
title="{{ check.checker|title }} returned warnings or errors.">
{{ check.symbol|safe }}
</span>
{% else %}
<span class="checker-success"
data-bs-toggle="modal"
data-bs-target="#check-{{ check.pk }}"
title="{{ check.checker|title }} passed">
{{ check.symbol|safe }}
</span>
{% endif %}
<a href="#"
data-bs-toggle="modal"
data-bs-target="#check-{{ check.pk }}">
{{ check.errors }} errors, {{ check.warnings }} warnings
</a>
{% include "doc/yang-check-modal-overlay.html" %}
</td>
</tr>
{% endif %}
{% endfor %}
{% if review_assignments or can_request_review %}
<tr>
<td></td>
<th scope="row">
Reviews
</th>
<td class="edit">
</td>
<td>
{% for review_assignment in review_assignments %}
{% include "doc/review_assignment_summary.html" with current_doc_name=doc.name current_rev=doc.rev %}
{% endfor %}
{% if no_review_from_teams %}
{% for team in no_review_from_teams %}
{{ team.acronym.upper }}{% if not forloop.last %},{% endif %}
{% endfor %}
will not review this version
{% endif %}
{% if can_request_review or can_submit_unsolicited_review_for_teams %}
<div {% if review_assignments or no_review_from_teams %} class="mt-3"{% endif %}>
{% if can_request_review %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.request_review" doc.name %}">
<i class="bi bi-check-circle">
</i>
Request review
</a>
{% endif %}
{% if can_submit_unsolicited_review_for_teams|length == 1 %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.complete_review" doc.name can_submit_unsolicited_review_for_teams.0.acronym %}">
<i class="bi bi-pencil-square">
</i>
Submit unsolicited review
</a>
{% elif can_submit_unsolicited_review_for_teams %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.submit_unsolicited_review_choose_team" doc.name %}">
<i class="bi bi-pencil-square">
</i>
Submit unsolicited review
</a>
{% endif %}
</div>
{% endif %}
</td>
</tr>
{% endif %}
{% if conflict_reviews %}
<tr>
<td></td>
<th scope="row">
IETF conflict review
</th>
<td class="edit">
</td>
<td>
{{ conflict_reviews|join:", "|urlize_ietf_docs }}
</td>
</tr>
{% endif %}
{% with doc.docextresource_set.all as resources %}
{% if resources or can_edit_stream_info or can_edit_individual %}
<tr>
<td>
</td>
<th scope="row">
Additional resources
</th>
<td class="edit">
{% if can_edit_stream_info or can_edit_individual %}
<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 or doc.group and doc.group.list_archive %}
{% 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 %}
{% if doc.group and doc.group.list_archive %}
{% if doc.group.list_archive|startswith:settings.MAILING_LIST_ARCHIVE_URL %}
<a href="{{ doc.group.list_archive }}?q={{ doc.name }}">
Mailing list discussion
</a>
{% elif doc.group.list_archive|is_valid_url %}
<a href="{{ doc.group.list_archive }}">
Mailing list discussion
</a>
{% else %}
{{ doc.group.list_archive|urlencode }}
{% endif %}
{% endif %}
{% endif %}
</td>
</tr>
{% endif %}
{% endwith %}
</tbody>
{% include "doc/document_info.html" %}
<tbody class="meta border-top">
<tr>
<th scope="row">

View file

@ -1,7 +1,18 @@
{% if file_urls %}
<div class="buttonlist">
{% for label, url in file_urls %}
<a class="btn btn-primary btn-sm" href="{{ url }}">
{% if label != skip_format %}
<a class="btn btn-primary btn-sm"
{% if label == 'pdf' or label == 'pdfized' %}
download="{% if not snapshot and doc.get_state_slug == 'rfc' %}rfc{{ doc.rfc_number }}{% else %}{{ doc.name }}-{{ doc.rev }}{% endif %}.pdf"
{% comment %}
TODO: determine if we want bibtex to jiust download
{% elif label == 'bibtex' %}
download="{% if not snapshot and doc.get_state_slug == 'rfc' %}rfc{{ doc.rfc_number }}{% else %}{{ doc.name }}-{{ doc.rev }}{% endif %}.bib"
{% endcomment %}
{% endif %}
{% if label != 'htmlized' %}target="_blank"{% endif %}
href="{{ url }}">
{% if label == 'pdf' or label == 'pdfized' %}
<i class="bi bi-file-pdf"></i> pdf
{% elif label == 'xml' or label == 'html' %}
@ -16,6 +27,7 @@
<i class="bi bi-file-diff"></i> w/errata
{% endif %}
</a>
{% endif %}
{% endfor %}
</div>
{% else %}

View file

@ -1,92 +1,276 @@
{% extends "doc/htmlized_base.html" %}
{# Copyright The IETF Trust 2016, All Rights Reserved #}
<!DOCTYPE html>
{% load analytical %}
{% load origin %}
{% load static %}
{% load ietf_filters %}
{% block pagehead %}
{% load ietf_filters textfilters %}
{% origin %}
<html lang="en">
<head>
{% analytical_head_top %}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>
{% if not snapshot and doc.get_state_slug == "rfc" %}
RFC {{ doc.rfc_number }} - {{ doc.title }}
{% else %}
{{ doc.name }}-{{ doc.rev }}
{% endif %}
</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
{% if request.COOKIES.pagedeps == 'inline' %}
<script>{{ js|safe }}</script>
<style>{{ css|safe }}</style>
{% else %}
<link rel="stylesheet" href="{% static 'ietf/css/document_html_referenced.css' %}">
{% if html %}
<link rel="stylesheet" href="{% static 'ietf/css/document_html_txt.css' %}">
{% endif %}
<script src="{% static 'ietf/js/document_html.js' %}"></script>
{% endif %}
<link rel="alternate"
type="application/atom+xml"
title="Document changes"
href="/feed/document-changes/{{ doc.name }}/">
<meta name="description"
{% if doc.get_state_slug == "rfc" %}
content="{{ doc.title }} (RFC {{ doc.rfc_number }}{% if published %}, {{ published.time|date:"F Y" }}{% endif %}{% if obsoleted_by %}; obsoleted by {{ obsoleted_by|join:", " }}{% endif %})"
{% if not snapshot and doc.get_state_slug == 'rfc' %}
content="{{ doc.title }} (RFC {{ doc.rfc_number }}{% if published %}, {{ published.time|date:'F Y' }}{% endif %}{% if obsoleted_by %}; obsoleted by {{ obsoleted_by|join:', ' }}{% endif %}"
{% else %}
content="{{ doc.title }} (Internet-Draft, {{ doc.time|date:"Y" }})"
content="{{ doc.title }} (Internet-Draft, {{ doc.time|date:'Y' }})"
{% endif %}>
{% endblock %}
{% block morecss %}
.bgwhite { background-color: white; }
.bgred { background-color: #F44; }
.bggrey { background-color: #666; }
.bgbrown { background-color: #840; }
.bgorange { background-color: #FA0; }
.bgyellow { background-color: #EE0; }
.bgmagenta{ background-color: #F4F; }
.bgblue { background-color: #66F; }
.bgcyan { background-color: #4DD; }
.bggreen { background-color: #4F4; }
.draftcontent { margin-top:1em;}
{% endblock %}
{% block title %}
{% if doc.get_state_slug == "rfc" %}
RFC {{ doc.rfc_number }} - {{ doc.title }}
{% include "base/icons.html" %}
{% include "doc/opengraph.html" %}
{% analytical_head_bottom %}
</head>
<body>
{% analytical_body_top %}
<button class="btn btn-outline-secondary position-fixed top-0 end-0 m-2 sidebar-toggle"
type="button"
id="sidebar-on"
data-bs-toggle="collapse"
data-bs-target="#sidebar"
aria-expanded="{% if request.COOKIES.sidebar != 'off'%}true{% else %}false{% endif %}"
aria-controls="sidebar"
aria-label="Show metadata sidebar"
title="Show metadata sidebar">
<i class="bi bi-layout-sidebar-reverse"></i>
</button>
<nav class="navbar navbar-light bg-light px-1 fixed-top d-print-none d-md-none">
<a class="nav-link ps-1"
href="{% url 'ietf.doc.views_doc.document_main' name=doc.canonical_name %}">
{% if not snapshot and doc.get_state_slug == "rfc" %}
RFC {{ doc.rfc_number }}
{% else %}
{{ doc.name }}-{{ doc.rev }}
{% endif %}
{% endblock %}
{% block content %}
<!-- [html-validate-disable-block no-inline-style, attr-quotes, void-style -- FIXME: it's everywhere in this old code] -->
{% origin %}
<br class="d-sm-none">
<span class="ms-sm-3 badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}draft{% endif %}">
{% if not snapshot %}
{{ doc.std_level }}
{% else %}
Internet-Draft
{% endif %}
</span>
</a>
<button class="navbar-toggler p-1"
type="button"
data-bs-toggle="collapse"
data-bs-target="#docinfo-collapse"
aria-controls="docinfo-collapse"
aria-expanded="false"
aria-label="Show document information">
<span class="navbar-toggler-icon small"></span>
</button>
<div class="navbar-nav navbar-nav-scroll overscroll-none collapse pt-1" id="docinfo-collapse">
<div class="bg-light p-0">
<table class="table table-sm table-borderless small">
<tbody class="meta align-top">
<tr>
<th scope="row"></th>
<th scope="row">Title</th>
<td class="edit"></td>
<td>{{ doc.title }}</td>
</tr>
</tbody>
{% include "doc/document_info.html" with sidebar=False %}
</table>
</div>
</div>
</nav>
<div class="row g-0">
<div class="col d-flex justify-content-center lh-sm"
data-bs-spy="scroll"
data-bs-target="#toc-nav"
data-bs-smooth-scroll="true"
tabindex="0"
id="content">
{% if html and request.COOKIES.htmlconf != 'txt' %}
<div class="rfchtml">
<br class="noprint">
{{ html|safe }}
</div>
{% else %}
<div class="rfcmarkup">
<div class="noprint" style="height: .5em;">
<div onmouseover="this.style.cursor='pointer';"
onclick="showLegend();"
onmouseout="hideLegend()"
style="height: .5em; min-height: .5em; width: 96ex; "
class="meta-info {{ doccolor }}"
title="Click for color legend.">
</div>
<div id="legend"
class="meta-info noprint pre legend"
style="position:absolute; top: 4px; left: 4ex; visibility:hidden; background-color: white; padding: 4px 9px 5px 7px; border: solid #345 1px; "
onmouseover="showLegend();"
onmouseout="hideLegend();">
</div>
</div>
{% if doc.meta %}
<div class="noprint">
<pre class="pre meta-info">{{ doc.supermeta|safe }}
{{ doc.meta|safe }}</pre>
<br class="noprint">
<!-- [html-validate-disable-block attr-quotes, void-style, element-permitted-content, heading-level -- FIXME: rfcmarkup/rfc2html generates HTML with issues] -->
{{ doc.htmlized|default:"Generation of htmlized text failed"|linkify|safe }}
</div>
{% endif %}
<div class="draftcontent">{{ doc.htmlized|default:"Generation of htmlized text failed"|safe }}</div>
</div>
{% endblock %}
{% block js %}
<script>
var legend_html = 'Color legend:<br> \
<table> \
<tr><td>Unknown:</td> <td><span class="bgwhite">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
<tr><td>Draft:</td> <td><span class="bgred">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
<tr><td>Informational:</td> <td><span class="bgorange">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
<tr><td>Experimental:</td> <td><span class="bgyellow">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
<tr><td>Best Common Practice:</td> <td><span class="bgmagenta">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
<tr><td>Proposed Standard:</td> <td><span class="bgblue">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
<tr><td>Draft Standard (old designation):</td> <td><span class="bgcyan">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
<tr><td>Internet Standard:</td> <td><span class="bggreen">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
<tr><td>Historic:</td> <td><span class="bggrey">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
<tr><td>Obsolete:</td> <td><span class="bgbrown">&nbsp;&nbsp;&nbsp;&nbsp;</span></td></tr> \
</table>';
function showLegend() {
var elem = document.getElementById('legend');
elem.innerHTML = legend_html
elem.style.visibility='visible';
}
function hideLegend() {
var elem = document.getElementById('legend');
elem.style.visibility='hidden';
elem.innerHTML = "";
}
</script>
{% endblock %}
<div class="d-none d-md-block d-print-none col-3 bg-light collapse{% if request.COOKIES.sidebar != 'off'%} show{% endif %}" id="sidebar">
<div class="position-fixed col-3 border-start sidebar overflow-scroll overscroll-none no-scrollbar">
<button class="btn btn-outline-secondary float-end m-2"
type="button"
id="sidebar-off"
data-bs-toggle="collapse"
data-bs-target="#sidebar"
aria-expanded="{% if request.COOKIES.sidebar != 'off'%}true{% else %}false{% endif %}"
aria-controls="sidebar"
aria-label="Hide metadata sidebar"
title="Hide metadata sidebar">
<i class="bi bi-arrow-bar-up"></i>
</button>
<div class="pt-2 pt-lg-3 px-md-2 px-lg-3">
<p>
<a href="{% url 'ietf.doc.views_doc.document_main' name=doc.canonical_name %}">
{% if not snapshot and doc.get_state_slug == "rfc" %}
RFC {{ doc.rfc_number }}
{% else %}
{{ doc.name }}-{{ doc.rev }}
{% endif %}
<br>
<span class="badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}draft{% endif %}">
{% if not snapshot %}
{{ doc.std_level }}
{% else %}
Internet-Draft
{% endif %}
</span>
</a>
</p>
{% if request.COOKIES.htmlconf != 'html' and html %}
<div class="alert alert-info small">
You are viewing the legacy <code><a class="text-decoration-none text-reset" href="https://github.com/ietf-tools/rfc2html">rfc2html</a></code>
rendering of this document. Change the
preferences for a modern <code><a class="text-decoration-none text-reset" href="https://github.com/ietf-tools/xml2rfc">xml2rfc</a></code>-based
HTMLization.
</div>
{% elif request.COOKIES.htmlconf == 'html' and not html %}
<div class="alert alert-info small">
You are viewing the legacy <code><a class="text-decoration-none text-reset" href="https://github.com/ietf-tools/rfc2html">rfc2html</a></code>
rendering, because no <code><a class="text-decoration-none text-reset" href="https://github.com/ietf-tools/xml2rfc">xml2rfc</a></code>-generated
HTML is available for this document.
</div>
{% endif %}
<ul class="nav nav-tabs nav-fill small" role="tablist">
<li class="nav-item" role="presentation" title="Document information">
<button class="nav-link px-2"
id="docinfo-tab"
data-bs-toggle="tab"
data-bs-target="#docinfo-tab-pane"
type="button"
role="tab"
aria-controls="docinfo-tab-pane"
aria-selected="true">
<i class="bi bi-info-circle"></i><span class="d-none d-md-block d-xl-inline ms-xl-1">Info</span>
</button>
</li>
<li class="nav-item" role="presentation" title="Table of contents">
<button class="nav-link px-2"
id="toc-tab"
data-bs-toggle="tab"
data-bs-target="#toc-tab-pane"
type="button"
role="tab"
aria-controls="toc-tab-pane"
aria-selected="false">
<i class="bi bi-list-ol"></i><span class="d-none d-md-block d-xl-inline ms-xl-1">Contents</span>
</button>
</li>
<li class="nav-item" role="presentation" title="Preferences">
<button class="nav-link px-2"
id="pref-tab"
data-bs-toggle="tab"
data-bs-target="#pref-tab-pane"
type="button"
role="tab"
aria-controls="pref-tab-pane"
aria-selected="false">
<i class="bi bi-gear"></i><span class="d-none d-md-block d-xl-inline ms-xl-1">Prefs</span>
</button>
</li>
</ul>
<div class="tab-content pt-2">
<div class="tab-pane"
id="docinfo-tab-pane"
role="tabpanel"
aria-labelledby="docinfo-tab"
tabindex="0">
<table class="table table-sm table-borderless">
{% include "doc/document_info.html" with sidebar=True %}
</table>
</div>
<div class="tab-pane mb-5"
id="toc-tab-pane"
role="tabpanel"
aria-labelledby="toc-tab"
tabindex="0">
<nav class="nav nav-pills flex-column small" id="toc-nav">
</nav>
</div>
<div class="tab-pane mb-5 small"
id="pref-tab-pane"
role="tabpanel"
aria-labelledby="pref-tab"
tabindex="0">
<label class="form-label fw-bold mb-2">Show sidebar by default</label>
<div class="btn-group-vertical btn-group-sm d-flex" role="group">
<input type="radio" class="btn-check" name="sidebar" id="on-radio">
<label class="btn btn-outline-primary" for="on-radio">Yes</label>
<input type="radio" class="btn-check" name="sidebar" id="off-radio">
<label class="btn btn-outline-primary" for="off-radio">No</label>
</div>
<label class="form-label fw-bold mt-4 mb-2">Tab to show by default</label>
<div class="btn-group-vertical btn-group-sm d-flex" role="group">
<input type="radio" class="btn-check" name="deftab" id="docinfo-radio">
<label class="btn btn-outline-primary" for="docinfo-radio">
<i class="bi bi-info-circle me-1"></i>Info
</label>
<input type="radio" class="btn-check" name="deftab" id="toc-radio">
<label class="btn btn-outline-primary" for="toc-radio">
<i class="bi bi-list-ol me-1"></i>Contents
</label>
</div>
<label class="form-label fw-bold mt-4 mb-2">HTMLization configuration</label>
<div class="btn-group-vertical btn-group-sm d-flex" role="group">
<input type="radio" class="btn-check" name="htmlconf" id="txt-radio">
<label class="btn btn-outline-primary" for="txt-radio" title="This is the traditional HTMLization method.">
<i class="bi bi-badge-sd me-1"></i>HTMLize the plaintext
</label>
<input type="radio" class="btn-check" name="htmlconf" id="html-radio">
<label class="btn btn-outline-primary" for="html-radio" title="This is the modern HTMLization method.">
<i class="bi bi-badge-hd me-1"></i>Plaintextify the HTML
</label>
</div>
<label class="form-label fw-bold mt-4 mb-2" for="ptsize">Maximum font size</label>
<input type="range" class="form-range" min="7" max="16" id="ptsize" oninput="ptdemo.value = ptsize.value">
<label class="form-label fw-bold mt-4 mb-2">Page dependencies</label>
<div class="btn-group-vertical btn-group-sm d-flex" role="group">
<input type="radio" class="btn-check" name="pagedeps" id="inline-radio">
<label class="btn btn-outline-primary" for="inline-radio" title="Generate larger, standalone web pages that do not require network access to render.">
<i class="bi bi-box me-1"></i>Inline
</label>
<input type="radio" class="btn-check" name="pagedeps" id="reference-radio">
<label class="btn btn-outline-primary" for="reference-radio" title="Generate regular web pages that require network access to render.">
<i class="bi bi-link-45deg me-1"></i>Reference
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% analytical_body_bottom %}
</body>
</html>

View file

@ -0,0 +1,449 @@
{# Copyright The IETF Trust 2016-2020, All Rights Reserved #}
{% load origin %}
{% load static %}
{% load ietf_filters %}
{% load person_filters %}
{% origin %}
<tbody class="meta align-top {% if not document_html %} border-top{% endif %}">
<tr>
<th scope="row">Document</th>
<th scope="row">{% if document_html %}Document type{% else %}Type{% endif %}</th>
<td class="edit"></td>
<td>
{% if doc.get_state_slug == "rfc" and not snapshot %}
<span class="text-success">RFC
{% if not document_html %}
- {{ doc.std_level }}
{% else %}
<span class="badge rounded-pill badge-{% if not snapshot %}{{ doc|std_level_to_label_format }}{% else %}draft{% endif %}">{{ doc.std_level }}</span>
{% endif %}
</span>
{% if published %}
{% if document_html %}<br>{% else %}({% endif %}{{ doc.pub_date|date:"F Y" }}{% if not document_html %}){% endif %}
{% else %}
<span class="text-muted">(Publication date unknown)</span>
{% endif %}
{% if document_html %}<br>{% endif %}
{% if has_verified_errata or has_errata %}
<a class="{% if document_html %}btn btn-danger btn-sm my-1{% else %}badge rounded-pill bg-danger text-decoration-none text-light{% endif %}"
href="https://www.rfc-editor.org/errata_search.php?rfc={{ doc.rfc_number }}" title="Click to view errata." rel="nofollow">
Errata
</a>
{% endif %}
{% if doc.related_ipr %}
<a title="Click to view IPR declarations." class="{% if document_html %}btn btn-warning btn-sm my-1{% else %}badge rounded-pill bg-warning text-decoration-none text-light{% endif %}" href="{% url 'ietf.ipr.views.search' %}?submit=draft&amp;id={{ doc.name }}">IPR</a>
{% endif %}
{% if obsoleted_by %}<div>Obsoleted by {{ obsoleted_by|urlize_related_source_list:document_html|join:", " }}</div>{% endif %}
{% if updated_by %}<div>Updated by {{ updated_by|urlize_related_source_list:document_html|join:", " }}</div>{% endif %}
{% if obsoletes %}<div>Obsoletes {{ obsoletes|urlize_related_target_list:document_html|join:", " }}</div>{% endif %}
{% if updates %}<div>Updates {{ updates|urlize_related_target_list:document_html|join:", " }}</div>{% endif %}
{% if status_changes %}
<div>Status changed by {{ status_changes|urlize_related_source_list|join:", " }}</div>
{% endif %}
{% if proposed_status_changes %}
<div>Proposed status changed by {{ proposed_status_changes|urlize_related_source_list|join:", " }}</div>
{% endif %}
{% if rfc_aliases %}<div>Also known as {{ rfc_aliases|join:", "|urlize_ietf_docs }}</div>{% endif %}
{% if draft_name %}
<div>
Was
<a href="{% url 'ietf.doc.views_doc.document_main' name=draft_name %}">{{ draft_name }}</a>
{% if submission %}({{ submission|safe }}){% endif %}
</div>
{% endif %}
{% else %}
{% if snapshot and doc.doc.get_state_slug == 'rfc' %}
<div{% if document_html %} class="alert alert-warning small"{% endif %}>This is an older version of an Internet-Draft that was ultimately published as <a href="{% url 'ietf.doc.views_doc.document_html' name=doc.doc.canonical_name %}">{{doc.doc.canonical_name|prettystdname}}</a>.</div>
{% elif snapshot and doc.rev != latest_rev %}
<div{% if document_html %} class="alert alert-warning small"{% endif %}>This is an older version of an Internet-Draft whose latest revision state is "{{ doc.doc.get_state }}".</div>
{% else %}
<span class="{% if doc.get_state_slug == 'active' %}text-success{% elif doc.get_state_slug == 'expired' or doc.get_state_slug == 'repl' %}text-danger{% endif %}">{% if snapshot and doc.rev == latest_rev %}{{ doc.doc.get_state }}{% else %}{{ doc.get_state }}{% endif %} Internet-Draft</span>
{% if submission %}({{ submission|safe }}){% endif %}
{% if resurrected_by %}- resurrect requested by {{ resurrected_by }}{% endif %}
{% endif %}
{% endif %}
{% if doc.get_state_slug != "active" and doc.get_state_slug != "rfc" %}
<div class="badge rounded-pill bg-warning{% if not document_html %} float-end{% endif %}">
Expired &amp; archived
</div>
{% endif %}
</td>
</tr>
{% if document_html %}
<tr>
<td></td>
<th scope="row">Select version</th>
<td class="edit"></td>
<td>
{% include "doc/revisions_list.html" with document_html=document_html %}
</td>
</tr>
{% if doc.rev != "00" %}
<tr>
<td></td>
<th scope="row">Compare versions</th>
<td class="edit"></td>
<td>
<a class="btn btn-primary btn-sm" href="{{ settings.RFCDIFF_BASE_URL }}?difftype=--hwdiff&amp;url2={{ doc.name }}-{{ doc.rev }}.txt" title="Inline diff (wdiff)">Inline</a>
<a class="btn btn-primary btn-sm" href="{{ settings.RFCDIFF_BASE_URL }}?url2={{ doc.name }}-{{ doc.rev }}.txt" title="Side-by-side diff">Side-by-side</a>
</td>
</tr>
{% endif %}
{% endif %}
<tr>
<td></td>
<th scope="row">Author{{ doc.authors|pluralize }}</th>
<td class="edit">
{% if can_edit_authors %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_doc.edit_authors' name=doc.name %}">Edit</a>
{% endif %}
</td>
<td>
{# Implementation that uses the current primary email for each author #}
{% for author in doc.authors %}
{% person_link author %}{% if not forloop.last %},{% endif %}
{% endfor %}
{% if document_html and not snapshot or document_html and doc.rev == latest_rev%}
<br>
<a class="btn btn-primary btn-sm mt-1" href="mailto:{{ doc.name }}@ietf.org?subject={{ doc.name}}" title="Send email to the document authors">Email authors</a>
{% endif %}
</td>
</tr>
{% if not document_html %}
{# FIXME: This shows the date of the last history event, which is not what participants necessarily expect here. #}
<tr>
<td></td>
<th scope="row">Last updated</th>
<td class="edit"></td>
<td>
{{ doc.time|date:"Y-m-d" }}
{% if latest_revision and latest_revision.time|date:"Y-m-d" != doc.time|date:"Y-m-d" %}
<span class="text-muted">(Latest revision {{ latest_revision.time|date:"Y-m-d" }})</span>
{% endif %}
</td>
</tr>
{% endif %}
{% if replaces or not document_html and can_edit_stream_info %}
<tr>
<td></td>
<th scope="row">Replaces</th>
<td class="edit">
{% if can_edit_stream_info and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.replaces' name=doc.name %}">Edit</a>
{% endif %}
</td>
<td>
{% if replaces %}
{% if document_html %}
{{ replaces|urlize_related_target_list:document_html|join:"<br>" }}
{% else %}
{{ replaces|urlize_related_target_list:document_html|join:", " }}
{% endif %}
{% else %}
<span class="text-muted">(None)</span>
{% endif %}
</td>
</tr>
{% endif %}
{% if replaced_by %}
<tr>
<td></td>
<th scope="row">
Replaced by
</th>
<td class="edit">
</td>
<td>
{% if document_html %}
{{ replaced_by|urlize_related_source_list:document_html|join:"<br>" }}
{% else %}
{{ replaced_by|urlize_related_source_list:document_html|join:", " }}
{% endif %}
</td>
</tr>
{% endif %}
{% if can_view_possibly_replaces %}
{% if possibly_replaces %}
<tr>
<td></td>
<th scope="row">
Possibly Replaces
</th>
<td class="edit">
{% if can_edit_replaces and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">
Edit
</a>
{% endif %}
</td>
<td>
{% if document_html %}
{{ possibly_replaces|urlize_related_target_list:document_html|join:"<br>" }}
{% else %}
{{ possibly_replaces|urlize_related_target_list:document_html|join:", " }}
{% endif %}
</td>
</tr>
{% endif %}
{% if possibly_replaced_by %}
<tr>
<td></td>
<th scope="row">
Possibly Replaced By
</th>
<td class="edit">
{% if can_edit_replaces and not snapshot %}
{% comment %}<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.review_possibly_replaces' name=doc.name %}">Edit</a>{% endcomment %}
{% endif %}
</td>
<td>
{% if document_html %}
{{ possibly_replaced_by|urlize_related_source_list:document_html|join:"<br>" }}
{% else %}
{{ possibly_replaced_by|urlize_related_source_list:document_html|join:", " }}
{% endif %}
</td>
</tr>
{% endif %}
{% endif %}
<tr>
<td></td>
<th scope="row">
RFC stream
</th>
<td class="edit">
{% if can_change_stream and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.change_stream' name=doc.name %}">
Edit
</a>
{% endif %}
</td>
<td {% if stream_desc == "(None)" %}class="text-muted"{%endif%}>
{% if stream_desc != "(None)" %}
{% if doc.stream.name|lower in 'iab,irtf,ise,editorial' %}
<a href="{% url 'ietf.group.views.stream_documents' acronym=doc.stream.name|lower %}">
{% endif %}
{% if document_html %}
{% if doc.stream.name|lower in 'iab,ietf,irtf' %}
<img alt="{{ doc.stream.name|upper }} Logo"
title="{{ stream_desc }}"
class="w-25 mt-1"
{% if doc.stream.name|lower == 'iab' %}
src="{% static 'ietf/images/iab-logo.svg' %}"
{% elif doc.stream.name|lower == 'ietf' %}
src="{% static 'ietf/images/ietf-logo.svg' %}"
{% elif doc.stream.name|lower == 'irtf' %}
src="{% static 'ietf/images/irtf-logo.svg' %}"
{% endif %}
>
{% else %}
{{ stream_desc }}
{% endif %}
{% else %}
{{ stream_desc }}
{% endif %}
{% if doc.stream.name|lower in 'iab,irtf,ise,editorial' %}
</a>
{% endif %}
{% else %}
{{ stream_desc }}
{% endif %}
</td>
</tr>
{% if doc.get_state_slug != "rfc" and not snapshot %}
<tr>
<td></td>
<th scope="row">
Intended RFC status
</th>
<td class="edit">
{% if can_edit_stream_info and not snapshot %}
<a class="btn btn-primary btn-sm"
href="{% url 'ietf.doc.views_draft.change_intention' name=doc.name %}">
Edit
</a>
{% endif %}
</td>
<td>
{% if doc.intended_std_level %}
{{ doc.intended_std_level }}
{% else %}
<span class="text-muted">
(None)
</span>
{% endif %}
</td>
</tr>
{% endif %}
<tr>
<td></td>
<th scope="row">
{% if document_html %}Other formats{% else %}Formats{% endif %}
</th>
<td class="edit">
</td>
<td>
{% if document_html %}
{% include "doc/document_format_buttons.html" with skip_format="htmlized" %}
{% else %}
{% include "doc/document_format_buttons.html" %}
{% endif %}
</td>
</tr>
{% for check in doc.submission.latest_checks %}
{% if check.passed != None and check.symbol.strip %}
<tr>
<td></td>
<th scope="row">
{{ check.checker|title }}
</th>
<td class="edit">
</td>
<td>
{% if check.errors or check.warnings %}
<span class="checker-warning"
data-bs-toggle="modal"
data-bs-target="#check-{{ check.pk }}"
title="{{ check.checker|title }} returned warnings or errors.">
{{ check.symbol|safe }}
</span>
{% else %}
<span class="checker-success"
data-bs-toggle="modal"
data-bs-target="#check-{{ check.pk }}"
title="{{ check.checker|title }} passed">
{{ check.symbol|safe }}
</span>
{% endif %}
<a href="#"
data-bs-toggle="modal"
data-bs-target="#check-{{ check.pk }}">
{{ check.errors }} errors, {{ check.warnings }} warnings
</a>
{% include "doc/yang-check-modal-overlay.html" %}
</td>
</tr>
{% endif %}
{% endfor %}
{% if not document_html %}
{% if review_assignments or can_request_review %}
<tr>
<td></td>
<th scope="row">
Reviews
</th>
<td class="edit">
</td>
<td>
{% for review_assignment in review_assignments %}
{% include "doc/review_assignment_summary.html" with current_doc_name=doc.name current_rev=doc.rev %}
{% endfor %}
{% if no_review_from_teams %}
{% for team in no_review_from_teams %}
{{ team.acronym.upper }}{% if not forloop.last %},{% endif %}
{% endfor %}
will not review this version
{% endif %}
{% if can_request_review or can_submit_unsolicited_review_for_teams %}
<div {% if review_assignments or no_review_from_teams %}class="mt-3"{% endif %}>
{% if can_request_review %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.request_review" doc.name %}">
<i class="bi bi-check-circle">
</i>
Request review
</a>
{% endif %}
{% if can_submit_unsolicited_review_for_teams|length == 1 %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.complete_review" doc.name can_submit_unsolicited_review_for_teams.0.acronym %}">
<i class="bi bi-pencil-square">
</i>
Submit unsolicited review
</a>
{% elif can_submit_unsolicited_review_for_teams %}
<a class="btn btn-primary btn-sm"
href="{% url "ietf.doc.views_review.submit_unsolicited_review_choose_team" doc.name %}">
<i class="bi bi-pencil-square">
</i>
Submit unsolicited review
</a>
{% endif %}
</div>
{% endif %}
</td>
</tr>
{% endif %}
{% if conflict_reviews %}
<tr>
<td></td>
<th scope="row">
IETF conflict review
</th>
<td class="edit">
</td>
<td>
{{ conflict_reviews|join:", "|urlize_ietf_docs }}
</td>
</tr>
{% endif %}
{% endif %}
{% with doc.docextresource_set.all as resources %}
{% if document_html and resources or document_html and doc.group and doc.group.list_archive %}
{% if resources or doc.group and doc.group.list_archive or can_edit_stream_info or can_edit_individual %}
<tr>
<td>
</td>
<th scope="row">
Additional resources
</th>
<td class="edit">
{% if can_edit_stream_info or can_edit_individual %}
<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 or doc.group and doc.group.list_archive %}
{% 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 %}
{% if doc.group and doc.group.list_archive %}
{% if doc.group.list_archive|startswith:settings.MAILING_LIST_ARCHIVE_URL %}
<a href="{{ doc.group.list_archive }}?q={{ doc.name }}">
Mailing list discussion
</a>
{% elif doc.group.list_archive|is_valid_url %}
<a href="{{ doc.group.list_archive }}">
Mailing list discussion
</a>
{% else %}
{{ doc.group.list_archive|urlencode }}
{% endif %}
{% endif %}
{% endif %}
</td>
</tr>
{% endif %}
{% endif %}
{% endwith %}
</tbody>

View file

@ -1,139 +0,0 @@
<!DOCTYPE html>
{% load ietf_filters static %}
{# Copyright The IETF Trust 2021, All Rights Reserved #}
{% load origin %}
{% origin %}
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>
{% block title %}No title{% endblock %}
</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@media only screen
and (min-width: 992px)
and (max-width: 1199px) {
body { font-size: 14pt; }
div.content { width: 96ex; margin: 0 auto; }
}
@media only screen
and (min-width: 768px)
and (max-width: 991px) {
body { font-size: 14pt; }
div.content { width: 96ex; margin: 0 auto; }
}
@media only screen
and (min-width: 480px)
and (max-width: 767px) {
body { font-size: 11pt; }
div.content { width: 96ex; margin: 0 auto; }
}
@media only screen
and (max-width: 479px) {
body { font-size: 8pt; }
div.content { width: 96ex; margin: 0 auto; }
}
@media only screen
and (min-width : 375px)
and (max-width : 667px) {
body { font-size: 9.5pt; }
div.content { width: 96ex; margin: 0; }
}
@media only screen
and (min-width: 1200px) {
body { font-size: 10pt; margin: 0 4em; }
div.content { width: 96ex; margin: 0; }
}
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
font-weight: bold;
/* line-height: 0pt; */
display: inline;
white-space: pre;
font-family: monospace;
font-size: 1em;
font-weight: bold;
}
pre {
font-size: 1em;
margin-top: 0px;
margin-bottom: 0px;
}
.pre {
white-space: pre;
font-family: monospace;
}
.header{
font-weight: bold;
}
.newpage {
page-break-before: always;
}
.invisible {
text-decoration: none;
color: white;
}
@media print {
body {
margin-top: 5em;
font-family: monospace;
font-size: 10.5pt;
}
h1, h2, h3, h4, h5, h6 {
font-size: 1em;
}
a:link, a:visited {
color: inherit;
text-decoration: none;
}
.noprint {
display: none;
}
}
@media screen {
.grey, .grey a:link, .grey a:visited {
color: #777;
}
.meta-info {
background-color: #EEE;
width: 96ex;
}
.top {
border-top: 7px solid #EEE;
}
.pad {
padding-top: 7px;
line-height: 24px;
padding-bottom: 4px;
}
.bgwhite { background-color: white; }
.bgred { background-color: #F44; }
.bggrey { background-color: #666; }
.bgbrown { background-color: #840; }
.bgorange { background-color: #FA0; }
.bgyellow { background-color: #EE0; }
.bgmagenta{ background-color: #F4F; }
.bgblue { background-color: #66F; }
.bgcyan { background-color: #4DD; }
.bggreen { background-color: #4F4; }
.legend { font-size: 90%; }
.cplate { font-size: 70%; border: solid grey 1px; }
}
{% block morecss %}{% endblock %}
</style>
{% block pagehead %}{% endblock %}
{% include "base/icons.html" %}
</head>
<body {% block bodyAttrs %}{% endblock %}>
<div class="content" id="content">
{% block content %}{{ content|safe }}{% endblock %}
{% block content_end %}{% endblock %}
</div>
{% block footer %}{% endblock %}
{% block js %}{% endblock %}
</body>
</html>

View file

@ -1,4 +1,4 @@
{% autoescape off %}As you requsted, the Internet Draft {{ doc.file_tag }}
{% autoescape off %}As you requested, the Internet Draft {{ doc.file_tag }}
has been resurrected.
Datatracker URL: {{ url }}

Some files were not shown because too many files have changed in this diff Show more