Compare commits

...

16 Commits

Author SHA1 Message Date
3b9848d457 Set user on level confirmation 2022-01-24 22:36:53 +00:00
308d0c697e Fix (probably) reversion for trainingitemqualification 2022-01-24 22:32:12 +00:00
f243a589fa Remove duplicate RevisionMixin 2022-01-24 21:07:32 +00:00
79c90ac92c PATCH: Bullets in paperwork hard crashing 2022-01-24 15:51:57 +00:00
8244287a64 FIX: inability to scroll modals on dark theme
What. The. Hell.
2022-01-24 15:51:57 +00:00
da4d62729b Properly folderise rigboard views 2022-01-24 13:49:11 +00:00
f8a48798de Hide index page images on mobile 2022-01-20 18:35:30 +00:00
fc817fa9b5 Swap to EasyMDE
Various other fixes too
2022-01-20 11:31:23 +00:00
b04a168f01 Remove flatpickr polyfill
Firefox now supports it natively...finally! Closes #475
2022-01-20 11:04:15 +00:00
cc6992976e Fix #476 broken darktheme embeds
One tiny step toward #419 as well ;p
2022-01-20 10:50:41 +00:00
a556b17d2d Rip out analytics
Closes #423
2022-01-20 10:38:02 +00:00
f9e38338dc Fix error when trying to load detailed item list 2022-01-20 10:35:52 +00:00
ce83ae6dd1 Refactor asset search to use SecureAPIRequest
Closes #474 and #465
2022-01-19 19:19:50 +00:00
9e1d54dc02 FIX #472: Filter common competencies out of get_levels_of_depth 2022-01-19 18:38:37 +00:00
375b0af2fd Delete Dockerfile 2022-01-19 13:20:03 +00:00
imgbot[bot]
0354662864 [ImgBot] Optimize images (#471)
*Total -- 8,936.02kb -> 7,990.11kb (10.59%)

/assets/static/imgs/square_logo.png -- 23.90kb -> 17.64kb (26.18%)
/RIGS/static/imgs/tappytaptap.gif -- 6,433.15kb -> 5,493.51kb (14.61%)
/RIGS/static/imgs/rigs.jpg -- 277.61kb -> 277.60kb (0%)
/RIGS/static/imgs/training.jpg -- 852.42kb -> 852.42kb (0%)
/RIGS/static/imgs/assets.jpg -- 1,348.94kb -> 1,348.94kb (0%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>

Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2022-01-19 13:17:44 +00:00
50 changed files with 555 additions and 580 deletions

View File

@@ -1,12 +0,0 @@
FROM python:3.6
WORKDIR /app
ADD . /app
RUN pip install -r requirements.txt && \
python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

View File

@@ -49,7 +49,7 @@ PyPOM = "~=2.2.0"
python-dateutil = "~=2.8.1" python-dateutil = "~=2.8.1"
pytoml = "~=0.1.21" pytoml = "~=0.1.21"
pytz = "~=2020.5" pytz = "~=2020.5"
reportlab = "~=3.5.59" reportlab = "*"
requests = "~=2.25.1" requests = "~=2.25.1"
retrying = "~=1.3.3" retrying = "~=1.3.3"
simplejson = "~=3.17.2" simplejson = "~=3.17.2"
@@ -63,7 +63,6 @@ tornado = "~=6.1"
urllib3 = "~=1.26.5" urllib3 = "~=1.26.5"
whitenoise = "~=5.2.0" whitenoise = "~=5.2.0"
yolk = "~=0.4.3" yolk = "~=0.4.3"
"z3c.rml" = "~=4.1.2"
zipp = "~=3.4.0" zipp = "~=3.4.0"
"zope.component" = "~=4.6.2" "zope.component" = "~=4.6.2"
"zope.deferredimport" = "~=4.3.1" "zope.deferredimport" = "~=4.3.1"
@@ -79,6 +78,8 @@ python-barcode = "*"
django-hCaptcha = "*" django-hCaptcha = "*"
importlib-metadata = "*" importlib-metadata = "*"
django-hcaptcha = "*" django-hcaptcha = "*"
"z3c.rml" = "*"
pikepdf = "*"
[dev-packages] [dev-packages]
selenium = "~=3.141.0" selenium = "~=3.141.0"

115
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "7db5b3a9029be79c79efff791a42803a4765fe52c4f264f8a7be48ac4b1bda7a" "sha256": "841781f4c4d3c12a34c0ff8ef3fd58171baf657478d5c339d4f6fc79d5830978"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -73,6 +73,7 @@
}, },
"brotli": { "brotli": {
"hashes": [ "hashes": [
"sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d",
"sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8", "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8",
"sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b", "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b",
"sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c", "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c",
@@ -82,10 +83,13 @@
"sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181", "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181",
"sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130", "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130",
"sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19", "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19",
"sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa",
"sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429", "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429",
"sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126", "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126",
"sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4", "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4",
"sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0", "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0",
"sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b",
"sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6",
"sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438", "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438",
"sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f", "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f",
"sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389", "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389",
@@ -97,10 +101,13 @@
"sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430", "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430",
"sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296", "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296",
"sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12", "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12",
"sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f",
"sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d", "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d",
"sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a",
"sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452", "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452",
"sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c", "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c",
"sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761", "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761",
"sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649",
"sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b", "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b",
"sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea", "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea",
"sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c", "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c",
@@ -110,15 +117,23 @@
"sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5", "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5",
"sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7", "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7",
"sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d", "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d",
"sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c",
"sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43", "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43",
"sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa", "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa",
"sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17",
"sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb", "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb",
"sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb",
"sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b", "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b",
"sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4", "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4",
"sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3", "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3",
"sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7", "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7",
"sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1", "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1",
"sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb", "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb",
"sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91",
"sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b",
"sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1",
"sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806",
"sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3",
"sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1" "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"
], ],
"index": "pypi", "index": "pypi",
@@ -386,10 +401,10 @@
}, },
"h11": { "h11": {
"hashes": [ "hashes": [
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06",
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"
], ],
"version": "==0.12.0" "version": "==0.13.0"
}, },
"html5lib": { "html5lib": {
"hashes": [ "hashes": [
@@ -416,12 +431,12 @@
}, },
"importlib-metadata": { "importlib-metadata": {
"hashes": [ "hashes": [
"sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6", "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6",
"sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4" "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version < '3.10'", "markers": "python_version < '3.10'",
"version": "==4.10.0" "version": "==4.10.1"
}, },
"lxml": { "lxml": {
"hashes": [ "hashes": [
@@ -589,6 +604,7 @@
"sha256:cd575cf0131683a7b661357bfd777b27c3c6c0d0fb7ef27e627f521122f75536", "sha256:cd575cf0131683a7b661357bfd777b27c3c6c0d0fb7ef27e627f521122f75536",
"sha256:fb3d7fb390192cfb1e287503dbc03229c1c77fe9820cf084546bb63fa997fd87" "sha256:fb3d7fb390192cfb1e287503dbc03229c1c77fe9820cf084546bb63fa997fd87"
], ],
"index": "pypi",
"version": "==4.3.1" "version": "==4.3.1"
}, },
"pillow": { "pillow": {
@@ -785,38 +801,53 @@
}, },
"reportlab": { "reportlab": {
"hashes": [ "hashes": [
"sha256:010f86a192c397f7c8ae667953a85d913395a8a6a8da112bff1c1ea28e679bcd", "sha256:0430cfe397415759839ef89abee6db82e8a8f9bb5831a3c93e7763915c755345",
"sha256:08b53568979228b6969b790339d06a0b8db8883f92ae7339013f9878042dd9ca", "sha256:13072e33e8cbac6fd6e776fecabdefafb0261886b2ab7cb3b874a9384f1b0ffe",
"sha256:19708801278f600d712c04ee6bfb650e45d1b2898713f7bd97b39ab89bd08c1e", "sha256:1767106d03320e76a708d2c40488fe1785580a0d7abac7715e01a3cc910c1179",
"sha256:28c72d27f21d74a7301789c7950b5e82a430ed38817ecee060fa1f2f3e959360", "sha256:17f35a856bbf46989d557d4016822bcdd3ada88d3afb567de03a4b29676aa52e",
"sha256:2c0c88a7cf83a20a2bb355f97a1a9d0373a6de60c3aec35d301d3cc75dc4bb72", "sha256:19414f4357287a7573a60bcb76a092c9ea82bf09f01d04b3afb5c1bd3c660df2",
"sha256:2dc5ee0c5b659697cdfbc218ec9abea54dd9c5a95ea8ca95245fe94f5ef111f9", "sha256:1d8d9674eb6ba1b6c3d6a8e3d5d4e4231b3576db653d1b1fdac2538afee54c7a",
"sha256:332f836ff4c975c92d307302e86a54d6f0e3d2ce33a35759812e7a1d17e2091f", "sha256:23236dc70598b688e979444c4840c5cec88a2a12fe81ba6f8cc807120a2cad33",
"sha256:45113c1c359ba314499032c891487802cccd7c4225a3e930d6cf492d62ea4f07", "sha256:28c339d25eab804a8bd004dfaa5a80c7568178561741f4ce6e69dae05d38041f",
"sha256:46f15f5a34a50375c332ab8eaa907a0212c88787b0885ac25a9505c0741ee9ba", "sha256:2c93a551b60c7fd3b17942772847f7c4ee2f08ae74c87ef8f325fe8083d2aa6e",
"sha256:580eed6d9e5c20870ea909bec6840f9ceb9d13c33316d448cae21eb3ca47c7fd", "sha256:2e80045f36dd4b9b63b19fc073149f70857fe8590027ab3658db80ac6235ecd0",
"sha256:5865c4247229584408515055b5b19c7f935ae94433d6258c7a9234c4a07d6d34", "sha256:38aa912301d93e2267861d820cb3f6eebed8deb58d0df429421578b9ba033eee",
"sha256:6063466779e438375bcdd2c15fc551ebd68f16ebfb2766497234df9cfa57e5b1", "sha256:47587ce01cf9ac25f6d187116a9f9cef710dc58ccea001024d950c4f5a504643",
"sha256:63578cab96fc4383e71dd9fe1877bb26ab78b2a6c91139068e99d130687289ab", "sha256:587b3d8ce0a065a00975516013aebb062e6161fba3cf399b22f270e4d9a3db1e",
"sha256:66b5a08cbeb910edee7201efa786bd1bf7027c7ec526dddf7d60fc2252e2b30f", "sha256:5a650284cc09caa32b5845c055bf035cb76949b87d57e9eed56d98f863613417",
"sha256:6b448a1824d381d282c5ea1da1669a5fa53dac67c57a1ecad6bcc149f286d1fd", "sha256:5e113c630b6109efe0285230706c8423bff1b82c2e2824e441401a467a1215b7",
"sha256:6f905390f5e5801b21b6027c8ffaed915e5eec1e46bbdf6a74c8838213717b44", "sha256:68e339411cc9329ff50982a7c1d55eabd53ac9be24d4442088af58328bae54d3",
"sha256:70e7461aa47eff810be8c4e4a0cbc6fcf47aecaddd46de6ca4524c76065f8490", "sha256:6ae1fb03faf4b6710e2c081d5208416a5d557e0cc00ff24fc124dd42a7158114",
"sha256:7e466276f1a1121dac23b703af6c22db0cedf6cec5139969f8387e8d8046f203", "sha256:6f363e09aacaa7aaff232197fddb667d899822aa57d10091aea4fbb1f56b7fa7",
"sha256:81d1958d90fccf86f62b38ecbedf9208a973d99e0747b6cd75036914ae8641c4", "sha256:70841d7eb4aa2f8ad4afacce07711481a0dcd9d01679da5627173443131a33a2",
"sha256:9a00feb8eafbce1283cd3edbb29735bd40c9566b3f45913110a301700c16b63a", "sha256:71d91002878c4d2a17a6bd7208c59373e6148977fe674bb79eec3eb9e63aa20f",
"sha256:a48221d4ab7de37975ad052f7e565cf13ab708def63f203a38ae9927ab5442cd", "sha256:7a09e5bf9c8e02c373e5e558cc5c2cfbc5d3c68560a406c6d16254363cfa989e",
"sha256:ad9a49890de59e8dd16fa0ce03ef607e46a5ff2f39de44f8556f796b3d4ddffb", "sha256:85095ef9f3697859064cb1b22f19659bf4ba25e7dadb9c6be65f322cd68ba88f",
"sha256:b25608059558910585a9e229bae0fd3d67af49ae5e1c7a20057680c6b3d5f6f7", "sha256:8dafdcdde7243f0864d6d11dd9bfffbd1e6bce6c3e668fe992f56ae48377c822",
"sha256:b57ebeb28f7a58a9da6f8c293acb6d31d89f634b3eba0b728a040cef08afc4ea", "sha256:9a822486a98fe002bbe248fdf3f126739c1ad29032b54b71a3f67b6364a77677",
"sha256:b9ae0c534c09274b80f8fd87408071c1f814d56c5f51fe450b2157f1f13e921b", "sha256:aa57dc0818e066fdced9457b9e6c6fb269d63e2d96902001c7dbe010bce6ebcc",
"sha256:c0612d9101f40679245e7d9edb169d8d79378a47f38cd8e6b38c55d7ff31db3f", "sha256:b0836c6cdee4b88e2366e0ff152c1327578149e09850b7cab6016444c5b3eb26",
"sha256:ced16daf89f948eeb4e376b5d814da5d99f7205fbd42e17a96f257e35dc31bdd", "sha256:b2988ffc33032096e808e7a4a36f5b453fcc9587873c85c1b44bc6846bbbd09c",
"sha256:dd3409ebabe699c98058690b7b730f93e6b0bd4ed5e49ca3b15e1530ae07b40b", "sha256:bd38d58895b359ef429df3c97dc00c3fef0ab57f45556de416ba9b7d7fc71ae2",
"sha256:efef6a97e3ab49f3f40037dbf9a4166668a17cc6aaba13d5ecbabdf854a9b332" "sha256:be87dca9253efd3cd0f351b785530c02e67664e284e3c4a97cdd0c7dd806d39a",
"sha256:c21bdb11d7fccea28bf08eac13d9d031836e335c5e0620eae1d4336f193e9a03",
"sha256:c43f847f2598b5c2fc9b63871d7da641c0b90e384d8da8018d4d7173a0b82cd4",
"sha256:c780cc5208c67b25bdddd08480f874614cd0ec0bed39e1a848448543f2093945",
"sha256:c9bcf696bc8935ff90ecb50c7644e2af01f63a444d4b4bd39d41d2abdd7bb224",
"sha256:cb48b71088f5c9eff5715dde0bd4d5372d4713ffa92247acf0f04fd17ab2078d",
"sha256:d48f638893b3eb4c9b2afeec2de4f95a4b57fb8c398e3d7f9a7fb4b4d9546820",
"sha256:d8fe27ad312671c9347cf5997f7c1017833fac17233f33296281ba9fa0de189a",
"sha256:d98b759661070f5588b30152d0caaf16ac387f60372f8fa2568c9ad4014cd7f3",
"sha256:e2022ad36409e7616ed6311f7ab113f236cac66ba0d22be4f53bf7e77654b143",
"sha256:e45159f4d19304f5e79be13283fe53bdd006c4fd4d93ff3cb6ac082ca017c418",
"sha256:eb3ef5394b4b2c904ab467dbbe1efcfbe046e1395c2d3064420ccef89806570e",
"sha256:f326b04a3fb3c7c58b799bd23b60790b181893f052fe5a8011c9cd9984e24a43",
"sha256:f401ed014ea861dea2ae621f7810fb15b3bc021e6487dee97b32f175bbf1b7eb",
"sha256:f4d4eb3a949ccb0782e4d6560fcd5ee6f34636d1ee24f1d2a2b1f530af89481a",
"sha256:fdc3dc1242be557f6a8bb9e21751296cc721f60b8e2b684690049e656d798520"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.5.68" "version": "==3.6.5"
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
@@ -841,11 +872,11 @@
}, },
"sentry-sdk": { "sentry-sdk": {
"hashes": [ "hashes": [
"sha256:2cec50166bcb67e1965f8073541b2321e3864cd6fd42a526bcde9f0c4e4cc3f8", "sha256:141da032f0fa4c56f9af6b361fda57360af1789576285bd1944561f9c274f9c0",
"sha256:7bbaa32bba806ec629962f207b597e86831c7ee2c1f287c21ba7de7fea9a9c46" "sha256:9aeff2a47f4038460296b920bf4d269284e8454e1c67547ee002ccafd9c2442b"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.5.2" "version": "==1.5.3"
}, },
"simplejson": { "simplejson": {
"hashes": [ "hashes": [
@@ -1082,10 +1113,10 @@
}, },
"z3c.rml": { "z3c.rml": {
"hashes": [ "hashes": [
"sha256:0d730e2e61a29c69822ee955366f9d7e9a82e6909c11932329629fb0c1a128a0" "sha256:11eca2ffe2e5db6d53b5a19617126db780afe779e11b5215651c8214aa98d8a6"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.1.2" "version": "==4.2.0"
}, },
"zipp": { "zipp": {
"hashes": [ "hashes": [

View File

@@ -120,10 +120,10 @@ class TextBox(Region):
class SimpleMDETextArea(Region): class SimpleMDETextArea(Region):
@property @property
def value(self): def value(self):
return self.driver.execute_script("return document.querySelector('#' + arguments[0]).nextSibling.nextSibling.CodeMirror.getDoc().getValue();", self.root.get_attribute("id")) return self.driver.execute_script("return document.querySelector('#' + arguments[0]).nextSibling.children[1].CodeMirror.getDoc().getValue();", self.root.get_attribute("id"))
def set_value(self, value): def set_value(self, value):
self.driver.execute_script("document.querySelector('#' + arguments[0]).nextSibling.nextSibling.CodeMirror.getDoc().setValue(arguments[1]);", self.root.get_attribute("id"), value) self.driver.execute_script("document.querySelector('#' + arguments[0]).nextSibling.children[1].CodeMirror.getDoc().setValue(arguments[1]);", self.root.get_attribute("id"), value)
class CheckBox(Region): class CheckBox(Region):
@@ -145,7 +145,7 @@ class RadioSelect(Region): # Currently only works for yes/no radio selects
value = "0" value = "0"
else: else:
value = "1" value = "1"
self.find_element(By.XPATH, "//label[@for='{}_{}']".format(self.root.get_attribute("id"), value)).click() self.find_element(By.XPATH, f"//label[@for='{self.root.get_attribute('id')}_{value}']").click()
@property @property
def value(self): def value(self):

View File

@@ -2,13 +2,12 @@ import datetime
import operator import operator
from functools import reduce from functools import reduce
import simplejson
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from django.core import serializers from django.core import serializers
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy, reverse, NoReverseMatch from django.urls import reverse_lazy, reverse, NoReverseMatch
from django.views import generic from django.views import generic
@@ -27,7 +26,7 @@ class Index(generic.TemplateView): # Displays the current rig count along with
template_name = 'index.html' template_name = 'index.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(Index, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['rig_count'] = models.Event.objects.rig_count() context['rig_count'] = models.Event.objects.rig_count()
return context return context
@@ -39,6 +38,7 @@ class SecureAPIRequest(generic.View):
'organisation': models.Organisation, 'organisation': models.Organisation,
'profile': models.Profile, 'profile': models.Profile,
'event': models.Event, 'event': models.Event,
'asset': asset_models.Asset,
'supplier': asset_models.Supplier, 'supplier': asset_models.Supplier,
'training_item': training_models.TrainingItem, 'training_item': training_models.TrainingItem,
} }
@@ -49,8 +49,9 @@ class SecureAPIRequest(generic.View):
'organisation': 'RIGS.view_organisation', 'organisation': 'RIGS.view_organisation',
'profile': 'RIGS.view_profile', 'profile': 'RIGS.view_profile',
'event': None, 'event': None,
'asset': None,
'supplier': None, 'supplier': None,
'training_item': None, # TODO 'training_item': None,
} }
''' '''
@@ -125,8 +126,7 @@ class SecureAPIRequest(generic.View):
results.append(data) results.append(data)
# return a data response # return a data response
json = simplejson.dumps(results) return JsonResponse(results, safe=False)
return HttpResponse(json, content_type="application/json") # Always json
start = request.GET.get('start', None) start = request.GET.get('start', None)
end = request.GET.get('end', None) end = request.GET.get('end', None)
@@ -151,8 +151,7 @@ class SecureAPIRequest(generic.View):
} }
results.append(data) results.append(data)
json = simplejson.dumps(results) return JsonResponse(results, safe=False)
return HttpResponse(json, content_type="application/json") # Always json
return HttpResponse(model) return HttpResponse(model)
@@ -176,7 +175,7 @@ class GenericListView(generic.ListView):
paginate_by = 20 paginate_by = 20
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GenericListView, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['page_title'] = self.model.__name__ + "s" context['page_title'] = self.model.__name__ + "s"
if is_ajax(self.request): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
@@ -208,8 +207,8 @@ class GenericDetailView(generic.DetailView):
template_name = "generic_detail.html" template_name = "generic_detail.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GenericDetailView, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['page_title'] = "{} | {}".format(self.model.__name__, self.object.name) context['page_title'] = f"{self.model.__name__} | {self.object.name}"
if is_ajax(self.request): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
return context return context
@@ -219,8 +218,8 @@ class GenericUpdateView(generic.UpdateView):
template_name = "generic_form.html" template_name = "generic_form.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GenericUpdateView, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['page_title'] = "Edit {}".format(self.model.__name__) context['page_title'] = f"Edit {self.model.__name__}"
if is_ajax(self.request): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
return context return context
@@ -230,8 +229,8 @@ class GenericCreateView(generic.CreateView):
template_name = "generic_form.html" template_name = "generic_form.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GenericCreateView, self).get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['page_title'] = "Create {}".format(self.model.__name__) context['page_title'] = f"Create {self.model.__name__}"
if is_ajax(self.request): if is_ajax(self.request):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
return context return context
@@ -256,14 +255,13 @@ class CloseModal(generic.TemplateView):
class OEmbedView(generic.View): class OEmbedView(generic.View):
def get(self, request, pk=None): def get(self, request, pk=None):
embed_url = reverse(self.url_name, args=[pk]) embed_url = reverse(self.url_name, args=[pk])
full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) full_url = f"{request.scheme}://{request.META['HTTP_HOST']}{embed_url}"
data = { data = {
'html': '<iframe src="{0}" frameborder="0" width="100%" height="250"></iframe>'.format(full_url), 'html': f'<iframe src="{full_url}" frameborder="0" width="100%" height="250"></iframe>',
'version': '1.0', 'version': '1.0',
'type': 'rich', 'type': 'rich',
'height': '250' 'height': '250'
} }
json = simplejson.JSONEncoderForHTML().encode(data) return JsonResponse(data)
return HttpResponse(json, content_type="application/json")

View File

@@ -17,6 +17,7 @@ from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from reversion import revisions as reversion from reversion import revisions as reversion
from reversion.models import Version from reversion.models import Version
from versioning.versioning import RevisionMixin
class Profile(AbstractUser): class Profile(AbstractUser):
@@ -69,39 +70,6 @@ class Profile(AbstractUser):
return self.name return self.name
class RevisionMixin:
@property
def is_first_version(self):
versions = Version.objects.get_for_object(self)
return len(versions) == 1
@property
def current_version(self):
version = Version.objects.get_for_object(self).select_related('revision').first()
return version
@property
def last_edited_at(self):
version = self.current_version
if version is None:
return None
return version.revision.date_created
@property
def last_edited_by(self):
version = self.current_version
if version is None:
return None
return version.revision.user
@property
def current_version_id(self):
version = self.current_version
if version is None:
return None
return f"V{version.pk} | R{version.revision.pk}"
class Person(models.Model, RevisionMixin): class Person(models.Model, RevisionMixin):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
phone = models.CharField(max_length=15, blank=True, default='') phone = models.CharField(max_length=15, blank=True, default='')
@@ -601,7 +569,7 @@ class Invoice(models.Model, RevisionMixin):
@property @property
def activity_feed_string(self): def activity_feed_string(self):
return "#{} for Event {}".format(self.display_id, self.event.display_id) return f"#{self.display_id} for Event {self.event.display_id}"
def __str__(self): def __str__(self):
return "%i: %s (%.2f)" % (self.pk, self.event, self.balance) return "%i: %s (%.2f)" % (self.pk, self.event, self.balance)
@@ -637,11 +605,11 @@ class Payment(models.Model, RevisionMixin):
reversion_hide = True reversion_hide = True
def __str__(self): def __str__(self):
return "%s: %d" % (self.get_method_display(), self.amount) return f"{self.get_method_display()}: {self.amount}"
@property @property
def activity_feed_string(self): def activity_feed_string(self):
return str("payment of £{}".format(self.amount)) return f"payment of £{self.amount}"
def validate_url(value): def validate_url(value):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 MiB

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 KiB

After

Width:  |  Height:  |  Size: 852 KiB

View File

@@ -20,8 +20,6 @@
<script src="{% static 'js/autocompleter.js' %}"></script> <script src="{% static 'js/autocompleter.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script> <script src="{% static 'js/tooltip.js' %}"></script>
{% include 'partials/datetime-fix.html' %}
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('button[data-action=add]').on('click', function (event) { $('button[data-action=add]').on('click', function (event) {

View File

@@ -8,13 +8,13 @@
{% block css %} {% block css %}
{{ block.super }} {{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/> <link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'css/simplemde.min.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'css/easymde.min.css' %}">
{% endblock %} {% endblock %}
{% block preload_js %} {% block preload_js %}
{{ block.super }} {{ block.super }}
<script src="{% static 'js/selects.js' %}"></script> <script src="{% static 'js/selects.js' %}"></script>
<script src="{% static 'js/simplemde.min.js' %}"></script> <script src="{% static 'js/easymde.min.js' %}"></script>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
@@ -23,8 +23,6 @@
<script src="{% static 'js/interaction.js' %}"></script> <script src="{% static 'js/interaction.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script> <script src="{% static 'js/tooltip.js' %}"></script>
{% include 'partials/datetime-fix.html' %}
<script> <script>
const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches; const matches = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.matchMedia("(update: slow)").matches;
$(document).ready(function () { $(document).ready(function () {

View File

@@ -180,15 +180,10 @@
{% for item in object.items.all %} {% for item in object.items.all %}
<tr> <tr>
<td> <td>
<para>{{ item.name }} <para>{{ item.name }}</para>
{% if item.description %} {% if item.description %}
</para> {{ item.description|markdown:"rml" }}
<para style="item_description"> {% endif %}
{{ item.description|markdown:"rml" }}
</para>
<para>
{% endif %}
</para>
</td> </td>
<td>£{{ item.cost|floatformat:2 }}</td> <td>£{{ item.cost|floatformat:2 }}</td>
<td>{{ item.quantity }}</td> <td>{{ item.quantity }}</td>
@@ -208,9 +203,7 @@
<tr> <tr>
<td> <td>
{% if quote %} {% if quote %}
<para> <para>This quote is valid for 30 days unless otherwise arranged.</para>
This quote is valid for 30 days unless otherwise arranged.
</para>
{% endif %} {% endif %}
</td> </td>
{% if object.vat > 0 %} {% if object.vat > 0 %}

View File

@@ -9,7 +9,7 @@ register = template.Library()
@register.filter(name="markdown") @register.filter(name="markdown")
def markdown_filter(text, input_format='html'): def markdown_filter(text, input_format='html', add_style=""):
# markdown library can't handle text=None # markdown library can't handle text=None
if text is None: if text is None:
return text return text

View File

@@ -145,11 +145,11 @@ class CreateEvent(FormPage):
def add_person(self): def add_person(self):
self.find_element(*self._add_person_selector).click() self.find_element(*self._add_person_selector).click()
return regions.Modal(self, self.driver.find_element_by_id('modal')) return regions.Modal(self, self.driver.find_element(By.ID, 'modal'))
def add_event_item(self): def add_event_item(self):
self.find_element(*self._add_item_selector).click() self.find_element(*self._add_item_selector).click()
element = self.driver.find_element_by_id('itemModal') element = self.driver.find_element(By.ID, 'itemModal')
self.wait.until(EC.visibility_of(element)) self.wait.until(EC.visibility_of(element))
return rigs_regions.ItemModal(self, element) return rigs_regions.ItemModal(self, element)

155
RIGS/tests/sample.md Normal file
View File

@@ -0,0 +1,155 @@
An h1 header
============
Paragraphs are separated by a blank line.
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
look like:
* this one
* that one
* the other one
Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.
> Block quotes are
> written like so.
>
> They can span multiple paragraphs,
> if you like.
Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all
in chapters 12--14"). Three dots ... will be converted to an ellipsis.
Unicode is supported.
An h2 header
------------
Here's a numbered list:
1. first item
2. second item
3. third item
Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here's a code sample:
# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:
~~~
define foobar() {
print "Welcome to flavor country!";
}
~~~
(which makes copying & pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:
~~~python
import time
# Quick, count to ten!
for i in range(10):
# (but not *too* quick)
time.sleep(0.5)
print i
~~~
### An h3 header ###
Now a nested list:
1. First, get these ingredients:
* carrots
* celery
* lentils
2. Boil some water.
3. Dump everything in the pot and follow
this algorithm:
find wooden spoon
uncover pot
stir
cover pot
balance wooden spoon precariously on pot handle
wait 10 minutes
goto first step (or shut off burner when done)
Do not bump wooden spoon or it will fall.
Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).
Here's a link to [a website](http://foo.bar). Here's a footnote [^1].
[^1]: Footnote text goes here.
Tables can look like this:
size material color
---- ------------ ------------
9 leather brown
10 hemp canvas natural
11 glass transparent
Table: Shoes, their sizes, and what they're made of
(The above is the caption for the table.) Pandoc also supports
multi-line tables:
-------- -----------------------
keyword text
-------- -----------------------
red Sunsets, apples, and
other red or reddish
things.
green Leaves, grass, frogs
and other things it's
not easy being.
-------- -----------------------
A horizontal rule follows.
***
Here's a definition list:
apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There's no "e" in tomatoe.
Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)
Here's a "line block":
| Line one
| Line too
| Line tree
and images can be specified like so:
![example image](example-image.jpg "An exemplary image")
Inline math equations go in like so: $\\omega = d\\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:
$$I = \\int \rho R^{2} dV$$
And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: \\`foo\\`, \\*bar\\*, etc.

View File

@@ -1,5 +1,8 @@
import os
import pytest
from datetime import date from datetime import date
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
@@ -12,8 +15,6 @@ from pytest_django.asserts import assertRedirects, assertNotContains, assertCont
from PyRIGS.tests.base import assert_times_almost_equal, assert_oembed, login from PyRIGS.tests.base import assert_times_almost_equal, assert_oembed, login
from RIGS import models from RIGS import models
import pytest
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@@ -371,163 +372,7 @@ def test_ra_redirect(admin_client, admin_user, ra):
class TestMarkdownTemplateTags(TestCase): class TestMarkdownTemplateTags(TestCase):
markdown = """ markdown = open(os.path.join(settings.BASE_DIR, "RIGS/tests/sample.md")).read()
An h1 header
============
Paragraphs are separated by a blank line.
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
look like:
* this one
* that one
* the other one
Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.
> Block quotes are
> written like so.
>
> They can span multiple paragraphs,
> if you like.
Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all
in chapters 12--14"). Three dots ... will be converted to an ellipsis.
Unicode is supported.
An h2 header
------------
Here's a numbered list:
1. first item
2. second item
3. third item
Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here's a code sample:
# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:
~~~
define foobar() {
print "Welcome to flavor country!";
}
~~~
(which makes copying & pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:
~~~python
import time
# Quick, count to ten!
for i in range(10):
# (but not *too* quick)
time.sleep(0.5)
print i
~~~
### An h3 header ###
Now a nested list:
1. First, get these ingredients:
* carrots
* celery
* lentils
2. Boil some water.
3. Dump everything in the pot and follow
this algorithm:
find wooden spoon
uncover pot
stir
cover pot
balance wooden spoon precariously on pot handle
wait 10 minutes
goto first step (or shut off burner when done)
Do not bump wooden spoon or it will fall.
Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).
Here's a link to [a website](http://foo.bar). Here's a footnote [^1].
[^1]: Footnote text goes here.
Tables can look like this:
size material color
---- ------------ ------------
9 leather brown
10 hemp canvas natural
11 glass transparent
Table: Shoes, their sizes, and what they're made of
(The above is the caption for the table.) Pandoc also supports
multi-line tables:
-------- -----------------------
keyword text
-------- -----------------------
red Sunsets, apples, and
other red or reddish
things.
green Leaves, grass, frogs
and other things it's
not easy being.
-------- -----------------------
A horizontal rule follows.
***
Here's a definition list:
apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There's no "e" in tomatoe.
Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)
Here's a "line block":
| Line one
| Line too
| Line tree
and images can be specified like so:
![example image](example-image.jpg "An exemplary image")
Inline math equations go in like so: $\\omega = d\\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:
$$I = \\int \rho R^{2} dV$$
And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: \\`foo\\`, \\*bar\\*, etc.
"""
def test_html_safe(self): def test_html_safe(self):
html = markdown_filter(self.markdown) html = markdown_filter(self.markdown)
@@ -556,6 +401,7 @@ which you wish to be displayed literally, ex.: \\`foo\\`, \\*bar\\*, etc.
description=self.markdown, description=self.markdown,
start_date='2016-01-01', start_date='2016-01-01',
) )
event_item = models.EventItem.objects.create(event=event, name="TI I1", quantity=1, cost=1.00, order=1, description="* test \n * test \n * test")
user = models.Profile.objects.create( user = models.Profile.objects.create(
username='RML test', username='RML test',
is_superuser=True, # Don't care about permissions is_superuser=True, # Don't care about permissions

View File

@@ -5,7 +5,7 @@ from django.views.generic import RedirectView
from PyRIGS.decorators import (api_key_required, has_oembed, from PyRIGS.decorators import (api_key_required, has_oembed,
permission_required_with_403) permission_required_with_403)
from RIGS import finance, ical, rigboard, views, hs from . import views
urlpatterns = [ urlpatterns = [
# People # People
@@ -42,101 +42,101 @@ urlpatterns = [
name='venue_update'), name='venue_update'),
# Rigboard # Rigboard
path('rigboard/', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'), path('rigboard/', login_required(views.RigboardIndex.as_view()), name='rigboard'),
path('rigboard/calendar/', login_required()(rigboard.WebCalendar.as_view()), path('rigboard/calendar/', login_required()(views.WebCalendar.as_view()),
name='web_calendar'), name='web_calendar'),
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/$', re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/$',
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), login_required()(views.WebCalendar.as_view()), name='web_calendar'),
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$', re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$',
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'), login_required()(views.WebCalendar.as_view()), name='web_calendar'),
path('rigboard/archive/', RedirectView.as_view(permanent=True, pattern_name='event_archive')), path('rigboard/archive/', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
path('event/<int:pk>/', has_oembed(oembed_view="event_oembed")(rigboard.EventDetail.as_view()), path('event/<int:pk>/', has_oembed(oembed_view="event_oembed")(views.EventDetail.as_view()),
name='event_detail'), name='event_detail'),
path('event/create/', permission_required_with_403('RIGS.add_event')(rigboard.EventCreate.as_view()), path('event/create/', permission_required_with_403('RIGS.add_event')(views.EventCreate.as_view()),
name='event_create'), name='event_create'),
path('event/archive/', login_required()(rigboard.EventArchive.as_view()), path('event/archive/', login_required()(views.EventArchive.as_view()),
name='event_archive'), name='event_archive'),
path('event/<int:pk>/embed/', path('event/<int:pk>/embed/',
xframe_options_exempt(login_required(login_url='/user/login/embed/')(rigboard.EventEmbed.as_view())), xframe_options_exempt(login_required(login_url='/user/login/embed/')(views.EventEmbed.as_view())),
name='event_embed'), name='event_embed'),
path('event/<int:pk>/oembed_json/', rigboard.EventOEmbed.as_view(), path('event/<int:pk>/oembed_json/', views.EventOEmbed.as_view(),
name='event_oembed'), name='event_oembed'),
path('event/<int:pk>/print/', permission_required_with_403('RIGS.view_event')(rigboard.EventPrint.as_view()), path('event/<int:pk>/print/', permission_required_with_403('RIGS.view_event')(views.EventPrint.as_view()),
name='event_print'), name='event_print'),
path('event/<int:pk>/edit/', permission_required_with_403('RIGS.change_event')(rigboard.EventUpdate.as_view()), path('event/<int:pk>/edit/', permission_required_with_403('RIGS.change_event')(views.EventUpdate.as_view()),
name='event_update'), name='event_update'),
path('event/<int:pk>/duplicate/', permission_required_with_403('RIGS.add_event')(rigboard.EventDuplicate.as_view()), path('event/<int:pk>/duplicate/', permission_required_with_403('RIGS.add_event')(views.EventDuplicate.as_view()),
name='event_duplicate'), name='event_duplicate'),
# Event H&S # Event H&S
path('event/hs/', permission_required_with_403('RIGS.view_riskassessment')(hs.HSList.as_view()), name='hs_list'), path('event/hs/', permission_required_with_403('RIGS.view_riskassessment')(views.HSList.as_view()), name='hs_list'),
path('event/<int:pk>/ra/', permission_required_with_403('RIGS.add_riskassessment')(hs.EventRiskAssessmentCreate.as_view()), path('event/<int:pk>/ra/', permission_required_with_403('RIGS.add_riskassessment')(views.EventRiskAssessmentCreate.as_view()),
name='event_ra'), name='event_ra'),
path('event/ra/<int:pk>/', permission_required_with_403('RIGS.view_riskassessment')(hs.EventRiskAssessmentDetail.as_view()), path('event/ra/<int:pk>/', permission_required_with_403('RIGS.view_riskassessment')(views.EventRiskAssessmentDetail.as_view()),
name='ra_detail'), name='ra_detail'),
path('event/ra/<int:pk>/edit/', permission_required_with_403('RIGS.change_riskassessment')(hs.EventRiskAssessmentEdit.as_view()), path('event/ra/<int:pk>/edit/', permission_required_with_403('RIGS.change_riskassessment')(views.EventRiskAssessmentEdit.as_view()),
name='ra_edit'), name='ra_edit'),
path('event/ra/list', permission_required_with_403('RIGS.view_riskassessment')(hs.EventRiskAssessmentList.as_view()), path('event/ra/list', permission_required_with_403('RIGS.view_riskassessment')(views.EventRiskAssessmentList.as_view()),
name='ra_list'), name='ra_list'),
path('event/ra/<int:pk>/review/', permission_required_with_403('RIGS.review_riskassessment')(hs.EventRiskAssessmentReview.as_view()), path('event/ra/<int:pk>/review/', permission_required_with_403('RIGS.review_riskassessment')(views.EventRiskAssessmentReview.as_view()),
name='ra_review'), name='ra_review'),
path('event/<int:pk>/checklist/', permission_required_with_403('RIGS.add_eventchecklist')(hs.EventChecklistCreate.as_view()), path('event/<int:pk>/checklist/', permission_required_with_403('RIGS.add_eventchecklist')(views.EventChecklistCreate.as_view()),
name='event_ec'), name='event_ec'),
path('event/checklist/<int:pk>/', permission_required_with_403('RIGS.view_eventchecklist')(hs.EventChecklistDetail.as_view()), path('event/checklist/<int:pk>/', permission_required_with_403('RIGS.view_eventchecklist')(views.EventChecklistDetail.as_view()),
name='ec_detail'), name='ec_detail'),
path('event/checklist/<int:pk>/edit/', permission_required_with_403('RIGS.change_eventchecklist')(hs.EventChecklistEdit.as_view()), path('event/checklist/<int:pk>/edit/', permission_required_with_403('RIGS.change_eventchecklist')(views.EventChecklistEdit.as_view()),
name='ec_edit'), name='ec_edit'),
path('event/checklist/list', permission_required_with_403('RIGS.view_eventchecklist')(hs.EventChecklistList.as_view()), path('event/checklist/list', permission_required_with_403('RIGS.view_eventchecklist')(views.EventChecklistList.as_view()),
name='ec_list'), name='ec_list'),
path('event/checklist/<int:pk>/review/', permission_required_with_403('RIGS.review_eventchecklist')(hs.EventChecklistReview.as_view()), path('event/checklist/<int:pk>/review/', permission_required_with_403('RIGS.review_eventchecklist')(views.EventChecklistReview.as_view()),
name='ec_review'), name='ec_review'),
# Finance # Finance
path('invoice/', permission_required_with_403('RIGS.view_invoice')(finance.InvoiceIndex.as_view()), path('invoice/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceIndex.as_view()),
name='invoice_list'), name='invoice_list'),
path('invoice/archive/', permission_required_with_403('RIGS.view_invoice')(finance.InvoiceArchive.as_view()), path('invoice/archive/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceArchive.as_view()),
name='invoice_archive'), name='invoice_archive'),
path('invoice/waiting/', permission_required_with_403('RIGS.add_invoice')(finance.InvoiceWaiting.as_view()), path('invoice/waiting/', permission_required_with_403('RIGS.add_invoice')(views.InvoiceWaiting.as_view()),
name='invoice_waiting'), name='invoice_waiting'),
path('event/<int:pk>/invoice/', permission_required_with_403('RIGS.add_invoice')(finance.InvoiceEvent.as_view()), path('event/<int:pk>/invoice/', permission_required_with_403('RIGS.add_invoice')(views.InvoiceEvent.as_view()),
name='invoice_event'), name='invoice_event'),
path('event/<int:pk>/invoice/void', permission_required_with_403('RIGS.add_invoice')(finance.InvoiceEvent.as_view()), path('event/<int:pk>/invoice/void', permission_required_with_403('RIGS.add_invoice')(views.InvoiceEvent.as_view()),
name='invoice_event_void', kwargs={'void': True}), name='invoice_event_void', kwargs={'void': True}),
path('invoice/<int:pk>/', permission_required_with_403('RIGS.view_invoice')(finance.InvoiceDetail.as_view()), path('invoice/<int:pk>/', permission_required_with_403('RIGS.view_invoice')(views.InvoiceDetail.as_view()),
name='invoice_detail'), name='invoice_detail'),
path('invoice/<int:pk>/print/', permission_required_with_403('RIGS.view_invoice')(finance.InvoicePrint.as_view()), path('invoice/<int:pk>/print/', permission_required_with_403('RIGS.view_invoice')(views.InvoicePrint.as_view()),
name='invoice_print'), name='invoice_print'),
path('invoice/<int:pk>/void/', permission_required_with_403('RIGS.change_invoice')(finance.InvoiceVoid.as_view()), path('invoice/<int:pk>/void/', permission_required_with_403('RIGS.change_invoice')(views.InvoiceVoid.as_view()),
name='invoice_void'), name='invoice_void'),
path('invoice/<int:pk>/delete/', path('invoice/<int:pk>/delete/',
permission_required_with_403('RIGS.change_invoice')(finance.InvoiceDelete.as_view()), permission_required_with_403('RIGS.change_invoice')(views.InvoiceDelete.as_view()),
name='invoice_delete'), name='invoice_delete'),
path('payment/create/', permission_required_with_403('RIGS.add_payment')(finance.PaymentCreate.as_view()), path('payment/create/', permission_required_with_403('RIGS.add_payment')(views.PaymentCreate.as_view()),
name='payment_create'), name='payment_create'),
path('payment/<int:pk>/delete/', permission_required_with_403('RIGS.add_payment')(finance.PaymentDelete.as_view()), path('payment/<int:pk>/delete/', permission_required_with_403('RIGS.add_payment')(views.PaymentDelete.as_view()),
name='payment_delete'), name='payment_delete'),
# Client event authorisation # Client event authorisation
path('event/<pk>/auth/', path('event/<pk>/auth/',
permission_required_with_403('RIGS.change_event')(rigboard.EventAuthorisationRequest.as_view()), permission_required_with_403('RIGS.change_event')(views.EventAuthorisationRequest.as_view()),
name='event_authorise_request'), name='event_authorise_request'),
path('event/<int:pk>/auth/preview/', path('event/<int:pk>/auth/preview/',
permission_required_with_403('RIGS.change_event')(rigboard.EventAuthoriseRequestEmailPreview.as_view()), permission_required_with_403('RIGS.change_event')(views.EventAuthoriseRequestEmailPreview.as_view()),
name='event_authorise_preview'), name='event_authorise_preview'),
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(), re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', views.EventAuthorise.as_view(),
name='event_authorise'), name='event_authorise'),
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/preview/$', rigboard.EventAuthorise.as_view(preview=True), re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/preview/$', views.EventAuthorise.as_view(preview=True),
name='event_authorise_form_preview'), name='event_authorise_form_preview'),
# ICS Calendar - API key authentication # ICS Calendar - API key authentication
re_path(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), re_path(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(views.CalendarICS()),
name="ics_calendar"), name="ics_calendar"),

5
RIGS/views/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .crud import *
from .finance import *
from .hs import *
from .ical import *
from .rigboard import *

View File

@@ -27,7 +27,7 @@ from django.views import generic
from z3c.rml import rml2pdf from z3c.rml import rml2pdf
from PyRIGS import decorators from PyRIGS import decorators
from PyRIGS.views import OEmbedView, is_ajax from PyRIGS.views import OEmbedView, is_ajax, ModalURLMixin
from RIGS import models, forms from RIGS import models, forms
__author__ = 'ghost' __author__ = 'ghost'
@@ -53,10 +53,11 @@ class WebCalendar(generic.TemplateView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['view'] = kwargs.get('view', '') context['view'] = kwargs.get('view', '')
context['date'] = kwargs.get('date', '') context['date'] = kwargs.get('date', '')
# context['page_title'] = "Calendar"
return context return context
class EventDetail(generic.DetailView): class EventDetail(generic.DetailView, ModalURLMixin):
template_name = 'event_detail.html' template_name = 'event_detail.html'
model = models.Event model = models.Event
@@ -66,6 +67,10 @@ class EventDetail(generic.DetailView):
if self.object.dry_hire: if self.object.dry_hire:
title += " <span class='badge badge-secondary'>Dry Hire</span>" title += " <span class='badge badge-secondary'>Dry Hire</span>"
context['page_title'] = title context['page_title'] = title
if is_ajax(self.request):
context['override'] = "base_ajax.html"
else:
context['override'] = 'base_assets.html'
return context return context

View File

@@ -189,3 +189,7 @@ class Asset(models.Model, RevisionMixin):
@property @property
def display_id(self): def display_id(self):
return str(self.asset_id) return str(self.asset_id)
@property
def name(self):
return f"{self.display_id} | {self.description}"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -5,13 +5,13 @@
{% block css %} {% block css %}
{{ block.super }} {{ block.super }}
<link rel="stylesheet" href="{% static 'css/selects.css' %}"/> <link rel="stylesheet" href="{% static 'css/selects.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'css/simplemde.min.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'css/easymde.min.css' %}">
{% endblock %} {% endblock %}
{% block preload_js %} {% block preload_js %}
{{ block.super }} {{ block.super }}
<script src="{% static 'js/selects.js' %}"></script> <script src="{% static 'js/selects.js' %}"></script>
<script src="{% static 'js/simplemde.min.js' %}"></script> <script src="{% static 'js/easymde.min.js' %}"></script>
<script src="{% static 'js/interaction.js' %}"></script> <script src="{% static 'js/interaction.js' %}"></script>
{% endblock %} {% endblock %}
@@ -31,48 +31,8 @@
checkIfCableHidden(); checkIfCableHidden();
</script> </script>
<script> <script>
$('#parent_id') $('document').ready(function(){
.selectpicker({ $(document).find(".selectpicker").selectpicker().each(function(){initPicker($(this))});
liveSearch: true
})
.ajaxSelectPicker({
ajax: {
url: "{% url 'asset_search_json' %}",
type: "GET",
data: function () {
let params = {
{% verbatim %}query: '{{{q}}}'{% endverbatim %}
};
return params;
}
},
locale: {
emptyTitle: 'Search for item...'
},
preprocessData: function(data){
var assets = [];
if(data.length){
var len = data.length;
for(var i = 0; i < len; i++){
var curr = data[i];
assets.push(
{
'value': curr.id,
'text': curr.label,
'disabled': false
}
);
}
assets.push(
{
'value': null,
'text': "No parent"
});
}
return assets;
},
preserveSelected: false
}); });
</script> </script>
<script> <script>

View File

@@ -7,7 +7,7 @@
{% if create or edit or duplicate %} {% if create or edit or duplicate %}
<div class="form-group" id="parent-group"> <div class="form-group" id="parent-group">
<label for="selectpicker">Set Parent</label> <label for="selectpicker">Set Parent</label>
<select name="parent" id="parent_id" class="form-control selectpicker" data-live-search="true"> <select name="parent" id="parent_id" class="form-control selectpicker" data-live-search="true" data-sourceurl="{% url 'api_secure' model='asset' %}?fields=asset_id,description">
{% if object.parent %} {% if object.parent %}
<option value="{{object.parent.pk}}" selected>{{object.parent.description}}</option> <option value="{{object.parent.pk}}" selected>{{object.parent.description}}</option>
{% endif %} {% endif %}

View File

@@ -1,3 +1,4 @@
import time
import datetime import datetime
from django.utils import timezone from django.utils import timezone
@@ -138,11 +139,11 @@ class TestAssetForm(AutoLoginTest):
self.page.parent_selector.toggle() self.page.parent_selector.toggle()
self.assertTrue(self.page.parent_selector.is_open) self.assertTrue(self.page.parent_selector.is_open)
option = str(self.parent) option = self.parent.asset_id
self.page.parent_selector.search(option) self.page.parent_selector.search(option)
self.driver.implicitly_wait(1) time.sleep(2) # Slow down for javascript
self.page.parent_selector.set_option(option, True) # self.page.parent_selector.set_option(option, True)
self.assertTrue(self.page.parent_selector.options[0].selected) # self.assertTrue(self.page.parent_selector.options[0].selected)
self.page.parent_selector.toggle() self.page.parent_selector.toggle()
self.assertFalse(self.driver.find_element_by_id('cable-table').is_displayed()) self.assertFalse(self.driver.find_element_by_id('cable-table').is_displayed())

View File

@@ -27,7 +27,6 @@ urlpatterns = [
path('cabletype/<int:pk>/update/', permission_required_with_403('assets.change_cable_type')(views.CableTypeUpdate.as_view()), name='cable_type_update'), path('cabletype/<int:pk>/update/', permission_required_with_403('assets.change_cable_type')(views.CableTypeUpdate.as_view()), name='cable_type_update'),
path('cabletype/<int:pk>/detail/', login_required(views.CableTypeDetail.as_view()), name='cable_type_detail'), path('cabletype/<int:pk>/detail/', login_required(views.CableTypeDetail.as_view()), name='cable_type_detail'),
path('asset/search/', login_required(views.AssetSearch.as_view()), name='asset_search_json'),
path('asset/id/<str:pk>/embed/', path('asset/id/<str:pk>/embed/',
xframe_options_exempt( xframe_options_exempt(
login_required(login_url='/user/login/embed/')(views.AssetEmbed.as_view())), login_required(login_url='/user/login/embed/')(views.AssetEmbed.as_view())),
@@ -43,6 +42,4 @@ urlpatterns = [
(views.SupplierCreate.as_view()), name='supplier_create'), (views.SupplierCreate.as_view()), name='supplier_create'),
path('supplier/<int:pk>/edit/', permission_required_with_403('assets.change_supplier') path('supplier/<int:pk>/edit/', permission_required_with_403('assets.change_supplier')
(views.SupplierUpdate.as_view()), name='supplier_update'), (views.SupplierUpdate.as_view()), name='supplier_update'),
path('supplier/search/', login_required(views.SupplierSearch.as_view()), name='supplier_search_json'),
] ]

View File

@@ -87,28 +87,10 @@ class AssetList(LoginRequiredMixin, generic.ListView):
return context return context
class AssetSearch(AssetList):
hide_hidden_status = False
def render_to_response(self, context, **response_kwargs):
result = []
for asset in context["object_list"]:
result.append({"id": asset.pk, "label": (asset.asset_id + " | " + asset.description)})
return JsonResponse(result, safe=False)
class AssetIDUrlMixin: class AssetIDUrlMixin:
def get_object(self, queryset=None): def get_object(self, queryset=None):
pk = self.kwargs.get(self.pk_url_kwarg) pk = self.kwargs.get(self.pk_url_kwarg)
queryset = models.Asset.objects.filter(asset_id=pk) return get_object_or_404(models.Asset, asset_id=pk)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404("No assets found matching the query")
return obj
class AssetDetail(LoginRequiredMixin, AssetIDUrlMixin, generic.DetailView): class AssetDetail(LoginRequiredMixin, AssetIDUrlMixin, generic.DetailView):
@@ -194,7 +176,6 @@ class AssetOEmbed(OEmbedView):
class AssetAuditList(AssetList): class AssetAuditList(AssetList):
template_name = 'asset_audit_list.html' template_name = 'asset_audit_list.html'
hide_hidden_status = False
# TODO Refresh this when the modal is submitted # TODO Refresh this when the modal is submitted
def get_queryset(self): def get_queryset(self):
@@ -242,17 +223,6 @@ class SupplierList(GenericListView):
return context return context
class SupplierSearch(SupplierList):
hide_hidden_status = False
def render_to_response(self, context, **response_kwargs):
result = []
for supplier in context["object_list"]:
result.append({"id": supplier.pk, "name": supplier.name})
return JsonResponse(result, safe=False)
class SupplierDetail(GenericDetailView): class SupplierDetail(GenericDetailView):
model = models.Supplier model = models.Supplier

View File

@@ -27,8 +27,7 @@ function styles(done) {
'node_modules/fullcalendar/main.css', 'node_modules/fullcalendar/main.css',
'node_modules/bootstrap-select/dist/css/bootstrap-select.css', 'node_modules/bootstrap-select/dist/css/bootstrap-select.css',
'node_modules/ajax-bootstrap-select/dist/css/ajax-bootstrap-select.css', 'node_modules/ajax-bootstrap-select/dist/css/ajax-bootstrap-select.css',
'node_modules/flatpickr/dist/flatpickr.css', 'node_modules/easymde/dist/easymde.min.css'
'node_modules/simplemde/dist/simplemde.min.css'
]) ])
.pipe(sourcemaps.init()) .pipe(sourcemaps.init())
.pipe(sass().on('error', sass.logError)) .pipe(sass().on('error', sass.logError))
@@ -59,12 +58,11 @@ function scripts() {
'node_modules/html5sortable/dist/html5sortable.min.js', 'node_modules/html5sortable/dist/html5sortable.min.js',
'node_modules/clipboard/dist/clipboard.min.js', 'node_modules/clipboard/dist/clipboard.min.js',
'node_modules/flatpickr/dist/flatpickr.min.js',
'node_modules/moment/moment.js', 'node_modules/moment/moment.js',
'node_modules/fullcalendar/main.js', 'node_modules/fullcalendar/main.js',
'node_modules/bootstrap-select/dist/js/bootstrap-select.js', 'node_modules/bootstrap-select/dist/js/bootstrap-select.js',
'node_modules/ajax-bootstrap-select/dist/js/ajax-bootstrap-select.js', 'node_modules/ajax-bootstrap-select/dist/js/ajax-bootstrap-select.js',
'node_modules/simplemde/dist/simplemde.min.js', 'node_modules/easymde/dist/easymde.min.js',
'node_modules/konami/konami.js', 'node_modules/konami/konami.js',
'pipeline/source_assets/js/**/*.js',]) 'pipeline/source_assets/js/**/*.js',])
.pipe(gulpif(function(file) { return base_scripts.includes(file.relative);}, con('base.js'))) .pipe(gulpif(function(file) { return base_scripts.includes(file.relative);}, con('base.js')))

185
package-lock.json generated
View File

@@ -13,11 +13,11 @@
"ajax-bootstrap-select": "^1.4.5", "ajax-bootstrap-select": "^1.4.5",
"autocompleter": "^6.1.2", "autocompleter": "^6.1.2",
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.0",
"bootstrap": "^4.5.2", "bootstrap": "^4.6.1",
"bootstrap-select": "^1.13.17", "bootstrap-select": "^1.13.17",
"clipboard": "^2.0.8", "clipboard": "^2.0.8",
"cssnano": "^5.0.13", "cssnano": "^5.0.13",
"flatpickr": "^4.6.6", "easymde": "^2.16.1",
"fullcalendar": "^5.10.1", "fullcalendar": "^5.10.1",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
@@ -34,7 +34,6 @@
"node-sass": "^7.0.0", "node-sass": "^7.0.0",
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"simplemde": "^1.11.2",
"uglify-js": "^3.14.5" "uglify-js": "^3.14.5"
}, },
"devDependencies": { "devDependencies": {
@@ -256,6 +255,24 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/@types/codemirror": {
"version": "5.60.5",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",
"integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==",
"dependencies": {
"@types/tern": "*"
}
},
"node_modules/@types/estree": {
"version": "0.0.50",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw=="
},
"node_modules/@types/marked": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.1.tgz",
"integrity": "sha512-ZigEmCWdNUU7IjZEuQ/iaimYdDHWHfTe3kg8ORfKjyGYd9RWumPoOJRQXB0bO+XLkNwzCthW3wUIQtANaEZ1ag=="
},
"node_modules/@types/minimist": { "node_modules/@types/minimist": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
@@ -266,6 +283,14 @@
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz",
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw=="
}, },
"node_modules/@types/tern": {
"version": "0.23.4",
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
"integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/abbrev": { "node_modules/abbrev": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -932,9 +957,17 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
}, },
"node_modules/bootstrap": { "node_modules/bootstrap": {
"version": "4.6.0", "version": "4.6.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==" "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
},
"peerDependencies": {
"jquery": "1.9.1 - 3",
"popper.js": "^1.16.1"
}
}, },
"node_modules/bootstrap-select": { "node_modules/bootstrap-select": {
"version": "1.13.18", "version": "1.13.18",
@@ -1579,12 +1612,20 @@
} }
}, },
"node_modules/copy-props": { "node_modules/copy-props": {
"version": "2.0.4", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz",
"integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==",
"dependencies": { "dependencies": {
"each-props": "^1.3.0", "each-props": "^1.3.2",
"is-plain-object": "^2.0.1" "is-plain-object": "^5.0.0"
}
},
"node_modules/copy-props/node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
} }
}, },
"node_modules/core-util-is": { "node_modules/core-util-is": {
@@ -2026,6 +2067,18 @@
"node": ">= 4.0.0" "node": ">= 4.0.0"
} }
}, },
"node_modules/easymde": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/easymde/-/easymde-2.16.1.tgz",
"integrity": "sha512-FihYgjRsKfhGNk89SHSqxKLC4aJ1kfybPWW6iAmtb5GnXu+tnFPSzSaGBmk1RRlCuhFSjhF0SnIMGVPjEzkr6g==",
"dependencies": {
"@types/codemirror": "^5.60.4",
"@types/marked": "^4.0.1",
"codemirror": "^5.63.1",
"codemirror-spell-checker": "1.1.2",
"marked": "^4.0.10"
}
},
"node_modules/eazy-logger": { "node_modules/eazy-logger": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-3.1.0.tgz", "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-3.1.0.tgz",
@@ -2735,11 +2788,6 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/flatpickr": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz",
"integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw=="
},
"node_modules/flush-write-stream": { "node_modules/flush-write-stream": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
@@ -2750,12 +2798,23 @@
} }
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.14.6", "version": "1.14.7",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
"integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==", "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
"dev": true, "dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
} }
}, },
"node_modules/for-in": { "node_modules/for-in": {
@@ -4868,9 +4927,9 @@
} }
}, },
"node_modules/marked": { "node_modules/marked": {
"version": "4.0.8", "version": "4.0.10",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.8.tgz", "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz",
"integrity": "sha512-dkpJMIlJpc833hbjjg8jraw1t51e/eKDoG8TFOgc5O0Z77zaYKigYekTDop5AplRoKFGIaoazhYEhGkMtU3IeA==", "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==",
"bin": { "bin": {
"marked": "bin/marked.js" "marked": "bin/marked.js"
}, },
@@ -5441,9 +5500,9 @@
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.1.30", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==", "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==",
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@@ -7318,16 +7377,6 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
"integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ=="
}, },
"node_modules/simplemde": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/simplemde/-/simplemde-1.11.2.tgz",
"integrity": "sha1-ojo12XjSxA7wfewAjJLwcNjggOM=",
"dependencies": {
"codemirror": "*",
"codemirror-spell-checker": "*",
"marked": "*"
}
},
"node_modules/smart-buffer": { "node_modules/smart-buffer": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
@@ -9113,6 +9162,24 @@
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==" "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="
}, },
"@types/codemirror": {
"version": "5.60.5",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz",
"integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==",
"requires": {
"@types/tern": "*"
}
},
"@types/estree": {
"version": "0.0.50",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw=="
},
"@types/marked": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.1.tgz",
"integrity": "sha512-ZigEmCWdNUU7IjZEuQ/iaimYdDHWHfTe3kg8ORfKjyGYd9RWumPoOJRQXB0bO+XLkNwzCthW3wUIQtANaEZ1ag=="
},
"@types/minimist": { "@types/minimist": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
@@ -9123,6 +9190,14 @@
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz",
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw=="
}, },
"@types/tern": {
"version": "0.23.4",
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
"integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
"requires": {
"@types/estree": "*"
}
},
"abbrev": { "abbrev": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -9634,9 +9709,10 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
}, },
"bootstrap": { "bootstrap": {
"version": "4.6.0", "version": "4.6.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==" "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==",
"requires": {}
}, },
"bootstrap-select": { "bootstrap-select": {
"version": "1.13.18", "version": "1.13.18",
@@ -10551,6 +10627,18 @@
"lodash": "^4.17.10" "lodash": "^4.17.10"
} }
}, },
"easymde": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/easymde/-/easymde-2.16.1.tgz",
"integrity": "sha512-FihYgjRsKfhGNk89SHSqxKLC4aJ1kfybPWW6iAmtb5GnXu+tnFPSzSaGBmk1RRlCuhFSjhF0SnIMGVPjEzkr6g==",
"requires": {
"@types/codemirror": "^5.60.4",
"@types/marked": "^4.0.1",
"codemirror": "^5.63.1",
"codemirror-spell-checker": "1.1.2",
"marked": "^4.0.10"
}
},
"eazy-logger": { "eazy-logger": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-3.1.0.tgz", "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-3.1.0.tgz",
@@ -11163,11 +11251,6 @@
"resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
"integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q=="
}, },
"flatpickr": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz",
"integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw=="
},
"flush-write-stream": { "flush-write-stream": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
@@ -13348,9 +13431,9 @@
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
}, },
"nanoid": { "nanoid": {
"version": "3.1.30", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==" "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA=="
}, },
"nanomatch": { "nanomatch": {
"version": "1.2.13", "version": "1.2.13",
@@ -14810,16 +14893,6 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
"integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ=="
}, },
"simplemde": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/simplemde/-/simplemde-1.11.2.tgz",
"integrity": "sha1-ojo12XjSxA7wfewAjJLwcNjggOM=",
"requires": {
"codemirror": "*",
"codemirror-spell-checker": "*",
"marked": "*"
}
},
"smart-buffer": { "smart-buffer": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",

View File

@@ -10,11 +10,11 @@
"ajax-bootstrap-select": "^1.4.5", "ajax-bootstrap-select": "^1.4.5",
"autocompleter": "^6.1.2", "autocompleter": "^6.1.2",
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.0",
"bootstrap": "^4.5.2", "bootstrap": "^4.6.1",
"bootstrap-select": "^1.13.17", "bootstrap-select": "^1.13.17",
"clipboard": "^2.0.8", "clipboard": "^2.0.8",
"cssnano": "^5.0.13", "cssnano": "^5.0.13",
"flatpickr": "^4.6.6", "easymde": "^2.16.1",
"fullcalendar": "^5.10.1", "fullcalendar": "^5.10.1",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
@@ -31,7 +31,6 @@
"node-sass": "^7.0.0", "node-sass": "^7.0.0",
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"simplemde": "^1.11.2",
"uglify-js": "^3.14.5" "uglify-js": "^3.14.5"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,7 +1,3 @@
marked.setOptions({
breaks: true,
})
function setupItemTable(items_json) { function setupItemTable(items_json) {
objectitems = JSON.parse(items_json) objectitems = JSON.parse(items_json)
$.each(objectitems, function (key, val) { $.each(objectitems, function (key, val) {
@@ -37,7 +33,8 @@ function updatePrices() {
} }
function setupMDE(selector) { function setupMDE(selector) {
editor = new SimpleMDE({ editor = new EasyMDE({
autoDownloadFontAwesome: false,
element: $(selector)[0], element: $(selector)[0],
forceSync: true, forceSync: true,
toolbar: ["bold", "italic", "strikethrough", "|", "unordered-list", "ordered-list", "|", "link", "|", "preview", "guide"], toolbar: ["bold", "italic", "strikethrough", "|", "unordered-list", "ordered-list", "|", "link", "|", "preview", "guide"],
@@ -120,7 +117,7 @@ $('body').on('submit', '#item-form', function (e) {
// update the table // update the table
$row = $('#item-' + pk); $row = $('#item-' + pk);
$row.find('.name').html(escapeHtml(fields.name)); $row.find('.name').html(escapeHtml(fields.name));
$row.find('.description').html(marked(fields.description)); $row.find('.description').html(fields.description);
$row.find('.cost').html(parseFloat(fields.cost).toFixed(2)); $row.find('.cost').html(parseFloat(fields.cost).toFixed(2));
$row.find('.quantity').html(fields.quantity); $row.find('.quantity').html(fields.quantity);

View File

@@ -123,7 +123,7 @@
color: $gray-100; color: $gray-100;
} }
input:-webkit-autofill, input:-webkit-autofill,
input:-webkit-autofill:hover, input:-webkit-autofill:hover,
input:-webkit-autofill:focus, input:-webkit-autofill:focus,
textarea:-webkit-autofill, textarea:-webkit-autofill,
textarea:-webkit-autofill:hover, textarea:-webkit-autofill:hover,
@@ -145,7 +145,7 @@
.editor-toolbar > a.active { .editor-toolbar > a.active {
background: $info !important; background: $info !important;
} }
.cm-s-paper { .cm-s-easymde {
color: white; color: white;
background-color: $darktheme; background-color: $darktheme;
border-color: #bbb; border-color: #bbb;
@@ -153,4 +153,7 @@
.CodeMirror-cursor { .CodeMirror-cursor {
border-color: white !important; border-color: white !important;
} }
.modal {
overflow-y: auto !important; //Bootstrap Dark Theme overrides this to none for some insane reason so we need to change it back
}
} }

View File

@@ -116,10 +116,6 @@ textarea {
hyphens: auto; hyphens: auto;
} }
.modal-dialog {
z-index: inherit; // bug fix introduced in 52682ce
}
del { del {
background-color: #f2dede; background-color: #f2dede;
border-radius: 3px; border-radius: 3px;

View File

@@ -1,21 +0,0 @@
{% if not debug %}
<script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-43285686-12', 'auto');
{% if user.is_authenticated %}
ga('set', '&uid', {{ user.pk }});
{% endif %}
ga('require', 'linkid', 'linkid.js');
ga('send', 'pageview');
</script>
{% endif %}

View File

@@ -29,7 +29,6 @@
<body> <body>
<a class="skip-link" href='#main'>Skip to content</a> <a class="skip-link" href='#main'>Skip to content</a>
{% include "analytics.html" %}
{% block navbar %} {% block navbar %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" role="navigation"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark" role="navigation">
<div class="container"> <div class="container">
@@ -79,7 +78,6 @@
<div class="modal fade" id="modal" role="dialog" tabindex=-1></div> <div class="modal fade" id="modal" role="dialog" tabindex=-1></div>
<script src="{% static 'js/base.js' %}"></script> <script src="{% static 'js/base.js' %}"></script>
<script src="{% static 'js/marked.min.js' %}"></script>
{% include 'partials/dark_theme.html' %} {% include 'partials/dark_theme.html' %}
{% block js %} {% block js %}

View File

@@ -15,7 +15,6 @@
</head> </head>
<body> <body>
{% include "analytics.html" %}
<div class="embed_container"> <div class="embed_container">
<div class="container-fluid"> <div class="container-fluid">
{% if messages %} {% if messages %}

View File

@@ -10,7 +10,7 @@
<h2 class="col-sm-12 pb-3">Welcome back {{ user.get_full_name }}, there {%if rig_count == 1 %}is one rig coming up{%else%}are {{ rig_count|apnumber }} rigs coming up.{%endif%}</h2> <h2 class="col-sm-12 pb-3">Welcome back {{ user.get_full_name }}, there {%if rig_count == 1 %}is one rig coming up{%else%}are {{ rig_count|apnumber }} rigs coming up.{%endif%}</h2>
<div class="col-sm-4 mb-3"> <div class="col-sm-4 mb-3">
<div class="card"> <div class="card">
<img class="card-img-top" src="{% static 'imgs/rigs.jpg' %}" alt="" style="height: 150px; object-fit: cover;"> <img class="card-img-top d-none d-sm-block" src="{% static 'imgs/rigs.jpg' %}" alt="Some lights and haze, very purple" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Rigboard</h4> <h4 class="card-header">Rigboard</h4>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'rigboard' %}"><span class="fas fa-list align-middle"></span><span class="align-middle"> Rigboard</span></a> <a class="list-group-item list-group-item-action" href="{% url 'rigboard' %}"><span class="fas fa-list align-middle"></span><span class="align-middle"> Rigboard</span></a>
@@ -24,7 +24,7 @@
<div class="col-sm-4 mb-3"> <div class="col-sm-4 mb-3">
<div class="card"> <div class="card">
{% now "m-d" as todays_date %} {% now "m-d" as todays_date %}
<img class="card-img-top" src="{% if todays_date == '04-01' %}{% static 'imgs/tappytaptap.gif' %}{%else%}{% static 'imgs/assets.jpg' %}{%endif%}" alt="" style="height: 150px; object-fit: cover;"> <img class="card-img-top d-none d-sm-block" src="{% if todays_date == '04-01' %}{% static 'imgs/tappytaptap.gif' %}{%else%}{% static 'imgs/assets.jpg' %}{%endif%}" alt="M32 sound desk close up of the faders" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Asset Database</h4> <h4 class="card-header">Asset Database</h4>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="{% url 'asset_index' %}"><span class="fas fa-tag align-middle"></span><span class="align-middle"> Asset List</span></a> <a class="list-group-item list-group-item-action" href="{% url 'asset_index' %}"><span class="fas fa-tag align-middle"></span><span class="align-middle"> Asset List</span></a>
@@ -40,7 +40,7 @@
</div> </div>
<div class="col-sm-4 mb-3"> <div class="col-sm-4 mb-3">
<div class="card"> <div class="card">
<img class="card-img-top" src="{% static 'imgs/training.jpg' %}" alt="" style="height: 150px; object-fit: cover;"> <img class="card-img-top d-none d-sm-block" src="{% static 'imgs/training.jpg' %}" alt="People watching a presentation" style="height: 150px; object-fit: cover;">
<h4 class="card-header">Training Database</h4> <h4 class="card-header">Training Database</h4>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action text-info" href="{% url 'trainee_detail' request.user.pk %}"><span class="fas fa-file-signature align-middle"></span><span class="align-middle"> My Training Record</span></a> <a class="list-group-item list-group-item-action text-info" href="{% url 'trainee_detail' request.user.pk %}"><span class="fas fa-file-signature align-middle"></span><span class="align-middle"> My Training Record</span></a>

View File

@@ -1,7 +1,7 @@
{% load static %} {% load static %}
<script> <script>
if({{ request.user.dark_theme|lower|default:'false' }} || window.matchMedia('(prefers-color-scheme: dark)').matches) { if({{ request.user.dark_theme|lower|default:'false' }} || window.matchMedia('(prefers-color-scheme: dark)').matches) {
$('<link>').prependTo('head').attr({type : 'text/css', rel : 'stylesheet'}).attr('href', '{% static "css/dark_screen.css" %}'); document.querySelector('head').innerHTML += '<link rel="stylesheet" href="{% static "css/dark_screen.css" %}" type="text/css"/>';
document.body.setAttribute('data-theme', 'dark'); document.body.setAttribute('data-theme', 'dark');
} }
</script> </script>

View File

@@ -1,34 +0,0 @@
{% load static %}
<script>
function initDatetime() {
$('input[type=datetime-local]').not(':disabled').flatpickr({
dateFormat: 'Y-m-dTH:m',
enableTime: true,
allowInput: true,
altInput: true,
altFormat: "d/m/y H:m",
});
}
$(document).ready(function () {
function supportsDateTime() {
var input = document.createElement('input');
input.setAttribute('type','datetime-local');
return input.type !== "text";
}
//Firefox reports support for datetime-local without properly supporting it. Bah.
if(!supportsDateTime() || navigator.userAgent.toLowerCase().indexOf('firefox') > -1){
$('<link>')
.appendTo('head')
.attr({type : 'text/css', rel : 'stylesheet'})
.attr('href', '{% static "css/flatpickr.css" %}');
$.when(
$.getScript( '{% static "js/flatpickr.min.js" %}' ),
$.Deferred(function(deferred){
$(deferred.resolve);
})
).done(function(){
initDatetime();
});
}
});
</script>

View File

@@ -1,11 +1,12 @@
from RIGS.models import RevisionMixin, Profile from RIGS.models import Profile
from reversion import revisions as reversion from reversion import revisions as reversion
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from versioning.versioning import RevisionMixin
@reversion.register(for_concrete_model=False, fields=[], follow=["qualifications_obtained", "level_qualifications"]) @reversion.register(for_concrete_model=False, fields=[])
class Trainee(Profile, RevisionMixin): class Trainee(Profile, RevisionMixin):
class Meta: class Meta:
proxy = True proxy = True
@@ -38,6 +39,10 @@ class Trainee(Profile, RevisionMixin):
def display_id(self): def display_id(self):
return str(self) return str(self)
@property
def full_name(self):
return self.first_name + " " + self.last_name
class TrainingCategory(models.Model): class TrainingCategory(models.Model):
reference_number = models.IntegerField(unique=True) reference_number = models.IntegerField(unique=True)
@@ -96,14 +101,14 @@ class TrainingItemQualification(models.Model, RevisionMixin):
# TODO Maximum depth - some things stop at Complete and you can't be passed out in them # TODO Maximum depth - some things stop at Complete and you can't be passed out in them
def __str__(self): def __str__(self):
return "{} in {} on {}".format(self.get_depth_display(), self.item, self.date.strftime("%b %d %Y")) return f"{self.get_depth_display()} in {self.item} on {self.date.strftime('%b %d %Y')}"
@property @property
def activity_feed_string(self): def activity_feed_string(self):
return str("{} in {}".format(self.get_depth_display(), self.item)) return f"{self.get_depth_display()} in {self.item}"
@classmethod @classmethod
def get_colour_from_depth(cls, obj, depth): def get_colour_from_depth(cls, depth):
if depth == 0: if depth == 0:
return "warning" return "warning"
if depth == 1: if depth == 1:
@@ -238,7 +243,8 @@ class TrainingLevelRequirement(models.Model, RevisionMixin):
reversion_hide = True reversion_hide = True
def __str__(self): def __str__(self):
return "{} in {}".format(TrainingItemQualification.CHOICES[self.depth][1], self.item) depth = TrainingItemQualification.CHOICES[self.depth][1]
return f"{depth} in {self.item}"
class Meta: class Meta:
unique_together = ["level", "item"] unique_together = ["level", "item"]

View File

@@ -35,7 +35,7 @@ def colour_from_depth(depth):
@register.filter @register.filter
def get_levels_of_depth(trainee, level): def get_levels_of_depth(trainee, level):
return trainee.level_qualifications.all().exclude(confirmed_on=None).exclude(level__department=models.TrainingLevel.HAULAGE).select_related('level').filter(level__level=level) return trainee.level_qualifications.all().exclude(confirmed_on=None).exclude(level__department=None).exclude(level__department=models.TrainingLevel.HAULAGE).select_related('level').filter(level__level=level)
@register.simple_tag @register.simple_tag

View File

@@ -4,9 +4,10 @@ import pytest
from django.utils import timezone from django.utils import timezone
from django.urls import reverse from django.urls import reverse
from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains, assertURLEqual
from training import models from training import models
from reversion.models import Version, Revision
def test_add_qualification(admin_client, trainee, admin_user): def test_add_qualification(admin_client, trainee, admin_user):
@@ -19,6 +20,22 @@ def test_add_qualification(admin_client, trainee, admin_user):
assertFormError(response, 'form', 'supervisor', 'Selected supervisor must actually *be* a supervisor...') assertFormError(response, 'form', 'supervisor', 'Selected supervisor must actually *be* a supervisor...')
def test_add_qualification_reversion(admin_client, trainee, training_item, supervisor):
url = reverse('add_qualification', kwargs={'pk': trainee.pk})
date = (timezone.now() + datetime.timedelta(days=-3)).strftime("%Y-%m-%d")
response = admin_client.post(url, {'date': date, 'supervisor': supervisor.pk, 'trainee': trainee.pk, 'item': training_item.pk, 'depth': 0, 'notes': ""})
print(response.content)
assert response.status_code == 302
qual = models.TrainingItemQualification.objects.last()
assert qual is not None
assert training_item.pk == qual.pk
# Ensure only one revision has been created
assert Revision.objects.count() == 1
response = admin_client.post(url, {'date': date, 'supervisor': supervisor.pk, 'trainee': trainee.pk, 'item': training_item.pk, 'depth': 1})
assert Revision.objects.count() == 2
assert Version.objects.count() == 4 # Two item qualifications and the trainee twice
def test_add_requirement(admin_client, level): def test_add_requirement(admin_client, level):
url = reverse('add_requirement', kwargs={'pk': level.pk}) url = reverse('add_requirement', kwargs={'pk': level.pk})
response = admin_client.post(url) response = admin_client.post(url)
@@ -36,3 +53,10 @@ def test_trainee_detail(admin_client, trainee, admin_user):
assertNotContains(response, "Your") assertNotContains(response, "Your")
name = trainee.first_name + " " + trainee.last_name name = trainee.first_name + " " + trainee.last_name
assertContains(response, f"{name}'s Training Record") assertContains(response, f"{name}'s Training Record")
def test_trainee_item_detail(admin_client, trainee):
url = reverse('trainee_item_detail', kwargs={'pk': trainee.pk})
response = admin_client.get(url)
assert response.status_code == 200
assertContains(response, "Nothing found")

View File

@@ -9,6 +9,7 @@ from django.db.models import Q, Count
from PyRIGS.views import is_ajax, ModalURLMixin from PyRIGS.views import is_ajax, ModalURLMixin
from training import models, forms from training import models, forms
from users import views from users import views
from reversion.views import RevisionMixin
class ItemList(generic.ListView): class ItemList(generic.ListView):
@@ -34,7 +35,7 @@ class TraineeDetail(views.ProfileDetail):
if self.request.user.pk == self.object.pk: if self.request.user.pk == self.object.pk:
context["page_title"] = "Your Training Record" context["page_title"] = "Your Training Record"
else: else:
context["page_title"] = "{}'s Training Record".format(self.object.first_name + " " + self.object.last_name) context["page_title"] = f"{self.object.full_name}'s Training Record"
context["started_levels"] = self.object.started_levels() context["started_levels"] = self.object.started_levels()
context["completed_levels"] = self.object.level_qualifications.all() context["completed_levels"] = self.object.level_qualifications.all()
context["categories"] = models.TrainingCategory.objects.all().prefetch_related('items') context["categories"] = models.TrainingCategory.objects.all().prefetch_related('items')
@@ -63,7 +64,7 @@ class TraineeItemDetail(generic.ListView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
trainee = models.Trainee.objects.get(pk=self.kwargs['pk']) trainee = models.Trainee.objects.get(pk=self.kwargs['pk'])
context["trainee"] = models.Trainee.objects.get(pk=self.kwargs['pk']) context["trainee"] = models.Trainee.objects.get(pk=self.kwargs['pk'])
context["page_title"] = "Detailed Training Record for <a href='{}'>{}</a>".format(trainee.get_absolute_url(), trainee) context["page_title"] = f"Detailed Training Record for <a href='{trainee.get_absolute_url()}'>{trainee}</a>"
return context return context
@@ -73,7 +74,7 @@ class LevelDetail(generic.DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["page_title"] = "Training Level {} {}".format(self.object, self.object.get_icon) context["page_title"] = f"Training Level {self.object} {self.object.get_icon}"
context["users_with"] = map(lambda qual: qual.trainee, models.TrainingLevelQualification.objects.filter(level=self.object)) context["users_with"] = map(lambda qual: qual.trainee, models.TrainingLevelQualification.objects.filter(level=self.object))
context["u"] = models.Trainee.objects.get(pk=self.kwargs['u']) if 'u' in self.kwargs else self.request.user context["u"] = models.Trainee.objects.get(pk=self.kwargs['u']) if 'u' in self.kwargs else self.request.user
return context return context
@@ -120,8 +121,6 @@ class AddQualification(generic.CreateView, ModalURLMixin):
model = models.TrainingItemQualification model = models.TrainingItemQualification
form_class = forms.QualificationForm form_class = forms.QualificationForm
@transaction.atomic()
@reversion.create_revision()
def form_valid(self, form, *args, **kwargs): def form_valid(self, form, *args, **kwargs):
reversion.add_to_revision(form.cleaned_data['trainee']) reversion.add_to_revision(form.cleaned_data['trainee'])
return super().form_valid(form, *args, **kwargs) return super().form_valid(form, *args, **kwargs)
@@ -133,14 +132,15 @@ class AddQualification(generic.CreateView, ModalURLMixin):
context['override'] = "base_ajax.html" context['override'] = "base_ajax.html"
else: else:
context['override'] = 'base_training.html' context['override'] = 'base_training.html'
context['page_title'] = "Add Qualification for {}".format(models.Trainee.objects.get(pk=self.kwargs['pk'])) trainee = models.Trainee.objects.get(pk=self.kwargs['pk'])
context['page_title'] = f"Add Qualification for {trainee}"
return context return context
def get_success_url(self): def get_success_url(self):
return self.get_close_url('trainee_detail', 'trainee_detail') return self.get_close_url('add_qualification', 'trainee_detail')
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(AddQualification, self).get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs['pk'] = self.kwargs['pk'] kwargs['pk'] = self.kwargs['pk']
return kwargs return kwargs
@@ -153,7 +153,8 @@ class EditQualification(generic.UpdateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["depths"] = models.TrainingItemQualification.CHOICES context["depths"] = models.TrainingItemQualification.CHOICES
context['page_title'] = "Edit Qualification {} for {}".format(self.object, models.Trainee.objects.get(pk=self.kwargs['pk'])) trainee = models.Trainee.objects.get(pk=self.kwargs['pk'])
context['page_title'] = f"Edit Qualification {self.object} for {trainee}"
return context return context
def get_form_kwargs(self): def get_form_kwargs(self):
@@ -175,7 +176,8 @@ class AddLevelRequirement(generic.CreateView, ModalURLMixin):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["page_title"] = "Add Requirements to Training Level {}".format(models.TrainingLevel.objects.get(pk=self.kwargs['pk'])) level = models.TrainingLevel.objects.get(pk=self.kwargs['pk'])
context["page_title"] = f"Add Requirements to Training Level {level}"
return context return context
def get_form_kwargs(self): def get_form_kwargs(self):
@@ -220,9 +222,11 @@ class ConfirmLevel(generic.RedirectView):
level_qualification, created = models.TrainingLevelQualification.objects.get_or_create(trainee=trainee, level=models.TrainingLevel.objects.get(pk=kwargs['level_pk'])) level_qualification, created = models.TrainingLevelQualification.objects.get_or_create(trainee=trainee, level=models.TrainingLevel.objects.get(pk=kwargs['level_pk']))
if created: if created:
user = self.request.user
reversion.set_user(user)
level_qualification.confirmed_by = self.request.user level_qualification.confirmed_by = self.request.user
level_qualification.confirmed_on = timezone.now() level_qualification.confirmed_on = timezone.now()
level_qualification.save() level_qualification.save()
reversion.add_to_revision(trainee)
reversion.add_to_revision(trainee)
return reverse_lazy('trainee_detail', kwargs={'pk': kwargs['pk']}) return reverse_lazy('trainee_detail', kwargs={'pk': kwargs['pk']})

View File

@@ -14,8 +14,8 @@
{% for version in object_list %} {% for version in object_list %}
<tr> <tr>
<th scope="row">{{ version.revision.date_created }}</th> <th scope="row">{{ version.revision.date_created }}</th>
<td><a href="{{ version.changes.new.get_absolute_url }}">{{ version.changes.new.display_id|default:version.changes.new.pk }} | {{version.changes.new|to_class_name}}</a></td> <td><a href="{{ version.changes.new.get_absolute_url }}">{{ version.display_name }}</a></td>
<td>{{ version.pk }}|{{ version.revision.pk }}</td> <td>{{ version.display_id }}</td>
<td>{% include 'partials/linked_name.html' with profile=version.revision.user %}</td> <td>{% include 'partials/linked_name.html' with profile=version.revision.user %}</td>
<td> <td>
{% if version.changes.old == None %} {% if version.changes.old == None %}

View File

@@ -13,7 +13,7 @@
{% for version in object_list %} {% for version in object_list %}
<tr> <tr>
<th scope="row">{{ version.revision.date_created }}</th> <th scope="row">{{ version.revision.date_created }}</th>
<td>{{ version.pk }}|{{ version.revision.pk }}</td> <td>{{ version.display_id }}</td>
<td>{{ version.revision.user.name }}</td> <td>{{ version.revision.user.name }}</td>
<td> <td>
{% if version.changes.old is None %} {% if version.changes.old is None %}

View File

@@ -36,7 +36,7 @@ class RevisionMixin:
version = self.current_version version = self.current_version
if version is None: if version is None:
return None return None
return "V{0} | R{1}".format(version.pk, version.revision.pk) return version.display_id
@property @property
def date_created(self): def date_created(self):
@@ -148,9 +148,11 @@ class ModelComparison:
@cached_property @cached_property
def item_changes(self): def item_changes(self):
from RIGS.models import EventAuthorisation from RIGS.models import EventAuthorisation
from training.models import TrainingLevelQualification, TrainingItemQualification
if self.follow and self.version.object is not None: if self.follow and self.version.object is not None:
item_type = ContentType.objects.get_for_model(self.version.object) item_type = ContentType.objects.get_for_model(self.version.object)
old_item_versions = self.version.parent.revision.version_set.exclude(content_type=item_type) old_item_versions = self.version.parent.revision.version_set.exclude(content_type=item_type).exclude(content_type=ContentType.objects.get_for_model(TrainingItemQualification)) \
.exclude(content_type=ContentType.objects.get_for_model(TrainingLevelQualification))
new_item_versions = self.version.revision.version_set.exclude(content_type=item_type).exclude(content_type=ContentType.objects.get_for_model(EventAuthorisation)) new_item_versions = self.version.revision.version_set.exclude(content_type=item_type).exclude(content_type=ContentType.objects.get_for_model(EventAuthorisation))
comparisonParams = {'excluded_keys': ['id', 'event', 'order', 'checklist', 'level', '_order', 'date_joined']} comparisonParams = {'excluded_keys': ['id', 'event', 'order', 'checklist', 'level', '_order', 'date_joined']}
@@ -234,3 +236,16 @@ class RIGSVersion(Version):
old=self.parent._object_version.object if self.parent else None, old=self.parent._object_version.object if self.parent else None,
follow=True follow=True
) )
@property
def display_id(self):
return f"V{self.pk} | R{self.revision.pk}"
@property
def display_name(self):
if hasattr(self.changes.new, 'display_id'):
id = self.changes.new.display_id
else:
id = self.changes.new.pk
return f"{id} | {self.changes.new.__class__.__name__}"