ci: merge main to release (pull request #7265)
ci: merge main to release
This commit is contained in:
commit
c41c0bbad9
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -201,8 +201,8 @@ jobs:
|
|||
- name: Collect + Push Statics
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_KEY_SECRET }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_STATIC_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_STATIC_KEY_SECRET }}
|
||||
AWS_DEFAULT_REGION: auto
|
||||
AWS_ENDPOINT_URL: ${{ secrets.CF_R2_ENDPOINT }}
|
||||
run: |
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -26,6 +26,7 @@ datatracker.sublime-workspace
|
|||
/static
|
||||
/tmp-*
|
||||
/.testresult
|
||||
*.swp
|
||||
*.pyc
|
||||
__pycache__
|
||||
.yarn/*
|
||||
|
|
244
.pnp.cjs
generated
244
.pnp.cjs
generated
|
@ -49,13 +49,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.1.0"],\
|
||||
["@twuni/emojify", "npm:1.0.2"],\
|
||||
["@vitejs/plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:4.6.2"],\
|
||||
["@vue/language-plugin-pug", "npm:2.0.7"],\
|
||||
["bootstrap", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.3.3"],\
|
||||
["bootstrap-icons", "npm:1.11.3"],\
|
||||
["browser-fs-access", "npm:0.35.0"],\
|
||||
["browserlist", "npm:1.0.1"],\
|
||||
["c8", "npm:9.1.0"],\
|
||||
["caniuse-lite", "npm:1.0.30001597"],\
|
||||
["d3", "npm:7.8.5"],\
|
||||
["caniuse-lite", "npm:1.0.30001599"],\
|
||||
["d3", "npm:7.9.0"],\
|
||||
["eslint", "npm:8.57.0"],\
|
||||
["eslint-config-standard", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:17.1.0"],\
|
||||
["eslint-plugin-cypress", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.15.1"],\
|
||||
|
@ -63,10 +64,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
["eslint-plugin-n", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:16.6.2"],\
|
||||
["eslint-plugin-node", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:11.1.0"],\
|
||||
["eslint-plugin-promise", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:6.1.1"],\
|
||||
["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.22.0"],\
|
||||
["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.23.0"],\
|
||||
["file-saver", "npm:2.0.5"],\
|
||||
["highcharts", "npm:11.4.0"],\
|
||||
["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:8.15.0"],\
|
||||
["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:8.16.0"],\
|
||||
["ical.js", "npm:1.5.0"],\
|
||||
["jquery", "npm:3.7.1"],\
|
||||
["jquery-migrate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.4.1"],\
|
||||
|
@ -84,7 +85,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.1.7"],\
|
||||
["pinia-plugin-persist", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:1.0.0"],\
|
||||
["pug", "npm:3.0.2"],\
|
||||
["sass", "npm:1.71.1"],\
|
||||
["sass", "npm:1.72.0"],\
|
||||
["seedrandom", "npm:3.0.5"],\
|
||||
["select2", "npm:4.1.0-rc.0"],\
|
||||
["select2-bootstrap-5-theme", "npm:1.3.0"],\
|
||||
|
@ -2431,10 +2432,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:2a2a921469e6f0bfdb6b2bd79f75a3395d47a481854507365048f3d989418f207cf814cb2ce1a012d2da774c1d130b4ca418582463ec08381da55e543b959c4c#npm:2.1.3", {\
|
||||
"packageLocation": "./.yarn/__virtual__/@sidvind-better-ajv-errors-virtual-6ac4a81dfc/0/cache/@sidvind-better-ajv-errors-npm-2.1.3-e3d1c524a8-949cb805a1.zip/node_modules/@sidvind/better-ajv-errors/",\
|
||||
["virtual:127f44ed2d4bdd83725c8820683da6ed922142e851761151ec4911dcca098a2d4d832b774a31e7abf98185d360ff83c999b319ea3314c86d38eca853072fb0d2#npm:2.1.3", {\
|
||||
"packageLocation": "./.yarn/__virtual__/@sidvind-better-ajv-errors-virtual-ae7b5eb579/0/cache/@sidvind-better-ajv-errors-npm-2.1.3-e3d1c524a8-949cb805a1.zip/node_modules/@sidvind/better-ajv-errors/",\
|
||||
"packageDependencies": [\
|
||||
["@sidvind/better-ajv-errors", "virtual:2a2a921469e6f0bfdb6b2bd79f75a3395d47a481854507365048f3d989418f207cf814cb2ce1a012d2da774c1d130b4ca418582463ec08381da55e543b959c4c#npm:2.1.3"],\
|
||||
["@sidvind/better-ajv-errors", "virtual:127f44ed2d4bdd83725c8820683da6ed922142e851761151ec4911dcca098a2d4d832b774a31e7abf98185d360ff83c999b319ea3314c86d38eca853072fb0d2#npm:2.1.3"],\
|
||||
["@babel/code-frame", "npm:7.16.7"],\
|
||||
["@types/ajv", null],\
|
||||
["ajv", "npm:8.11.0"],\
|
||||
|
@ -2721,6 +2722,48 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@volar/language-core", [\
|
||||
["npm:2.1.4", {\
|
||||
"packageLocation": "./.yarn/cache/@volar-language-core-npm-2.1.4-18ee1a037d-7430f65143.zip/node_modules/@volar/language-core/",\
|
||||
"packageDependencies": [\
|
||||
["@volar/language-core", "npm:2.1.4"],\
|
||||
["@volar/source-map", "npm:2.1.4"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@volar/language-service", [\
|
||||
["npm:2.1.4", {\
|
||||
"packageLocation": "./.yarn/cache/@volar-language-service-npm-2.1.4-2d34cb628f-06cdcfacf0.zip/node_modules/@volar/language-service/",\
|
||||
"packageDependencies": [\
|
||||
["@volar/language-service", "npm:2.1.4"],\
|
||||
["@volar/language-core", "npm:2.1.4"],\
|
||||
["vscode-languageserver-protocol", "npm:3.17.5"],\
|
||||
["vscode-languageserver-textdocument", "npm:1.0.11"],\
|
||||
["vscode-uri", "npm:3.0.8"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@volar/source-map", [\
|
||||
["npm:2.1.4", {\
|
||||
"packageLocation": "./.yarn/cache/@volar-source-map-npm-2.1.4-5963b1701f-e2f65bcfd6.zip/node_modules/@volar/source-map/",\
|
||||
"packageDependencies": [\
|
||||
["@volar/source-map", "npm:2.1.4"],\
|
||||
["muggle-string", "npm:0.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@vscode/l10n", [\
|
||||
["npm:0.0.18", {\
|
||||
"packageLocation": "./.yarn/cache/@vscode-l10n-npm-0.0.18-8a12efe4b5-c33876cebd.zip/node_modules/@vscode/l10n/",\
|
||||
"packageDependencies": [\
|
||||
["@vscode/l10n", "npm:0.0.18"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@vue/compiler-core", [\
|
||||
["npm:3.4.21", {\
|
||||
"packageLocation": "./.yarn/cache/@vue-compiler-core-npm-3.4.21-ec7f24d7f5-0d6b7732bc.zip/node_modules/@vue/compiler-core/",\
|
||||
|
@ -2791,6 +2834,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@vue/language-plugin-pug", [\
|
||||
["npm:2.0.7", {\
|
||||
"packageLocation": "./.yarn/cache/@vue-language-plugin-pug-npm-2.0.7-547300c7e0-11cc96eb5f.zip/node_modules/@vue/language-plugin-pug/",\
|
||||
"packageDependencies": [\
|
||||
["@vue/language-plugin-pug", "npm:2.0.7"],\
|
||||
["@volar/source-map", "npm:2.1.4"],\
|
||||
["volar-service-pug", "npm:0.0.34"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@vue/reactivity", [\
|
||||
["npm:3.4.21", {\
|
||||
"packageLocation": "./.yarn/cache/@vue-reactivity-npm-3.4.21-fd3e254d08-79c7ebe3ec.zip/node_modules/@vue/reactivity/",\
|
||||
|
@ -3452,10 +3506,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.0.30001597", {\
|
||||
"packageLocation": "./.yarn/cache/caniuse-lite-npm-1.0.30001597-1e349680d5-ec6a2cf0fd.zip/node_modules/caniuse-lite/",\
|
||||
["npm:1.0.30001599", {\
|
||||
"packageLocation": "./.yarn/cache/caniuse-lite-npm-1.0.30001599-834cd4cb82-d7e619e2e7.zip/node_modules/caniuse-lite/",\
|
||||
"packageDependencies": [\
|
||||
["caniuse-lite", "npm:1.0.30001597"]\
|
||||
["caniuse-lite", "npm:1.0.30001599"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
|
@ -3764,10 +3818,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
}]\
|
||||
]],\
|
||||
["d3", [\
|
||||
["npm:7.8.5", {\
|
||||
"packageLocation": "./.yarn/cache/d3-npm-7.8.5-5db20a5616-e407e79731.zip/node_modules/d3/",\
|
||||
["npm:7.9.0", {\
|
||||
"packageLocation": "./.yarn/cache/d3-npm-7.9.0-d293821ce6-1c0e9135f1.zip/node_modules/d3/",\
|
||||
"packageDependencies": [\
|
||||
["d3", "npm:7.8.5"],\
|
||||
["d3", "npm:7.9.0"],\
|
||||
["d3-array", "npm:3.1.6"],\
|
||||
["d3-axis", "npm:3.0.0"],\
|
||||
["d3-brush", "npm:3.0.0"],\
|
||||
|
@ -5041,17 +5095,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
}]\
|
||||
]],\
|
||||
["eslint-plugin-vue", [\
|
||||
["npm:9.22.0", {\
|
||||
"packageLocation": "./.yarn/cache/eslint-plugin-vue-npm-9.22.0-2cdb92f3c1-5f1e94b412.zip/node_modules/eslint-plugin-vue/",\
|
||||
["npm:9.23.0", {\
|
||||
"packageLocation": "./.yarn/cache/eslint-plugin-vue-npm-9.23.0-d36e581933-acb3a4dd27.zip/node_modules/eslint-plugin-vue/",\
|
||||
"packageDependencies": [\
|
||||
["eslint-plugin-vue", "npm:9.22.0"]\
|
||||
["eslint-plugin-vue", "npm:9.23.0"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.22.0", {\
|
||||
"packageLocation": "./.yarn/__virtual__/eslint-plugin-vue-virtual-2da7be1523/0/cache/eslint-plugin-vue-npm-9.22.0-2cdb92f3c1-5f1e94b412.zip/node_modules/eslint-plugin-vue/",\
|
||||
["virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.23.0", {\
|
||||
"packageLocation": "./.yarn/__virtual__/eslint-plugin-vue-virtual-a9cf4d7c43/0/cache/eslint-plugin-vue-npm-9.23.0-d36e581933-acb3a4dd27.zip/node_modules/eslint-plugin-vue/",\
|
||||
"packageDependencies": [\
|
||||
["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.22.0"],\
|
||||
["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.23.0"],\
|
||||
["@eslint-community/eslint-utils", "virtual:4286e12a3a0f74af013bc8f16c6d8fdde823cfbf6389660266b171e551f576c805b0a7a8eb2a7087a5cee7dfe6ebb6e1ea3808d93daf915edc95656907a381bb#npm:4.4.0"],\
|
||||
["@types/eslint", null],\
|
||||
["eslint", "npm:8.57.0"],\
|
||||
|
@ -5059,7 +5113,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
["nth-check", "npm:2.1.1"],\
|
||||
["postcss-selector-parser", "npm:6.0.15"],\
|
||||
["semver", "npm:7.6.0"],\
|
||||
["vue-eslint-parser", "virtual:2da7be1523058e3d6960a64ca4697b767ffeb995ed69da9eb74f6f820005e744683aa6cde73ca784c556dfc97ad5b1bc884e0f3f37187c87e646cf74b01fce0e#npm:9.4.2"],\
|
||||
["vue-eslint-parser", "virtual:a9cf4d7c437b03eeeb49b01b4b3434ca26b6f59f98831796df73ffd0744dcbe1bfe9c60f8b7e2c803ab0f29084730afdc0220af1db5d8d3d00531e0df4f49dbe#npm:9.4.2"],\
|
||||
["xml-name-validator", "npm:4.0.0"]\
|
||||
],\
|
||||
"packagePeers": [\
|
||||
|
@ -5774,20 +5828,20 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
}]\
|
||||
]],\
|
||||
["html-validate", [\
|
||||
["npm:8.15.0", {\
|
||||
"packageLocation": "./.yarn/cache/html-validate-npm-8.15.0-a1dfa4198d-0af7685ca1.zip/node_modules/html-validate/",\
|
||||
["npm:8.16.0", {\
|
||||
"packageLocation": "./.yarn/cache/html-validate-npm-8.16.0-71c478330f-857b05ab87.zip/node_modules/html-validate/",\
|
||||
"packageDependencies": [\
|
||||
["html-validate", "npm:8.15.0"]\
|
||||
["html-validate", "npm:8.16.0"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:8.15.0", {\
|
||||
"packageLocation": "./.yarn/__virtual__/html-validate-virtual-2a2a921469/0/cache/html-validate-npm-8.15.0-a1dfa4198d-0af7685ca1.zip/node_modules/html-validate/",\
|
||||
["virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:8.16.0", {\
|
||||
"packageLocation": "./.yarn/__virtual__/html-validate-virtual-127f44ed2d/0/cache/html-validate-npm-8.16.0-71c478330f-857b05ab87.zip/node_modules/html-validate/",\
|
||||
"packageDependencies": [\
|
||||
["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:8.15.0"],\
|
||||
["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:8.16.0"],\
|
||||
["@babel/code-frame", "npm:7.16.7"],\
|
||||
["@html-validate/stylish", "npm:4.1.0"],\
|
||||
["@sidvind/better-ajv-errors", "virtual:2a2a921469e6f0bfdb6b2bd79f75a3395d47a481854507365048f3d989418f207cf814cb2ce1a012d2da774c1d130b4ca418582463ec08381da55e543b959c4c#npm:2.1.3"],\
|
||||
["@sidvind/better-ajv-errors", "virtual:127f44ed2d4bdd83725c8820683da6ed922142e851761151ec4911dcca098a2d4d832b774a31e7abf98185d360ff83c999b319ea3314c86d38eca853072fb0d2#npm:2.1.3"],\
|
||||
["@types/jest", null],\
|
||||
["@types/jest-diff", null],\
|
||||
["@types/jest-snapshot", null],\
|
||||
|
@ -7153,6 +7207,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["muggle-string", [\
|
||||
["npm:0.4.1", {\
|
||||
"packageLocation": "./.yarn/cache/muggle-string-npm-0.4.1-fe3c825cc2-85fe1766d1.zip/node_modules/muggle-string/",\
|
||||
"packageDependencies": [\
|
||||
["muggle-string", "npm:0.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["murmurhash-js", [\
|
||||
["npm:1.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/murmurhash-js-npm-1.0.0-b1fa804bc0-083cea92a1.zip/node_modules/murmurhash-js/",\
|
||||
|
@ -8269,13 +8332,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.1.0"],\
|
||||
["@twuni/emojify", "npm:1.0.2"],\
|
||||
["@vitejs/plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:4.6.2"],\
|
||||
["@vue/language-plugin-pug", "npm:2.0.7"],\
|
||||
["bootstrap", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.3.3"],\
|
||||
["bootstrap-icons", "npm:1.11.3"],\
|
||||
["browser-fs-access", "npm:0.35.0"],\
|
||||
["browserlist", "npm:1.0.1"],\
|
||||
["c8", "npm:9.1.0"],\
|
||||
["caniuse-lite", "npm:1.0.30001597"],\
|
||||
["d3", "npm:7.8.5"],\
|
||||
["caniuse-lite", "npm:1.0.30001599"],\
|
||||
["d3", "npm:7.9.0"],\
|
||||
["eslint", "npm:8.57.0"],\
|
||||
["eslint-config-standard", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:17.1.0"],\
|
||||
["eslint-plugin-cypress", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.15.1"],\
|
||||
|
@ -8283,10 +8347,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
["eslint-plugin-n", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:16.6.2"],\
|
||||
["eslint-plugin-node", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:11.1.0"],\
|
||||
["eslint-plugin-promise", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:6.1.1"],\
|
||||
["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.22.0"],\
|
||||
["eslint-plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:9.23.0"],\
|
||||
["file-saver", "npm:2.0.5"],\
|
||||
["highcharts", "npm:11.4.0"],\
|
||||
["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:8.15.0"],\
|
||||
["html-validate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:8.16.0"],\
|
||||
["ical.js", "npm:1.5.0"],\
|
||||
["jquery", "npm:3.7.1"],\
|
||||
["jquery-migrate", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.4.1"],\
|
||||
|
@ -8304,7 +8368,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
["pinia", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.1.7"],\
|
||||
["pinia-plugin-persist", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:1.0.0"],\
|
||||
["pug", "npm:3.0.2"],\
|
||||
["sass", "npm:1.71.1"],\
|
||||
["sass", "npm:1.72.0"],\
|
||||
["seedrandom", "npm:3.0.5"],\
|
||||
["select2", "npm:4.1.0-rc.0"],\
|
||||
["select2-bootstrap-5-theme", "npm:1.3.0"],\
|
||||
|
@ -8401,10 +8465,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.71.1", {\
|
||||
"packageLocation": "./.yarn/cache/sass-npm-1.71.1-3aced13991-19c4939d30.zip/node_modules/sass/",\
|
||||
["npm:1.72.0", {\
|
||||
"packageLocation": "./.yarn/cache/sass-npm-1.72.0-fb38bb530c-f420079c7d.zip/node_modules/sass/",\
|
||||
"packageDependencies": [\
|
||||
["sass", "npm:1.71.1"],\
|
||||
["sass", "npm:1.72.0"],\
|
||||
["chokidar", "npm:3.5.3"],\
|
||||
["immutable", "npm:4.0.0"],\
|
||||
["source-map-js", "npm:1.0.2"]\
|
||||
|
@ -9227,7 +9291,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
["lightningcss", null],\
|
||||
["postcss", "npm:8.4.33"],\
|
||||
["rollup", "npm:3.29.4"],\
|
||||
["sass", "npm:1.71.1"],\
|
||||
["sass", "npm:1.72.0"],\
|
||||
["stylus", null],\
|
||||
["sugarss", null],\
|
||||
["terser", null]\
|
||||
|
@ -9259,6 +9323,46 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["volar-service-html", [\
|
||||
["npm:0.0.34", {\
|
||||
"packageLocation": "./.yarn/cache/volar-service-html-npm-0.0.34-32b6d24136-83b50cd805.zip/node_modules/volar-service-html/",\
|
||||
"packageDependencies": [\
|
||||
["volar-service-html", "npm:0.0.34"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:6f5429e17c4ecd390af605a4e97ecc7b34f2f1374a5e30c21f0a978cbdc904738a42d0d6f5d44d2e969250218b3c205853d6afefd88b87bcda877286d12bef83#npm:0.0.34", {\
|
||||
"packageLocation": "./.yarn/__virtual__/volar-service-html-virtual-5a9107a24d/0/cache/volar-service-html-npm-0.0.34-32b6d24136-83b50cd805.zip/node_modules/volar-service-html/",\
|
||||
"packageDependencies": [\
|
||||
["volar-service-html", "virtual:6f5429e17c4ecd390af605a4e97ecc7b34f2f1374a5e30c21f0a978cbdc904738a42d0d6f5d44d2e969250218b3c205853d6afefd88b87bcda877286d12bef83#npm:0.0.34"],\
|
||||
["@types/volar__language-service", null],\
|
||||
["@volar/language-service", "npm:2.1.4"],\
|
||||
["vscode-html-languageservice", "npm:5.1.2"],\
|
||||
["vscode-languageserver-textdocument", "npm:1.0.11"],\
|
||||
["vscode-uri", "npm:3.0.8"]\
|
||||
],\
|
||||
"packagePeers": [\
|
||||
"@types/volar__language-service",\
|
||||
"@volar/language-service"\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["volar-service-pug", [\
|
||||
["npm:0.0.34", {\
|
||||
"packageLocation": "./.yarn/cache/volar-service-pug-npm-0.0.34-6f5429e17c-4691aa1c8e.zip/node_modules/volar-service-pug/",\
|
||||
"packageDependencies": [\
|
||||
["volar-service-pug", "npm:0.0.34"],\
|
||||
["@volar/language-service", "npm:2.1.4"],\
|
||||
["pug-lexer", "npm:5.0.1"],\
|
||||
["pug-parser", "npm:6.0.0"],\
|
||||
["volar-service-html", "virtual:6f5429e17c4ecd390af605a4e97ecc7b34f2f1374a5e30c21f0a978cbdc904738a42d0d6f5d44d2e969250218b3c205853d6afefd88b87bcda877286d12bef83#npm:0.0.34"],\
|
||||
["vscode-html-languageservice", "npm:5.1.2"],\
|
||||
["vscode-languageserver-textdocument", "npm:1.0.11"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["vooks", [\
|
||||
["npm:0.2.12", {\
|
||||
"packageLocation": "./.yarn/cache/vooks-npm-0.2.12-0d1a2d856b-e6841ec5b6.zip/node_modules/vooks/",\
|
||||
|
@ -9282,6 +9386,66 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["vscode-html-languageservice", [\
|
||||
["npm:5.1.2", {\
|
||||
"packageLocation": "./.yarn/cache/vscode-html-languageservice-npm-5.1.2-2ea2618bdd-3a2a5ee5ad.zip/node_modules/vscode-html-languageservice/",\
|
||||
"packageDependencies": [\
|
||||
["vscode-html-languageservice", "npm:5.1.2"],\
|
||||
["@vscode/l10n", "npm:0.0.18"],\
|
||||
["vscode-languageserver-textdocument", "npm:1.0.11"],\
|
||||
["vscode-languageserver-types", "npm:3.17.5"],\
|
||||
["vscode-uri", "npm:3.0.8"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["vscode-jsonrpc", [\
|
||||
["npm:8.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/vscode-jsonrpc-npm-8.2.0-b7d2e5b553-f302a01e59.zip/node_modules/vscode-jsonrpc/",\
|
||||
"packageDependencies": [\
|
||||
["vscode-jsonrpc", "npm:8.2.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["vscode-languageserver-protocol", [\
|
||||
["npm:3.17.5", {\
|
||||
"packageLocation": "./.yarn/cache/vscode-languageserver-protocol-npm-3.17.5-2b07e16989-dfb42d276d.zip/node_modules/vscode-languageserver-protocol/",\
|
||||
"packageDependencies": [\
|
||||
["vscode-languageserver-protocol", "npm:3.17.5"],\
|
||||
["vscode-jsonrpc", "npm:8.2.0"],\
|
||||
["vscode-languageserver-types", "npm:3.17.5"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["vscode-languageserver-textdocument", [\
|
||||
["npm:1.0.11", {\
|
||||
"packageLocation": "./.yarn/cache/vscode-languageserver-textdocument-npm-1.0.11-6fc94d2b7b-ea7cdc9d4f.zip/node_modules/vscode-languageserver-textdocument/",\
|
||||
"packageDependencies": [\
|
||||
["vscode-languageserver-textdocument", "npm:1.0.11"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["vscode-languageserver-types", [\
|
||||
["npm:3.17.5", {\
|
||||
"packageLocation": "./.yarn/cache/vscode-languageserver-types-npm-3.17.5-aca3b71a5a-79b420e757.zip/node_modules/vscode-languageserver-types/",\
|
||||
"packageDependencies": [\
|
||||
["vscode-languageserver-types", "npm:3.17.5"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["vscode-uri", [\
|
||||
["npm:3.0.8", {\
|
||||
"packageLocation": "./.yarn/cache/vscode-uri-npm-3.0.8-56f46b9d24-5142491268.zip/node_modules/vscode-uri/",\
|
||||
"packageDependencies": [\
|
||||
["vscode-uri", "npm:3.0.8"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["vue", [\
|
||||
["npm:3.4.21", {\
|
||||
"packageLocation": "./.yarn/cache/vue-npm-3.4.21-02110aa6d9-3c477982a0.zip/node_modules/vue/",\
|
||||
|
@ -9367,10 +9531,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:2da7be1523058e3d6960a64ca4697b767ffeb995ed69da9eb74f6f820005e744683aa6cde73ca784c556dfc97ad5b1bc884e0f3f37187c87e646cf74b01fce0e#npm:9.4.2", {\
|
||||
"packageLocation": "./.yarn/__virtual__/vue-eslint-parser-virtual-989a31128c/0/cache/vue-eslint-parser-npm-9.4.2-3e4e696025-67f14c8ea1.zip/node_modules/vue-eslint-parser/",\
|
||||
["virtual:a9cf4d7c437b03eeeb49b01b4b3434ca26b6f59f98831796df73ffd0744dcbe1bfe9c60f8b7e2c803ab0f29084730afdc0220af1db5d8d3d00531e0df4f49dbe#npm:9.4.2", {\
|
||||
"packageLocation": "./.yarn/__virtual__/vue-eslint-parser-virtual-6710b98248/0/cache/vue-eslint-parser-npm-9.4.2-3e4e696025-67f14c8ea1.zip/node_modules/vue-eslint-parser/",\
|
||||
"packageDependencies": [\
|
||||
["vue-eslint-parser", "virtual:2da7be1523058e3d6960a64ca4697b767ffeb995ed69da9eb74f6f820005e744683aa6cde73ca784c556dfc97ad5b1bc884e0f3f37187c87e646cf74b01fce0e#npm:9.4.2"],\
|
||||
["vue-eslint-parser", "virtual:a9cf4d7c437b03eeeb49b01b4b3434ca26b6f59f98831796df73ffd0744dcbe1bfe9c60f8b7e2c803ab0f29084730afdc0220af1db5d8d3d00531e0df4f49dbe#npm:9.4.2"],\
|
||||
["@types/eslint", null],\
|
||||
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"],\
|
||||
["eslint", "npm:8.57.0"],\
|
||||
|
|
BIN
.yarn/cache/@volar-language-core-npm-2.1.4-18ee1a037d-7430f65143.zip
vendored
Normal file
BIN
.yarn/cache/@volar-language-core-npm-2.1.4-18ee1a037d-7430f65143.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@volar-language-service-npm-2.1.4-2d34cb628f-06cdcfacf0.zip
vendored
Normal file
BIN
.yarn/cache/@volar-language-service-npm-2.1.4-2d34cb628f-06cdcfacf0.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@volar-source-map-npm-2.1.4-5963b1701f-e2f65bcfd6.zip
vendored
Normal file
BIN
.yarn/cache/@volar-source-map-npm-2.1.4-5963b1701f-e2f65bcfd6.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@vscode-l10n-npm-0.0.18-8a12efe4b5-c33876cebd.zip
vendored
Normal file
BIN
.yarn/cache/@vscode-l10n-npm-0.0.18-8a12efe4b5-c33876cebd.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@vue-language-plugin-pug-npm-2.0.7-547300c7e0-11cc96eb5f.zip
vendored
Normal file
BIN
.yarn/cache/@vue-language-plugin-pug-npm-2.0.7-547300c7e0-11cc96eb5f.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/caniuse-lite-npm-1.0.30001599-834cd4cb82-d7e619e2e7.zip
vendored
Normal file
BIN
.yarn/cache/caniuse-lite-npm-1.0.30001599-834cd4cb82-d7e619e2e7.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/d3-npm-7.8.5-5db20a5616-e407e79731.zip
vendored
BIN
.yarn/cache/d3-npm-7.8.5-5db20a5616-e407e79731.zip
vendored
Binary file not shown.
BIN
.yarn/cache/d3-npm-7.9.0-d293821ce6-1c0e9135f1.zip
vendored
Normal file
BIN
.yarn/cache/d3-npm-7.9.0-d293821ce6-1c0e9135f1.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/html-validate-npm-8.16.0-71c478330f-857b05ab87.zip
vendored
Normal file
BIN
.yarn/cache/html-validate-npm-8.16.0-71c478330f-857b05ab87.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/muggle-string-npm-0.4.1-fe3c825cc2-85fe1766d1.zip
vendored
Normal file
BIN
.yarn/cache/muggle-string-npm-0.4.1-fe3c825cc2-85fe1766d1.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/sass-npm-1.72.0-fb38bb530c-f420079c7d.zip
vendored
Normal file
BIN
.yarn/cache/sass-npm-1.72.0-fb38bb530c-f420079c7d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/volar-service-html-npm-0.0.34-32b6d24136-83b50cd805.zip
vendored
Normal file
BIN
.yarn/cache/volar-service-html-npm-0.0.34-32b6d24136-83b50cd805.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/volar-service-pug-npm-0.0.34-6f5429e17c-4691aa1c8e.zip
vendored
Normal file
BIN
.yarn/cache/volar-service-pug-npm-0.0.34-6f5429e17c-4691aa1c8e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/vscode-html-languageservice-npm-5.1.2-2ea2618bdd-3a2a5ee5ad.zip
vendored
Normal file
BIN
.yarn/cache/vscode-html-languageservice-npm-5.1.2-2ea2618bdd-3a2a5ee5ad.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/vscode-jsonrpc-npm-8.2.0-b7d2e5b553-f302a01e59.zip
vendored
Normal file
BIN
.yarn/cache/vscode-jsonrpc-npm-8.2.0-b7d2e5b553-f302a01e59.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/vscode-languageserver-protocol-npm-3.17.5-2b07e16989-dfb42d276d.zip
vendored
Normal file
BIN
.yarn/cache/vscode-languageserver-protocol-npm-3.17.5-2b07e16989-dfb42d276d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/vscode-languageserver-textdocument-npm-1.0.11-6fc94d2b7b-ea7cdc9d4f.zip
vendored
Normal file
BIN
.yarn/cache/vscode-languageserver-textdocument-npm-1.0.11-6fc94d2b7b-ea7cdc9d4f.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/vscode-languageserver-types-npm-3.17.5-aca3b71a5a-79b420e757.zip
vendored
Normal file
BIN
.yarn/cache/vscode-languageserver-types-npm-3.17.5-aca3b71a5a-79b420e757.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/vscode-uri-npm-3.0.8-56f46b9d24-5142491268.zip
vendored
Normal file
BIN
.yarn/cache/vscode-uri-npm-3.0.8-56f46b9d24-5142491268.zip
vendored
Normal file
Binary file not shown.
|
@ -47,8 +47,5 @@ $DTDIR/ietf/manage.py run_yang_model_checks -v0
|
|||
# Enable when removed from /a/www/ietf-datatracker/scripts/Cron-runner:
|
||||
$DTDIR/ietf/bin/expire-last-calls
|
||||
|
||||
# Send reminders originating from the review app
|
||||
$DTDIR/ietf/bin/send-review-reminders
|
||||
|
||||
# Purge older PersonApiKeyEvents
|
||||
$DTDIR/ietf/manage.py purge_old_personal_api_key_events 14
|
||||
|
|
|
@ -58,14 +58,17 @@
|
|||
n-button(
|
||||
:type='agendaStore.isTimezoneMeeting ? `primary` : `default`'
|
||||
@click='setTimezone(`meeting`)'
|
||||
:text-color='agendaStore.isTimezoneMeeting ? `#FFF` : null'
|
||||
) Meeting
|
||||
n-button(
|
||||
:type='agendaStore.isTimezoneLocal ? `primary` : `default`'
|
||||
@click='setTimezone(`local`)'
|
||||
:text-color='agendaStore.isTimezoneLocal ? `#FFF` : null'
|
||||
) Local
|
||||
n-button(
|
||||
:type='agendaStore.timezone === `UTC` ? `primary` : `default`'
|
||||
@click='setTimezone(`UTC`)'
|
||||
:text-color='agendaStore.timezone === `UTC` ? `#FFF` : null'
|
||||
) UTC
|
||||
n-select.agenda-timezone-ddn(
|
||||
v-if='siteStore.viewport > 1250'
|
||||
|
|
|
@ -62,7 +62,8 @@
|
|||
n-button.mt-2(
|
||||
id='agenda-quickaccess-calview-btn'
|
||||
block
|
||||
color='#6c757d'
|
||||
color='#6f42c1'
|
||||
text-color='#FFF'
|
||||
size='large'
|
||||
strong
|
||||
@click='agendaStore.$patch({ calendarShown: true })'
|
||||
|
@ -78,8 +79,8 @@
|
|||
n-button.mt-2(
|
||||
id='agenda-quickaccess-addtocal-btn'
|
||||
block
|
||||
secondary
|
||||
color='#6c757d'
|
||||
:color='siteStore.theme === `dark` ? `rgba(111, 66, 193, .3)` : `#e2d9f3`'
|
||||
:text-color='siteStore.theme === `dark` ? `#e2d9f3` : `#59359a`'
|
||||
size='large'
|
||||
strong
|
||||
)
|
||||
|
|
|
@ -11,14 +11,17 @@ n-drawer(v-model:show='isShown', placement='bottom', :height='state.drawerHeight
|
|||
n-button(
|
||||
:type='agendaStore.isTimezoneMeeting ? `primary` : `default`'
|
||||
@click='setTimezone(`meeting`)'
|
||||
:text-color='agendaStore.isTimezoneMeeting ? `#FFF` : null'
|
||||
) Meeting
|
||||
n-button(
|
||||
:type='agendaStore.isTimezoneLocal ? `primary` : `default`'
|
||||
@click='setTimezone(`local`)'
|
||||
:text-color='agendaStore.isTimezoneLocal ? `#FFF` : null'
|
||||
) Local
|
||||
n-button(
|
||||
:type='agendaStore.timezone === `UTC` ? `primary` : `default`'
|
||||
@click='setTimezone(`UTC`)'
|
||||
:text-color='agendaStore.timezone === `UTC` ? `#FFF` : null'
|
||||
) UTC
|
||||
n-divider(vertical)
|
||||
n-button.me-2(
|
||||
|
@ -32,7 +35,7 @@ n-drawer(v-model:show='isShown', placement='bottom', :height='state.drawerHeight
|
|||
n-badge.ms-2(:value='agendaStore.selectedCatSubs.length', processing)
|
||||
n-button(
|
||||
ghost
|
||||
color='gray'
|
||||
:color='siteStore.theme === `dark` ? `#e35d6a` : `gray`'
|
||||
strong
|
||||
@click='close'
|
||||
)
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
n-data-table(
|
||||
v-if='state.items.length > 0'
|
||||
:data='state.items'
|
||||
:columns='columns'
|
||||
:columns='state.columns'
|
||||
striped
|
||||
)
|
||||
span.text-danger(v-else-if='state.errMessage')
|
||||
em {{ state.errMessage }}
|
||||
span.text-body-secondary(v-else)
|
||||
em No polls available.
|
||||
</template>
|
||||
|
@ -13,9 +15,8 @@
|
|||
<script setup>
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { DateTime } from 'luxon'
|
||||
import {
|
||||
NDataTable
|
||||
} from 'naive-ui'
|
||||
import { cloneDeep, startCase } from 'lodash-es'
|
||||
import { NDataTable } from 'naive-ui'
|
||||
|
||||
// PROPS
|
||||
|
||||
|
@ -29,13 +30,15 @@ const props = defineProps({
|
|||
// STATE
|
||||
|
||||
const state = reactive({
|
||||
items: []
|
||||
items: [],
|
||||
colums: [],
|
||||
errMessage: null
|
||||
})
|
||||
|
||||
const columns = [
|
||||
const defaultColumns = [
|
||||
{
|
||||
title: 'Question',
|
||||
key: 'question'
|
||||
key: 'text'
|
||||
},
|
||||
{
|
||||
title: 'Start Time',
|
||||
|
@ -44,14 +47,6 @@ const columns = [
|
|||
{
|
||||
title: 'End Time',
|
||||
key: 'end_time'
|
||||
},
|
||||
{
|
||||
title: 'Raise Hand',
|
||||
key: 'raise_hand'
|
||||
},
|
||||
{
|
||||
title: 'Do Not Raise Hand',
|
||||
key: 'do_not_raise_hand'
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -59,20 +54,38 @@ const columns = [
|
|||
|
||||
onMounted(() => {
|
||||
// Get polls from embedded json tag
|
||||
const polls = JSON.parse(document.getElementById(`${props.componentId}-data`).textContent || '[]')
|
||||
if (polls.length > 0) {
|
||||
let idx = 1
|
||||
for (const poll of polls) {
|
||||
state.items.push({
|
||||
id: `poll-${idx}`,
|
||||
question: poll.text,
|
||||
start_time: DateTime.fromISO(poll.start_time).toFormat('dd LLLL yyyy \'at\' HH:mm:ss a ZZZZ'),
|
||||
end_time: DateTime.fromISO(poll.end_time).toFormat('dd LLLL yyyy \'at\' HH:mm:ss a ZZZZ'),
|
||||
raise_hand: poll.raise_hand,
|
||||
do_not_raise_hand: poll.do_not_raise_hand
|
||||
})
|
||||
idx++
|
||||
try {
|
||||
const polls = JSON.parse(document.getElementById(`${props.componentId}-data`).textContent || '[]')
|
||||
if (polls.length > 0) {
|
||||
// Populate columns
|
||||
state.columns = cloneDeep(defaultColumns)
|
||||
for (const col in polls[0]) {
|
||||
if (!['text', 'start_time', 'end_time'].includes(col)) {
|
||||
state.columns.push({
|
||||
title: startCase(col),
|
||||
key: col,
|
||||
minWidth: 100,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Populate rows
|
||||
let idx = 1
|
||||
for (const poll of polls) {
|
||||
state.items.push({
|
||||
...poll,
|
||||
id: `poll-${idx}`,
|
||||
start_time: DateTime.fromISO(poll.start_time).toFormat('dd LLLL yyyy \'at\' HH:mm:ss a ZZZZ'),
|
||||
end_time: DateTime.fromISO(poll.end_time).toFormat('dd LLLL yyyy \'at\' HH:mm:ss a ZZZZ')
|
||||
})
|
||||
idx++
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
state.errMessage = 'Failed to load poll results.'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
20
debug.py
20
debug.py
|
@ -55,7 +55,7 @@ def trace(fn): # renamed from 'report' by henrik 16 Jun 2011
|
|||
if len(s) > n+3:
|
||||
s = s[:n]+"..."
|
||||
return s
|
||||
def wrap(fn, *params,**kwargs):
|
||||
def wrap(*params,**kwargs):
|
||||
call = wrap.callcount = wrap.callcount + 1
|
||||
|
||||
indent = ' ' * _report_indent[0]
|
||||
|
@ -81,8 +81,8 @@ def trace(fn): # renamed from 'report' by henrik 16 Jun 2011
|
|||
return ret
|
||||
wrap.callcount = 0
|
||||
if debug:
|
||||
from decorator import decorator
|
||||
return decorator(wrap, fn)
|
||||
from functools import update_wrapper
|
||||
return update_wrapper(wrap, fn)
|
||||
else:
|
||||
return fn
|
||||
|
||||
|
@ -119,7 +119,7 @@ def clock(s):
|
|||
def time(fn):
|
||||
"""Decorator to print timing information about a function call.
|
||||
"""
|
||||
def wrap(fn, *params,**kwargs):
|
||||
def wrap(*params,**kwargs):
|
||||
|
||||
indent = ' ' * _report_indent[0]
|
||||
fc = "%s.%s()" % (fn.__module__, fn.__name__,)
|
||||
|
@ -132,8 +132,8 @@ def time(fn):
|
|||
return ret
|
||||
wrap.callcount = 0
|
||||
if debug:
|
||||
from decorator import decorator
|
||||
return decorator(wrap, fn)
|
||||
from functools import update_wrapper
|
||||
return update_wrapper(wrap, fn)
|
||||
else:
|
||||
return fn
|
||||
|
||||
|
@ -190,7 +190,7 @@ def type(name):
|
|||
value = eval(name, frame.f_globals, frame.f_locals)
|
||||
indent = ' ' * (_report_indent[0])
|
||||
sys.stderr.write("%s%s: %s\n" % (indent, name, value))
|
||||
|
||||
|
||||
def say(s):
|
||||
if debug:
|
||||
indent = ' ' * (_report_indent[0])
|
||||
|
@ -205,11 +205,11 @@ def profile(fn):
|
|||
prof.dump_stats(datafn)
|
||||
return retval
|
||||
if debug:
|
||||
from decorator import decorator
|
||||
return decorator(wrapper, fn)
|
||||
from functools import update_wrapper
|
||||
return update_wrapper(wrapper, fn)
|
||||
else:
|
||||
return fn
|
||||
|
||||
|
||||
def traceback(levels=None):
|
||||
if debug:
|
||||
indent = ' ' * (_report_indent[0])
|
||||
|
|
14
dev/coverage-action/package-lock.json
generated
14
dev/coverage-action/package-lock.json
generated
|
@ -22,7 +22,7 @@
|
|||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"npm-check-updates": "16.14.15"
|
||||
"npm-check-updates": "16.14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
|
@ -4079,9 +4079,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/npm-check-updates": {
|
||||
"version": "16.14.15",
|
||||
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.15.tgz",
|
||||
"integrity": "sha512-WH0wJ9j6CP7Azl+LLCxWAYqroT2IX02kRIzgK/fg0rPpMbETgHITWBdOPtrv521xmA3JMgeNsQ62zvVtS/nCmQ==",
|
||||
"version": "16.14.17",
|
||||
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.17.tgz",
|
||||
"integrity": "sha512-ElnDdXKe60f8S6RhzFeaGuH2TFJmt2cU2HjLdowldabdm27nWFCxV2ebeP3xGbQkzp2+RPDQNdW9HqU1lcY8ag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
|
@ -9249,9 +9249,9 @@
|
|||
}
|
||||
},
|
||||
"npm-check-updates": {
|
||||
"version": "16.14.15",
|
||||
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.15.tgz",
|
||||
"integrity": "sha512-WH0wJ9j6CP7Azl+LLCxWAYqroT2IX02kRIzgK/fg0rPpMbETgHITWBdOPtrv521xmA3JMgeNsQ62zvVtS/nCmQ==",
|
||||
"version": "16.14.17",
|
||||
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.17.tgz",
|
||||
"integrity": "sha512-ElnDdXKe60f8S6RhzFeaGuH2TFJmt2cU2HjLdowldabdm27nWFCxV2ebeP3xGbQkzp2+RPDQNdW9HqU1lcY8ag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^5.3.0",
|
||||
|
|
|
@ -19,6 +19,6 @@
|
|||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"npm-check-updates": "16.14.15"
|
||||
"npm-check-updates": "16.14.17"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,14 +42,14 @@ def check_group_email_aliases_exists(app_configs, **kwargs):
|
|||
if not ok:
|
||||
errors.append(checks.Error(
|
||||
"Found no aliases in the group email aliases file\n'%s'."%settings.GROUP_ALIASES_PATH,
|
||||
hint="Please run the generate_group_aliases management command to generate them.",
|
||||
hint="These should be created by the infrastructure using ietf/bin/aliases-from-json.py.",
|
||||
obj=None,
|
||||
id="datatracker.E0002",
|
||||
))
|
||||
except IOError as e:
|
||||
errors.append(checks.Error(
|
||||
"Could not read group email aliases:\n %s" % e,
|
||||
hint="Please run the generate_group_aliases management command to generate them.",
|
||||
hint="These should be created by the infrastructure using ietf/bin/aliases-from-json.py.",
|
||||
obj=None,
|
||||
id="datatracker.E0003",
|
||||
))
|
||||
|
@ -69,14 +69,14 @@ def check_doc_email_aliases_exists(app_configs, **kwargs):
|
|||
if not ok:
|
||||
errors.append(checks.Error(
|
||||
"Found no aliases in the document email aliases file\n'%s'."%settings.DRAFT_VIRTUAL_PATH,
|
||||
hint="Please run the generate_draft_aliases management command to generate them.",
|
||||
hint="These should be created by the infrastructure using ietf/bin/aliases-from-json.py.",
|
||||
obj=None,
|
||||
id="datatracker.E0004",
|
||||
))
|
||||
except IOError as e:
|
||||
errors.append(checks.Error(
|
||||
"Could not read document email aliases:\n %s" % e,
|
||||
hint="Please run the generate_draft_aliases management command to generate them.",
|
||||
hint="These should be created by the infrastructure using ietf/bin/aliases-from-json.py.",
|
||||
obj=None,
|
||||
id="datatracker.E0005",
|
||||
))
|
||||
|
|
|
@ -1,183 +0,0 @@
|
|||
# Copyright The IETF Trust 2012-2021, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This was written as a script by Markus Stenberg <markus.stenberg@iki.fi>.
|
||||
# It was turned into a management command by Russ Housley <housley@vigisec.com>.
|
||||
|
||||
import datetime
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import time
|
||||
|
||||
from tempfile import mkstemp
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import Document
|
||||
from ietf.group.utils import get_group_role_emails, get_group_ad_emails
|
||||
from ietf.utils.aliases import dump_sublist
|
||||
from utils.mail import parseaddr
|
||||
from ietf.utils import log
|
||||
|
||||
DEFAULT_YEARS = 2
|
||||
|
||||
|
||||
def get_draft_ad_emails(doc):
|
||||
"""Get AD email addresses for the given draft, if any."""
|
||||
ad_emails = set()
|
||||
# If working group document, return current WG ADs
|
||||
if doc.group and doc.group.acronym != 'none':
|
||||
ad_emails.update(get_group_ad_emails(doc.group))
|
||||
# Document may have an explicit AD set
|
||||
if doc.ad:
|
||||
ad_emails.add(doc.ad.email_address())
|
||||
return ad_emails
|
||||
|
||||
|
||||
def get_draft_chair_emails(doc):
|
||||
"""Get chair email addresses for the given draft, if any."""
|
||||
chair_emails = set()
|
||||
if doc.group:
|
||||
chair_emails.update(get_group_role_emails(doc.group, ['chair', 'secr']))
|
||||
return chair_emails
|
||||
|
||||
|
||||
def get_draft_shepherd_email(doc):
|
||||
"""Get shepherd email addresses for the given draft, if any."""
|
||||
shepherd_email = set()
|
||||
if doc.shepherd:
|
||||
shepherd_email.add(doc.shepherd.email_address())
|
||||
return shepherd_email
|
||||
|
||||
|
||||
def get_draft_authors_emails(doc):
|
||||
"""Get list of authors for the given draft."""
|
||||
author_emails = set()
|
||||
for author in doc.documentauthor_set.all():
|
||||
if author.email and author.email.email_address():
|
||||
author_emails.add(author.email.email_address())
|
||||
return author_emails
|
||||
|
||||
|
||||
def get_draft_notify_emails(doc):
|
||||
"""Get list of email addresses to notify for the given draft."""
|
||||
ad_email_alias_regex = r"^%s.ad@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
|
||||
all_email_alias_regex = r"^%s.all@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
|
||||
author_email_alias_regex = r"^%s@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
|
||||
notify_email_alias_regex = r"^%s.notify@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
|
||||
shepherd_email_alias_regex = r"^%s.shepherd@(%s|%s)$" % (doc.name, settings.DRAFT_ALIAS_DOMAIN, settings.TOOLS_SERVER)
|
||||
notify_emails = set()
|
||||
if doc.notify:
|
||||
for e in doc.notify.split(','):
|
||||
e = e.strip()
|
||||
if re.search(ad_email_alias_regex, e):
|
||||
notify_emails.update(get_draft_ad_emails(doc))
|
||||
elif re.search(author_email_alias_regex, e):
|
||||
notify_emails.update(get_draft_authors_emails(doc))
|
||||
elif re.search(shepherd_email_alias_regex, e):
|
||||
notify_emails.update(get_draft_shepherd_email(doc))
|
||||
elif re.search(all_email_alias_regex, e):
|
||||
notify_emails.update(get_draft_ad_emails(doc))
|
||||
notify_emails.update(get_draft_authors_emails(doc))
|
||||
notify_emails.update(get_draft_shepherd_email(doc))
|
||||
elif re.search(notify_email_alias_regex, e):
|
||||
pass
|
||||
else:
|
||||
(name, email) = parseaddr(e)
|
||||
notify_emails.add(email)
|
||||
return notify_emails
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = ('Generate the draft-aliases and draft-virtual files for Internet-Draft '
|
||||
'mail aliases, placing them in the files configured in '
|
||||
'settings.DRAFT_ALIASES_PATH and settings.DRAFT_VIRTUAL_PATH, '
|
||||
'respectively. The generation includes aliases for Internet-Drafts '
|
||||
'that have seen activity in the last %s years.' % (DEFAULT_YEARS))
|
||||
|
||||
def handle(self, *args, **options):
|
||||
show_since = timezone.now() - datetime.timedelta(DEFAULT_YEARS*365)
|
||||
|
||||
date = time.strftime("%Y-%m-%d_%H:%M:%S")
|
||||
signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date)
|
||||
|
||||
ahandle, aname = mkstemp()
|
||||
os.close(ahandle)
|
||||
afile = io.open(aname,"w")
|
||||
|
||||
vhandle, vname = mkstemp()
|
||||
os.close(vhandle)
|
||||
vfile = io.open(vname,"w")
|
||||
|
||||
afile.write(signature)
|
||||
vfile.write(signature)
|
||||
vfile.write("%s anything\n" % settings.DRAFT_VIRTUAL_DOMAIN)
|
||||
|
||||
# Internet-Drafts with active status or expired within DEFAULT_YEARS
|
||||
drafts = Document.objects.filter(type_id="draft")
|
||||
active_drafts = drafts.filter(states__slug='active')
|
||||
inactive_recent_drafts = drafts.exclude(states__slug='active').filter(expires__gte=show_since)
|
||||
interesting_drafts = active_drafts | inactive_recent_drafts
|
||||
|
||||
alias_domains = ['ietf.org', ]
|
||||
for draft in interesting_drafts.distinct().iterator():
|
||||
# Omit drafts that became RFCs, unless they were published in the last DEFAULT_YEARS
|
||||
if draft.get_state_slug()=="rfc":
|
||||
rfc = draft.became_rfc()
|
||||
log.assertion("rfc is not None")
|
||||
if rfc.latest_event(type='published_rfc').time < show_since:
|
||||
continue
|
||||
|
||||
alias = draft.name
|
||||
all = set()
|
||||
|
||||
# no suffix and .authors are the same list
|
||||
emails = get_draft_authors_emails(draft)
|
||||
all.update(emails)
|
||||
dump_sublist(afile, vfile, alias, alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
|
||||
dump_sublist(afile, vfile, alias+'.authors', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
|
||||
|
||||
# .chairs = group chairs
|
||||
emails = get_draft_chair_emails(draft)
|
||||
if emails:
|
||||
all.update(emails)
|
||||
dump_sublist(afile, vfile, alias+'.chairs', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
|
||||
|
||||
# .ad = sponsoring AD / WG AD (WG document)
|
||||
emails = get_draft_ad_emails(draft)
|
||||
if emails:
|
||||
all.update(emails)
|
||||
dump_sublist(afile, vfile, alias+'.ad', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
|
||||
|
||||
# .notify = notify email list from the Document
|
||||
emails = get_draft_notify_emails(draft)
|
||||
if emails:
|
||||
all.update(emails)
|
||||
dump_sublist(afile, vfile, alias+'.notify', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
|
||||
|
||||
# .shepherd = shepherd email from the Document
|
||||
emails = get_draft_shepherd_email(draft)
|
||||
if emails:
|
||||
all.update(emails)
|
||||
dump_sublist(afile, vfile, alias+'.shepherd', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, emails)
|
||||
|
||||
# .all = everything from above
|
||||
dump_sublist(afile, vfile, alias+'.all', alias_domains, settings.DRAFT_VIRTUAL_DOMAIN, all)
|
||||
|
||||
afile.close()
|
||||
vfile.close()
|
||||
|
||||
os.chmod(aname, stat.S_IWUSR|stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH)
|
||||
os.chmod(vname, stat.S_IWUSR|stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH)
|
||||
|
||||
shutil.move(aname, settings.DRAFT_ALIASES_PATH)
|
||||
shutil.move(vname, settings.DRAFT_VIRTUAL_PATH)
|
||||
|
||||
|
|
@ -881,3 +881,21 @@ def badgeify(blob):
|
|||
)
|
||||
|
||||
return text
|
||||
|
||||
@register.filter
|
||||
def simple_history_delta_changes(history):
|
||||
"""Returns diff between given history and previous entry."""
|
||||
prev = history.prev_record
|
||||
if prev:
|
||||
delta = history.diff_against(prev)
|
||||
return delta.changes
|
||||
return []
|
||||
|
||||
@register.filter
|
||||
def simple_history_delta_change_cnt(history):
|
||||
"""Returns number of changes between given history and previous entry."""
|
||||
prev = history.prev_record
|
||||
if prev:
|
||||
delta = history.diff_against(prev)
|
||||
return len(delta.changes)
|
||||
return 0
|
||||
|
|
|
@ -37,7 +37,7 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf.doc.models import ( Document, DocRelationshipName, RelatedDocument, State,
|
||||
DocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, NewRevisionDocEvent, BallotType,
|
||||
EditedAuthorsDocEvent )
|
||||
EditedAuthorsDocEvent, StateType)
|
||||
from ietf.doc.factories import ( DocumentFactory, DocEventFactory, CharterFactory,
|
||||
ConflictReviewFactory, WgDraftFactory, IndividualDraftFactory, WgRfcFactory,
|
||||
IndividualRfcFactory, StateDocEventFactory, BallotPositionDocEventFactory,
|
||||
|
@ -2181,152 +2181,6 @@ class GenerateDraftAliasesTests(TestCase):
|
|||
os.unlink(self.doc_virtual_file.name)
|
||||
super().tearDown()
|
||||
|
||||
def testManagementCommand(self):
|
||||
a_month_ago = (timezone.now() - datetime.timedelta(30)).astimezone(RPC_TZINFO)
|
||||
a_month_ago = a_month_ago.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
ad = RoleFactory(
|
||||
name_id="ad", group__type_id="area", group__state_id="active"
|
||||
).person
|
||||
shepherd = PersonFactory()
|
||||
author1 = PersonFactory()
|
||||
author2 = PersonFactory()
|
||||
author3 = PersonFactory()
|
||||
author4 = PersonFactory()
|
||||
author5 = PersonFactory()
|
||||
author6 = PersonFactory()
|
||||
mars = GroupFactory(type_id="wg", acronym="mars")
|
||||
marschairman = PersonFactory(user__username="marschairman")
|
||||
mars.role_set.create(
|
||||
name_id="chair", person=marschairman, email=marschairman.email()
|
||||
)
|
||||
doc1 = IndividualDraftFactory(
|
||||
authors=[author1], shepherd=shepherd.email(), ad=ad
|
||||
)
|
||||
doc2 = WgDraftFactory(
|
||||
name="draft-ietf-mars-test", group__acronym="mars", authors=[author2], ad=ad
|
||||
)
|
||||
doc3 = WgDraftFactory.create(
|
||||
name="draft-ietf-mars-finished",
|
||||
group__acronym="mars",
|
||||
authors=[author3],
|
||||
ad=ad,
|
||||
std_level_id="ps",
|
||||
states=[("draft", "rfc"), ("draft-iesg", "pub")],
|
||||
time=a_month_ago,
|
||||
)
|
||||
rfc3 = WgRfcFactory()
|
||||
DocEventFactory.create(doc=rfc3, type="published_rfc", time=a_month_ago)
|
||||
doc3.relateddocument_set.create(
|
||||
relationship_id="became_rfc", target=rfc3
|
||||
)
|
||||
doc4 = WgDraftFactory.create(
|
||||
authors=[author4, author5],
|
||||
ad=ad,
|
||||
std_level_id="ps",
|
||||
states=[("draft", "rfc"), ("draft-iesg", "pub")],
|
||||
time=datetime.datetime(2010, 10, 10, tzinfo=ZoneInfo(settings.TIME_ZONE)),
|
||||
)
|
||||
rfc4 = WgRfcFactory()
|
||||
DocEventFactory.create(
|
||||
doc=rfc4,
|
||||
type="published_rfc",
|
||||
time=datetime.datetime(2010, 10, 10, tzinfo=RPC_TZINFO),
|
||||
)
|
||||
doc4.relateddocument_set.create(
|
||||
relationship_id="became_rfc", target=rfc4
|
||||
)
|
||||
doc5 = IndividualDraftFactory(authors=[author6])
|
||||
|
||||
args = []
|
||||
kwargs = {}
|
||||
out = io.StringIO()
|
||||
call_command("generate_draft_aliases", *args, **kwargs, stdout=out, stderr=out)
|
||||
self.assertFalse(out.getvalue())
|
||||
|
||||
with open(settings.DRAFT_ALIASES_PATH) as afile:
|
||||
acontent = afile.read()
|
||||
for x in [
|
||||
"xfilter-" + doc1.name,
|
||||
"xfilter-" + doc1.name + ".ad",
|
||||
"xfilter-" + doc1.name + ".authors",
|
||||
"xfilter-" + doc1.name + ".shepherd",
|
||||
"xfilter-" + doc1.name + ".all",
|
||||
"xfilter-" + doc2.name,
|
||||
"xfilter-" + doc2.name + ".ad",
|
||||
"xfilter-" + doc2.name + ".authors",
|
||||
"xfilter-" + doc2.name + ".chairs",
|
||||
"xfilter-" + doc2.name + ".all",
|
||||
"xfilter-" + doc3.name,
|
||||
"xfilter-" + doc3.name + ".ad",
|
||||
"xfilter-" + doc3.name + ".authors",
|
||||
"xfilter-" + doc3.name + ".chairs",
|
||||
"xfilter-" + doc5.name,
|
||||
"xfilter-" + doc5.name + ".authors",
|
||||
"xfilter-" + doc5.name + ".all",
|
||||
]:
|
||||
self.assertIn(x, acontent)
|
||||
|
||||
for x in [
|
||||
"xfilter-" + doc1.name + ".chairs",
|
||||
"xfilter-" + doc2.name + ".shepherd",
|
||||
"xfilter-" + doc3.name + ".shepherd",
|
||||
"xfilter-" + doc4.name,
|
||||
"xfilter-" + doc5.name + ".shepherd",
|
||||
"xfilter-" + doc5.name + ".ad",
|
||||
]:
|
||||
self.assertNotIn(x, acontent)
|
||||
|
||||
with open(settings.DRAFT_VIRTUAL_PATH) as vfile:
|
||||
vcontent = vfile.read()
|
||||
for x in [
|
||||
ad.email_address(),
|
||||
shepherd.email_address(),
|
||||
marschairman.email_address(),
|
||||
author1.email_address(),
|
||||
author2.email_address(),
|
||||
author3.email_address(),
|
||||
author6.email_address(),
|
||||
]:
|
||||
self.assertIn(x, vcontent)
|
||||
|
||||
for x in [
|
||||
author4.email_address(),
|
||||
author5.email_address(),
|
||||
]:
|
||||
self.assertNotIn(x, vcontent)
|
||||
|
||||
for x in [
|
||||
"xfilter-" + doc1.name,
|
||||
"xfilter-" + doc1.name + ".ad",
|
||||
"xfilter-" + doc1.name + ".authors",
|
||||
"xfilter-" + doc1.name + ".shepherd",
|
||||
"xfilter-" + doc1.name + ".all",
|
||||
"xfilter-" + doc2.name,
|
||||
"xfilter-" + doc2.name + ".ad",
|
||||
"xfilter-" + doc2.name + ".authors",
|
||||
"xfilter-" + doc2.name + ".chairs",
|
||||
"xfilter-" + doc2.name + ".all",
|
||||
"xfilter-" + doc3.name,
|
||||
"xfilter-" + doc3.name + ".ad",
|
||||
"xfilter-" + doc3.name + ".authors",
|
||||
"xfilter-" + doc3.name + ".chairs",
|
||||
"xfilter-" + doc3.name + ".all",
|
||||
"xfilter-" + doc5.name,
|
||||
"xfilter-" + doc5.name + ".authors",
|
||||
"xfilter-" + doc5.name + ".all",
|
||||
]:
|
||||
self.assertIn(x, vcontent)
|
||||
|
||||
for x in [
|
||||
"xfilter-" + doc1.name + ".chairs",
|
||||
"xfilter-" + doc2.name + ".shepherd",
|
||||
"xfilter-" + doc3.name + ".shepherd",
|
||||
"xfilter-" + doc4.name,
|
||||
"xfilter-" + doc5.name + ".shepherd",
|
||||
"xfilter-" + doc5.name + ".ad",
|
||||
]:
|
||||
self.assertNotIn(x, vcontent)
|
||||
|
||||
@override_settings(TOOLS_SERVER="tools.example.org", DRAFT_ALIAS_DOMAIN="draft.example.org")
|
||||
def test_generator_class(self):
|
||||
"""The DraftAliasGenerator should generate the same lists as the old mgmt cmd"""
|
||||
|
@ -3273,3 +3127,17 @@ class DocInfoMethodsTests(TestCase):
|
|||
rfc.referenced_by_rfcs_as_rfc_or_draft(),
|
||||
draft.targets_related.filter(source__type="rfc") | rfc.targets_related.filter(source__type="rfc"),
|
||||
)
|
||||
|
||||
class StateIndexTests(TestCase):
|
||||
|
||||
def test_state_index(self):
|
||||
url = urlreverse('ietf.doc.views_help.state_index')
|
||||
r = self.client.get(url)
|
||||
q = PyQuery(r.content)
|
||||
content = [ e.text for e in q('#content table td a ') ]
|
||||
names = StateType.objects.values_list('slug', flat=True)
|
||||
# The following doesn't cover all doc types, only a selection
|
||||
for name in names:
|
||||
if not '-' in name:
|
||||
self.assertIn(name, content)
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ from django.utils.html import escape
|
|||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.expire import get_expired_drafts, send_expire_notice_for_draft, expire_draft
|
||||
from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory, RgDraftFactory, DocEventFactory
|
||||
from ietf.doc.factories import EditorialDraftFactory, IndividualDraftFactory, WgDraftFactory, RgDraftFactory, DocEventFactory
|
||||
from ietf.doc.models import ( Document, DocReminder, DocEvent,
|
||||
ConsensusDocEvent, LastCallDocEvent, RelatedDocument, State, TelechatDocEvent,
|
||||
WriteupDocEvent, DocRelationshipName, IanaExpertDocEvent )
|
||||
|
@ -2161,3 +2161,13 @@ class ShepherdWriteupTests(TestCase):
|
|||
self.assertContains(r, "for Group Documents", status_code=200)
|
||||
r = self.client.post(url,dict(reset_text=''))
|
||||
self.assertContains(r, "for Group Documents", status_code=200)
|
||||
|
||||
class EditorialDraftMetadataTests(TestCase):
|
||||
def test_editorial_metadata(self):
|
||||
draft = EditorialDraftFactory()
|
||||
url = urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=draft.name))
|
||||
r = self.client.get(url)
|
||||
q = PyQuery(r.content)
|
||||
top_level_metadata_headings = q("tbody>tr>th:first-child").text()
|
||||
self.assertNotIn("IESG", top_level_metadata_headings)
|
||||
self.assertNotIn("IANA", top_level_metadata_headings)
|
||||
|
|
|
@ -163,6 +163,7 @@ urlpatterns = [
|
|||
url(r'^%(name)s/edit/issueballot/rsab/$' % settings.URL_REGEXPS, views_ballot.issue_rsab_ballot),
|
||||
url(r'^%(name)s/edit/closeballot/rsab/$' % settings.URL_REGEXPS, views_ballot.close_rsab_ballot),
|
||||
|
||||
url(r'^help/state/?$', views_help.state_index),
|
||||
url(r'^help/state/(?P<type>[\w-]+)/$', views_help.state_help),
|
||||
url(r'^help/relationships/$', views_help.relationship_help),
|
||||
url(r'^help/relationships/(?P<subset>\w+)/$', views_help.relationship_help),
|
||||
|
|
|
@ -1062,6 +1062,8 @@ def build_file_urls(doc: Union[Document, DocHistory]):
|
|||
base = settings.IETF_ID_ARCHIVE_URL
|
||||
file_urls = []
|
||||
for t in found_types:
|
||||
if t == "ps": # Postscript might have been submitted but should not be displayed in the list of URLs
|
||||
continue
|
||||
label = "plain text" if t == "txt" else t
|
||||
file_urls.append((label, base + doc.name + "-" + doc.rev + "." + t))
|
||||
|
||||
|
|
|
@ -9,6 +9,18 @@ from ietf.doc.models import State, StateType, IESG_SUBSTATE_TAGS
|
|||
from ietf.name.models import DocRelationshipName, DocTagName
|
||||
from ietf.doc.utils import get_tags_for_stream_id
|
||||
|
||||
def state_index(request):
|
||||
types = StateType.objects.all()
|
||||
names = [ type.slug for type in types ]
|
||||
for type in types:
|
||||
if "-" in type.slug and type.slug.split('-',1)[0] in names:
|
||||
type.stategroups = None
|
||||
else:
|
||||
groups = StateType.objects.filter(slug__startswith=type.slug)
|
||||
type.stategroups = [ g.slug[len(type.slug)+1:] for g in groups if not g == type ] or ""
|
||||
|
||||
return render(request, 'doc/state_index.html', {"types": types})
|
||||
|
||||
def state_help(request, type=None):
|
||||
slug, title = {
|
||||
"draft-iesg": ("draft-iesg", "IESG States for Internet-Drafts"),
|
||||
|
@ -26,7 +38,30 @@ def state_help(request, type=None):
|
|||
"status-change": ("statchg", "RFC Status Change States"),
|
||||
"bofreq": ("bofreq", "BOF Request States"),
|
||||
"procmaterials": ("procmaterials", "Proceedings Materials States"),
|
||||
"statement": {"statement", "Statement States"}
|
||||
"statement": ("statement", "Statement States"),
|
||||
"slides": ("slides", "Slides States"),
|
||||
"minutes": ("minutes", "Minutes States"),
|
||||
"liai-att": ("liai-att", "Liaison Attachment States"),
|
||||
"recording": ("recording", "Recording States"),
|
||||
"bluesheets": ("bluesheets", "Bluesheets States"),
|
||||
"reuse_policy": ("reuse_policy", "Reuse Policy States"),
|
||||
"review": ("review", "Review States"),
|
||||
"liaison": ("liaison", "Liaison States"),
|
||||
"shepwrit": ("shepwrit", "Shapherd Writeup States"),
|
||||
"bofreq": ("bofreq", "BOF Request States"),
|
||||
"procmaterials": ("procmaterials", "Proceedings Materials States"),
|
||||
"chatlog": ("chatlog", "Chat Log States"),
|
||||
"polls": ("polls", "Polls States"),
|
||||
"statement": ("statement", "Statement States"),
|
||||
"rfc": ("rfc", "RFC States"),
|
||||
"bcp": ("bcp", "BCP States"),
|
||||
"std": ("std", "STD States"),
|
||||
"fyi": ("fyi", "FYI States"),
|
||||
"narrativeminutes": ("narrativeminutes", "Narrative Minutes States"),
|
||||
"draft": ("draft", "Draft States"),
|
||||
"statchg": ("statchg", "Status Change States"),
|
||||
"agenda": ("agenda", "Agenda States"),
|
||||
"conflrev": ("conflrev", "Conflict Review States")
|
||||
}.get(type, (None, None))
|
||||
state_type = get_object_or_404(StateType, slug=slug)
|
||||
|
||||
|
|
|
@ -1053,7 +1053,11 @@ def edit_deadline(request, name, request_id):
|
|||
if form.is_valid():
|
||||
if form.cleaned_data['deadline'] != old_deadline:
|
||||
form.save()
|
||||
subject = "Deadline changed: {} {} review of {}-{}".format(review_req.team.acronym.capitalize(),review_req.type.name.lower(), review_req.doc.name, review_req.requested_rev)
|
||||
subject = f"Deadline changed: {review_req.team.acronym.capitalize()} {review_req.type.name.lower()} review of {review_req.doc.name}"
|
||||
if review_req.requested_rev:
|
||||
subject += f"-{review_req.requested_rev}"
|
||||
descr = "Deadine changed from {} to {}".format(old_deadline, review_req.deadline)
|
||||
update_change_reason(review_req, descr)
|
||||
msg = render_to_string("review/deadline_changed.txt", {
|
||||
"review_req": review_req,
|
||||
"old_deadline": old_deadline,
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
# Copyright The IETF Trust 2012-2021, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This was written as a script by Markus Stenberg <markus.stenberg@iki.fi>.
|
||||
# It was turned into a management command by Russ Housley <housley@vigisec.com>.
|
||||
|
||||
import datetime
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import time
|
||||
|
||||
from tempfile import mkstemp
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import get_group_ad_emails, get_group_role_emails, get_child_group_role_emails
|
||||
from ietf.name.models import GroupTypeName
|
||||
from ietf.utils.aliases import dump_sublist
|
||||
|
||||
DEFAULT_YEARS = 5
|
||||
ACTIVE_STATES=['active', 'bof', 'proposed']
|
||||
GROUP_TYPES=['wg', 'rg', 'rag', 'dir', 'team', 'review', 'program', 'rfcedtyp', 'edappr', 'edwg'] # This should become groupfeature driven...
|
||||
NO_AD_GROUP_TYPES=['rg', 'rag', 'team', 'program', 'rfcedtyp', 'edappr', 'edwg']
|
||||
IETF_DOMAIN=['ietf.org', ]
|
||||
IRTF_DOMAIN=['irtf.org', ]
|
||||
IAB_DOMAIN=['iab.org', ]
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = ('Generate the group-aliases and group-virtual files for Internet-Draft '
|
||||
'mail aliases, placing them in the file configured in '
|
||||
'settings.GROUP_ALIASES_PATH and settings.GROUP_VIRTUAL_PATH, '
|
||||
'respectively. The generation includes aliases for groups that '
|
||||
'have seen activity in the last %s years.' % (DEFAULT_YEARS))
|
||||
|
||||
def handle(self, *args, **options):
|
||||
show_since = timezone.now() - datetime.timedelta(DEFAULT_YEARS*365)
|
||||
|
||||
date = time.strftime("%Y-%m-%d_%H:%M:%S")
|
||||
signature = '# Generated by %s at %s\n' % (os.path.abspath(__file__), date)
|
||||
|
||||
ahandle, aname = mkstemp()
|
||||
os.close(ahandle)
|
||||
afile = io.open(aname,"w")
|
||||
|
||||
vhandle, vname = mkstemp()
|
||||
os.close(vhandle)
|
||||
vfile = io.open(vname,"w")
|
||||
|
||||
afile.write(signature)
|
||||
vfile.write(signature)
|
||||
vfile.write("%s anything\n" % settings.GROUP_VIRTUAL_DOMAIN)
|
||||
|
||||
# Loop through each group type and build -ads and -chairs entries
|
||||
for g in GROUP_TYPES:
|
||||
domains = []
|
||||
domains += IETF_DOMAIN
|
||||
if g in ('rg', 'rag'):
|
||||
domains += IRTF_DOMAIN
|
||||
if g == 'program':
|
||||
domains += IAB_DOMAIN
|
||||
|
||||
entries = Group.objects.filter(type=g).all()
|
||||
active_entries = entries.filter(state__in=ACTIVE_STATES)
|
||||
inactive_recent_entries = entries.exclude(state__in=ACTIVE_STATES).filter(time__gte=show_since)
|
||||
interesting_entries = active_entries | inactive_recent_entries
|
||||
|
||||
for e in interesting_entries.distinct().iterator():
|
||||
name = e.acronym
|
||||
|
||||
# Research groups, teams, and programs do not have -ads lists
|
||||
if not g in NO_AD_GROUP_TYPES:
|
||||
dump_sublist(afile, vfile, name+'-ads', domains, settings.GROUP_VIRTUAL_DOMAIN, get_group_ad_emails(e))
|
||||
# All group types have -chairs lists
|
||||
dump_sublist(afile, vfile, name+'-chairs', domains, settings.GROUP_VIRTUAL_DOMAIN, get_group_role_emails(e, ['chair', 'secr']))
|
||||
|
||||
# The area lists include every chair in active working groups in the area
|
||||
areas = Group.objects.filter(type='area').all()
|
||||
active_areas = areas.filter(state__in=ACTIVE_STATES)
|
||||
for area in active_areas:
|
||||
name = area.acronym
|
||||
area_ad_emails = get_group_role_emails(area, ['pre-ad', 'ad', 'chair'])
|
||||
dump_sublist(afile, vfile, name+'-ads', IETF_DOMAIN, settings.GROUP_VIRTUAL_DOMAIN, area_ad_emails)
|
||||
dump_sublist(afile, vfile, name+'-chairs', IETF_DOMAIN, settings.GROUP_VIRTUAL_DOMAIN, (get_child_group_role_emails(area, ['chair', 'secr']) | area_ad_emails))
|
||||
|
||||
# Other groups with chairs that require Internet-Draft submission approval
|
||||
gtypes = GroupTypeName.objects.values_list('slug', flat=True)
|
||||
special_groups = Group.objects.filter(type__features__req_subm_approval=True, acronym__in=gtypes, state='active')
|
||||
for group in special_groups:
|
||||
dump_sublist(afile, vfile, group.acronym+'-chairs', IETF_DOMAIN, settings.GROUP_VIRTUAL_DOMAIN, get_group_role_emails(group, ['chair', 'delegate']))
|
||||
|
||||
afile.close()
|
||||
vfile.close()
|
||||
|
||||
os.chmod(aname, stat.S_IWUSR|stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH)
|
||||
os.chmod(vname, stat.S_IWUSR|stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH)
|
||||
|
||||
shutil.move(aname, settings.GROUP_ALIASES_PATH)
|
||||
shutil.move(vname, settings.GROUP_VIRTUAL_PATH)
|
|
@ -1,14 +1,12 @@
|
|||
# Copyright The IETF Trust 2013-2020, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import io
|
||||
import os
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.conf import settings
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.db.models import Q
|
||||
|
@ -146,108 +144,6 @@ class GenerateGroupAliasesTests(TestCase):
|
|||
os.unlink(self.doc_virtual_file.name)
|
||||
super().tearDown()
|
||||
|
||||
def testManagementCommand(self):
|
||||
a_month_ago = timezone.now() - datetime.timedelta(30)
|
||||
a_decade_ago = timezone.now() - datetime.timedelta(3650)
|
||||
role1 = RoleFactory(name_id='ad', group__type_id='area', group__acronym='myth', group__state_id='active')
|
||||
area = role1.group
|
||||
ad = role1.person
|
||||
mars = GroupFactory(type_id='wg', acronym='mars', parent=area)
|
||||
marschair = PersonFactory(user__username='marschair')
|
||||
mars.role_set.create(name_id='chair', person=marschair, email=marschair.email())
|
||||
marssecr = PersonFactory(user__username='marssecr')
|
||||
mars.role_set.create(name_id='secr', person=marssecr, email=marssecr.email())
|
||||
ames = GroupFactory(type_id='wg', acronym='ames', parent=area)
|
||||
ameschair = PersonFactory(user__username='ameschair')
|
||||
ames.role_set.create(name_id='chair', person=ameschair, email=ameschair.email())
|
||||
recent = GroupFactory(type_id='wg', acronym='recent', parent=area, state_id='conclude', time=a_month_ago)
|
||||
recentchair = PersonFactory(user__username='recentchair')
|
||||
recent.role_set.create(name_id='chair', person=recentchair, email=recentchair.email())
|
||||
wayold = GroupFactory(type_id='wg', acronym='wayold', parent=area, state_id='conclude', time=a_decade_ago)
|
||||
wayoldchair = PersonFactory(user__username='wayoldchair')
|
||||
wayold.role_set.create(name_id='chair', person=wayoldchair, email=wayoldchair.email())
|
||||
role2 = RoleFactory(name_id='ad', group__type_id='area', group__acronym='done', group__state_id='conclude')
|
||||
done = role2.group
|
||||
done_ad = role2.person
|
||||
irtf = Group.objects.get(acronym='irtf')
|
||||
testrg = GroupFactory(type_id='rg', acronym='testrg', parent=irtf)
|
||||
testrgchair = PersonFactory(user__username='testrgchair')
|
||||
testrg.role_set.create(name_id='chair', person=testrgchair, email=testrgchair.email())
|
||||
testrag = GroupFactory(type_id='rg', acronym='testrag', parent=irtf)
|
||||
testragchair = PersonFactory(user__username='testragchair')
|
||||
testrag.role_set.create(name_id='chair', person=testragchair, email=testragchair.email())
|
||||
individual = PersonFactory()
|
||||
|
||||
args = [ ]
|
||||
kwargs = { }
|
||||
out = io.StringIO()
|
||||
call_command("generate_group_aliases", *args, **kwargs, stdout=out, stderr=out)
|
||||
self.assertFalse(out.getvalue())
|
||||
|
||||
with open(settings.GROUP_ALIASES_PATH) as afile:
|
||||
acontent = afile.read()
|
||||
self.assertTrue('xfilter-' + area.acronym + '-ads' in acontent)
|
||||
self.assertTrue('xfilter-' + area.acronym + '-chairs' in acontent)
|
||||
self.assertTrue('xfilter-' + mars.acronym + '-ads' in acontent)
|
||||
self.assertTrue('xfilter-' + mars.acronym + '-chairs' in acontent)
|
||||
self.assertTrue('xfilter-' + ames.acronym + '-ads' in acontent)
|
||||
self.assertTrue('xfilter-' + ames.acronym + '-chairs' in acontent)
|
||||
self.assertTrue(all([x in acontent for x in [
|
||||
'xfilter-' + area.acronym + '-ads',
|
||||
'xfilter-' + area.acronym + '-chairs',
|
||||
'xfilter-' + mars.acronym + '-ads',
|
||||
'xfilter-' + mars.acronym + '-chairs',
|
||||
'xfilter-' + ames.acronym + '-ads',
|
||||
'xfilter-' + ames.acronym + '-chairs',
|
||||
'xfilter-' + recent.acronym + '-ads',
|
||||
'xfilter-' + recent.acronym + '-chairs',
|
||||
]]))
|
||||
self.assertFalse(all([x in acontent for x in [
|
||||
'xfilter-' + done.acronym + '-ads',
|
||||
'xfilter-' + done.acronym + '-chairs',
|
||||
'xfilter-' + wayold.acronym + '-ads',
|
||||
'xfilter-' + wayold.acronym + '-chairs',
|
||||
]]))
|
||||
|
||||
with open(settings.GROUP_VIRTUAL_PATH) as vfile:
|
||||
vcontent = vfile.read()
|
||||
self.assertTrue(all([x in vcontent for x in [
|
||||
ad.email_address(),
|
||||
marschair.email_address(),
|
||||
marssecr.email_address(),
|
||||
ameschair.email_address(),
|
||||
recentchair.email_address(),
|
||||
testrgchair.email_address(),
|
||||
testragchair.email_address(),
|
||||
]]))
|
||||
self.assertFalse(any([x in vcontent for x in [
|
||||
done_ad.email_address(),
|
||||
wayoldchair.email_address(),
|
||||
individual.email_address(),
|
||||
]]))
|
||||
self.assertTrue(all([x in vcontent for x in [
|
||||
'xfilter-' + area.acronym + '-ads',
|
||||
'xfilter-' + area.acronym + '-chairs',
|
||||
'xfilter-' + mars.acronym + '-ads',
|
||||
'xfilter-' + mars.acronym + '-chairs',
|
||||
'xfilter-' + ames.acronym + '-ads',
|
||||
'xfilter-' + ames.acronym + '-chairs',
|
||||
'xfilter-' + recent.acronym + '-ads',
|
||||
'xfilter-' + recent.acronym + '-chairs',
|
||||
'xfilter-' + testrg.acronym + '-chairs',
|
||||
'xfilter-' + testrag.acronym + '-chairs',
|
||||
testrg.acronym + '-chairs@ietf.org',
|
||||
testrg.acronym + '-chairs@irtf.org',
|
||||
testrag.acronym + '-chairs@ietf.org',
|
||||
testrag.acronym + '-chairs@irtf.org',
|
||||
]]))
|
||||
self.assertFalse(all([x in vcontent for x in [
|
||||
'xfilter-' + done.acronym + '-ads',
|
||||
'xfilter-' + done.acronym + '-chairs',
|
||||
'xfilter-' + wayold.acronym + '-ads',
|
||||
'xfilter-' + wayold.acronym + '-chairs',
|
||||
]]))
|
||||
|
||||
def test_generator_class(self):
|
||||
"""The GroupAliasGenerator should generate the same lists as the old mgmt cmd"""
|
||||
# clean out test fixture group roles we don't need for this test
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
from pyquery import PyQuery
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.utils.test_utils import TestCase
|
||||
from ietf.doc.models import StateType
|
||||
|
||||
class HelpPageTests(TestCase):
|
||||
|
||||
def test_state_index(self):
|
||||
url = reverse('ietf.help.views.state_index')
|
||||
r = self.client.get(url)
|
||||
q = PyQuery(r.content)
|
||||
content = [ e.text for e in q('#content table td a ') ]
|
||||
names = StateType.objects.values_list('slug', flat=True)
|
||||
# The following doesn't cover all doc types, only a selection
|
||||
for name in names:
|
||||
if not '-' in name:
|
||||
self.assertIn(name, content)
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
from ietf.help import views
|
||||
from ietf.utils.urls import url
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^state/(?P<doc>[-\w]+)/(?P<type>[-\w]+)/?$', views.state),
|
||||
url(r'^state/(?P<doc>[-\w]+)/?$', views.state),
|
||||
url(r'^state/?$', views.state_index),
|
||||
url(r'^state/?$', RedirectView.as_view(url='/doc/help/state/', permanent=True)),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
# Copyright The IETF Trust 2007, All Rights Reserved
|
||||
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import State, StateType
|
||||
from ietf.name.models import StreamName
|
||||
from django.shortcuts import redirect
|
||||
|
||||
def state_index(request):
|
||||
types = StateType.objects.all()
|
||||
names = [ type.slug for type in types ]
|
||||
for type in types:
|
||||
if "-" in type.slug and type.slug.split('-',1)[0] in names:
|
||||
type.stategroups = None
|
||||
else:
|
||||
groups = StateType.objects.filter(slug__startswith=type.slug)
|
||||
type.stategroups = [ g.slug[len(type.slug)+1:] for g in groups if not g == type ] or ""
|
||||
|
||||
return render(request, 'help/state_index.html', {"types": types})
|
||||
# This is just a redirect to the new URL under /doc; can probably go away eventually.
|
||||
|
||||
def state(request, doc, type=None):
|
||||
if type:
|
||||
|
@ -25,6 +13,5 @@ def state(request, doc, type=None):
|
|||
if type in streams:
|
||||
type = "stream-%s" % type
|
||||
slug = "%s-%s" % (doc,type) if type else doc
|
||||
statetype = get_object_or_404(StateType, slug=slug)
|
||||
states = State.objects.filter(used=True, type=statetype).order_by('order')
|
||||
return render(request, 'help/states.html', {"doc": doc, "type": statetype, "states":states} )
|
||||
return redirect('/doc/help/state/%s' % slug, permanent = True)
|
||||
|
|
@ -149,9 +149,5 @@ def gather_relevant_expansions(**kwargs):
|
|||
return sorted(rule_list)
|
||||
|
||||
|
||||
def get_base_submission_message_address():
|
||||
return Recipient.objects.get(slug="submission_manualpost_handling").gather()[0]
|
||||
|
||||
|
||||
def get_base_ipr_request_address():
|
||||
return Recipient.objects.get(slug="ipr_requests").gather()[0]
|
||||
|
|
|
@ -8598,7 +8598,7 @@ class ProceedingsTests(BaseMeetingTestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r, '3 attendees')
|
||||
for person in persons:
|
||||
self.assertContains(r, person.name)
|
||||
self.assertContains(r, person.plain_name())
|
||||
|
||||
# Test for the "I was there" button.
|
||||
def _test_button(person, expected):
|
||||
|
@ -8618,14 +8618,14 @@ class ProceedingsTests(BaseMeetingTestCase):
|
|||
# attempt to POST anyway is ignored
|
||||
r = self.client.post(attendance_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertNotContains(r, persons[3].name)
|
||||
self.assertNotContains(r, persons[3].plain_name())
|
||||
self.assertEqual(session.attended_set.count(), 3)
|
||||
# button is shown, and POST is accepted
|
||||
meeting.importantdate_set.update(name_id='revsub',date=date_today() + datetime.timedelta(days=20))
|
||||
_test_button(persons[3], True)
|
||||
r = self.client.post(attendance_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r, persons[3].name)
|
||||
self.assertContains(r, persons[3].plain_name())
|
||||
self.assertEqual(session.attended_set.count(), 4)
|
||||
|
||||
# When the meeting is finalized, a bluesheet file is generated,
|
||||
|
@ -8639,7 +8639,7 @@ class ProceedingsTests(BaseMeetingTestCase):
|
|||
text = doc.text()
|
||||
self.assertIn('4 attendees', text)
|
||||
for person in persons:
|
||||
self.assertIn(person.name, text)
|
||||
self.assertIn(person.plain_name(), text)
|
||||
r = self.client.get(session_url)
|
||||
self.assertContains(r, doc.get_href())
|
||||
self.assertNotContains(r, attendance_url)
|
||||
|
|
|
@ -2604,6 +2604,19 @@
|
|||
"model": "doc.state",
|
||||
"pk": 179
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "The editorial stream processing of this document is complete and it has been sent to the RFC Editor for publication. The document may be in the RFC Editor's queue, or it may have been published as an RFC; this state doesn't distinguish between different states occurring after the document has left the RSAB.",
|
||||
"name": "Sent to the RFC Editor",
|
||||
"next_states": [],
|
||||
"order": 10,
|
||||
"slug": "rfc-edit",
|
||||
"type": "draft-stream-editorial",
|
||||
"used": true
|
||||
},
|
||||
"model": "doc.state",
|
||||
"pk": 180
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"label": "State"
|
||||
|
@ -2660,13 +2673,6 @@
|
|||
"model": "doc.statetype",
|
||||
"pk": "draft"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"label": "IANA state"
|
||||
},
|
||||
"model": "doc.statetype",
|
||||
"pk": "draft-iana"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"label": "IANA Action state"
|
||||
|
@ -11286,8 +11292,8 @@
|
|||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "Issuer Tracker",
|
||||
"name": "Issuer Tracker",
|
||||
"desc": "Issue Tracker",
|
||||
"name": "Issue Tracker",
|
||||
"order": 0,
|
||||
"type": "url",
|
||||
"used": true
|
||||
|
@ -13368,6 +13374,16 @@
|
|||
"model": "name.rolename",
|
||||
"pk": "lead"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
"name": "Lead Maintainer",
|
||||
"order": 0,
|
||||
"used": true
|
||||
},
|
||||
"model": "name.rolename",
|
||||
"pk": "leadmaintainer"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"desc": "",
|
||||
|
@ -16778,7 +16794,7 @@
|
|||
"fields": {
|
||||
"command": "xym",
|
||||
"switch": "--version",
|
||||
"time": "2024-02-21T08:06:28.313Z",
|
||||
"time": "2024-03-21T07:06:23.405Z",
|
||||
"used": true,
|
||||
"version": "xym 0.7.0"
|
||||
},
|
||||
|
@ -16789,7 +16805,7 @@
|
|||
"fields": {
|
||||
"command": "pyang",
|
||||
"switch": "--version",
|
||||
"time": "2024-02-21T08:06:28.663Z",
|
||||
"time": "2024-03-21T07:06:23.755Z",
|
||||
"used": true,
|
||||
"version": "pyang 2.6.0"
|
||||
},
|
||||
|
@ -16800,7 +16816,7 @@
|
|||
"fields": {
|
||||
"command": "yanglint",
|
||||
"switch": "--version",
|
||||
"time": "2024-02-21T08:06:28.685Z",
|
||||
"time": "2024-03-21T07:06:23.773Z",
|
||||
"used": true,
|
||||
"version": "yanglint SO 1.9.2"
|
||||
},
|
||||
|
@ -16811,9 +16827,9 @@
|
|||
"fields": {
|
||||
"command": "xml2rfc",
|
||||
"switch": "--version",
|
||||
"time": "2024-02-21T08:06:29.492Z",
|
||||
"time": "2024-03-21T07:06:24.609Z",
|
||||
"used": true,
|
||||
"version": "xml2rfc 3.19.4"
|
||||
"version": "xml2rfc 3.20.1"
|
||||
},
|
||||
"model": "utils.versioninfo",
|
||||
"pk": 4
|
||||
|
|
|
@ -131,12 +131,15 @@ class AbstractReviewerQueuePolicy:
|
|||
assignee_index = rotation_pks.index(assignee_person.pk)
|
||||
skipped = rotation_pks[0:assignee_index]
|
||||
skipped_settings = self.team.reviewersettings_set.filter(person__in=skipped) # list of PKs is valid here
|
||||
changed = []
|
||||
for ss in skipped_settings:
|
||||
ss.skip_next = max(0, ss.skip_next - 1) # ensure we don't go negative
|
||||
bulk_update_with_history(skipped_settings,
|
||||
if ss.skip_next > 0:
|
||||
ss.skip_next = max(0, ss.skip_next - 1) # ensure we don't go negative
|
||||
ss._change_reason = "Skip count decremented"
|
||||
changed.append(ss)
|
||||
bulk_update_with_history(changed,
|
||||
ReviewerSettings,
|
||||
['skip_next'],
|
||||
default_change_reason='skipped')
|
||||
['skip_next'])
|
||||
|
||||
def _assignment_in_order(self, rotation_pks, assignee_person):
|
||||
"""Is this an in-order assignment?"""
|
||||
|
@ -262,12 +265,15 @@ class AbstractReviewerQueuePolicy:
|
|||
|
||||
def _clear_request_next_assignment(self, person):
|
||||
s = self._reviewer_settings_for(person)
|
||||
s.request_assignment_next = False
|
||||
s.save()
|
||||
if s.request_assignment_next:
|
||||
s.request_assignment_next = False
|
||||
s._change_reason = "Clearing request next assignment"
|
||||
s.save()
|
||||
|
||||
def _add_skip(self, person):
|
||||
s = self._reviewer_settings_for(person)
|
||||
s.skip_next += 1
|
||||
s._change_reason = "Incrementing skip count"
|
||||
s.save()
|
||||
|
||||
def _reviewer_settings_for(self, person):
|
||||
|
@ -484,6 +490,7 @@ class RotateAlphabeticallyReviewerQueuePolicy(AbstractReviewerQueuePolicy):
|
|||
# Instead, the "assign me next" flag is set.
|
||||
settings = self._reviewer_settings_for(reviewer_person)
|
||||
settings.request_assignment_next = True
|
||||
settings._change_reason = "Setting request next assignment"
|
||||
settings.save()
|
||||
|
||||
def _update_skip_next(self, rotation_pks, assignee_person):
|
||||
|
@ -523,20 +530,22 @@ class RotateAlphabeticallyReviewerQueuePolicy(AbstractReviewerQueuePolicy):
|
|||
min_skip_next = min([rs.skip_next for rs in rotation_settings.values()])
|
||||
|
||||
next_reviewer_index = None
|
||||
changed = []
|
||||
for index, pk in enumerate(unfolded_rotation_pks):
|
||||
rs = rotation_settings.get(pk)
|
||||
if (rs is None) or (rs.skip_next == min_skip_next):
|
||||
next_reviewer_index = index
|
||||
break
|
||||
else:
|
||||
rs.skip_next = max(0, rs.skip_next - 1) # ensure never negative
|
||||
if rs.skip_next > 0:
|
||||
rs.skip_next = max(0, rs.skip_next - 1) # ensure never negative
|
||||
rs._change_reason = "Skip count decremented"
|
||||
changed.append(rs)
|
||||
|
||||
log.assertion('next_reviewer_index is not None') # some entry in the list must have the minimum value
|
||||
|
||||
bulk_update_with_history(rotation_settings.values(),
|
||||
bulk_update_with_history(changed,
|
||||
ReviewerSettings,
|
||||
['skip_next'],
|
||||
default_change_reason='skipped')
|
||||
['skip_next'])
|
||||
|
||||
next_reviewer_pk = unfolded_rotation_pks[next_reviewer_index]
|
||||
NextReviewerInTeam.objects.update_or_create(
|
||||
|
@ -578,6 +587,7 @@ class LeastRecentlyUsedReviewerQueuePolicy(AbstractReviewerQueuePolicy):
|
|||
# who rejected a review and no further action is needed.
|
||||
settings = self._reviewer_settings_for(reviewer_person)
|
||||
settings.request_assignment_next = True
|
||||
settings._change_reason = "Setting request next assignment"
|
||||
settings.save()
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<h2>Activies Log</h2>
|
||||
<h2>Activities Log</h2>
|
||||
<div class="inline-related last-related">
|
||||
<table class="full-width">
|
||||
<thead>
|
||||
|
|
|
@ -1,33 +1,22 @@
|
|||
# Copyright The IETF Trust 2013-2020, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import re
|
||||
import email
|
||||
import base64
|
||||
import os
|
||||
import pyzmail
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.contrib.sites.models import Site
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.utils.log import log
|
||||
from ietf.utils.mail import send_mail, send_mail_message
|
||||
from ietf.doc.models import Document
|
||||
from ietf.ipr.mail import utc_from_string
|
||||
from ietf.person.models import Person
|
||||
from ietf.message.models import Message, MessageAttachment
|
||||
from ietf.message.models import Message
|
||||
from ietf.utils.accesstoken import generate_access_token
|
||||
from ietf.mailtrigger.utils import gather_address_lists, get_base_submission_message_address
|
||||
from ietf.submit.models import SubmissionEmailEvent, Submission
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.submit.checkers import DraftIdnitsChecker
|
||||
from ietf.utils.timezone import date_today
|
||||
|
||||
|
||||
def send_submission_confirmation(request, submission, chair_notice=False):
|
||||
|
@ -196,181 +185,3 @@ def announce_to_authors(request, submission):
|
|||
'group': group},
|
||||
cc=cc)
|
||||
|
||||
|
||||
def get_reply_to():
|
||||
"""Returns a new reply-to address for use with an outgoing message. This is an
|
||||
address with "plus addressing" using a random string. Guaranteed to be unique"""
|
||||
local,domain = get_base_submission_message_address().split('@')
|
||||
while True:
|
||||
rand = force_str(base64.urlsafe_b64encode(os.urandom(12)))
|
||||
address = "{}+{}@{}".format(local,rand,domain)
|
||||
q = Message.objects.filter(reply_to=address)
|
||||
if not q:
|
||||
return address
|
||||
|
||||
|
||||
def process_response_email(msg):
|
||||
"""Saves an incoming message. msg=string. Message "To" field is expected to
|
||||
be in the format ietf-submit+[identifier]@ietf.org. Expect to find a message with
|
||||
a matching value in the reply_to field, associated to a submission.
|
||||
Create a Message object for the incoming message and associate it to
|
||||
the original message via new SubmissionEvent"""
|
||||
message = email.message_from_string(force_str(msg))
|
||||
to = message.get('To')
|
||||
|
||||
# exit if this isn't a response we're interested in (with plus addressing)
|
||||
local,domain = get_base_submission_message_address().split('@')
|
||||
if not re.match(r'^{}\+[a-zA-Z0-9_\-]{}@{}'.format(local,'{16}',domain),to):
|
||||
return None
|
||||
|
||||
try:
|
||||
to_message = Message.objects.get(reply_to=to)
|
||||
except Message.DoesNotExist:
|
||||
log('Error finding matching message ({})'.format(to))
|
||||
return None
|
||||
|
||||
try:
|
||||
submission = to_message.manualevents.first().submission
|
||||
except:
|
||||
log('Error processing message ({})'.format(to))
|
||||
return None
|
||||
|
||||
if not submission:
|
||||
log('Error processing message - no submission ({})'.format(to))
|
||||
return None
|
||||
|
||||
parts = pyzmail.parse.get_mail_parts(message)
|
||||
body=''
|
||||
for part in parts:
|
||||
if part.is_body == 'text/plain' and part.disposition == None:
|
||||
payload, used_charset = pyzmail.decode_text(part.get_payload(), part.charset, None)
|
||||
body = body + payload + '\n'
|
||||
|
||||
by = Person.objects.get(name="(System)")
|
||||
msg = submit_message_from_message(message, body, by)
|
||||
|
||||
desc = "Email: received message - manual post - {}-{}".format(
|
||||
submission.name,
|
||||
submission.rev)
|
||||
|
||||
submission_email_event = SubmissionEmailEvent.objects.create(
|
||||
submission = submission,
|
||||
desc = desc,
|
||||
msgtype = 'msgin',
|
||||
by = by,
|
||||
message = msg,
|
||||
in_reply_to = to_message
|
||||
)
|
||||
|
||||
save_submission_email_attachments(submission_email_event, parts)
|
||||
|
||||
log("Received submission email from %s" % msg.frm)
|
||||
return msg
|
||||
|
||||
|
||||
def add_submission_email(request, remote_ip, name, rev, submission_pk, message, by, msgtype):
|
||||
"""Add email to submission history"""
|
||||
|
||||
#in_reply_to = form.cleaned_data['in_reply_to']
|
||||
# create Message
|
||||
parts = pyzmail.parse.get_mail_parts(message)
|
||||
body=''
|
||||
for part in parts:
|
||||
if part.is_body == 'text/plain' and part.disposition == None:
|
||||
payload, used_charset = pyzmail.decode_text(part.get_payload(), part.charset, None)
|
||||
body = body + payload + '\n'
|
||||
|
||||
msg = submit_message_from_message(message, body, by)
|
||||
|
||||
if (submission_pk != None):
|
||||
# Must exist - we're adding a message to an existing submission
|
||||
submission = Submission.objects.get(pk=submission_pk)
|
||||
else:
|
||||
# Must not exist
|
||||
submissions = Submission.objects.filter(name=name,rev=rev).exclude(state_id='cancel')
|
||||
if submissions.count() > 0:
|
||||
raise ValidationError("Submission {} already exists".format(name))
|
||||
|
||||
# create Submission using the name
|
||||
try:
|
||||
submission = Submission.objects.create(
|
||||
state_id="waiting-for-draft",
|
||||
remote_ip=remote_ip,
|
||||
name=name,
|
||||
rev=rev,
|
||||
title=name,
|
||||
note="",
|
||||
submission_date=date_today(),
|
||||
replaces="",
|
||||
)
|
||||
from ietf.submit.utils import create_submission_event, docevent_from_submission
|
||||
desc = "Submission created for rev {} in response to email".format(rev)
|
||||
create_submission_event(request,
|
||||
submission,
|
||||
desc)
|
||||
docevent_from_submission(submission, desc)
|
||||
except Exception as e:
|
||||
log("Exception: %s\n" % e)
|
||||
raise
|
||||
|
||||
if msgtype == 'msgin':
|
||||
rs = "Received"
|
||||
else:
|
||||
rs = "Sent"
|
||||
|
||||
desc = "{} message - manual post - {}-{}".format(rs, name, rev)
|
||||
submission_email_event = SubmissionEmailEvent.objects.create(
|
||||
desc = desc,
|
||||
submission = submission,
|
||||
msgtype = msgtype,
|
||||
by = by,
|
||||
message = msg)
|
||||
#in_reply_to = in_reply_to
|
||||
|
||||
save_submission_email_attachments(submission_email_event, parts)
|
||||
return submission, submission_email_event
|
||||
|
||||
|
||||
def submit_message_from_message(message,body,by=None):
|
||||
"""Returns a ietf.message.models.Message. msg=email.Message
|
||||
A copy of mail.message_from_message with different body handling
|
||||
"""
|
||||
if not by:
|
||||
by = Person.objects.get(name="(System)")
|
||||
msg = Message.objects.create(
|
||||
by = by,
|
||||
subject = message.get('subject',''),
|
||||
frm = message.get('from',''),
|
||||
to = message.get('to',''),
|
||||
cc = message.get('cc',''),
|
||||
bcc = message.get('bcc',''),
|
||||
reply_to = message.get('reply_to',''),
|
||||
body = body,
|
||||
time = utc_from_string(message.get('date', '')),
|
||||
content_type = message.get('content_type', 'text/plain'),
|
||||
)
|
||||
return msg
|
||||
|
||||
def save_submission_email_attachments(submission_email_event, parts):
|
||||
for part in parts:
|
||||
if part.disposition != 'attachment':
|
||||
continue
|
||||
|
||||
if part.type == 'text/plain':
|
||||
payload, used_charset = pyzmail.decode_text(part.get_payload(),
|
||||
part.charset,
|
||||
None)
|
||||
encoding = ""
|
||||
else:
|
||||
# Need a better approach - for the moment we'll just handle these
|
||||
# and encode as base64
|
||||
payload = base64.b64encode(part.get_payload())
|
||||
encoding = "base64"
|
||||
|
||||
#name = submission_email_event.submission.name
|
||||
|
||||
MessageAttachment.objects.create(message = submission_email_event.message,
|
||||
content_type = part.type,
|
||||
encoding = encoding,
|
||||
filename=part.filename,
|
||||
body=payload)
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
# Copyright The IETF Trust 2016-2020, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import io
|
||||
import sys
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from ietf.submit.mail import process_response_email
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = ("Process incoming manual post email responses")
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--email-file', dest='email', help='File containing email (default: stdin)')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
email = options.get('email', None)
|
||||
msg = None
|
||||
|
||||
if not email:
|
||||
msg = sys.stdin.read()
|
||||
else:
|
||||
msg = io.open(email, "r").read()
|
||||
|
||||
try:
|
||||
process_response_email(msg)
|
||||
except ValueError as e:
|
||||
raise CommandError(e)
|
|
@ -42,17 +42,15 @@ from ietf.group.models import Group
|
|||
from ietf.group.utils import setup_default_community_list_for_group
|
||||
from ietf.meeting.models import Meeting
|
||||
from ietf.meeting.factories import MeetingFactory
|
||||
from ietf.message.models import Message
|
||||
from ietf.name.models import FormalLanguageName
|
||||
from ietf.person.models import Person
|
||||
from ietf.person.factories import UserFactory, PersonFactory, EmailFactory
|
||||
from ietf.submit.factories import SubmissionFactory, SubmissionExtResourceFactory
|
||||
from ietf.submit.forms import SubmissionBaseUploadForm, SubmissionAutoUploadForm
|
||||
from ietf.submit.models import Submission, Preapproval, SubmissionExtResource
|
||||
from ietf.submit.mail import add_submission_email, process_response_email
|
||||
from ietf.submit.tasks import cancel_stale_submissions, process_and_accept_uploaded_submission_task
|
||||
from ietf.utils.accesstoken import generate_access_token
|
||||
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
|
||||
from ietf.utils.mail import outbox, get_payload_text
|
||||
from ietf.utils.models import VersionInfo
|
||||
from ietf.utils.test_utils import login_testing_unauthorized, TestCase
|
||||
from ietf.utils.timezone import date_today
|
||||
|
@ -198,6 +196,28 @@ def create_draft_submission_with_rev_mismatch(rev='01'):
|
|||
return draft, sub
|
||||
|
||||
|
||||
class ManualSubmissionTests(TestCase):
|
||||
def test_manualpost_view(self):
|
||||
submission = SubmissionFactory(state_id="manual")
|
||||
url = urlreverse("ietf.submit.views.manualpost")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertIn(
|
||||
urlreverse(
|
||||
"ietf.submit.views.submission_status",
|
||||
kwargs=dict(submission_id=submission.pk)
|
||||
),
|
||||
q("#manual.submissions td a").attr("href")
|
||||
)
|
||||
self.assertIn(
|
||||
submission.name,
|
||||
q("#manual.submissions td a").text()
|
||||
)
|
||||
|
||||
def test_manualpost_cancel(self):
|
||||
pass
|
||||
|
||||
class SubmitTests(BaseSubmitTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -2293,451 +2313,6 @@ class ApprovalsTestCase(BaseSubmitTestCase):
|
|||
|
||||
self.assertEqual(len(Preapproval.objects.filter(name=preapproval.name)), 0)
|
||||
|
||||
class ManualPostsTestCase(BaseSubmitTestCase):
|
||||
def test_manual_posts(self):
|
||||
GroupFactory(acronym='mars')
|
||||
|
||||
url = urlreverse('ietf.submit.views.manualpost')
|
||||
# Secretariat has access
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
Submission.objects.create(name="draft-ietf-mars-foo",
|
||||
group=Group.objects.get(acronym="mars"),
|
||||
submission_date=date_today(),
|
||||
state_id="manual")
|
||||
Submission.objects.create(name="draft-ietf-mars-bar",
|
||||
group=Group.objects.get(acronym="mars"),
|
||||
submission_date=date_today(),
|
||||
rev="00",
|
||||
state_id="grp-appr")
|
||||
|
||||
# get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
|
||||
self.assertEqual(len(q('.submissions a:contains("draft-ietf-mars-foo")')), 1)
|
||||
self.assertEqual(len(q('.submissions a:contains("draft-ietf-mars-bar")')), 0)
|
||||
|
||||
def test_waiting_for_draft(self):
|
||||
message_string = """To: somebody@ietf.org
|
||||
From: joe@test.com
|
||||
Date: {}
|
||||
Subject: test submission via email
|
||||
|
||||
Please submit my draft at http://test.com/mydraft.txt
|
||||
|
||||
Thank you
|
||||
""".format(timezone.now().ctime())
|
||||
message = email.message_from_string(force_str(message_string))
|
||||
submission, submission_email_event = (
|
||||
add_submission_email(request=None,
|
||||
remote_ip ="192.168.0.1",
|
||||
name = "draft-my-new-draft",
|
||||
rev='00',
|
||||
submission_pk=None,
|
||||
message = message,
|
||||
by = Person.objects.get(name="(System)"),
|
||||
msgtype = "msgin") )
|
||||
|
||||
url = urlreverse('ietf.submit.views.manualpost')
|
||||
# Secretariat has access
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
# get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
|
||||
self.assertEqual(len(q('.waiting-for-draft a:contains("draft-my-new-draft")')), 1)
|
||||
|
||||
# Same name should raise an error
|
||||
with self.assertRaises(Exception):
|
||||
add_submission_email(request=None,
|
||||
remote_ip ="192.168.0.1",
|
||||
name = "draft-my-new-draft",
|
||||
rev='00',
|
||||
submission_pk=None,
|
||||
message = message,
|
||||
by = Person.objects.get(name="(System)"),
|
||||
msgtype = "msgin")
|
||||
|
||||
# Cancel this one
|
||||
r = self.client.post(urlreverse("ietf.submit.views.cancel_waiting_for_draft"), {
|
||||
"submission_id": submission.pk,
|
||||
"access_token": submission.access_token(),
|
||||
})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
url = r["Location"]
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('.waiting-for-draft a:contains("draft-my-new-draft")')), 0)
|
||||
|
||||
# Should now be able to add it again
|
||||
submission, submission_email_event = (
|
||||
add_submission_email(request=None,
|
||||
remote_ip ="192.168.0.1",
|
||||
name = "draft-my-new-draft",
|
||||
rev='00',
|
||||
submission_pk=None,
|
||||
message = message,
|
||||
by = Person.objects.get(name="(System)"),
|
||||
msgtype = "msgin") )
|
||||
|
||||
|
||||
def test_waiting_for_draft_with_attachment(self):
|
||||
frm = "joe@test.com"
|
||||
|
||||
message_string = """To: somebody@ietf.org
|
||||
From: {}
|
||||
Date: {}
|
||||
Subject: A very important message with a small attachment
|
||||
Content-Type: multipart/mixed; boundary="------------090908050800030909090207"
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------090908050800030909090207
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
The message body will probably say something about the attached document
|
||||
|
||||
--------------090908050800030909090207
|
||||
Content-Type: text/plain; charset=UTF-8; name="attach.txt"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename="attach.txt"
|
||||
|
||||
QW4gZXhhbXBsZSBhdHRhY2htZW50IHd0aG91dCB2ZXJ5IG11Y2ggaW4gaXQuCgpBIGNvdXBs
|
||||
ZSBvZiBsaW5lcyAtIGJ1dCBpdCBjb3VsZCBiZSBhIGRyYWZ0Cg==
|
||||
--------------090908050800030909090207--
|
||||
""".format(frm, timezone.now().ctime())
|
||||
|
||||
message = email.message_from_string(force_str(message_string))
|
||||
submission, submission_email_event = (
|
||||
add_submission_email(request=None,
|
||||
remote_ip ="192.168.0.1",
|
||||
name = "draft-my-new-draft",
|
||||
rev='00',
|
||||
submission_pk=None,
|
||||
message = message,
|
||||
by = Person.objects.get(name="(System)"),
|
||||
msgtype = "msgin") )
|
||||
|
||||
manualpost_page_url = urlreverse('ietf.submit.views.manualpost')
|
||||
# Secretariat has access
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
self.check_manualpost_page(submission=submission,
|
||||
submission_email_event=submission_email_event,
|
||||
the_url=manualpost_page_url,
|
||||
submission_name_fragment='draft-my-new-draft',
|
||||
frm=frm,
|
||||
is_secretariat=True)
|
||||
|
||||
# Try the status page with no credentials
|
||||
self.client.logout()
|
||||
|
||||
self.check_manualpost_page(submission=submission,
|
||||
submission_email_event=submission_email_event,
|
||||
the_url=manualpost_page_url,
|
||||
submission_name_fragment='draft-my-new-draft',
|
||||
frm=frm,
|
||||
is_secretariat=False)
|
||||
|
||||
# Post another message to this submission using the link
|
||||
message_string = """To: somebody@ietf.org
|
||||
From: joe@test.com
|
||||
Date: {}
|
||||
Subject: A new submission message with a small attachment
|
||||
Content-Type: multipart/mixed; boundary="------------090908050800030909090207"
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------090908050800030909090207
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
The message body will probably say something more about the attached document
|
||||
|
||||
--------------090908050800030909090207
|
||||
Content-Type: text/plain; charset=UTF-8; name="attach.txt"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment; filename="attachment.txt"
|
||||
|
||||
QW4gZXhhbXBsZSBhdHRhY2htZW50IHd0aG91dCB2ZXJ5IG11Y2ggaW4gaXQuCgpBIGNvdXBs
|
||||
ZSBvZiBsaW5lcyAtIGJ1dCBpdCBjb3VsZCBiZSBhIGRyYWZ0Cg==
|
||||
--------------090908050800030909090207--
|
||||
""".format(timezone.now().ctime())
|
||||
|
||||
# Back to secretariat
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
||||
r, q = self.request_and_parse(manualpost_page_url)
|
||||
|
||||
url = self.get_href(q, "a#new-submission-email:contains('New submission from email')")
|
||||
|
||||
# Get the form
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
#self.assertEqual(len(q('input[name=edit-title]')), 1)
|
||||
|
||||
# Post the new message
|
||||
r = self.client.post(url, {
|
||||
"name": "draft-my-next-new-draft-00",
|
||||
"direction": "incoming",
|
||||
"message": message_string,
|
||||
})
|
||||
|
||||
if r.status_code != 302:
|
||||
q = PyQuery(r.content)
|
||||
print(q)
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
||||
#self.check_manualpost_page(submission, submission_email_event,
|
||||
# url, 'draft-my-next-new-draft'
|
||||
# 'Another very important message',
|
||||
# true)
|
||||
|
||||
def check_manualpost_page(self, submission, submission_email_event,
|
||||
the_url, submission_name_fragment,
|
||||
frm,
|
||||
is_secretariat):
|
||||
# get the page listing manual posts
|
||||
r, q = self.request_and_parse(the_url)
|
||||
selector = "#waiting-for-draft a#add-submission-email%s:contains('Add email')" % submission.pk
|
||||
|
||||
if is_secretariat:
|
||||
# Can add an email to the submission
|
||||
add_email_url = self.get_href(q, selector)
|
||||
else:
|
||||
# No add email button button
|
||||
self.assertEqual(len(q(selector)), 0)
|
||||
|
||||
# Find the link for our submission in those awaiting drafts
|
||||
submission_url = self.get_href(q, "#waiting-for-draft a#aw{}:contains('{}')".
|
||||
format(submission.pk, submission_name_fragment))
|
||||
|
||||
# Follow the link to the status page for this submission
|
||||
r, q = self.request_and_parse(submission_url)
|
||||
|
||||
selector = "#history a#reply%s:contains('Reply')" % submission.pk
|
||||
|
||||
if is_secretariat:
|
||||
# check that reply button is visible and get the form
|
||||
reply_url = self.get_href(q, selector)
|
||||
|
||||
# Get the form
|
||||
r = self.client.get(reply_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
reply_q = PyQuery(r.content)
|
||||
self.assertEqual(len(reply_q('input[name=to]')), 1)
|
||||
else:
|
||||
# No reply button
|
||||
self.assertEqual(len(q(selector)), 0)
|
||||
|
||||
if is_secretariat:
|
||||
# Now try to send an email using the send email link
|
||||
|
||||
selector = "a#send%s:contains('Send Email')" % submission.pk
|
||||
send_url = self.get_href(q, selector)
|
||||
|
||||
self.do_submission_email(the_url = send_url,
|
||||
to = frm,
|
||||
body = "A new message")
|
||||
|
||||
# print q
|
||||
# print submission.pk
|
||||
# print submission_email_event.pk
|
||||
|
||||
# Find the link for our message in the list
|
||||
url = self.get_href(q, "#aw{}-{}:contains('{}')".format(submission.pk,
|
||||
submission_email_event.message.pk,
|
||||
"Received message - manual post"))
|
||||
|
||||
# Page displaying message details
|
||||
r, q = self.request_and_parse(url)
|
||||
|
||||
if is_secretariat:
|
||||
# check that reply button is visible
|
||||
|
||||
reply_href = self.get_href(q, "a#reply%s:contains('Reply')" % submission.pk)
|
||||
|
||||
else:
|
||||
# No reply button
|
||||
self.assertEqual(len(q(selector)), 0)
|
||||
reply_href = None
|
||||
|
||||
# check that attachment link is visible
|
||||
|
||||
url = self.get_href(q, "#email-details a#attach{}:contains('attach.txt')".format(submission.pk))
|
||||
|
||||
# Fetch the attachment
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
# Attempt a reply if we can
|
||||
if reply_href == None:
|
||||
return
|
||||
|
||||
self.do_submission_email(the_url = reply_href,
|
||||
to = frm,
|
||||
body = "A reply to the message")
|
||||
|
||||
# try adding an email to the submission
|
||||
# Use the add email link from the manual post listing page
|
||||
|
||||
if is_secretariat:
|
||||
# Can add an email to the submission
|
||||
# add_email_url set previously
|
||||
r = self.client.get(add_email_url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
add_email_q = PyQuery(r.content)
|
||||
self.assertEqual(len(add_email_q('input[name=submission_pk]')), 1)
|
||||
|
||||
# Add a simple email
|
||||
new_message_string = """To: somebody@ietf.org
|
||||
From: joe@test.com
|
||||
Date: {}
|
||||
Subject: Another message
|
||||
|
||||
About my submission
|
||||
|
||||
Thank you
|
||||
""".format(timezone.now().ctime())
|
||||
|
||||
r = self.client.post(add_email_url, {
|
||||
"name": "{}-{}".format(submission.name, submission.rev),
|
||||
"direction": "incoming",
|
||||
"submission_pk": submission.pk,
|
||||
"message": new_message_string,
|
||||
})
|
||||
|
||||
if r.status_code != 302:
|
||||
q = PyQuery(r.content)
|
||||
print(q)
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
def request_and_parse(self, url):
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
return r, PyQuery(r.content)
|
||||
|
||||
|
||||
def get_href(self, q, query):
|
||||
link = q(query)
|
||||
self.assertEqual(len(link), 1)
|
||||
|
||||
return PyQuery(link[0]).attr('href')
|
||||
|
||||
|
||||
def do_submission_email(self, the_url, to, body):
|
||||
# check the page
|
||||
r = self.client.get(the_url)
|
||||
q = PyQuery(r.content)
|
||||
post_button = q('[type=submit]:contains("Send email")')
|
||||
self.assertEqual(len(post_button), 1)
|
||||
subject = post_button.parents("form").find('input[name="subject"]').val()
|
||||
frm = post_button.parents("form").find('input[name="frm"]').val()
|
||||
cc = post_button.parents("form").find('input[name="cc"]').val()
|
||||
reply_to = post_button.parents("form").find('input[name="reply_to"]').val()
|
||||
|
||||
empty_outbox()
|
||||
|
||||
# post submitter info
|
||||
r = self.client.post(the_url, {
|
||||
"subject": subject,
|
||||
"frm": frm,
|
||||
"to": to,
|
||||
"cc": cc,
|
||||
"reply_to": reply_to,
|
||||
"body": body,
|
||||
})
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
self.assertEqual(len(outbox), 1)
|
||||
|
||||
outmsg = outbox[0]
|
||||
self.assertTrue(to in outmsg['To'])
|
||||
|
||||
reply_to = outmsg['Reply-To']
|
||||
self.assertIsNotNone(reply_to, "Expected Reply-To")
|
||||
|
||||
# Build a reply
|
||||
|
||||
message_string = """To: {}
|
||||
From: {}
|
||||
Date: {}
|
||||
Subject: test
|
||||
""".format(reply_to, to, timezone.now().ctime())
|
||||
|
||||
result = process_response_email(message_string)
|
||||
self.assertIsInstance(result, Message)
|
||||
|
||||
return r
|
||||
|
||||
def do_submission(self, name, rev, group=None, formats=["txt",]):
|
||||
# We're not testing the submission process - just the submission status
|
||||
|
||||
# get
|
||||
url = urlreverse('ietf.submit.views.upload_submission')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('input[type=file][name=txt]')), 1)
|
||||
self.assertEqual(len(q('input[type=file][name=xml]')), 1)
|
||||
|
||||
# submit
|
||||
files = {}
|
||||
for format in formats:
|
||||
files[format], author = submission_file(f'{name}-{rev}', f'{name}-{rev}.{format}', group, "test_submission.%s" % format)
|
||||
|
||||
r = self.post_to_upload_submission(url, files)
|
||||
if r.status_code != 302:
|
||||
q = PyQuery(r.content)
|
||||
print(q('div.invalid-feedback span.form-text div').text())
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
status_url = r["Location"]
|
||||
for format in formats:
|
||||
self.assertTrue(os.path.exists(os.path.join(self.staging_dir, "%s-%s.%s" % (name, rev, format))))
|
||||
self.assertEqual(Submission.objects.filter(name=name).count(), 1)
|
||||
submission = Submission.objects.get(name=name)
|
||||
self.assertTrue(all([ c.passed!=False for c in submission.checks.all() ]))
|
||||
self.assertEqual(len(submission.authors), 1)
|
||||
author = submission.authors[0]
|
||||
self.assertEqual(author["name"], "Author Name")
|
||||
self.assertEqual(author["email"], "author@example.com")
|
||||
|
||||
return status_url
|
||||
|
||||
|
||||
def supply_extra_metadata(self, name, status_url, submitter_name, submitter_email):
|
||||
# check the page
|
||||
r = self.client.get(status_url)
|
||||
q = PyQuery(r.content)
|
||||
post_button = q('[type=submit]:contains("Post")')
|
||||
self.assertEqual(len(post_button), 1)
|
||||
action = post_button.parents("form").find('input[type=hidden][name="action"]').val()
|
||||
|
||||
# post submitter info
|
||||
r = self.client.post(status_url, {
|
||||
"action": action,
|
||||
"submitter-name": submitter_name,
|
||||
"submitter-email": submitter_email,
|
||||
"approvals_received": True,
|
||||
})
|
||||
|
||||
if r.status_code == 302:
|
||||
submission = Submission.objects.get(name=name)
|
||||
self.assertEqual(submission.submitter, email.utils.formataddr((submitter_name, submitter_email)))
|
||||
|
||||
return r
|
||||
|
||||
|
||||
# Transaction.on_commit() requires use of TransactionTestCase, but that has a performance penalty. Replace it
|
||||
# with a no-op for testing purposes.
|
||||
|
|
|
@ -17,15 +17,6 @@ urlpatterns = [
|
|||
url(r'^approvals/cancelpreapproval/(?P<preapproval_id>[a-f\d]+)/$', views.cancel_preapproval),
|
||||
|
||||
url(r'^manualpost/$', views.manualpost),
|
||||
url(r'^manualpost/addemail$', views.add_manualpost_email),
|
||||
url(r'^manualpost/addemail/(?P<submission_id>\d+)/(?P<access_token>[a-f\d]*)/$', views.add_manualpost_email),
|
||||
url(r'^manualpost/attachment/(?P<submission_id>\d+)/(?P<message_id>\d+)/(?P<filename>.*)$', views.show_submission_email_attachment),
|
||||
url(r'^manualpost/cancel$', views.cancel_waiting_for_draft),
|
||||
url(r'^manualpost/email/(?P<submission_id>\d+)/(?P<message_id>\d+)/$', views.show_submission_email_message),
|
||||
url(r'^manualpost/email/(?P<submission_id>\d+)/(?P<message_id>\d+)/(?P<access_token>[a-f\d]*)/$', views.show_submission_email_message),
|
||||
url(r'^manualpost/replyemail/(?P<submission_id>\d+)/(?P<message_id>\d+)/$', views.send_submission_email),
|
||||
url(r'^manualpost/sendemail/(?P<submission_id>\d+)/$', views.send_submission_email),
|
||||
|
||||
# proof-of-concept for celery async tasks
|
||||
url(r'^async-poke/?$', views.async_poke_test),
|
||||
]
|
||||
]
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
|
||||
import re
|
||||
import base64
|
||||
import datetime
|
||||
|
||||
from typing import Optional, cast # pyflakes:ignore
|
||||
|
@ -22,31 +21,30 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import Document, AddedMessageEvent
|
||||
from ietf.doc.models import Document
|
||||
from ietf.doc.forms import ExtResourceForm
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import group_features_group_filter
|
||||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.message.models import Message, MessageAttachment
|
||||
from ietf.person.models import Email
|
||||
from ietf.submit.forms import (SubmissionAutoUploadForm, AuthorForm, SubmitterForm, EditSubmissionForm,
|
||||
PreapprovalForm, ReplacesForm, SubmissionEmailForm, MessageModelForm,
|
||||
PreapprovalForm, ReplacesForm,
|
||||
DeprecatedSubmissionAutoUploadForm, SubmissionManualUploadForm)
|
||||
from ietf.submit.mail import send_full_url, send_manual_post_request, add_submission_email, get_reply_to
|
||||
from ietf.submit.mail import send_full_url, send_manual_post_request
|
||||
from ietf.submit.models import (Submission, Preapproval, SubmissionExtResource,
|
||||
DraftSubmissionStateName, SubmissionEmailEvent )
|
||||
DraftSubmissionStateName )
|
||||
from ietf.submit.tasks import process_uploaded_submission_task, process_and_accept_uploaded_submission_task, poke
|
||||
from ietf.submit.utils import ( approvable_submissions_for_user, preapprovals_for_user,
|
||||
recently_approved_by_user, validate_submission, create_submission_event, docevent_from_submission,
|
||||
post_submission, cancel_submission, rename_submission_files, remove_submission_files, get_draft_meta,
|
||||
get_submission, fill_in_submission, apply_checkers, save_files, clear_existing_files,
|
||||
check_submission_revision_consistency, accept_submission, accept_submission_requires_group_approval,
|
||||
accept_submission_requires_prev_auth_approval, update_submission_external_resources, remote_ip )
|
||||
accept_submission_requires_prev_auth_approval, update_submission_external_resources)
|
||||
from ietf.stats.utils import clean_country_name
|
||||
from ietf.utils.accesstoken import generate_access_token
|
||||
from ietf.utils.log import log
|
||||
from ietf.utils.mail import parseaddr, send_mail_message
|
||||
from ietf.utils.mail import parseaddr
|
||||
from ietf.utils.response import permission_denied
|
||||
from ietf.utils.timezone import date_today
|
||||
|
||||
|
@ -780,243 +778,14 @@ def manualpost(request):
|
|||
s.passes_checks = all([ c.passed!=False for c in s.checks.all() ])
|
||||
s.errors = validate_submission(s)
|
||||
|
||||
waiting_for_draft = Submission.objects.filter(state_id = "waiting-for-draft").distinct()
|
||||
|
||||
return render(request, 'submit/manual_post.html',
|
||||
{'manual': manual,
|
||||
'selected': 'manual_posts',
|
||||
'waiting_for_draft': waiting_for_draft})
|
||||
|
||||
|
||||
def cancel_waiting_for_draft(request):
|
||||
if request.method == 'POST':
|
||||
can_cancel = has_role(request.user, "Secretariat")
|
||||
|
||||
if not can_cancel:
|
||||
permission_denied(request, 'You do not have permission to perform this action.')
|
||||
|
||||
submission_id = request.POST.get('submission_id', '')
|
||||
access_token = request.POST.get('access_token', '')
|
||||
|
||||
submission = get_submission_or_404(submission_id, access_token = access_token)
|
||||
cancel_submission(submission)
|
||||
|
||||
create_submission_event(request, submission, "Cancelled submission")
|
||||
if (submission.rev != "00"):
|
||||
# Add a doc event
|
||||
docevent_from_submission(submission, "Cancelled submission for rev {}".format(submission.rev))
|
||||
|
||||
return redirect("ietf.submit.views.manualpost")
|
||||
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def add_manualpost_email(request, submission_id=None, access_token=None):
|
||||
"""Add email to submission history"""
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
return redirect("submit/manual_post.html")
|
||||
|
||||
form = SubmissionEmailForm(request.POST)
|
||||
if form.is_valid():
|
||||
submission_pk = form.cleaned_data['submission_pk']
|
||||
message = form.cleaned_data['message']
|
||||
#in_reply_to = form.cleaned_data['in_reply_to']
|
||||
# create Message
|
||||
|
||||
if form.cleaned_data['direction'] == 'incoming':
|
||||
msgtype = 'msgin'
|
||||
else:
|
||||
msgtype = 'msgout'
|
||||
|
||||
submission, submission_email_event = (
|
||||
add_submission_email(request=request,
|
||||
remote_ip=remote_ip(request),
|
||||
name = form.draft_name,
|
||||
rev=form.revision,
|
||||
submission_pk = submission_pk,
|
||||
message = message,
|
||||
by = request.user.person,
|
||||
msgtype = msgtype) )
|
||||
|
||||
messages.success(request, 'Email added.')
|
||||
|
||||
try:
|
||||
draft = Document.objects.get(name=submission.name)
|
||||
except Document.DoesNotExist:
|
||||
# Assume this is revision 00 - we'll do this later
|
||||
draft = None
|
||||
|
||||
if (draft != None):
|
||||
e = AddedMessageEvent(type="added_message", doc=draft)
|
||||
e.message = submission_email_event.submissionemailevent.message
|
||||
e.msgtype = submission_email_event.submissionemailevent.msgtype
|
||||
e.in_reply_to = submission_email_event.submissionemailevent.in_reply_to
|
||||
e.by = request.user.person
|
||||
e.desc = submission_email_event.desc
|
||||
e.time = submission_email_event.time
|
||||
e.save()
|
||||
|
||||
return redirect("ietf.submit.views.manualpost")
|
||||
except ValidationError as e:
|
||||
form = SubmissionEmailForm(request.POST)
|
||||
form._errors = {}
|
||||
form._errors["__all__"] = form.error_class(["There was a failure uploading your message. (%s)" % e.message])
|
||||
else:
|
||||
initial = {
|
||||
return render(
|
||||
request,
|
||||
'submit/manual_post.html',
|
||||
{
|
||||
'manual': manual,
|
||||
'selected': 'manual_posts'
|
||||
}
|
||||
|
||||
if (submission_id != None):
|
||||
submission = get_submission_or_404(submission_id, access_token)
|
||||
initial['name'] = "{}-{}".format(submission.name, submission.rev)
|
||||
initial['direction'] = 'incoming'
|
||||
initial['submission_pk'] = submission.pk
|
||||
else:
|
||||
initial['direction'] = 'incoming'
|
||||
|
||||
form = SubmissionEmailForm(initial=initial)
|
||||
|
||||
return render(request, 'submit/add_submit_email.html',dict(form=form))
|
||||
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def send_submission_email(request, submission_id, message_id=None):
|
||||
"""Send an email related to a submission"""
|
||||
submission = get_submission_or_404(submission_id, access_token = None)
|
||||
|
||||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
return redirect('ietf.submit.views.submission_status',
|
||||
submission_id=submission.id,
|
||||
access_token=submission.access_token())
|
||||
|
||||
form = MessageModelForm(request.POST)
|
||||
if form.is_valid():
|
||||
# create Message
|
||||
msg = Message.objects.create(
|
||||
by = request.user.person,
|
||||
subject = form.cleaned_data['subject'],
|
||||
frm = form.cleaned_data['frm'],
|
||||
to = form.cleaned_data['to'],
|
||||
cc = form.cleaned_data['cc'],
|
||||
bcc = form.cleaned_data['bcc'],
|
||||
reply_to = form.cleaned_data['reply_to'],
|
||||
body = form.cleaned_data['body']
|
||||
)
|
||||
|
||||
in_reply_to_id = form.cleaned_data['in_reply_to_id']
|
||||
in_reply_to = None
|
||||
rp = ""
|
||||
|
||||
if in_reply_to_id:
|
||||
rp = " reply"
|
||||
try:
|
||||
in_reply_to = Message.objects.get(id=in_reply_to_id)
|
||||
except Message.DoesNotExist:
|
||||
log("Unable to retrieve in_reply_to message: %s" % in_reply_to_id)
|
||||
|
||||
desc = "Sent message {} - manual post - {}-{}".format(rp,
|
||||
submission.name,
|
||||
submission.rev)
|
||||
SubmissionEmailEvent.objects.create(
|
||||
submission = submission,
|
||||
desc = desc,
|
||||
msgtype = 'msgout',
|
||||
by = request.user.person,
|
||||
message = msg,
|
||||
in_reply_to = in_reply_to)
|
||||
|
||||
# send email
|
||||
send_mail_message(None,msg)
|
||||
|
||||
messages.success(request, 'Email sent.')
|
||||
return redirect('ietf.submit.views.submission_status',
|
||||
submission_id=submission.id,
|
||||
access_token=submission.access_token())
|
||||
|
||||
else:
|
||||
reply_to = get_reply_to()
|
||||
msg = None
|
||||
|
||||
if not message_id:
|
||||
addrs = gather_address_lists('sub_confirmation_requested',submission=submission).as_strings(compact=False)
|
||||
to_email = addrs.to
|
||||
cc = addrs.cc
|
||||
subject = 'Regarding {}'.format(submission.name)
|
||||
else:
|
||||
try:
|
||||
submitEmail = SubmissionEmailEvent.objects.get(id=message_id)
|
||||
msg = submitEmail.message
|
||||
|
||||
if msg:
|
||||
to_email = msg.frm
|
||||
cc = msg.cc
|
||||
subject = 'Re:{}'.format(msg.subject)
|
||||
else:
|
||||
to_email = None
|
||||
cc = None
|
||||
subject = 'Regarding {}'.format(submission.name)
|
||||
except Message.DoesNotExist:
|
||||
to_email = None
|
||||
cc = None
|
||||
subject = 'Regarding {}'.format(submission.name)
|
||||
|
||||
initial = {
|
||||
'to': to_email,
|
||||
'cc': cc,
|
||||
'frm': settings.IDSUBMIT_FROM_EMAIL,
|
||||
'subject': subject,
|
||||
'reply_to': reply_to,
|
||||
}
|
||||
|
||||
if msg:
|
||||
initial['in_reply_to_id'] = msg.id
|
||||
|
||||
form = MessageModelForm(initial=initial)
|
||||
|
||||
return render(request, "submit/email.html", {
|
||||
'submission': submission,
|
||||
'access_token': submission.access_token(),
|
||||
'form':form})
|
||||
|
||||
|
||||
def show_submission_email_message(request, submission_id, message_id, access_token=None):
|
||||
submission = get_submission_or_404(submission_id, access_token)
|
||||
|
||||
submitEmail = get_object_or_404(SubmissionEmailEvent, pk=message_id)
|
||||
attachments = submitEmail.message.messageattachment_set.all()
|
||||
|
||||
return render(request, 'submit/submission_email.html',
|
||||
{'submission': submission,
|
||||
'message': submitEmail,
|
||||
'attachments': attachments})
|
||||
|
||||
def show_submission_email_attachment(request, submission_id, message_id, filename, access_token=None):
|
||||
get_submission_or_404(submission_id, access_token)
|
||||
|
||||
message = get_object_or_404(SubmissionEmailEvent, pk=message_id)
|
||||
|
||||
attach = get_object_or_404(MessageAttachment,
|
||||
message=message.message,
|
||||
filename=filename)
|
||||
|
||||
if attach.encoding == "base64":
|
||||
body = base64.b64decode(attach.body)
|
||||
else:
|
||||
body = attach.body.encode('utf-8')
|
||||
|
||||
if attach.content_type is None:
|
||||
content_type='text/plain'
|
||||
else:
|
||||
content_type=attach.content_type
|
||||
|
||||
response = HttpResponse(body, content_type=content_type)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s' % attach.filename
|
||||
response['Content-Length'] = len(body)
|
||||
return response
|
||||
)
|
||||
|
||||
|
||||
def get_submission_or_404(submission_id, access_token=None):
|
||||
|
|
|
@ -802,6 +802,10 @@ def post_approved_draft(url, name):
|
|||
the data from the Datatracker and start processing it. Returns
|
||||
response and error (empty string if no error)."""
|
||||
|
||||
if settings.SERVER_MODE != "production":
|
||||
log(f"In production, would have posted RFC-Editor notification of approved I-D '{name}' to '{url}'")
|
||||
return "", ""
|
||||
|
||||
# HTTP basic auth
|
||||
username = "dtracksync"
|
||||
password = settings.RFC_EDITOR_SYNC_PASSWORD
|
||||
|
|
|
@ -597,6 +597,29 @@ class RFCSyncTests(TestCase):
|
|||
auth48_docurl = draft.documenturl_set.filter(tag_id='auth48').first()
|
||||
self.assertIsNone(auth48_docurl)
|
||||
|
||||
def test_post_approved_draft_in_production_only(self):
|
||||
self.requests_mock.post("https://rfceditor.example.com/", status_code=200, text="OK")
|
||||
|
||||
# be careful playing with SERVER_MODE!
|
||||
with override_settings(SERVER_MODE="test"):
|
||||
self.assertEqual(
|
||||
rfceditor.post_approved_draft("https://rfceditor.example.com/", "some-draft"),
|
||||
("", "")
|
||||
)
|
||||
self.assertFalse(self.requests_mock.called)
|
||||
with override_settings(SERVER_MODE="development"):
|
||||
self.assertEqual(
|
||||
rfceditor.post_approved_draft("https://rfceditor.example.com/", "some-draft"),
|
||||
("", "")
|
||||
)
|
||||
self.assertFalse(self.requests_mock.called)
|
||||
with override_settings(SERVER_MODE="production"):
|
||||
self.assertEqual(
|
||||
rfceditor.post_approved_draft("https://rfceditor.example.com/", "some-draft"),
|
||||
("", "")
|
||||
)
|
||||
self.assertTrue(self.requests_mock.called)
|
||||
|
||||
|
||||
class DiscrepanciesTests(TestCase):
|
||||
def test_discrepancies(self):
|
||||
|
@ -636,6 +659,7 @@ class DiscrepanciesTests(TestCase):
|
|||
r = self.client.get(urlreverse("ietf.sync.views.discrepancies"))
|
||||
self.assertContains(r, doc.name)
|
||||
|
||||
|
||||
class RFCEditorUndoTests(TestCase):
|
||||
def test_rfceditor_undo(self):
|
||||
draft = WgDraftFactory()
|
||||
|
|
|
@ -267,7 +267,7 @@
|
|||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
{% if not doc.stream_id == 'iab' %}
|
||||
{% if doc.stream_id != 'iab' and doc.stream_id != 'editorial' %}
|
||||
<tbody class="meta border-top">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
|
@ -447,125 +447,127 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
{% endif %}
|
||||
{% if can_edit_iana_state or iana_review_state or iana_experts_state or iana_experts_comment %}
|
||||
<tbody class="meta border-top">
|
||||
{% if iana_review_state or can_edit_iana_state %}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
IANA
|
||||
</th>
|
||||
<th scope="row">
|
||||
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iana-review" %}">
|
||||
IANA review state
|
||||
</a>
|
||||
</th>
|
||||
<td class="edit">
|
||||
{% if can_edit_iana_state and not snapshot %}
|
||||
<a class="btn btn-primary btn-sm"
|
||||
href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-review" %}">
|
||||
Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{% if iana_review_state|slugify|slice:7 != "iana-ok" %}text-danger{% else %}text-success{% endif %}">
|
||||
{% if not iana_review_state %}
|
||||
<span class="text-body-secondary">
|
||||
(None)
|
||||
</span>
|
||||
{% else %}
|
||||
{{ iana_review_state }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if iana_action_state or can_edit_iana_state %}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{% if not can_edit_iana_state and not iana_review_state %}IANA{% endif %}
|
||||
</th>
|
||||
<th scope="row">
|
||||
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iana-action" %}">
|
||||
IANA action state
|
||||
</a>
|
||||
</th>
|
||||
<td class="edit">
|
||||
{% if can_edit_iana_state and not snapshot %}
|
||||
<a class="btn btn-primary btn-sm"
|
||||
href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-action" %}">
|
||||
Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not iana_action_state %}
|
||||
<span class="text-body-secondary">
|
||||
(None)
|
||||
</span>
|
||||
{% else %}
|
||||
{{ iana_action_state }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if iana_experts_state or can_edit_iana_state %}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{% if not can_edit_iana_state and not iana_review_state and not iana_action_state %}IANA{% endif %}
|
||||
</th>
|
||||
<th scope="row">
|
||||
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iana-experts" %}">
|
||||
IANA expert review state
|
||||
</a>
|
||||
</th>
|
||||
<td class="edit">
|
||||
{% if can_edit_iana_state and not snapshot %}
|
||||
<a class="btn btn-primary btn-sm"
|
||||
href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-experts" %}">
|
||||
Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{% if iana_experts_state|slugify|slice:"-2:" != "ok" %}text-danger{% else %}text-success{% endif %}">
|
||||
{% if not iana_experts_state %}
|
||||
<span class="text-body-secondary">
|
||||
(None)
|
||||
</span>
|
||||
{% else %}
|
||||
{{ iana_experts_state }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if iana_experts_comment or can_edit_iana_state %}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{% if not can_edit_iana_state and not iana_review_state and not iana_action_state and not iana_experts_state %}
|
||||
{% if doc.stream_id != 'editorial' %}
|
||||
{% if can_edit_iana_state or iana_review_state or iana_experts_state or iana_experts_comment %}
|
||||
<tbody class="meta border-top">
|
||||
{% if iana_review_state or can_edit_iana_state %}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
IANA
|
||||
{% endif %}
|
||||
</th>
|
||||
<th scope="row">
|
||||
IANA expert review comments
|
||||
</th>
|
||||
<td class="edit">
|
||||
{% if can_edit_iana_state and not snapshot %}
|
||||
<a class="btn btn-primary btn-sm"
|
||||
href="{% url 'ietf.doc.views_draft.add_iana_experts_comment' name=doc.name %}">
|
||||
Edit
|
||||
</th>
|
||||
<th scope="row">
|
||||
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iana-review" %}">
|
||||
IANA review state
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not iana_experts_comment %}
|
||||
<span class="text-body-secondary">
|
||||
(None)
|
||||
</span>
|
||||
{% else %}
|
||||
{{ iana_experts_comment }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</th>
|
||||
<td class="edit">
|
||||
{% if can_edit_iana_state and not snapshot %}
|
||||
<a class="btn btn-primary btn-sm"
|
||||
href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-review" %}">
|
||||
Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{% if iana_review_state|slugify|slice:7 != "iana-ok" %}text-danger{% else %}text-success{% endif %}">
|
||||
{% if not iana_review_state %}
|
||||
<span class="text-body-secondary">
|
||||
(None)
|
||||
</span>
|
||||
{% else %}
|
||||
{{ iana_review_state }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if iana_action_state or can_edit_iana_state %}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{% if not can_edit_iana_state and not iana_review_state %}IANA{% endif %}
|
||||
</th>
|
||||
<th scope="row">
|
||||
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iana-action" %}">
|
||||
IANA action state
|
||||
</a>
|
||||
</th>
|
||||
<td class="edit">
|
||||
{% if can_edit_iana_state and not snapshot %}
|
||||
<a class="btn btn-primary btn-sm"
|
||||
href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-action" %}">
|
||||
Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not iana_action_state %}
|
||||
<span class="text-body-secondary">
|
||||
(None)
|
||||
</span>
|
||||
{% else %}
|
||||
{{ iana_action_state }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if iana_experts_state or can_edit_iana_state %}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{% if not can_edit_iana_state and not iana_review_state and not iana_action_state %}IANA{% endif %}
|
||||
</th>
|
||||
<th scope="row">
|
||||
<a href="{% url "ietf.doc.views_help.state_help" type="draft-iana-experts" %}">
|
||||
IANA expert review state
|
||||
</a>
|
||||
</th>
|
||||
<td class="edit">
|
||||
{% if can_edit_iana_state and not snapshot %}
|
||||
<a class="btn btn-primary btn-sm"
|
||||
href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-experts" %}">
|
||||
Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="{% if iana_experts_state|slugify|slice:"-2:" != "ok" %}text-danger{% else %}text-success{% endif %}">
|
||||
{% if not iana_experts_state %}
|
||||
<span class="text-body-secondary">
|
||||
(None)
|
||||
</span>
|
||||
{% else %}
|
||||
{{ iana_experts_state }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if iana_experts_comment or can_edit_iana_state %}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{% if not can_edit_iana_state and not iana_review_state and not iana_action_state and not iana_experts_state %}
|
||||
IANA
|
||||
{% endif %}
|
||||
</th>
|
||||
<th scope="row">
|
||||
IANA expert review comments
|
||||
</th>
|
||||
<td class="edit">
|
||||
{% if can_edit_iana_state and not snapshot %}
|
||||
<a class="btn btn-primary btn-sm"
|
||||
href="{% url 'ietf.doc.views_draft.add_iana_experts_comment' name=doc.name %}">
|
||||
Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not iana_experts_comment %}
|
||||
<span class="text-body-secondary">
|
||||
(None)
|
||||
</span>
|
||||
{% else %}
|
||||
{{ iana_experts_comment }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if rfc_editor_state %}
|
||||
<tbody class="meta border-top">
|
||||
|
|
|
@ -23,12 +23,17 @@
|
|||
{% if type.stategroups != None %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'ietf.help.views.state' doc=type.slug %}">{{ type.slug }}</a>
|
||||
<a href="{% url 'ietf.doc.views_help.state_help' type=type.slug %}">{{ type.slug }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% for group in type.stategroups %}
|
||||
{# djlint:off #}
|
||||
<a href="{% url 'ietf.help.views.state' doc=type.slug type=group %}">{{ group }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% if type.slug == "draft" %}
|
||||
<a href="{% url 'ietf.doc.views_help.state_help' type=type.slug|add:'-'|add:group %}">
|
||||
{% else %}
|
||||
<a href="{% url 'ietf.help.views.state' doc=type.slug type=group %}">
|
||||
{% endif %}
|
||||
{{ group }}</a>{% if not forloop.last %},{% endif %}
|
||||
{# djlint:on #}
|
||||
{% endfor %}
|
||||
</td>
|
|
@ -105,11 +105,17 @@
|
|||
{% if reviewersettings.history.all %}
|
||||
<tbody>
|
||||
{% for h in reviewersettings.history.all %}
|
||||
{% if h|simple_history_delta_change_cnt > 0 or h.history_change_reason != "skipped" and h.history_change_reason %}
|
||||
<tr>
|
||||
<td>{{ h.history_date|date }}</td>
|
||||
<td>{% person_link h.history_user.person %}</td>
|
||||
<td>{{ h.history_change_reason }}</td>
|
||||
<td>{% if h.history_change_reason != "skipped" and h.history_change_reason %} {{ h.history_change_reason }}<br> {% endif %}
|
||||
{% for change in h|simple_history_delta_changes %}
|
||||
{{ change.field }} changed from "{{change.old}}" to "{{change.new}}"<br>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin static ietf_filters textfilters %}
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
|
||||
{% endblock %}
|
||||
{% block title %}{{ type.label|cut:"state"|cut:"state" }} states{% endblock %}
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>{{ type.label|cut:"state"|cut:"State" }} states</h1>
|
||||
<table class="table table-sm table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" data-sort="state">State</th>
|
||||
<th scope="col" data-sort="desc">Description</th>
|
||||
<th scope="col" data-sort="next">Next states</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for state in states %}
|
||||
<tr>
|
||||
<th scope="row">{{ state.name }}</th>
|
||||
<td>{{ state.desc|urlize_ietf_docs|linkify }}</td>
|
||||
<td>
|
||||
<ul>
|
||||
{% for s in state.next_states.all %}
|
||||
<li>{{ s.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
<script src="{% static "ietf/js/list.js" %}"></script>
|
||||
{% endblock %}
|
|
@ -5,10 +5,16 @@
|
|||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
|
||||
{% endblock %}
|
||||
{% block title %}Non-Working Group email lists{% endblock %}
|
||||
{% block title %}Other (not WG) email lists{% endblock %}
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Non-Working Group email lists</h1>
|
||||
<h1>Other (not Working Group) email lists</h1>
|
||||
<p>Guidelines for these lists, including how to request a
|
||||
new one to be created, are at
|
||||
<a href="https://www.ietf.org/how/lists/nonwglist-guidelines/">
|
||||
https://www.ietf.org/how/lists/nonwglist-guidelines/
|
||||
</a>
|
||||
</p>
|
||||
{% cache 900 nonwglisttable %}
|
||||
<table class="table table-sm table-striped tablesorter">
|
||||
<thead>
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
<small class="text-body-secondary">{{ form.nominee.email.person.name }} for {{ form.position.name }}</small>
|
||||
</h2>
|
||||
<p class="mt-3">
|
||||
Provide feedback about {% person_link form.nominee.person %}
|
||||
Provide feedback about {% person_link form.nominee.person with_email=False %}
|
||||
for the <b>{{ form.position.name }}</b> position.
|
||||
</p>
|
||||
{% elif form.topic %}
|
||||
|
@ -97,8 +97,8 @@
|
|||
{% endif %}
|
||||
<p>
|
||||
This feedback will only be available to
|
||||
<a href="{% url 'ietf.nomcom.views.year_index' year=year %}">NomCom {{ year }}</a>.
|
||||
You may have the feedback mailed back to you by selecting the option below.
|
||||
<a href="{% url 'ietf.nomcom.views.year_index' year=year %}">the current NomCom</a>.
|
||||
You can have the feedback mailed back to you by selecting the option below.
|
||||
</p>
|
||||
<form class="float-start" id="feedbackform" method="post">
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{% if email and email == "system@datatracker.ietf.org" or name and name == "(System)" %}<span class="text-body-secondary">(System)</span>{% else %}<span {% if class %}class="{{ class }}"
|
||||
{% endif %}>{% if email or name %}<a {% if class %}class="text-reset"{% endif %}
|
||||
title="{% if title %}{{ title }}{% else %}Datatracker profile of {{ name }}.{% endif %}"
|
||||
title="{% if title %}{{ title }}{% else %}Datatracker profile of {{ name }}{% endif %}"
|
||||
{% if email %} href="{% url 'ietf.person.views.profile' email_or_name=email %}" {% else %} href="{% url 'ietf.person.views.profile' email_or_name=name %}" {% endif %}>{{ name }}</a>{% if email and with_email %} <a {% if class %}class="text-reset"{% endif %}
|
||||
href="mailto:{{ email|urlencode }}"
|
||||
aria-label="Compose email to {{ email }}."
|
||||
title="Compose email to {{ email }}.">
|
||||
aria-label="Compose email to {{ email }}"
|
||||
title="Compose email to {{ email }}">
|
||||
<i class="bi bi-envelope"></i></a>{% endif %}{% else %}<span class="text-body-secondary">(None)</span>{% endif %}</span>{% endif %}
|
|
@ -2,7 +2,7 @@
|
|||
{% load origin static person_filters %}
|
||||
<div class="card shadow-sm mb-3 text-center photo">
|
||||
{% if name or email %}
|
||||
<a title="Datatracker profile of {{ person.name }}."
|
||||
<a title="Datatracker profile of {{ person.name }}"
|
||||
href="{% if name %}{% url 'ietf.person.views.profile' email_or_name=name %}{% else %}{% url 'ietf.person.views.profile' email_or_name=email %}{% endif %}">
|
||||
{% endif %}
|
||||
{% if person.photo_thumb %}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load django_bootstrap5 %}
|
||||
{% block title %}
|
||||
{% if submission == None %}
|
||||
Add new submission request email
|
||||
{% else %}
|
||||
Add submission request email to {{ submission.name }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Add email</h1>
|
||||
<p class="my-3">
|
||||
{% if submission == None %}
|
||||
A new submission request will be created for the given name and revision. The
|
||||
name must take the form <code>draft-xxx-nn</code> where <code>xxx</code> is lowercase letters, digits or dashes
|
||||
and <code>nn</code> is the revision number, <code>00</code> for the initial revision. For example,
|
||||
<code>draft-my-spec-00</code>.
|
||||
{% else %}
|
||||
The email will be added to the submission history for {{ submission.name }}.
|
||||
{% endif %}
|
||||
</p>
|
||||
<form class="add-email my-3" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
<button type="submit" class="btn btn-primary">Add Email</button>
|
||||
<a class="btn btn-secondary float-end"
|
||||
href="{% url "ietf.submit.views.manualpost" %}">Back</a>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,25 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
{% load static %}
|
||||
{% load django_bootstrap5 %}
|
||||
{% block title %}Email related to{{ submission.name }}{% endblock %}
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'ietf/css/datepicker.css' %}">
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>
|
||||
Email related to
|
||||
<br>
|
||||
<small class="text-body-secondary">{{ submission.name }}</small>
|
||||
</h1>
|
||||
<form method="post" class="show-required my-3">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
<button type="submit" class="btn btn-primary">Send email</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
<script src="{% static 'ietf/js/datepicker.js' %}"></script>
|
||||
{% endblock %}
|
|
@ -58,76 +58,6 @@
|
|||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<h2 class="mt-3" id="id_upload">Submissions awaiting Internet-Draft upload</h2>
|
||||
{% if not waiting_for_draft %}
|
||||
<p class="alert alert-info my-3" id="no-waiting-for-draft">
|
||||
There are no submissions awaiting Internet-Draft upload.
|
||||
</p>
|
||||
{% else %}
|
||||
<table id="waiting-for-draft"
|
||||
class="waiting-for-draft table table-sm table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" data-sort="name">Name</th>
|
||||
<th scope="col" data-sort="rev">Rev</th>
|
||||
<th scope="col" data-sort="date">Submitted</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in waiting_for_draft %}
|
||||
<tr>
|
||||
{% if user.is_authenticated %}
|
||||
<td>
|
||||
<a id="aw{{ s.pk }}"
|
||||
href="{% url "ietf.submit.views.submission_status" submission_id=s.pk access_token=s.access_token %}">
|
||||
{{ s.name }}
|
||||
</a>
|
||||
</td>
|
||||
{% else %}
|
||||
<td>
|
||||
<a id="aw{{ s.pk }}"
|
||||
href="{% url "ietf.submit.views.submission_status" submission_id=s.pk %}">{{ s.name }}</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>{{ s.rev }}</td>
|
||||
<td>{{ s.submission_date }}</td>
|
||||
<td>
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<form id="cancel-submission"
|
||||
action="/submit/awaitingdraft/cancel"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="submission_id" value="{{ s.pk }}">
|
||||
<input type="hidden" name="access_token" value="{{ s.access_token }}">
|
||||
<button class="btn btn-danger btn-sm float-end ms-1"
|
||||
type="submit"
|
||||
data-bs-toggle="tooltip"
|
||||
title="Cancels the submission permanently.">
|
||||
Cancel submission
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<a id="add-submission-email{{ s.pk }}"
|
||||
class="btn btn-primary btn-sm float-end ms-1"
|
||||
href="{% url "ietf.submit.views.add_manualpost_email" submission_id=s.pk access_token=s.access_token %}">
|
||||
Add email
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<a id="new-submission-email"
|
||||
class="btn btn-primary"
|
||||
href="{% url "ietf.submit.views.add_manualpost_email" %}">
|
||||
New submission from email
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
<script src="{% static "ietf/js/list.js" %}"></script>
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
{% extends "submit/submit_base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{% load origin static textfilters ietf_filters %}
|
||||
{% block title %}Submission email{% endblock %}
|
||||
{% load ietf_filters %}
|
||||
{% block submit_content %}
|
||||
{% origin %}
|
||||
<h2 id="approvals">Email for {{ submission.name }}</h2>
|
||||
<dl id="email-details" class="emails row my-3">
|
||||
<dt class="col-sm-2">
|
||||
Uploaded
|
||||
</dt>
|
||||
<dd class="col-sm-10">
|
||||
{{ message.time }}
|
||||
</dd>
|
||||
<dt class="col-sm-2">
|
||||
Date
|
||||
</dt>
|
||||
<dd class="col-sm-10">
|
||||
{{ message.message.time }}
|
||||
</dd>
|
||||
<dt class="col-sm-2">
|
||||
From
|
||||
</dt>
|
||||
<dd class="col-sm-10">
|
||||
{{ message.message.frm|linkify }}
|
||||
</dd>
|
||||
<dt class="col-sm-2">
|
||||
Subject
|
||||
</dt>
|
||||
<dd class="col-sm-10">
|
||||
{{ message.message.subject }}
|
||||
</dd>
|
||||
<dt class="col-sm-2">
|
||||
Message
|
||||
</dt>
|
||||
<dd class="col-sm-10">
|
||||
<pre>{{ message.message.body|urlize_ietf_docs|linkify|linebreaksbr }}</pre>
|
||||
</dd>
|
||||
<dt class="col-sm-2">
|
||||
Attachment
|
||||
</dt>
|
||||
<dd class="col-sm-10">
|
||||
{% for a in attachments %}
|
||||
<a id="attach{{ submission.pk }}"
|
||||
href="{% url "ietf.submit.views.show_submission_email_attachment" submission_id=submission.pk message_id=message.pk filename=a.filename %}">
|
||||
{{ a.filename }}
|
||||
</a>
|
||||
<br>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
</dl>
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<a id="reply{{ submission.pk }}"
|
||||
class="btn btn-primary"
|
||||
href="{% url "ietf.submit.views.send_submission_email" submission_id=submission.pk message_id=message.pk %}"
|
||||
title="Reply">
|
||||
<i class="bi bi-envelope" aria-hidden="true"></i> Reply
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -520,14 +520,6 @@
|
|||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<a id="send{{ submission.pk }}"
|
||||
class="btn btn-primary mt-3"
|
||||
href="{% url "ietf.submit.views.send_submission_email" submission_id=submission.pk %}"
|
||||
title="Email submitter">
|
||||
<i class="bi bi-envelope" aria-hidden="true"></i> Send Email
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if show_send_full_url %}
|
||||
<div class="alert alert-info my-3">
|
||||
<p>
|
||||
|
@ -584,39 +576,10 @@
|
|||
{% endif %}
|
||||
|
||||
</td>
|
||||
{% if e.desc|startswith:"Received message" or e.desc|startswith:"Sent message" %}
|
||||
{% with m=e.submissionemailevent.message %}
|
||||
{% if user.is_authenticated %}
|
||||
<td>
|
||||
{% if e.desc|startswith:"Received message" and user|has_role:"Secretariat" %}
|
||||
<a id="reply{{ submission.pk }}"
|
||||
class="btn btn-primary btn-sm"
|
||||
href="{% url "ietf.submit.views.send_submission_email" submission_id=submission.pk message_id=e.submissionemailevent.pk %}"
|
||||
title="Reply">
|
||||
<i class="bi bi-envelope" aria-hidden="true"></i> Reply
|
||||
</a>
|
||||
{% endif %}
|
||||
Email:
|
||||
<a id="aw{{ submission.pk }}-{{ m.pk }}"
|
||||
href="{% url "ietf.submit.views.show_submission_email_message" submission_id=submission.pk message_id=e.submissionemailevent.pk access_token=submission.access_token %}">
|
||||
{{ e.desc }}
|
||||
</a>
|
||||
</td>
|
||||
{% else %}
|
||||
<td>
|
||||
Email:
|
||||
<a id="aw{{ submission.pk }}-{{ m.pk }}"
|
||||
href="{% url "ietf.submit.views.show_submission_email_message" submission_id=submission.pk message_id=e.submissionemailevent.pk %}">
|
||||
{{ e.desc }}
|
||||
</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<td>
|
||||
{{ e.desc|urlize_ietf_docs|linkify }}
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
<td>
|
||||
{{ e.desc|urlize_ietf_docs|linkify }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
import datetime
|
||||
|
||||
from decorator import decorator, decorate
|
||||
from functools import wraps
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -20,25 +19,29 @@ from ietf.utils.test_runner import set_coverage_checking
|
|||
from ietf.person.models import Person, PersonalApiKey, PersonApiKeyEvent
|
||||
from ietf.utils import log
|
||||
|
||||
@decorator
|
||||
def skip_coverage(f, *args, **kwargs):
|
||||
if settings.TEST_CODE_COVERAGE_CHECKER:
|
||||
set_coverage_checking(False)
|
||||
result = f(*args, **kwargs)
|
||||
set_coverage_checking(True)
|
||||
return result
|
||||
else:
|
||||
return f(*args, **kwargs)
|
||||
def skip_coverage(f):
|
||||
@wraps(f)
|
||||
def _wrapper(*args, **kwargs):
|
||||
if settings.TEST_CODE_COVERAGE_CHECKER:
|
||||
set_coverage_checking(False)
|
||||
result = f(*args, **kwargs)
|
||||
set_coverage_checking(True)
|
||||
return result
|
||||
else:
|
||||
return f(*args, **kwargs)
|
||||
return _wrapper
|
||||
|
||||
@decorator
|
||||
def person_required(f, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
raise ValueError("The @person_required decorator should be called after @login_required.")
|
||||
try:
|
||||
request.user.person
|
||||
except Person.DoesNotExist:
|
||||
return render(request, 'registration/missing_person.html')
|
||||
return f(request, *args, **kwargs)
|
||||
def person_required(f):
|
||||
@wraps(f)
|
||||
def _wrapper(request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
raise ValueError("The @person_required decorator should be called after @login_required.")
|
||||
try:
|
||||
request.user.person
|
||||
except Person.DoesNotExist:
|
||||
return render(request, 'registration/missing_person.html')
|
||||
return f(request, *args, **kwargs)
|
||||
return _wrapper
|
||||
|
||||
|
||||
def require_api_key(f):
|
||||
|
@ -90,29 +93,31 @@ def require_api_key(f):
|
|||
return _wrapper
|
||||
|
||||
|
||||
def _memoize(func, self, *args, **kwargs):
|
||||
'''Memoize wrapper for instance methods. Use @lru_cache for functions.'''
|
||||
if kwargs: # frozenset is used to ensure hashability
|
||||
key = args, frozenset(list(kwargs.items()))
|
||||
else:
|
||||
key = args
|
||||
# instance method, set up cache if needed
|
||||
if not hasattr(self, '_cache'):
|
||||
self._cache = {}
|
||||
if not func in self._cache:
|
||||
self._cache[func] = {}
|
||||
#
|
||||
cache = self._cache[func]
|
||||
if key not in cache:
|
||||
cache[key] = func(self, *args, **kwargs)
|
||||
return cache[key]
|
||||
def memoize(func):
|
||||
@wraps(func)
|
||||
def _memoize(self, *args, **kwargs):
|
||||
'''Memoize wrapper for instance methods. Use @lru_cache for functions.'''
|
||||
if kwargs: # frozenset is used to ensure hashability
|
||||
key = args, frozenset(list(kwargs.items()))
|
||||
else:
|
||||
key = args
|
||||
# instance method, set up cache if needed
|
||||
if not hasattr(self, '_cache'):
|
||||
self._cache = {}
|
||||
if not func in self._cache:
|
||||
self._cache[func] = {}
|
||||
#
|
||||
cache = self._cache[func]
|
||||
if key not in cache:
|
||||
cache[key] = func(self, *args, **kwargs)
|
||||
return cache[key]
|
||||
|
||||
if not hasattr(func, '__class__'):
|
||||
raise NotImplementedError("Use @lru_cache instead of memoize() for functions.")
|
||||
# For methods, we want the cache on the object, not on the class, in order
|
||||
# to not having to think about cache bloat and content becoming stale, so
|
||||
# we cannot set up the cache here.
|
||||
return decorate(func, _memoize)
|
||||
return _memoize
|
||||
|
||||
|
||||
def ignore_view_kwargs(*args):
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"vueCompilerOptions": {
|
||||
"target": 3,
|
||||
"plugins": [
|
||||
"@volar/vue-language-plugin-pug"
|
||||
"@vue/language-plugin-pug"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
11
package.json
11
package.json
|
@ -21,8 +21,8 @@
|
|||
"bootstrap": "5.3.3",
|
||||
"bootstrap-icons": "1.11.3",
|
||||
"browser-fs-access": "0.35.0",
|
||||
"caniuse-lite": "1.0.30001597",
|
||||
"d3": "7.8.5",
|
||||
"caniuse-lite": "1.0.30001599",
|
||||
"d3": "7.9.0",
|
||||
"file-saver": "2.0.5",
|
||||
"highcharts": "11.4.0",
|
||||
"ical.js": "1.5.0",
|
||||
|
@ -56,6 +56,7 @@
|
|||
"@parcel/transformer-sass": "2.12.0",
|
||||
"@rollup/pluginutils": "5.1.0",
|
||||
"@vitejs/plugin-vue": "4.6.2",
|
||||
"@vue/language-plugin-pug": "2.0.7",
|
||||
"browserlist": "latest",
|
||||
"c8": "9.1.0",
|
||||
"eslint": "8.57.0",
|
||||
|
@ -65,12 +66,12 @@
|
|||
"eslint-plugin-n": "16.6.2",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-vue": "9.22.0",
|
||||
"html-validate": "8.15.0",
|
||||
"eslint-plugin-vue": "9.23.0",
|
||||
"html-validate": "8.16.0",
|
||||
"jquery-migrate": "3.4.1",
|
||||
"parcel": "2.12.0",
|
||||
"pug": "3.0.2",
|
||||
"sass": "1.71.1",
|
||||
"sass": "1.72.0",
|
||||
"seedrandom": "3.0.5",
|
||||
"vite": "4.5.2"
|
||||
},
|
||||
|
|
14
playwright/package-lock.json
generated
14
playwright/package-lock.json
generated
|
@ -22,7 +22,7 @@
|
|||
"eslint-plugin-n": "16.6.2",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"npm-check-updates": "16.14.15"
|
||||
"npm-check-updates": "16.14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
|
@ -3804,9 +3804,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/npm-check-updates": {
|
||||
"version": "16.14.15",
|
||||
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.15.tgz",
|
||||
"integrity": "sha512-WH0wJ9j6CP7Azl+LLCxWAYqroT2IX02kRIzgK/fg0rPpMbETgHITWBdOPtrv521xmA3JMgeNsQ62zvVtS/nCmQ==",
|
||||
"version": "16.14.17",
|
||||
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.17.tgz",
|
||||
"integrity": "sha512-ElnDdXKe60f8S6RhzFeaGuH2TFJmt2cU2HjLdowldabdm27nWFCxV2ebeP3xGbQkzp2+RPDQNdW9HqU1lcY8ag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
|
@ -8721,9 +8721,9 @@
|
|||
}
|
||||
},
|
||||
"npm-check-updates": {
|
||||
"version": "16.14.15",
|
||||
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.15.tgz",
|
||||
"integrity": "sha512-WH0wJ9j6CP7Azl+LLCxWAYqroT2IX02kRIzgK/fg0rPpMbETgHITWBdOPtrv521xmA3JMgeNsQ62zvVtS/nCmQ==",
|
||||
"version": "16.14.17",
|
||||
"resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.17.tgz",
|
||||
"integrity": "sha512-ElnDdXKe60f8S6RhzFeaGuH2TFJmt2cU2HjLdowldabdm27nWFCxV2ebeP3xGbQkzp2+RPDQNdW9HqU1lcY8ag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^5.3.0",
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"eslint-plugin-n": "16.6.2",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"npm-check-updates": "16.14.15"
|
||||
"npm-check-updates": "16.14.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "8.4.1",
|
||||
|
|
|
@ -8,8 +8,6 @@ bleach>=6
|
|||
types-bleach>=6
|
||||
celery>=5.2.6
|
||||
coverage>=4.5.4,<5.0 # Coverage 5.x moves from a json database to SQLite. Moving to 5.x will require substantial rewrites in ietf.utils.test_runner and ietf.release.views
|
||||
decorator>=5.1.1
|
||||
types-decorator>=5.1.1
|
||||
defusedxml>=0.7.1 # for TastyPie when using xml; not a declared dependency
|
||||
Django>4.2,<5
|
||||
django-analytical>=3.1.0
|
||||
|
@ -56,7 +54,7 @@ pyquery>=1.4.3
|
|||
python-dateutil>=2.8.2
|
||||
types-python-dateutil>=2.8.2
|
||||
python-magic==0.4.18 # Versions beyond the yanked .19 and .20 introduce form failures
|
||||
pymemcache>=4.0.0 # for django.core.cache.backends.memcached.PyMemcacheCache
|
||||
pymemcache>=4.0.0 # for django.core.cache.backends.memcached.PyMemcacheCache
|
||||
python-mimeparse>=1.6 # from TastyPie
|
||||
pytz==2022.2.1 # Pinned as changes need to be vetted for their effect on Meeting fields
|
||||
requests>=2.31.0
|
||||
|
|
185
yarn.lock
185
yarn.lock
|
@ -2019,6 +2019,43 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@volar/language-core@npm:2.1.4":
|
||||
version: 2.1.4
|
||||
resolution: "@volar/language-core@npm:2.1.4"
|
||||
dependencies:
|
||||
"@volar/source-map": 2.1.4
|
||||
checksum: 7430f651431ed00eb7489d48c0596f4653fe70da3c779acfaa5807051db4491c9e4e154e9f0de3c9d863a3b4b1194a517a75395ca9134ea2b1b8af5ff637b204
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@volar/language-service@npm:~2.1.0":
|
||||
version: 2.1.4
|
||||
resolution: "@volar/language-service@npm:2.1.4"
|
||||
dependencies:
|
||||
"@volar/language-core": 2.1.4
|
||||
vscode-languageserver-protocol: ^3.17.5
|
||||
vscode-languageserver-textdocument: ^1.0.11
|
||||
vscode-uri: ^3.0.8
|
||||
checksum: 06cdcfacf0fab22cee652cab1ae1729628d7ebf68f5f9e791e19e3715b2a4775c0bd2ec2e7a9b0815d93f244d7a745f3ea41aa5084923b10e9258a5f54c1107b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@volar/source-map@npm:2.1.4, @volar/source-map@npm:~2.1.3":
|
||||
version: 2.1.4
|
||||
resolution: "@volar/source-map@npm:2.1.4"
|
||||
dependencies:
|
||||
muggle-string: ^0.4.0
|
||||
checksum: e2f65bcfd667a02ee5cfe49e612b12e75c05fdaecf3b3590fdd7a0255dce7e51d09e8d4c390c2098ca7321cea219c16a8ea3f6c0f36ca9c0edff3975990b458b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vscode/l10n@npm:^0.0.18":
|
||||
version: 0.0.18
|
||||
resolution: "@vscode/l10n@npm:0.0.18"
|
||||
checksum: c33876cebdef0385359619200ecb5d7c46d7f9abffb80f9fab1f83abb5d6bfdb44cc6d792d1b1b9c736c729121274733bbdcd5d2d2eea0d157bdf662d521edef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/compiler-core@npm:3.4.21":
|
||||
version: 3.4.21
|
||||
resolution: "@vue/compiler-core@npm:3.4.21"
|
||||
|
@ -2083,6 +2120,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/language-plugin-pug@npm:2.0.7":
|
||||
version: 2.0.7
|
||||
resolution: "@vue/language-plugin-pug@npm:2.0.7"
|
||||
dependencies:
|
||||
"@volar/source-map": ~2.1.3
|
||||
volar-service-pug: 0.0.34
|
||||
checksum: 11cc96eb5f240144e91b27fe06fcd48de4ef1e4c7fe666d1173b346ed64b7edfa922bd4eb2e512a91a0c6b907975afcaf69cfee4c91af11168590142b3aba4c3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/reactivity@npm:3.4.21":
|
||||
version: 3.4.21
|
||||
resolution: "@vue/reactivity@npm:3.4.21"
|
||||
|
@ -2637,10 +2684,10 @@ browserlist@latest:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"caniuse-lite@npm:1.0.30001597":
|
||||
version: 1.0.30001597
|
||||
resolution: "caniuse-lite@npm:1.0.30001597"
|
||||
checksum: ec6a2cf0fd49f37d16732e6595939fc80a125dcd188a950bc936c61b4ad53becc0fe51bf2d9a625415de7b1cb23bd835f220e8b68d8ab951a940edeea65476fd
|
||||
"caniuse-lite@npm:1.0.30001599":
|
||||
version: 1.0.30001599
|
||||
resolution: "caniuse-lite@npm:1.0.30001599"
|
||||
checksum: d7e619e2e723547b7311ba0ca5134d9cd55df548e93dbedcf8a6e4ec74c7db91969c4272fb1ab2fd94cddeac6a8176ebf05853eb06689d5e76bb97d979a214b0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -3216,9 +3263,9 @@ browserlist@latest:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3@npm:7.8.5":
|
||||
version: 7.8.5
|
||||
resolution: "d3@npm:7.8.5"
|
||||
"d3@npm:7.9.0":
|
||||
version: 7.9.0
|
||||
resolution: "d3@npm:7.9.0"
|
||||
dependencies:
|
||||
d3-array: 3
|
||||
d3-axis: 3
|
||||
|
@ -3250,7 +3297,7 @@ browserlist@latest:
|
|||
d3-timer: 3
|
||||
d3-transition: 3
|
||||
d3-zoom: 3
|
||||
checksum: e407e79731f74d946a5eb8dec2f037b5a4ad33c294409b1d3531fdf7094de48adfe364974cb37e2396bdb81e23149d56d0ede716c004d6aebb52b3cc114cd15c
|
||||
checksum: 1c0e9135f1fb78aa32b187fafc8b56ae6346102bd0e4e5e5a5339611a51e6038adbaa293fae373994228100eddd87320e930b1be922baeadc07c9fd43d26d99b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -3938,9 +3985,9 @@ browserlist@latest:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-vue@npm:9.22.0":
|
||||
version: 9.22.0
|
||||
resolution: "eslint-plugin-vue@npm:9.22.0"
|
||||
"eslint-plugin-vue@npm:9.23.0":
|
||||
version: 9.23.0
|
||||
resolution: "eslint-plugin-vue@npm:9.23.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": ^4.4.0
|
||||
natural-compare: ^1.4.0
|
||||
|
@ -3951,7 +3998,7 @@ browserlist@latest:
|
|||
xml-name-validator: ^4.0.0
|
||||
peerDependencies:
|
||||
eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
|
||||
checksum: 5f1e94b412567b8d0bb99c89ea1032d7801d1aa34344e61d82b71c926d3ced049861b484c31c641606923246d482972a45e690236e5479f46b9736d432545ecd
|
||||
checksum: acb3a4dd27a815be37b63fc735c88bf9bfd12a5147639635eb6e473c7fdf47f69a78693ffeb67581ffd11e0016c134bb56a667193b958a189cbfc2df3e93160b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -4667,9 +4714,9 @@ browserlist@latest:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-validate@npm:8.15.0":
|
||||
version: 8.15.0
|
||||
resolution: "html-validate@npm:8.15.0"
|
||||
"html-validate@npm:8.16.0":
|
||||
version: 8.16.0
|
||||
resolution: "html-validate@npm:8.16.0"
|
||||
dependencies:
|
||||
"@babel/code-frame": ^7.10.0
|
||||
"@html-validate/stylish": ^4.1.0
|
||||
|
@ -4698,7 +4745,7 @@ browserlist@latest:
|
|||
optional: true
|
||||
bin:
|
||||
html-validate: bin/html-validate.js
|
||||
checksum: 0af7685ca1302cbcbbaebae771b64048267aa3ce26fe279f99647b4febd03373e1fbb85cc3e5ba2b0986741eecb6a2b6f5a3143615b6d8407e28210199ec136a
|
||||
checksum: 857b05ab87ae8b92d9d256dfc45def15275d6038bb7456afb00c6e23872acc4bc7bcc50be89ce82bad36f6c4026603106f57574f1058f3ae3f2e35fca0f34a15
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -5968,6 +6015,13 @@ browserlist@latest:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"muggle-string@npm:^0.4.0":
|
||||
version: 0.4.1
|
||||
resolution: "muggle-string@npm:0.4.1"
|
||||
checksum: 85fe1766d18d43cf22b6da7d047203a65b2e2b1ccfac505b699c2a459644f95ebb3c854a96db5be559eea0e213f6ee32b986b8c2f73c48e6c89e1fd829616532
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"murmurhash-js@npm:1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "murmurhash-js@npm:1.0.0"
|
||||
|
@ -6982,13 +7036,14 @@ browserlist@latest:
|
|||
"@rollup/pluginutils": 5.1.0
|
||||
"@twuni/emojify": 1.0.2
|
||||
"@vitejs/plugin-vue": 4.6.2
|
||||
"@vue/language-plugin-pug": 2.0.7
|
||||
bootstrap: 5.3.3
|
||||
bootstrap-icons: 1.11.3
|
||||
browser-fs-access: 0.35.0
|
||||
browserlist: latest
|
||||
c8: 9.1.0
|
||||
caniuse-lite: 1.0.30001597
|
||||
d3: 7.8.5
|
||||
caniuse-lite: 1.0.30001599
|
||||
d3: 7.9.0
|
||||
eslint: 8.57.0
|
||||
eslint-config-standard: 17.1.0
|
||||
eslint-plugin-cypress: 2.15.1
|
||||
|
@ -6996,10 +7051,10 @@ browserlist@latest:
|
|||
eslint-plugin-n: 16.6.2
|
||||
eslint-plugin-node: 11.1.0
|
||||
eslint-plugin-promise: 6.1.1
|
||||
eslint-plugin-vue: 9.22.0
|
||||
eslint-plugin-vue: 9.23.0
|
||||
file-saver: 2.0.5
|
||||
highcharts: 11.4.0
|
||||
html-validate: 8.15.0
|
||||
html-validate: 8.16.0
|
||||
ical.js: 1.5.0
|
||||
jquery: 3.7.1
|
||||
jquery-migrate: 3.4.1
|
||||
|
@ -7017,7 +7072,7 @@ browserlist@latest:
|
|||
pinia: 2.1.7
|
||||
pinia-plugin-persist: 1.0.0
|
||||
pug: 3.0.2
|
||||
sass: 1.71.1
|
||||
sass: 1.72.0
|
||||
seedrandom: 3.0.5
|
||||
select2: 4.1.0-rc.0
|
||||
select2-bootstrap-5-theme: 1.3.0
|
||||
|
@ -7093,16 +7148,16 @@ browserlist@latest:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sass@npm:1.71.1":
|
||||
version: 1.71.1
|
||||
resolution: "sass@npm:1.71.1"
|
||||
"sass@npm:1.72.0":
|
||||
version: 1.72.0
|
||||
resolution: "sass@npm:1.72.0"
|
||||
dependencies:
|
||||
chokidar: ">=3.0.0 <4.0.0"
|
||||
immutable: ^4.0.0
|
||||
source-map-js: ">=0.6.2 <2.0.0"
|
||||
bin:
|
||||
sass: sass.js
|
||||
checksum: 19c4939d3042eb9459d462bbd27b1f576fa18034e23c87ca0005b87effdee431c16503b5a785edcdcde1a76dfb804716d9ad42c85a78968ac3825d515e45cb53
|
||||
checksum: f420079c7d51660b7256ee52463c1499ede36f7fd5c8ef50c687451777ad641509001454dea45244073cedd7c00e7a3bc1c362e55206ac6686171b994edb41e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -7868,6 +7923,36 @@ browserlist@latest:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"volar-service-html@npm:0.0.34":
|
||||
version: 0.0.34
|
||||
resolution: "volar-service-html@npm:0.0.34"
|
||||
dependencies:
|
||||
vscode-html-languageservice: ^5.1.0
|
||||
vscode-languageserver-textdocument: ^1.0.11
|
||||
vscode-uri: ^3.0.8
|
||||
peerDependencies:
|
||||
"@volar/language-service": ~2.1.0
|
||||
peerDependenciesMeta:
|
||||
"@volar/language-service":
|
||||
optional: true
|
||||
checksum: 83b50cd805680c77b5632e9534b23cddb85bf7e0cd425624d474981d173ddf07a66fcce6348f675c9d5c2551df9ae1e58206c2ed1c32052f8a70940fb7f5fe50
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"volar-service-pug@npm:0.0.34":
|
||||
version: 0.0.34
|
||||
resolution: "volar-service-pug@npm:0.0.34"
|
||||
dependencies:
|
||||
"@volar/language-service": ~2.1.0
|
||||
pug-lexer: ^5.0.1
|
||||
pug-parser: ^6.0.0
|
||||
volar-service-html: 0.0.34
|
||||
vscode-html-languageservice: ^5.1.0
|
||||
vscode-languageserver-textdocument: ^1.0.11
|
||||
checksum: 4691aa1c8ea9039e1b5ce4218445309575c2cb4bc08ad5341a8af6f0db1a60711f26cc905e124c3485cc780eb58b895332fbb6a2ccf427a9d0e08012f2c5ad4a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vooks@npm:^0.2.12, vooks@npm:^0.2.4":
|
||||
version: 0.2.12
|
||||
resolution: "vooks@npm:0.2.12"
|
||||
|
@ -7879,6 +7964,56 @@ browserlist@latest:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vscode-html-languageservice@npm:^5.1.0":
|
||||
version: 5.1.2
|
||||
resolution: "vscode-html-languageservice@npm:5.1.2"
|
||||
dependencies:
|
||||
"@vscode/l10n": ^0.0.18
|
||||
vscode-languageserver-textdocument: ^1.0.11
|
||||
vscode-languageserver-types: ^3.17.5
|
||||
vscode-uri: ^3.0.8
|
||||
checksum: 3a2a5ee5ad4ea429e85f4fb8f45da5b47d50541784d703fc9ccd009f68426034a48be6c04f8c420dc7236de07df93ccc28873da3395db5f5626fe169f18f1ac6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vscode-jsonrpc@npm:8.2.0":
|
||||
version: 8.2.0
|
||||
resolution: "vscode-jsonrpc@npm:8.2.0"
|
||||
checksum: f302a01e59272adc1ae6494581fa31c15499f9278df76366e3b97b2236c7c53ebfc71efbace9041cfd2caa7f91675b9e56f2407871a1b3c7f760a2e2ee61484a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vscode-languageserver-protocol@npm:^3.17.5":
|
||||
version: 3.17.5
|
||||
resolution: "vscode-languageserver-protocol@npm:3.17.5"
|
||||
dependencies:
|
||||
vscode-jsonrpc: 8.2.0
|
||||
vscode-languageserver-types: 3.17.5
|
||||
checksum: dfb42d276df5dfea728267885b99872ecff62f6c20448b8539fae71bb196b420f5351c5aca7c1047bf8fb1f89fa94a961dce2bc5bf7e726198f4be0bb86a1e71
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vscode-languageserver-textdocument@npm:^1.0.11":
|
||||
version: 1.0.11
|
||||
resolution: "vscode-languageserver-textdocument@npm:1.0.11"
|
||||
checksum: ea7cdc9d4ffaae5952071fa11d17d714215a76444e6936c9359f94b9ba3222a52a55edb5bd5928bd3e9712b900a9f175bb3565ec1c8923234fe3bd327584bafb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vscode-languageserver-types@npm:3.17.5, vscode-languageserver-types@npm:^3.17.5":
|
||||
version: 3.17.5
|
||||
resolution: "vscode-languageserver-types@npm:3.17.5"
|
||||
checksum: 79b420e7576398d396579ca3a461c9ed70e78db4403cd28bbdf4d3ed2b66a2b4114031172e51fad49f0baa60a2180132d7cb2ea35aa3157d7af3c325528210ac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vscode-uri@npm:^3.0.8":
|
||||
version: 3.0.8
|
||||
resolution: "vscode-uri@npm:3.0.8"
|
||||
checksum: 514249126850c0a41a7d8c3c2836cab35983b9dc1938b903cfa253b9e33974c1416d62a00111385adcfa2b98df456437ab704f709a2ecca76a90134ef5eb4832
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vue-demi@npm:>=0.14.5":
|
||||
version: 0.14.5
|
||||
resolution: "vue-demi@npm:0.14.5"
|
||||
|
|
Loading…
Reference in a new issue