Compare commits
428 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
919a5265c2 | ||
|
|
507d08f37c | ||
|
|
40c295aa37 | ||
|
|
2905a6af1a | ||
|
|
4cc27adb8b | ||
|
|
a5d000f702 | ||
|
|
39aa1d443d | ||
|
|
4b4086afb3 | ||
|
|
b0bb7d32ca | ||
|
|
ff1bd91223 | ||
|
|
3dad3580bb | ||
|
|
8f687145be | ||
|
|
9c405edd09 | ||
|
|
fe791dc478 | ||
|
|
42417621fa | ||
|
|
d3b3d85c84 | ||
|
|
b700013704 | ||
|
|
bac229c731 | ||
|
|
28043e634b | ||
|
|
b672108155 | ||
|
|
5e569ab0e6 | ||
|
|
43a07e53a6 | ||
|
|
fbd83196fc | ||
|
|
465f4e1e96 | ||
|
|
43d409ce64 | ||
|
|
a5fc52ec29 | ||
|
|
a9b06575d0 | ||
|
|
3070cbed3c | ||
|
|
d712881e16 | ||
|
|
991dc7f8c8 | ||
|
|
6b6c8da785 | ||
|
|
f40fbaed3e | ||
|
|
4973386dd0 | ||
|
|
13536b8bad | ||
|
|
caea7e334c | ||
|
|
b248774774 | ||
|
|
7a9d6325d5 | ||
|
|
b0d0b41502 | ||
|
|
30c89cb13c | ||
|
|
9175737b9c | ||
|
|
7ae772205a | ||
|
|
00b0a20c83 | ||
|
|
6604866342 | ||
|
|
881c3d943a | ||
|
|
d5118cc91f | ||
|
|
ac74cbdf72 | ||
|
|
01f7fc3cff | ||
|
|
85c850e5bf | ||
|
|
67dfffdd58 | ||
|
|
ae4aadb8d3 | ||
|
|
e5dc2bad6a | ||
|
|
0e2fabf139 | ||
|
|
c45a372e83 | ||
|
|
98ecb4c27c | ||
|
|
9023094326 | ||
|
|
497de05db2 | ||
|
|
cb3224664e | ||
|
|
9b532a5470 | ||
|
|
f1f9d9790b | ||
|
|
96190910a7 | ||
|
|
6484763d37 | ||
|
|
6f1e7624ec | ||
|
|
eef5bd6062 | ||
|
|
de60fbb25a | ||
|
|
fd9a638879 | ||
|
|
ddcb718a3a | ||
|
|
a17a7453e7 | ||
|
|
479be0b8ee | ||
|
|
6f40c357b3 | ||
|
|
81db6c544d | ||
|
|
be4e3aa963 | ||
|
|
6da0c07a3d | ||
|
|
b4ad10ca35 | ||
|
|
2388b878dc | ||
|
|
8cdaa7877a | ||
|
|
d314287883 | ||
|
|
b70dfc8e82 | ||
|
|
a5a7184f9a | ||
|
|
4e019d0a43 | ||
|
|
8453b54360 | ||
|
|
9f9dfdb26d | ||
|
|
9fd4984247 | ||
|
|
9ebd64f47d | ||
|
|
4316a37ed6 | ||
|
|
2d745460e8 | ||
|
|
b5258b6d9f | ||
|
|
41b076c0db | ||
|
|
9d65e5e398 | ||
|
|
7250bf7d65 | ||
|
|
4d7b247378 | ||
|
|
0aaa58cd54 | ||
|
|
014b85f12c | ||
|
|
929f97cb72 | ||
|
|
de9cb935ee | ||
|
|
9aafc176e4 | ||
|
|
0488ae8305 | ||
|
|
60fd317d98 | ||
|
|
e54435d85d | ||
|
|
3a23b91c90 | ||
|
|
69591577bf | ||
|
|
e56afba6d3 | ||
|
|
98536ce4c7 | ||
|
|
05282178dd | ||
|
|
1af547288c | ||
|
|
b4af82acbc | ||
|
|
50fbe00d23 | ||
|
|
b44428677e | ||
|
|
d67faa1610 | ||
|
|
7b3f4c29d8 | ||
|
|
a49871c5b1 | ||
|
|
e4005792af | ||
|
|
8c0c09a21b | ||
|
|
a9b05f4256 | ||
|
|
cb6013a7a6 | ||
|
|
bb23b78a4f | ||
|
|
243277012f | ||
|
|
c9ed8a4b03 | ||
|
|
d413acaef3 | ||
|
|
d6aad6cd74 | ||
|
|
ca45e43003 | ||
|
|
ad39530705 | ||
|
|
a6c2378b56 | ||
|
|
c073d2201d | ||
|
|
6d70de2eb1 | ||
|
|
48982e8f4a | ||
|
|
397128f980 | ||
|
|
1d77fd3f94 | ||
|
|
60e78e8e74 | ||
|
|
4a9ccc6fde | ||
|
|
a707095fae | ||
|
|
d4f662f65e | ||
|
|
509b1365d9 | ||
|
|
d0b236e381 | ||
|
|
fe98265636 | ||
|
|
3f7d1b1e83 | ||
|
|
52cde329c1 | ||
|
|
68b2dd6147 | ||
|
|
5fa0d022dc | ||
|
|
d996a5c53f | ||
|
|
b6dfc6ed4d | ||
|
|
c7c2ba83f3 | ||
|
|
2bffabff05 | ||
|
|
697e81df10 | ||
|
|
f1b791845b | ||
|
|
6310845cdd | ||
|
|
230cca63f3 | ||
|
|
af9f4d4b1e | ||
|
|
0111ff9c99 | ||
|
|
12bec14c92 | ||
|
|
174ea1ddd4 | ||
|
|
038a7463e1 | ||
|
|
a702909216 | ||
|
|
8effd5614f | ||
|
|
a02365c223 | ||
|
|
2d589aefa2 | ||
|
|
bc2dc679a8 | ||
|
|
f2432d78ee | ||
|
|
f27eecce1f | ||
|
|
caf967f2e2 | ||
|
|
eecc9b53df | ||
|
|
8e12cae91f | ||
|
|
12c5ad54e7 | ||
|
|
c20fa7e093 | ||
|
|
4c83264c4a | ||
|
|
f592cf08d8 | ||
|
|
bf0cb25a88 | ||
|
|
2ff3d83d8f | ||
|
|
3f5c3e89c8 | ||
|
|
a1bb7962bc | ||
|
|
bf5cc5e1d1 | ||
|
|
d840d2fc18 | ||
|
|
1e458921e8 | ||
|
|
55feb41998 | ||
|
|
a7dbdd844b | ||
|
|
f3d6ad6c84 | ||
|
|
a0255e1743 | ||
|
|
1046d28092 | ||
|
|
affd2b47bd | ||
|
|
814870fd69 | ||
|
|
50c1a566a8 | ||
|
|
47783997c6 | ||
|
|
70d8505596 | ||
|
|
6e8cf9ca25 | ||
|
|
c9cda6c6f5 | ||
|
|
6c4d3ea37e | ||
|
|
8ad0e99b3c | ||
|
|
60277ed6e9 | ||
|
|
7b570420ca | ||
|
|
9205b59e29 | ||
|
|
685c5babe5 | ||
|
|
681dc8fbc1 | ||
|
|
7c623b1a8d | ||
|
|
277c089adc | ||
|
|
cfc3c231ff | ||
|
|
1000a22490 | ||
|
|
65c0ebac50 | ||
|
|
80843ec44b | ||
|
|
be23220e01 | ||
|
|
c82c10d17e | ||
|
|
5918b8be60 | ||
|
|
647a0a8ff1 | ||
|
|
bf3c6bc6be | ||
|
|
cc7832614b | ||
|
|
d5387a0d1a | ||
|
|
5d7ad973a2 | ||
|
|
0fcea692c7 | ||
|
|
db71f1271b | ||
|
|
3fde923190 | ||
|
|
4f97760e8a | ||
|
|
5614a6203f | ||
|
|
5727b7cd73 | ||
|
|
1c0644aa7a | ||
|
|
23e6ebe8ee | ||
|
|
5602c0b6c3 | ||
|
|
8e59c10b90 | ||
|
|
90587b0508 | ||
|
|
153a8428f7 | ||
|
|
3d00d96716 | ||
|
|
fb9824301f | ||
|
|
33f4e82399 | ||
|
|
0d99269109 | ||
|
|
8098532215 | ||
|
|
24e9f46e2f | ||
|
|
7c3b40f9d5 | ||
|
|
29860583f4 | ||
|
|
9c00a5561a | ||
|
|
82b8853f39 | ||
|
|
c4ab91a565 | ||
|
|
87e5096f5d | ||
|
|
1a07021bbf | ||
|
|
6ab4f15d0c | ||
|
|
f137f8e048 | ||
|
|
04501143ec | ||
|
|
07276f5c17 | ||
|
|
a9bd01b34e | ||
|
|
93db82305f | ||
|
|
ce09f27373 | ||
|
|
ffd9d56896 | ||
|
|
833e714a1f | ||
|
|
e402f322f6 | ||
|
|
2a0636b32b | ||
|
|
677dc59399 | ||
|
|
db408b21d2 | ||
|
|
fe08f4cf09 | ||
|
|
ccb7c1485b | ||
|
|
4a76f42e35 | ||
|
|
6f31d50a27 | ||
|
|
018c26b885 | ||
|
|
f9f70f208f | ||
|
|
b940ddca25 | ||
|
|
cd82527ef3 | ||
|
|
f600f016ae | ||
|
|
27101ffd31 | ||
|
|
9376b0b010 | ||
|
|
e2c1f6aa2d | ||
|
|
e2287cc73c | ||
|
|
c350ce1b60 | ||
|
|
09dbe0fed7 | ||
|
|
12b3d16662 | ||
|
|
1676329eb2 | ||
|
|
86434ea320 | ||
|
|
de6b4f7fb2 | ||
|
|
6e488cba3e | ||
|
|
03a6cc85fe | ||
|
|
b8b32681bf | ||
|
|
7f67c6c6d9 | ||
|
|
0487b2c987 | ||
|
|
d5f10db250 | ||
|
|
74df0f19cf | ||
|
|
47c19b4e3d | ||
|
|
b197e36ba7 | ||
|
|
4b049dcf71 | ||
|
|
04ed7f412f | ||
|
|
ac64e59c43 | ||
|
|
d5524a8d67 | ||
|
|
bc350a2661 | ||
|
|
575fb623ba | ||
|
|
b7783659c9 | ||
|
|
bddd4743a7 | ||
|
|
ce8ac15faa | ||
|
|
46e7c83fae | ||
|
|
3dcb55f603 | ||
|
|
fb3e0832d6 | ||
|
|
982d6c9045 | ||
|
|
a061a89ee7 | ||
|
|
00bb71f714 | ||
|
|
e23cfac5a7 | ||
|
|
ed651959c1 | ||
|
|
01a1632a5a | ||
|
|
c2a6697f72 | ||
|
|
211775dba5 | ||
|
|
2ba85ba6a7 | ||
|
|
3b3f1d692f | ||
|
|
d90bb1e5ea | ||
|
|
0c14a1ab4c | ||
|
|
ea27acc683 | ||
|
|
b2122e7707 | ||
|
|
9cdc8a50f6 | ||
|
|
03620be7bb | ||
|
|
55296cd9cc | ||
|
|
5fefea1434 | ||
|
|
66dbe68a15 | ||
|
|
066e2ddc69 | ||
|
|
59f08ad4da | ||
|
|
551936e7c4 | ||
|
|
4660240395 | ||
|
|
5d0a50242e | ||
|
|
141a778c9a | ||
|
|
d83d6cf2d8 | ||
|
|
71dc32098e | ||
|
|
6e47b4a941 | ||
|
|
8479e90aeb | ||
|
|
24276b779d | ||
|
|
47e254ed9b | ||
|
|
9c021ef3b1 | ||
|
|
c284e95dc8 | ||
|
|
39663a872c | ||
|
|
14cefca735 | ||
|
|
55c56207c2 | ||
|
|
79d9f31db7 | ||
|
|
845b906851 | ||
|
|
5d2b19cc43 | ||
|
|
a5bc30f776 | ||
|
|
cce77a475a | ||
|
|
13a26321f5 | ||
|
|
e7a2eb7373 | ||
|
|
1cc168404a | ||
|
|
cef3d21ab3 | ||
|
|
d0ac0e4523 | ||
|
|
abaa7754a6 | ||
|
|
02e875cbdc | ||
|
|
a218257952 | ||
|
|
7dfcabde5e | ||
|
|
6573602dfc | ||
|
|
3c374f48b3 | ||
|
|
2412ef0260 | ||
|
|
d4dcfcdbc6 | ||
|
|
60aec7c801 | ||
|
|
1862d726ad | ||
|
|
3d27d5f755 | ||
|
|
0b3f76590f | ||
|
|
294832834c | ||
|
|
e3338e0236 | ||
|
|
5c7ae55775 | ||
|
|
bc8827547e | ||
|
|
7990675c5c | ||
|
|
0182db8030 | ||
|
|
295feccb49 | ||
|
|
b5005f41fe | ||
|
|
37f06a8ba4 | ||
|
|
71d421898d | ||
|
|
bf5a69cee4 | ||
|
|
11e6b8a372 | ||
|
|
d763dba204 | ||
|
|
9e5cd84214 | ||
|
|
14cf0c9ae1 | ||
|
|
7d410e9ec8 | ||
|
|
07c3d423aa | ||
|
|
5d8003549f | ||
|
|
b286daad16 | ||
|
|
caa9144a12 | ||
|
|
3606902753 | ||
|
|
1abb75a58e | ||
|
|
d35c15c384 | ||
|
|
1888209027 | ||
|
|
f3830bfdd5 | ||
|
|
0e1b91f1ec | ||
|
|
8353aa24f3 | ||
|
|
99f1a8dfc3 | ||
|
|
3d8237008f | ||
|
|
22199da7d4 | ||
|
|
d8e11f69cc | ||
|
|
c9a5c0801e | ||
|
|
bb0abe27cd | ||
|
|
7d18e1d928 | ||
|
|
da72513f6a | ||
|
|
6f8c161e03 | ||
|
|
ac1f02971f | ||
|
|
d19538abd2 | ||
|
|
b1ab7e7783 | ||
|
|
35c080fcc2 | ||
|
|
43e89d9dc2 | ||
|
|
547b69dd31 | ||
|
|
1ff8514b22 | ||
|
|
5fffe51c4e | ||
|
|
af9aa3e37b | ||
|
|
d644ee7ccd | ||
|
|
08e3278ca0 | ||
|
|
85feaa00fc | ||
|
|
76ba5c188e | ||
|
|
9941e0e936 | ||
|
|
89206f94f0 | ||
|
|
43128d7ea3 | ||
|
|
6f1026434d | ||
|
|
db35e3e425 | ||
|
|
1d8de792a5 | ||
|
|
0db47dfee1 | ||
|
|
fc086fdbc3 | ||
|
|
8a86d19b79 | ||
|
|
9198302f7e | ||
|
|
d076451ea8 | ||
|
|
c2e9ef59d6 | ||
|
|
100f72de9d | ||
|
|
5a32109d5d | ||
|
|
f5e7934906 | ||
|
|
9800c24939 | ||
|
|
dcd81e0a3f | ||
|
|
6574b55440 | ||
|
|
8474d1c8c4 | ||
|
|
38ae9ab9f5 | ||
|
|
659b35f03e | ||
|
|
414f126f1e | ||
|
|
b336d769c8 | ||
|
|
c173953c6a | ||
|
|
afac45e65f | ||
|
|
f0aa5b8744 | ||
|
|
a101e7b089 | ||
|
|
85903d5385 | ||
|
|
fe80ef9b85 | ||
|
|
961f8c1627 | ||
|
|
6c7fc9b317 | ||
|
|
2afa14d68e | ||
|
|
f6cdac4826 | ||
|
|
bb39999b84 | ||
|
|
70a036e5a7 | ||
|
|
0432751050 | ||
|
|
36b3b1d086 | ||
|
|
6a783e540b |
1
.github/helper/install_dependencies.sh
vendored
1
.github/helper/install_dependencies.sh
vendored
@@ -4,6 +4,7 @@ set -e
|
|||||||
echo "Setting Up System Dependencies..."
|
echo "Setting Up System Dependencies..."
|
||||||
|
|
||||||
sudo apt update
|
sudo apt update
|
||||||
|
sudo apt remove mysql-server mysql-client
|
||||||
sudo apt install libcups2-dev redis-server mariadb-client-10.6
|
sudo apt install libcups2-dev redis-server mariadb-client-10.6
|
||||||
|
|
||||||
install_wkhtmltopdf() {
|
install_wkhtmltopdf() {
|
||||||
|
|||||||
32
.github/try-on-f-cloud.svg
vendored
Normal file
32
.github/try-on-f-cloud.svg
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4 2 193 52">
|
||||||
|
<g filter="url(#filter0_dd)">
|
||||||
|
<rect x="4" y="2" width="193" height="52" rx="6" fill="#2490EF"/>
|
||||||
|
<path d="M28 22.2891H32.8786V35.5H36.2088V22.2891H41.0874V19.5H28V22.2891Z" fill="white"/>
|
||||||
|
<path d="M41.6982 35.5H45.0129V28.7109C45.0129 27.2344 46.0866 26.2188 47.5494 26.2188C48.0085 26.2188 48.6388 26.2969 48.95 26.3984V23.4453C48.6543 23.375 48.2419 23.3281 47.9074 23.3281C46.5691 23.3281 45.472 24.1094 45.0362 25.5938H44.9117V23.5H41.6982V35.5Z" fill="white"/>
|
||||||
|
<path d="M52.8331 40C55.2996 40 56.6068 38.7344 57.2837 36.7969L61.9289 23.5156L58.4197 23.5L55.9221 32.3125H55.7976L53.3233 23.5H49.8374L54.1247 35.8437L53.9302 36.3516C53.4944 37.4766 52.6619 37.5312 51.4947 37.1719L50.7478 39.6562C51.2224 39.8594 51.9927 40 52.8331 40Z" fill="white"/>
|
||||||
|
<path d="M73.6142 35.7344C77.2401 35.7344 79.4966 33.2422 79.4966 29.5469C79.4966 25.8281 77.2401 23.3438 73.6142 23.3438C69.9883 23.3438 67.7319 25.8281 67.7319 29.5469C67.7319 33.2422 69.9883 35.7344 73.6142 35.7344ZM73.6298 33.1562C71.9569 33.1562 71.101 31.6171 71.101 29.5233C71.101 27.4296 71.9569 25.8827 73.6298 25.8827C75.2715 25.8827 76.1274 27.4296 76.1274 29.5233C76.1274 31.6171 75.2715 33.1562 73.6298 33.1562Z" fill="white"/>
|
||||||
|
<path d="M84.7253 28.5625C84.7331 27.0156 85.6512 26.1094 86.9895 26.1094C88.3201 26.1094 89.1215 26.9844 89.1137 28.4531V35.5H92.4284V27.8594C92.4284 25.0625 90.7945 23.3438 88.3046 23.3438C86.5306 23.3438 85.2466 24.2187 84.7097 25.6172H84.5697V23.5H81.4106V35.5H84.7253V28.5625Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.429 19.5H113.429V22.3141H102.429V19.5ZM102.429 35.5V26.6794H112.699V29.4982H105.94V35.5H102.429Z" fill="white"/>
|
||||||
|
<path d="M131.584 24.9625C131.09 21.5057 128.345 19.5 124.785 19.5C120.589 19.5 117.429 22.463 117.429 27.4924C117.429 32.5142 120.55 35.4848 124.785 35.4848C128.604 35.4848 131.137 33.0916 131.584 30.1211L128.651 30.1059C128.282 31.9293 126.745 32.9549 124.824 32.9549C122.22 32.9549 120.354 31.0632 120.354 27.4924C120.354 23.9824 122.204 22.0299 124.832 22.0299C126.784 22.0299 128.314 23.1011 128.651 24.9625H131.584Z" fill="white"/>
|
||||||
|
<path d="M136.409 19.7124H133.571V35.2718H136.409V19.7124Z" fill="white"/>
|
||||||
|
<path d="M144.031 35.5001C147.56 35.5001 149.803 33.0917 149.803 29.483C149.803 25.8667 147.56 23.4507 144.031 23.4507C140.502 23.4507 138.259 25.8667 138.259 29.483C138.259 33.0917 140.502 35.5001 144.031 35.5001ZM144.047 33.2969C142.094 33.2969 141.137 31.6103 141.137 29.4754C141.137 27.3406 142.094 25.6312 144.047 25.6312C145.968 25.6312 146.925 27.3406 146.925 29.4754C146.925 31.6103 145.968 33.2969 144.047 33.2969Z" fill="white"/>
|
||||||
|
<path d="M159.338 30.3641C159.338 32.1419 158.028 33.0232 156.773 33.0232C155.409 33.0232 154.499 32.0887 154.499 30.6072V23.6025H151.66V31.0327C151.66 33.8361 153.307 35.4239 155.675 35.4239C157.479 35.4239 158.749 34.5046 159.298 33.1979H159.424V35.272H162.176V23.6025H159.338V30.3641Z" fill="white"/>
|
||||||
|
<path d="M169.014 35.4769C171.084 35.4769 172.017 34.2841 172.464 33.4332H172.637V35.2718H175.429V19.7124H172.582V25.532H172.464C172.033 24.6887 171.147 23.4503 169.022 23.4503C166.238 23.4503 164.05 25.5624 164.05 29.4522C164.05 33.2965 166.175 35.4769 169.014 35.4769ZM169.806 33.2205C167.931 33.2205 166.943 31.6251 166.943 29.437C166.943 27.2642 167.916 25.7067 169.806 25.7067C171.633 25.7067 172.637 27.173 172.637 29.437C172.637 31.701 171.617 33.2205 169.806 33.2205Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_dd" x="0" y="0" width="201" height="60" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset/>
|
||||||
|
<feGaussianBlur stdDeviation="0.25"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="2"/>
|
||||||
|
<feGaussianBlur stdDeviation="2"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.13 0"/>
|
||||||
|
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
- name: setup node
|
- name: setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
node-version: '18'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: setup cache for bench
|
- name: setup cache for bench
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
@@ -78,4 +78,3 @@ jobs:
|
|||||||
- name: run tests
|
- name: run tests
|
||||||
working-directory: /home/runner/frappe-bench
|
working-directory: /home/runner/frappe-bench
|
||||||
run: bench --site frappe.local run-tests --app lms
|
run: bench --site frappe.local run-tests --app lms
|
||||||
|
|
||||||
|
|||||||
7
.github/workflows/ui-tests.yml
vendored
7
.github/workflows/ui-tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Add to Hosts
|
- name: Add to Hosts
|
||||||
@@ -100,6 +100,11 @@ jobs:
|
|||||||
bench --site lms.test execute frappe.utils.install.complete_setup_wizard
|
bench --site lms.test execute frappe.utils.install.complete_setup_wizard
|
||||||
bench --site lms.test execute frappe.tests.ui_test_helpers.create_test_user
|
bench --site lms.test execute frappe.tests.ui_test_helpers.create_test_user
|
||||||
|
|
||||||
|
- name: cypress pre-requisites
|
||||||
|
run: |
|
||||||
|
cd ~/frappe-bench/apps/lms
|
||||||
|
yarn add cypress@^10 --no-lockfile
|
||||||
|
|
||||||
- name: UI Tests
|
- name: UI Tests
|
||||||
run: cd ~/frappe-bench/ && bench --site lms.test run-ui-tests lms --headless
|
run: cd ~/frappe-bench/ && bench --site lms.test run-ui-tests lms --headless
|
||||||
env:
|
env:
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -1,17 +1,36 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.frappelms.com/">
|
<a href="https://www.frappelms.com/">
|
||||||
<img src="https://www.frappelms.com/files/flms.svg" alt="Frappe LMS" width="100" height="100">
|
<img src="https://frappe.io/files/lms.png" alt="Frappe LMS" width="50px" height="50px">
|
||||||
</a>
|
</a>
|
||||||
<p align="center">Easy to use, open source, learning management system.</p>
|
<p align="center">Easy to use, open source, learning management system.</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
<a href="https://www.producthunt.com/posts/frappe-lms?utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-frappe-lms" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=396079&theme=dark&period=weekly&topic_id=204" alt="Frappe LMS - Easy to use, 100% open source learning management system | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center" style="max-height: 40px;">
|
||||||
|
<a href="https://frappecloud.com/lms/signup">
|
||||||
|
<img src=".github/try-on-f-cloud.svg" height="40">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://dashboard.cypress.io/projects/vandxn/runs">
|
||||||
|
<img alt="cypress" src="https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/vandxn/main&style=flat&logo=cypress">
|
||||||
|
</a>
|
||||||
<a href="https://github.com/frappe/lms/blob/main/LICENSE">
|
<a href="https://github.com/frappe/lms/blob/main/LICENSE">
|
||||||
<img alt="license" src="https://img.shields.io/badge/license-AGPLv3-blue">
|
<img alt="license" src="https://img.shields.io/badge/license-AGPLv3-blue">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<img width="1402" alt="Lesson" src="https://frappelms.com/files/fs-banner71f330.png">
|
<img width="1402" alt="Lesson" src="https://frappelms.com/files/banner.png">
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Show more screenshots</summary>
|
<summary>Show more screenshots</summary>
|
||||||
@@ -29,7 +48,7 @@ You can create courses and lessons through simple forms. Lessons can be in the f
|
|||||||
- Add detailed descriptions and preview videos to the course. 🎬
|
- Add detailed descriptions and preview videos to the course. 🎬
|
||||||
- Add videos, quizzes, and assignments to your lessons and make them interesting and interactive 📝
|
- Add videos, quizzes, and assignments to your lessons and make them interesting and interactive 📝
|
||||||
- Discussions section below each lesson where instructors and students can interact with each other. 💬
|
- Discussions section below each lesson where instructors and students can interact with each other. 💬
|
||||||
- Create classes to group your students based on courses and track their progress 🏛
|
- Create batches to group your students based on courses and track their progress 🏛
|
||||||
- Statistics dashboard that provides all important numbers at a glimpse. 📈
|
- Statistics dashboard that provides all important numbers at a glimpse. 📈
|
||||||
- Job Board where users can post and look for jobs. 💼
|
- Job Board where users can post and look for jobs. 💼
|
||||||
- People directory with each person's profile page 👨👩👧👦
|
- People directory with each person's profile page 👨👩👧👦
|
||||||
|
|||||||
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
The Frappe team and community take security issues seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).
|
||||||
|
|
||||||
|
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly and will keep you updated throughout the process.
|
||||||
@@ -13,6 +13,6 @@ module.exports = defineConfig({
|
|||||||
openMode: 0,
|
openMode: 0,
|
||||||
},
|
},
|
||||||
e2e: {
|
e2e: {
|
||||||
baseUrl: "http://test_site_ui:8000",
|
baseUrl: "http://pyp:8000",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ describe("Course Creation", () => {
|
|||||||
|
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
cy.get(".edit-header .btn-add-chapter").click();
|
cy.get(".edit-header .btn-add-chapter").click();
|
||||||
|
cy.wait(500);
|
||||||
cy.get("#chapter-title").type("Test Chapter");
|
cy.get("#chapter-title").type("Test Chapter");
|
||||||
cy.get("#chapter-description").type("Test Chapter Description");
|
cy.get("#chapter-description").type("Test Chapter Description");
|
||||||
cy.button("Save").click();
|
cy.button("Save").click();
|
||||||
@@ -32,19 +33,17 @@ describe("Course Creation", () => {
|
|||||||
cy.get("#lesson-title").type("Test Lesson");
|
cy.get("#lesson-title").type("Test Lesson");
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
cy.get(".ce-block").click().type("{enter}");
|
cy.get(".collapse-section.collapsed:first").click();
|
||||||
cy.get(".ce-toolbar__plus").click();
|
cy.get("#lesson-content .ce-block")
|
||||||
cy.get('[data-item-name="youtube"]').click();
|
.click()
|
||||||
|
.type(
|
||||||
|
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now. {enter}"
|
||||||
|
);
|
||||||
|
cy.get("#lesson-content .ce-toolbar__plus").click();
|
||||||
|
cy.get('#lesson-content [data-item-name="youtube"]').click();
|
||||||
cy.get('input[data-fieldname="youtube"]').type("GoDtyItReto");
|
cy.get('input[data-fieldname="youtube"]').type("GoDtyItReto");
|
||||||
cy.button("Insert").click();
|
cy.button("Insert").click();
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
|
|
||||||
cy.get(".ce-block:last").click().type("{enter}");
|
|
||||||
cy.get(".ce-block:last")
|
|
||||||
.click()
|
|
||||||
.type(
|
|
||||||
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
|
|
||||||
);
|
|
||||||
cy.button("Save").click();
|
cy.button("Save").click();
|
||||||
|
|
||||||
// View Course
|
// View Course
|
||||||
@@ -85,26 +84,50 @@ describe("Course Creation", () => {
|
|||||||
// Add Discussion
|
// Add Discussion
|
||||||
cy.get(".reply").click();
|
cy.get(".reply").click();
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.get(".topic-title").type("Question Title");
|
cy.get(".discussion-modal").should("be.visible");
|
||||||
cy.get(".comment-field").type(
|
|
||||||
"Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test."
|
|
||||||
);
|
|
||||||
cy.get(".submit-discussion").click();
|
|
||||||
|
|
||||||
// View Discussion
|
// Enter title
|
||||||
cy.wait(1000);
|
cy.get(".modal .topic-title")
|
||||||
cy.get(".discussion-topic-title:first").contains("Question Title");
|
.type("Discussion from tests")
|
||||||
cy.get(".sidebar-parent:first").click();
|
.should("have.value", "Discussion from tests");
|
||||||
cy.get(".reply-text").contains(
|
|
||||||
"Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test."
|
// Enter comment
|
||||||
|
cy.get(".modal .discussions-comment").type(
|
||||||
|
"This is a discussion from the cypress ui tests."
|
||||||
);
|
);
|
||||||
cy.get(".comment-field:visible").type(
|
|
||||||
"This is a reply to the previous comment. Its not that long."
|
// Submit
|
||||||
|
cy.get(".modal .submit-discussion").click();
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
|
// Check if discussion is added to page and content is visible
|
||||||
|
cy.get(".sidebar-parent:first .discussion-topic-title").should(
|
||||||
|
"have.text",
|
||||||
|
"Discussion from tests"
|
||||||
);
|
);
|
||||||
cy.get(".submit-discussion:visible").click();
|
cy.get(".sidebar-parent:first .discussion-topic-title").click();
|
||||||
cy.wait(1000);
|
cy.get(".discussion-on-page:visible").should("have.class", "show");
|
||||||
cy.get(".reply-text:last p").contains(
|
cy.get(
|
||||||
"This is a reply to the previous comment. Its not that long."
|
".discussion-on-page:visible .reply-card .reply-text .ql-editor p"
|
||||||
|
).should(
|
||||||
|
"have.text",
|
||||||
|
"This is a discussion from the cypress ui tests."
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.get(".discussion-form:visible .discussions-comment").type(
|
||||||
|
"This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page."
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.get(".discussion-form:visible .submit-discussion").click();
|
||||||
|
cy.wait(3000);
|
||||||
|
cy.get(".discussion-on-page:visible").should("have.class", "show");
|
||||||
|
cy.get(".discussion-on-page:visible")
|
||||||
|
.children(".reply-card")
|
||||||
|
.eq(1)
|
||||||
|
.find(".reply-text")
|
||||||
|
.should(
|
||||||
|
"have.text",
|
||||||
|
"This is a discussion from the cypress ui tests. This comment was entered through the commentbox on the page.\n"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
$ git clone https://github.com/frappe/lms.git
|
$ git clone https://github.com/frappe/lms.git
|
||||||
|
|
||||||
$ cd lms
|
$ cd lms
|
||||||
|
|
||||||
|
$ cd docker
|
||||||
```
|
```
|
||||||
|
|
||||||
**Step 2:** Run docker-compose
|
**Step 2:** Run docker-compose
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ else
|
|||||||
echo "Creating new bench..."
|
echo "Creating new bench..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export PATH="${NVM_DIR}/versions/node/v${NODE_VERSION_DEVELOP}/bin/:${PATH}"
|
||||||
|
|
||||||
bench init --skip-redis-config-generation frappe-bench
|
bench init --skip-redis-config-generation frappe-bench
|
||||||
|
|
||||||
cd frappe-bench
|
cd frappe-bench
|
||||||
|
|||||||
1
frappe-ui
Submodule
1
frappe-ui
Submodule
Submodule frappe-ui added at 2898a0bdd1
@@ -1 +1 @@
|
|||||||
__version__ = "0.0.1"
|
__version__ = "1.0.0"
|
||||||
|
|||||||
@@ -138,12 +138,12 @@
|
|||||||
"label": "User Category",
|
"label": "User Category",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"mandatory_depends_on": null,
|
"mandatory_depends_on": null,
|
||||||
"modified": "2022-04-19 13:02:18.219508",
|
"modified": "2022-04-19 13:02:18.219510",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "User-user_category",
|
"name": "User-user_category",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"non_negative": 0,
|
"non_negative": 0,
|
||||||
"options": "Business Owner\nManager (Sales/Marketing/Customer)\nEmployee\nStudent\nFreelancer/Just looking\nOthers",
|
"options": "\nBusiness Owner\nManager (Sales/Marketing/Customer)\nEmployee\nStudent\nFreelancer/Just looking\nOthers",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
|||||||
53
lms/hooks.py
53
lms/hooks.py
@@ -97,17 +97,16 @@ override_doctype_class = {
|
|||||||
# Hook on document methods and events
|
# Hook on document methods and events
|
||||||
|
|
||||||
doc_events = {
|
doc_events = {
|
||||||
"Discussion Reply": {"after_insert": "lms.lms.utils.create_notification_log"},
|
"Discussion Reply": {"after_insert": "lms.lms.utils.handle_notifications"},
|
||||||
"Course Lesson": {"on_update": "lms.lms.doctype.lms_quiz.lms_quiz.update_lesson_info"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Scheduled Tasks
|
# Scheduled Tasks
|
||||||
# ---------------
|
# ---------------
|
||||||
# scheduler_events = {
|
scheduler_events = {
|
||||||
# "daily": [
|
"hourly": [
|
||||||
# "erpnext.stock.reorder_item.reorder_item"
|
"lms.lms.doctype.lms_certificate_request.lms_certificate_request.schedule_evals"
|
||||||
# ]
|
]
|
||||||
# }
|
}
|
||||||
|
|
||||||
fixtures = ["Custom Field", "Function", "Industry"]
|
fixtures = ["Custom Field", "Function", "Industry"]
|
||||||
|
|
||||||
@@ -119,9 +118,9 @@ fixtures = ["Custom Field", "Function", "Industry"]
|
|||||||
# Overriding Methods
|
# Overriding Methods
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
#
|
#
|
||||||
# override_whitelisted_methods = {
|
override_whitelisted_methods = {
|
||||||
# "frappe.desk.doctype.event.event.get_events": "lms.event.get_events"
|
# "frappe.desk.search.get_names_for_mentions": "lms.lms.utils.get_names_for_mentions",
|
||||||
# }
|
}
|
||||||
#
|
#
|
||||||
# each overriding function accepts a `data` argument;
|
# each overriding function accepts a `data` argument;
|
||||||
# generated from the base implementation of the doctype dashboard,
|
# generated from the base implementation of the doctype dashboard,
|
||||||
@@ -152,7 +151,7 @@ website_route_rules = [
|
|||||||
},
|
},
|
||||||
{"from_route": "/quizzes", "to_route": "batch/quiz_list"},
|
{"from_route": "/quizzes", "to_route": "batch/quiz_list"},
|
||||||
{"from_route": "/quizzes/<quizname>", "to_route": "batch/quiz"},
|
{"from_route": "/quizzes/<quizname>", "to_route": "batch/quiz"},
|
||||||
{"from_route": "/classes/<classname>", "to_route": "classes/class"},
|
{"from_route": "/batches/<batchname>", "to_route": "batches/batch"},
|
||||||
{"from_route": "/courses/<course>/progress", "to_route": "batch/progress"},
|
{"from_route": "/courses/<course>/progress", "to_route": "batch/progress"},
|
||||||
{"from_route": "/courses/<course>/join", "to_route": "batch/join"},
|
{"from_route": "/courses/<course>/join", "to_route": "batch/join"},
|
||||||
{"from_route": "/courses/<course>/manage", "to_route": "cohorts"},
|
{"from_route": "/courses/<course>/manage", "to_route": "cohorts"},
|
||||||
@@ -174,12 +173,33 @@ website_route_rules = [
|
|||||||
"to_route": "cohorts/join",
|
"to_route": "cohorts/join",
|
||||||
},
|
},
|
||||||
{"from_route": "/users", "to_route": "profiles/profile"},
|
{"from_route": "/users", "to_route": "profiles/profile"},
|
||||||
{"from_route": "/jobs/<job>", "to_route": "jobs/job"},
|
{"from_route": "/job-openings", "to_route": "jobs_openings/index"},
|
||||||
|
{"from_route": "/job-openings/<job>", "to_route": "jobs_openings/job"},
|
||||||
{
|
{
|
||||||
"from_route": "/classes/<classname>/students/<username>",
|
"from_route": "/batches/<batchname>/students/<username>",
|
||||||
"to_route": "/classes/progress",
|
"to_route": "/batches/progress",
|
||||||
},
|
},
|
||||||
{"from_route": "/assignments/<assignment>", "to_route": "assignments/assignment"},
|
{"from_route": "/assignments/<assignment>", "to_route": "assignments/assignment"},
|
||||||
|
{
|
||||||
|
"from_route": "/assignment-submission/<assignment>/<submission>",
|
||||||
|
"to_route": "assignment_submission/assignment_submission",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from_route": "/quiz-submission/<quiz>/<submission>",
|
||||||
|
"to_route": "quiz_submission/quiz_submission",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from_route": "/billing/<module>/<modulename>",
|
||||||
|
"to_route": "billing/billing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from_route": "/batches/details/<batchname>",
|
||||||
|
"to_route": "batches/batch_details",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from_route": "/certified-participants",
|
||||||
|
"to_route": "certified_participants/certified_participants",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
website_redirects = [
|
website_redirects = [
|
||||||
@@ -236,6 +256,8 @@ jinja = {
|
|||||||
"lms.lms.utils.show_start_learing_cta",
|
"lms.lms.utils.show_start_learing_cta",
|
||||||
"lms.lms.utils.can_create_courses",
|
"lms.lms.utils.can_create_courses",
|
||||||
"lms.lms.utils.get_telemetry_boot_info",
|
"lms.lms.utils.get_telemetry_boot_info",
|
||||||
|
"lms.lms.utils.is_onboarding_complete",
|
||||||
|
"lms.www.utils.is_student",
|
||||||
],
|
],
|
||||||
"filters": [],
|
"filters": [],
|
||||||
}
|
}
|
||||||
@@ -282,6 +304,9 @@ lms_markdown_macro_renderers = {
|
|||||||
"YouTubeVideo": "lms.plugins.youtube_video_renderer",
|
"YouTubeVideo": "lms.plugins.youtube_video_renderer",
|
||||||
"Video": "lms.plugins.video_renderer",
|
"Video": "lms.plugins.video_renderer",
|
||||||
"Assignment": "lms.plugins.assignment_renderer",
|
"Assignment": "lms.plugins.assignment_renderer",
|
||||||
|
"Embed": "lms.plugins.embed_renderer",
|
||||||
|
"Audio": "lms.plugins.audio_renderer",
|
||||||
|
"PDF": "lms.plugins.pdf_renderer",
|
||||||
}
|
}
|
||||||
|
|
||||||
# page_renderer to manage profile pages
|
# page_renderer to manage profile pages
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
|
|||||||
|
|
||||||
def after_install():
|
def after_install():
|
||||||
add_pages_to_nav()
|
add_pages_to_nav()
|
||||||
|
create_batch_source()
|
||||||
|
|
||||||
|
|
||||||
def after_sync():
|
def after_sync():
|
||||||
create_lms_roles()
|
create_lms_roles()
|
||||||
set_default_home()
|
set_default_certificate_print_format()
|
||||||
add_all_roles_to("Administrator")
|
add_all_roles_to("Administrator")
|
||||||
|
|
||||||
|
|
||||||
@@ -16,9 +17,9 @@ def add_pages_to_nav():
|
|||||||
pages = [
|
pages = [
|
||||||
{"label": "Explore", "idx": 1},
|
{"label": "Explore", "idx": 1},
|
||||||
{"label": "Courses", "url": "/courses", "parent": "Explore", "idx": 2},
|
{"label": "Courses", "url": "/courses", "parent": "Explore", "idx": 2},
|
||||||
{"label": "Classes", "url": "/classes", "parent": "Explore", "idx": 3},
|
{"label": "Batches", "url": "/batches", "parent": "Explore", "idx": 3},
|
||||||
{"label": "Statistics", "url": "/statistics", "parent": "Explore", "idx": 4},
|
{"label": "Statistics", "url": "/statistics", "parent": "Explore", "idx": 4},
|
||||||
{"label": "Jobs", "url": "/jobs", "parent": "Explore", "idx": 5},
|
{"label": "Jobs", "url": "/job-openings", "parent": "Explore", "idx": 5},
|
||||||
{"label": "People", "url": "/community", "parent": "Explore", "idx": 6},
|
{"label": "People", "url": "/community", "parent": "Explore", "idx": 6},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -52,6 +53,8 @@ def before_uninstall():
|
|||||||
def create_lms_roles():
|
def create_lms_roles():
|
||||||
create_course_creator_role()
|
create_course_creator_role()
|
||||||
create_moderator_role()
|
create_moderator_role()
|
||||||
|
create_evaluator_role()
|
||||||
|
create_lms_student_role()
|
||||||
|
|
||||||
|
|
||||||
def delete_lms_roles():
|
def delete_lms_roles():
|
||||||
@@ -61,10 +64,6 @@ def delete_lms_roles():
|
|||||||
frappe.db.delete("Role", role)
|
frappe.db.delete("Role", role)
|
||||||
|
|
||||||
|
|
||||||
def set_default_home():
|
|
||||||
frappe.db.set_value("Portal Settings", None, "default_portal_home", "/courses")
|
|
||||||
|
|
||||||
|
|
||||||
def create_course_creator_role():
|
def create_course_creator_role():
|
||||||
if not frappe.db.exists("Role", "Course Creator"):
|
if not frappe.db.exists("Role", "Course Creator"):
|
||||||
role = frappe.get_doc(
|
role = frappe.get_doc(
|
||||||
@@ -75,7 +74,7 @@ def create_course_creator_role():
|
|||||||
"desk_access": 0,
|
"desk_access": 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
role.save(ignore_permissions=True)
|
role.save()
|
||||||
|
|
||||||
|
|
||||||
def create_moderator_role():
|
def create_moderator_role():
|
||||||
@@ -88,7 +87,52 @@ def create_moderator_role():
|
|||||||
"desk_access": 0,
|
"desk_access": 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
role.save(ignore_permissions=True)
|
role.save()
|
||||||
|
|
||||||
|
|
||||||
|
def create_evaluator_role():
|
||||||
|
if not frappe.db.exists("Role", "Class Evaluator"):
|
||||||
|
role = frappe.new_doc("Role")
|
||||||
|
role.update(
|
||||||
|
{
|
||||||
|
"role_name": "Class Evaluator",
|
||||||
|
"home_page": "",
|
||||||
|
"desk_access": 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
role.save()
|
||||||
|
|
||||||
|
|
||||||
|
def create_lms_student_role():
|
||||||
|
if not frappe.db.exists("Role", "LMS Student"):
|
||||||
|
role = frappe.new_doc("Role")
|
||||||
|
role.update(
|
||||||
|
{
|
||||||
|
"role_name": "LMS Student",
|
||||||
|
"home_page": "",
|
||||||
|
"desk_access": 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
role.save()
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_certificate_print_format():
|
||||||
|
filters = {
|
||||||
|
"doc_type": "LMS Certificate",
|
||||||
|
"property": "default_print_format",
|
||||||
|
}
|
||||||
|
if not frappe.db.exists("Property Setter", filters):
|
||||||
|
filters.update(
|
||||||
|
{
|
||||||
|
"doctype_or_field": "DocType",
|
||||||
|
"property_type": "Data",
|
||||||
|
"value": "Certificate",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
doc = frappe.new_doc("Property Setter")
|
||||||
|
doc.update(filters)
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
|
||||||
def delete_custom_fields():
|
def delete_custom_fields():
|
||||||
@@ -134,3 +178,20 @@ def delete_custom_fields():
|
|||||||
|
|
||||||
for field in fields:
|
for field in fields:
|
||||||
frappe.db.delete("Custom Field", {"fieldname": field})
|
frappe.db.delete("Custom Field", {"fieldname": field})
|
||||||
|
|
||||||
|
|
||||||
|
def create_batch_source():
|
||||||
|
sources = [
|
||||||
|
"Newsletter",
|
||||||
|
"LinkedIn",
|
||||||
|
"Twitter",
|
||||||
|
"Website",
|
||||||
|
"Friend/Colleague/Connection",
|
||||||
|
"Google Search",
|
||||||
|
]
|
||||||
|
|
||||||
|
for source in sources:
|
||||||
|
if not frappe.db.exists("LMS Source", source):
|
||||||
|
doc = frappe.new_doc("LMS Source")
|
||||||
|
doc.source = source
|
||||||
|
doc.save()
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
frappe.ui.form.on("Job Opportunity", {
|
frappe.ui.form.on("Job Opportunity", {
|
||||||
refresh: (frm) => {
|
refresh: (frm) => {
|
||||||
if (frm.doc.name)
|
if (frm.doc.name)
|
||||||
frm.add_web_link(`/jobs/${frm.doc.name}`, "See on Website");
|
frm.add_web_link(`/job-openings/${frm.doc.name}`, "See on Website");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2022-09-15 17:22:21.662675",
|
"modified": "2023-09-29 17:03:30.825021",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Job",
|
"module": "Job",
|
||||||
"name": "Job Opportunity",
|
"name": "Job Opportunity",
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
frappe.ready(function () {
|
frappe.ready(function () {
|
||||||
frappe.web_form.after_save = () => {
|
frappe.web_form.after_save = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = `/jobs`;
|
window.location.href = `/job-openings`;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"list_columns": [],
|
"list_columns": [],
|
||||||
"login_required": 1,
|
"login_required": 1,
|
||||||
"max_attachment_size": 0,
|
"max_attachment_size": 0,
|
||||||
"modified": "2022-09-15 17:22:43.957184",
|
"modified": "2022-09-15 17:22:43.957185",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Job",
|
"module": "Job",
|
||||||
"name": "job-opportunity",
|
"name": "job-opportunity",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"show_list": 1,
|
"show_list": 1,
|
||||||
"show_sidebar": 0,
|
"show_sidebar": 0,
|
||||||
"success_message": "",
|
"success_message": "",
|
||||||
"success_url": "/jobs",
|
"success_url": "/job-openings",
|
||||||
"title": "Job Opportunity",
|
"title": "Job Opportunity",
|
||||||
"web_form_fields": [
|
"web_form_fields": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,15 +32,15 @@ def submit_solution(exercise, code):
|
|||||||
def save_current_lesson(course_name, lesson_name):
|
def save_current_lesson(course_name, lesson_name):
|
||||||
"""Saves the current lesson for a student/mentor."""
|
"""Saves the current lesson for a student/mentor."""
|
||||||
name = frappe.get_value(
|
name = frappe.get_value(
|
||||||
doctype="LMS Batch Membership",
|
doctype="LMS Enrollment",
|
||||||
filters={"course": course_name, "member": frappe.session.user},
|
filters={"course": course_name, "member": frappe.session.user},
|
||||||
fieldname="name",
|
fieldname="name",
|
||||||
)
|
)
|
||||||
if not name:
|
if not name:
|
||||||
return
|
return
|
||||||
doc = frappe.get_doc("LMS Batch Membership", name)
|
doc = frappe.get_doc("LMS Enrollment", name)
|
||||||
doc.current_lesson = lesson_name
|
doc.current_lesson = lesson_name
|
||||||
doc.save(ignore_permissions=True)
|
doc.save()
|
||||||
return {"current_lesson": doc.current_lesson}
|
return {"current_lesson": doc.current_lesson}
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ def join_cohort(course, cohort, subgroup, invite_code):
|
|||||||
return {"ok": True, "status": "record found"}
|
return {"ok": True, "status": "record found"}
|
||||||
else:
|
else:
|
||||||
doc = frappe.get_doc(data)
|
doc = frappe.get_doc(data)
|
||||||
doc.insert(ignore_permissions=True)
|
doc.insert()
|
||||||
return {"ok": True, "status": "record created"}
|
return {"ok": True, "status": "record created"}
|
||||||
|
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ def approve_cohort_join_request(join_request):
|
|||||||
return {"ok": False, "error": "Permission Deined"}
|
return {"ok": False, "error": "Permission Deined"}
|
||||||
|
|
||||||
r.status = "Accepted"
|
r.status = "Accepted"
|
||||||
r.save(ignore_permissions=True)
|
r.save()
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ def reject_cohort_join_request(join_request):
|
|||||||
return {"ok": False, "error": "Permission Deined"}
|
return {"ok": False, "error": "Permission Deined"}
|
||||||
|
|
||||||
r.status = "Rejected"
|
r.status = "Rejected"
|
||||||
r.save(ignore_permissions=True)
|
r.save()
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ def undo_reject_cohort_join_request(join_request):
|
|||||||
return {"ok": False, "error": "Permission Deined"}
|
return {"ok": False, "error": "Permission Deined"}
|
||||||
|
|
||||||
r.status = "Pending"
|
r.status = "Pending"
|
||||||
r.save(ignore_permissions=True)
|
r.save()
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"xIsSeries\": 1}, \"lineOptions\": {\"regionFill\": 1}}",
|
"custom_options": "{\"type\": \"line\", \"axisOptions\": {\"xIsSeries\": 1}, \"lineOptions\": {\"regionFill\": 1}}",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Dashboard Chart",
|
"doctype": "Dashboard Chart",
|
||||||
"document_type": "LMS Batch Membership",
|
"document_type": "LMS Enrollment",
|
||||||
"dynamic_filters_json": "[]",
|
"dynamic_filters_json": "[]",
|
||||||
"filters_json": "[]",
|
"filters_json": "[]",
|
||||||
"group_by_type": "Count",
|
"group_by_type": "Count",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"is_public": 1,
|
"is_public": 1,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"last_synced_on": "2022-10-20 10:46:56.859520",
|
"last_synced_on": "2022-10-20 10:46:56.859520",
|
||||||
"modified": "2022-10-20 11:30:26.863008",
|
"modified": "2022-10-20 11:30:26.863009",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Course Enrollments",
|
"name": "Course Enrollments",
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"course",
|
"course",
|
||||||
"title"
|
"title",
|
||||||
|
"evaluator"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -23,17 +24,24 @@
|
|||||||
"fieldname": "title",
|
"fieldname": "title",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Title",
|
"label": "Course Title",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "evaluator",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Evaluator",
|
||||||
|
"options": "Course Evaluator"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-11 15:51:45.560864",
|
"modified": "2023-08-28 10:03:02.960844",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Class Course",
|
"name": "Batch Course",
|
||||||
"naming_rule": "Autoincrement",
|
"naming_rule": "Autoincrement",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
@@ -5,5 +5,5 @@
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class ClassCourse(Document):
|
class BatchCourse(Document):
|
||||||
pass
|
pass
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2022, Frappe and contributors
|
// Copyright (c) 2022, Frappe and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Class Student", {
|
frappe.ui.form.on("Batch Student", {
|
||||||
// refresh: function(frm) {
|
// refresh: function(frm) {
|
||||||
// }
|
// }
|
||||||
});
|
});
|
||||||
@@ -7,9 +7,14 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"student_details_section",
|
||||||
"student",
|
"student",
|
||||||
"student_name",
|
"student_name",
|
||||||
"username"
|
"username",
|
||||||
|
"column_break_oduu",
|
||||||
|
"payment",
|
||||||
|
"source",
|
||||||
|
"confirmation_email_sent"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -34,15 +39,42 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Username",
|
"label": "Username",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "student_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Student Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_oduu",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment",
|
||||||
|
"options": "LMS Payment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "confirmation_email_sent",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Confirmation Email Sent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "source",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Source",
|
||||||
|
"options": "LMS Source"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-15 11:13:39.410578",
|
"modified": "2023-10-26 16:52:04.266693",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Class Student",
|
"name": "Batch Student",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
23
lms/lms/doctype/batch_student/batch_student.py
Normal file
23
lms/lms/doctype/batch_student/batch_student.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Copyright (c) 2022, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class BatchStudent(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def enroll_batch(batch_name):
|
||||||
|
if frappe.db.exists(
|
||||||
|
"Batch Student", {"student": frappe.session.user, "parent": batch_name}
|
||||||
|
):
|
||||||
|
frappe.throw("You are already enrolled in this batch")
|
||||||
|
enrollment = frappe.new_doc("Batch Student")
|
||||||
|
enrollment.student = frappe.session.user
|
||||||
|
enrollment.parent = batch_name
|
||||||
|
enrollment.parentfield = "students"
|
||||||
|
enrollment.parenttype = "LMS Batch"
|
||||||
|
enrollment.save(ignore_permissions=True)
|
||||||
@@ -5,5 +5,5 @@
|
|||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestLMSClass(FrappeTestCase):
|
class TestBatchStudent(FrappeTestCase):
|
||||||
pass
|
pass
|
||||||
@@ -16,7 +16,7 @@ class Cohort(Document):
|
|||||||
|
|
||||||
if include_counts:
|
if include_counts:
|
||||||
mentors = self._get_subgroup_counts("Cohort Mentor")
|
mentors = self._get_subgroup_counts("Cohort Mentor")
|
||||||
students = self._get_subgroup_counts("LMS Batch Membership")
|
students = self._get_subgroup_counts("LMS Enrollment")
|
||||||
join_requests = self._get_subgroup_counts("Cohort Join Request", status="Pending")
|
join_requests = self._get_subgroup_counts("Cohort Join Request", status="Pending")
|
||||||
for s in subgroups:
|
for s in subgroups:
|
||||||
s.num_mentors = mentors.get(s.name, 0)
|
s.num_mentors = mentors.get(s.name, 0)
|
||||||
@@ -56,7 +56,7 @@ class Cohort(Document):
|
|||||||
return {
|
return {
|
||||||
"subgroups": self._get_count("Cohort Subgroup"),
|
"subgroups": self._get_count("Cohort Subgroup"),
|
||||||
"mentors": self._get_count("Cohort Mentor"),
|
"mentors": self._get_count("Cohort Mentor"),
|
||||||
"students": self._get_count("LMS Batch Membership"),
|
"students": self._get_count("LMS Enrollment"),
|
||||||
"join_requests": self._get_count("Cohort Join Request", status="Pending"),
|
"join_requests": self._get_count("Cohort Join Request", status="Pending"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-16 15:06:03.985221",
|
"modified": "2023-09-29 17:08:18.950560",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Cohort Join Request",
|
"name": "Cohort Join Request",
|
||||||
@@ -68,9 +68,21 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "LMS Student",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ class CohortJoinRequest(Document):
|
|||||||
def ensure_student(self):
|
def ensure_student(self):
|
||||||
# case 1 - user is already a member
|
# case 1 - user is already a member
|
||||||
q = {
|
q = {
|
||||||
"doctype": "LMS Batch Membership",
|
"doctype": "LMS Enrollment",
|
||||||
"cohort": self.cohort,
|
"cohort": self.cohort,
|
||||||
"subgroup": self.subgroup,
|
"subgroup": self.subgroup,
|
||||||
"member": self.email,
|
"member": self.email,
|
||||||
@@ -26,21 +26,21 @@ class CohortJoinRequest(Document):
|
|||||||
cohort = frappe.get_doc("Cohort", self.cohort)
|
cohort = frappe.get_doc("Cohort", self.cohort)
|
||||||
|
|
||||||
q = {
|
q = {
|
||||||
"doctype": "LMS Batch Membership",
|
"doctype": "LMS Enrollment",
|
||||||
"course": cohort.course,
|
"course": cohort.course,
|
||||||
"member": self.email,
|
"member": self.email,
|
||||||
"member_type": "Student",
|
"member_type": "Student",
|
||||||
}
|
}
|
||||||
name = frappe.db.exists(q)
|
name = frappe.db.exists(q)
|
||||||
if name:
|
if name:
|
||||||
doc = frappe.get_doc("LMS Batch Membership", name)
|
doc = frappe.get_doc("LMS Enrollment", name)
|
||||||
doc.cohort = self.cohort
|
doc.cohort = self.cohort
|
||||||
doc.subgroup = self.subgroup
|
doc.subgroup = self.subgroup
|
||||||
doc.save(ignore_permissions=True)
|
doc.save(ignore_permissions=True)
|
||||||
else:
|
else:
|
||||||
# case 3 - user has not signed up for this course yet
|
# case 3 - user has not signed up for this course yet
|
||||||
data = {
|
data = {
|
||||||
"doctype": "LMS Batch Membership",
|
"doctype": "LMS Enrollment",
|
||||||
"course": cohort.course,
|
"course": cohort.course,
|
||||||
"cohort": self.cohort,
|
"cohort": self.cohort,
|
||||||
"subgroup": self.subgroup,
|
"subgroup": self.subgroup,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class CohortSubgroup(Document):
|
|||||||
|
|
||||||
def has_student(self, email):
|
def has_student(self, email):
|
||||||
"""Check if given user is a student of this subgroup."""
|
"""Check if given user is a student of this subgroup."""
|
||||||
q = {"doctype": "LMS Batch Membership", "subgroup": self.name, "member": email}
|
q = {"doctype": "LMS Enrollment", "subgroup": self.name, "member": email}
|
||||||
return frappe.db.exists(q)
|
return frappe.db.exists(q)
|
||||||
|
|
||||||
def has_join_request(self, email):
|
def has_join_request(self, email):
|
||||||
@@ -45,7 +45,7 @@ class CohortSubgroup(Document):
|
|||||||
|
|
||||||
def get_students(self):
|
def get_students(self):
|
||||||
emails = frappe.get_all(
|
emails = frappe.get_all(
|
||||||
"LMS Batch Membership",
|
"LMS Enrollment",
|
||||||
filters={"subgroup": self.name},
|
filters={"subgroup": self.name},
|
||||||
fields=["member"],
|
fields=["member"],
|
||||||
pluck="member",
|
pluck="member",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
"link_fieldname": "chapter"
|
"link_fieldname": "chapter"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-03-14 17:57:00.707416",
|
"modified": "2023-09-29 17:03:58.013819",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Course Chapter",
|
"name": "Course Chapter",
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-04-01 15:14:03.300260",
|
"modified": "2023-07-13 11:30:22.641076",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Course Evaluator",
|
"name": "Course Evaluator",
|
||||||
@@ -45,6 +45,30 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Class Evaluator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from lms.lms.utils import get_evaluator
|
||||||
|
|
||||||
|
|
||||||
class CourseEvaluator(Document):
|
class CourseEvaluator(Document):
|
||||||
@@ -36,21 +37,26 @@ class CourseEvaluator(Document):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_schedule(course, date):
|
def get_schedule(course, date, batch=None):
|
||||||
evaluator = frappe.db.get_value("LMS Course", course, "evaluator")
|
evaluator = get_evaluator(course, batch)
|
||||||
|
|
||||||
all_slots = frappe.get_all(
|
all_slots = frappe.get_all(
|
||||||
"Evaluator Schedule",
|
"Evaluator Schedule",
|
||||||
filters={"parent": evaluator},
|
filters={"parent": evaluator},
|
||||||
fields=["day", "start_time", "end_time"],
|
fields=["day", "start_time", "end_time"],
|
||||||
|
order_by="start_time",
|
||||||
)
|
)
|
||||||
|
|
||||||
booked_slots = frappe.get_all(
|
booked_slots = frappe.get_all(
|
||||||
"LMS Certificate Request",
|
"LMS Certificate Request",
|
||||||
filters={"evaluator": evaluator, "date": date},
|
filters={"evaluator": evaluator, "date": date},
|
||||||
fields=["start_time"],
|
fields=["start_time", "day"],
|
||||||
)
|
)
|
||||||
|
|
||||||
for slot in booked_slots:
|
for slot in booked_slots:
|
||||||
same_slot = list(filter(lambda x: x.start_time == slot.start_time, all_slots))
|
same_slot = list(
|
||||||
|
filter(lambda x: x.start_time == slot.start_time and x.day == slot.day, all_slots)
|
||||||
|
)
|
||||||
if len(same_slot):
|
if len(same_slot):
|
||||||
all_slots.remove(same_slot[0])
|
all_slots.remove(same_slot[0])
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"file_type",
|
"file_type",
|
||||||
"section_break_11",
|
"section_break_11",
|
||||||
"body",
|
"body",
|
||||||
|
"instructor_notes",
|
||||||
"help_section",
|
"help_section",
|
||||||
"help"
|
"help"
|
||||||
],
|
],
|
||||||
@@ -131,11 +132,16 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_15",
|
"fieldname": "column_break_15",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "instructor_notes",
|
||||||
|
"fieldtype": "Markdown Editor",
|
||||||
|
"label": "Instructor Notes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-05-02 12:42:16.926753",
|
"modified": "2023-09-29 17:04:19.252897",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Course Lesson",
|
"name": "Course Lesson",
|
||||||
@@ -163,7 +169,7 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
|||||||
@@ -89,10 +89,26 @@ class CourseLesson(Document):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def save_progress(lesson, course, status):
|
def save_progress(lesson, course, status):
|
||||||
membership = frappe.db.exists(
|
membership = frappe.db.exists(
|
||||||
"LMS Batch Membership", {"member": frappe.session.user, "course": course}
|
"LMS Enrollment", {"member": frappe.session.user, "course": course}
|
||||||
)
|
)
|
||||||
if not membership:
|
if not membership:
|
||||||
return
|
return 0
|
||||||
|
|
||||||
|
body = frappe.db.get_value("Course Lesson", lesson, "body")
|
||||||
|
macros = find_macros(body)
|
||||||
|
quizzes = [value for name, value in macros if name == "Quiz"]
|
||||||
|
|
||||||
|
for quiz in quizzes:
|
||||||
|
passing_percentage = frappe.db.get_value("LMS Quiz", quiz, "passing_percentage")
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"LMS Quiz Submission",
|
||||||
|
{
|
||||||
|
"quiz": quiz,
|
||||||
|
"owner": frappe.session.user,
|
||||||
|
"percentage": [">=", passing_percentage],
|
||||||
|
},
|
||||||
|
):
|
||||||
|
return 0
|
||||||
|
|
||||||
filters = {"lesson": lesson, "owner": frappe.session.user, "course": course}
|
filters = {"lesson": lesson, "owner": frappe.session.user, "course": course}
|
||||||
if frappe.db.exists("LMS Course Progress", filters):
|
if frappe.db.exists("LMS Course Progress", filters):
|
||||||
@@ -110,7 +126,7 @@ def save_progress(lesson, course, status):
|
|||||||
).save(ignore_permissions=True)
|
).save(ignore_permissions=True)
|
||||||
|
|
||||||
progress = get_course_progress(course)
|
progress = get_course_progress(course)
|
||||||
frappe.db.set_value("LMS Batch Membership", membership, "progress", progress)
|
frappe.db.set_value("LMS Enrollment", membership, "progress", progress)
|
||||||
return progress
|
return progress
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"exercise",
|
"exercise",
|
||||||
"status",
|
"status",
|
||||||
"batch",
|
"batch_old",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"exercise_title",
|
"exercise_title",
|
||||||
"course",
|
"course",
|
||||||
@@ -40,10 +40,10 @@
|
|||||||
"options": "Correct\nIncorrect"
|
"options": "Correct\nIncorrect"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "batch",
|
"fieldname": "batch_old",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Batch",
|
"label": "Batch Old",
|
||||||
"options": "LMS Batch"
|
"options": "LMS Batch Old"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_4",
|
"fieldname": "column_break_4",
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"fieldname": "member",
|
"fieldname": "member",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Member",
|
"label": "Member",
|
||||||
"options": "LMS Batch Membership"
|
"options": "LMS Enrollment"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "member.member",
|
"fetch_from": "member.member",
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-08 22:58:46.312861",
|
"modified": "2021-12-08 22:58:46.312863",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Exercise Latest Submission",
|
"name": "Exercise Latest Submission",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"exercise",
|
"exercise",
|
||||||
"status",
|
"status",
|
||||||
"batch",
|
"batch_old",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"exercise_title",
|
"exercise_title",
|
||||||
"course",
|
"course",
|
||||||
@@ -44,10 +44,10 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "batch",
|
"fieldname": "batch_old",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Batch",
|
"label": "Batch Old",
|
||||||
"options": "LMS Batch"
|
"options": "LMS Batch Old"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "exercise.lesson",
|
"fetch_from": "exercise.lesson",
|
||||||
@@ -96,12 +96,12 @@
|
|||||||
"fieldname": "member",
|
"fieldname": "member",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Member",
|
"label": "Member",
|
||||||
"options": "LMS Batch Membership"
|
"options": "LMS Enrollment"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-08 22:25:05.809376",
|
"modified": "2021-12-08 22:25:05.809377",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Exercise Submission",
|
"name": "Exercise Submission",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-21 09:34:35.018280",
|
"modified": "2023-09-29 17:04:58.167481",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Function",
|
"name": "Function",
|
||||||
@@ -44,11 +44,12 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1
|
"share": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-12-21 09:35:20.443192",
|
"modified": "2023-09-29 17:05:27.231982",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Industry",
|
"name": "Industry",
|
||||||
@@ -44,11 +44,12 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "LMS Student",
|
||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1
|
"share": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,11 @@ from frappe.utils.password import get_decrypted_password
|
|||||||
|
|
||||||
class InviteRequest(Document):
|
class InviteRequest(Document):
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
if self.has_value_changed("status") and self.status == "Approved":
|
if (
|
||||||
|
self.has_value_changed("status")
|
||||||
|
and self.status == "Approved"
|
||||||
|
and not frappe.flags.in_test
|
||||||
|
):
|
||||||
self.send_email()
|
self.send_email()
|
||||||
|
|
||||||
def create_user(self, password):
|
def create_user(self, password):
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
# Copyright (c) 2021, Frappe and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe import _
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
|
|
||||||
class LessonAssignment(Document):
|
|
||||||
def validate(self):
|
|
||||||
self.validate_duplicates()
|
|
||||||
|
|
||||||
def validate_duplicates(self):
|
|
||||||
if frappe.db.exists(
|
|
||||||
"Lesson Assignment",
|
|
||||||
{"lesson": self.lesson, "member": self.member, "name": ["!=", self.name]},
|
|
||||||
):
|
|
||||||
lesson_title = frappe.db.get_value("Course Lesson", self.lesson, "title")
|
|
||||||
frappe.throw(
|
|
||||||
_("Assignment for Lesson {0} by {1} already exists.").format(
|
|
||||||
lesson_title, self.member_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def upload_assignment(assignment, lesson):
|
|
||||||
args = {
|
|
||||||
"doctype": "Lesson Assignment",
|
|
||||||
"lesson": lesson,
|
|
||||||
"member": frappe.session.user,
|
|
||||||
}
|
|
||||||
if frappe.db.exists(args):
|
|
||||||
del args["doctype"]
|
|
||||||
frappe.db.set_value("Lesson Assignment", args, "assignment", assignment)
|
|
||||||
else:
|
|
||||||
args.update({"assignment": assignment})
|
|
||||||
lesson_work = frappe.get_doc(args)
|
|
||||||
lesson_work.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_assignment(lesson):
|
|
||||||
assignment = frappe.db.get_value(
|
|
||||||
"Lesson Assignment",
|
|
||||||
{"lesson": lesson, "member": frappe.session.user},
|
|
||||||
["lesson", "member", "assignment", "comments", "status"],
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
assignment.file_name = frappe.db.get_value(
|
|
||||||
"File", {"file_url": assignment.assignment}, "file_name"
|
|
||||||
)
|
|
||||||
return assignment
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def grade_assignment(name, result, comments):
|
|
||||||
doc = frappe.get_doc("Lesson Assignment", name)
|
|
||||||
doc.status = result
|
|
||||||
doc.comments = comments
|
|
||||||
doc.save(ignore_permissions=True)
|
|
||||||
40
lms/lms/doctype/lms_assessment/lms_assessment.json
Normal file
40
lms/lms/doctype/lms_assessment/lms_assessment.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2023-05-29 14:50:07.910319",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"assessment_type",
|
||||||
|
"assessment_name"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "assessment_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Assessment Type",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "assessment_name",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Assessment Name",
|
||||||
|
"options": "assessment_type"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-05-29 14:56:36.602399",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Assessment",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
9
lms/lms/doctype/lms_assessment/lms_assessment.py
Normal file
9
lms/lms/doctype/lms_assessment/lms_assessment.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LMSAssessment(Document):
|
||||||
|
pass
|
||||||
8
lms/lms/doctype/lms_assignment/lms_assignment.js
Normal file
8
lms/lms/doctype/lms_assignment/lms_assignment.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("LMS Assignment", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
104
lms/lms/doctype/lms_assignment/lms_assignment.json
Normal file
104
lms/lms/doctype/lms_assignment/lms_assignment.json
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "format: ASG-{#####}",
|
||||||
|
"creation": "2023-05-26 19:41:26.025081",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"title",
|
||||||
|
"grade_assignment",
|
||||||
|
"question",
|
||||||
|
"column_break_hmwv",
|
||||||
|
"type",
|
||||||
|
"show_answer",
|
||||||
|
"answer"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "question",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Question"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Type",
|
||||||
|
"options": "Document\nPDF\nURL\nImage\nText"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_hmwv",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.type == \"Text\"",
|
||||||
|
"fieldname": "show_answer",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Show Answer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "show_answer",
|
||||||
|
"fieldname": "answer",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Answer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"depends_on": "eval:doc.type == \"Text\"",
|
||||||
|
"fieldname": "grade_assignment",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Grade Assignment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-10-06 12:08:46.898950",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Assignment",
|
||||||
|
"naming_rule": "Expression (old style)",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"show_title_field_in_link": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "title"
|
||||||
|
}
|
||||||
25
lms/lms/doctype/lms_assignment/lms_assignment.py
Normal file
25
lms/lms/doctype/lms_assignment/lms_assignment.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from lms.lms.utils import has_course_moderator_role, has_course_instructor_role
|
||||||
|
|
||||||
|
|
||||||
|
class LMSAssignment(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def save_assignment(assignment, title, type, question):
|
||||||
|
if not has_course_moderator_role() or not has_course_instructor_role():
|
||||||
|
return
|
||||||
|
|
||||||
|
if assignment:
|
||||||
|
doc = frappe.get_doc("LMS Assignment", assignment)
|
||||||
|
else:
|
||||||
|
doc = frappe.get_doc({"doctype": "LMS Assignment"})
|
||||||
|
|
||||||
|
doc.update({"title": title, "type": type, "question": question})
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
|
return doc.name
|
||||||
9
lms/lms/doctype/lms_assignment/test_lms_assignment.py
Normal file
9
lms/lms/doctype/lms_assignment/test_lms_assignment.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSAssignment(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2022, Frappe and contributors
|
// Copyright (c) 2021, Frappe and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("LMS Class", {
|
frappe.ui.form.on("LMS Assignment Submission", {
|
||||||
onload: function (frm) {
|
onload: function (frm) {
|
||||||
frm.set_query("student", "students", function (doc) {
|
frm.set_query("member", function (doc) {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
ignore_user_type: 1,
|
ignore_user_type: 1,
|
||||||
@@ -1,30 +1,39 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
|
"autoname": "format: ASG-SUB-{#####}",
|
||||||
"creation": "2021-12-21 16:15:22.651658",
|
"creation": "2021-12-21 16:15:22.651658",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"assignment",
|
"assignment",
|
||||||
"lesson",
|
"assignment_title",
|
||||||
"course",
|
"type",
|
||||||
"evaluator",
|
|
||||||
"status",
|
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
"comments"
|
"section_break_dlzh",
|
||||||
|
"question",
|
||||||
|
"column_break_zvis",
|
||||||
|
"assignment_attachment",
|
||||||
|
"answer",
|
||||||
|
"section_break_rqal",
|
||||||
|
"status",
|
||||||
|
"evaluator",
|
||||||
|
"column_break_esgd",
|
||||||
|
"comments",
|
||||||
|
"section_break_cwaw",
|
||||||
|
"lesson",
|
||||||
|
"course",
|
||||||
|
"column_break_ygdu"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "lesson",
|
"fieldname": "lesson",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Lesson",
|
"label": "Lesson",
|
||||||
"options": "Course Lesson",
|
"options": "Course Lesson"
|
||||||
"reqd": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
@@ -32,9 +41,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "assignment",
|
"fieldname": "assignment",
|
||||||
"fieldtype": "Attach",
|
"fieldtype": "Link",
|
||||||
"label": "Assignment",
|
"label": "Assignment",
|
||||||
"reqd": 1
|
"options": "LMS Assignment"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "member",
|
"fieldname": "member",
|
||||||
@@ -69,7 +78,7 @@
|
|||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "Pass\nFail\nNot Graded"
|
"options": "Pass\nFail\nNot Graded\nNot Applicable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "comments",
|
"fieldname": "comments",
|
||||||
@@ -83,15 +92,75 @@
|
|||||||
"label": "Evaluator",
|
"label": "Evaluator",
|
||||||
"options": "User",
|
"options": "User",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:!([\"URL\", \"Text\"]).includes(doc.type);",
|
||||||
|
"fieldname": "assignment_attachment",
|
||||||
|
"fieldtype": "Attach",
|
||||||
|
"label": "Assignment Attachment",
|
||||||
|
"mandatory_depends_on": "eval:doc.type != \"URL\";"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "assignment.type",
|
||||||
|
"fieldname": "type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Type",
|
||||||
|
"options": "Document\nPDF\nURL\nImage\nText"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "assignment.question",
|
||||||
|
"fieldname": "question",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Question",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "assignment.title",
|
||||||
|
"fieldname": "assignment_title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Assignment Title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_rqal",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_esgd",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_cwaw",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_ygdu",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:([\"URL\", \"Text\"]).includes(doc.type);",
|
||||||
|
"fieldname": "answer",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Answer",
|
||||||
|
"mandatory_depends_on": "eval:doc.type == \"URL\";"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_dlzh",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_zvis",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2023-03-27 13:24:18.696868",
|
"modified": "2023-10-06 15:14:55.984714",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Lesson Assignment",
|
"name": "LMS Assignment Submission",
|
||||||
|
"naming_rule": "Expression (old style)",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -121,7 +190,11 @@
|
|||||||
{
|
{
|
||||||
"color": "Red",
|
"color": "Red",
|
||||||
"title": "Fail"
|
"title": "Fail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Blue",
|
||||||
|
"title": "Not Applicable"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title_field": "lesson"
|
"title_field": "assignment_title"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
# Copyright (c) 2021, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import validate_url, validate_email_address
|
||||||
|
from frappe.email.doctype.email_template.email_template import get_email_template
|
||||||
|
|
||||||
|
|
||||||
|
class LMSAssignmentSubmission(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_duplicates()
|
||||||
|
|
||||||
|
def after_insert(self):
|
||||||
|
if not frappe.flags.in_test:
|
||||||
|
self.send_mail()
|
||||||
|
|
||||||
|
def validate_duplicates(self):
|
||||||
|
if frappe.db.exists(
|
||||||
|
"LMS Assignment Submission",
|
||||||
|
{"assignment": self.assignment, "member": self.member, "name": ["!=", self.name]},
|
||||||
|
):
|
||||||
|
lesson_title = frappe.db.get_value("Course Lesson", self.lesson, "title")
|
||||||
|
frappe.throw(
|
||||||
|
_("Assignment for Lesson {0} by {1} already exists.").format(
|
||||||
|
lesson_title, self.member_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_mail(self):
|
||||||
|
subject = _("New Assignment Submission")
|
||||||
|
template = "assignment_submission"
|
||||||
|
custom_template = frappe.db.get_single_value(
|
||||||
|
"LMS Settings", "assignment_submission_template"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"member_name": self.member_name,
|
||||||
|
"assignment_name": self.assignment,
|
||||||
|
"assignment_title": self.assignment_title,
|
||||||
|
"submission_name": self.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
moderators = frappe.get_all("Has Role", {"role": "Moderator"}, pluck="parent")
|
||||||
|
for moderator in moderators:
|
||||||
|
if not validate_email_address(moderator):
|
||||||
|
moderators.remove(moderator)
|
||||||
|
|
||||||
|
if custom_template:
|
||||||
|
email_template = get_email_template(custom_template, args)
|
||||||
|
subject = email_template.get("subject")
|
||||||
|
content = email_template.get("message")
|
||||||
|
frappe.sendmail(
|
||||||
|
recipients=moderators,
|
||||||
|
subject=subject,
|
||||||
|
template=template if not custom_template else None,
|
||||||
|
content=content if custom_template else None,
|
||||||
|
args=args,
|
||||||
|
header=[subject, "green"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def upload_assignment(
|
||||||
|
assignment_attachment=None,
|
||||||
|
answer=None,
|
||||||
|
assignment=None,
|
||||||
|
lesson=None,
|
||||||
|
status="Not Graded",
|
||||||
|
comments=None,
|
||||||
|
submission=None,
|
||||||
|
):
|
||||||
|
if frappe.session.user == "Guest":
|
||||||
|
return
|
||||||
|
|
||||||
|
assignment_details = frappe.db.get_value(
|
||||||
|
"LMS Assignment", assignment, ["type", "grade_assignment"], as_dict=1
|
||||||
|
)
|
||||||
|
assignment_type = assignment_details.type
|
||||||
|
|
||||||
|
if assignment_type in ["URL", "Text"] and not answer:
|
||||||
|
frappe.throw(_("Please enter the URL for assignment submission."))
|
||||||
|
|
||||||
|
if assignment_type == "File" and not assignment_attachment:
|
||||||
|
frappe.throw(_("Please upload the assignment file."))
|
||||||
|
|
||||||
|
if assignment_type == "URL" and not validate_url(answer):
|
||||||
|
frappe.throw(_("Please enter a valid URL."))
|
||||||
|
|
||||||
|
if submission:
|
||||||
|
doc = frappe.get_doc("LMS Assignment Submission", submission)
|
||||||
|
else:
|
||||||
|
doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "LMS Assignment Submission",
|
||||||
|
"assignment": assignment,
|
||||||
|
"lesson": lesson,
|
||||||
|
"member": frappe.session.user,
|
||||||
|
"type": assignment_type,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.update(
|
||||||
|
{
|
||||||
|
"assignment_attachment": assignment_attachment,
|
||||||
|
"status": "Not Applicable"
|
||||||
|
if assignment_type == "Text" and not assignment_details.grade_assignment
|
||||||
|
else status,
|
||||||
|
"comments": comments,
|
||||||
|
"answer": answer,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
|
return doc.name
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_assignment(lesson):
|
||||||
|
assignment = frappe.db.get_value(
|
||||||
|
"LMS Assignment Submission",
|
||||||
|
{"lesson": lesson, "member": frappe.session.user},
|
||||||
|
["name", "lesson", "member", "assignment_attachment", "comments", "status"],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
assignment.file_name = frappe.db.get_value(
|
||||||
|
"File", {"file_url": assignment.assignment_attachment}, "file_name"
|
||||||
|
)
|
||||||
|
return assignment
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def grade_assignment(name, result, comments):
|
||||||
|
doc = frappe.get_doc("LMS Assignment Submission", name)
|
||||||
|
doc.status = result
|
||||||
|
doc.comments = comments
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
@@ -5,5 +5,5 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
class TestLessonAssignment(unittest.TestCase):
|
class TestLMSAssignmentSubmission(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
@@ -1,7 +1,167 @@
|
|||||||
// Copyright (c) 2021, FOSS United and contributors
|
// Copyright (c) 2022, Frappe and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("LMS Batch", {
|
frappe.ui.form.on("LMS Batch", {
|
||||||
// refresh: function(frm) {
|
onload: function (frm) {
|
||||||
// }
|
frm.set_query("student", "students", function (doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
ignore_user_type: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("reference_doctype", "timetable", function () {
|
||||||
|
let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"];
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
name: ["in", doctypes],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("reference_doctype", "timetable_legends", function () {
|
||||||
|
let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"];
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
name: ["in", doctypes],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (frm.doc.timetable.length && !frm.doc.timetable_legends.length) {
|
||||||
|
set_default_legends(frm);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
timetable_template: function (frm) {
|
||||||
|
set_timetable(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: (frm) => {
|
||||||
|
frm.add_web_link(`/batches/details/${frm.doc.name}`, "See on website");
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const set_timetable = (frm) => {
|
||||||
|
if (frm.doc.timetable_template) {
|
||||||
|
frm.clear_table("timetable");
|
||||||
|
frm.refresh_fields();
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get_list",
|
||||||
|
args: {
|
||||||
|
doctype: "LMS Batch Timetable",
|
||||||
|
parent: "LMS Timetable Template",
|
||||||
|
fields: [
|
||||||
|
"reference_doctype",
|
||||||
|
"reference_docname",
|
||||||
|
"day",
|
||||||
|
"start_time",
|
||||||
|
"end_time",
|
||||||
|
"duration",
|
||||||
|
"milestone",
|
||||||
|
],
|
||||||
|
filters: {
|
||||||
|
parent: frm.doc.timetable_template,
|
||||||
|
parenttype: "LMS Timetable Template",
|
||||||
|
},
|
||||||
|
order_by: "idx",
|
||||||
|
},
|
||||||
|
callback: (data) => {
|
||||||
|
add_timetable_rows(frm, data.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const add_timetable_rows = (frm, timetable) => {
|
||||||
|
timetable.forEach((row) => {
|
||||||
|
let child = frm.add_child("timetable");
|
||||||
|
child.reference_doctype = row.reference_doctype;
|
||||||
|
child.reference_docname = row.reference_docname;
|
||||||
|
child.date = frappe.datetime.add_days(frm.doc.start_date, row.day - 1);
|
||||||
|
child.start_time = row.start_time;
|
||||||
|
child.end_time = row.end_time
|
||||||
|
? row.end_time
|
||||||
|
: row.duration
|
||||||
|
? moment
|
||||||
|
.utc(row.start_time, "HH:mm")
|
||||||
|
.add(row.duration, "hour")
|
||||||
|
.format("HH:mm")
|
||||||
|
: null;
|
||||||
|
child.duration = row.duration;
|
||||||
|
child.milestone = row.milestone;
|
||||||
|
});
|
||||||
|
frm.refresh_field("timetable");
|
||||||
|
|
||||||
|
set_legends(frm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const set_legends = (frm) => {
|
||||||
|
if (frm.doc.timetable_template) {
|
||||||
|
frm.clear_table("timetable_legends");
|
||||||
|
frm.refresh_fields();
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get_list",
|
||||||
|
args: {
|
||||||
|
doctype: "LMS Timetable Legend",
|
||||||
|
parent: "LMS Timetable Template",
|
||||||
|
fields: ["reference_doctype", "label", "color"],
|
||||||
|
filters: {
|
||||||
|
parent: frm.doc.timetable_template,
|
||||||
|
parenttype: "LMS Timetable Template",
|
||||||
|
},
|
||||||
|
order_by: "idx",
|
||||||
|
},
|
||||||
|
callback: (data) => {
|
||||||
|
add_legend_rows(frm, data.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const add_legend_rows = (frm, legends) => {
|
||||||
|
legends.forEach((row) => {
|
||||||
|
let child = frm.add_child("timetable_legends");
|
||||||
|
child.reference_doctype = row.reference_doctype;
|
||||||
|
child.label = row.label;
|
||||||
|
child.color = row.color;
|
||||||
|
});
|
||||||
|
frm.refresh_field("timetable_legends");
|
||||||
|
frm.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
const set_default_legends = (frm) => {
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
reference_doctype: "Course Lesson",
|
||||||
|
label: "Lesson",
|
||||||
|
color: "#449CF0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference_doctype: "LMS Quiz",
|
||||||
|
label: "LMS Quiz",
|
||||||
|
color: "#39E4A5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference_doctype: "LMS Assignment",
|
||||||
|
label: "LMS Assignment",
|
||||||
|
color: "#ECAD4B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference_doctype: "LMS Live Class",
|
||||||
|
label: "LMS Live Class",
|
||||||
|
color: "#bb8be8",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
data.forEach((detail) => {
|
||||||
|
let child = frm.add_child("timetable_legends");
|
||||||
|
child.reference_doctype = detail.reference_doctype;
|
||||||
|
child.label = detail.label;
|
||||||
|
child.color = detail.color;
|
||||||
|
});
|
||||||
|
frm.refresh_field("timetable_legends");
|
||||||
|
frm.save();
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,132 +1,314 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"autoname": "format: BATCH-{#####}",
|
"allow_rename": 1,
|
||||||
"creation": "2021-03-18 19:37:34.614796",
|
"autoname": "format: CLS-{#####}",
|
||||||
|
"creation": "2022-11-09 16:14:05.876933",
|
||||||
|
"default_view": "List",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"course",
|
|
||||||
"start_date",
|
|
||||||
"start_time",
|
|
||||||
"column_break_3",
|
|
||||||
"title",
|
"title",
|
||||||
"sessions_on",
|
"start_date",
|
||||||
|
"end_date",
|
||||||
|
"column_break_4",
|
||||||
|
"start_time",
|
||||||
"end_time",
|
"end_time",
|
||||||
"section_break_5",
|
"published",
|
||||||
|
"allow_self_enrollment",
|
||||||
|
"section_break_rgfj",
|
||||||
|
"medium",
|
||||||
|
"category",
|
||||||
|
"column_break_flwy",
|
||||||
|
"seat_count",
|
||||||
|
"evaluation_end_date",
|
||||||
|
"section_break_6",
|
||||||
"description",
|
"description",
|
||||||
"section_break_7",
|
"batch_details_raw",
|
||||||
"visibility",
|
"column_break_hlqw",
|
||||||
"membership",
|
"batch_details",
|
||||||
"column_break_9",
|
"meta_image",
|
||||||
"status",
|
"section_break_jgji",
|
||||||
"stage"
|
"students",
|
||||||
|
"courses",
|
||||||
|
"assessment_tab",
|
||||||
|
"assessment",
|
||||||
|
"schedule_tab",
|
||||||
|
"timetable_template",
|
||||||
|
"column_break_anya",
|
||||||
|
"show_live_class",
|
||||||
|
"allow_future",
|
||||||
|
"section_break_ontp",
|
||||||
|
"timetable",
|
||||||
|
"timetable_legends",
|
||||||
|
"pricing_tab",
|
||||||
|
"section_break_gsac",
|
||||||
|
"paid_batch",
|
||||||
|
"column_break_iens",
|
||||||
|
"amount",
|
||||||
|
"currency",
|
||||||
|
"amount_usd",
|
||||||
|
"customisations_tab",
|
||||||
|
"section_break_ubxi",
|
||||||
|
"custom_component",
|
||||||
|
"column_break_pxgb",
|
||||||
|
"custom_script"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
|
||||||
"fieldname": "course",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Course",
|
|
||||||
"options": "LMS Course",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "title",
|
"fieldname": "title",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "end_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "End Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Markdown Editor",
|
"fieldtype": "Small Text",
|
||||||
"label": "Description"
|
"label": "Description",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Public",
|
"fieldname": "section_break_6",
|
||||||
"fieldname": "visibility",
|
"fieldtype": "Section Break"
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Visibility",
|
|
||||||
"options": "Public\nUnlisted\nPrivate"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "membership",
|
"fieldname": "students",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Table",
|
||||||
"label": "Membership",
|
"label": "Students",
|
||||||
"options": "\nOpen\nRestricted\nInvite Only\nClosed"
|
"options": "Batch Student"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Active",
|
"fieldname": "courses",
|
||||||
"fieldname": "status",
|
"fieldtype": "Table",
|
||||||
"fieldtype": "Select",
|
"label": "Courses",
|
||||||
"in_list_view": 1,
|
"options": "Batch Course"
|
||||||
"label": "Status",
|
|
||||||
"options": "Active\nInactive"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "Ready",
|
|
||||||
"fieldname": "stage",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"label": "Stage",
|
|
||||||
"options": "Ready\nIn Progress\nCompleted\nCancelled"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_3",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "section_break_5",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Batch Description"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_9",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "section_break_7",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Batch Settings"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "start_date",
|
"fieldname": "start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Start Date"
|
"label": "Start Date",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "custom_component",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Custom HTML",
|
||||||
|
"options": "HTML"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Students will be enrolled in a paid batch once they complete the payment",
|
||||||
|
"fieldname": "paid_batch",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Paid Batch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "seat_count",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Seat Count"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "start_time",
|
"fieldname": "start_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"in_list_view": 1,
|
"label": "Start Time",
|
||||||
"label": "Start Time"
|
"reqd": 1
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "sessions_on",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Sessions On Days"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "end_time",
|
"fieldname": "end_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"in_list_view": 1,
|
"label": "End Time",
|
||||||
"label": "End Time"
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "assessment_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Assessment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "assessment",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Assessment",
|
||||||
|
"options": "LMS Assessment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_rgfj",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Online",
|
||||||
|
"fieldname": "medium",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Medium",
|
||||||
|
"options": "Online\nOffline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_flwy",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "category",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Category",
|
||||||
|
"options": "LMS Category"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "These customisations will work on the main batch page.",
|
||||||
|
"fieldname": "section_break_ubxi",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "schedule_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Timetable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_gsac",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_iens",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "paid_batch",
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "paid_batch",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Currency",
|
||||||
|
"options": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_details",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Batch Details",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "published",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Published"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "timetable",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Timetable",
|
||||||
|
"options": "LMS Batch Timetable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "timetable_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Timetable Template",
|
||||||
|
"options": "LMS Timetable Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_anya",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "show_live_class",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Show live class"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_ontp",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_details_raw",
|
||||||
|
"fieldtype": "HTML Editor",
|
||||||
|
"label": "Batch Details Raw"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_hlqw",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_jgji",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "meta_image",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"label": "Meta Image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_pxgb",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customisations_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Customisations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pricing_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Pricing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "custom_script",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Custom Script (JavaScript)",
|
||||||
|
"options": "Javascript"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "timetable_legends",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Timetable Legends",
|
||||||
|
"options": "LMS Timetable Legend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "allow_future",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow accessing future dates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "evaluation_end_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Evaluation End Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "paid_batch",
|
||||||
|
"description": "If you set an amount here, then the USD equivalent setting will not get applied.",
|
||||||
|
"fieldname": "amount_usd",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount (USD)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_self_enrollment",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow Self Enrollment"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [
|
"links": [],
|
||||||
{
|
"modified": "2024-01-22 10:42:42.872995",
|
||||||
"group": "Members",
|
|
||||||
"link_doctype": "LMS Batch Membership",
|
|
||||||
"link_fieldname": "batch"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modified": "2022-09-28 18:43:22.955907",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Batch",
|
"name": "LMS Batch",
|
||||||
"naming_rule": "Expression",
|
"naming_rule": "Expression (old style)",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -140,11 +322,23 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"show_title_field_in_link": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"title_field": "title"
|
||||||
}
|
}
|
||||||
@@ -1,93 +1,446 @@
|
|||||||
# Copyright (c) 2021, FOSS United and contributors
|
# Copyright (c) 2022, Frappe and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
import requests
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from datetime import timedelta
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import (
|
||||||
from lms.lms.doctype.lms_batch_membership.lms_batch_membership import create_membership
|
cint,
|
||||||
from lms.lms.utils import is_mentor
|
format_date,
|
||||||
|
format_datetime,
|
||||||
|
get_time,
|
||||||
|
)
|
||||||
|
from lms.lms.utils import get_lessons, get_lesson_index, get_lesson_url
|
||||||
|
from lms.www.utils import get_quiz_details, get_assignment_details
|
||||||
|
from frappe.email.doctype.email_template.email_template import get_email_template
|
||||||
|
|
||||||
|
|
||||||
class LMSBatch(Document):
|
class LMSBatch(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
pass
|
if self.seat_count:
|
||||||
# self.validate_if_mentor()
|
self.validate_seats_left()
|
||||||
|
self.validate_duplicate_courses()
|
||||||
|
self.validate_duplicate_students()
|
||||||
|
self.validate_duplicate_assessments()
|
||||||
|
self.validate_membership()
|
||||||
|
self.validate_timetable()
|
||||||
|
self.send_confirmation_mail()
|
||||||
|
self.validate_evaluation_end_date()
|
||||||
|
|
||||||
def validate_if_mentor(self):
|
def validate_duplicate_students(self):
|
||||||
if not is_mentor(self.course, frappe.session.user):
|
students = [row.student for row in self.students]
|
||||||
course_title = frappe.db.get_value("LMS Course", self.course, "title")
|
duplicates = {student for student in students if students.count(student) > 1}
|
||||||
frappe.throw(_("You are not a mentor of the course {0}").format(course_title))
|
if len(duplicates):
|
||||||
|
frappe.throw(
|
||||||
def after_insert(self):
|
_("Student {0} has already been added to this batch.").format(
|
||||||
create_membership(batch=self.name, course=self.course, member_type="Mentor")
|
frappe.bold(next(iter(duplicates)))
|
||||||
|
)
|
||||||
def is_member(self, email, member_type=None):
|
|
||||||
"""Checks if a person is part of a batch.
|
|
||||||
|
|
||||||
If member_type is specified, checks if the person is a Student/Mentor.
|
|
||||||
"""
|
|
||||||
|
|
||||||
filters = {"batch": self.name, "member": email}
|
|
||||||
if member_type:
|
|
||||||
filters["member_type"] = member_type
|
|
||||||
return frappe.db.exists("LMS Batch Membership", filters)
|
|
||||||
|
|
||||||
def get_membership(self, email):
|
|
||||||
"""Returns the membership document of given user."""
|
|
||||||
name = frappe.get_value(
|
|
||||||
doctype="LMS Batch Membership",
|
|
||||||
filters={"batch": self.name, "member": email},
|
|
||||||
fieldname="name",
|
|
||||||
)
|
)
|
||||||
return frappe.get_doc("LMS Batch Membership", name)
|
|
||||||
|
|
||||||
def get_current_lesson(self, user):
|
def validate_duplicate_courses(self):
|
||||||
"""Returns the name of the current lesson for the given user."""
|
courses = [row.course for row in self.courses]
|
||||||
membership = self.get_membership(user)
|
duplicates = {course for course in courses if courses.count(course) > 1}
|
||||||
return membership and membership.current_lesson
|
if len(duplicates):
|
||||||
|
title = frappe.db.get_value("LMS Course", next(iter(duplicates)), "title")
|
||||||
|
frappe.throw(
|
||||||
|
_("Course {0} has already been added to this batch.").format(frappe.bold(title))
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_duplicate_assessments(self):
|
||||||
|
assessments = [row.assessment_name for row in self.assessment]
|
||||||
|
for assessment in self.assessment:
|
||||||
|
if assessments.count(assessment.assessment_name) > 1:
|
||||||
|
title = frappe.db.get_value(
|
||||||
|
assessment.assessment_type, assessment.assessment_name, "title"
|
||||||
|
)
|
||||||
|
frappe.throw(
|
||||||
|
_("Assessment {0} has already been added to this batch.").format(
|
||||||
|
frappe.bold(title)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_confirmation_mail(self):
|
||||||
|
for student in self.students:
|
||||||
|
if not student.confirmation_email_sent:
|
||||||
|
self.send_mail(student)
|
||||||
|
student.confirmation_email_sent = 1
|
||||||
|
|
||||||
|
def validate_evaluation_end_date(self):
|
||||||
|
if self.evaluation_end_date and self.evaluation_end_date < self.end_date:
|
||||||
|
frappe.throw(_("Evaluation end date cannot be less than the batch end date."))
|
||||||
|
|
||||||
|
def send_mail(self, student):
|
||||||
|
subject = _("Enrollment Confirmation for the Next Training Batch")
|
||||||
|
template = "batch_confirmation"
|
||||||
|
custom_template = frappe.db.get_single_value(
|
||||||
|
"LMS Settings", "batch_confirmation_template"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"student_name": student.student_name,
|
||||||
|
"start_time": self.start_time,
|
||||||
|
"start_date": self.start_date,
|
||||||
|
"medium": self.medium,
|
||||||
|
"name": self.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if custom_template:
|
||||||
|
email_template = get_email_template(custom_template, args)
|
||||||
|
subject = email_template.get("subject")
|
||||||
|
content = email_template.get("message")
|
||||||
|
|
||||||
|
frappe.sendmail(
|
||||||
|
recipients=student.student,
|
||||||
|
subject=subject,
|
||||||
|
template=template if not custom_template else None,
|
||||||
|
content=content if custom_template else None,
|
||||||
|
args=args,
|
||||||
|
header=[subject, "green"],
|
||||||
|
retry=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_membership(self):
|
||||||
|
for course in self.courses:
|
||||||
|
for student in self.students:
|
||||||
|
filters = {
|
||||||
|
"doctype": "LMS Enrollment",
|
||||||
|
"member": student.student,
|
||||||
|
"course": course.course,
|
||||||
|
}
|
||||||
|
if not frappe.db.exists(filters):
|
||||||
|
frappe.get_doc(filters).save()
|
||||||
|
|
||||||
|
def validate_seats_left(self):
|
||||||
|
if cint(self.seat_count) < len(self.students):
|
||||||
|
frappe.throw(_("There are no seats available in this batch."))
|
||||||
|
|
||||||
|
def validate_timetable(self):
|
||||||
|
for schedule in self.timetable:
|
||||||
|
if schedule.start_time and schedule.end_time:
|
||||||
|
if get_time(schedule.start_time) > get_time(schedule.end_time) or get_time(
|
||||||
|
schedule.start_time
|
||||||
|
) == get_time(schedule.end_time):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0} Start time cannot be greater than or equal to end time.").format(
|
||||||
|
schedule.idx
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if get_time(schedule.start_time) < get_time(self.start_time) or get_time(
|
||||||
|
schedule.start_time
|
||||||
|
) > get_time(self.end_time):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0} Start time cannot be outside the batch duration.").format(
|
||||||
|
schedule.idx
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if get_time(schedule.end_time) < get_time(self.start_time) or get_time(
|
||||||
|
schedule.end_time
|
||||||
|
) > get_time(self.end_time):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0} End time cannot be outside the batch duration.").format(schedule.idx)
|
||||||
|
)
|
||||||
|
|
||||||
|
if schedule.date < self.start_date or schedule.date > self.end_date:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0} Date cannot be outside the batch duration.").format(schedule.idx)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def save_message(message, batch):
|
def remove_student(student, batch_name):
|
||||||
doc = frappe.get_doc(
|
frappe.only_for("Moderator")
|
||||||
|
frappe.db.delete("Batch Student", {"student": student, "parent": batch_name})
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def remove_course(course, parent):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
|
frappe.db.delete("Batch Course", {"course": course, "parent": parent})
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def remove_assessment(assessment, parent):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
|
frappe.db.delete("LMS Assessment", {"assessment_name": assessment, "parent": parent})
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_live_class(
|
||||||
|
batch_name, title, duration, date, time, timezone, auto_recording, description=None
|
||||||
|
):
|
||||||
|
date = format_date(date, "yyyy-mm-dd", True)
|
||||||
|
frappe.only_for("Moderator")
|
||||||
|
payload = {
|
||||||
|
"topic": title,
|
||||||
|
"start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"),
|
||||||
|
"duration": duration,
|
||||||
|
"agenda": description,
|
||||||
|
"private_meeting": True,
|
||||||
|
"auto_recording": "none"
|
||||||
|
if auto_recording == "No Recording"
|
||||||
|
else auto_recording.lower(),
|
||||||
|
"timezone": timezone,
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
"Authorization": "Bearer " + authenticate(),
|
||||||
|
"content-type": "application/json",
|
||||||
|
}
|
||||||
|
response = requests.post(
|
||||||
|
"https://api.zoom.us/v2/users/me/meetings", headers=headers, data=json.dumps(payload)
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 201:
|
||||||
|
data = json.loads(response.text)
|
||||||
|
payload.update(
|
||||||
{
|
{
|
||||||
"doctype": "LMS Message",
|
"doctype": "LMS Live Class",
|
||||||
"batch": batch,
|
"start_url": data.get("start_url"),
|
||||||
"author": frappe.session.user,
|
"join_url": data.get("join_url"),
|
||||||
"message": message,
|
"title": title,
|
||||||
|
"host": frappe.session.user,
|
||||||
|
"date": date,
|
||||||
|
"time": time,
|
||||||
|
"batch_name": batch_name,
|
||||||
|
"password": data.get("password"),
|
||||||
|
"description": description,
|
||||||
|
"auto_recording": auto_recording,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
doc.save(ignore_permissions=True)
|
class_details = frappe.get_doc(payload)
|
||||||
|
class_details.save()
|
||||||
|
return class_details
|
||||||
|
|
||||||
|
|
||||||
def switch_batch(course_name, email, batch_name):
|
def authenticate():
|
||||||
"""Switches the user from the current batch of the course to a new batch."""
|
zoom = frappe.get_single("Zoom Settings")
|
||||||
membership = frappe.get_last_doc(
|
if not zoom.enable:
|
||||||
"LMS Batch Membership", filters={"course": course_name, "member": email}
|
frappe.throw(_("Please enable Zoom Settings to use this feature."))
|
||||||
|
|
||||||
|
authenticate_url = f"https://zoom.us/oauth/token?grant_type=account_credentials&account_id={zoom.account_id}"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": "Basic "
|
||||||
|
+ base64.b64encode(
|
||||||
|
bytes(
|
||||||
|
zoom.client_id
|
||||||
|
+ ":"
|
||||||
|
+ zoom.get_password(fieldname="client_secret", raise_exception=False),
|
||||||
|
encoding="utf8",
|
||||||
|
)
|
||||||
|
).decode()
|
||||||
|
}
|
||||||
|
response = requests.request("POST", authenticate_url, headers=headers)
|
||||||
|
return response.json()["access_token"]
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_batch(
|
||||||
|
title,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
description=None,
|
||||||
|
batch_details=None,
|
||||||
|
batch_details_raw=None,
|
||||||
|
meta_image=None,
|
||||||
|
seat_count=0,
|
||||||
|
start_time=None,
|
||||||
|
end_time=None,
|
||||||
|
medium="Online",
|
||||||
|
category=None,
|
||||||
|
paid_batch=0,
|
||||||
|
amount=0,
|
||||||
|
currency=None,
|
||||||
|
amount_usd=0,
|
||||||
|
name=None,
|
||||||
|
published=0,
|
||||||
|
evaluation_end_date=None,
|
||||||
|
):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
|
if name:
|
||||||
|
doc = frappe.get_doc("LMS Batch", name)
|
||||||
|
else:
|
||||||
|
doc = frappe.get_doc({"doctype": "LMS Batch"})
|
||||||
|
|
||||||
|
doc.update(
|
||||||
|
{
|
||||||
|
"title": title,
|
||||||
|
"start_date": start_date,
|
||||||
|
"end_date": end_date,
|
||||||
|
"description": description,
|
||||||
|
"batch_details": batch_details,
|
||||||
|
"batch_details_raw": batch_details_raw,
|
||||||
|
"meta_image": meta_image,
|
||||||
|
"seat_count": seat_count,
|
||||||
|
"start_time": start_time,
|
||||||
|
"end_time": end_time,
|
||||||
|
"medium": medium,
|
||||||
|
"category": category,
|
||||||
|
"paid_batch": paid_batch,
|
||||||
|
"amount": amount,
|
||||||
|
"currency": currency,
|
||||||
|
"amount_usd": amount_usd,
|
||||||
|
"published": published,
|
||||||
|
"evaluation_end_date": evaluation_end_date,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
doc.save()
|
||||||
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def fetch_lessons(courses):
|
||||||
|
lessons = []
|
||||||
|
courses = json.loads(courses)
|
||||||
|
|
||||||
|
for course in courses:
|
||||||
|
lessons.extend(get_lessons(course.get("course")))
|
||||||
|
|
||||||
|
return lessons
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def add_course(course, parent, name=None, evaluator=None):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
|
|
||||||
|
if frappe.db.exists("Batch Course", {"course": course, "parent": parent}):
|
||||||
|
frappe.throw(_("Course already added to the batch."))
|
||||||
|
|
||||||
|
if name:
|
||||||
|
doc = frappe.get_doc("Batch Course", name)
|
||||||
|
else:
|
||||||
|
doc = frappe.new_doc("Batch Course")
|
||||||
|
|
||||||
|
doc.update(
|
||||||
|
{
|
||||||
|
"course": course,
|
||||||
|
"evaluator": evaluator,
|
||||||
|
"parent": parent,
|
||||||
|
"parentfield": "courses",
|
||||||
|
"parenttype": "LMS Batch",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
return doc.name
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_batch_timetable(batch):
|
||||||
|
timetable = frappe.get_all(
|
||||||
|
"LMS Batch Timetable",
|
||||||
|
filters={"parent": batch},
|
||||||
|
fields=[
|
||||||
|
"reference_doctype",
|
||||||
|
"reference_docname",
|
||||||
|
"date",
|
||||||
|
"start_time",
|
||||||
|
"end_time",
|
||||||
|
"milestone",
|
||||||
|
"name",
|
||||||
|
"idx",
|
||||||
|
"parent",
|
||||||
|
],
|
||||||
|
order_by="date",
|
||||||
)
|
)
|
||||||
|
|
||||||
batch = frappe.get_doc("LMS Batch", batch_name)
|
show_live_class = frappe.db.get_value("LMS Batch", batch, "show_live_class")
|
||||||
if not batch:
|
if show_live_class:
|
||||||
raise ValueError(f"Invalid Batch: {batch_name}")
|
live_classes = get_live_classes(batch)
|
||||||
|
timetable.extend(live_classes)
|
||||||
|
|
||||||
if batch.course != course_name:
|
timetable = get_timetable_details(timetable)
|
||||||
raise ValueError("Can not switch batches across courses")
|
return timetable
|
||||||
|
|
||||||
if batch.is_member(email):
|
|
||||||
print(f"{email} is already a member of {batch.title}")
|
|
||||||
return
|
|
||||||
|
|
||||||
old_batch = frappe.get_doc("LMS Batch", membership.batch)
|
def get_live_classes(batch):
|
||||||
|
live_classes = frappe.get_all(
|
||||||
|
"LMS Live Class",
|
||||||
|
{"batch_name": batch},
|
||||||
|
["name", "title", "date", "time as start_time", "duration", "join_url as url"],
|
||||||
|
order_by="date",
|
||||||
|
)
|
||||||
|
for class_ in live_classes:
|
||||||
|
class_.end_time = class_.start_time + timedelta(minutes=class_.duration)
|
||||||
|
class_.reference_doctype = "LMS Live Class"
|
||||||
|
class_.reference_docname = class_.name
|
||||||
|
class_.icon = "icon-call"
|
||||||
|
|
||||||
print("updating membership", membership.name)
|
return live_classes
|
||||||
membership.batch = batch_name
|
|
||||||
membership.save()
|
|
||||||
|
|
||||||
# update exercise submissions
|
|
||||||
filters = {"owner": email, "batch": old_batch.name}
|
def get_timetable_details(timetable):
|
||||||
for name in frappe.db.get_all("Exercise Submission", filters=filters, pluck="name"):
|
for entry in timetable:
|
||||||
doc = frappe.get_doc("Exercise Submission", name)
|
entry.title = frappe.db.get_value(
|
||||||
print("updating exercise submission", name)
|
entry.reference_doctype, entry.reference_docname, "title"
|
||||||
doc.batch = batch_name
|
)
|
||||||
doc.save()
|
assessment = frappe._dict({"assessment_name": entry.reference_docname})
|
||||||
|
|
||||||
|
if entry.reference_doctype == "Course Lesson":
|
||||||
|
course = frappe.db.get_value(
|
||||||
|
entry.reference_doctype, entry.reference_docname, "course"
|
||||||
|
)
|
||||||
|
entry.url = get_lesson_url(course, get_lesson_index(entry.reference_docname))
|
||||||
|
|
||||||
|
entry.completed = (
|
||||||
|
True
|
||||||
|
if frappe.db.exists(
|
||||||
|
"LMS Course Progress",
|
||||||
|
{"lesson": entry.reference_docname, "member": frappe.session.user},
|
||||||
|
)
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
|
elif entry.reference_doctype == "LMS Quiz":
|
||||||
|
entry.url = "/quizzes"
|
||||||
|
details = get_quiz_details(assessment, frappe.session.user)
|
||||||
|
entry.update(details)
|
||||||
|
|
||||||
|
elif entry.reference_doctype == "LMS Assignment":
|
||||||
|
details = get_assignment_details(assessment, frappe.session.user)
|
||||||
|
entry.update(details)
|
||||||
|
|
||||||
|
timetable = sorted(timetable, key=lambda k: k["date"])
|
||||||
|
return timetable
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def is_milestone_complete(idx, batch):
|
||||||
|
previous_rows = frappe.get_all(
|
||||||
|
"LMS Batch Timetable",
|
||||||
|
filters={"parent": batch, "idx": ["<", cint(idx)]},
|
||||||
|
fields=["reference_doctype", "reference_docname", "idx"],
|
||||||
|
order_by="idx",
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in previous_rows:
|
||||||
|
if row.reference_doctype == "Course Lesson":
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"LMS Course Progress",
|
||||||
|
{"member": frappe.session.user, "lesson": row.reference_docname},
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if row.reference_doctype == "LMS Quiz":
|
||||||
|
passing_percentage = frappe.db.get_value(
|
||||||
|
row.reference_doctype, row.reference_docname, "passing_percentage"
|
||||||
|
)
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"LMS Quiz Submission",
|
||||||
|
{"quiz": row.reference_docname, "member": frappe.session.user},
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if row.reference_doctype == "LMS Assignment":
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"LMS Assignment Submission",
|
||||||
|
{"assignment": row.reference_docname, "member": frappe.session.user},
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Copyright (c) 2021, FOSS United and Contributors
|
# Copyright (c) 2022, Frappe and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestLMSBatch(unittest.TestCase):
|
class TestLMSClass(FrappeTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2021, Frappe and contributors
|
// Copyright (c) 2021, FOSS United and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Lesson Assignment", {
|
frappe.ui.form.on("LMS Batch Old", {
|
||||||
// refresh: function(frm) {
|
// refresh: function(frm) {
|
||||||
// }
|
// }
|
||||||
});
|
});
|
||||||
150
lms/lms/doctype/lms_batch_old/lms_batch_old.json
Normal file
150
lms/lms/doctype/lms_batch_old/lms_batch_old.json
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "format: BATCH-{#####}",
|
||||||
|
"creation": "2021-03-18 19:37:34.614796",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"course",
|
||||||
|
"start_date",
|
||||||
|
"start_time",
|
||||||
|
"column_break_3",
|
||||||
|
"title",
|
||||||
|
"sessions_on",
|
||||||
|
"end_time",
|
||||||
|
"section_break_5",
|
||||||
|
"description",
|
||||||
|
"section_break_7",
|
||||||
|
"visibility",
|
||||||
|
"membership",
|
||||||
|
"column_break_9",
|
||||||
|
"status",
|
||||||
|
"stage"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "course",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Course",
|
||||||
|
"options": "LMS Course",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Title",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Markdown Editor",
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Public",
|
||||||
|
"fieldname": "visibility",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Visibility",
|
||||||
|
"options": "Public\nUnlisted\nPrivate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "membership",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Membership",
|
||||||
|
"options": "\nOpen\nRestricted\nInvite Only\nClosed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Active",
|
||||||
|
"fieldname": "status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Status",
|
||||||
|
"options": "Active\nInactive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Ready",
|
||||||
|
"fieldname": "stage",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Stage",
|
||||||
|
"options": "Ready\nIn Progress\nCompleted\nCancelled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_5",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Batch Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_9",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_7",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Batch Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "start_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Start Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "start_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Start Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sessions_on",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Sessions On Days"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "end_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "End Time"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"group": "Members",
|
||||||
|
"link_doctype": "LMS Enrollment",
|
||||||
|
"link_fieldname": "batch_old"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2022-09-28 18:43:22.955907",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Batch Old",
|
||||||
|
"naming_rule": "Expression",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
92
lms/lms/doctype/lms_batch_old/lms_batch_old.py
Normal file
92
lms/lms/doctype/lms_batch_old/lms_batch_old.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Copyright (c) 2021, FOSS United and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
from lms.lms.doctype.lms_enrollment.lms_enrollment import create_membership
|
||||||
|
from lms.lms.utils import is_mentor
|
||||||
|
|
||||||
|
|
||||||
|
class LMSBatchOld(Document):
|
||||||
|
def validate(self):
|
||||||
|
pass
|
||||||
|
# self.validate_if_mentor()
|
||||||
|
|
||||||
|
def validate_if_mentor(self):
|
||||||
|
if not is_mentor(self.course, frappe.session.user):
|
||||||
|
course_title = frappe.db.get_value("LMS Course", self.course, "title")
|
||||||
|
frappe.throw(_("You are not a mentor of the course {0}").format(course_title))
|
||||||
|
|
||||||
|
def after_insert(self):
|
||||||
|
create_membership(batch=self.name, course=self.course, member_type="Mentor")
|
||||||
|
|
||||||
|
def is_member(self, email, member_type=None):
|
||||||
|
"""Checks if a person is part of a batch.
|
||||||
|
|
||||||
|
If member_type is specified, checks if the person is a Student/Mentor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
filters = {"batch_old": self.name, "member": email}
|
||||||
|
if member_type:
|
||||||
|
filters["member_type"] = member_type
|
||||||
|
return frappe.db.exists("LMS Enrollment", filters)
|
||||||
|
|
||||||
|
def get_membership(self, email):
|
||||||
|
"""Returns the membership document of given user."""
|
||||||
|
name = frappe.get_value(
|
||||||
|
doctype="LMS Enrollment",
|
||||||
|
filters={"batch_old": self.name, "member": email},
|
||||||
|
fieldname="name",
|
||||||
|
)
|
||||||
|
return frappe.get_doc("LMS Enrollment", name)
|
||||||
|
|
||||||
|
def get_current_lesson(self, user):
|
||||||
|
"""Returns the name of the current lesson for the given user."""
|
||||||
|
membership = self.get_membership(user)
|
||||||
|
return membership and membership.current_lesson
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def save_message(message, batch):
|
||||||
|
doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "LMS Message",
|
||||||
|
"batch_old": batch,
|
||||||
|
"author": frappe.session.user,
|
||||||
|
"message": message,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
|
def switch_batch(course_name, email, batch_name):
|
||||||
|
"""Switches the user from the current batch of the course to a new batch."""
|
||||||
|
membership = frappe.get_last_doc(
|
||||||
|
"LMS Enrollment", filters={"course": course_name, "member": email}
|
||||||
|
)
|
||||||
|
|
||||||
|
batch = frappe.get_doc("LMS Batch Old", batch_name)
|
||||||
|
if not batch:
|
||||||
|
raise ValueError(f"Invalid Batch: {batch_name}")
|
||||||
|
|
||||||
|
if batch.course != course_name:
|
||||||
|
raise ValueError("Can not switch batches across courses")
|
||||||
|
|
||||||
|
if batch.is_member(email):
|
||||||
|
print(f"{email} is already a member of {batch.title}")
|
||||||
|
return
|
||||||
|
|
||||||
|
old_batch = frappe.get_doc("LMS Batch Old", membership.batch_old)
|
||||||
|
|
||||||
|
membership.batch_old = batch_name
|
||||||
|
membership.save()
|
||||||
|
|
||||||
|
# update exercise submissions
|
||||||
|
filters = {"owner": email, "batch_old": old_batch.name}
|
||||||
|
for name in frappe.db.get_all("Exercise Submission", filters=filters, pluck="name"):
|
||||||
|
doc = frappe.get_doc("Exercise Submission", name)
|
||||||
|
print("updating exercise submission", name)
|
||||||
|
doc.batch_old = batch_name
|
||||||
|
doc.save()
|
||||||
9
lms/lms/doctype/lms_batch_old/test_lms_batch_old.py
Normal file
9
lms/lms/doctype/lms_batch_old/test_lms_batch_old.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2021, FOSS United and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSBatchOld(unittest.TestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("LMS Batch Timetable", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
93
lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.json
Normal file
93
lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.json
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "hash",
|
||||||
|
"creation": "2023-09-14 12:44:51.098956",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"column_break_htdc",
|
||||||
|
"reference_doctype",
|
||||||
|
"reference_docname",
|
||||||
|
"date",
|
||||||
|
"day",
|
||||||
|
"column_break_merq",
|
||||||
|
"start_time",
|
||||||
|
"end_time",
|
||||||
|
"duration",
|
||||||
|
"milestone"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "reference_doctype",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Reference DocType",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_docname",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Reference DocName",
|
||||||
|
"options": "reference_doctype"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_merq",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.parenttype == \"LMS Batch\";",
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "start_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Start Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "duration",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_htdc",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "end_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "End Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.parenttype == \"LMS Timetable Template\";",
|
||||||
|
"fieldname": "day",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Day"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "milestone",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Milestone"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-10-20 11:58:01.782921",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Batch Timetable",
|
||||||
|
"naming_rule": "Random",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LMSBatchTimetable(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSBatchTimetable(FrappeTestCase):
|
||||||
|
pass
|
||||||
0
lms/lms/doctype/lms_category/__init__.py
Normal file
0
lms/lms/doctype/lms_category/__init__.py
Normal file
8
lms/lms/doctype/lms_category/lms_category.js
Normal file
8
lms/lms/doctype/lms_category/lms_category.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("LMS Category", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
59
lms/lms/doctype/lms_category/lms_category.json
Normal file
59
lms/lms/doctype/lms_category/lms_category.json
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:category",
|
||||||
|
"creation": "2023-06-15 12:40:36.484165",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"category"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "category",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Category",
|
||||||
|
"unique": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-06-15 15:14:11.341961",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Category",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "category"
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# Copyright (c) 2022, Frappe and contributors
|
# Copyright (c) 2023, Frappe and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class ClassStudent(Document):
|
class LMSCategory(Document):
|
||||||
pass
|
pass
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# Copyright (c) 2022, Frappe and Contributors
|
# Copyright (c) 2023, Frappe and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestClassStudent(FrappeTestCase):
|
class TestLMSCategory(FrappeTestCase):
|
||||||
pass
|
pass
|
||||||
@@ -10,6 +10,14 @@ frappe.ui.form.on("LMS Certificate", {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("template", function (doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
doc_type: "LMS Certificate",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
refresh: (frm) => {
|
refresh: (frm) => {
|
||||||
if (frm.doc.name)
|
if (frm.doc.name)
|
||||||
|
|||||||
@@ -8,9 +8,12 @@
|
|||||||
"course",
|
"course",
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
|
"template",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"issue_date",
|
"issue_date",
|
||||||
"expiry_date"
|
"expiry_date",
|
||||||
|
"batch_name",
|
||||||
|
"published"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -52,11 +55,31 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Member Name",
|
"label": "Member Name",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Batch",
|
||||||
|
"options": "LMS Batch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "published",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Publish on Participant Page"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Template",
|
||||||
|
"options": "Print Format",
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-14 12:33:37.839625",
|
"modified": "2023-10-25 12:20:56.091979",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Certificate",
|
"name": "LMS Certificate",
|
||||||
@@ -73,6 +96,18 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
|||||||
@@ -6,15 +6,46 @@ from frappe import _
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import add_years, nowdate
|
from frappe.utils import add_years, nowdate
|
||||||
from lms.lms.utils import is_certified
|
from lms.lms.utils import is_certified
|
||||||
|
from frappe.email.doctype.email_template.email_template import get_email_template
|
||||||
|
|
||||||
|
|
||||||
class LMSCertificate(Document):
|
class LMSCertificate(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_duplicate_certificate()
|
self.validate_duplicate_certificate()
|
||||||
|
|
||||||
|
def after_insert(self):
|
||||||
|
if not frappe.flags.in_test:
|
||||||
|
self.send_mail()
|
||||||
|
|
||||||
|
def send_mail(self):
|
||||||
|
subject = _("Congratulations on getting certified!")
|
||||||
|
template = "certification"
|
||||||
|
custom_template = frappe.db.get_single_value("LMS Settings", "certification_template")
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"student_name": self.member_name,
|
||||||
|
"course_name": self.course,
|
||||||
|
"course_title": frappe.db.get_value("LMS Course", self.course, "title"),
|
||||||
|
"certificate_name": self.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if custom_template:
|
||||||
|
email_template = get_email_template(custom_template, args)
|
||||||
|
subject = email_template.get("subject")
|
||||||
|
content = email_template.get("message")
|
||||||
|
frappe.sendmail(
|
||||||
|
recipients=self.member,
|
||||||
|
subject=subject,
|
||||||
|
template=template if not custom_template else None,
|
||||||
|
content=content if custom_template else None,
|
||||||
|
args=args,
|
||||||
|
header=[subject, "green"],
|
||||||
|
)
|
||||||
|
|
||||||
def validate_duplicate_certificate(self):
|
def validate_duplicate_certificate(self):
|
||||||
certificates = frappe.get_all(
|
certificates = frappe.get_all(
|
||||||
"LMS Certificate", {"member": self.member, "course": self.course}
|
"LMS Certificate",
|
||||||
|
{"member": self.member, "course": self.course, "name": ["!=", self.name]},
|
||||||
)
|
)
|
||||||
if len(certificates):
|
if len(certificates):
|
||||||
full_name = frappe.db.get_value("User", self.member, "full_name")
|
full_name = frappe.db.get_value("User", self.member, "full_name")
|
||||||
@@ -23,17 +54,15 @@ class LMSCertificate(Document):
|
|||||||
_("{0} is already certified for the course {1}").format(full_name, course_name)
|
_("{0} is already certified for the course {1}").format(full_name, course_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
def after_insert(self):
|
def on_update(self):
|
||||||
share = frappe.get_doc(
|
frappe.share.add_docshare(
|
||||||
{
|
self.doctype,
|
||||||
"doctype": "DocShare",
|
self.name,
|
||||||
"read": 1,
|
self.member,
|
||||||
"share_doctype": "LMS Certificate",
|
write=1,
|
||||||
"share_name": self.name,
|
share=1,
|
||||||
"user": self.member,
|
flags={"ignore_share_permission": True},
|
||||||
}
|
|
||||||
)
|
)
|
||||||
share.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -49,6 +78,15 @@ def create_certificate(course):
|
|||||||
if expires_after_yrs:
|
if expires_after_yrs:
|
||||||
expiry_date = add_years(nowdate(), expires_after_yrs)
|
expiry_date = add_years(nowdate(), expires_after_yrs)
|
||||||
|
|
||||||
|
default_certificate_template = frappe.db.get_value(
|
||||||
|
"Property Setter",
|
||||||
|
{
|
||||||
|
"doc_type": "LMS Certificate",
|
||||||
|
"property": "default_print_format",
|
||||||
|
},
|
||||||
|
"value",
|
||||||
|
)
|
||||||
|
|
||||||
certificate = frappe.get_doc(
|
certificate = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "LMS Certificate",
|
"doctype": "LMS Certificate",
|
||||||
@@ -56,6 +94,7 @@ def create_certificate(course):
|
|||||||
"course": course,
|
"course": course,
|
||||||
"issue_date": nowdate(),
|
"issue_date": nowdate(),
|
||||||
"expiry_date": expiry_date,
|
"expiry_date": expiry_date,
|
||||||
|
"template": default_certificate_template,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
certificate.save(ignore_permissions=True)
|
certificate.save(ignore_permissions=True)
|
||||||
|
|||||||
@@ -8,16 +8,16 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
"column_break_5",
|
|
||||||
"status",
|
|
||||||
"course",
|
"course",
|
||||||
"class",
|
"column_break_5",
|
||||||
"section_break_6",
|
|
||||||
"date",
|
"date",
|
||||||
"start_time",
|
"start_time",
|
||||||
"end_time",
|
"end_time",
|
||||||
"column_break_10",
|
"batch_name",
|
||||||
|
"section_break_6",
|
||||||
"rating",
|
"rating",
|
||||||
|
"status",
|
||||||
|
"column_break_10",
|
||||||
"summary"
|
"summary"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -47,12 +47,13 @@
|
|||||||
"fieldtype": "Rating",
|
"fieldtype": "Rating",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rating",
|
"label": "Rating",
|
||||||
"reqd": 1
|
"mandatory_depends_on": "eval:doc.status != 'Pending' && doc.status != 'In Progress'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "summary",
|
"fieldname": "summary",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Summary"
|
"label": "Summary",
|
||||||
|
"mandatory_depends_on": "eval:doc.status != 'Pending' && doc.status != 'In Progress'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "date",
|
"fieldname": "date",
|
||||||
@@ -84,27 +85,29 @@
|
|||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "Pass\nFail",
|
"options": "Pending\nIn Progress\nPass\nFail",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_6",
|
"fieldname": "section_break_6",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Evaluation Details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_10",
|
"fieldname": "column_break_10",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "class",
|
"fieldname": "batch_name",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Class",
|
"in_standard_filter": 1,
|
||||||
"options": "LMS Class"
|
"label": "Batch Name",
|
||||||
|
"options": "LMS Batch"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-02-22 16:00:34.361934",
|
"modified": "2023-12-18 20:03:27.040073",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Certificate Evaluation",
|
"name": "LMS Certificate Evaluation",
|
||||||
@@ -121,10 +124,39 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Class Evaluator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [
|
||||||
|
{
|
||||||
|
"color": "Green",
|
||||||
|
"title": "Pass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Red",
|
||||||
|
"title": "Fail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Blue",
|
||||||
|
"title": "Pending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "Orange",
|
||||||
|
"title": "In Progress"
|
||||||
|
}
|
||||||
|
],
|
||||||
"title_field": "member_name"
|
"title_field": "member_name"
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,19 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from lms.lms.utils import has_course_moderator_role
|
from lms.lms.utils import has_course_moderator_role
|
||||||
|
|
||||||
|
|
||||||
class LMSCertificateEvaluation(Document):
|
class LMSCertificateEvaluation(Document):
|
||||||
pass
|
def validate(self):
|
||||||
|
self.validate_rating()
|
||||||
|
|
||||||
|
def validate_rating(self):
|
||||||
|
if self.status not in ["Pending", "In Progress"] and self.rating == 0:
|
||||||
|
frappe.throw(_("Rating cannot be 0"))
|
||||||
|
|
||||||
|
|
||||||
def has_website_permission(doc, ptype, user, verbose=False):
|
def has_website_permission(doc, ptype, user, verbose=False):
|
||||||
|
|||||||
@@ -8,12 +8,14 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"course",
|
"course",
|
||||||
"evaluator",
|
"evaluator",
|
||||||
|
"batch_name",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
"section_break_lifi",
|
"section_break_lifi",
|
||||||
"date",
|
"date",
|
||||||
"day",
|
"day",
|
||||||
|
"google_meet_link",
|
||||||
"column_break_ddyh",
|
"column_break_ddyh",
|
||||||
"start_time",
|
"start_time",
|
||||||
"end_time"
|
"end_time"
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "course.evaluator",
|
"fetch_from": "course.evaluator",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "evaluator",
|
"fieldname": "evaluator",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Evaluator",
|
"label": "Evaluator",
|
||||||
@@ -89,11 +92,24 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_ddyh",
|
"fieldname": "column_break_ddyh",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "google_meet_link",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Google Meet Link",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Batch",
|
||||||
|
"options": "LMS Batch"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-02-28 19:53:17.534351",
|
"modified": "2023-11-29 15:00:30.617298",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Certificate Request",
|
"name": "LMS Certificate Request",
|
||||||
@@ -111,6 +127,30 @@
|
|||||||
"select": 1,
|
"select": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Class Evaluator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
|||||||
@@ -5,26 +5,37 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils import format_date, format_time, getdate
|
from frappe.utils import format_date, format_time, getdate, add_to_date, get_datetime
|
||||||
|
from lms.lms.utils import get_evaluator
|
||||||
|
|
||||||
|
|
||||||
class LMSCertificateRequest(Document):
|
class LMSCertificateRequest(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_slot()
|
||||||
self.validate_if_existing_requests()
|
self.validate_if_existing_requests()
|
||||||
|
self.validate_evaluation_end_date()
|
||||||
|
|
||||||
def after_insert(self):
|
def validate_slot(self):
|
||||||
if frappe.db.get_single_value("LMS Settings", "send_calendar_invite_for_evaluations"):
|
if frappe.db.exists(
|
||||||
self.create_event()
|
"LMS Certificate Request",
|
||||||
|
{
|
||||||
|
"evaluator": self.evaluator,
|
||||||
|
"date": self.date,
|
||||||
|
"start_time": self.start_time,
|
||||||
|
},
|
||||||
|
):
|
||||||
|
frappe.throw(_("The slot is already booked by another participant."))
|
||||||
|
|
||||||
def validate_if_existing_requests(self):
|
def validate_if_existing_requests(self):
|
||||||
existing_requests = frappe.get_all(
|
existing_requests = frappe.get_all(
|
||||||
"LMS Certificate Request",
|
"LMS Certificate Request",
|
||||||
{"member": self.member, "course": self.course},
|
{"member": self.member, "course": self.course, "name": ["!=", self.name]},
|
||||||
["date", "start_time", "course"],
|
["date", "start_time", "course"],
|
||||||
)
|
)
|
||||||
|
|
||||||
for req in existing_requests:
|
for req in existing_requests:
|
||||||
if req.date == getdate(self.date) and getdate() <= getdate(self.date):
|
|
||||||
|
if req.date == getdate(self.date) or getdate() <= getdate(req.date):
|
||||||
course_title = frappe.db.get_value("LMS Course", req.course, "title")
|
course_title = frappe.db.get_value("LMS Course", req.course, "title")
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("You already have an evaluation on {0} at {1} for the course {2}.").format(
|
_("You already have an evaluation on {0} at {1} for the course {2}.").format(
|
||||||
@@ -34,23 +45,59 @@ class LMSCertificateRequest(Document):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_event(self):
|
def validate_evaluation_end_date(self):
|
||||||
|
if self.batch_name:
|
||||||
|
evaluation_end_date = frappe.db.get_value(
|
||||||
|
"LMS Batch", self.batch_name, "evaluation_end_date"
|
||||||
|
)
|
||||||
|
|
||||||
|
if evaluation_end_date:
|
||||||
|
if getdate(self.date) > getdate(evaluation_end_date):
|
||||||
|
frappe.throw(
|
||||||
|
_("You cannot schedule evaluations after {0}.").format(
|
||||||
|
format_date(evaluation_end_date, "medium")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def schedule_evals():
|
||||||
|
if frappe.db.get_single_value("LMS Settings", "send_calendar_invite_for_evaluations"):
|
||||||
|
one_hour_ago = add_to_date(get_datetime(), hours=-1)
|
||||||
|
evals = frappe.get_all(
|
||||||
|
"LMS Certificate Request",
|
||||||
|
{"creation": [">=", one_hour_ago], "google_meet_link": ["is", "not set"]},
|
||||||
|
["name", "member", "member_name", "evaluator", "date", "start_time", "end_time"],
|
||||||
|
)
|
||||||
|
for eval in evals:
|
||||||
|
setup_calendar_event(eval)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_calendar_event(eval):
|
||||||
calendar = frappe.db.get_value(
|
calendar = frappe.db.get_value(
|
||||||
"Google Calendar", {"user": self.evaluator, "enable": 1}, "name"
|
"Google Calendar", {"user": eval.evaluator, "enable": 1}, "name"
|
||||||
)
|
)
|
||||||
|
|
||||||
if calendar:
|
if calendar:
|
||||||
|
event = create_event(eval)
|
||||||
|
add_participants(eval, event)
|
||||||
|
update_meeting_details(eval, event, calendar)
|
||||||
|
|
||||||
|
|
||||||
|
def create_event(eval):
|
||||||
event = frappe.get_doc(
|
event = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Event",
|
"doctype": "Event",
|
||||||
"subject": f"Evaluation of {self.member_name}",
|
"subject": f"Evaluation of {eval.member_name}",
|
||||||
"starts_on": f"{self.date} {self.start_time}",
|
"starts_on": f"{eval.date} {eval.start_time}",
|
||||||
"ends_on": f"{self.date} {self.end_time}",
|
"ends_on": f"{eval.date} {eval.end_time}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
event.save()
|
event.save()
|
||||||
|
return event
|
||||||
|
|
||||||
participants = [self.member, self.evaluator]
|
|
||||||
|
def add_participants(eval, event):
|
||||||
|
participants = [eval.member, eval.evaluator]
|
||||||
for participant in participants:
|
for participant in participants:
|
||||||
contact_name = frappe.db.get_value("Contact", {"email_id": participant}, "name")
|
contact_name = frappe.db.get_value("Contact", {"email_id": participant}, "name")
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
@@ -65,6 +112,8 @@ class LMSCertificateRequest(Document):
|
|||||||
}
|
}
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
|
|
||||||
|
def update_meeting_details(eval, event, calendar):
|
||||||
event.reload()
|
event.reload()
|
||||||
event.update(
|
event.update(
|
||||||
{
|
{
|
||||||
@@ -75,28 +124,36 @@ class LMSCertificateRequest(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
event.save()
|
event.save()
|
||||||
|
event.reload()
|
||||||
|
frappe.db.set_value(
|
||||||
|
"LMS Certificate Request", eval.name, "google_meet_link", event.google_meet_link
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_certificate_request(course, date, day, start_time, end_time):
|
def create_certificate_request(
|
||||||
|
course, date, day, start_time, end_time, batch_name=None
|
||||||
|
):
|
||||||
is_member = frappe.db.exists(
|
is_member = frappe.db.exists(
|
||||||
{"doctype": "LMS Batch Membership", "course": course, "member": frappe.session.user}
|
{"doctype": "LMS Enrollment", "course": course, "member": frappe.session.user}
|
||||||
)
|
)
|
||||||
|
|
||||||
if not is_member:
|
if not is_member:
|
||||||
return
|
return
|
||||||
|
eval = frappe.new_doc("LMS Certificate Request")
|
||||||
frappe.get_doc(
|
eval.update(
|
||||||
{
|
{
|
||||||
"doctype": "LMS Certificate Request",
|
|
||||||
"course": course,
|
"course": course,
|
||||||
|
"evaluator": get_evaluator(course, batch_name),
|
||||||
"member": frappe.session.user,
|
"member": frappe.session.user,
|
||||||
"date": date,
|
"date": date,
|
||||||
"day": day,
|
"day": day,
|
||||||
"start_time": start_time,
|
"start_time": start_time,
|
||||||
"end_time": end_time,
|
"end_time": end_time,
|
||||||
|
"batch_name": batch_name,
|
||||||
}
|
}
|
||||||
).save(ignore_permissions=True)
|
)
|
||||||
|
eval.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_rename": 1,
|
|
||||||
"autoname": "format: CLS-{#####}",
|
|
||||||
"creation": "2022-11-09 16:14:05.876933",
|
|
||||||
"default_view": "List",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"editable_grid": 1,
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"title",
|
|
||||||
"start_date",
|
|
||||||
"end_date",
|
|
||||||
"paid_class",
|
|
||||||
"column_break_4",
|
|
||||||
"seat_count",
|
|
||||||
"start_time",
|
|
||||||
"end_time",
|
|
||||||
"section_break_6",
|
|
||||||
"description",
|
|
||||||
"students",
|
|
||||||
"courses",
|
|
||||||
"custom_component"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "title",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Title",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "end_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "End Date",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_4",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "description",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Description"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "section_break_6",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "students",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"label": "Students",
|
|
||||||
"options": "Class Student"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "courses",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"label": "Courses",
|
|
||||||
"options": "Class Course"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "start_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Start Date",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "The HTML code entered here will be displayed on the class details page.",
|
|
||||||
"fieldname": "custom_component",
|
|
||||||
"fieldtype": "Code",
|
|
||||||
"label": "Custom Component",
|
|
||||||
"options": "HTML"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "paid_class",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Paid Class"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "seat_count",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"label": "Seat Count"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "start_time",
|
|
||||||
"fieldtype": "Time",
|
|
||||||
"label": "Start Time"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "end_time",
|
|
||||||
"fieldtype": "Time",
|
|
||||||
"label": "End Time"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"index_web_pages_for_search": 1,
|
|
||||||
"links": [],
|
|
||||||
"modified": "2023-05-03 23:07:06.725720",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "LMS",
|
|
||||||
"name": "LMS Class",
|
|
||||||
"naming_rule": "Expression (old style)",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Moderator",
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"states": [],
|
|
||||||
"title_field": "title"
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
# Copyright (c) 2022, Frappe and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe import _
|
|
||||||
from frappe.utils import cint, format_date, format_datetime
|
|
||||||
import requests
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class LMSClass(Document):
|
|
||||||
def validate(self):
|
|
||||||
if self.seat_count:
|
|
||||||
self.validate_seats_left()
|
|
||||||
self.validate_duplicate_students()
|
|
||||||
self.validate_membership()
|
|
||||||
|
|
||||||
def validate_duplicate_students(self):
|
|
||||||
students = [row.student for row in self.students]
|
|
||||||
duplicates = {student for student in students if students.count(student) > 1}
|
|
||||||
if len(duplicates):
|
|
||||||
frappe.throw(
|
|
||||||
_("Student {0} has already been added to this class.").format(
|
|
||||||
frappe.bold(next(iter(duplicates)))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_membership(self):
|
|
||||||
for course in self.courses:
|
|
||||||
for student in self.students:
|
|
||||||
filters = {
|
|
||||||
"doctype": "LMS Batch Membership",
|
|
||||||
"member": student.student,
|
|
||||||
"course": course.course,
|
|
||||||
}
|
|
||||||
if not frappe.db.exists(filters):
|
|
||||||
frappe.get_doc(filters).save()
|
|
||||||
|
|
||||||
def validate_seats_left(self):
|
|
||||||
if cint(self.seat_count) < len(self.students):
|
|
||||||
frappe.throw(_("There are no seats available in this class."))
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def add_student(email, class_name):
|
|
||||||
if not frappe.db.exists("User", email):
|
|
||||||
frappe.throw(_("There is no such user. Please create a user with this Email ID."))
|
|
||||||
|
|
||||||
filters = {
|
|
||||||
"student": email,
|
|
||||||
"parent": class_name,
|
|
||||||
"parenttype": "LMS Class",
|
|
||||||
"parentfield": "students",
|
|
||||||
}
|
|
||||||
if frappe.db.exists("Class Student", filters):
|
|
||||||
frappe.throw(
|
|
||||||
_("Student {0} has already been added to this class.").format(frappe.bold(email))
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Class Student",
|
|
||||||
"student": email,
|
|
||||||
"student_name": frappe.db.get_value("User", email, "full_name"),
|
|
||||||
"parent": class_name,
|
|
||||||
"parenttype": "LMS Class",
|
|
||||||
"parentfield": "students",
|
|
||||||
}
|
|
||||||
).save()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def remove_student(student, class_name):
|
|
||||||
frappe.db.delete("Class Student", {"student": student, "parent": class_name})
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def remove_course(course, parent):
|
|
||||||
frappe.db.delete("Class Course", {"course": course, "parent": parent})
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def create_live_class(
|
|
||||||
class_name, title, duration, date, time, timezone, auto_recording, description=None
|
|
||||||
):
|
|
||||||
date = format_date(date, "yyyy-mm-dd", True)
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"topic": title,
|
|
||||||
"start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"),
|
|
||||||
"duration": duration,
|
|
||||||
"agenda": description,
|
|
||||||
"private_meeting": True,
|
|
||||||
"auto_recording": "none"
|
|
||||||
if auto_recording == "No Recording"
|
|
||||||
else auto_recording.lower(),
|
|
||||||
"timezone": timezone,
|
|
||||||
}
|
|
||||||
headers = {
|
|
||||||
"Authorization": "Bearer " + authenticate(),
|
|
||||||
"content-type": "application/json",
|
|
||||||
}
|
|
||||||
response = requests.post(
|
|
||||||
"https://api.zoom.us/v2/users/me/meetings", headers=headers, data=json.dumps(payload)
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code == 201:
|
|
||||||
data = json.loads(response.text)
|
|
||||||
payload.update(
|
|
||||||
{
|
|
||||||
"doctype": "LMS Live Class",
|
|
||||||
"start_url": data.get("start_url"),
|
|
||||||
"join_url": data.get("join_url"),
|
|
||||||
"title": title,
|
|
||||||
"host": frappe.session.user,
|
|
||||||
"date": date,
|
|
||||||
"time": time,
|
|
||||||
"class_name": class_name,
|
|
||||||
"password": data.get("password"),
|
|
||||||
"description": description,
|
|
||||||
"auto_recording": auto_recording,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
class_details = frappe.get_doc(payload)
|
|
||||||
class_details.save()
|
|
||||||
return class_details
|
|
||||||
|
|
||||||
|
|
||||||
def authenticate():
|
|
||||||
zoom = frappe.get_single("Zoom Settings")
|
|
||||||
if not zoom.enable:
|
|
||||||
frappe.throw(_("Please enable Zoom Settings to use this feature."))
|
|
||||||
|
|
||||||
authenticate_url = f"https://zoom.us/oauth/token?grant_type=account_credentials&account_id={zoom.account_id}"
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
"Authorization": "Basic "
|
|
||||||
+ base64.b64encode(
|
|
||||||
bytes(
|
|
||||||
zoom.client_id
|
|
||||||
+ ":"
|
|
||||||
+ zoom.get_password(fieldname="client_secret", raise_exception=False),
|
|
||||||
encoding="utf8",
|
|
||||||
)
|
|
||||||
).decode()
|
|
||||||
}
|
|
||||||
response = requests.request("POST", authenticate_url, headers=headers)
|
|
||||||
return response.json()["access_token"]
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def create_class(
|
|
||||||
title,
|
|
||||||
start_date,
|
|
||||||
end_date,
|
|
||||||
description=None,
|
|
||||||
seat_count=0,
|
|
||||||
start_time=None,
|
|
||||||
end_time=None,
|
|
||||||
name=None,
|
|
||||||
):
|
|
||||||
if name:
|
|
||||||
class_details = frappe.get_doc("LMS Class", name)
|
|
||||||
else:
|
|
||||||
class_details = frappe.get_doc({"doctype": "LMS Class"})
|
|
||||||
|
|
||||||
class_details.update(
|
|
||||||
{
|
|
||||||
"title": title,
|
|
||||||
"start_date": start_date,
|
|
||||||
"end_date": end_date,
|
|
||||||
"description": description,
|
|
||||||
"seat_count": seat_count,
|
|
||||||
"start_time": start_time,
|
|
||||||
"end_time": end_time,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
class_details.save()
|
|
||||||
return class_details
|
|
||||||
@@ -11,14 +11,6 @@ frappe.ui.form.on("LMS Course", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("instructor", "instructors", function () {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
ignore_user_type: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_query("course", "related_courses", function () {
|
frm.set_query("course", "related_courses", function () {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@@ -29,5 +21,12 @@ frappe.ui.form.on("LMS Course", {
|
|||||||
},
|
},
|
||||||
refresh: (frm) => {
|
refresh: (frm) => {
|
||||||
frm.add_web_link(`/courses/${frm.doc.name}`, "See on Website");
|
frm.add_web_link(`/courses/${frm.doc.name}`, "See on Website");
|
||||||
|
|
||||||
|
if (!frm.doc.currency)
|
||||||
|
frappe.db
|
||||||
|
.get_single_value("LMS Settings", "default_currency")
|
||||||
|
.then((value) => {
|
||||||
|
frm.set_value("currency", value);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,19 +32,20 @@
|
|||||||
"description",
|
"description",
|
||||||
"chapters",
|
"chapters",
|
||||||
"related_courses",
|
"related_courses",
|
||||||
|
"pricing_section",
|
||||||
|
"paid_course",
|
||||||
|
"column_break_acoj",
|
||||||
|
"course_price",
|
||||||
|
"currency",
|
||||||
|
"amount_usd",
|
||||||
"certification_section",
|
"certification_section",
|
||||||
"enable_certification",
|
"enable_certification",
|
||||||
"expiry",
|
"expiry",
|
||||||
"section_break_23",
|
"max_attempts",
|
||||||
|
"column_break_rxww",
|
||||||
"grant_certificate_after",
|
"grant_certificate_after",
|
||||||
"evaluator",
|
"evaluator",
|
||||||
"column_break_26",
|
"duration"
|
||||||
"max_attempts",
|
|
||||||
"duration",
|
|
||||||
"pricing_section",
|
|
||||||
"paid_certificate",
|
|
||||||
"currency",
|
|
||||||
"price_certificate"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -170,13 +171,6 @@
|
|||||||
"fieldname": "column_break_12",
|
"fieldname": "column_break_12",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "enable_certification",
|
|
||||||
"fieldname": "paid_certificate",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Paid Certificate"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"depends_on": "enable_certification",
|
"depends_on": "enable_certification",
|
||||||
"fieldname": "grant_certificate_after",
|
"fieldname": "grant_certificate_after",
|
||||||
@@ -193,24 +187,16 @@
|
|||||||
"options": "Course Evaluator"
|
"options": "Course Evaluator"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.grant_certificate_after == \"Evaluation\"",
|
|
||||||
"fieldname": "pricing_section",
|
"fieldname": "pricing_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Pricing"
|
"label": "Pricing"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "paid_certificate",
|
"depends_on": "paid_course",
|
||||||
"fieldname": "price_certificate",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Certificate Price",
|
|
||||||
"mandatory_depends_on": "paid_certificate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "paid_certificate",
|
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Currency",
|
"label": "Currency",
|
||||||
"mandatory_depends_on": "paid_certificate",
|
"mandatory_depends_on": "paid_course",
|
||||||
"options": "Currency"
|
"options": "Currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -228,12 +214,32 @@
|
|||||||
"options": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12"
|
"options": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_23",
|
"default": "0",
|
||||||
"fieldtype": "Section Break"
|
"fieldname": "paid_course",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Paid Course"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_26",
|
"depends_on": "paid_course",
|
||||||
|
"fieldname": "course_price",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Course Price",
|
||||||
|
"mandatory_depends_on": "paid_course"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_rxww",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_acoj",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "paid_course",
|
||||||
|
"description": "If you set an amount here, then the USD equivalent setting will not get applied.",
|
||||||
|
"fieldname": "amount_usd",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount (USD)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_published_field": "published",
|
"is_published_field": "published",
|
||||||
@@ -245,7 +251,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Batches",
|
"group": "Batches",
|
||||||
"link_doctype": "LMS Batch",
|
"link_doctype": "LMS Batch Old",
|
||||||
"link_fieldname": "course"
|
"link_fieldname": "course"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -260,7 +266,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2023-05-11 17:08:19.763405",
|
"modified": "2023-12-21 12:27:32.559901",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Course",
|
"name": "LMS Course",
|
||||||
@@ -275,20 +281,18 @@
|
|||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"select": 1,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"create": 1,
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 1,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "All",
|
"role": "Course Creator",
|
||||||
"select": 1,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
from frappe.utils.telemetry import capture
|
from frappe.utils.telemetry import capture
|
||||||
from lms.lms.utils import get_chapters
|
from lms.lms.utils import get_chapters, can_create_courses
|
||||||
from ...utils import generate_slug, validate_image
|
from ...utils import generate_slug, validate_image
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
class LMSCourse(Document):
|
class LMSCourse(Document):
|
||||||
@@ -117,20 +118,18 @@ class LMSCourse(Document):
|
|||||||
return
|
return
|
||||||
|
|
||||||
batch_name = frappe.get_value(
|
batch_name = frappe.get_value(
|
||||||
doctype="LMS Batch Membership",
|
doctype="LMS Enrollment",
|
||||||
filters={"course": self.name, "member_type": "Student", "member": email},
|
filters={"course": self.name, "member_type": "Student", "member": email},
|
||||||
fieldname="batch",
|
fieldname="batch_old",
|
||||||
)
|
)
|
||||||
return batch_name and frappe.get_doc("LMS Batch", batch_name)
|
return batch_name and frappe.get_doc("LMS Batch Old", batch_name)
|
||||||
|
|
||||||
def get_batches(self, mentor=None):
|
def get_batches(self, mentor=None):
|
||||||
batches = frappe.get_all("LMS Batch", {"course": self.name})
|
batches = frappe.get_all("LMS Batch Old", {"course": self.name})
|
||||||
if mentor:
|
if mentor:
|
||||||
# TODO: optimize this
|
# TODO: optimize this
|
||||||
memberships = frappe.db.get_all(
|
memberships = frappe.db.get_all("LMS Enrollment", {"member": mentor}, ["batch_old"])
|
||||||
"LMS Batch Membership", {"member": mentor}, ["batch"]
|
batch_names = {m.batch_old for m in memberships}
|
||||||
)
|
|
||||||
batch_names = {m.batch for m in memberships}
|
|
||||||
return [b for b in batches if b.name in batch_names]
|
return [b for b in batches if b.name in batch_names]
|
||||||
|
|
||||||
def get_cohorts(self):
|
def get_cohorts(self):
|
||||||
@@ -160,10 +159,12 @@ class LMSCourse(Document):
|
|||||||
|
|
||||||
def get_all_memberships(self, member):
|
def get_all_memberships(self, member):
|
||||||
all_memberships = frappe.get_all(
|
all_memberships = frappe.get_all(
|
||||||
"LMS Batch Membership", {"member": member, "course": self.name}, ["batch"]
|
"LMS Enrollment", {"member": member, "course": self.name}, ["batch_old"]
|
||||||
)
|
)
|
||||||
for membership in all_memberships:
|
for membership in all_memberships:
|
||||||
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
|
membership.batch_title = frappe.db.get_value(
|
||||||
|
"LMS Batch Old", membership.batch_old, "title"
|
||||||
|
)
|
||||||
return all_memberships
|
return all_memberships
|
||||||
|
|
||||||
|
|
||||||
@@ -211,7 +212,13 @@ def save_course(
|
|||||||
published,
|
published,
|
||||||
upcoming,
|
upcoming,
|
||||||
image=None,
|
image=None,
|
||||||
|
paid_course=False,
|
||||||
|
course_price=None,
|
||||||
|
currency=None,
|
||||||
):
|
):
|
||||||
|
if not can_create_courses(course):
|
||||||
|
return
|
||||||
|
|
||||||
if course:
|
if course:
|
||||||
doc = frappe.get_doc("LMS Course", course)
|
doc = frappe.get_doc("LMS Course", course)
|
||||||
else:
|
else:
|
||||||
@@ -227,6 +234,9 @@ def save_course(
|
|||||||
"tags": tags,
|
"tags": tags,
|
||||||
"published": cint(published),
|
"published": cint(published),
|
||||||
"upcoming": cint(upcoming),
|
"upcoming": cint(upcoming),
|
||||||
|
"paid_course": cint(paid_course),
|
||||||
|
"course_price": course_price,
|
||||||
|
"currency": currency,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
doc.save(ignore_permissions=True)
|
doc.save(ignore_permissions=True)
|
||||||
@@ -270,6 +280,7 @@ def save_lesson(
|
|||||||
preview,
|
preview,
|
||||||
idx,
|
idx,
|
||||||
lesson,
|
lesson,
|
||||||
|
instructor_notes=None,
|
||||||
youtube=None,
|
youtube=None,
|
||||||
quiz_id=None,
|
quiz_id=None,
|
||||||
question=None,
|
question=None,
|
||||||
@@ -285,6 +296,7 @@ def save_lesson(
|
|||||||
"chapter": chapter,
|
"chapter": chapter,
|
||||||
"title": title,
|
"title": title,
|
||||||
"body": body,
|
"body": body,
|
||||||
|
"instructor_notes": instructor_notes,
|
||||||
"include_in_preview": preview,
|
"include_in_preview": preview,
|
||||||
"youtube": youtube,
|
"youtube": youtube,
|
||||||
"quiz_id": quiz_id,
|
"quiz_id": quiz_id,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class TestLMSCourse(unittest.TestCase):
|
|||||||
def test_new_course(self):
|
def test_new_course(self):
|
||||||
course = new_course("Test Course")
|
course = new_course("Test Course")
|
||||||
assert course.title == "Test Course"
|
assert course.title == "Test Course"
|
||||||
assert course.name == "test-course"
|
|
||||||
|
|
||||||
# disabled this test as it is failing
|
# disabled this test as it is failing
|
||||||
def _test_add_mentors(self):
|
def _test_add_mentors(self):
|
||||||
@@ -36,8 +35,10 @@ class TestLMSCourse(unittest.TestCase):
|
|||||||
frappe.db.delete("Exercise Submission", {"course": "test-course"})
|
frappe.db.delete("Exercise Submission", {"course": "test-course"})
|
||||||
frappe.db.delete("Exercise Latest Submission", {"course": "test-course"})
|
frappe.db.delete("Exercise Latest Submission", {"course": "test-course"})
|
||||||
frappe.db.delete("LMS Exercise", {"course": "test-course"})
|
frappe.db.delete("LMS Exercise", {"course": "test-course"})
|
||||||
frappe.db.delete("LMS Batch Membership", {"course": "test-course"})
|
frappe.db.delete("LMS Enrollment", {"course": "test-course"})
|
||||||
frappe.db.delete("LMS Batch", {"course": "test-course"})
|
frappe.db.delete("Course Lesson", {"course": "test-course"})
|
||||||
|
frappe.db.delete("Course Chapter", {"course": "test-course"})
|
||||||
|
frappe.db.delete("LMS Batch Old", {"course": "test-course"})
|
||||||
frappe.db.delete("LMS Course Mentor Mapping", {"course": "test-course"})
|
frappe.db.delete("LMS Course Mentor Mapping", {"course": "test-course"})
|
||||||
frappe.db.delete("Course Instructor", {"parent": "test-course"})
|
frappe.db.delete("Course Instructor", {"parent": "test-course"})
|
||||||
frappe.db.sql("delete from `tabCourse Instructor`")
|
frappe.db.sql("delete from `tabCourse Instructor`")
|
||||||
@@ -50,14 +51,14 @@ def new_user(name, email):
|
|||||||
return frappe.get_doc("User", user)
|
return frappe.get_doc("User", user)
|
||||||
else:
|
else:
|
||||||
filters = {
|
filters = {
|
||||||
"doctype": "User",
|
|
||||||
"email": email,
|
"email": email,
|
||||||
"first_name": name,
|
"first_name": name,
|
||||||
"send_welcome_email": False,
|
"send_welcome_email": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
doc = frappe.get_doc(filters)
|
doc = frappe.new_doc("User")
|
||||||
doc.insert()
|
doc.update(filters)
|
||||||
|
doc.save()
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
@@ -68,7 +69,6 @@ def new_course(title, additional_filters=None):
|
|||||||
else:
|
else:
|
||||||
create_evaluator()
|
create_evaluator()
|
||||||
filters = {
|
filters = {
|
||||||
"doctype": "LMS Course",
|
|
||||||
"title": title,
|
"title": title,
|
||||||
"short_introduction": title,
|
"short_introduction": title,
|
||||||
"description": title,
|
"description": title,
|
||||||
@@ -77,8 +77,9 @@ def new_course(title, additional_filters=None):
|
|||||||
if additional_filters:
|
if additional_filters:
|
||||||
filters.update(additional_filters)
|
filters.update(additional_filters)
|
||||||
|
|
||||||
doc = frappe.get_doc(filters)
|
doc = frappe.new_doc("LMS Course")
|
||||||
doc.insert(ignore_permissions=True)
|
doc.update(filters)
|
||||||
|
doc.save()
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
0
lms/lms/doctype/lms_enrollment/__init__.py
Normal file
0
lms/lms/doctype/lms_enrollment/__init__.py
Normal file
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2021, FOSS United and contributors
|
// Copyright (c) 2021, FOSS United and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("LMS Batch Membership", {
|
frappe.ui.form.on("LMS Enrollment", {
|
||||||
onload: function (frm) {
|
onload: function (frm) {
|
||||||
frm.set_query("member", function (doc) {
|
frm.set_query("member", function (doc) {
|
||||||
return {
|
return {
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"course",
|
"course",
|
||||||
"member_type",
|
"member_type",
|
||||||
"batch",
|
"payment",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"section_break_8",
|
"section_break_8",
|
||||||
"cohort",
|
"cohort",
|
||||||
"subgroup",
|
"subgroup",
|
||||||
|
"batch_old",
|
||||||
"column_break_12",
|
"column_break_12",
|
||||||
"current_lesson",
|
"current_lesson",
|
||||||
"progress",
|
"progress",
|
||||||
@@ -22,10 +23,10 @@
|
|||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "batch",
|
"fieldname": "batch_old",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Batch",
|
"label": "Batch Old",
|
||||||
"options": "LMS Batch"
|
"options": "LMS Batch Old"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "member",
|
"fieldname": "member",
|
||||||
@@ -112,14 +113,20 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment",
|
||||||
|
"options": "LMS Payment"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-10-10 12:38:17.839526",
|
"modified": "2023-10-02 12:41:25.139734",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Batch Membership",
|
"name": "LMS Enrollment",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -133,6 +140,31 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "LMS Student",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
@@ -6,17 +6,17 @@ from frappe import _
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class LMSBatchMembership(Document):
|
class LMSEnrollment(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_membership_in_same_batch()
|
self.validate_membership_in_same_batch()
|
||||||
self.validate_membership_in_different_batch_same_course()
|
self.validate_membership_in_different_batch_same_course()
|
||||||
|
|
||||||
def validate_membership_in_same_batch(self):
|
def validate_membership_in_same_batch(self):
|
||||||
filters = {"member": self.member, "course": self.course, "name": ["!=", self.name]}
|
filters = {"member": self.member, "course": self.course, "name": ["!=", self.name]}
|
||||||
if self.batch:
|
if self.batch_old:
|
||||||
filters["batch"] = self.batch
|
filters["batch_old"] = self.batch_old
|
||||||
previous_membership = frappe.db.get_value(
|
previous_membership = frappe.db.get_value(
|
||||||
"LMS Batch Membership", filters, fieldname=["member_type", "member"], as_dict=1
|
"LMS Enrollment", filters, fieldname=["member_type", "member"], as_dict=1
|
||||||
)
|
)
|
||||||
|
|
||||||
if previous_membership:
|
if previous_membership:
|
||||||
@@ -34,16 +34,16 @@ class LMSBatchMembership(Document):
|
|||||||
if self.member_type != "Student":
|
if self.member_type != "Student":
|
||||||
return
|
return
|
||||||
|
|
||||||
course = frappe.db.get_value("LMS Batch", self.batch, "course")
|
course = frappe.db.get_value("LMS Batch Old", self.batch_old, "course")
|
||||||
memberships = frappe.get_all(
|
memberships = frappe.get_all(
|
||||||
"LMS Batch Membership",
|
"LMS Enrollment",
|
||||||
filters={
|
filters={
|
||||||
"member": self.member,
|
"member": self.member,
|
||||||
"name": ["!=", self.name],
|
"name": ["!=", self.name],
|
||||||
"member_type": "Student",
|
"member_type": "Student",
|
||||||
"course": self.course,
|
"course": self.course,
|
||||||
},
|
},
|
||||||
fields=["batch", "member_type", "name"],
|
fields=["batch_old", "member_type", "name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
if memberships:
|
if memberships:
|
||||||
@@ -51,7 +51,7 @@ class LMSBatchMembership(Document):
|
|||||||
member_name = frappe.db.get_value("User", self.member, "full_name")
|
member_name = frappe.db.get_value("User", self.member, "full_name")
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} is already a Student of {1} course through {2} batch").format(
|
_("{0} is already a Student of {1} course through {2} batch").format(
|
||||||
member_name, course, membership.batch
|
member_name, course, membership.batch_old
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,8 +62,8 @@ def create_membership(
|
|||||||
):
|
):
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "LMS Batch Membership",
|
"doctype": "LMS Enrollment",
|
||||||
"batch": batch,
|
"batch_old": batch,
|
||||||
"course": course,
|
"course": course,
|
||||||
"role": role,
|
"role": role,
|
||||||
"member_type": member_type,
|
"member_type": member_type,
|
||||||
@@ -76,15 +76,13 @@ def create_membership(
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_current_membership(batch, course, member):
|
def update_current_membership(batch, course, member):
|
||||||
all_memberships = frappe.get_all(
|
all_memberships = frappe.get_all(
|
||||||
"LMS Batch Membership", {"member": member, "course": course}
|
"LMS Enrollment", {"member": member, "course": course}
|
||||||
)
|
)
|
||||||
for membership in all_memberships:
|
for membership in all_memberships:
|
||||||
frappe.db.set_value("LMS Batch Membership", membership.name, "is_current", 0)
|
frappe.db.set_value("LMS Enrollment", membership.name, "is_current", 0)
|
||||||
|
|
||||||
current_membership = frappe.get_all(
|
current_membership = frappe.get_all(
|
||||||
"LMS Batch Membership", {"batch": batch, "member": member}
|
"LMS Enrollment", {"batch_old": batch, "member": member}
|
||||||
)
|
)
|
||||||
if len(current_membership):
|
if len(current_membership):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value("LMS Enrollment", current_membership[0].name, "is_current", 1)
|
||||||
"LMS Batch Membership", current_membership[0].name, "is_current", 1
|
|
||||||
)
|
|
||||||
@@ -8,12 +8,12 @@ import frappe
|
|||||||
from lms.lms.doctype.lms_course.test_lms_course import new_course, new_user
|
from lms.lms.doctype.lms_course.test_lms_course import new_course, new_user
|
||||||
|
|
||||||
|
|
||||||
class TestLMSBatchMembership(unittest.TestCase):
|
class TestLMSEnrollment(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
frappe.db.sql("DELETE FROM `tabLMS Batch Membership`")
|
frappe.db.delete("LMS Enrollment")
|
||||||
frappe.db.sql("DELETE FROM `tabLMS Batch`")
|
frappe.db.delete("LMS Batch Old")
|
||||||
frappe.db.sql("delete from `tabLMS Course Mentor Mapping`")
|
frappe.db.delete("LMS Course Mentor Mapping")
|
||||||
frappe.db.sql("DELETE FROM `tabUser` where email like '%@test.com'")
|
frappe.db.delete("User", {"email": ("like", "%@test.com")})
|
||||||
|
|
||||||
def new_course_batch(self):
|
def new_course_batch(self):
|
||||||
course = new_course("Test Course")
|
course = new_course("Test Course")
|
||||||
@@ -26,7 +26,7 @@ class TestLMSBatchMembership(unittest.TestCase):
|
|||||||
|
|
||||||
batch = frappe.get_doc(
|
batch = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "LMS Batch",
|
"doctype": "LMS Batch Old",
|
||||||
"name": "test-batch",
|
"name": "test-batch",
|
||||||
"title": "Test Batch",
|
"title": "Test Batch",
|
||||||
"course": course.name,
|
"course": course.name,
|
||||||
@@ -37,13 +37,14 @@ class TestLMSBatchMembership(unittest.TestCase):
|
|||||||
frappe.session.user = "Administrator"
|
frappe.session.user = "Administrator"
|
||||||
return course, batch
|
return course, batch
|
||||||
|
|
||||||
def add_membership(self, batch_name, member_name, member_type="Student"):
|
def add_membership(self, batch_name, member_name, course, member_type="Student"):
|
||||||
doc = frappe.get_doc(
|
doc = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "LMS Batch Membership",
|
"doctype": "LMS Enrollment",
|
||||||
"batch": batch_name,
|
"batch_old": batch_name,
|
||||||
"member": member_name,
|
"member": member_name,
|
||||||
"member_type": member_type,
|
"member_type": member_type,
|
||||||
|
"course": course,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
doc.insert()
|
doc.insert()
|
||||||
@@ -52,7 +53,7 @@ class TestLMSBatchMembership(unittest.TestCase):
|
|||||||
def test_membership(self):
|
def test_membership(self):
|
||||||
course, batch = self.new_course_batch()
|
course, batch = self.new_course_batch()
|
||||||
member = new_user("Test", "test01@test.com")
|
member = new_user("Test", "test01@test.com")
|
||||||
membership = self.add_membership(batch.name, member.name)
|
membership = self.add_membership(batch.name, member.name, course.name)
|
||||||
|
|
||||||
assert membership.course == course.name
|
assert membership.course == course.name
|
||||||
assert membership.member_name == member.full_name
|
assert membership.member_name == member.full_name
|
||||||
@@ -60,7 +61,7 @@ class TestLMSBatchMembership(unittest.TestCase):
|
|||||||
def test_membership_change_role(self):
|
def test_membership_change_role(self):
|
||||||
course, batch = self.new_course_batch()
|
course, batch = self.new_course_batch()
|
||||||
member = new_user("Test", "test01@test.com")
|
member = new_user("Test", "test01@test.com")
|
||||||
membership = self.add_membership(batch.name, member.name)
|
membership = self.add_membership(batch.name, member.name, course.name)
|
||||||
|
|
||||||
# it should be possible to change role
|
# it should be possible to change role
|
||||||
membership.role = "Admin"
|
membership.role = "Admin"
|
||||||
@@ -43,7 +43,7 @@ class LMSExercise(Document):
|
|||||||
exercise_title=self.title,
|
exercise_title=self.title,
|
||||||
course=self.course,
|
course=self.course,
|
||||||
lesson=self.lesson,
|
lesson=self.lesson,
|
||||||
batch=member.batch,
|
batch=member.batch_old,
|
||||||
solution=code,
|
solution=code,
|
||||||
member=member.name,
|
member=member.name,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class TestLMSExercise(unittest.TestCase):
|
|||||||
course = new_course("Test Course")
|
course = new_course("Test Course")
|
||||||
member = frappe.get_doc(
|
member = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "LMS Batch Membership",
|
"doctype": "LMS Enrollment",
|
||||||
"course": course.name,
|
"course": course.name,
|
||||||
"member": frappe.session.user,
|
"member": frappe.session.user,
|
||||||
}
|
}
|
||||||
@@ -49,6 +49,6 @@ class TestLMSExercise(unittest.TestCase):
|
|||||||
assert user_submission.name == submission.name
|
assert user_submission.name == submission.name
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.db.sql("delete from `tabLMS Batch Membership`")
|
frappe.db.delete("LMS Enrollment")
|
||||||
frappe.db.sql("delete from `tabExercise Submission`")
|
frappe.db.delete("Exercise Submission")
|
||||||
frappe.db.sql("delete from `tabLMS Exercise`")
|
frappe.db.delete("LMS Exercise")
|
||||||
|
|||||||
@@ -9,17 +9,17 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"title",
|
"title",
|
||||||
"host",
|
"host",
|
||||||
"class_name",
|
"batch_name",
|
||||||
"password",
|
|
||||||
"auto_recording",
|
|
||||||
"column_break_astv",
|
"column_break_astv",
|
||||||
"description",
|
|
||||||
"section_break_glxh",
|
|
||||||
"date",
|
"date",
|
||||||
"timezone",
|
|
||||||
"column_break_spvt",
|
|
||||||
"time",
|
"time",
|
||||||
"duration",
|
"duration",
|
||||||
|
"section_break_glxh",
|
||||||
|
"description",
|
||||||
|
"column_break_spvt",
|
||||||
|
"timezone",
|
||||||
|
"password",
|
||||||
|
"auto_recording",
|
||||||
"section_break_yrpq",
|
"section_break_yrpq",
|
||||||
"start_url",
|
"start_url",
|
||||||
"column_break_yokr",
|
"column_break_yokr",
|
||||||
@@ -111,10 +111,10 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "class_name",
|
"fieldname": "batch_name",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Class",
|
"label": "Batch",
|
||||||
"options": "LMS Class"
|
"options": "LMS Batch"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "No Recording",
|
"default": "No Recording",
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-14 18:44:48.813102",
|
"modified": "2023-09-20 11:29:20.899897",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Live Class",
|
"name": "LMS Live Class",
|
||||||
@@ -157,8 +157,10 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"show_title_field_in_link": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
"title_field": "title",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ class LMSLiveClass(Document):
|
|||||||
|
|
||||||
def add_event_participants(self, event, calendar):
|
def add_event_participants(self, event, calendar):
|
||||||
participants = frappe.get_all(
|
participants = frappe.get_all(
|
||||||
"Class Student", {"parent": self.class_name}, pluck="student"
|
"Batch Student", {"parent": self.class_name}, pluck="student"
|
||||||
)
|
)
|
||||||
|
|
||||||
participants.append(frappe.session.user)
|
participants.append(frappe.session.user)
|
||||||
|
|||||||
0
lms/lms/doctype/lms_payment/__init__.py
Normal file
0
lms/lms/doctype/lms_payment/__init__.py
Normal file
14
lms/lms/doctype/lms_payment/lms_payment.js
Normal file
14
lms/lms/doctype/lms_payment/lms_payment.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) 2023, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on("LMS Payment", {
|
||||||
|
onload(frm) {
|
||||||
|
frm.set_query("member", function (doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
ignore_user_type: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user