forked from DGNum/gestioCOF
Compare commits
1677 commits
Kerl/suppo
...
master
Author | SHA1 | Date | |
---|---|---|---|
8a56e7a280 | |||
|
f640a25f59 | ||
|
f881c7cd8b | ||
b548b87c25 | |||
|
a72302291f | ||
a0bde75f50 | |||
|
44b19c12e5 | ||
f97d339a1c | |||
|
094116e88d | ||
b32a07fc22 | |||
|
4fc9902cf6 | ||
7164cfa37a | |||
|
90f96fb5c9 | ||
e50249355d | |||
|
c304d734d9 | ||
c36dd30bce | |||
2571cc955e | |||
3eaac5c68f | |||
af4c8e0744 | |||
14e0a3ef0a | |||
|
83078d4726 | ||
cb262ad479 | |||
5c47118834 | |||
30e842ce80 | |||
892bf51163 | |||
e20d7ca6c2 | |||
|
1b09293206 | ||
|
e7da476697 | ||
|
761ab6df90 | ||
|
a8d4035d33 | ||
b80426c56f | |||
429e611daa | |||
|
5160da7862 | ||
aad3775222 | |||
4b92716092 | |||
dfa5b4bf69 | |||
|
1be5dcb6af | ||
a891ec56a6 | |||
7a52690a63 | |||
a2f396ce7a | |||
85e30056a6 | |||
|
69de48f285 | ||
|
eba36f2712 | ||
|
bfdb34aae7 | ||
fcf2002cd7 | |||
b236d6a950 | |||
4b29097f02 | |||
87f383bef1 | |||
|
1ad025e046 | ||
|
b3c047738a | ||
|
17a9ae3302 | ||
|
e41bcbb6d7 | ||
|
e384bfb0f3 | ||
373ff1f62c | |||
|
65eb95a3c9 | ||
|
1c880b265e | ||
|
75fbdc7efb | ||
1b8dd971b0 | |||
713d686047 | |||
|
77aa269c90 | ||
|
9a143521d5 | ||
|
a77cf59b18 | ||
|
d8cabda678 | ||
|
f086140dad | ||
|
4d1ae8f540 | ||
|
6d824a58be | ||
20880114aa | |||
|
df180d7446 | ||
ef1793a348 | |||
2d677b2093 | |||
f70eacfc37 | |||
264a0a852f | |||
7ca7f7298a | |||
|
a5c822e7f7 | ||
|
6b316c482b | ||
|
4060730ec5 | ||
|
c6cfc311e0 | ||
|
4326ba9016 | ||
|
4205e0ad0e | ||
|
964eec6ab1 | ||
|
29236e0b0e | ||
|
1939a54fef | ||
|
348881d207 | ||
|
ef8c1b8bf2 | ||
|
8743301105 | ||
|
6a11139588 | ||
|
a34b83c236 | ||
|
02584982f6 | ||
|
7bf0c5f09e | ||
|
b9aaf6a19c | ||
|
16dee0c143 | ||
|
a947b9d3f2 | ||
|
93d283fecb | ||
|
63738e8e02 | ||
|
1e44550e12 | ||
|
4136cb6868 | ||
|
99809209e0 | ||
|
0351f6728b | ||
|
7efc7e6b94 | ||
7d21a5a1fc | |||
dba785bf13 | |||
71878caf2c | |||
|
db42028228 | ||
|
7171a7567c | ||
|
339223bec0 | ||
|
d62a8d61de | ||
|
f6c83dc692 | ||
|
a984d1fd6f | ||
|
f901ea9396 | ||
|
17d96f1775 | ||
|
c10e5fe45c | ||
|
9bbe3f50cb | ||
|
1f4a4ec76f | ||
|
2befa584aa | ||
|
b48d32f4bc | ||
|
e36e88e77a | ||
|
8e9fc341ca | ||
|
c14c2d54a5 | ||
|
6adfaba8e9 | ||
|
06005014f9 | ||
|
4268a30d51 | ||
|
c71e6d22bf | ||
|
4df3ef4dd9 | ||
|
af95e64344 | ||
|
ac8ad15ad1 | ||
|
47dd078b6a | ||
|
472a44c30f | ||
|
b72ea9ebf9 | ||
|
f9958e4da0 | ||
|
47f406e09e | ||
|
1450b65dcd | ||
|
aac94afcd0 | ||
|
209360f535 | ||
|
b224fedf28 | ||
|
1ab071d16e | ||
|
1cf6f6f3e7 | ||
|
a421bec625 | ||
|
4e758fbba0 | ||
|
2350109a33 | ||
|
778637d60e | ||
|
23f7865140 | ||
|
cc7c4306f4 | ||
|
1183e50f60 | ||
|
a8de7e0ae0 | ||
|
30a39ef2f6 | ||
|
9a635148bb | ||
|
4b95b65be2 | ||
|
884ec2535b | ||
|
beba3052dd | ||
|
46242ad2c0 | ||
|
fa8c57269c | ||
|
b97bc8bfa8 | ||
|
89fc309c01 | ||
|
d7367476bc | ||
|
7297baaf7e | ||
|
8bf7914728 | ||
|
71fbbcff8a | ||
|
9303772f9a | ||
559b36b6f0 | |||
|
fbafdb7134 | ||
|
a53bd94737 | ||
|
46ef12309a | ||
|
4f60ba35eb | ||
|
f29b3f0187 | ||
|
aa3462aaee | ||
|
7c35357060 | ||
|
726b3f55a0 | ||
|
63eeb5b7a9 | ||
|
7081380058 | ||
|
288de95c49 | ||
|
9a01d1e877 | ||
|
10746c0469 | ||
|
5c8eca15b6 | ||
|
ef64f9ce5c | ||
|
9762838921 | ||
|
bf6d6d6430 | ||
|
ba9aa06b4f | ||
|
880dc31353 | ||
|
9a78fca507 | ||
|
4bc56d34e0 | ||
|
79f0757e9f | ||
|
a2eed13717 | ||
|
830aba984e | ||
|
33319cfe76 | ||
|
44b001bd3c | ||
|
ab9d95055e | ||
|
40391d8814 | ||
|
681507f211 | ||
|
7f133316a4 | ||
|
0bdbcf59fa | ||
|
0bad404b71 | ||
|
c100f2fc8d | ||
|
ba74779f95 | ||
|
035bbe68a5 | ||
|
319db68655 | ||
|
9d2c13e67c | ||
|
73c068055b | ||
|
30ce8d13af | ||
|
340f8f16a7 | ||
|
0ce1e62586 | ||
|
783fe1de32 | ||
|
49fde85187 | ||
|
479e751b7c | ||
|
f2c1ff2abd | ||
|
8b73460165 | ||
|
411d7e7dce | ||
|
f952d50b12 | ||
|
7324a72e6e | ||
|
ad73cc987d | ||
|
e9e0c79b40 | ||
|
badee498a3 | ||
|
72cd55716b | ||
|
a7cbd2d451 | ||
|
b9699637aa | ||
|
521be6db85 | ||
|
f3701d91fc | ||
|
59dacda37d | ||
|
7f58b5fa00 | ||
|
df222f18a3 | ||
|
5d22a4cac4 | ||
|
cc3a436750 | ||
|
404d3f4f4c | ||
|
43fcdc8526 | ||
|
35f896b40f | ||
|
fb1a38cff3 | ||
|
f88795a60e | ||
|
0ffebdf82f | ||
|
147b8514ef | ||
|
d535cf24a3 | ||
|
84dab59c72 | ||
|
22cf0d403e | ||
|
a525cffaff | ||
|
1af602c9f7 | ||
|
8a17aa2caa | ||
|
7a9d96d83a | ||
|
3869c02dfa | ||
|
848eb2274a | ||
|
ebd8b7ccdb | ||
|
3c6ab35390 | ||
|
57901c0013 | ||
|
d172dad0ab | ||
|
d0b7000747 | ||
|
84ff0d7182 | ||
|
600927b21c | ||
|
d965050563 | ||
|
a14c9d9574 | ||
|
8f9c94fe10 | ||
|
46f447ec5d | ||
|
4dbf11f91e | ||
|
569ce0ba25 | ||
|
aa955a06ef | ||
|
a9eb32217f | ||
|
c7998f56f0 | ||
|
a6e58dcd68 | ||
|
49591fa67e | ||
|
9f9724b1d1 | ||
|
205dc93f4b | ||
|
7f6d4527ed | ||
|
43a2f8db53 | ||
|
ba4cc01ed4 | ||
|
11d94ecba8 | ||
|
82d58d23c9 | ||
|
6377dd5c95 | ||
|
f364928004 | ||
|
dc070278f7 | ||
|
561a121e04 | ||
|
b03cf05ef7 | ||
|
edf6a03bc4 | ||
|
eb3cba31a7 | ||
|
1ffda1a5c4 | ||
|
2bc97a115c | ||
|
b8072f4346 | ||
|
cedd3cf816 | ||
|
2c833daa7f | ||
|
2a05c2247c | ||
|
c957ab2b72 | ||
|
98cce25f4c | ||
|
ba6ddfc516 | ||
|
c5d7eb9d30 | ||
|
d3185f25c3 | ||
|
007b5006d4 | ||
|
2d36c85085 | ||
|
d6fa738a25 | ||
|
c145191e55 | ||
|
91852bd4a0 | ||
|
6f5fa19fc3 | ||
|
e92d50593c | ||
|
0590bc3aab | ||
|
3286ad09df | ||
|
3da0a613f7 | ||
|
34be9e2393 | ||
|
97bdeed97a | ||
|
8016b16904 | ||
|
6e8926595d | ||
|
a9b6bc65a2 | ||
|
b1fd6e6021 | ||
|
576d43f44d | ||
|
c55a2c8c8e | ||
|
65c979ea59 | ||
|
dcd592ed11 | ||
|
e401303a08 | ||
|
2b72f3b40b | ||
|
2aae281120 | ||
|
1387da3b54 | ||
|
e868e6eb18 | ||
|
fc988e3fad | ||
|
205b5c206b | ||
|
ab9b4d14ef | ||
|
5f8b8661bf | ||
|
858a0c61e2 | ||
|
12d3ef0c02 | ||
|
da40ed1d8c | ||
|
f10bd1eea2 | ||
|
8576023b42 | ||
|
24eaaa277f | ||
|
8f4e3bb048 | ||
|
7775e45b60 | ||
|
b3ada0eb89 | ||
|
74b9721fbd | ||
|
7db75c0060 | ||
|
d96b3d26b6 | ||
|
72210e1980 | ||
|
359f85a42d | ||
|
95cac47f4e | ||
|
7dc32add35 | ||
|
0011cfe8f7 | ||
|
5a7c4f64d5 | ||
|
46893a8df5 | ||
|
81b45f74e3 | ||
|
c6dfcea5e2 | ||
|
198e456c22 | ||
|
1518f4c703 | ||
|
2d59565f61 | ||
|
d3384dc5fc | ||
|
e7cc705350 | ||
|
8fa635773c | ||
|
1ac3e0f976 | ||
|
f811230c25 | ||
|
74c3afe9ca | ||
|
0875ef1278 | ||
|
1d707aad41 | ||
|
fef19024d8 | ||
|
566e968849 | ||
|
55c69ae42b | ||
|
40839458a5 | ||
|
a259dd524f | ||
|
85c750d380 | ||
|
826e45f619 | ||
|
5989f65154 | ||
|
2cf0ccbb6b | ||
|
1677768177 | ||
|
7a7e02adab | ||
|
62d26560d9 | ||
|
df9639715b | ||
|
72237fef60 | ||
|
8fa07bb845 | ||
|
7931f50611 | ||
|
910536c6d3 | ||
|
3d830884b1 | ||
|
9110e5b185 | ||
|
22f60163fe | ||
|
8661716df9 | ||
|
ae64f09869 | ||
|
422e2f7b42 | ||
|
f990934425 | ||
|
effed1b5c5 | ||
|
efbb9c2be3 | ||
|
a6c9cf11bd | ||
|
26fa9dc898 | ||
|
ee1d158f2d | ||
|
5e5b224f89 | ||
|
f33416b712 | ||
|
b6626093e5 | ||
|
15936751c0 | ||
|
9efc200f74 | ||
|
54e8f95667 | ||
|
a5ccd40ec1 | ||
|
b4fbc3edf8 | ||
|
eb10c904e0 | ||
|
c8c8c6abc8 | ||
|
e64f405299 | ||
|
aa2f691f1e | ||
|
8cd9434664 | ||
|
e323f2f755 | ||
|
deae1c4639 | ||
|
62281cb3b7 | ||
|
6454931e70 | ||
|
6e88f1a887 | ||
|
2e28986503 | ||
|
b24935b938 | ||
|
ac06211841 | ||
|
5c1e2e9cda | ||
|
c1e48579f1 | ||
|
c6a6e7fafa | ||
|
be064262da | ||
|
9ac030fd16 | ||
|
7caee5665b | ||
|
e7517195cd | ||
|
30783d677b | ||
|
c863b2010e | ||
|
24d7d90c28 | ||
|
f2b1962e1c | ||
|
68ccd4722f | ||
|
9a90f19502 | ||
|
fbbc9937f6 | ||
|
e9f00b4f06 | ||
|
c7ca96bce5 | ||
|
637572ab58 | ||
|
28370c8e67 | ||
|
701ea96a90 | ||
|
de1bba3695 | ||
|
56f1edebe3 | ||
|
c52bac05b3 | ||
|
5d24786e20 | ||
|
bca75dbf98 | ||
|
0789da7bed | ||
|
f6458074b2 | ||
|
eadfd1d3cd | ||
|
3a34ab4462 | ||
|
25b603d667 | ||
|
f26d330973 | ||
|
7a52e841e6 | ||
|
9a3914ece6 | ||
|
6a32a72c15 | ||
|
d464b69b2e | ||
|
d16bf5e6b0 | ||
|
1ba6b5753f | ||
|
21fcb5daa9 | ||
|
c5adc6b7d8 | ||
|
b9ba0a3829 | ||
|
028b6f6cb7 | ||
|
3ca8b45014 | ||
|
90fc6aa3e7 | ||
|
707b7b76db | ||
|
6fff995ccd | ||
|
9b0440429c | ||
|
50266f2466 | ||
|
65171d1276 | ||
|
3b43ad84b5 | ||
|
bb72a16b64 | ||
|
b1c69eddb5 | ||
|
88c9187e2e | ||
|
bbe831a226 | ||
|
f642b218d0 | ||
|
cc72f47f00 | ||
|
24180e747e | ||
|
5a0cf58d8a | ||
|
d7d4d73af3 | ||
|
c2f6622a9f | ||
|
8778695e95 | ||
|
e0fd3db638 | ||
|
d5e9d09044 | ||
|
6e9dc03bc7 | ||
|
6384cfc701 | ||
|
67d7dafc14 | ||
|
1ada8645b8 | ||
|
d4a9e96e38 | ||
|
abb8cc5a2d | ||
|
5a9ea4234e | ||
|
c9136dbcfa | ||
|
61e4ad9741 | ||
|
c9dad9465a | ||
|
f10d6d1a71 | ||
|
97cb9d1f3b | ||
|
c66fb7eb6f | ||
|
48ad5cd1c7 | ||
|
ef35f45ad2 | ||
|
26bcd729bb | ||
|
78ad4402b0 | ||
|
6767ba8e8c | ||
|
4f15bb9624 | ||
|
3b2251a1d6 | ||
|
64ceb813c6 | ||
|
3b0d4ba58f | ||
|
5298a19667 | ||
|
b1d8bb04c4 | ||
|
b8cd5f1da5 | ||
|
a259b04d9c | ||
|
e45ee3fb40 | ||
|
d2c6c9da7a | ||
|
914888d18a | ||
|
c8b8c90580 | ||
|
922190d20f | ||
|
6362740a77 | ||
|
9eebc7fb22 | ||
|
2aa06d2954 | ||
|
931b2c4e1f | ||
|
b450cb09e6 | ||
|
8d11044610 | ||
|
786c8f132f | ||
|
677ba5b92e | ||
|
fb4455af39 | ||
|
7438445110 | ||
|
f7ce2edd87 | ||
|
0221221d53 | ||
|
49ef8b3c15 | ||
|
550a073d51 | ||
|
af0de33d4c | ||
|
c95e1818b2 | ||
|
41ad2a15ac | ||
|
36d6a4a1cd | ||
|
9b2c4c1f98 | ||
|
c3b5de336a | ||
|
bf117ec070 | ||
|
a3b0ea9b8d | ||
|
9dabab51db | ||
|
2ad400c5e7 | ||
|
8a27f70e89 | ||
|
fcf29fe6df | ||
|
31e4658766 | ||
|
7b554e4778 | ||
|
fe2f8aaa5a | ||
|
137dd655d1 | ||
|
494cd5ddc1 | ||
|
80188fa88d | ||
|
4580f8bf0f | ||
|
03e6fe3ef6 | ||
|
8c75189ce1 | ||
|
68b7219cf5 | ||
|
7a828760b3 | ||
|
5280ec2d18 | ||
|
b0d8b0b7f8 | ||
|
bc90de76b6 | ||
|
ed97ff466d | ||
|
6cce9779fa | ||
|
2c2872275a | ||
|
fb3f6b9073 | ||
|
28cb35e0b0 | ||
|
79ad1346d3 | ||
|
bb05edfd6b | ||
|
4d3531c2cb | ||
|
ff968b68b2 | ||
|
84c36b9903 | ||
|
3088098a0a | ||
|
f9feff4b24 | ||
|
08d7e12c38 | ||
|
bd74f4098c | ||
|
ee79281f53 | ||
|
ee4d2d7f0e | ||
|
f19b257afd | ||
|
87e3795c76 | ||
|
f5d6d91e51 | ||
|
a1a2aac1f3 | ||
|
c1449d50ce | ||
|
e13a5b0e60 | ||
|
858759865e | ||
|
8bae013152 | ||
|
4d5419fdbc | ||
|
2e4d7101ce | ||
|
229b6e55f5 | ||
|
d2ba9471da | ||
|
1f945d1af3 | ||
|
64c792b11f | ||
|
67e28c704f | ||
|
ac901e5b77 | ||
|
21fc91c3a4 | ||
|
00bad52570 | ||
|
59d93900a3 | ||
|
2df4e931d4 | ||
|
36e802082e | ||
|
d7e1583a8e | ||
|
2c848a564c | ||
|
e97c873b4f | ||
|
f151ad75c6 | ||
|
83ce873e25 | ||
|
71e3c210f2 | ||
|
a4fdb578bc | ||
|
0498db1140 | ||
|
77ceae37ef | ||
|
085013b256 | ||
|
381b52f46c | ||
|
0bd3bd63aa | ||
|
c6c4814519 | ||
|
b1747f61fe | ||
|
85aa56d030 | ||
|
361ad46be4 | ||
|
4e15ab8041 | ||
|
091208b66c | ||
|
7df8a9ef6b | ||
|
4c7993f48f | ||
|
94d5e0f0ac | ||
|
a521caba8d | ||
|
797f0356f6 | ||
|
8dcc1f012a | ||
|
1115960107 | ||
|
727b3042a1 | ||
|
61efded673 | ||
|
38aecdd741 | ||
|
e0ffee295d | ||
|
e62756ed29 | ||
|
ac3bfbe368 | ||
|
affdf43e0b | ||
|
20ceec0e64 | ||
|
4c9ee8a57d | ||
|
5c581d8984 | ||
|
ac4d5cf7d5 | ||
|
b90e749a7f | ||
|
d04b79bcb5 | ||
|
14164ec4a5 | ||
|
f9b461a08d | ||
|
8c5d09dbee | ||
|
e2af45929e | ||
|
481cb5e478 | ||
|
a3ca2e66bf | ||
|
82746f1492 | ||
|
ea45eb1f55 | ||
|
a60df91b04 | ||
|
c1a99453d5 | ||
|
be5218f7e1 | ||
|
dd5fe36ee1 | ||
|
4a4dae9951 | ||
|
5d87d7f249 | ||
|
f9cde30e08 | ||
|
b376114bee | ||
|
57088cda03 | ||
|
b542f805f0 | ||
|
28a129f69c | ||
|
c4f5e168f2 | ||
|
5b2dad6fff | ||
|
b22a77e603 | ||
|
4d9c66cb51 | ||
|
9254e3f8f7 | ||
|
635c14ff8f | ||
|
898abd15c6 | ||
|
337453c6c6 | ||
|
bdb8f06e1d | ||
|
64b4c2c08a | ||
|
8a6a3d0994 | ||
|
8ea0cb84d1 | ||
|
099857e226 | ||
|
f8e954ff79 | ||
|
435bb392ea | ||
|
de10392a7f | ||
|
e421792906 | ||
|
abb3823a8b | ||
|
b343c6c6e0 | ||
|
22cfaf9b44 | ||
|
a9dce881bd | ||
|
0100a9a62e | ||
|
6fba63846a | ||
|
2a06cc5806 | ||
|
380e38519b | ||
|
8ccaf8beaf | ||
|
7892f42e3e | ||
|
2964d3a4aa | ||
|
46da197507 | ||
|
b005e772ea | ||
|
f83eeb7a5f | ||
|
e94015a142 | ||
|
98fe68d0be | ||
|
f9aee86a1c | ||
|
e2a7e1f6de | ||
|
53efb4b542 | ||
|
53ea6f24ee | ||
|
b3e7b59903 | ||
|
bc7c30e2ee | ||
|
4da5add25a | ||
|
f5766e9207 | ||
|
a8fd04e4c0 | ||
|
33bc3c5882 | ||
|
41a3c4c161 | ||
|
83c83d791b | ||
|
d5f0060e2e | ||
|
41256154ad | ||
|
0814cfe1ef | ||
|
730611039b | ||
|
9b355d5b56 | ||
|
6781122fc1 | ||
|
e9ca8eb8dd | ||
|
97f682dfcd | ||
|
9f2004bb54 | ||
|
8119591c62 | ||
|
34e552f760 | ||
|
ef97afd86c | ||
|
9a7a447246 | ||
|
f85f014bc9 | ||
|
fb1519cece | ||
|
51e8058f97 | ||
|
fba6b592f5 | ||
|
db158ad312 | ||
|
8c9de4303b | ||
|
9661751df2 | ||
|
d1c9d27a65 | ||
|
838bf325ba | ||
|
b99fd03df2 | ||
|
d37c41e99f | ||
|
a4ecd344d0 | ||
|
e0285607a0 | ||
|
96adadce5e | ||
|
e8a9e808f5 | ||
|
966cf6ce15 | ||
|
6406b493a2 | ||
|
0701213225 | ||
|
411e66b13c | ||
|
5db7eef1d7 | ||
|
0a1b20dd4e | ||
|
92ebf0d233 | ||
|
405f95a43b | ||
|
ab89002cfc | ||
|
c319780ab5 | ||
|
9f23f85b87 | ||
|
1cf333f0fc | ||
|
bf372a1ce2 | ||
|
96430d852c | ||
|
fc8c8fdf29 | ||
|
8d30c5c7e5 | ||
|
fb56293273 | ||
|
baa3826a42 | ||
|
4598abc721 | ||
|
d7d0daea0d | ||
|
46e7305953 | ||
|
4f15b820a5 | ||
|
edd92beadf | ||
|
20bb9fe54b | ||
|
ba5aa6da5f | ||
|
b11e35616c | ||
|
fa98bb34bd | ||
|
a67446048e | ||
|
b37e7c4c41 | ||
|
29111059f9 | ||
|
9776a18e4c | ||
|
7f1adf7c4e | ||
|
56bc281b30 | ||
|
c4948be1f7 | ||
|
f3dbb72f69 | ||
|
173affd8eb | ||
|
51fe9cc9f8 | ||
|
d4be8b426e | ||
|
65dd7e5fa3 | ||
|
123e2b84df | ||
|
f12370a6cd | ||
|
52521e89a6 | ||
|
08ac0ac890 | ||
|
63fff6ca7c | ||
|
85b1e974ff | ||
|
3a5eceba83 | ||
|
018865967d | ||
|
8dd003f81f | ||
|
f03e708280 | ||
|
198658f5f9 | ||
|
03c74a7940 | ||
|
7717d1ed34 | ||
|
72560397a2 | ||
|
413a9cddb1 | ||
|
4064218010 | ||
|
a2fcc05672 | ||
|
8fc6f96324 | ||
|
271732f40d | ||
|
019acb90ac | ||
|
759b6d9489 | ||
|
f32e4a9b0d | ||
|
209cb1fbe5 | ||
|
ceff3ed6c9 | ||
|
2c49b25d59 | ||
|
5f963d5451 | ||
|
a1ead1bfc8 | ||
|
cef75e56d7 | ||
|
8bfb8029b2 | ||
|
fd0f387dbc | ||
|
676239ad24 | ||
|
5fd4cb5c78 | ||
|
2b3a8760ff | ||
|
0eccfcf886 | ||
|
f90663bf97 | ||
|
1043e5725a | ||
|
3bf0906697 | ||
|
85642d00d8 | ||
|
fabb30cec2 | ||
|
641bdd9464 | ||
|
4f6579c3d1 | ||
|
1c45dd833d | ||
|
f3117c9e69 | ||
|
fc72425c05 | ||
|
f74277af66 | ||
|
f64c7a6e69 | ||
|
73508c0251 | ||
|
2c4ba3258d | ||
|
0f1e05acdd | ||
|
86dbdbd2b6 | ||
|
c134e1ae0f | ||
|
abf05131d5 | ||
|
869e634adb | ||
|
be155f2f2d | ||
|
68cead600f | ||
|
d85eeb5801 | ||
|
2140a59777 | ||
|
512868ee14 | ||
|
d7d819dde0 | ||
|
eb83c58f05 | ||
|
f883082814 | ||
|
b65d37b141 | ||
|
aa498e0261 | ||
|
670fda3c9c | ||
|
dd5d773405 | ||
|
d08cec06d3 | ||
|
1f3e65fa68 | ||
|
55c1d7f02c | ||
|
71cae7f5ca | ||
|
445745ee15 | ||
|
6e51ca749d | ||
|
babb458aa4 | ||
|
288e3f15b6 | ||
|
bb23c45fad | ||
|
47c02d72af | ||
|
ae0abb5cb3 | ||
|
f66a54bb73 | ||
|
010ce0df3e | ||
|
519ef9dc20 | ||
|
a30955fb75 | ||
|
31223aaed9 | ||
|
5d14fef032 | ||
|
7ca0144004 | ||
|
7c1d1df1a9 | ||
|
a6bf1fc16a | ||
|
84c88dfd5e | ||
|
39eaf4b109 | ||
|
376cc96343 | ||
|
e0e75b53bb | ||
|
aba0be7960 | ||
|
a057776e1f | ||
|
e21154e869 | ||
|
f105225abf | ||
|
debbf265c4 | ||
|
8db55d792c | ||
|
09e99ee3a3 | ||
|
3e38e48d7a | ||
|
1664554083 | ||
|
f8610d4ff1 | ||
|
a71fbb0ed3 | ||
|
57a2af285a | ||
|
e6e4a13440 | ||
|
f9ddee60ed | ||
|
66104e1137 | ||
|
15ab316909 | ||
|
6be42d57ca | ||
|
5c8164dd3b | ||
|
625825cf3f | ||
|
dee2f4badc | ||
|
633cd49094 | ||
|
d58d08a4d4 | ||
|
5f9695ef8e | ||
|
2e08951d44 | ||
|
2b8f81c94b | ||
|
c960d97b67 | ||
|
d82c9baf20 | ||
|
4ae0b3c5f0 | ||
|
4e34583e3f | ||
|
a2116bf290 | ||
|
d48cb3aaed | ||
|
dbd017f680 | ||
|
712588af7d | ||
|
6524f89ebd | ||
|
7f2f25cb71 | ||
|
042ce80b78 | ||
|
d7f4d32c92 | ||
|
b80927efa3 | ||
|
511981e762 | ||
|
e09fa2b847 | ||
|
39d82a573f | ||
|
7124821f7c | ||
|
ba21de683b | ||
|
00a1e79af6 | ||
|
95ba7798d7 | ||
|
0fe63d3eae | ||
|
3d27dc9a41 | ||
|
1a5bbf32a4 | ||
|
25dfe2f496 | ||
|
8be913cbf9 | ||
|
49a74e8e1e | ||
|
b69f1b6dbc | ||
|
d7ca072af3 | ||
|
928abc5a06 | ||
|
0ba7100110 | ||
|
b795a06b9c | ||
|
e478beee5c | ||
|
128a9e32c0 | ||
|
1e3850bb6b | ||
|
7f5a442bae | ||
|
9da9649a45 | ||
|
1f350d60dd | ||
|
26b19685b8 | ||
|
2c0ab1e55e | ||
|
b86f3113ea | ||
|
aac9b41442 | ||
|
ec287c8a3b | ||
|
fc4b852bde | ||
|
9bc3355a21 | ||
|
61fbf0bc80 | ||
|
402b544393 | ||
|
fdd2b35289 | ||
|
104e71dcf6 | ||
|
b23810917d | ||
|
b39b6d6bb3 | ||
|
dda803b7d5 | ||
|
cc4e3223b6 | ||
|
39abfceb76 | ||
|
56d979bb47 | ||
|
6c5b7ed5cc | ||
|
7e55bf0cb1 | ||
|
507a59c914 | ||
|
22011faba9 | ||
|
0f688a8f1c | ||
|
93d3c124fd | ||
|
ec29e6ef5c | ||
|
79c26c9dd6 | ||
|
44af796e73 | ||
|
6858df02be | ||
|
3eb939928f | ||
|
10f4bd02d5 | ||
|
064c23902b | ||
|
44e5387f15 | ||
|
f297a1a0cf | ||
|
a750c62baf | ||
|
19c51c6a3a | ||
|
327ef210db | ||
|
73cf39baa8 | ||
|
91393dcea7 | ||
|
a8bfedb28b | ||
|
92ca838601 | ||
|
66184fbee6 | ||
|
898a354c2d | ||
|
954a6fdb53 | ||
|
6d6ba70bd7 | ||
|
e515a55956 | ||
|
645c01ebd1 | ||
|
68e71317cb | ||
|
96416bffea | ||
|
12ae10f2c4 | ||
|
af817534e4 | ||
|
837b48af36 | ||
|
c7cdf29129 | ||
|
7e9df3fc72 | ||
|
b0301f0304 | ||
|
2a9125ffaa | ||
|
483f192af3 | ||
|
20cc2e7345 | ||
|
ece9a54df3 | ||
|
6d72644ee3 | ||
|
e21666a112 | ||
|
f85d51e3bd | ||
|
2818e43b2e | ||
|
ea737dab29 | ||
|
a73736bf41 | ||
|
2faa8d6b65 | ||
|
769f4fc7b8 | ||
|
dd9a81d891 | ||
|
fc37c5a8a0 | ||
|
6168045c3a | ||
|
6c983e16e5 | ||
|
a7cd1e04cd | ||
|
09cfcc476a | ||
|
556c354f8a | ||
|
25f4c64835 | ||
|
87a6722143 | ||
|
568a58c52a | ||
|
53a4c78903 | ||
|
158b19778b | ||
|
6d0ec6de43 | ||
|
9d7486585b | ||
|
71b4e6253d | ||
|
fba105fdd1 | ||
|
7512454825 | ||
|
3463017d59 | ||
|
9986cb1dc7 | ||
|
71a61fe31d | ||
|
0cceed9052 | ||
|
6328cdaa19 | ||
|
ee33762845 | ||
|
2d14296c47 | ||
|
660f395b67 | ||
|
f23d351ddc | ||
|
60d8e76fee | ||
|
623047dca2 | ||
|
16d7b4f7c0 | ||
|
bf464f9378 | ||
|
35e17a81a6 | ||
|
cc27e4d964 | ||
|
3d091f50b5 | ||
|
347497602c | ||
|
6d6c995563 | ||
|
307c48ca76 | ||
|
cadbd1b7c2 | ||
|
746c5e2053 | ||
|
6ecc9a54b3 | ||
|
3314670cab | ||
|
ac1a57d969 | ||
|
8f0eec0e88 | ||
|
d88ce44989 | ||
|
c94a9ecb44 | ||
|
d57c75d2a0 | ||
|
a3295ca6a3 | ||
|
09433f6f15 | ||
|
8c68270a4d | ||
|
63ce694b4d | ||
|
59116f2d46 | ||
|
88e911ff9d | ||
|
ba87044638 | ||
|
f8952225d6 | ||
|
c11ccf2ecc | ||
|
afa6972280 | ||
|
0235c4f7e8 | ||
|
f8361b9114 | ||
|
0876a004e5 | ||
|
91162addb9 | ||
|
bd89dce11d | ||
|
8488beeb4e | ||
|
a813507ddd | ||
|
f371606cdb | ||
|
80ca35a4c0 | ||
|
38539a9d53 | ||
|
acf284862a | ||
|
2e6a54c7db | ||
|
7e0ecd8e0f | ||
|
bbe46645f7 | ||
|
a5071aa257 | ||
|
8551ffcfd3 | ||
|
ea495e8f29 | ||
|
4084444dc3 | ||
|
7bf2f73e72 | ||
|
c239f28f17 | ||
|
0921f32e4c | ||
|
7160a9c954 | ||
|
dfb9ccb0af | ||
|
a6f52cfdc5 | ||
|
f5b280896f | ||
|
ce73499077 | ||
|
8675948d9e | ||
|
57de31d59a | ||
|
776ff28141 | ||
|
42e762bc4a | ||
|
478f56d94b | ||
|
525bb4d16d | ||
|
e23e1bdba6 | ||
|
6059ca067b | ||
|
79fccc136a | ||
|
52fd49616d | ||
|
5a5b60ec4d | ||
|
501d592d2f | ||
|
91119f68bc | ||
|
44eee9be38 | ||
|
f58f120e7a | ||
|
771b642a98 | ||
|
5086128f16 | ||
|
c80e63415b | ||
|
e9b901337e | ||
|
433b3f4716 | ||
|
62d8c2ffaf | ||
|
97eed06b6f | ||
|
57411ab46f | ||
|
475f1adec5 | ||
|
9a8773978c | ||
|
1783196a9c | ||
|
f1bbade002 | ||
|
dfa8c1a1a1 | ||
|
5f9f222cda | ||
|
212528011a | ||
|
32720c56a6 | ||
|
241c77e3b4 | ||
|
b0b0542407 | ||
|
36ce038050 | ||
|
91bdf11852 | ||
|
f18959c0a1 | ||
|
e1794a654f | ||
|
273e6374ef | ||
|
93fa79128c | ||
|
19e6ddc8bf | ||
|
895f7e062c | ||
|
1c90d067fa | ||
|
785555c05c | ||
|
364648fb4f | ||
|
1a136088bf | ||
|
40abe27e81 | ||
|
a07b5308a3 | ||
|
8e8e9aa076 | ||
|
53ef8b517a | ||
|
af3a7cf697 | ||
|
1cc51f17a3 | ||
|
8673da1874 | ||
|
8b1f174b13 | ||
|
8cdb4c62fc | ||
|
6a6549e0d7 | ||
|
684603709e | ||
|
1b0e4285ec | ||
|
919bcd197d | ||
|
e74dbb11f1 | ||
|
732e47707e | ||
|
adf43889e1 | ||
|
cb820e1414 | ||
|
46187659ed | ||
|
fccad5edee | ||
|
b9aaea0f99 | ||
|
47e48e36b2 | ||
|
85657591f5 | ||
|
c59bc487c3 | ||
|
f4a7e9dbf1 | ||
|
03deb54d92 | ||
|
3b1d8487e2 | ||
|
f8bb5b081e | ||
|
3f6c5be748 | ||
|
e0ab7f5f94 | ||
|
116b4da1a5 | ||
|
29ef297b2a | ||
|
503b305299 | ||
|
3d22a1b029 | ||
|
528532cca7 | ||
|
4d1cb3c2d7 | ||
|
5a22b1cd37 | ||
|
57a143e0e1 | ||
|
435e211b3d | ||
|
596868f5b6 | ||
|
0e19abb51a | ||
|
b73faa3b84 | ||
|
22413d861c | ||
|
d18fb86a98 | ||
|
b42452080f | ||
|
11e8cb1be2 | ||
|
db512a97f6 | ||
|
3fa7754ff4 | ||
|
e5d19811e8 | ||
|
1d19d1797c | ||
|
6f2652c485 | ||
|
d89ba1efe5 | ||
|
4091185a68 | ||
|
bf61e41b50 | ||
|
a4eedbc1a6 | ||
|
4bd2562edf | ||
|
7d16001ee5 | ||
|
1921f05eba | ||
|
368ee3190f | ||
|
9e61887868 | ||
|
faed7bff73 | ||
|
f581279825 | ||
|
937a485704 | ||
|
439f49c3ba | ||
|
35b352ac1d | ||
|
50432d969f | ||
|
51f4bf3fb5 | ||
|
fb5ba5fb1b | ||
|
af97c0cda6 | ||
|
997b63d6b6 | ||
|
7d490f0253 | ||
|
5220bb75d0 | ||
|
d8391e54a5 | ||
|
b7502e51ed | ||
|
be1e67626c | ||
|
0afbd577b1 | ||
|
afda1ba2ca | ||
|
6e140e540d | ||
|
38af50a866 | ||
|
7853987ebb | ||
|
09e63bf00c | ||
|
53658589f8 | ||
|
f5778fed2a | ||
|
66fc364739 | ||
|
b4338ce8db | ||
|
22d8317dee | ||
|
b4b15ab371 | ||
|
414b0eb433 | ||
|
a3bb9852be | ||
|
2cfce1c921 | ||
|
343b52f986 | ||
|
1d9310236b | ||
|
2a519bfedf | ||
|
4c08962e09 | ||
|
4d026407d1 | ||
|
5923166196 | ||
|
853fa57ce4 | ||
|
522acafb2e | ||
|
dae418af3d | ||
|
73296ea251 | ||
|
4075fcaa64 | ||
|
bd1dace8e8 | ||
|
c9aac8a49d | ||
|
ad15c45237 | ||
|
cb1d253517 | ||
|
6023211ab0 | ||
|
a6b0c51d39 | ||
|
81f3d6ab81 | ||
|
062f547315 | ||
|
784513b3cc | ||
|
88597e62f1 | ||
|
e13d68a127 | ||
|
65d7a66eb8 | ||
|
878c617cc7 | ||
|
68c1b45342 | ||
|
d30b36269a | ||
|
455b730cc3 | ||
|
b49d96d18f | ||
|
815ba50603 | ||
|
5673fabeff | ||
|
19847ac9d8 | ||
|
98f5f0c391 | ||
|
782e105644 | ||
|
b8110c11a4 | ||
|
2381af92e3 | ||
|
398893b904 | ||
|
539c32dbbe | ||
|
80f6a808dc | ||
|
69aee6b8ac | ||
|
ec59bc2edc | ||
|
d004287957 | ||
|
1499c0bced | ||
|
5c11893059 | ||
|
4fce1e8afc | ||
|
ffb777a4cd | ||
|
76c75d5ccd | ||
|
fbdfdeef46 | ||
|
31c034a96a | ||
|
2a3614540f | ||
|
0e03fc85ee | ||
|
6e82a2cf88 | ||
|
5bed02c3fc | ||
|
8c6d56b27c | ||
|
ea21b07657 | ||
|
b13e992a30 | ||
|
0815c96c1c | ||
|
3a69c3371f | ||
|
74135f8877 | ||
|
92c946a286 | ||
|
1d269ef4f9 | ||
|
cd5a2e0591 | ||
|
3c8f1c58c5 | ||
|
f0f1585661 | ||
|
a36d002b17 | ||
|
3762b38afe | ||
|
dba8a0a857 | ||
|
76dcaf7d51 | ||
|
17b140fb12 | ||
|
e60e347a53 | ||
|
1845ce825f | ||
|
109a692cea | ||
|
c0b8430a12 | ||
|
c11c3f84ed | ||
|
4344b7d537 | ||
|
0995f712ca | ||
|
ecce2fda21 | ||
|
51dca32d05 | ||
|
ae27065626 | ||
|
e9073e2265 | ||
|
1a661c1fd3 | ||
|
5776c81764 | ||
|
95a8b484e0 | ||
|
647b32e727 | ||
|
6871945dcd | ||
|
bf0e345301 | ||
|
e0b1db1e1e | ||
|
5d8427e601 | ||
|
4ac7b30bdd | ||
|
b1e46792c8 | ||
|
b0e7ebfbc5 | ||
|
6cdb791989 | ||
|
e7266e7a9d | ||
|
673dc0295d | ||
|
4ed5128829 | ||
|
fb4258f821 | ||
|
dbff7740c8 | ||
|
5c5fc6da1b | ||
|
52d9979d7c | ||
|
2aee43e01a | ||
|
739990cdb6 | ||
|
6de0844d28 | ||
|
0d8a613f28 | ||
|
2eee8f58aa | ||
|
8622002e8d | ||
|
dbf5844f6a | ||
|
e772d12721 | ||
|
a0fe94e2f4 | ||
|
ce23eece6a | ||
|
ea81ab7b25 | ||
|
ff73a635f8 | ||
|
0a21858b33 | ||
|
55b509b3dd | ||
|
7db497d095 | ||
|
9668f1d1ec | ||
|
18425b82c2 | ||
|
06572f0bb5 | ||
|
3f4a1adbb9 | ||
|
e97e0081d7 | ||
|
b5cc26bb1b | ||
|
a725f8020c | ||
|
b357283f87 | ||
|
40abe81402 | ||
|
a5fb162aaf | ||
|
a68002d0f6 | ||
|
2a3c892d39 | ||
|
36771c2c4f | ||
|
495c6ac3d1 | ||
|
ab31c20649 | ||
|
2c40838938 | ||
|
be8d249ed7 | ||
|
e0b0a53112 | ||
|
cb9ba76a4f | ||
|
5d6012b6bd | ||
|
c228416809 | ||
|
5c6a73c597 | ||
|
15873085e1 | ||
|
6ce2f178bf | ||
|
3dc91e30bd | ||
|
8870b5ace2 | ||
|
029d59e615 | ||
|
1e18c4043e | ||
|
ebfe5c41d4 | ||
|
e89ff68d3b | ||
|
457ee64004 | ||
|
784ba9bd10 | ||
|
ce70269e7b | ||
|
0750551d7c | ||
|
6451f971bd | ||
|
15861824b6 | ||
|
bbe6f41962 | ||
|
98f355ed20 | ||
|
15d2faf8e1 | ||
|
93a3a9af2c | ||
|
fdc1128bd5 | ||
|
3556e3b1b0 | ||
|
4a6bfd5e3d | ||
|
e18c2c698c | ||
|
deb0d4de1e | ||
|
052e7b2b6e | ||
|
531a4611d3 | ||
|
d31101aff3 | ||
|
9f307c1bd0 | ||
|
3e0bd2e758 | ||
|
e26668e5eb | ||
|
b8aa5d8bbe | ||
|
a0979c220b | ||
|
0ed70eb0a7 | ||
|
026fba867d | ||
|
2731d4630f | ||
|
afdb08b424 | ||
|
f8b71b604c | ||
|
709d421b2c | ||
|
33cee05884 | ||
|
56fa70e495 | ||
|
c9019c4eb4 | ||
|
1302adf156 | ||
|
f57bab8ae9 | ||
|
387edd2fd5 | ||
|
5e68fcf315 | ||
|
4b2e20e728 | ||
|
e2d7192b23 | ||
|
c849306a32 | ||
|
6f66fbbf3c | ||
|
981759f5ce | ||
|
9ec151e040 | ||
|
ff9cee5ffc | ||
|
853a239e6e | ||
|
ef8fec89fe | ||
|
a5671fdf4c | ||
|
ebf948d042 | ||
|
3ee9de93d9 | ||
|
42728e58be | ||
|
a262983170 | ||
|
fe66a6ef6b | ||
|
85ba44c231 | ||
|
7350006990 | ||
|
ba11aa49db | ||
|
dc07b072ab | ||
|
885e40fd05 | ||
|
ddd71c7ac1 | ||
|
714903210c | ||
|
72615bf400 | ||
|
278459e80f | ||
|
0405eee7a7 | ||
|
b383923d49 | ||
|
7989a07b5f | ||
|
df467767f4 | ||
|
f13d1072c7 | ||
|
ce2a05766d | ||
|
51acb4e00a | ||
|
32474a6865 | ||
|
f46ba0dd6c | ||
|
7384465cbd | ||
|
921dce1f35 | ||
|
b3418db33d | ||
|
5607d7c9a4 | ||
|
ad2c8537e3 | ||
|
85caa6b058 | ||
|
10d2b58fa7 | ||
|
6d2e150aa0 | ||
|
903da795ab | ||
|
40da3bc299 | ||
|
b113a57b74 | ||
|
87bc90ec8b | ||
|
769c37634d | ||
|
a4a854bc50 | ||
|
1bb83ccdd7 | ||
|
7dc233c0e2 | ||
|
b157ffac4c | ||
|
b3a9ad8a96 | ||
|
d97a7be819 | ||
|
c01de558e1 | ||
|
f585247224 | ||
|
31261fd376 | ||
|
6ebea6557c | ||
|
48721b7dca | ||
|
df7e935390 | ||
|
1ee993e1e1 | ||
|
87b9db520f | ||
|
f6022ecf7d | ||
|
78aa5df350 | ||
|
e8fdd083aa | ||
|
96597aa146 | ||
|
c9973cde75 | ||
|
55116f7179 | ||
|
f6d43dffa1 | ||
|
c3d740ade0 | ||
|
e4d51de569 | ||
|
91a057873d | ||
|
944a73eef7 | ||
|
a793e9a2e7 | ||
|
9cdf064005 | ||
|
cce0411ee9 | ||
|
1eed2283f5 | ||
|
66dd7848b8 | ||
|
6b8001db56 | ||
|
bbb517fbd3 | ||
|
6797c92a1f | ||
|
e20ab2f352 | ||
|
271654b447 | ||
|
c355316045 | ||
|
ccf5f80685 | ||
|
8da832c1f7 | ||
|
063446efb5 | ||
|
cf03fba1cc | ||
|
9c9ad21d73 | ||
|
92fd94d1f9 | ||
|
1286606f7a | ||
|
fafa7e536e | ||
|
aa29621c7c | ||
|
ab15dbae76 | ||
|
38bfccf331 | ||
|
8bf1bd5343 | ||
|
413df0806d | ||
|
49c84076de | ||
|
1d8e084a19 | ||
|
67b4421933 | ||
|
8a1af73ee0 | ||
|
499c081565 | ||
|
fdcd2e864c | ||
|
530aafad19 | ||
|
6ac1241bd3 | ||
|
50cb6e51a1 | ||
|
e54324e9f1 | ||
|
8cf14d3f6b | ||
|
73c21d83ee | ||
|
f3b9266e35 | ||
|
a9c8de7544 | ||
|
c4a3c1a92a | ||
|
ebf133d9af | ||
|
20561eb515 | ||
|
c1b8849cb8 | ||
|
91d5fe234c | ||
|
8e65298c7c | ||
|
079666c0db | ||
|
0c21238391 | ||
|
63cb7b7fd0 | ||
|
998838ca3e | ||
|
31888e33ce | ||
|
56b5fd6279 | ||
|
95c59de4fc | ||
|
eb7d436b90 | ||
|
f32f83db75 | ||
|
2d22c202e9 | ||
|
ca6e120a38 | ||
|
c95d7cb670 | ||
|
47e1071b12 | ||
|
579711ed2d | ||
|
e6a1d16860 | ||
|
3b793dc726 | ||
|
946182f1fe | ||
|
06a89055c4 | ||
|
9a081ddae0 | ||
|
f645121fb1 | ||
|
14e0d8090f | ||
|
d8f572bb01 | ||
|
bc0affc038 | ||
|
ae82c34023 | ||
|
215d4a63ad | ||
|
794527772f | ||
|
5f3f044084 | ||
|
608e67fe6a | ||
|
44fbc92893 | ||
|
d7740e66fe | ||
|
cadaf43131 | ||
|
f063c3470c | ||
|
ff5959638b | ||
|
a221d7841c | ||
|
cb13280458 | ||
|
7520effe53 | ||
|
33b3d558b5 | ||
|
5e802217fd | ||
|
dd4d1f3061 | ||
|
aa247b465a | ||
|
ae38b5d1e7 | ||
|
59f57793ba | ||
|
0aed975615 | ||
|
6d36d50e9a | ||
|
2c63e6b667 | ||
|
36dc6439a8 | ||
|
0356ec34ae | ||
|
74f0b63e96 | ||
|
dc50a23b10 | ||
|
a057869d77 | ||
|
4899bba1f7 | ||
|
51fba4da21 | ||
|
47b7d934e0 | ||
|
29d7654052 | ||
|
f512c1bdd7 | ||
|
de724a2c0d | ||
|
a107fa0309 | ||
|
fcc2ab8810 | ||
|
55b67f38c8 | ||
|
741f0183e6 | ||
|
a8428f1b41 | ||
|
7ecea0e391 | ||
|
68334e15d7 | ||
|
3793863bab | ||
|
3acc8bca75 | ||
|
e98a407982 | ||
|
84a39b41f2 | ||
|
c4edff717d | ||
|
c513794474 | ||
|
98abe71681 | ||
|
c37754376b | ||
|
f64a865de1 | ||
|
d0938dfae7 | ||
|
e6f0369874 | ||
|
b0643c0928 | ||
|
ee6de35627 | ||
|
52dda7be9a | ||
|
5ddcd2f1ff | ||
|
cd31c55254 | ||
|
f18bb9f336 | ||
|
8e7b9e2fd3 | ||
|
5694e4b9bc | ||
|
ce96bc4b67 | ||
|
f8e09cf257 | ||
|
0f96d3bc46 | ||
|
392338eddd | ||
|
b7040d5867 | ||
|
8e462134c7 | ||
|
964dd716c6 | ||
|
ca17e36b5a | ||
|
dcc19bd9e8 | ||
|
fbd0d760b6 | ||
|
8e31fae340 | ||
|
61e2fedb08 | ||
|
624707570e | ||
|
197da29366 | ||
|
15b8900d43 | ||
|
a828ecbcf0 | ||
|
b0b2210e93 | ||
|
9f94262b82 | ||
|
f52aba8ca6 | ||
|
95b129e396 | ||
|
a0a7be43e1 | ||
|
42a93027d3 | ||
|
d96f4ead87 | ||
|
8953d3de07 | ||
|
d524d9286f | ||
|
d9402e1059 | ||
|
ddbcfe0c69 | ||
|
0fcb29252b | ||
|
46f343b1ab | ||
|
1c90608f6d | ||
|
451fddaff0 | ||
|
e8d21882fb | ||
|
85e593ddb8 | ||
|
ef864cec32 | ||
|
2e8fc45a1b | ||
|
46e9e82da8 | ||
|
9cae3ad0bd | ||
|
83e41c292e | ||
|
43aab26530 | ||
|
1a684471c2 | ||
|
3fa0e4de6c | ||
|
3dfd8bf616 | ||
|
031b992fa3 | ||
|
99d04fb75e | ||
|
4a5d9f880a | ||
|
db899a891b | ||
|
15d79aff2d | ||
|
be8eb4ac28 | ||
|
49e6101156 | ||
|
db4786abf8 | ||
|
f87f1ceff1 | ||
|
4808650fa0 | ||
|
ef59c115f6 | ||
|
6be2adc47a | ||
|
a4fdb99267 | ||
|
8d0f803639 | ||
|
5654fd84e8 | ||
|
2394a5e5d2 | ||
|
95e96d8ce5 | ||
|
44202811ae | ||
|
5b0dc1a07f | ||
|
1bf5352097 | ||
|
d5bf249212 | ||
|
bc14205d29 | ||
|
47d7a3ec6e | ||
|
863699bfb6 | ||
|
1275fdff99 | ||
|
a400832c6d | ||
|
ce9f717f06 | ||
|
ddf6343826 | ||
|
df824a3a70 | ||
|
0ac1eaf414 | ||
|
5cedabf5a8 | ||
|
2f1fe15be3 | ||
|
5ee188e4df | ||
|
a13c40736c | ||
|
7ab4e949d8 | ||
|
aa1afccf1f | ||
|
3ca38bd8e9 | ||
|
75ff77c4ec | ||
|
a63966b2f6 | ||
|
bd7f767953 | ||
|
9607fab046 | ||
|
0d1cece6db | ||
|
939e0f32a2 | ||
|
f48f6c57a7 | ||
|
94f8705ea4 | ||
|
df2a9bb1b3 | ||
|
947de3b638 | ||
|
61a7f2df1e | ||
|
232b293052 | ||
|
f8c49ae90c | ||
|
e6854d7987 | ||
|
de3db1405e | ||
|
fff7a2022b | ||
|
8b4f7ad60c | ||
|
7e5910e0b3 | ||
|
1b0144691f | ||
|
832dc0ce10 | ||
|
016989c4de | ||
|
e7956f3b62 | ||
|
eff37f6c89 | ||
|
88aec52141 | ||
|
bdbb252a05 | ||
|
d6e13dbd61 | ||
|
1ddd34d50e | ||
|
a14f1db9a1 | ||
|
76db0c86a3 | ||
|
5877218af5 | ||
|
fabd0949e2 | ||
|
6315ddf6b8 | ||
|
7dc7d57a5e | ||
|
fcf76b4af8 | ||
|
2a20beeb59 | ||
|
1c5c1fe94d | ||
|
74f4d94f28 | ||
|
219835be17 | ||
|
de9387c6ad | ||
|
7070129add | ||
|
ccf7c4a484 | ||
|
e981cad405 | ||
|
5784b4d20a | ||
|
783332bbe0 | ||
|
0c3c41a812 | ||
|
4db55efb67 | ||
|
75be9fd2a6 | ||
|
b7ebf4ee1c | ||
|
71fee9bf8a | ||
|
080ff0f821 | ||
|
711ef7e97b | ||
|
9443f86298 | ||
|
437233fd10 | ||
|
5492ecf534 | ||
|
4da9327666 | ||
|
83d49ad3a4 | ||
|
14f021b5b0 | ||
|
d19daa04b1 | ||
|
3a7ffefacf | ||
|
e4c8209df8 | ||
|
cb9b967d78 | ||
|
92ec85dbbd |
628 changed files with 88804 additions and 29490 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use nix
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -5,7 +5,19 @@ cof/settings.py
|
||||||
settings.py
|
settings.py
|
||||||
*~
|
*~
|
||||||
venv/
|
venv/
|
||||||
|
.venv/
|
||||||
.vagrant
|
.vagrant
|
||||||
/src
|
/src
|
||||||
media/
|
media/
|
||||||
*.log
|
*.log
|
||||||
|
.sass-cache/
|
||||||
|
*.sqlite3
|
||||||
|
.coverage
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
.idea
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
|
.direnv
|
||||||
|
|
107
.gitlab-ci.yml
107
.gitlab-ci.yml
|
@ -1,40 +1,95 @@
|
||||||
services:
|
image: "python:3.7"
|
||||||
- mysql:latest
|
|
||||||
- redis:latest
|
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
# GestioCOF settings
|
# GestioCOF settings
|
||||||
DJANGO_SETTINGS_MODULE: "gestioCOF.settings_dev"
|
DBHOST: "postgres"
|
||||||
DBNAME: "cof_gestion"
|
|
||||||
DBUSER: "cof_gestion"
|
|
||||||
DBPASSWD: "cof_password"
|
|
||||||
DBHOST: "mysql"
|
|
||||||
REDIS_HOST: "redis"
|
REDIS_HOST: "redis"
|
||||||
|
REDIS_PASSWD: "dummy"
|
||||||
|
|
||||||
# Cached packages
|
# Cached packages
|
||||||
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/vendor/pip"
|
||||||
|
|
||||||
# mysql service configuration
|
# postgres service configuration
|
||||||
MYSQL_DATABASE: "$DBNAME"
|
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||||
MYSQL_USER: "$DBUSER"
|
POSTGRES_USER: "cof_gestion"
|
||||||
MYSQL_PASSWORD: "$DBPASSWD"
|
POSTGRES_DB: "cof_gestion"
|
||||||
MYSQL_ROOT_PASSWORD: "root_password"
|
|
||||||
|
|
||||||
|
# psql password authentication
|
||||||
|
PGPASSWORD: $POSTGRES_PASSWORD
|
||||||
|
|
||||||
cache:
|
# apps to check migrations for
|
||||||
paths:
|
MIGRATION_APPS: "bda bds cofcms clubs events gestioncof kfet kfetauth kfetcms open petitscours shared"
|
||||||
- vendor/python
|
|
||||||
- vendor/pip
|
|
||||||
- vendor/apt
|
|
||||||
|
|
||||||
|
.test_template:
|
||||||
before_script:
|
before_script:
|
||||||
- mkdir -p vendor/{python,pip,apt}
|
- mkdir -p vendor/{pip,apt}
|
||||||
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq mysql-client
|
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client libldap2-dev libsasl2-dev
|
||||||
- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST"
|
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' gestioasso/settings/secret_example.py > gestioasso/settings/secret.py
|
||||||
-e "GRANT ALL ON test_$DBNAME.* TO '$DBUSER'@'%'"
|
- sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' gestioasso/settings/secret.py
|
||||||
- pip install --cache-dir vendor/pip -t vendor/python -r requirements-devel.txt
|
# Remove the old test database if it has not been done yet
|
||||||
|
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
|
||||||
|
- pip install --upgrade -r requirements-prod.txt coverage tblib
|
||||||
|
- python --version
|
||||||
|
after_script:
|
||||||
|
- coverage report
|
||||||
|
services:
|
||||||
|
- postgres:11.7
|
||||||
|
- redis:latest
|
||||||
|
cache:
|
||||||
|
key: test
|
||||||
|
paths:
|
||||||
|
- vendor/
|
||||||
|
# For GitLab CI to get coverage from build.
|
||||||
|
# Keep this disabled for now, as it may kill GitLab...
|
||||||
|
# coverage: '/TOTAL.*\s(\d+\.\d+)\%$/'
|
||||||
|
|
||||||
test:
|
coftest:
|
||||||
stage: test
|
stage: test
|
||||||
|
extends: .test_template
|
||||||
|
variables:
|
||||||
|
DJANGO_SETTINGS_MODULE: "gestioasso.settings.cof_prod"
|
||||||
script:
|
script:
|
||||||
- python manage.py test
|
- coverage run manage.py test gestioncof bda kfet petitscours shared --parallel
|
||||||
|
|
||||||
|
bdstest:
|
||||||
|
stage: test
|
||||||
|
extends: .test_template
|
||||||
|
variables:
|
||||||
|
DJANGO_SETTINGS_MODULE: "gestioasso.settings.bds_prod"
|
||||||
|
script:
|
||||||
|
- coverage run manage.py test bds clubs events --parallel
|
||||||
|
|
||||||
|
linters:
|
||||||
|
stage: test
|
||||||
|
before_script:
|
||||||
|
- mkdir -p vendor/pip
|
||||||
|
- pip install --upgrade black isort flake8
|
||||||
|
script:
|
||||||
|
- black --check .
|
||||||
|
- isort --check --diff .
|
||||||
|
# Print errors only
|
||||||
|
- flake8 --exit-zero bda bds clubs gestioasso events gestioncof kfet petitscours provisioning shared
|
||||||
|
cache:
|
||||||
|
key: linters
|
||||||
|
paths:
|
||||||
|
- vendor/
|
||||||
|
|
||||||
|
# Check whether there are some missing migrations.
|
||||||
|
migration_checks:
|
||||||
|
stage: test
|
||||||
|
variables:
|
||||||
|
DJANGO_SETTINGS_MODULE: "gestioasso.settings.local"
|
||||||
|
before_script:
|
||||||
|
- mkdir -p vendor/{pip,apt}
|
||||||
|
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client libldap2-dev libsasl2-dev
|
||||||
|
- cp gestioasso/settings/secret_example.py gestioasso/settings/secret.py
|
||||||
|
- pip install --upgrade -r requirements-devel.txt
|
||||||
|
- python --version
|
||||||
|
script: python manage.py makemigrations --dry-run --check $MIGRATION_APPS
|
||||||
|
services:
|
||||||
|
# this should not be necessary…
|
||||||
|
- postgres:11.7
|
||||||
|
cache:
|
||||||
|
key: migration_checks
|
||||||
|
paths:
|
||||||
|
- vendor/
|
||||||
|
|
106
.pre-commit.sh
Executable file
106
.pre-commit.sh
Executable file
|
@ -0,0 +1,106 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# pre-commit hook for gestioCOF project.
|
||||||
|
#
|
||||||
|
# Run formatters first, then checkers.
|
||||||
|
# Formatters which changed a file must set the flag 'formatter_updated'.
|
||||||
|
|
||||||
|
exit_code=0
|
||||||
|
formatter_updated=0
|
||||||
|
checker_dirty=0
|
||||||
|
|
||||||
|
# TODO(AD): We should check only staged changes.
|
||||||
|
# Working? -> Stash unstaged changes, run it, pop stash
|
||||||
|
STAGED_PYTHON_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".py$")
|
||||||
|
|
||||||
|
|
||||||
|
# Formatter: black
|
||||||
|
|
||||||
|
printf "> black ... "
|
||||||
|
|
||||||
|
if type black &>/dev/null; then
|
||||||
|
if [ -z "$STAGED_PYTHON_FILES" ]; then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
BLACK_OUTPUT="/tmp/gc-black-output.log"
|
||||||
|
touch $BLACK_OUTPUT
|
||||||
|
|
||||||
|
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' black --check &>$BLACK_OUTPUT; then
|
||||||
|
echo "$STAGED_PYTHON_FILES" | xargs -d'\n' black &>$BLACK_OUTPUT
|
||||||
|
tail -1 $BLACK_OUTPUT
|
||||||
|
formatter_updated=1
|
||||||
|
else
|
||||||
|
printf "OK\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "SKIP: program not found\n"
|
||||||
|
printf "HINT: Install black with 'pip3 install black' (black requires Python>=3.6)\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Formatter: isort
|
||||||
|
|
||||||
|
printf "> isort ... "
|
||||||
|
|
||||||
|
if type isort &>/dev/null; then
|
||||||
|
if [ -z "$STAGED_PYTHON_FILES" ]; then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
ISORT_OUTPUT="/tmp/gc-isort-output.log"
|
||||||
|
touch $ISORT_OUTPUT
|
||||||
|
|
||||||
|
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' isort --check &>$ISORT_OUTPUT; then
|
||||||
|
echo "$STAGED_PYTHON_FILES" | xargs -d'\n' isort &>$ISORT_OUTPUT
|
||||||
|
printf "Reformatted.\n"
|
||||||
|
formatter_updated=1
|
||||||
|
else
|
||||||
|
printf "OK\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "SKIP: program not found\n"
|
||||||
|
printf "HINT: Install isort with 'pip install isort'\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Checker: flake8
|
||||||
|
|
||||||
|
printf "> flake8 ... "
|
||||||
|
|
||||||
|
if type flake8 &>/dev/null; then
|
||||||
|
if [ -z "$STAGED_PYTHON_FILES" ]; then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
FLAKE8_OUTPUT="/tmp/gc-flake8-output.log"
|
||||||
|
touch $FLAKE8_OUTPUT
|
||||||
|
|
||||||
|
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' flake8 &>$FLAKE8_OUTPUT; then
|
||||||
|
printf "FAIL\n"
|
||||||
|
cat $FLAKE8_OUTPUT
|
||||||
|
checker_dirty=1
|
||||||
|
else
|
||||||
|
printf "OK\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "SKIP: program not found\n"
|
||||||
|
printf "HINT: Install flake8 with 'pip install flake8'\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# End
|
||||||
|
|
||||||
|
if [ $checker_dirty -ne 0 ]
|
||||||
|
then
|
||||||
|
printf ">>> Checker(s) detect(s) issue(s)\n"
|
||||||
|
printf " You can still commit and push :)\n"
|
||||||
|
printf " Be warned that our CI may cause you more trouble.\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $formatter_updated -ne 0 ]
|
||||||
|
then
|
||||||
|
printf ">>> Working tree updated by formatter(s)\n"
|
||||||
|
printf " Add changes to staging area and retry.\n"
|
||||||
|
exit_code=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
exit $exit_code
|
298
CHANGELOG.md
Normal file
298
CHANGELOG.md
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
Liste des changements notables dans GestioCOF depuis la version 0.1 (septembre
|
||||||
|
2018).
|
||||||
|
|
||||||
|
## Le FUTUR ! (pas prêt pour la prod)
|
||||||
|
|
||||||
|
### Nouveau module de gestion des événements
|
||||||
|
|
||||||
|
- Désormais complet niveau modèles
|
||||||
|
- Export des participants implémenté
|
||||||
|
|
||||||
|
#### TODO
|
||||||
|
|
||||||
|
- Vue de création d'événements ergonomique
|
||||||
|
- Vue d'inscription à un événement **ou** intégration propre dans la vue
|
||||||
|
"inscription d'un nouveau membre"
|
||||||
|
|
||||||
|
### Nouveau module de gestion des clubs
|
||||||
|
|
||||||
|
Uniquement un modèle simple de clubs avec des respos. Aucune gestion des
|
||||||
|
adhérents ni des cotisations.
|
||||||
|
|
||||||
|
## TODO Prod
|
||||||
|
|
||||||
|
- Créer un compte hCaptcha (https://www.hcaptcha.com/), au COF, et remplacer les secrets associés
|
||||||
|
|
||||||
|
## Version ??? - ??/??/????
|
||||||
|
|
||||||
|
## Version 0.15.1 - 15/06/2023
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- Rattrape les erreurs d'envoi de mail de négatif
|
||||||
|
- Utilise l'adresse chefs pour les envois de négatifs
|
||||||
|
|
||||||
|
## Version 0.15 - 22/05/2023
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- Rajoute un formulaire de contact
|
||||||
|
- Rajoute un formulaire de demande de soirée
|
||||||
|
- Désactive les mails d'envoi de négatifs sur les comptes gelés
|
||||||
|
|
||||||
|
## Version 0.14 - 19/05/2023
|
||||||
|
|
||||||
|
- Répare les dépendances en spécifiant toutes les versions
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- Répare la gestion des changement d'heure via moment.js
|
||||||
|
|
||||||
|
## Version 0.13 - 19/02/2023
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- Rajoute la valeur des inventaires
|
||||||
|
- Résout les problèmes de négatif ne disparaissant pas
|
||||||
|
- Affiche son surnom s'il y en a un
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
|
## Version 0.12.1 - 03/10/2022
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- Fixe un problème de rendu causé par l'agrandissement du menu
|
||||||
|
|
||||||
|
## Version 0.12 - 17/06/2022
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- Ajoute une exception à la limite d'historique pour les comptes `LIQ` et `#13`
|
||||||
|
- Répare le problème des étiquettes LIQ/Comptes K-Fêt inversées dans les stats des articles K-Fêt
|
||||||
|
|
||||||
|
## Version 0.11 - 26/10/2021
|
||||||
|
|
||||||
|
### COF
|
||||||
|
|
||||||
|
- Répare un problème de rendu sur le wagtail du COF
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- Ajoute de mails de rappels pour les comptes en négatif
|
||||||
|
- La recherche de comptes sur K-Psul remarche normalement
|
||||||
|
- Le pointeur de la souris change de forme quand on survole un item d'autocomplétion
|
||||||
|
- Modification du gel de compte:
|
||||||
|
- on ne peut plus geler/dégeler son compte soi-même (il faut la permission "Gérer les permissions K-Fêt")
|
||||||
|
- on ne peut rien compter sur un compte gelé (aucune override possible), et les K-Fêteux·ses dont le compte est gelé perdent tout accès à K-Psul
|
||||||
|
- les comptes actuellement gelés (sur l'ancien système) sont dégelés automatiquement
|
||||||
|
- Modification du fonctionnement des négatifs
|
||||||
|
- impossible d'avoir des négatifs inférieurs à `kfet_config.overdraft_amount`
|
||||||
|
- il n'y a plus de limite de temps sur les négatifs
|
||||||
|
- supression des autorisations de négatif
|
||||||
|
- il n'est plus possible de réinitialiser la durée d'un négatif en faisant puis en annulant une charge
|
||||||
|
- La gestion des erreurs passe du client au serveur, ce qui permet d'avoir des messages plus explicites
|
||||||
|
- La supression d'opérations anciennes est réparée
|
||||||
|
|
||||||
|
## Version 0.10 - 18/04/2021
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- On fait sauter la limite qui empêchait de vendre plus de 24 unités d'un item à
|
||||||
|
la fois.
|
||||||
|
- L'interface indique plus clairement quand on fait une erreur en modifiant un
|
||||||
|
compte.
|
||||||
|
- On supprime la fonction "décalage de balance".
|
||||||
|
- L'accès à l'historique est maintenant limité à 7 jours pour raison de
|
||||||
|
confidentialité. Les chefs/trez peuvent disposer d'une permission
|
||||||
|
supplémentaire pour accéder à jusqu'à 30 jours en cas de problème de compta.
|
||||||
|
L'accès à son historique personnel n'est pas limité. Les durées sont
|
||||||
|
configurables dans `settings/cof_prod.py`.
|
||||||
|
|
||||||
|
### COF
|
||||||
|
|
||||||
|
- Le Captcha sur la page de demande de petits cours utilise maintenant hCaptcha
|
||||||
|
au lieu de ReCaptcha, pour mieux respecter la vie privée des utilisateur·ices
|
||||||
|
|
||||||
|
## Version 0.9 - 06/02/2020
|
||||||
|
|
||||||
|
### COF / BdA
|
||||||
|
|
||||||
|
- Le COF peut remettre à zéro la liste de ses adhérents en août (sans passer par
|
||||||
|
KDE).
|
||||||
|
- La page d'accueil affiche la date de fermeture des tirages BdA.
|
||||||
|
- On peut revendre une place dès qu'on l'a payée, plus besoin de payer toutes
|
||||||
|
ses places pour pouvoir revendre.
|
||||||
|
- On s'assure que l'email fourni lors d'une demande de petit cours est valide.
|
||||||
|
|
||||||
|
### BDS
|
||||||
|
|
||||||
|
- Le burô peut maintenant accorder ou révoquer le statut de membre du Burô
|
||||||
|
en modifiant le profil d'un membre du BDS.
|
||||||
|
- Le burô peut exporter la liste de ses membres avec email au format CSV depuis
|
||||||
|
la page d'accueil.
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- On affiche les articles actuellement en vente en premier lors des inventaires
|
||||||
|
et des commandes.
|
||||||
|
- On peut supprimer un inventaire. Seuls les articles dont c'est le dernier
|
||||||
|
inventaire sont affectés.
|
||||||
|
|
||||||
|
## Version 0.8 - 03/12/2020
|
||||||
|
|
||||||
|
### COF
|
||||||
|
|
||||||
|
- La page "Mes places" dans la section BdA indique quelles places sont sur
|
||||||
|
listing.
|
||||||
|
- ergonomie de l'interface admin du BdA : moins d'options inutiles lors de
|
||||||
|
la sélection de participants.
|
||||||
|
- les tirages sont maintenant archivables pour éviter d'avoir encore d'autres
|
||||||
|
options inutiles.
|
||||||
|
- l'autocomplétion dans l'admin BdA est réparée.
|
||||||
|
- Les icones de la page de gestion des petits cours sont (à nouveau) réparées.
|
||||||
|
- On a supprimé la possibilité de modifier les mails automatiques depuis
|
||||||
|
l'interface admin car trop problématique. Faute de mieux, envoyer un mail à
|
||||||
|
KDE pour modifier ces mails.
|
||||||
|
- corrige un crash sporadique sur la page d'inscription au système de petits
|
||||||
|
cours
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- (fix partiel) Empêche la K-Fêt de modifier des données COF (e.g. nom, prénom,
|
||||||
|
username) lors de la création d'un nouveau compte.
|
||||||
|
- Les statistiques de conso globales montrent deux courbes COF / non-COF au
|
||||||
|
lieu de LIQ / sur compte.
|
||||||
|
- Un bug empêchait de fermer manuellement la K-Fêt depuis un compte non
|
||||||
|
privilégié en tapant un mot de passe. C'est corrigé.
|
||||||
|
|
||||||
|
## Version 0.7.2 - 08/09/2020
|
||||||
|
|
||||||
|
- Nouvelle page 404
|
||||||
|
- Correction de bug en K-Fêt : le lien pour créer un nouveau compte exté apparaît
|
||||||
|
à nouveau dans l'autocomplétion
|
||||||
|
|
||||||
|
## Version 0.7.1 - 05/09/2020
|
||||||
|
|
||||||
|
Petits ajustements sur le site du COF :
|
||||||
|
|
||||||
|
- Possibilité d'ajouter des champs d'infos supplémentaires en plus de l'email et
|
||||||
|
de la page web dans les annuaires (clubs et partenaires).
|
||||||
|
- Corrige un bug d'affichage des adresses emails de clubs
|
||||||
|
|
||||||
|
## Version 0.7 - 29/08/2020
|
||||||
|
|
||||||
|
### GestioBDS
|
||||||
|
|
||||||
|
- Ajout d'un bouton pour supprimer un compte
|
||||||
|
- Le nombre d'adhérent⋅es est affiché sur la page d'accueil
|
||||||
|
- le groupe BDS a les bonnes permissions
|
||||||
|
|
||||||
|
### Site du COF
|
||||||
|
|
||||||
|
- Captcha fonctionnel pour les mailing-listes
|
||||||
|
|
||||||
|
### K-Fêt
|
||||||
|
|
||||||
|
- L'autocomplétion pour la création de compte K-Fêt se lance à 3 caractères seulement,
|
||||||
|
donc est plus rapide.
|
||||||
|
|
||||||
|
## Version 0.6 - 27/07/2020
|
||||||
|
|
||||||
|
Arrivée du BDS !
|
||||||
|
GestioCOF et GestioBDS ont du code en commun mais tournent de façon séparée, les
|
||||||
|
deux bases de données sont distinctes.
|
||||||
|
|
||||||
|
## Version 0.5 - 11/07/2020
|
||||||
|
|
||||||
|
### Problèmes corrigés
|
||||||
|
|
||||||
|
- La recherche d'utilisateurices (COF + K-Fêt) fonctionne de nouveau
|
||||||
|
- Bug d'affichage quand on a beaucoup de clubs dans le cadre "Accès rapide" sur
|
||||||
|
la page des clubs (nouveau site du COF)
|
||||||
|
- Version mobile plus ergonimique sur le nouveau site du COF
|
||||||
|
- Cliquer sur "visualiser" sur les pages de clubs dans wagtail ne provoque plus
|
||||||
|
d'erreurs 500 (nouveau site du COF)
|
||||||
|
- L'historique des ventes des articles K-Fêt fonctionne à nouveau
|
||||||
|
- Les montants en K-Fêt sont à nouveau affichés en UKF (et non en €).
|
||||||
|
- Les boutons "afficher/cacher" des mails et noms des participant⋅e⋅s à un
|
||||||
|
spectacle BdA fonctionnent à nouveau.
|
||||||
|
- on ne peut plus compter de consos sur ☠☠☠, ni éditer les comptes spéciaux
|
||||||
|
(LIQ, GNR, ☠☠☠, #13).
|
||||||
|
|
||||||
|
### Nouvelles fonctionnalités
|
||||||
|
|
||||||
|
- On n'affiche que 4 articles sur la pages "nouveautés" (nouveau site du COF)
|
||||||
|
- Plus de traductions sur le nouveau site du COF
|
||||||
|
- Les transferts apparaissent maintenant dans l'historique K-Fêt et l'historique
|
||||||
|
personnel.
|
||||||
|
- les statistiques K-Fêt remontent à plus d'un an (et le code est simplifié)
|
||||||
|
|
||||||
|
## Version 0.4.1 - 17/01/2020
|
||||||
|
|
||||||
|
- Corrige un bug sur K-Psul lorsqu'un trigramme contient des caractères réservés
|
||||||
|
aux urls (\#, /...)
|
||||||
|
|
||||||
|
## Version 0.4 - 15/01/2020
|
||||||
|
|
||||||
|
- Corrige un bug d'affichage d'images sur l'interface des petits cours
|
||||||
|
- La page des transferts permet de créer un nombre illimité de transferts en
|
||||||
|
une fois.
|
||||||
|
- Nouveau site du COF : les liens sont optionnels dans les descriptions de clubs
|
||||||
|
- Mise à jour du lien vers le calendire de la K-Fêt sur la page d'accueil
|
||||||
|
- Certaines opérations sont à nouveau accessibles depuis la session partagée
|
||||||
|
K-Fêt.
|
||||||
|
- Le bouton "déconnexion" déconnecte vraiment du CAS pour les comptes clipper
|
||||||
|
- Corrige un crash sur la page des reventes pour les nouveaux participants.
|
||||||
|
- Corrige un bug d'affichage pour les trigrammes avec caractères spéciaux
|
||||||
|
|
||||||
|
## Version 0.3.3 - 30/11/2019
|
||||||
|
|
||||||
|
- Corrige un problème de redirection lors de la déconnexion (CAS seulement)
|
||||||
|
- Les catégories d'articles K-Fêt peuvent être exemptées de subvention COF
|
||||||
|
- Corrige un bug d'affichage dans K-Psul quand on annule une transaction sur LIQ
|
||||||
|
- Corrige une privilege escalation liée aux sessions partagées en K-Fêt
|
||||||
|
https://git.eleves.ens.fr/klub-dev-ens/gestioCOF/issues/240
|
||||||
|
|
||||||
|
## Version 0.3.2 - 04/11/2019
|
||||||
|
|
||||||
|
- Bugfix: modifier un compte K-Fêt ne supprime plus nom/prénom
|
||||||
|
|
||||||
|
## Version 0.3.1 - 19/10/2019
|
||||||
|
|
||||||
|
- Bugfix: l'historique des utilisateurices s'affiche à nouveau
|
||||||
|
|
||||||
|
## Version 0.3 - 16/10/2019
|
||||||
|
|
||||||
|
- Comptes extés: lien pour changer son mot de passe sur la page d'accueil
|
||||||
|
- Les utilisateurices non-COF peuvent éditer leur profil
|
||||||
|
- Un peu de pub pour KDEns sur la page d'accueil
|
||||||
|
- Fix erreur 500 sur /bda/revente/<tirage_id>/manage
|
||||||
|
- Si on essaie d'accéder au compte que qqn d'autre on a une 404 (et plus une 403)
|
||||||
|
- On ne peut plus modifier des comptes COF depuis l'interface K-Fêt
|
||||||
|
- Le champ de paiement BdA se fait au niveau des attributions
|
||||||
|
- Affiche un message d'erreur plutôt que de crasher si échec de l'envoi du mail
|
||||||
|
de bienvenue aux nouveaux membres
|
||||||
|
- On peut supprimer des comptes et des articles K-Fêt
|
||||||
|
- Passage à Django2
|
||||||
|
- Dev : on peut désactiver la barre de debug avec une variable shell
|
||||||
|
- Remplace les CSS de Google par des polices de proximité
|
||||||
|
- Passage du site du COF et de la K-Fêt en Wagtail 2.3 et Wagtail-modeltranslation 0.9
|
||||||
|
- Ajoute un lien vers l'administration générale depuis les petits cours
|
||||||
|
- Abandon de l'ancien catalogue BdA (déjà plus utilisé depuis longtemps)
|
||||||
|
- Force l'unicité des logins clipper
|
||||||
|
- Nouveau site du COF en wagtail
|
||||||
|
- Meilleurs affichage des longues listes de spectacles à cocher dans BdA-Revente
|
||||||
|
- Bugfix : les pages de la revente ne sont plus accessibles qu'aux membres du
|
||||||
|
COF
|
||||||
|
|
||||||
|
## Version 0.2 - 07/11/2018
|
||||||
|
|
||||||
|
- Corrections de bugs d'interface dans l'inscription aux tirages BdA
|
||||||
|
- On peut annuler une revente à tout moment
|
||||||
|
- Pleiiiiin de tests
|
||||||
|
|
||||||
|
## Version 0.1 - 09/09/2018
|
||||||
|
|
||||||
|
Début de la numérotation des versions, début du changelog
|
240
README.md
240
README.md
|
@ -1,10 +1,79 @@
|
||||||
# GestioCOF
|
# GestioCOF / GestioBDS
|
||||||
|
|
||||||
|
[![pipeline status](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/pipeline.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
|
||||||
|
[![coverage report](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/coverage.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Il est possible d'installer GestioCOF sur votre machine de deux façons différentes :
|
||||||
|
|
||||||
|
- L'[installation manuelle](#installation-manuelle) (**recommandée** sous linux et OSX), plus légère
|
||||||
|
- L'[installation via vagrant](#vagrant) qui fonctionne aussi sous windows mais un peu plus lourde
|
||||||
|
|
||||||
|
### Installation manuelle
|
||||||
|
|
||||||
|
Il est fortement conseillé d'utiliser un environnement virtuel pour Python.
|
||||||
|
|
||||||
|
Il vous faudra installer pip, les librairies de développement de python ainsi
|
||||||
|
que sqlite3, un moteur de base de données léger et simple d'utilisation. Sous
|
||||||
|
Debian et dérivées (Ubuntu, ...) :
|
||||||
|
|
||||||
|
sudo apt-get install python3-pip python3-dev python3-venv sqlite3
|
||||||
|
|
||||||
|
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
||||||
|
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
||||||
|
(le dossier où se trouve ce README), et créez-le maintenant :
|
||||||
|
|
||||||
|
python3 -m venv venv
|
||||||
|
|
||||||
|
Pour l'activer, il faut taper
|
||||||
|
|
||||||
|
. venv/bin/activate
|
||||||
|
|
||||||
|
depuis le même dossier.
|
||||||
|
|
||||||
|
Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
||||||
|
`requirements-devel.txt` :
|
||||||
|
|
||||||
|
pip install -U pip # parfois nécessaire la première fois
|
||||||
|
pip install -r requirements-devel.txt
|
||||||
|
|
||||||
|
Pour terminer, copier le fichier `gestioasso/settings/secret_example.py` vers
|
||||||
|
`gestioasso/settings/secret.py`. Sous Linux ou Mac, préférez plutôt un lien symbolique
|
||||||
|
pour profiter de façon transparente des mises à jour du fichier:
|
||||||
|
|
||||||
|
ln -s secret_example.py gestioasso/settings/secret.py
|
||||||
|
|
||||||
|
Nous avons un git hook de pre-commit pour formatter et vérifier que votre code
|
||||||
|
vérifie nos conventions. Pour bénéficier des mises à jour du hook, préférez
|
||||||
|
encore l'installation *via* un lien symbolique:
|
||||||
|
|
||||||
|
ln -s ../../.pre-commit.sh .git/hooks/pre-commit
|
||||||
|
|
||||||
|
Pour plus d'informations à ce sujet, consulter la
|
||||||
|
[page](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/coding-style)
|
||||||
|
du wiki gestioCOF liée aux conventions.
|
||||||
|
|
||||||
|
|
||||||
|
#### Fin d'installation
|
||||||
|
|
||||||
|
Il ne vous reste plus qu'à initialiser les modèles de Django et peupler la base
|
||||||
|
de donnée avec les données nécessaires au bon fonctionnement de GestioCOF + des
|
||||||
|
données bidons bien pratiques pour développer avec la commande suivante :
|
||||||
|
|
||||||
|
bash provisioning/prepare_django.sh
|
||||||
|
|
||||||
|
Voir le paragraphe ["outils pour développer"](#outils-pour-d-velopper) plus bas
|
||||||
|
pour plus de détails.
|
||||||
|
|
||||||
|
Vous êtes prêts à développer ! Lancer GestioCOF en faisant
|
||||||
|
|
||||||
|
python manage.py runserver
|
||||||
|
|
||||||
|
|
||||||
### Vagrant
|
### Vagrant
|
||||||
|
|
||||||
La façon recommandée d'installer GestioCOF sur votre machine est d'utiliser
|
Une autre façon d'installer GestioCOF sur votre machine est d'utiliser
|
||||||
[Vagrant](https://www.vagrantup.com/). Vagrant permet de créer une machine
|
[Vagrant](https://www.vagrantup.com/). Vagrant permet de créer une machine
|
||||||
virtuelle minimale sur laquelle tournera GestioCOF; ainsi on s'assure que tout
|
virtuelle minimale sur laquelle tournera GestioCOF; ainsi on s'assure que tout
|
||||||
le monde à la même configuration de développement (même sous Windows !), et
|
le monde à la même configuration de développement (même sous Windows !), et
|
||||||
|
@ -66,137 +135,72 @@ car par défaut Django n'écoute que sur l'adresse locale de la machine virtuell
|
||||||
or vous voudrez accéder à GestioCOF depuis votre machine physique. L'url à
|
or vous voudrez accéder à GestioCOF depuis votre machine physique. L'url à
|
||||||
entrer dans le navigateur est `localhost:8000`.
|
entrer dans le navigateur est `localhost:8000`.
|
||||||
|
|
||||||
|
|
||||||
#### Serveur de développement type production
|
#### Serveur de développement type production
|
||||||
|
|
||||||
Sur la VM Vagrant, un serveur apache est configuré pour servir GestioCOF de
|
Juste histoire de jouer, pas indispensable pour développer :
|
||||||
façon similaire à la version en production : on utilise
|
|
||||||
|
La VM Vagrant héberge en plus un serveur nginx configuré pour servir GestioCOF
|
||||||
|
comme en production : on utilise
|
||||||
[Daphne](https://github.com/django/daphne/) et `python manage.py runworker`
|
[Daphne](https://github.com/django/daphne/) et `python manage.py runworker`
|
||||||
derrière un reverse-proxy apache. Le tout est monitoré par
|
derrière un reverse-proxy nginx.
|
||||||
[supervisor](http://supervisord.org/).
|
|
||||||
|
|
||||||
Ce serveur se lance tout seul et est accessible en dehors de la VM à l'url
|
Ce serveur se lance tout seul et est accessible en dehors de la VM à l'url
|
||||||
`localhost:8080`. Toutefois il ne se recharge pas tout seul lorsque le code
|
`localhost:8080/gestion/`. Toutefois il ne se recharge pas tout seul lorsque le
|
||||||
change, il faut relancer le worker avec `sudo supervisorctl restart worker` pour
|
code change, il faut relancer le worker avec `sudo systemctl restart
|
||||||
visualiser la dernière version du code.
|
worker.service` pour visualiser la dernière version du code.
|
||||||
|
|
||||||
### Installation manuelle
|
|
||||||
|
|
||||||
Si vous optez pour une installation manuelle plutôt que d'utiliser Vagrant, il
|
|
||||||
est fortement conseillé d'utiliser un environnement virtuel pour Python.
|
|
||||||
|
|
||||||
Il vous faudra installer pip, les librairies de développement de python, un
|
|
||||||
client et un serveur MySQL ainsi qu'un serveur redis ; sous Debian et dérivées
|
|
||||||
(Ubuntu, ...) :
|
|
||||||
|
|
||||||
sudo apt-get install python-pip python-dev libmysqlclient-dev redis-server
|
|
||||||
|
|
||||||
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
|
||||||
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
|
||||||
(le dossier où se trouve ce README), et créez-le maintenant :
|
|
||||||
|
|
||||||
virtualenv env -p $(which python3)
|
|
||||||
|
|
||||||
L'option `-p` sert à préciser l'exécutable python à utiliser. Vous devez choisir
|
|
||||||
python3, si c'est la version de python par défaut sur votre système, ceci n'est
|
|
||||||
pas nécessaire. Pour l'activer, il faut faire
|
|
||||||
|
|
||||||
. env/bin/activate
|
|
||||||
|
|
||||||
dans le même dossier.
|
|
||||||
|
|
||||||
Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
|
||||||
`requirements-devel.txt` :
|
|
||||||
|
|
||||||
pip install -r requirements-devel.txt
|
|
||||||
|
|
||||||
Copiez le fichier `cof/settings_dev.py` dans `cof/settings.py`.
|
|
||||||
|
|
||||||
#### Installation avec MySQL
|
|
||||||
|
|
||||||
Il faut maintenant installer MySQL. Si vous n'avez pas déjà MySQL installé sur
|
|
||||||
votre serveur, il faut l'installer ; sous Debian et dérivées (Ubuntu, ...) :
|
|
||||||
|
|
||||||
sudo apt-get install mysql-server
|
|
||||||
|
|
||||||
Il vous demandera un mot de passe pour le compte d'administration MySQL,
|
|
||||||
notez-le quelque part (ou n'en mettez pas, le serveur n'est accessible que
|
|
||||||
localement par défaut). Si vous utilisez une autre distribution, consultez la
|
|
||||||
documentation de votre distribution pour savoir comment changer ce mot de passe
|
|
||||||
et démarrer le serveur MySQL (c'est automatique sous Ubuntu).
|
|
||||||
|
|
||||||
Vous devez alors créer un utilisateur local et une base `cof_gestion`, avec le
|
|
||||||
mot de passe de votre choix (remplacez `mot_de_passe`) :
|
|
||||||
|
|
||||||
mysql -uroot -e "CREATE DATABASE cof_gestion; GRANT ALL PRIVILEGES ON cof_gestion.* TO 'cof_gestion'@'localhost' IDENTIFIER BY 'mot_de_passe'"
|
|
||||||
|
|
||||||
Éditez maintenant le fichier `cof/settings.py` pour y intégrer ces changements ;
|
|
||||||
la définition de `DATABASES` doit ressembler à (à nouveau, remplacez
|
|
||||||
`mot_de_passe` de façon appropriée) :
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
|
||||||
'NAME': 'cof_gestion',
|
|
||||||
'USER': 'cof_gestion',
|
|
||||||
'PASSWORD': 'mot_de_passe',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#### Installation avec SQLite
|
|
||||||
|
|
||||||
GestioCOF est installé avec MySQL sur la VM COF, et afin d'avoir un
|
|
||||||
environnement de développement aussi proche que possible de ce qui tourne en
|
|
||||||
vrai pour éviter les mauvaises surprises, il est conseillé d'utiliser MySQL sur
|
|
||||||
votre machine de développement également. Toutefois, GestioCOF devrait
|
|
||||||
fonctionner avec d'autres moteurs SQL, et certains préfèrent utiliser SQLite
|
|
||||||
pour sa légèreté et facilité d'installation.
|
|
||||||
|
|
||||||
Si vous décidez d'utiliser SQLite, il faut l'installer ; sous Debian et dérivées :
|
|
||||||
|
|
||||||
sudo apt-get install sqlite3
|
|
||||||
|
|
||||||
puis éditer le fichier `cof/settings.py` pour que la définition de `DATABASES`
|
|
||||||
ressemble à :
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#### Fin d'installation
|
|
||||||
|
|
||||||
Il ne vous reste plus qu'à initialiser les modèles de Django avec la commande suivante :
|
|
||||||
|
|
||||||
python manage.py migrate
|
|
||||||
|
|
||||||
Charger les mails indispensables au bon fonctionnement de GestioCOF :
|
|
||||||
|
|
||||||
python manage.py syncmails
|
|
||||||
|
|
||||||
Une base de donnée pré-remplie est disponible en lançant les commandes :
|
|
||||||
|
|
||||||
python manage.py loaddata gestion sites accounts groups articles
|
|
||||||
python manage.py loaddevdata
|
|
||||||
|
|
||||||
Vous êtes prêts à développer ! Lancer GestioCOF en faisant
|
|
||||||
|
|
||||||
python manage.py runserver
|
|
||||||
|
|
||||||
### Mise à jour
|
### Mise à jour
|
||||||
|
|
||||||
Pour mettre à jour les paquets Python, utiliser la commande suivante :
|
Pour mettre à jour les paquets Python, utiliser la commande suivante :
|
||||||
|
|
||||||
pip install --upgrade -r requirements.txt -r requirements-devel.txt
|
pip install --upgrade -r requirements-devel.txt
|
||||||
|
|
||||||
Pour mettre à jour les modèles après une migration, il faut ensuite faire :
|
Pour mettre à jour les modèles après une migration, il faut ensuite faire :
|
||||||
|
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
|
|
||||||
|
|
||||||
|
## Outils pour développer
|
||||||
|
|
||||||
|
### Base de donnée
|
||||||
|
|
||||||
|
Quelle que soit la méthode d'installation choisie, la base de donnée locale est
|
||||||
|
peuplée avec des données artificielles pour faciliter le développement.
|
||||||
|
|
||||||
|
- Un compte `root` (mot de passe `root`) avec tous les accès est créé. Connectez
|
||||||
|
vous sur ce compte pour accéder à tout GestioCOF.
|
||||||
|
- Des comptes utilisateurs COF et non-COF sont créés ainsi que quelques
|
||||||
|
spectacles BdA et deux tirages au sort pour jouer avec les fonctionnalités du BdA.
|
||||||
|
- À chaque compte est associé un trigramme K-Fêt
|
||||||
|
- Un certain nombre d'articles K-Fêt sont renseignés.
|
||||||
|
|
||||||
|
### Tests unitaires
|
||||||
|
|
||||||
|
On écrit désormais des tests unitaires qui sont lancés automatiquement sur gitlab
|
||||||
|
à chaque push. Il est conseillé de lancer les tests sur sa machine avant de proposer un patch pour s'assurer qu'on ne casse pas une fonctionnalité existante.
|
||||||
|
|
||||||
|
Pour lancer les tests :
|
||||||
|
|
||||||
|
```
|
||||||
|
python manage.py test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Astuces
|
||||||
|
|
||||||
|
- En développement on utilise la django debug toolbar parce que c'est utile pour
|
||||||
|
débuguer les templates ou les requêtes SQL mais des fois c'est pénible parce
|
||||||
|
ça fait ramer GestioCOF (surtout dans wagtail).
|
||||||
|
Vous pouvez la désactiver temporairement en définissant la variable
|
||||||
|
d'environnement `DJANGO_NO_DDT` dans votre shell : par exemple dans
|
||||||
|
bash/zsh/…:
|
||||||
|
```
|
||||||
|
$ export DJANGO_NO_DDT=1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Documentation utilisateur
|
## Documentation utilisateur
|
||||||
|
|
||||||
Une brève documentation utilisateur pour se familiariser plus vite avec l'outil
|
Une brève documentation utilisateur est accessible sur le
|
||||||
est accessible sur le
|
[wiki](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/home) pour avoir une
|
||||||
[wiki](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/home).
|
idée de la façon dont le COF utilise GestioCOF.
|
||||||
|
|
42
Vagrantfile
vendored
42
Vagrantfile
vendored
|
@ -1,47 +1,19 @@
|
||||||
# -*- mode: ruby -*-
|
# -*- mode: ruby -*-
|
||||||
# vi: set ft=ruby :
|
# vi: set ft=ruby :
|
||||||
|
|
||||||
# All Vagrant configuration is done below. The "2" in Vagrant.configure
|
# Configuration de base pour GestioCOF.
|
||||||
# configures the configuration version (we support older styles for
|
# Voir https://docs.vagrantup.com pour plus d'informations.
|
||||||
# backwards compatibility). Please don't change it unless you know what
|
|
||||||
# you're doing.
|
|
||||||
Vagrant.configure(2) do |config|
|
Vagrant.configure(2) do |config|
|
||||||
# The most common configuration options are documented and commented below.
|
# On se base sur Debian 10 (Buster) pour avoir le même environnement qu'en
|
||||||
# For a complete reference, please see the online documentation at
|
# production.
|
||||||
# https://docs.vagrantup.com.
|
config.vm.box = "debian/contrib-buster64"
|
||||||
|
|
||||||
config.vm.box = "ubuntu/xenial64"
|
|
||||||
|
|
||||||
# On associe le port 80 dans la machine virtuelle avec le port 8080 de notre
|
# On associe le port 80 dans la machine virtuelle avec le port 8080 de notre
|
||||||
# ordinateur, et le port 8000 avec le port 8000.
|
# ordinateur, et le port 8000 avec le port 8000.
|
||||||
config.vm.network :forwarded_port, guest: 80, host: 8080
|
config.vm.network :forwarded_port, guest: 80, host: 8080
|
||||||
config.vm.network :forwarded_port, guest: 8000, host: 8000
|
config.vm.network :forwarded_port, guest: 8000, host: 8000
|
||||||
|
|
||||||
# Create a private network, which allows host-only access to the machine
|
# Le restes de la configuration (installation de paquets, etc) est géré un
|
||||||
# using a specific IP.
|
# script shell.
|
||||||
# config.vm.network "private_network", ip: "192.168.33.10"
|
|
||||||
|
|
||||||
# Provider-specific configuration so you can fine-tune various
|
|
||||||
# backing providers for Vagrant. These expose provider-specific options.
|
|
||||||
# Example for VirtualBox:
|
|
||||||
#
|
|
||||||
# config.vm.provider "virtualbox" do |vb|
|
|
||||||
# # Display the VirtualBox GUI when booting the machine
|
|
||||||
# vb.gui = true
|
|
||||||
#
|
|
||||||
# # Customize the amount of memory on the VM:
|
|
||||||
# vb.memory = "1024"
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# View the documentation for the provider you are using for more
|
|
||||||
# information on available options.
|
|
||||||
|
|
||||||
# Enable provisioning with a shell script. Additional provisioners such as
|
|
||||||
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
|
|
||||||
# documentation for more information about their specific syntax and use.
|
|
||||||
# config.vm.provision "shell", inline: <<-SHELL
|
|
||||||
# sudo apt-get update
|
|
||||||
# sudo apt-get install -y apache2
|
|
||||||
# SHELL
|
|
||||||
config.vm.provision :shell, path: "provisioning/bootstrap.sh"
|
config.vm.provision :shell, path: "provisioning/bootstrap.sh"
|
||||||
end
|
end
|
||||||
|
|
231
bda/admin.py
231
bda/admin.py
|
@ -1,231 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
from custommail.shortcuts import send_mass_custom_mail
|
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.db.models import Sum, Count
|
|
||||||
from django.template.defaultfilters import pluralize
|
|
||||||
from django.utils import timezone
|
|
||||||
from django import forms
|
|
||||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
|
||||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleInline(admin.TabularInline):
|
|
||||||
model = ChoixSpectacle
|
|
||||||
sortable_field_name = "priority"
|
|
||||||
|
|
||||||
|
|
||||||
class AttributionInline(admin.TabularInline):
|
|
||||||
model = Attribution
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
qs = super(AttributionInline, self).get_queryset(request)
|
|
||||||
return qs.filter(spectacle__listing=False)
|
|
||||||
|
|
||||||
|
|
||||||
class AttributionInlineListing(admin.TabularInline):
|
|
||||||
model = Attribution
|
|
||||||
exclude = ('given', )
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
qs = super(AttributionInlineListing, self).get_queryset(request)
|
|
||||||
return qs.filter(spectacle__listing=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ParticipantAdmin(admin.ModelAdmin):
|
|
||||||
inlines = [AttributionInline, AttributionInlineListing]
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
return Participant.objects.annotate(nb_places=Count('attributions'),
|
|
||||||
total=Sum('attributions__price'))
|
|
||||||
|
|
||||||
def nb_places(self, obj):
|
|
||||||
return obj.nb_places
|
|
||||||
nb_places.admin_order_field = "nb_places"
|
|
||||||
nb_places.short_description = "Nombre de places"
|
|
||||||
|
|
||||||
def total(self, obj):
|
|
||||||
tot = obj.total
|
|
||||||
if tot:
|
|
||||||
return "%.02f €" % tot
|
|
||||||
else:
|
|
||||||
return "0 €"
|
|
||||||
total.admin_order_field = "total"
|
|
||||||
total.short_description = "Total à payer"
|
|
||||||
list_display = ("user", "nb_places", "total", "paid", "paymenttype",
|
|
||||||
"tirage")
|
|
||||||
list_filter = ("paid", "tirage")
|
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name')
|
|
||||||
actions = ['send_attribs', ]
|
|
||||||
actions_on_bottom = True
|
|
||||||
list_per_page = 400
|
|
||||||
readonly_fields = ("total",)
|
|
||||||
|
|
||||||
def send_attribs(self, request, queryset):
|
|
||||||
datatuple = []
|
|
||||||
for member in queryset.all():
|
|
||||||
attribs = member.attributions.all()
|
|
||||||
context = {'member': member.user}
|
|
||||||
shortname = ""
|
|
||||||
if len(attribs) == 0:
|
|
||||||
shortname = "bda-attributions-decus"
|
|
||||||
else:
|
|
||||||
shortname = "bda-attributions"
|
|
||||||
context['places'] = attribs
|
|
||||||
print(context)
|
|
||||||
datatuple.append((shortname, context, "bda@ens.fr",
|
|
||||||
[member.user.email]))
|
|
||||||
send_mass_custom_mail(datatuple)
|
|
||||||
count = len(queryset.all())
|
|
||||||
if count == 1:
|
|
||||||
message_bit = "1 membre a"
|
|
||||||
plural = ""
|
|
||||||
else:
|
|
||||||
message_bit = "%d membres ont" % count
|
|
||||||
plural = "s"
|
|
||||||
self.message_user(request, "%s été informé%s avec succès."
|
|
||||||
% (message_bit, plural))
|
|
||||||
send_attribs.short_description = "Envoyer les résultats par mail"
|
|
||||||
|
|
||||||
|
|
||||||
class AttributionAdminForm(forms.ModelForm):
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super(AttributionAdminForm, self).clean()
|
|
||||||
participant = cleaned_data.get("participant")
|
|
||||||
spectacle = cleaned_data.get("spectacle")
|
|
||||||
if participant and spectacle:
|
|
||||||
if participant.tirage != spectacle.tirage:
|
|
||||||
raise forms.ValidationError(
|
|
||||||
"Erreur : le participant et le spectacle n'appartiennent"
|
|
||||||
"pas au même tirage")
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
class AttributionAdmin(admin.ModelAdmin):
|
|
||||||
def paid(self, obj):
|
|
||||||
return obj.participant.paid
|
|
||||||
paid.short_description = 'A payé'
|
|
||||||
paid.boolean = True
|
|
||||||
list_display = ("id", "spectacle", "participant", "given", "paid")
|
|
||||||
search_fields = ('spectacle__title', 'participant__user__username',
|
|
||||||
'participant__user__first_name',
|
|
||||||
'participant__user__last_name')
|
|
||||||
form = AttributionAdminForm
|
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
|
||||||
def tirage(self, obj):
|
|
||||||
return obj.participant.tirage
|
|
||||||
list_display = ("participant", "tirage", "spectacle", "priority",
|
|
||||||
"double_choice")
|
|
||||||
list_filter = ("double_choice", "participant__tirage")
|
|
||||||
search_fields = ('participant__user__username',
|
|
||||||
'participant__user__first_name',
|
|
||||||
'participant__user__last_name',
|
|
||||||
'spectacle__title')
|
|
||||||
|
|
||||||
|
|
||||||
class QuoteInline(admin.TabularInline):
|
|
||||||
model = Quote
|
|
||||||
|
|
||||||
|
|
||||||
class SpectacleAdmin(admin.ModelAdmin):
|
|
||||||
inlines = [QuoteInline]
|
|
||||||
model = Spectacle
|
|
||||||
list_display = ("title", "date", "tirage", "location", "slots", "price",
|
|
||||||
"listing")
|
|
||||||
list_filter = ("location", "tirage",)
|
|
||||||
search_fields = ("title", "location__name")
|
|
||||||
readonly_fields = ("rappel_sent", )
|
|
||||||
|
|
||||||
|
|
||||||
class TirageAdmin(admin.ModelAdmin):
|
|
||||||
model = Tirage
|
|
||||||
list_display = ("title", "ouverture", "fermeture", "active",
|
|
||||||
"enable_do_tirage")
|
|
||||||
readonly_fields = ("tokens", )
|
|
||||||
list_filter = ("active", )
|
|
||||||
search_fields = ("title", )
|
|
||||||
|
|
||||||
|
|
||||||
class SalleAdmin(admin.ModelAdmin):
|
|
||||||
model = Salle
|
|
||||||
search_fields = ('name', 'address')
|
|
||||||
|
|
||||||
|
|
||||||
class SpectacleReventeAdmin(admin.ModelAdmin):
|
|
||||||
"""
|
|
||||||
Administration des reventes de spectacles
|
|
||||||
"""
|
|
||||||
model = SpectacleRevente
|
|
||||||
|
|
||||||
def spectacle(self, obj):
|
|
||||||
"""
|
|
||||||
Raccourci vers le spectacle associé à la revente.
|
|
||||||
"""
|
|
||||||
return obj.attribution.spectacle
|
|
||||||
|
|
||||||
list_display = ("spectacle", "seller", "date", "soldTo")
|
|
||||||
raw_id_fields = ("attribution",)
|
|
||||||
readonly_fields = ("date_tirage",)
|
|
||||||
search_fields = ['attribution__spectacle__title',
|
|
||||||
'seller__user__username',
|
|
||||||
'seller__user__first_name',
|
|
||||||
'seller__user__last_name']
|
|
||||||
|
|
||||||
actions = ['transfer', 'reinit']
|
|
||||||
actions_on_bottom = True
|
|
||||||
|
|
||||||
def transfer(self, request, queryset):
|
|
||||||
"""
|
|
||||||
Effectue le transfert des reventes pour lesquels on connaît l'acheteur.
|
|
||||||
"""
|
|
||||||
reventes = queryset.exclude(soldTo__isnull=True).all()
|
|
||||||
count = reventes.count()
|
|
||||||
for revente in reventes:
|
|
||||||
attrib = revente.attribution
|
|
||||||
attrib.participant = revente.soldTo
|
|
||||||
attrib.save()
|
|
||||||
self.message_user(
|
|
||||||
request,
|
|
||||||
"%d attribution%s %s été transférée%s avec succès." % (
|
|
||||||
count, pluralize(count),
|
|
||||||
pluralize(count, "a,ont"), pluralize(count))
|
|
||||||
)
|
|
||||||
transfer.short_description = "Transférer les reventes sélectionnées"
|
|
||||||
|
|
||||||
def reinit(self, request, queryset):
|
|
||||||
"""
|
|
||||||
Réinitialise les reventes.
|
|
||||||
"""
|
|
||||||
count = queryset.count()
|
|
||||||
for revente in queryset.filter(
|
|
||||||
attribution__spectacle__date__gte=timezone.now()):
|
|
||||||
revente.date = timezone.now() - timedelta(hours=1)
|
|
||||||
revente.soldTo = None
|
|
||||||
revente.notif_sent = False
|
|
||||||
revente.tirage_done = False
|
|
||||||
if revente.answered_mail:
|
|
||||||
revente.answered_mail.clear()
|
|
||||||
revente.save()
|
|
||||||
self.message_user(
|
|
||||||
request,
|
|
||||||
"%d attribution%s %s été réinitialisée%s avec succès." % (
|
|
||||||
count, pluralize(count),
|
|
||||||
pluralize(count, "a,ont"), pluralize(count))
|
|
||||||
)
|
|
||||||
reinit.short_description = "Réinitialiser les reventes sélectionnées"
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(CategorieSpectacle)
|
|
||||||
admin.site.register(Spectacle, SpectacleAdmin)
|
|
||||||
admin.site.register(Salle, SalleAdmin)
|
|
||||||
admin.site.register(Participant, ParticipantAdmin)
|
|
||||||
admin.site.register(Attribution, AttributionAdmin)
|
|
||||||
admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin)
|
|
||||||
admin.site.register(Tirage, TirageAdmin)
|
|
||||||
admin.site.register(SpectacleRevente, SpectacleReventeAdmin)
|
|
107
bda/algorithm.py
107
bda/algorithm.py
|
@ -1,107 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db.models import Max
|
|
||||||
|
|
||||||
import random
|
|
||||||
|
|
||||||
|
|
||||||
class Algorithm(object):
|
|
||||||
|
|
||||||
shows = None
|
|
||||||
ranks = None
|
|
||||||
origranks = None
|
|
||||||
double = None
|
|
||||||
|
|
||||||
def __init__(self, shows, members, choices):
|
|
||||||
"""Initialisation :
|
|
||||||
- on aggrège toutes les demandes pour chaque spectacle dans
|
|
||||||
show.requests
|
|
||||||
- on crée des tables de demandes pour chaque personne, afin de
|
|
||||||
pouvoir modifier les rankings"""
|
|
||||||
self.max_group = \
|
|
||||||
2 * choices.aggregate(Max('priority'))['priority__max']
|
|
||||||
self.shows = []
|
|
||||||
showdict = {}
|
|
||||||
for show in shows:
|
|
||||||
show.nrequests = 0
|
|
||||||
showdict[show] = show
|
|
||||||
show.requests = []
|
|
||||||
self.shows.append(show)
|
|
||||||
self.ranks = {}
|
|
||||||
self.origranks = {}
|
|
||||||
self.choices = {}
|
|
||||||
next_rank = {}
|
|
||||||
member_shows = {}
|
|
||||||
for member in members:
|
|
||||||
self.ranks[member] = {}
|
|
||||||
self.choices[member] = {}
|
|
||||||
next_rank[member] = 1
|
|
||||||
member_shows[member] = {}
|
|
||||||
for choice in choices:
|
|
||||||
member = choice.participant
|
|
||||||
if choice.spectacle in member_shows[member]:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
member_shows[member][choice.spectacle] = True
|
|
||||||
showdict[choice.spectacle].requests.append(member)
|
|
||||||
showdict[choice.spectacle].nrequests += 2 if choice.double else 1
|
|
||||||
self.ranks[member][choice.spectacle] = next_rank[member]
|
|
||||||
next_rank[member] += 2 if choice.double else 1
|
|
||||||
self.choices[member][choice.spectacle] = choice
|
|
||||||
for member in members:
|
|
||||||
self.origranks[member] = dict(self.ranks[member])
|
|
||||||
|
|
||||||
def IncrementRanks(self, member, currank, increment=1):
|
|
||||||
for show in self.ranks[member]:
|
|
||||||
if self.ranks[member][show] > currank:
|
|
||||||
self.ranks[member][show] -= increment
|
|
||||||
|
|
||||||
def appendResult(self, l, member, show):
|
|
||||||
l.append((member,
|
|
||||||
self.ranks[member][show],
|
|
||||||
self.origranks[member][show],
|
|
||||||
self.choices[member][show].double))
|
|
||||||
|
|
||||||
def __call__(self, seed):
|
|
||||||
random.seed(seed)
|
|
||||||
results = []
|
|
||||||
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots,
|
|
||||||
reverse=True)
|
|
||||||
for show in shows:
|
|
||||||
# On regroupe tous les gens ayant le même rang
|
|
||||||
groups = dict([(i, []) for i in range(1, self.max_group + 1)])
|
|
||||||
for member in show.requests:
|
|
||||||
if self.ranks[member][show] == 0:
|
|
||||||
raise RuntimeError(member, show.title)
|
|
||||||
groups[self.ranks[member][show]].append(member)
|
|
||||||
# On passe à l'attribution
|
|
||||||
winners = []
|
|
||||||
losers = []
|
|
||||||
for i in range(1, self.max_group + 1):
|
|
||||||
group = list(groups[i])
|
|
||||||
random.shuffle(group)
|
|
||||||
for member in group:
|
|
||||||
if self.choices[member][show].double: # double
|
|
||||||
if len(winners) + 1 < show.slots:
|
|
||||||
self.appendResult(winners, member, show)
|
|
||||||
self.appendResult(winners, member, show)
|
|
||||||
elif not self.choices[member][show].autoquit \
|
|
||||||
and len(winners) < show.slots:
|
|
||||||
self.appendResult(winners, member, show)
|
|
||||||
self.appendResult(losers, member, show)
|
|
||||||
else:
|
|
||||||
self.appendResult(losers, member, show)
|
|
||||||
self.appendResult(losers, member, show)
|
|
||||||
self.IncrementRanks(member, i, 2)
|
|
||||||
else: # simple
|
|
||||||
if len(winners) < show.slots:
|
|
||||||
self.appendResult(winners, member, show)
|
|
||||||
else:
|
|
||||||
self.appendResult(losers, member, show)
|
|
||||||
self.IncrementRanks(member, i)
|
|
||||||
results.append((show, winners, losers))
|
|
||||||
return results
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class BdAConfig(AppConfig):
|
|
||||||
name = "bda"
|
|
||||||
verbose_name = "Gestion des tirages du BdA"
|
|
83
bda/forms.py
83
bda/forms.py
|
@ -1,83 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.forms.models import BaseInlineFormSet
|
|
||||||
from django.utils import timezone
|
|
||||||
from bda.models import Attribution, Spectacle
|
|
||||||
|
|
||||||
|
|
||||||
class BaseBdaFormSet(BaseInlineFormSet):
|
|
||||||
def clean(self):
|
|
||||||
"""Checks that no two articles have the same title."""
|
|
||||||
super(BaseBdaFormSet, self).clean()
|
|
||||||
if any(self.errors):
|
|
||||||
# Don't bother validating the formset unless each form is valid on
|
|
||||||
# its own
|
|
||||||
return
|
|
||||||
spectacles = []
|
|
||||||
for i in range(0, self.total_form_count()):
|
|
||||||
form = self.forms[i]
|
|
||||||
if not form.cleaned_data:
|
|
||||||
continue
|
|
||||||
spectacle = form.cleaned_data['spectacle']
|
|
||||||
delete = form.cleaned_data['DELETE']
|
|
||||||
if not delete and spectacle in spectacles:
|
|
||||||
raise forms.ValidationError(
|
|
||||||
"Vous ne pouvez pas vous inscrire deux fois pour le "
|
|
||||||
"même spectacle.")
|
|
||||||
spectacles.append(spectacle)
|
|
||||||
|
|
||||||
|
|
||||||
class TokenForm(forms.Form):
|
|
||||||
token = forms.CharField(widget=forms.widgets.Textarea())
|
|
||||||
|
|
||||||
|
|
||||||
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|
||||||
def label_from_instance(self, obj):
|
|
||||||
return "%s" % obj.spectacle
|
|
||||||
|
|
||||||
|
|
||||||
class ResellForm(forms.Form):
|
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
|
||||||
queryset=Attribution.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
|
||||||
super(ResellForm, self).__init__(*args, **kwargs)
|
|
||||||
self.fields['attributions'].queryset = participant.attribution_set\
|
|
||||||
.filter(spectacle__date__gte=timezone.now())\
|
|
||||||
.exclude(revente__seller=participant)
|
|
||||||
|
|
||||||
|
|
||||||
class AnnulForm(forms.Form):
|
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
|
||||||
queryset=Attribution.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
|
||||||
super(AnnulForm, self).__init__(*args, **kwargs)
|
|
||||||
self.fields['attributions'].queryset = participant.attribution_set\
|
|
||||||
.filter(spectacle__date__gte=timezone.now(),
|
|
||||||
revente__isnull=False,
|
|
||||||
revente__date__gt=timezone.now()-timedelta(hours=1),
|
|
||||||
revente__soldTo__isnull=True)
|
|
||||||
|
|
||||||
|
|
||||||
class InscriptionReventeForm(forms.Form):
|
|
||||||
spectacles = forms.ModelMultipleChoiceField(
|
|
||||||
queryset=Spectacle.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, tirage, *args, **kwargs):
|
|
||||||
super(InscriptionReventeForm, self).__init__(*args, **kwargs)
|
|
||||||
self.fields['spectacles'].queryset = tirage.spectacle_set.filter(
|
|
||||||
date__gte=timezone.now())
|
|
|
@ -1,108 +0,0 @@
|
||||||
"""
|
|
||||||
Crée deux tirages de test et y inscrit les utilisateurs
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
|
|
||||||
from cof.management.base import MyBaseCommand
|
|
||||||
from bda.models import Tirage, Spectacle, Salle, Participant, ChoixSpectacle
|
|
||||||
from bda.views import do_tirage
|
|
||||||
|
|
||||||
|
|
||||||
# Où sont stockés les fichiers json
|
|
||||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
|
||||||
'data')
|
|
||||||
|
|
||||||
|
|
||||||
class Command(MyBaseCommand):
|
|
||||||
help = "Crée deux tirages de test et y inscrit les utilisateurs."
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
# ---
|
|
||||||
# Tirages
|
|
||||||
# ---
|
|
||||||
|
|
||||||
Tirage.objects.all().delete()
|
|
||||||
Tirage.objects.bulk_create([
|
|
||||||
Tirage(
|
|
||||||
title="Tirage de test 1",
|
|
||||||
ouverture=timezone.now()-timezone.timedelta(days=7),
|
|
||||||
fermeture=timezone.now(),
|
|
||||||
active=True
|
|
||||||
),
|
|
||||||
Tirage(
|
|
||||||
title="Tirage de test 2",
|
|
||||||
ouverture=timezone.now(),
|
|
||||||
fermeture=timezone.now()+timezone.timedelta(days=60),
|
|
||||||
active=True
|
|
||||||
)
|
|
||||||
])
|
|
||||||
tirages = Tirage.objects.all()
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# Salles
|
|
||||||
# ---
|
|
||||||
|
|
||||||
locations = self.from_json('locations.json', DATA_DIR, Salle)
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# Spectacles
|
|
||||||
# ---
|
|
||||||
|
|
||||||
def show_callback(show):
|
|
||||||
"""
|
|
||||||
Assigne un tirage, une date et un lieu à un spectacle et décide si
|
|
||||||
les places sont sur listing.
|
|
||||||
"""
|
|
||||||
show.tirage = random.choice(tirages)
|
|
||||||
show.listing = bool(random.randint(0, 1))
|
|
||||||
show.date = (
|
|
||||||
show.tirage.fermeture
|
|
||||||
+ timezone.timedelta(days=random.randint(60, 90))
|
|
||||||
)
|
|
||||||
show.location = random.choice(locations)
|
|
||||||
return show
|
|
||||||
shows = self.from_json(
|
|
||||||
'shows.json', DATA_DIR, Spectacle, show_callback
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# Inscriptions
|
|
||||||
# ---
|
|
||||||
|
|
||||||
self.stdout.write("Inscription des utilisateurs aux tirages")
|
|
||||||
ChoixSpectacle.objects.all().delete()
|
|
||||||
choices = []
|
|
||||||
cof_members = Group.objects.get(name="cof_members")
|
|
||||||
for user in cof_members.user_set.all():
|
|
||||||
for tirage in tirages:
|
|
||||||
part, _ = Participant.objects.get_or_create(
|
|
||||||
user=user,
|
|
||||||
tirage=tirage
|
|
||||||
)
|
|
||||||
shows = random.sample(
|
|
||||||
list(tirage.spectacle_set.all()),
|
|
||||||
tirage.spectacle_set.count() // 2
|
|
||||||
)
|
|
||||||
for (rank, show) in enumerate(shows):
|
|
||||||
choices.append(ChoixSpectacle(
|
|
||||||
participant=part,
|
|
||||||
spectacle=show,
|
|
||||||
priority=rank + 1,
|
|
||||||
double_choice=random.choice(
|
|
||||||
['1', 'double', 'autoquit']
|
|
||||||
)
|
|
||||||
))
|
|
||||||
ChoixSpectacle.objects.bulk_create(choices)
|
|
||||||
self.stdout.write("- {:d} inscriptions générées".format(len(choices)))
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# On lance le premier tirage
|
|
||||||
# ---
|
|
||||||
|
|
||||||
self.stdout.write("Lancement du premier tirage")
|
|
||||||
do_tirage(tirages[0], "dummy_token")
|
|
|
@ -1,43 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Gestion en ligne de commande des reventes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
from django.core.management import BaseCommand
|
|
||||||
from django.utils import timezone
|
|
||||||
from bda.models import SpectacleRevente
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Envoie les mails de notification et effectue " \
|
|
||||||
"les tirages au sort des reventes"
|
|
||||||
leave_locale_alone = True
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
now = timezone.now()
|
|
||||||
reventes = SpectacleRevente.objects.all()
|
|
||||||
for revente in reventes:
|
|
||||||
# Check si < 24h
|
|
||||||
if (revente.attribution.spectacle.date <=
|
|
||||||
revente.date + timedelta(days=1)) and \
|
|
||||||
now >= revente.date + timedelta(minutes=15) and \
|
|
||||||
not revente.notif_sent:
|
|
||||||
self.stdout.write(str(now))
|
|
||||||
revente.mail_shotgun()
|
|
||||||
self.stdout.write("Mail de disponibilité immédiate envoyé")
|
|
||||||
# Check si délai de retrait dépassé
|
|
||||||
elif (now >= revente.date + timedelta(hours=1) and
|
|
||||||
not revente.notif_sent):
|
|
||||||
self.stdout.write(str(now))
|
|
||||||
revente.send_notif()
|
|
||||||
self.stdout.write("Mail d'inscription à une revente envoyé")
|
|
||||||
# Check si tirage à faire
|
|
||||||
elif (now >= revente.date_tirage and
|
|
||||||
not revente.tirage_done):
|
|
||||||
self.stdout.write(str(now))
|
|
||||||
revente.tirage()
|
|
||||||
self.stdout.write("Tirage effectué, mails envoyés")
|
|
|
@ -1,33 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Gestion en ligne de commande des mails de rappel.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.utils import timezone
|
|
||||||
from bda.models import Spectacle
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = 'Envoie les mails de rappel des spectacles dont la date ' \
|
|
||||||
'approche.\nNe renvoie pas les mails déjà envoyés.'
|
|
||||||
leave_locale_alone = True
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
now = timezone.now()
|
|
||||||
delay = timedelta(days=4)
|
|
||||||
shows = Spectacle.objects \
|
|
||||||
.filter(date__range=(now, now+delay)) \
|
|
||||||
.filter(tirage__active=True) \
|
|
||||||
.filter(rappel_sent__isnull=True) \
|
|
||||||
.all()
|
|
||||||
for show in shows:
|
|
||||||
show.send_rappel()
|
|
||||||
self.stdout.write(
|
|
||||||
'Mails de rappels pour %s envoyés avec succès.' % show)
|
|
||||||
if not shows:
|
|
||||||
self.stdout.write('Aucun mail à envoyer.')
|
|
|
@ -1,26 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "Cour\u00f4",
|
|
||||||
"address": "45 rue d'Ulm, cour\u00f4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "K-F\u00eat",
|
|
||||||
"address": "45 rue d'Ulm, escalier C, niveau -1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Th\u00e9\u00e2tre",
|
|
||||||
"address": "45 rue d'Ulm, escalier C, niveau -1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Cours Pasteur",
|
|
||||||
"address": "45 rue d'Ulm, cours pasteur"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Salle des actes",
|
|
||||||
"address": "45 rue d'Ulm, escalier A, niveau 1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Amphi Rataud",
|
|
||||||
"address": "45 rue d'Ulm, NIR, niveau PB"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,100 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"description": "Jazz / Funk",
|
|
||||||
"title": "Un super concert",
|
|
||||||
"price": 10.0,
|
|
||||||
"slots_description": "Debout",
|
|
||||||
"slots": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Homemade",
|
|
||||||
"title": "Une super pi\u00e8ce",
|
|
||||||
"price": 10.0,
|
|
||||||
"slots_description": "Assises",
|
|
||||||
"slots": 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Plein air, soleil, bonne musique",
|
|
||||||
"title": "Concert pour la f\u00eate de la musique",
|
|
||||||
"price": 5.0,
|
|
||||||
"slots_description": "Debout, attention \u00e0 la fontaine",
|
|
||||||
"slots": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Sous le regard s\u00e9v\u00e8re de Louis Pasteur",
|
|
||||||
"title": "Op\u00e9ra sans d\u00e9cors",
|
|
||||||
"price": 5.0,
|
|
||||||
"slots_description": "Assis sur l'herbe",
|
|
||||||
"slots": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Buffet \u00e0 la fin",
|
|
||||||
"title": "Concert Trouv\u00e8re",
|
|
||||||
"price": 20.0,
|
|
||||||
"slots_description": "Assises",
|
|
||||||
"slots": 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Vive les maths",
|
|
||||||
"title": "Dessin \u00e0 la craie sur tableau noir",
|
|
||||||
"price": 10.0,
|
|
||||||
"slots_description": "Assises, tablette pour prendre des notes",
|
|
||||||
"slots": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Une pi\u00e8ce \u00e0 un personnage",
|
|
||||||
"title": "D\u00e9cors, d\u00e9montage en musique",
|
|
||||||
"price": 0.0,
|
|
||||||
"slots_description": "Assises",
|
|
||||||
"slots": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Annulera, annulera pas\u00a0?",
|
|
||||||
"title": "La Nuit",
|
|
||||||
"price": 27.0,
|
|
||||||
"slots_description": "",
|
|
||||||
"slots": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Le boum fait sa carte blanche",
|
|
||||||
"title": "Turbomix",
|
|
||||||
"price": 10.0,
|
|
||||||
"slots_description": "Debout les mains en l'air",
|
|
||||||
"slots": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Unique repr\u00e9sentation",
|
|
||||||
"title": "Carinettes et trombone",
|
|
||||||
"price": 15.0,
|
|
||||||
"slots_description": "Chaises ikea",
|
|
||||||
"slots": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Suivi d'une jam session",
|
|
||||||
"title": "Percussion sur rondins",
|
|
||||||
"price": 5.0,
|
|
||||||
"slots_description": "B\u00fbches",
|
|
||||||
"slots": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "\u00c9preuve sportive et artistique",
|
|
||||||
"title": "Bassin aux ernests, nage libre",
|
|
||||||
"price": 5.0,
|
|
||||||
"slots_description": "Humides",
|
|
||||||
"slots": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Sonore",
|
|
||||||
"title": "Chant du barde",
|
|
||||||
"price": 13.0,
|
|
||||||
"slots_description": "Ne venez pas",
|
|
||||||
"slots": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Cocorico",
|
|
||||||
"title": "Chant du coq",
|
|
||||||
"price": 4.0,
|
|
||||||
"slots_description": "bancs",
|
|
||||||
"slots": 15
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,122 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Attribution',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('given', models.BooleanField(default=False, verbose_name='Donn\xe9e')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ChoixSpectacle',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('priority', models.PositiveIntegerField(verbose_name=b'Priorit\xc3\xa9')),
|
|
||||||
('double_choice', models.CharField(default=b'1', max_length=10, verbose_name=b'Nombre de places', choices=[(b'1', b'1 place'), (b'autoquit', b'2 places si possible, 1 sinon'), (b'double', b'2 places sinon rien')])),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'ordering': ('priority',),
|
|
||||||
'verbose_name': 'voeu',
|
|
||||||
'verbose_name_plural': 'voeux',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Participant',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('paid', models.BooleanField(default=False, verbose_name='A pay\xe9')),
|
|
||||||
('paymenttype', models.CharField(blank=True, max_length=6, verbose_name='Moyen de paiement', choices=[(b'cash', 'Cash'), (b'cb', b'CB'), (b'cheque', 'Ch\xe8que'), (b'autre', 'Autre')])),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Salle',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('name', models.CharField(max_length=300, verbose_name=b'Nom')),
|
|
||||||
('address', models.TextField(verbose_name=b'Adresse')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Spectacle',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('title', models.CharField(max_length=300, verbose_name=b'Titre')),
|
|
||||||
('date', models.DateTimeField(verbose_name=b'Date & heure')),
|
|
||||||
('description', models.TextField(verbose_name=b'Description', blank=True)),
|
|
||||||
('slots_description', models.TextField(verbose_name=b'Description des places', blank=True)),
|
|
||||||
('price', models.FloatField(verbose_name=b"Prix d'une place", blank=True)),
|
|
||||||
('slots', models.IntegerField(verbose_name=b'Places')),
|
|
||||||
('priority', models.IntegerField(default=1000, verbose_name=b'Priorit\xc3\xa9')),
|
|
||||||
('location', models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='bda.Salle')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'ordering': ('priority', 'date', 'title'),
|
|
||||||
'verbose_name': 'Spectacle',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='participant',
|
|
||||||
name='attributions',
|
|
||||||
field=models.ManyToManyField(related_name='attributed_to', through='bda.Attribution', to='bda.Spectacle'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='participant',
|
|
||||||
name='choices',
|
|
||||||
field=models.ManyToManyField(related_name='chosen_by', through='bda.ChoixSpectacle', to='bda.Spectacle'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='participant',
|
|
||||||
name='user',
|
|
||||||
field=models.OneToOneField(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='choixspectacle',
|
|
||||||
name='participant',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='bda.Participant'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='choixspectacle',
|
|
||||||
name='spectacle',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='participants',
|
|
||||||
to='bda.Spectacle'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='attribution',
|
|
||||||
name='participant',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='bda.Participant'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='attribution',
|
|
||||||
name='spectacle',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
related_name='attribues',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='bda.Spectacle'),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='choixspectacle',
|
|
||||||
unique_together=set([('participant', 'spectacle')]),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,64 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
def forwards_func(apps, schema_editor):
|
|
||||||
Tirage = apps.get_model("bda", "Tirage")
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
Tirage.objects.using(db_alias).bulk_create([
|
|
||||||
Tirage(
|
|
||||||
id=1,
|
|
||||||
title="Tirage de test (migration)",
|
|
||||||
active=False,
|
|
||||||
ouverture=timezone.now(),
|
|
||||||
fermeture=timezone.now()),
|
|
||||||
])
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Tirage',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('title', models.CharField(max_length=300, verbose_name=b'Titre')),
|
|
||||||
('ouverture', models.DateTimeField(verbose_name=b"Date et heure d'ouverture du tirage")),
|
|
||||||
('fermeture', models.DateTimeField(verbose_name=b'Date et heure de fermerture du tirage')),
|
|
||||||
('token', models.TextField(verbose_name=b'Graine du tirage', blank=True)),
|
|
||||||
('active', models.BooleanField(default=True, verbose_name=b'Tirage actif')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='participant',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='participant',
|
|
||||||
name='tirage',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
default=1,
|
|
||||||
to='bda.Tirage'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='tirage',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
default=1,
|
|
||||||
to='bda.Tirage'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,24 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0002_add_tirage'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='price',
|
|
||||||
field=models.FloatField(verbose_name=b"Prix d'une place"),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='tirage',
|
|
||||||
name='active',
|
|
||||||
field=models.BooleanField(default=False, verbose_name=b'Tirage actif'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0003_update_tirage_and_spectacle'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='listing',
|
|
||||||
field=models.BooleanField(default=False, verbose_name=b'Les places sont sur listing'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='rappel_sent',
|
|
||||||
field=models.DateTimeField(null=True, verbose_name=b'Mail de rappel envoy\xc3\xa9', blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,29 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0004_mails-rappel'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='choixspectacle',
|
|
||||||
name='priority',
|
|
||||||
field=models.PositiveIntegerField(verbose_name='Priorit\xe9'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='priority',
|
|
||||||
field=models.IntegerField(default=1000, verbose_name='Priorit\xe9'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='rappel_sent',
|
|
||||||
field=models.DateTimeField(null=True, verbose_name='Mail de rappel envoy\xe9', blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,35 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
|
|
||||||
def forwards_func(apps, schema_editor):
|
|
||||||
Tirage = apps.get_model("bda", "Tirage")
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
for tirage in Tirage.objects.using(db_alias).all():
|
|
||||||
if tirage.tokens:
|
|
||||||
tirage.tokens = "Before %s\n\"\"\"%s\"\"\"\n" % (
|
|
||||||
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
|
||||||
tirage.tokens)
|
|
||||||
tirage.save()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0005_encoding'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField('tirage', 'token', 'tokens'),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='tirage',
|
|
||||||
name='enable_do_tirage',
|
|
||||||
field=models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
verbose_name=b'Le tirage peut \xc3\xaatre lanc\xc3\xa9'),
|
|
||||||
),
|
|
||||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
|
||||||
]
|
|
|
@ -1,94 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0006_add_tirage_switch'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CategorieSpectacle',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
|
||||||
auto_created=True, primary_key=True)),
|
|
||||||
('name', models.CharField(max_length=100, verbose_name='Nom',
|
|
||||||
unique=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Cat\xe9gorie',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Quote',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
|
||||||
auto_created=True, primary_key=True)),
|
|
||||||
('text', models.TextField(verbose_name='Citation')),
|
|
||||||
('author', models.CharField(max_length=200,
|
|
||||||
verbose_name='Auteur')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='spectacle',
|
|
||||||
options={'ordering': ('date', 'title'),
|
|
||||||
'verbose_name': 'Spectacle'},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='priority',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='ext_link',
|
|
||||||
field=models.CharField(
|
|
||||||
max_length=500,
|
|
||||||
verbose_name='Lien vers le site du spectacle',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='image',
|
|
||||||
field=models.ImageField(upload_to='imgs/shows/', null=True,
|
|
||||||
verbose_name='Image', blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='tirage',
|
|
||||||
name='enable_do_tirage',
|
|
||||||
field=models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
verbose_name='Le tirage peut \xeatre lanc\xe9'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='tirage',
|
|
||||||
name='tokens',
|
|
||||||
field=models.TextField(verbose_name='Graine(s) du tirage',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='category',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
blank=True,
|
|
||||||
to='bda.CategorieSpectacle',
|
|
||||||
null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='vips',
|
|
||||||
field=models.TextField(verbose_name='Personnalit\xe9s',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='quote',
|
|
||||||
name='spectacle',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='bda.Spectacle'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,103 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0007_extends_spectacle'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='choixspectacle',
|
|
||||||
name='double_choice',
|
|
||||||
field=models.CharField(
|
|
||||||
verbose_name='Nombre de places',
|
|
||||||
choices=[('1', '1 place'),
|
|
||||||
('autoquit', '2 places si possible, 1 sinon'),
|
|
||||||
('double', '2 places sinon rien')],
|
|
||||||
max_length=10, default='1'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='participant',
|
|
||||||
name='paymenttype',
|
|
||||||
field=models.CharField(
|
|
||||||
blank=True,
|
|
||||||
choices=[('cash', 'Cash'), ('cb', 'CB'),
|
|
||||||
('cheque', 'Chèque'), ('autre', 'Autre')],
|
|
||||||
max_length=6, verbose_name='Moyen de paiement'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='salle',
|
|
||||||
name='address',
|
|
||||||
field=models.TextField(verbose_name='Adresse'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='salle',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(verbose_name='Nom', max_length=300),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='date',
|
|
||||||
field=models.DateTimeField(verbose_name='Date & heure'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(verbose_name='Description', blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='listing',
|
|
||||||
field=models.BooleanField(
|
|
||||||
verbose_name='Les places sont sur listing'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='price',
|
|
||||||
field=models.FloatField(verbose_name="Prix d'une place"),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='slots',
|
|
||||||
field=models.IntegerField(verbose_name='Places'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='slots_description',
|
|
||||||
field=models.TextField(verbose_name='Description des places',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='title',
|
|
||||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='tirage',
|
|
||||||
name='active',
|
|
||||||
field=models.BooleanField(verbose_name='Tirage actif',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='tirage',
|
|
||||||
name='fermeture',
|
|
||||||
field=models.DateTimeField(
|
|
||||||
verbose_name='Date et heure de fermerture du tirage'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='tirage',
|
|
||||||
name='ouverture',
|
|
||||||
field=models.DateTimeField(
|
|
||||||
verbose_name="Date et heure d'ouverture du tirage"),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='tirage',
|
|
||||||
name='title',
|
|
||||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,73 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import django.utils.timezone
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0008_py3'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SpectacleRevente',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(serialize=False, primary_key=True,
|
|
||||||
auto_created=True, verbose_name='ID')),
|
|
||||||
('date', models.DateTimeField(
|
|
||||||
verbose_name='Date de mise en vente',
|
|
||||||
default=django.utils.timezone.now)),
|
|
||||||
('notif_sent', models.BooleanField(
|
|
||||||
verbose_name='Notification envoyée', default=False)),
|
|
||||||
('tirage_done', models.BooleanField(
|
|
||||||
verbose_name='Tirage effectué', default=False)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Revente',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='participant',
|
|
||||||
name='choicesrevente',
|
|
||||||
field=models.ManyToManyField(to='bda.Spectacle',
|
|
||||||
related_name='subscribed',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectaclerevente',
|
|
||||||
name='answered_mail',
|
|
||||||
field=models.ManyToManyField(to='bda.Participant',
|
|
||||||
related_name='wanted',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectaclerevente',
|
|
||||||
name='attribution',
|
|
||||||
field=models.OneToOneField(
|
|
||||||
to='bda.Attribution',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='revente'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectaclerevente',
|
|
||||||
name='seller',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='bda.Participant',
|
|
||||||
verbose_name='Vendeur',
|
|
||||||
related_name='original_shows'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectaclerevente',
|
|
||||||
name='soldTo',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='bda.Participant',
|
|
||||||
verbose_name='Vendue à',
|
|
||||||
null=True,
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,33 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.utils import timezone
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
|
|
||||||
def forwards_func(apps, schema_editor):
|
|
||||||
SpectacleRevente = apps.get_model("bda", "SpectacleRevente")
|
|
||||||
|
|
||||||
for revente in SpectacleRevente.objects.all():
|
|
||||||
is_expired = timezone.now() > revente.date_tirage()
|
|
||||||
is_direct = (revente.attribution.spectacle.date >= revente.date and
|
|
||||||
timezone.now() > revente.date + timedelta(minutes=15))
|
|
||||||
revente.shotgun = is_expired or is_direct
|
|
||||||
revente.save()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0009_revente'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectaclerevente',
|
|
||||||
name='shotgun',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Disponible imm\xe9diatement'),
|
|
||||||
),
|
|
||||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
|
||||||
]
|
|
359
bda/models.py
359
bda/models.py
|
@ -1,359 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import calendar
|
|
||||||
import random
|
|
||||||
from datetime import timedelta
|
|
||||||
from custommail.shortcuts import send_mass_custom_mail
|
|
||||||
|
|
||||||
from django.contrib.sites.models import Site
|
|
||||||
from django.db import models
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone, formats
|
|
||||||
|
|
||||||
|
|
||||||
class Tirage(models.Model):
|
|
||||||
title = models.CharField("Titre", max_length=300)
|
|
||||||
ouverture = models.DateTimeField("Date et heure d'ouverture du tirage")
|
|
||||||
fermeture = models.DateTimeField("Date et heure de fermerture du tirage")
|
|
||||||
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
|
||||||
active = models.BooleanField("Tirage actif", default=False)
|
|
||||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
|
||||||
default=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s - %s" % (self.title, formats.localize(
|
|
||||||
timezone.template_localtime(self.fermeture)))
|
|
||||||
|
|
||||||
|
|
||||||
class Salle(models.Model):
|
|
||||||
name = models.CharField("Nom", max_length=300)
|
|
||||||
address = models.TextField("Adresse")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class CategorieSpectacle(models.Model):
|
|
||||||
name = models.CharField('Nom', max_length=100, unique=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Catégorie"
|
|
||||||
|
|
||||||
|
|
||||||
class Spectacle(models.Model):
|
|
||||||
title = models.CharField("Titre", max_length=300)
|
|
||||||
category = models.ForeignKey(
|
|
||||||
CategorieSpectacle,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
date = models.DateTimeField("Date & heure")
|
|
||||||
location = models.ForeignKey(Salle, on_delete=models.CASCADE)
|
|
||||||
vips = models.TextField('Personnalités', blank=True)
|
|
||||||
description = models.TextField("Description", blank=True)
|
|
||||||
slots_description = models.TextField("Description des places", blank=True)
|
|
||||||
image = models.ImageField('Image', blank=True, null=True,
|
|
||||||
upload_to='imgs/shows/')
|
|
||||||
ext_link = models.CharField('Lien vers le site du spectacle', blank=True,
|
|
||||||
max_length=500)
|
|
||||||
price = models.FloatField("Prix d'une place")
|
|
||||||
slots = models.IntegerField("Places")
|
|
||||||
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
|
||||||
listing = models.BooleanField("Les places sont sur listing")
|
|
||||||
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
|
|
||||||
null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Spectacle"
|
|
||||||
ordering = ("date", "title",)
|
|
||||||
|
|
||||||
def timestamp(self):
|
|
||||||
return "%d" % calendar.timegm(self.date.utctimetuple())
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s - %s, %s, %.02f€" % (
|
|
||||||
self.title,
|
|
||||||
formats.localize(timezone.template_localtime(self.date)),
|
|
||||||
self.location,
|
|
||||||
self.price
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_rappel(self):
|
|
||||||
"""
|
|
||||||
Envoie un mail de rappel à toutes les personnes qui ont une place pour
|
|
||||||
ce spectacle.
|
|
||||||
"""
|
|
||||||
# On récupère la liste des participants
|
|
||||||
members = {}
|
|
||||||
for attr in Attribution.objects.filter(spectacle=self).all():
|
|
||||||
member = attr.participant.user
|
|
||||||
if member.id in members:
|
|
||||||
members[member.id][1] = 2
|
|
||||||
else:
|
|
||||||
members[member.id] = [member, 1]
|
|
||||||
# FIXME : faire quelque chose de ça, un utilisateur bda_generic ?
|
|
||||||
# # Pour le BdA
|
|
||||||
# members[0] = ['BdA', 1, 'bda@ens.fr']
|
|
||||||
# members[-1] = ['BdA', 2, 'bda@ens.fr']
|
|
||||||
# On écrit un mail personnalisé à chaque participant
|
|
||||||
datatuple = [(
|
|
||||||
'bda-rappel',
|
|
||||||
{'member': member[0], 'nb_attr': member[1], 'show': self},
|
|
||||||
settings.MAIL_DATA['rappels']['FROM'],
|
|
||||||
[member[0].email])
|
|
||||||
for member in members.values()
|
|
||||||
]
|
|
||||||
send_mass_custom_mail(datatuple)
|
|
||||||
# On enregistre le fait que l'envoi a bien eu lieu
|
|
||||||
self.rappel_sent = timezone.now()
|
|
||||||
self.save()
|
|
||||||
# On renvoie la liste des destinataires
|
|
||||||
return members.values()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_past(self):
|
|
||||||
return self.date < timezone.now()
|
|
||||||
|
|
||||||
|
|
||||||
class Quote(models.Model):
|
|
||||||
spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE)
|
|
||||||
text = models.TextField('Citation')
|
|
||||||
author = models.CharField('Auteur', max_length=200)
|
|
||||||
|
|
||||||
|
|
||||||
PAYMENT_TYPES = (
|
|
||||||
("cash", "Cash"),
|
|
||||||
("cb", "CB"),
|
|
||||||
("cheque", "Chèque"),
|
|
||||||
("autre", "Autre"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Participant(models.Model):
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
choices = models.ManyToManyField(Spectacle,
|
|
||||||
through="ChoixSpectacle",
|
|
||||||
related_name="chosen_by")
|
|
||||||
attributions = models.ManyToManyField(Spectacle,
|
|
||||||
through="Attribution",
|
|
||||||
related_name="attributed_to")
|
|
||||||
paid = models.BooleanField("A payé", default=False)
|
|
||||||
paymenttype = models.CharField("Moyen de paiement",
|
|
||||||
max_length=6, choices=PAYMENT_TYPES,
|
|
||||||
blank=True)
|
|
||||||
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
|
||||||
choicesrevente = models.ManyToManyField(Spectacle,
|
|
||||||
related_name="subscribed",
|
|
||||||
blank=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s - %s" % (self.user, self.tirage.title)
|
|
||||||
|
|
||||||
DOUBLE_CHOICES = (
|
|
||||||
("1", "1 place"),
|
|
||||||
("autoquit", "2 places si possible, 1 sinon"),
|
|
||||||
("double", "2 places sinon rien"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacle(models.Model):
|
|
||||||
participant = models.ForeignKey(
|
|
||||||
Participant,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
spectacle = models.ForeignKey(
|
|
||||||
Spectacle,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="participants"
|
|
||||||
)
|
|
||||||
priority = models.PositiveIntegerField("Priorité")
|
|
||||||
double_choice = models.CharField("Nombre de places",
|
|
||||||
default="1", choices=DOUBLE_CHOICES,
|
|
||||||
max_length=10)
|
|
||||||
|
|
||||||
def get_double(self):
|
|
||||||
return self.double_choice != "1"
|
|
||||||
double = property(get_double)
|
|
||||||
|
|
||||||
def get_autoquit(self):
|
|
||||||
return self.double_choice == "autoquit"
|
|
||||||
autoquit = property(get_autoquit)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Vœux de %s pour %s" % (
|
|
||||||
self.participant.user.get_full_name(),
|
|
||||||
self.spectacle.title)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ("priority",)
|
|
||||||
unique_together = (("participant", "spectacle",),)
|
|
||||||
verbose_name = "voeu"
|
|
||||||
verbose_name_plural = "voeux"
|
|
||||||
|
|
||||||
|
|
||||||
class Attribution(models.Model):
|
|
||||||
participant = models.ForeignKey(
|
|
||||||
Participant,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
spectacle = models.ForeignKey(
|
|
||||||
Spectacle,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="attribues"
|
|
||||||
)
|
|
||||||
given = models.BooleanField("Donnée", default=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s -- %s, %s" % (self.participant.user, self.spectacle.title,
|
|
||||||
self.spectacle.date)
|
|
||||||
|
|
||||||
|
|
||||||
class SpectacleRevente(models.Model):
|
|
||||||
attribution = models.OneToOneField(
|
|
||||||
Attribution,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="revente"
|
|
||||||
)
|
|
||||||
date = models.DateTimeField("Date de mise en vente",
|
|
||||||
default=timezone.now)
|
|
||||||
answered_mail = models.ManyToManyField(Participant,
|
|
||||||
related_name="wanted",
|
|
||||||
blank=True)
|
|
||||||
seller = models.ForeignKey(
|
|
||||||
Participant,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="original_shows",
|
|
||||||
verbose_name="Vendeur"
|
|
||||||
)
|
|
||||||
soldTo = models.ForeignKey(
|
|
||||||
Participant,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
verbose_name="Vendue à"
|
|
||||||
)
|
|
||||||
notif_sent = models.BooleanField("Notification envoyée",
|
|
||||||
default=False)
|
|
||||||
tirage_done = models.BooleanField("Tirage effectué",
|
|
||||||
default=False)
|
|
||||||
shotgun = models.BooleanField("Disponible immédiatement",
|
|
||||||
default=False)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def date_tirage(self):
|
|
||||||
"""Renvoie la date du tirage au sort de la revente."""
|
|
||||||
# L'acheteur doit être connu au plus 12h avant le spectacle
|
|
||||||
remaining_time = (self.attribution.spectacle.date
|
|
||||||
- self.date - timedelta(hours=13))
|
|
||||||
# Au minimum, on attend 2 jours avant le tirage
|
|
||||||
delay = min(remaining_time, timedelta(days=2))
|
|
||||||
# Le vendeur a aussi 1h pour changer d'avis
|
|
||||||
return self.date + delay + timedelta(hours=1)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s -- %s" % (self.seller,
|
|
||||||
self.attribution.spectacle.title)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Revente"
|
|
||||||
|
|
||||||
def send_notif(self):
|
|
||||||
"""
|
|
||||||
Envoie une notification pour indiquer la mise en vente d'une place sur
|
|
||||||
BdA-Revente à tous les intéressés.
|
|
||||||
"""
|
|
||||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
|
||||||
datatuple = [(
|
|
||||||
'bda-revente',
|
|
||||||
{
|
|
||||||
'member': participant.user,
|
|
||||||
'show': self.attribution.spectacle,
|
|
||||||
'revente': self,
|
|
||||||
'site': Site.objects.get_current()
|
|
||||||
},
|
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
[participant.user.email])
|
|
||||||
for participant in inscrits
|
|
||||||
]
|
|
||||||
send_mass_custom_mail(datatuple)
|
|
||||||
self.notif_sent = True
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def mail_shotgun(self):
|
|
||||||
"""
|
|
||||||
Envoie un mail à toutes les personnes intéréssées par le spectacle pour
|
|
||||||
leur indiquer qu'il est désormais disponible au shotgun.
|
|
||||||
"""
|
|
||||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
|
||||||
datatuple = [(
|
|
||||||
'bda-shotgun',
|
|
||||||
{
|
|
||||||
'member': participant.user,
|
|
||||||
'show': self.attribution.spectacle,
|
|
||||||
'site': Site.objects.get_current(),
|
|
||||||
},
|
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
[participant.user.email])
|
|
||||||
for participant in inscrits
|
|
||||||
]
|
|
||||||
send_mass_custom_mail(datatuple)
|
|
||||||
self.notif_sent = True
|
|
||||||
# Flag inutile, sauf si l'horloge interne merde
|
|
||||||
self.tirage_done = True
|
|
||||||
self.shotgun = True
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def tirage(self):
|
|
||||||
"""
|
|
||||||
Lance le tirage au sort associé à la revente. Un gagnant est choisi
|
|
||||||
parmis les personnes intéressées par le spectacle. Les personnes sont
|
|
||||||
ensuites prévenues par mail du résultat du tirage.
|
|
||||||
"""
|
|
||||||
inscrits = list(self.answered_mail.all())
|
|
||||||
spectacle = self.attribution.spectacle
|
|
||||||
seller = self.seller
|
|
||||||
|
|
||||||
if inscrits:
|
|
||||||
# Envoie un mail au gagnant et au vendeur
|
|
||||||
winner = random.choice(inscrits)
|
|
||||||
self.soldTo = winner
|
|
||||||
datatuple = []
|
|
||||||
context = {
|
|
||||||
'acheteur': winner.user,
|
|
||||||
'vendeur': seller.user,
|
|
||||||
'show': spectacle,
|
|
||||||
}
|
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-winner',
|
|
||||||
context,
|
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
[winner.user.email],
|
|
||||||
))
|
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-seller',
|
|
||||||
context,
|
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
[seller.user.email]
|
|
||||||
))
|
|
||||||
|
|
||||||
# Envoie un mail aux perdants
|
|
||||||
for inscrit in inscrits:
|
|
||||||
if inscrit != winner:
|
|
||||||
context['acheteur'] = inscrit.user
|
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-loser',
|
|
||||||
context,
|
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
[inscrit.user.email]
|
|
||||||
))
|
|
||||||
send_mass_custom_mail(datatuple)
|
|
||||||
# Si personne ne veut de la place, elle part au shotgun
|
|
||||||
else:
|
|
||||||
self.shotgun = True
|
|
||||||
self.tirage_done = True
|
|
||||||
self.save()
|
|
|
@ -1,48 +0,0 @@
|
||||||
form#tokenform {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
margin-right: 10px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
form#tokenform textarea {
|
|
||||||
font-size: 2em;
|
|
||||||
width: 350px;
|
|
||||||
height: 200px;
|
|
||||||
font-family: 'Droif Serif', serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* wft ?
|
|
||||||
input {
|
|
||||||
width: 400px;
|
|
||||||
font-size: 2em;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
ul.losers {
|
|
||||||
display: inline;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.losers li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.details {
|
|
||||||
font-size: 0.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
border: 0px solid black;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
.attribresult {
|
|
||||||
margin: 10px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spectacle-passe {
|
|
||||||
opacity:0.5;
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,28 +0,0 @@
|
||||||
{% extends "bda-attrib.html" %}
|
|
||||||
|
|
||||||
{% block extracontent %}
|
|
||||||
|
|
||||||
<h2>Attributions (détails)</h2>
|
|
||||||
<h3 class="horizontal-title">Token :</h3>
|
|
||||||
<pre>{{ token }}</pre>
|
|
||||||
<h3 class="horizontal-title">Placés : {{ total_slots }} ; Déçus : {{ total_losers }}</h3>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
{% for member, shows in members2 %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ member.user.get_full_name }}</td>
|
|
||||||
<td>{{ member.user.email }}</td>
|
|
||||||
<td>Total: {{ member.total }}€</td>
|
|
||||||
<td style="width: 120px;"></td>
|
|
||||||
</tr>
|
|
||||||
{% for show in shows %}
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td>{{ show }}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
|
|
@ -1,49 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
|
|
||||||
<h2>Attributions</h2>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<p class="success">Pour raison de sécurité, le lancement du tirage
|
|
||||||
a été désactivé. Vous pouvez le réactiver dans
|
|
||||||
l'<a href="{% url "admin:index" %}">interface admin</a></p>
|
|
||||||
|
|
||||||
<h3 class="horizontal-title">Token :</h3>
|
|
||||||
<pre>{{ token }}</pre>
|
|
||||||
<h3 class="horizontal-title">Placés : {{ total_slots }} ; Déçus : {{ total_losers }}</h3>
|
|
||||||
{% if user.profile.is_buro %}<h3 class="horizontal-title">Déficit total: {{ total_deficit }} €, Opéra: {{ opera_deficit }} €, Attribué: {{ total_sold }} €</h3>{% endif %}
|
|
||||||
<h3 class="horizontal-title">Temps de calcul : {{ duration|floatformat }}s</h3>
|
|
||||||
|
|
||||||
{% for show, members, losers in results %}
|
|
||||||
<div class="attribresult">
|
|
||||||
<h3 class="horizontal-title">{{ show.title }} - {{ show.date }} @ {{ show.location }}</h3>
|
|
||||||
<p>
|
|
||||||
<strong>{{ show.nrequests }} demandes pour {{ show.slots }} places</strong>
|
|
||||||
{{ show.price }}€ par place{% if user.profile.is_buro and show.nrequests < show.slots %}, {{ show.deficit }}€ de déficit{% endif %}
|
|
||||||
</p>
|
|
||||||
Places :
|
|
||||||
<ul>
|
|
||||||
{% for member, rank, origrank, double in members %}
|
|
||||||
<li>{{ member.user.get_full_name }} <span class="details">(souhait {{ origrank }} — rang {{ rank }})</span></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
Déçus :
|
|
||||||
{% if not losers %}/{% else %}
|
|
||||||
<ul class="losers">
|
|
||||||
{% for member, rank, origrank, double in losers %}
|
|
||||||
{% if not forloop.first %} ; {% endif %}
|
|
||||||
<li>{{ member.user.get_full_name }} <span class="details">(souhait {{ origrank }} — rang {{ rank }})</span></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% block extracontent %}
|
|
||||||
{% endblock %}
|
|
||||||
{% endblock %}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>{{ spectacle }}</h2>
|
|
||||||
<textarea style="width: 100%; height: 100px; margin-top: 10px;">
|
|
||||||
{% for attrib in spectacle.attribues.all %}{{ attrib.participant.user.email }}, {% endfor %}</textarea>
|
|
||||||
{% endblock %}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Inscription à une revente</h2>
|
|
||||||
<p class="success"> Votre inscription a bien été enregistrée !</p>
|
|
||||||
<p>Le tirage au sort pour cette revente ({{spectacle}}) sera effectué le {{date}}.
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>BdA-Revente</h2>
|
|
||||||
<p>Il n'y a plus de places en revente pour ce spectacle, désolé !</p>
|
|
||||||
{% endblock %}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2><strong>Nope</strong></h2>
|
|
||||||
<p>Avant de revendre des places, il faut aller les payer !</p>
|
|
||||||
{% endblock %}
|
|
|
@ -1,64 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>{{ spectacle }}</h2>
|
|
||||||
<table class='table table-striped etat-bda'>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-sort="string">Nom</th>
|
|
||||||
<th data-sort="int">Places</th>
|
|
||||||
<th data-sort="string">Adresse Mail</th>
|
|
||||||
<th data-sort="string">Payé</th>
|
|
||||||
<th data-sort="string">Donné</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for participant in participants %}
|
|
||||||
<tr>
|
|
||||||
<td data-sort-value="{{ participan.name}}">{{participant.name}}</td>
|
|
||||||
<td data-sort-value="{{participant.nb_places}}">{{participant.nb_places}} place{{participant.nb_places|pluralize}}</td>
|
|
||||||
<td data-sort-value="{{participant.email}}">{{participant.email}}</td>
|
|
||||||
<td data-sort-value="{{ participant.paid}}" class={%if participant.paid %}"greenratio"{%else%}"redratio"{%endif%}>
|
|
||||||
{% if participant.paid %}Oui{% else %}Non{%endif%}
|
|
||||||
</td>
|
|
||||||
<td data-sort-value="{{participant.given}}" class={%if participant.given == participant.nb_places %}"greenratio"
|
|
||||||
{%elif participant.given == 0%}"redratio"
|
|
||||||
{%else%}"orangeratio"
|
|
||||||
{%endif%}>
|
|
||||||
{% if participant.given == participant.nb_places %}Oui
|
|
||||||
{% elif participant.given == 0 %}Non
|
|
||||||
{% else %}{{participant.given}}/{{participant.nb_places}}
|
|
||||||
{%endif%}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<h3><a href="{% url "admin:bda_attribution_add" %}?spectacle={{spectacle.id}}"><span class="glyphicon glyphicon-plus-sign"></span> Ajouter une attribution</a></h3>
|
|
||||||
<br>
|
|
||||||
<button class="btn btn-default" type="button" onclick="toggle('export-mails')">Afficher/Cacher mails participants</button>
|
|
||||||
<pre id="export-mails" style="display:none">
|
|
||||||
{%for participant in participants %}{{participant.email}}, {%endfor%}
|
|
||||||
</pre>
|
|
||||||
<br>
|
|
||||||
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
|
|
||||||
<pre id="export-salle" style="display:none">
|
|
||||||
{% for participant in participants %}{{participant.name}} : {{participant.nb_places}} places
|
|
||||||
{% endfor %}
|
|
||||||
</pre>
|
|
||||||
<script type="text/javascript"
|
|
||||||
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script>
|
|
||||||
<script>
|
|
||||||
function toggle(id) {
|
|
||||||
var pre = document.getElementById(id) ;
|
|
||||||
pre.style.display = pre.style.display == "none" ? "block" : "none" ;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function(){
|
|
||||||
$("table.etat-bda").stupidtable();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,73 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
|
|
||||||
<h2>Revente de place</h2>
|
|
||||||
<h3>Places non revendues</h3>
|
|
||||||
<form class="form-horizontal" action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="multiple-checkbox">
|
|
||||||
<ul>
|
|
||||||
{% for box in resellform.attributions %}
|
|
||||||
<li>
|
|
||||||
{{box.tag}}
|
|
||||||
{{box.choice_label}}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-actions">
|
|
||||||
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<br>
|
|
||||||
{% if annulform.attributions or overdue %}
|
|
||||||
<h3>Places en cours de revente</h3>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="multiple-checkbox">
|
|
||||||
<ul>
|
|
||||||
{% for box in annulform.attributions %}
|
|
||||||
<li>
|
|
||||||
{{box.tag}}
|
|
||||||
{{box.choice_label}}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% for attrib in overdue %}
|
|
||||||
<li>
|
|
||||||
<input type="checkbox" style="visibility:hidden">
|
|
||||||
{{attrib.spectacle}}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if annulform.attributions %}
|
|
||||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{% if sold %}
|
|
||||||
<h3>Places revendues</h3>
|
|
||||||
<table class="table">
|
|
||||||
{% for attrib in sold %}
|
|
||||||
<tr>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<td>{{attrib.spectacle}}</td>
|
|
||||||
<td>{{attrib.revente.soldTo.user.get_full_name}}</td>
|
|
||||||
<td><button type="submit" class="btn btn-primary" name="transfer"
|
|
||||||
value="{{attrib.revente.id}}">Transférer</button></td>
|
|
||||||
<td><button type="submit" class="btn btn-primary" name="reinit"
|
|
||||||
value="{{attrib.revente.id}}">Réinitialiser</button></td>
|
|
||||||
</form>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
|
@ -1,14 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Places disponibles immédiatement</h2>
|
|
||||||
{% if shotgun %}
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
{% for spectacle in shotgun %}
|
|
||||||
<li><a href="{% url "bda-buy-revente" spectacle.id %}">{{spectacle}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<p> Pas de places disponibles immédiatement, désolé !</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
|
|
||||||
<h2>Revente de place</h2>
|
|
||||||
<p class="success">Un mail a bien été envoyé à {{seller.get_full_name}} ({{seller.email}}), pour racheter une place pour {{spectacle.title}} !</p>
|
|
||||||
{% endblock %}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Tirage au sort du BdA</h2>
|
|
||||||
<form action="" method="post" id="tokenform">
|
|
||||||
{% csrf_token %}
|
|
||||||
<strong>La graine :</strong>
|
|
||||||
<div>
|
|
||||||
{{ form.token }}
|
|
||||||
</div>
|
|
||||||
<input type="submit" onsubmit="return confirm('Voulez vous lancer le Tirage maintenant ?\n\nCECI REMETTRA À ZÉRO TOUTES LES DONNÉES si le tirage a déjà été lancé.')" value="Go" />
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Impayés</h2>
|
|
||||||
<textarea style="width: 100%; height: 100px; margin-top: 10px;">
|
|
||||||
{% for participant in unpaid %}{{ participant.user.email }}, {% endfor %}</textarea>
|
|
||||||
<h3>Total : {{ unpaid|length }}</h3>
|
|
||||||
{% endblock %}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Nope</h2>
|
|
||||||
{% if revente.shotgun %}
|
|
||||||
<p>Le tirage au sort de cette revente a déjà été effectué !</p>
|
|
||||||
|
|
||||||
<p>Si personne n'était intéressé, elle est maintenant disponible
|
|
||||||
<a href="{% url "bda-buy-revente" revente.attribution.spectacle.id %}">ici</a>.</p>
|
|
||||||
{% else %}
|
|
||||||
<p> Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
|
@ -1,53 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>État des inscriptions BdA</h2>
|
|
||||||
<table class="table table-striped etat-bda">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-sort="string">Titre</th>
|
|
||||||
<th data-sort="int">Date</th>
|
|
||||||
<th data-sort="string">Lieu</th>
|
|
||||||
<th data-sort="int">Places</th>
|
|
||||||
<th data-sort="int">Demandes</th>
|
|
||||||
<th data-sort="float">Ratio</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for spectacle in spectacles %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ spectacle.title }}</td>
|
|
||||||
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date }}</td>
|
|
||||||
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
|
|
||||||
<td data-sort-value="{{ spectacle.slots }}">{{ spectacle.slots }} places</td>
|
|
||||||
<td data-sort-value="{{ spectacle.total }}">{{ spectacle.total }} demandes</td>
|
|
||||||
<td data-sort-value="{{ spectacle.ratio |stringformat:".3f" }}"
|
|
||||||
class={% if spectacle.ratio < 1.0 %}
|
|
||||||
"greenratio"
|
|
||||||
{% else %}
|
|
||||||
{% if spectacle.ratio < 2.5 %}
|
|
||||||
"orangeratio"
|
|
||||||
{% else %}
|
|
||||||
"redratio"
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}>
|
|
||||||
{{ spectacle.ratio |floatformat }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<span class="bda-prix">
|
|
||||||
Total : {{ total }} place{{ total|pluralize }} demandée{{ total|pluralize }}
|
|
||||||
sur {{ proposed }} place{{ proposed|pluralize }} proposée{{ proposed|pluralize }}
|
|
||||||
</span>
|
|
||||||
<script type="text/javascript"
|
|
||||||
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}">
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function(){
|
|
||||||
$("table.etat-bda").stupidtable();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -1,41 +0,0 @@
|
||||||
{% load bootstrap %}
|
|
||||||
{{ formset.non_form_errors.as_ul }}
|
|
||||||
<table id="bda_formset" class="form table">
|
|
||||||
{{ formset.management_form }}
|
|
||||||
{% for form in formset.forms %}
|
|
||||||
{% if forloop.first %}
|
|
||||||
<thead><tr>
|
|
||||||
{% for field in form.visible_fields %}
|
|
||||||
{% if field.name != "DELETE" and field.name != "priority" %}
|
|
||||||
<th class="bda-field-{{ field.name }}">{{ field.label|safe|capfirst }}</th>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
<th><sup>1</sup></th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody class="bda_formset_content">
|
|
||||||
{% endif %}
|
|
||||||
<tr class="{% cycle row1,row2 %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
|
|
||||||
{% for field in form.visible_fields %}
|
|
||||||
{% if field.name != "DELETE" and field.name != "priority" %}
|
|
||||||
<td class="bda-field-{{ field.name }}">
|
|
||||||
{% if forloop.first %}
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{{ field.errors.as_ul }}
|
|
||||||
{{ field | bootstrap }}
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
<td class="tools-cell"><div class="tools">
|
|
||||||
<a href="javascript://" class="glyphicon glyphicon-sort drag-btn" title="Déplacer"></a>
|
|
||||||
<input type="checkbox" name="{{ form.DELETE.html_name }}" style="display: none;" />
|
|
||||||
<input type="hidden" name="{{ form.priority.html_name }}" style="{{ form.priority.value }}" />
|
|
||||||
<a href="javascript://" class="glyphicon glyphicon-remove remove-btn" title="Supprimer"></a>
|
|
||||||
</div>
|
|
||||||
<div class="spacer"></div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
|
@ -1,115 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<script src="{% static 'js/jquery-ui.min.js' %}" type="text/javascript"></script>
|
|
||||||
<script src="{% static "js/jquery.ui.touch-punch.min.js" %}" type="text/javascript"></script>
|
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "css/jquery-ui.min.css" %}" />
|
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<script type="text/javascript">
|
|
||||||
var django = {
|
|
||||||
"jQuery": jQuery.noConflict(true)
|
|
||||||
};
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
cloneMore = function(selector, type) {
|
|
||||||
var newElement = $(selector).clone(true);
|
|
||||||
var total = $('#id_' + type + '-TOTAL_FORMS').val();
|
|
||||||
newElement.find(':input').each(function() {
|
|
||||||
var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
|
|
||||||
var id = 'id_' + name;
|
|
||||||
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
|
|
||||||
});
|
|
||||||
newElement.find('label').each(function() {
|
|
||||||
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
|
|
||||||
$(this).attr('for', newFor);
|
|
||||||
});
|
|
||||||
total++;
|
|
||||||
$('#id_' + type + '-TOTAL_FORMS').val(total);
|
|
||||||
$(selector).after(newElement);
|
|
||||||
}
|
|
||||||
deleteButtonHandler = function(elem) {
|
|
||||||
elem.bind("click", function() {
|
|
||||||
var deleteInput = $(this).prev().prev(),
|
|
||||||
form = $(this).parents(".dynamic-form").first();
|
|
||||||
// callback
|
|
||||||
// toggle options.predeleteCssClass and toggle checkbox
|
|
||||||
if (form.hasClass("has_original")) {
|
|
||||||
form.toggleClass("predelete");
|
|
||||||
if (deleteInput.attr("checked")) {
|
|
||||||
deleteInput.attr("checked", false);
|
|
||||||
} else {
|
|
||||||
deleteInput.attr("checked", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// callback
|
|
||||||
});
|
|
||||||
};
|
|
||||||
$(document).ready(function($) {
|
|
||||||
deleteButtonHandler($("table#bda_formset tbody.bda_formset_content").find("a.remove-btn"));
|
|
||||||
$("table#bda_formset tbody.bda_formset_content").sortable({
|
|
||||||
handle: "a.drag-btn",
|
|
||||||
items: "tr",
|
|
||||||
axis: "y",
|
|
||||||
appendTo: 'body',
|
|
||||||
forceHelperSize: true,
|
|
||||||
placeholder: 'ui-sortable-placeholder',
|
|
||||||
forcePlaceholderSize: true,
|
|
||||||
containment: 'form#bda_form',
|
|
||||||
tolerance: 'pointer',
|
|
||||||
start: function(evt, ui) {
|
|
||||||
var template = "",
|
|
||||||
len = ui.item.children("td").length;
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
template += "<td style='height:" + (ui.item.outerHeight() + 12 ) + "px' class='placeholder-cell'> </td>"
|
|
||||||
}
|
|
||||||
template += "";
|
|
||||||
ui.placeholder.html(template);
|
|
||||||
},
|
|
||||||
stop: function(evt, ui) {
|
|
||||||
// Toggle div.table twice to remove webkits border-spacing bug
|
|
||||||
$("table#bda_formset").toggle().toggle();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
$("#bda_form").bind("submit", function(){
|
|
||||||
var sortable_field_name = "priority";
|
|
||||||
var i = 1;
|
|
||||||
$(".bda_formset_content").find("tr").each(function(){
|
|
||||||
var fields = $(this).find("td :input[value]"),
|
|
||||||
select = $(this).find("td select");
|
|
||||||
if (select.val() && fields.serialize()) {
|
|
||||||
$(this).find("input[name$='"+sortable_field_name+"']").val(i);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
})(django.jQuery);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h2 class="no-bottom-margin">Inscription au tirage au sort du BdA</h2>
|
|
||||||
<form class="form-horizontal" id="bda_form" method="post" action="{% url 'bda-tirage-inscription' tirage.id %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% include "bda/inscription-formset.html" %}
|
|
||||||
<div class="inscription-bottom">
|
|
||||||
<span class="bda-prix">Prix total actuel : {{ total_price }}€</span>
|
|
||||||
<div class="pull-right">
|
|
||||||
<input type="button" class="btn btn-default" value="Ajouter un autre vœu" id="add_more">
|
|
||||||
<script>
|
|
||||||
django.jQuery('#add_more').click(function() {
|
|
||||||
cloneMore('tbody.bda_formset_content tr:last-child', 'choixspectacle_set');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<input type="hidden" name="dbstate" value="{{ dbstate }}" />
|
|
||||||
<input type="submit" class="btn btn-primary" value="Enregistrer" />
|
|
||||||
</div>
|
|
||||||
<p class="footnotes">
|
|
||||||
<sup>1</sup>: cette liste de vœux est ordonnée (du plus important au moins important), pour ajuster la priorité vous pouvez déplacer chaque vœu.<br />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
|
@ -1,33 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Inscriptions pour BdA-Revente</h2>
|
|
||||||
<form action="" class="form-horizontal" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<h3>Spectacles</h3>
|
|
||||||
<br/>
|
|
||||||
<button type="button" class="btn btn-primary" onClick="select(true)">Tout sélectionner</button>
|
|
||||||
<button type="button" class="btn btn-primary" onClick="select(false)">Tout désélectionner</button>
|
|
||||||
|
|
||||||
<div class="multiple-checkbox">
|
|
||||||
<ul>
|
|
||||||
{% for checkbox in form.spectacles %}
|
|
||||||
<li>{{checkbox}}</li>
|
|
||||||
{%endfor%}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="submit" class="btn btn-primary" value="S'inscrire pour les places sélectionnées">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script language="JavaScript">
|
|
||||||
function select(check) {
|
|
||||||
checkboxes = document.getElementsByName("spectacles");
|
|
||||||
for(var i=0, n=checkboxes.length;i<n;i++) {
|
|
||||||
checkboxes[i].checked = check;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -1,43 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Mails de rappels</h2>
|
|
||||||
{% if sent %}
|
|
||||||
<h3>Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes</h3>
|
|
||||||
<ul>
|
|
||||||
{% for member in members %}
|
|
||||||
<li>{{ member.get_full_name }} ({{ member.email }})</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% else %}
|
|
||||||
<h3>Voulez vous envoyer les mails de rappel pour le spectacle
|
|
||||||
{{ show.title }} ?</h3>
|
|
||||||
{% if show.rappel_sent %}
|
|
||||||
<p class="error">Attention, les mails ont déjà été envoyés le
|
|
||||||
{{ show.rappel_sent }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not sent %}
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="pull-right">
|
|
||||||
<input class="btn btn-primary" type="submit" value="Envoyer" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<hr/>
|
|
||||||
<h3>Forme des mails</h3>
|
|
||||||
|
|
||||||
<h4>Une seule place</h4>
|
|
||||||
{% for part in exemple_mail_1place %}
|
|
||||||
<pre>{{ part }}</pre>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<h4>Deux places</h4>
|
|
||||||
{% for part in exemple_mail_2places %}
|
|
||||||
<pre>{{ part }}</pre>
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
|
@ -1,14 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
{% if choices %}
|
|
||||||
<h3>Vos vœux:</h3>
|
|
||||||
<ol>
|
|
||||||
{% for choice in choices %}
|
|
||||||
<li>{{ choice.spectacle }}{% if choice.double %} (deux places{% if autoquit %}, abandon automatique{% endif %}){% endif %}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ol>
|
|
||||||
{% else %}
|
|
||||||
<h3>Vous n'avez enregistré aucun vœu pour le tirage au sort</h3>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
|
@ -1,24 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2><strong>Places attribuées</strong></h3>
|
|
||||||
{% if places %}
|
|
||||||
<table class="table table-striped">
|
|
||||||
{% for place in places %}
|
|
||||||
<tr>
|
|
||||||
<td>{{place.spectacle.title}}</td>
|
|
||||||
<td>{{place.spectacle.location}}</td>
|
|
||||||
<td>{{place.spectacle.date}}</td>
|
|
||||||
<td>{% if place.double %}deux places{%else%}une place{% endif %}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
|
|
||||||
<br/>
|
|
||||||
<p>Ne manque pas un spectacle avec le
|
|
||||||
<a href="{% url "calendar" %}">calendrier
|
|
||||||
automatique !</a></p>
|
|
||||||
{% else %}
|
|
||||||
<h3>Vous n'avez aucune place :(</h3>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
|
@ -1,113 +0,0 @@
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<base target="_parent"/>
|
|
||||||
<style>
|
|
||||||
@font-face {
|
|
||||||
font-family: josefinsans;
|
|
||||||
src: url({% static "fonts/josefinsans.ttf" %});
|
|
||||||
}
|
|
||||||
|
|
||||||
*::-moz-selection {
|
|
||||||
background: #B0B0B0;
|
|
||||||
}
|
|
||||||
|
|
||||||
*::selection {
|
|
||||||
background: #B0B0B0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.descTable{
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto 1em;
|
|
||||||
border-bottom: 2px solid;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 2;
|
|
||||||
max-width: 100%;
|
|
||||||
background-color: transparent;
|
|
||||||
font-family: 'josefinsans', 'Arial';
|
|
||||||
font-weight: 700;
|
|
||||||
color: #5a5a5a;
|
|
||||||
}
|
|
||||||
|
|
||||||
img{
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta charset="utf8" />
|
|
||||||
</head>
|
|
||||||
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
|
|
||||||
<body>
|
|
||||||
{% for show in shows %}
|
|
||||||
<table class="descTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2"><p style="text-align:center;font-size:22px;">{{ show.title }}</p></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td><p style="text-align: left;">{{ show.location }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.category }}</p></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><p style="text-align: left;">{{ show.date|date:"l j F Y - H\hi" }}</p></td><td class="column-2"><p style="text-align: right;">{{ show.slots }} place{{ show.slots|pluralize}} {% if show.slots_description != "" %}({{ show.slots_description }}){% endif %} - {{ show.price }} euro{{ show.price|pluralize}}</p></td>
|
|
||||||
</tr>
|
|
||||||
{% if show.vips %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="2"><p style="text-align: justify;">{{ show.vips }}</p></td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<p style="text-align: justify;">{{ show.description }}</p>
|
|
||||||
{% for quote in show.quote_set.all %}
|
|
||||||
<p style="text-align:center; font-style: italic;">«{{ quote.text }}»{% if quote.author %} - {{ quote.author }}{% endif %}</p>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% if show.image %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="2"><p style="text-align:center;"><a href="{{ show.ext_link }}"><img class="imgDesc" style="display: inline;" src="{{ MEDIA_URL }}{{ show.image }}" alt="{{ show.title }}"></a></p></td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endfor %}
|
|
||||||
<script>
|
|
||||||
// Correction de la taille des images
|
|
||||||
|
|
||||||
/*$(document).ready(function() {
|
|
||||||
$(".descTable").each(function() {
|
|
||||||
$(this).width($("body").width());
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".imgDesc").on("load", function() {
|
|
||||||
|
|
||||||
// Dimensions
|
|
||||||
origHeight = 500; // Hauteur souhaitée
|
|
||||||
|
|
||||||
w = $(this).width();
|
|
||||||
h = $(this).height();
|
|
||||||
r = w/h; // Ratio de l'image
|
|
||||||
maxWidth = $("body").width();
|
|
||||||
|
|
||||||
if (r * origHeight > maxWidth)
|
|
||||||
{
|
|
||||||
$(this).width(maxWidth);
|
|
||||||
$(this).height(maxWidth/r);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$(this).width(r * origHeight);
|
|
||||||
$(this).height(origHeight);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});*/
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,20 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
|
|
||||||
{%block realcontent %}
|
|
||||||
<h2>Rachat d'une place</h2>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<pre>
|
|
||||||
Bonjour !
|
|
||||||
|
|
||||||
Je souhaiterais racheter ta place pour {{spectacle.title}} le {{spectacle.date}} ({{spectacle.location}}) à {{spectacle.price}}€.
|
|
||||||
Contacte-moi si tu es toujours intéressé-e !
|
|
||||||
|
|
||||||
{{user.get_full_name}} ({{user.email}})
|
|
||||||
</pre>
|
|
||||||
<input type="submit" class="btn btn-primary pull-right" value="Envoyer">
|
|
||||||
</form>
|
|
||||||
<p class="bda-prix">Note : ce mail sera envoyé à une personne au hasard revendant sa place.</p>
|
|
||||||
{%endblock%}
|
|
|
@ -1,56 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load staticfiles %}
|
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2><strong>{{tirage_name}}</strong></h2>
|
|
||||||
<h3>Liste des spectacles</h3>
|
|
||||||
|
|
||||||
|
|
||||||
<table class="table table-striped table-hover etat-bda">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-sort="string">Titre</th>
|
|
||||||
<th data-sort="int">Date</th>
|
|
||||||
<th data-sort="string">Lieu</th>
|
|
||||||
<th data-sort="float">Prix</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for spectacle in object_list %}
|
|
||||||
<tr class="clickable-row {% if spectacle.is_past %}spectacle-passe{% endif %}" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}">
|
|
||||||
<td><a href="{% url 'bda-spectacle' tirage_id spectacle.id %}">{{ spectacle.title }} <span style="font-size:small;" class="glyphicon glyphicon-link" aria-hidden="true"></span></a></td>
|
|
||||||
<td data-sort-value="{{ spectacle.timestamp }}"">{{ spectacle.date }}</td>
|
|
||||||
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
|
|
||||||
<td data-sort-value="{{ spectacle.price |stringformat:".3f" }}">
|
|
||||||
{{ spectacle.price |floatformat }}€
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<script type="text/javascript"
|
|
||||||
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}">
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function(){
|
|
||||||
$("table.etat-bda").stupidtable();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
jQuery(document).ready(function($) {
|
|
||||||
$(".clickable-row").click(function() {
|
|
||||||
window.document.location = $(this).data("href");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3> Exports </h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="{% url 'bda-unpaid' tirage_id %}">Mailing list impayés</a>
|
|
||||||
<li><a href="{% url 'bda-descriptions' tirage_id %}">Lien vers les descriptions des spectacles, à utiliser dans une page wordpress</a>
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Raté, le tirage ne peut pas être lancé !</h2>
|
|
||||||
|
|
||||||
<p>Soit les inscriptions ne sont en pas encore fermées, soit le lancement du
|
|
||||||
tirage est désactivé. Si vous savez ce que vous faites, vous pouvez autoriser
|
|
||||||
le lancement du tirage dans
|
|
||||||
l'<a href="{% url "admin:index" %}">interface admin</a>.</p>
|
|
||||||
{% endblock %}
|
|
22
bda/tests.py
22
bda/tests.py
|
@ -1,22 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
This file demonstrates writing tests using the unittest module. These will pass
|
|
||||||
when you run "manage.py test".
|
|
||||||
|
|
||||||
Replace this with more appropriate tests for your application.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleTest(TestCase):
|
|
||||||
def test_basic_addition(self):
|
|
||||||
"""
|
|
||||||
Tests that 1 + 1 always equals 2.
|
|
||||||
"""
|
|
||||||
self.assertEqual(1 + 1, 2)
|
|
50
bda/urls.py
50
bda/urls.py
|
@ -1,50 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
from cof.decorators import buro_required
|
|
||||||
from bda.views import SpectacleListView
|
|
||||||
from bda import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^inscription/(?P<tirage_id>\d+)$',
|
|
||||||
views.inscription,
|
|
||||||
name='bda-tirage-inscription'),
|
|
||||||
url(r'^places/(?P<tirage_id>\d+)$',
|
|
||||||
views.places,
|
|
||||||
name="bda-places-attribuees"),
|
|
||||||
url(r'^revente/(?P<tirage_id>\d+)$',
|
|
||||||
views.revente,
|
|
||||||
name='bda-revente'),
|
|
||||||
url(r'^etat-places/(?P<tirage_id>\d+)$',
|
|
||||||
views.etat_places,
|
|
||||||
name='bda-etat-places'),
|
|
||||||
url(r'^tirage/(?P<tirage_id>\d+)$', views.tirage),
|
|
||||||
url(r'^spectacles/(?P<tirage_id>\d+)$',
|
|
||||||
buro_required(SpectacleListView.as_view()),
|
|
||||||
name="bda-liste-spectacles"),
|
|
||||||
url(r'^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$',
|
|
||||||
views.spectacle,
|
|
||||||
name="bda-spectacle"),
|
|
||||||
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
|
||||||
views.unpaid,
|
|
||||||
name="bda-unpaid"),
|
|
||||||
url(r'^liste-revente/(?P<tirage_id>\d+)$',
|
|
||||||
views.list_revente,
|
|
||||||
name="bda-liste-revente"),
|
|
||||||
url(r'^buy-revente/(?P<spectacle_id>\d+)$',
|
|
||||||
views.buy_revente,
|
|
||||||
name="bda-buy-revente"),
|
|
||||||
url(r'^revente-interested/(?P<revente_id>\d+)$',
|
|
||||||
views.revente_interested,
|
|
||||||
name='bda-revente-interested'),
|
|
||||||
url(r'^revente-immediat/(?P<tirage_id>\d+)$',
|
|
||||||
views.revente_shotgun,
|
|
||||||
name="bda-shotgun"),
|
|
||||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
|
|
||||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
|
||||||
name='bda-descriptions'),
|
|
||||||
]
|
|
653
bda/views.py
653
bda/views.py
|
@ -1,653 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import random
|
|
||||||
import hashlib
|
|
||||||
import time
|
|
||||||
from datetime import timedelta
|
|
||||||
from custommail.shortcuts import (
|
|
||||||
send_mass_custom_mail, send_custom_mail, render_custom_mail
|
|
||||||
)
|
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.db import models, transaction
|
|
||||||
from django.core import serializers
|
|
||||||
from django.db.models import Count, Q, Sum
|
|
||||||
from django.forms.models import inlineformset_factory
|
|
||||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone, formats
|
|
||||||
from django.views.generic.list import ListView
|
|
||||||
|
|
||||||
from cof.decorators import cof_required, buro_required
|
|
||||||
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
|
||||||
Tirage, SpectacleRevente
|
|
||||||
from bda.algorithm import Algorithm
|
|
||||||
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
|
|
||||||
InscriptionReventeForm
|
|
||||||
|
|
||||||
|
|
||||||
@cof_required
|
|
||||||
def etat_places(request, tirage_id):
|
|
||||||
"""
|
|
||||||
Résumé des spectacles d'un tirage avec pour chaque spectacle :
|
|
||||||
- Le nombre de places en jeu
|
|
||||||
- Le nombre de demandes
|
|
||||||
- Le ratio demandes/places
|
|
||||||
Et le total de toutes les demandes
|
|
||||||
"""
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
spectacles1 = ChoixSpectacle.objects \
|
|
||||||
.filter(spectacle__tirage=tirage) \
|
|
||||||
.filter(double_choice="1") \
|
|
||||||
.all() \
|
|
||||||
.values('spectacle', 'spectacle__title') \
|
|
||||||
.annotate(total=models.Count('spectacle'))
|
|
||||||
spectacles2 = ChoixSpectacle.objects \
|
|
||||||
.filter(spectacle__tirage=tirage) \
|
|
||||||
.exclude(double_choice="1") \
|
|
||||||
.all() \
|
|
||||||
.values('spectacle', 'spectacle__title') \
|
|
||||||
.annotate(total=models.Count('spectacle'))
|
|
||||||
spectacles = tirage.spectacle_set.all()
|
|
||||||
spectacles_dict = {}
|
|
||||||
total = 0
|
|
||||||
for spectacle in spectacles:
|
|
||||||
spectacle.total = 0
|
|
||||||
spectacle.ratio = 0.0
|
|
||||||
spectacles_dict[spectacle.id] = spectacle
|
|
||||||
for spectacle in spectacles1:
|
|
||||||
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
|
|
||||||
spectacles_dict[spectacle["spectacle"]].ratio = \
|
|
||||||
spectacles_dict[spectacle["spectacle"]].total / \
|
|
||||||
spectacles_dict[spectacle["spectacle"]].slots
|
|
||||||
total += spectacle["total"]
|
|
||||||
for spectacle in spectacles2:
|
|
||||||
spectacles_dict[spectacle["spectacle"]].total += 2*spectacle["total"]
|
|
||||||
spectacles_dict[spectacle["spectacle"]].ratio = \
|
|
||||||
spectacles_dict[spectacle["spectacle"]].total / \
|
|
||||||
spectacles_dict[spectacle["spectacle"]].slots
|
|
||||||
total += 2*spectacle["total"]
|
|
||||||
context = {
|
|
||||||
"proposed": tirage.spectacle_set.aggregate(Sum('slots'))['slots__sum'],
|
|
||||||
"spectacles": spectacles,
|
|
||||||
"total": total,
|
|
||||||
'tirage': tirage
|
|
||||||
}
|
|
||||||
return render(request, "bda/etat-places.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
def _hash_queryset(queryset):
|
|
||||||
data = serializers.serialize("json", queryset).encode('utf-8')
|
|
||||||
hasher = hashlib.sha256()
|
|
||||||
hasher.update(data)
|
|
||||||
return hasher.hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
@cof_required
|
|
||||||
def places(request, tirage_id):
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
participant, created = Participant.objects.get_or_create(
|
|
||||||
user=request.user, tirage=tirage)
|
|
||||||
places = participant.attribution_set.order_by(
|
|
||||||
"spectacle__date", "spectacle").all()
|
|
||||||
total = sum([place.spectacle.price for place in places])
|
|
||||||
filtered_places = []
|
|
||||||
places_dict = {}
|
|
||||||
spectacles = []
|
|
||||||
dates = []
|
|
||||||
warning = False
|
|
||||||
for place in places:
|
|
||||||
if place.spectacle in spectacles:
|
|
||||||
places_dict[place.spectacle].double = True
|
|
||||||
else:
|
|
||||||
place.double = False
|
|
||||||
places_dict[place.spectacle] = place
|
|
||||||
spectacles.append(place.spectacle)
|
|
||||||
filtered_places.append(place)
|
|
||||||
date = place.spectacle.date.date()
|
|
||||||
if date in dates:
|
|
||||||
warning = True
|
|
||||||
else:
|
|
||||||
dates.append(date)
|
|
||||||
# On prévient l'utilisateur s'il a deux places à la même date
|
|
||||||
if warning:
|
|
||||||
messages.warning(request, "Attention, vous avez reçu des places pour "
|
|
||||||
"des spectacles différents à la même date.")
|
|
||||||
return render(request, "bda/resume_places.html",
|
|
||||||
{"participant": participant,
|
|
||||||
"places": filtered_places,
|
|
||||||
"tirage": tirage,
|
|
||||||
"total": total})
|
|
||||||
|
|
||||||
|
|
||||||
@cof_required
|
|
||||||
def inscription(request, tirage_id):
|
|
||||||
"""
|
|
||||||
Vue d'inscription à un tirage BdA.
|
|
||||||
- On vérifie qu'on se situe bien entre la date d'ouverture et la date de
|
|
||||||
fermeture des inscriptions.
|
|
||||||
- On vérifie que l'inscription n'a pas été modifiée entre le moment où le
|
|
||||||
client demande le formulaire et le moment où il soumet son inscription
|
|
||||||
(autre session par exemple).
|
|
||||||
"""
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
if timezone.now() < tirage.ouverture:
|
|
||||||
# Le tirage n'est pas encore ouvert.
|
|
||||||
opening = formats.localize(
|
|
||||||
timezone.template_localtime(tirage.ouverture))
|
|
||||||
messages.error(request, "Le tirage n'est pas encore ouvert : "
|
|
||||||
"ouverture le {:s}".format(opening))
|
|
||||||
return render(request, 'bda/resume-inscription-tirage.html', {})
|
|
||||||
if timezone.now() > tirage.fermeture:
|
|
||||||
# Le tirage est fermé.
|
|
||||||
participant, created = Participant.objects.get_or_create(
|
|
||||||
user=request.user, tirage=tirage)
|
|
||||||
choices = participant.choixspectacle_set.order_by("priority").all()
|
|
||||||
messages.error(request,
|
|
||||||
" C'est fini : tirage au sort dans la journée !")
|
|
||||||
return render(request, "bda/resume-inscription-tirage.html",
|
|
||||||
{"choices": choices})
|
|
||||||
|
|
||||||
def formfield_callback(f, **kwargs):
|
|
||||||
"""
|
|
||||||
Fonction utilisée par inlineformset_factory ci dessous.
|
|
||||||
Restreint les spectacles proposés aux spectacles du bo tirage.
|
|
||||||
"""
|
|
||||||
if f.name == "spectacle":
|
|
||||||
kwargs['queryset'] = tirage.spectacle_set
|
|
||||||
return f.formfield(**kwargs)
|
|
||||||
BdaFormSet = inlineformset_factory(
|
|
||||||
Participant,
|
|
||||||
ChoixSpectacle,
|
|
||||||
fields=("spectacle", "double_choice", "priority"),
|
|
||||||
formset=BaseBdaFormSet,
|
|
||||||
formfield_callback=formfield_callback)
|
|
||||||
participant, created = Participant.objects.get_or_create(
|
|
||||||
user=request.user, tirage=tirage)
|
|
||||||
success = False
|
|
||||||
stateerror = False
|
|
||||||
if request.method == "POST":
|
|
||||||
dbstate = _hash_queryset(participant.choixspectacle_set.all())
|
|
||||||
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
|
|
||||||
stateerror = True
|
|
||||||
formset = BdaFormSet(instance=participant)
|
|
||||||
else:
|
|
||||||
formset = BdaFormSet(request.POST, instance=participant)
|
|
||||||
if formset.is_valid():
|
|
||||||
formset.save()
|
|
||||||
success = True
|
|
||||||
formset = BdaFormSet(instance=participant)
|
|
||||||
else:
|
|
||||||
formset = BdaFormSet(instance=participant)
|
|
||||||
dbstate = _hash_queryset(participant.choixspectacle_set.all())
|
|
||||||
total_price = 0
|
|
||||||
for choice in participant.choixspectacle_set.all():
|
|
||||||
total_price += choice.spectacle.price
|
|
||||||
if choice.double:
|
|
||||||
total_price += choice.spectacle.price
|
|
||||||
# Messages
|
|
||||||
if success:
|
|
||||||
messages.success(request, "Votre inscription a été mise à jour avec "
|
|
||||||
"succès !")
|
|
||||||
if stateerror:
|
|
||||||
messages.error(request, "Impossible d'enregistrer vos modifications "
|
|
||||||
": vous avez apporté d'autres modifications "
|
|
||||||
"entre temps.")
|
|
||||||
return render(request, "bda/inscription-tirage.html",
|
|
||||||
{"formset": formset,
|
|
||||||
"total_price": total_price,
|
|
||||||
"dbstate": dbstate,
|
|
||||||
'tirage': tirage})
|
|
||||||
|
|
||||||
|
|
||||||
def do_tirage(tirage_elt, token):
|
|
||||||
"""
|
|
||||||
Fonction auxiliaire à la vue ``tirage`` qui lance effectivement le tirage
|
|
||||||
après qu'on a vérifié que c'est légitime et que le token donné en argument
|
|
||||||
est correct.
|
|
||||||
Rend les résultats
|
|
||||||
"""
|
|
||||||
# Initialisation du dictionnaire data qui va contenir les résultats
|
|
||||||
start = time.time()
|
|
||||||
data = {
|
|
||||||
'shows': tirage_elt.spectacle_set.select_related().all(),
|
|
||||||
'token': token,
|
|
||||||
'members': tirage_elt.participant_set.all(),
|
|
||||||
'total_slots': 0,
|
|
||||||
'total_losers': 0,
|
|
||||||
'total_sold': 0,
|
|
||||||
'total_deficit': 0,
|
|
||||||
'opera_deficit': 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
# On lance le tirage
|
|
||||||
choices = (
|
|
||||||
ChoixSpectacle.objects
|
|
||||||
.filter(spectacle__tirage=tirage_elt)
|
|
||||||
.order_by('participant', 'priority')
|
|
||||||
.select_related().all()
|
|
||||||
)
|
|
||||||
results = Algorithm(data['shows'], data['members'], choices)(token)
|
|
||||||
|
|
||||||
# On compte les places attribuées et les déçus
|
|
||||||
for (_, members, losers) in results:
|
|
||||||
data['total_slots'] += len(members)
|
|
||||||
data['total_losers'] += len(losers)
|
|
||||||
|
|
||||||
# On calcule le déficit et les bénéfices pour le BdA
|
|
||||||
# FIXME: le traitement de l'opéra est sale
|
|
||||||
for (show, members, _) in results:
|
|
||||||
deficit = (show.slots - len(members)) * show.price
|
|
||||||
data['total_sold'] += show.slots * show.price
|
|
||||||
if deficit >= 0:
|
|
||||||
if "Opéra" in show.location.name:
|
|
||||||
data['opera_deficit'] += deficit
|
|
||||||
data['total_deficit'] += deficit
|
|
||||||
data["total_sold"] -= data['total_deficit']
|
|
||||||
|
|
||||||
# Participant objects are not shared accross spectacle results,
|
|
||||||
# so assign a single object for each Participant id
|
|
||||||
members_uniq = {}
|
|
||||||
members2 = {}
|
|
||||||
for (show, members, _) in results:
|
|
||||||
for (member, _, _, _) in members:
|
|
||||||
if member.id not in members_uniq:
|
|
||||||
members_uniq[member.id] = member
|
|
||||||
members2[member] = []
|
|
||||||
member.total = 0
|
|
||||||
member = members_uniq[member.id]
|
|
||||||
members2[member].append(show)
|
|
||||||
member.total += show.price
|
|
||||||
members2 = members2.items()
|
|
||||||
data["members2"] = sorted(members2, key=lambda m: m[0].user.last_name)
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# À partir d'ici, le tirage devient effectif
|
|
||||||
# ---
|
|
||||||
|
|
||||||
# On suppression les vieilles attributions, on sauvegarde le token et on
|
|
||||||
# désactive le tirage
|
|
||||||
Attribution.objects.filter(spectacle__tirage=tirage_elt).delete()
|
|
||||||
tirage_elt.tokens += '{:s}\n"""{:s}"""\n'.format(
|
|
||||||
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
|
||||||
token)
|
|
||||||
tirage_elt.enable_do_tirage = False
|
|
||||||
tirage_elt.save()
|
|
||||||
|
|
||||||
# On enregistre les nouvelles attributions
|
|
||||||
Attribution.objects.bulk_create([
|
|
||||||
Attribution(spectacle=show, participant=member)
|
|
||||||
for show, members, _ in results
|
|
||||||
for member, _, _, _ in members
|
|
||||||
])
|
|
||||||
|
|
||||||
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
|
|
||||||
for (show, _, losers) in results:
|
|
||||||
for (loser, _, _, _) in losers:
|
|
||||||
loser.choicesrevente.add(show)
|
|
||||||
loser.save()
|
|
||||||
|
|
||||||
data["duration"] = time.time() - start
|
|
||||||
data["results"] = results
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
|
||||||
def tirage(request, tirage_id):
|
|
||||||
tirage_elt = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
if not (tirage_elt.enable_do_tirage
|
|
||||||
and tirage_elt.fermeture < timezone.now()):
|
|
||||||
return render(request, "tirage-failed.html", {'tirage': tirage_elt})
|
|
||||||
if request.POST:
|
|
||||||
form = TokenForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
results = do_tirage(tirage_elt, form.cleaned_data['token'])
|
|
||||||
return render(request, "bda-attrib-extra.html", results)
|
|
||||||
else:
|
|
||||||
form = TokenForm()
|
|
||||||
return render(request, "bda-token.html", {"form": form})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def revente(request, tirage_id):
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
participant, created = Participant.objects.get_or_create(
|
|
||||||
user=request.user, tirage=tirage)
|
|
||||||
if not participant.paid:
|
|
||||||
return render(request, "bda-notpaid.html", {})
|
|
||||||
if request.method == 'POST':
|
|
||||||
# On met en vente une place
|
|
||||||
if 'resell' in request.POST:
|
|
||||||
resellform = ResellForm(participant, request.POST, prefix='resell')
|
|
||||||
annulform = AnnulForm(participant, prefix='annul')
|
|
||||||
if resellform.is_valid():
|
|
||||||
datatuple = []
|
|
||||||
attributions = resellform.cleaned_data["attributions"]
|
|
||||||
with transaction.atomic():
|
|
||||||
for attribution in attributions:
|
|
||||||
revente, created = \
|
|
||||||
SpectacleRevente.objects.get_or_create(
|
|
||||||
attribution=attribution,
|
|
||||||
defaults={'seller': participant})
|
|
||||||
if not created:
|
|
||||||
revente.seller = participant
|
|
||||||
revente.date = timezone.now()
|
|
||||||
revente.soldTo = None
|
|
||||||
revente.notif_sent = False
|
|
||||||
revente.tirage_done = False
|
|
||||||
revente.shotgun = False
|
|
||||||
context = {
|
|
||||||
'vendeur': participant.user,
|
|
||||||
'show': attribution.spectacle,
|
|
||||||
'revente': revente
|
|
||||||
}
|
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-new', context,
|
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
[participant.user.email]
|
|
||||||
))
|
|
||||||
revente.save()
|
|
||||||
send_mass_custom_mail(datatuple)
|
|
||||||
# On annule une revente
|
|
||||||
elif 'annul' in request.POST:
|
|
||||||
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
|
||||||
resellform = ResellForm(participant, prefix='resell')
|
|
||||||
if annulform.is_valid():
|
|
||||||
attributions = annulform.cleaned_data["attributions"]
|
|
||||||
for attribution in attributions:
|
|
||||||
attribution.revente.delete()
|
|
||||||
# On confirme une vente en transférant la place à la personne qui a
|
|
||||||
# gagné le tirage
|
|
||||||
elif 'transfer' in request.POST:
|
|
||||||
resellform = ResellForm(participant, prefix='resell')
|
|
||||||
annulform = AnnulForm(participant, prefix='annul')
|
|
||||||
|
|
||||||
revente_id = request.POST['transfer'][0]
|
|
||||||
rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
|
|
||||||
id=revente_id)
|
|
||||||
if rev.exists():
|
|
||||||
revente = rev.get()
|
|
||||||
attrib = revente.attribution
|
|
||||||
attrib.participant = revente.soldTo
|
|
||||||
attrib.save()
|
|
||||||
# On annule la revente après le tirage au sort (par exemple si
|
|
||||||
# la personne qui a gagné le tirage ne se manifeste pas). La place est
|
|
||||||
# alors remise en vente
|
|
||||||
elif 'reinit' in request.POST:
|
|
||||||
resellform = ResellForm(participant, prefix='resell')
|
|
||||||
annulform = AnnulForm(participant, prefix='annul')
|
|
||||||
revente_id = request.POST['reinit'][0]
|
|
||||||
rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
|
|
||||||
id=revente_id)
|
|
||||||
if rev.exists():
|
|
||||||
revente = rev.get()
|
|
||||||
if revente.attribution.spectacle.date > timezone.now():
|
|
||||||
revente.date = timezone.now() - timedelta(hours=1)
|
|
||||||
revente.soldTo = None
|
|
||||||
revente.notif_sent = False
|
|
||||||
revente.tirage_done = False
|
|
||||||
revente.shotgun = False
|
|
||||||
if revente.answered_mail:
|
|
||||||
revente.answered_mail.clear()
|
|
||||||
revente.save()
|
|
||||||
|
|
||||||
else:
|
|
||||||
resellform = ResellForm(participant, prefix='resell')
|
|
||||||
annulform = AnnulForm(participant, prefix='annul')
|
|
||||||
else:
|
|
||||||
resellform = ResellForm(participant, prefix='resell')
|
|
||||||
annulform = AnnulForm(participant, prefix='annul')
|
|
||||||
|
|
||||||
overdue = participant.attribution_set.filter(
|
|
||||||
spectacle__date__gte=timezone.now(),
|
|
||||||
revente__isnull=False,
|
|
||||||
revente__seller=participant,
|
|
||||||
revente__date__lte=timezone.now()-timedelta(hours=1)).filter(
|
|
||||||
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
|
|
||||||
sold = participant.attribution_set.filter(
|
|
||||||
spectacle__date__gte=timezone.now(),
|
|
||||||
revente__isnull=False,
|
|
||||||
revente__soldTo__isnull=False)
|
|
||||||
|
|
||||||
return render(request, "bda-revente.html",
|
|
||||||
{'tirage': tirage, 'overdue': overdue, "sold": sold,
|
|
||||||
"annulform": annulform, "resellform": resellform})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def revente_interested(request, revente_id):
|
|
||||||
revente = get_object_or_404(SpectacleRevente, id=revente_id)
|
|
||||||
participant, _ = Participant.objects.get_or_create(
|
|
||||||
user=request.user, tirage=revente.attribution.spectacle.tirage)
|
|
||||||
if (timezone.now() < revente.date + timedelta(hours=1)) or revente.shotgun:
|
|
||||||
return render(request, "bda-wrongtime.html",
|
|
||||||
{"revente": revente})
|
|
||||||
|
|
||||||
revente.answered_mail.add(participant)
|
|
||||||
return render(request, "bda-interested.html",
|
|
||||||
{"spectacle": revente.attribution.spectacle,
|
|
||||||
"date": revente.date_tirage})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def list_revente(request, tirage_id):
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
participant, _ = Participant.objects.get_or_create(
|
|
||||||
user=request.user, tirage=tirage)
|
|
||||||
deja_revente = False
|
|
||||||
success = False
|
|
||||||
inscrit_revente = []
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = InscriptionReventeForm(tirage, request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
choices = form.cleaned_data['spectacles']
|
|
||||||
participant.choicesrevente = choices
|
|
||||||
participant.save()
|
|
||||||
for spectacle in choices:
|
|
||||||
qset = SpectacleRevente.objects.filter(
|
|
||||||
attribution__spectacle=spectacle)
|
|
||||||
if qset.filter(shotgun=True, soldTo__isnull=True).exists():
|
|
||||||
# Une place est disponible au shotgun, on suggère à
|
|
||||||
# l'utilisateur d'aller la récupérer
|
|
||||||
deja_revente = True
|
|
||||||
else:
|
|
||||||
# La place n'est pas disponible au shotgun, si des reventes
|
|
||||||
# pour ce spectacle existent déjà, on inscrit la personne à
|
|
||||||
# la revente ayant le moins d'inscrits
|
|
||||||
min_resell = (
|
|
||||||
qset.filter(shotgun=False)
|
|
||||||
.annotate(nb_subscribers=Count('answered_mail'))
|
|
||||||
.order_by('nb_subscribers')
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if min_resell is not None:
|
|
||||||
min_resell.answered_mail.add(participant)
|
|
||||||
min_resell.save()
|
|
||||||
inscrit_revente.append(spectacle)
|
|
||||||
success = True
|
|
||||||
else:
|
|
||||||
form = InscriptionReventeForm(
|
|
||||||
tirage,
|
|
||||||
initial={'spectacles': participant.choicesrevente.all()}
|
|
||||||
)
|
|
||||||
# Messages
|
|
||||||
if success:
|
|
||||||
messages.success(request, "Ton inscription a bien été prise en compte")
|
|
||||||
if deja_revente:
|
|
||||||
messages.info(request, "Des reventes existent déjà pour certains de "
|
|
||||||
"ces spectacles, vérifie les places "
|
|
||||||
"disponibles sans tirage !")
|
|
||||||
if inscrit_revente:
|
|
||||||
shows = map("<li>{!s}</li>".format, inscrit_revente)
|
|
||||||
msg = (
|
|
||||||
"Tu as été inscrit à des reventes en cours pour les spectacles "
|
|
||||||
"<ul>{:s}</ul>".format('\n'.join(shows))
|
|
||||||
)
|
|
||||||
messages.info(request, msg, extra_tags="safe")
|
|
||||||
|
|
||||||
return render(request, "bda/liste-reventes.html", {"form": form})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def buy_revente(request, spectacle_id):
|
|
||||||
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
|
|
||||||
tirage = spectacle.tirage
|
|
||||||
participant, _ = Participant.objects.get_or_create(
|
|
||||||
user=request.user, tirage=tirage)
|
|
||||||
reventes = SpectacleRevente.objects.filter(
|
|
||||||
attribution__spectacle=spectacle,
|
|
||||||
soldTo__isnull=True)
|
|
||||||
|
|
||||||
# Si l'utilisateur veut racheter une place qu'il est en train de revendre,
|
|
||||||
# on supprime la revente en question.
|
|
||||||
if reventes.filter(seller=participant).exists():
|
|
||||||
revente = reventes.filter(seller=participant)[0]
|
|
||||||
revente.delete()
|
|
||||||
return HttpResponseRedirect(reverse("bda-shotgun",
|
|
||||||
args=[tirage.id]))
|
|
||||||
|
|
||||||
reventes_shotgun = list(reventes.filter(shotgun=True).all())
|
|
||||||
|
|
||||||
if not reventes_shotgun:
|
|
||||||
return render(request, "bda-no-revente.html", {})
|
|
||||||
|
|
||||||
if request.POST:
|
|
||||||
revente = random.choice(reventes_shotgun)
|
|
||||||
revente.soldTo = participant
|
|
||||||
revente.save()
|
|
||||||
context = {
|
|
||||||
'show': spectacle,
|
|
||||||
'acheteur': request.user,
|
|
||||||
'vendeur': revente.seller.user
|
|
||||||
}
|
|
||||||
send_custom_mail(
|
|
||||||
'bda-buy-shotgun',
|
|
||||||
'bda@ens.fr',
|
|
||||||
[revente.seller.user.email],
|
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
return render(request, "bda-success.html",
|
|
||||||
{"seller": revente.attribution.participant.user,
|
|
||||||
"spectacle": spectacle})
|
|
||||||
|
|
||||||
return render(request, "revente-confirm.html",
|
|
||||||
{"spectacle": spectacle,
|
|
||||||
"user": request.user})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def revente_shotgun(request, tirage_id):
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
spectacles = tirage.spectacle_set.filter(
|
|
||||||
date__gte=timezone.now())
|
|
||||||
shotgun = []
|
|
||||||
for spectacle in spectacles:
|
|
||||||
reventes = SpectacleRevente.objects.filter(
|
|
||||||
attribution__spectacle=spectacle,
|
|
||||||
shotgun=True,
|
|
||||||
soldTo__isnull=True)
|
|
||||||
if reventes.exists():
|
|
||||||
shotgun.append(spectacle)
|
|
||||||
|
|
||||||
return render(request, "bda-shotgun.html",
|
|
||||||
{"shotgun": shotgun})
|
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
|
||||||
def spectacle(request, tirage_id, spectacle_id):
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
spectacle = get_object_or_404(Spectacle, id=spectacle_id, tirage=tirage)
|
|
||||||
attributions = spectacle.attribues.all()
|
|
||||||
participants = {}
|
|
||||||
for attrib in attributions:
|
|
||||||
participant = attrib.participant
|
|
||||||
participant_info = {'lastname': participant.user.last_name,
|
|
||||||
'name': participant.user.get_full_name,
|
|
||||||
'username': participant.user.username,
|
|
||||||
'email': participant.user.email,
|
|
||||||
'given': int(attrib.given),
|
|
||||||
'paid': participant.paid,
|
|
||||||
'nb_places': 1}
|
|
||||||
if participant.id in participants:
|
|
||||||
participants[participant.id]['nb_places'] += 1
|
|
||||||
participants[participant.id]['given'] += attrib.given
|
|
||||||
else:
|
|
||||||
participants[participant.id] = participant_info
|
|
||||||
|
|
||||||
participants_info = sorted(participants.values(),
|
|
||||||
key=lambda part: part['lastname'])
|
|
||||||
return render(request, "bda-participants.html",
|
|
||||||
{"spectacle": spectacle, "participants": participants_info})
|
|
||||||
|
|
||||||
|
|
||||||
class SpectacleListView(ListView):
|
|
||||||
model = Spectacle
|
|
||||||
template_name = 'spectacle_list.html'
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
self.tirage = get_object_or_404(Tirage, id=self.kwargs['tirage_id'])
|
|
||||||
categories = self.tirage.spectacle_set.all()
|
|
||||||
return categories
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(SpectacleListView, self).get_context_data(**kwargs)
|
|
||||||
context['tirage_id'] = self.tirage.id
|
|
||||||
context['tirage_name'] = self.tirage.title
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
|
||||||
def unpaid(request, tirage_id):
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
unpaid = tirage.participant_set \
|
|
||||||
.annotate(nb_attributions=Count('attribution')) \
|
|
||||||
.filter(paid=False, nb_attributions__gt=0).all()
|
|
||||||
return render(request, "bda-unpaid.html", {"unpaid": unpaid})
|
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
|
||||||
def send_rappel(request, spectacle_id):
|
|
||||||
show = get_object_or_404(Spectacle, id=spectacle_id)
|
|
||||||
# Mails d'exemples
|
|
||||||
exemple_mail_1place = render_custom_mail('bda-rappel', {
|
|
||||||
'member': request.user,
|
|
||||||
'show': show,
|
|
||||||
'nb_attr': 1
|
|
||||||
})
|
|
||||||
exemple_mail_2places = render_custom_mail('bda-rappel', {
|
|
||||||
'member': request.user,
|
|
||||||
'show': show,
|
|
||||||
'nb_attr': 2
|
|
||||||
})
|
|
||||||
# Contexte
|
|
||||||
ctxt = {'show': show,
|
|
||||||
'exemple_mail_1place': exemple_mail_1place,
|
|
||||||
'exemple_mail_2places': exemple_mail_2places}
|
|
||||||
# Envoi confirmé
|
|
||||||
if request.method == 'POST':
|
|
||||||
members = show.send_rappel()
|
|
||||||
ctxt['sent'] = True
|
|
||||||
ctxt['members'] = members
|
|
||||||
# Demande de confirmation
|
|
||||||
else:
|
|
||||||
ctxt['sent'] = False
|
|
||||||
return render(request, "bda/mails-rappel.html", ctxt)
|
|
||||||
|
|
||||||
|
|
||||||
def descriptions_spectacles(request, tirage_id):
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
shows_qs = tirage.spectacle_set
|
|
||||||
category_name = request.GET.get('category', '')
|
|
||||||
location_id = request.GET.get('location', '')
|
|
||||||
if category_name:
|
|
||||||
shows_qs = shows_qs.filter(category__name=category_name)
|
|
||||||
if location_id:
|
|
||||||
try:
|
|
||||||
shows_qs = shows_qs.filter(location__id=int(location_id))
|
|
||||||
except ValueError:
|
|
||||||
return HttpResponseBadRequest(
|
|
||||||
"La variable GET 'location' doit contenir un entier")
|
|
||||||
return render(request, 'descriptions.html', {'shows': shows_qs.all()})
|
|
|
@ -1,4 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
from .models import BdsProfile
|
|
||||||
|
|
||||||
admin.site.register(BdsProfile)
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class BDSConfig(AppConfig):
|
|
||||||
name = "bds"
|
|
||||||
verbose_name = "Application de gestion du BDS"
|
|
|
@ -1,32 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import bds.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('gestion', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='BdsProfile',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)),
|
|
||||||
('ASPSL_number', models.CharField(null=True, blank=True, verbose_name='Numéro AS PSL', max_length=50)),
|
|
||||||
('FFSU_number', models.CharField(null=True, blank=True, verbose_name='Numéro FFSU', max_length=50)),
|
|
||||||
('have_certificate', models.BooleanField(verbose_name='Certificat médical', default=False)),
|
|
||||||
('certificate_file', models.FileField(blank=True, upload_to=bds.models.BdsProfile.issue_file_name, verbose_name='Fichier de certificat médical')),
|
|
||||||
('cotisation_period', models.CharField(choices=[('ANN', 'Année'), ('SE1', 'Premier semestre'), ('SE2', 'Deuxième semestre')], verbose_name='Inscription', max_length=3, default='ANN')),
|
|
||||||
('registration_date', models.DateField(verbose_name="Date d'inscription", auto_now_add=True)),
|
|
||||||
('payment_method', models.CharField(choices=[('CASH', 'Liquide'), ('BANK', 'Transfer bancaire'), ('CHEQUE', 'Cheque'), ('OTHER', 'Autre')], verbose_name='Methode de paiement', max_length=6, default='CASH')),
|
|
||||||
('profile', models.OneToOneField(
|
|
||||||
related_name='bds',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='gestion.Profile')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,35 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def create_groups(apps, schema_editor):
|
|
||||||
"""
|
|
||||||
Creates the groups for BDS members and staff
|
|
||||||
"""
|
|
||||||
Group = apps.get_model("auth", "Group")
|
|
||||||
Group.objects.get_or_create(name="bds_members")
|
|
||||||
Group.objects.get_or_create(name="bds_buro")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bds', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='bdsprofile',
|
|
||||||
options={
|
|
||||||
'permissions': [
|
|
||||||
('member', 'Is a BDS member'),
|
|
||||||
('buro', 'Is part of the BDS staff')
|
|
||||||
],
|
|
||||||
'verbose_name': 'Profil BDS',
|
|
||||||
'verbose_name_plural': 'Profils BDS'
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.RunPython(create_groups, migrations.RunPython.noop),
|
|
||||||
]
|
|
|
@ -1,65 +0,0 @@
|
||||||
import os.path
|
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from gestion.models import Profile
|
|
||||||
|
|
||||||
|
|
||||||
class BdsProfile(models.Model):
|
|
||||||
profile = models.OneToOneField(Profile,
|
|
||||||
related_name='bds',
|
|
||||||
on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
def issue_file_name(sportif, filename):
|
|
||||||
fn, extension = os.path.splitext(filename)
|
|
||||||
year = timezone.now().year
|
|
||||||
return "certifs/{!s}-{:d}{:s}".format(sportif, year, extension)
|
|
||||||
|
|
||||||
COTIZ_DURATION_CHOICES = (
|
|
||||||
('ANN', 'Année'),
|
|
||||||
('SE1', 'Premier semestre'),
|
|
||||||
('SE2', 'Deuxième semestre'),
|
|
||||||
)
|
|
||||||
|
|
||||||
PAYMENT_METHOD_CHOICES = (
|
|
||||||
('CASH', 'Liquide'),
|
|
||||||
('BANK', 'Transfer bancaire'),
|
|
||||||
('CHEQUE', 'Cheque'),
|
|
||||||
('OTHER', 'Autre'),
|
|
||||||
)
|
|
||||||
|
|
||||||
ASPSL_number = models.CharField("Numéro AS PSL",
|
|
||||||
max_length=50,
|
|
||||||
blank=True,
|
|
||||||
null=True)
|
|
||||||
FFSU_number = models.CharField("Numéro FFSU",
|
|
||||||
max_length=50,
|
|
||||||
blank=True,
|
|
||||||
null=True)
|
|
||||||
|
|
||||||
have_certificate = models.BooleanField("Certificat médical",
|
|
||||||
default=False)
|
|
||||||
certificate_file = models.FileField("Fichier de certificat médical",
|
|
||||||
upload_to=issue_file_name,
|
|
||||||
blank=True)
|
|
||||||
|
|
||||||
cotisation_period = models.CharField("Inscription",
|
|
||||||
default="ANN",
|
|
||||||
choices=COTIZ_DURATION_CHOICES,
|
|
||||||
max_length=3)
|
|
||||||
registration_date = models.DateField(auto_now_add=True,
|
|
||||||
verbose_name="Date d'inscription")
|
|
||||||
|
|
||||||
payment_method = models.CharField('Methode de paiement',
|
|
||||||
default='CASH',
|
|
||||||
choices=PAYMENT_METHOD_CHOICES,
|
|
||||||
max_length=6)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Profil BDS"
|
|
||||||
verbose_name_plural = "Profils BDS"
|
|
||||||
permissions = [
|
|
||||||
("member", "Is a BDS member"),
|
|
||||||
("buro", "Is part of the BDS staff")
|
|
||||||
]
|
|
12
bds/tests.py
12
bds/tests.py
|
@ -1,12 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from gestion.tests import create_profile
|
|
||||||
|
|
||||||
from .models import BdsProfile
|
|
||||||
|
|
||||||
class TestBdsProfile(TestCase):
|
|
||||||
def test_profile(self):
|
|
||||||
# each bdspofile should have an associated profile
|
|
||||||
p = create_profile('foo')
|
|
||||||
bdsp = BdsProfile.objects.create(profile=p)
|
|
||||||
self.assertEqual(p.bds, bdsp)
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
145
cof/admin.py
145
cof/admin.py
|
@ -1,145 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
import django.utils.six as six
|
|
||||||
|
|
||||||
from .petits_cours_models import PetitCoursDemande, \
|
|
||||||
PetitCoursSubject, PetitCoursAbility, PetitCoursAttribution, \
|
|
||||||
PetitCoursAttributionCounter
|
|
||||||
from .models import (
|
|
||||||
SurveyQuestionAnswer, SurveyQuestion, CofProfile, EventOption,
|
|
||||||
EventOptionChoice, Event, EventCommentField, EventRegistration,
|
|
||||||
Survey
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def add_link_field(target_model='', field='', link_text=six.text_type,
|
|
||||||
desc_text=six.text_type):
|
|
||||||
def add_link(cls):
|
|
||||||
reverse_name = target_model or cls.model.__name__.lower()
|
|
||||||
|
|
||||||
def link(self, instance):
|
|
||||||
app_name = instance._meta.app_label
|
|
||||||
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
|
|
||||||
link_obj = getattr(instance, field, None) or instance
|
|
||||||
if not link_obj.id:
|
|
||||||
return ""
|
|
||||||
url = reverse(reverse_path, args=(link_obj.id,))
|
|
||||||
return mark_safe("<a href='%s'>%s</a>"
|
|
||||||
% (url, link_text(link_obj)))
|
|
||||||
link.allow_tags = True
|
|
||||||
link.short_description = desc_text(reverse_name + ' link')
|
|
||||||
cls.link = link
|
|
||||||
cls.readonly_fields =\
|
|
||||||
list(getattr(cls, 'readonly_fields', [])) + ['link']
|
|
||||||
return cls
|
|
||||||
return add_link
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyQuestionAnswerInline(admin.TabularInline):
|
|
||||||
model = SurveyQuestionAnswer
|
|
||||||
|
|
||||||
|
|
||||||
@add_link_field(desc_text=lambda x: "Réponses",
|
|
||||||
link_text=lambda x: "Éditer les réponses")
|
|
||||||
class SurveyQuestionInline(admin.TabularInline):
|
|
||||||
model = SurveyQuestion
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyQuestionAdmin(admin.ModelAdmin):
|
|
||||||
search_fields = ('survey__title', 'answer')
|
|
||||||
inlines = [
|
|
||||||
SurveyQuestionAnswerInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyAdmin(admin.ModelAdmin):
|
|
||||||
search_fields = ('title', 'details')
|
|
||||||
inlines = [
|
|
||||||
SurveyQuestionInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EventOptionChoiceInline(admin.TabularInline):
|
|
||||||
model = EventOptionChoice
|
|
||||||
|
|
||||||
|
|
||||||
@add_link_field(desc_text=lambda x: "Choix",
|
|
||||||
link_text=lambda x: "Éditer les choix")
|
|
||||||
class EventOptionInline(admin.TabularInline):
|
|
||||||
model = EventOption
|
|
||||||
|
|
||||||
|
|
||||||
class EventCommentFieldInline(admin.TabularInline):
|
|
||||||
model = EventCommentField
|
|
||||||
|
|
||||||
|
|
||||||
class EventOptionAdmin(admin.ModelAdmin):
|
|
||||||
search_fields = ('event__title', 'name')
|
|
||||||
inlines = [
|
|
||||||
EventOptionChoiceInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EventAdmin(admin.ModelAdmin):
|
|
||||||
search_fields = ('title', 'location', 'description')
|
|
||||||
inlines = [
|
|
||||||
EventOptionInline,
|
|
||||||
EventCommentFieldInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EventRegistrationAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('__unicode__' if six.PY2 else '__str__', 'event', 'user',
|
|
||||||
'paid')
|
|
||||||
list_filter = ('paid',)
|
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
|
||||||
'user__email', 'event__title')
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAbilityAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('user', 'matiere', 'niveau', 'agrege')
|
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
|
||||||
'user__email', 'matiere__name', 'niveau')
|
|
||||||
list_filter = ('matiere', 'niveau', 'agrege')
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttributionAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('user', 'demande', 'matiere', 'rank', )
|
|
||||||
search_fields = ('user__username', 'matiere__name')
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttributionCounterAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('user', 'matiere', 'count', )
|
|
||||||
list_filter = ('matiere',)
|
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
|
||||||
'user__email', 'matiere__name')
|
|
||||||
actions = ['reset', ]
|
|
||||||
actions_on_bottom = True
|
|
||||||
|
|
||||||
def reset(self, request, queryset):
|
|
||||||
queryset.update(count=0)
|
|
||||||
reset.short_description = "Remise à zéro du compteur"
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursDemandeAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'email', 'agrege_requis', 'niveau', 'created',
|
|
||||||
'traitee', 'processed')
|
|
||||||
list_filter = ('traitee', 'niveau')
|
|
||||||
search_fields = ('name', 'email', 'phone', 'lieu', 'remarques')
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Survey, SurveyAdmin)
|
|
||||||
admin.site.register(SurveyQuestion, SurveyQuestionAdmin)
|
|
||||||
admin.site.register(Event, EventAdmin)
|
|
||||||
admin.site.register(EventOption, EventOptionAdmin)
|
|
||||||
admin.site.register(CofProfile)
|
|
||||||
admin.site.register(PetitCoursSubject)
|
|
||||||
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
|
|
||||||
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
|
|
||||||
admin.site.register(PetitCoursAttributionCounter,
|
|
||||||
PetitCoursAttributionCounterAdmin)
|
|
||||||
admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin)
|
|
||||||
admin.site.register(EventRegistration, EventRegistrationAdmin)
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class COFConfig(AppConfig):
|
|
||||||
name = "cof"
|
|
||||||
verbose_name = "Application de gestion du COF"
|
|
|
@ -1,78 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from ldap3 import Connection
|
|
||||||
|
|
||||||
from django import shortcuts
|
|
||||||
from django.http import Http404
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.contrib.auth.models import User, Group
|
|
||||||
from .decorators import buro_required
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class Clipper(object):
|
|
||||||
def __init__(self, clipper, fullname):
|
|
||||||
self.clipper = clipper
|
|
||||||
self.fullname = fullname
|
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
|
||||||
def autocomplete(request):
|
|
||||||
if "q" not in request.GET:
|
|
||||||
raise Http404
|
|
||||||
q = request.GET['q']
|
|
||||||
data = {'q': q}
|
|
||||||
cof_members = Group.objects.get(name="cof_members")
|
|
||||||
|
|
||||||
queries = {}
|
|
||||||
bits = q.split()
|
|
||||||
|
|
||||||
# Fetching data from User and Profile tables
|
|
||||||
queries['members'] = User.objects.filter(groups=cof_members)
|
|
||||||
queries['users'] = User.objects.exclude(groups=cof_members)
|
|
||||||
for bit in bits:
|
|
||||||
queries['members'] = queries['members'].filter(
|
|
||||||
Q(first_name__icontains=bit)
|
|
||||||
| Q(last_name__icontains=bit)
|
|
||||||
| Q(username__icontains=bit)
|
|
||||||
| Q(profile__login_clipper__icontains=bit))
|
|
||||||
queries['users'] = queries['users'].filter(
|
|
||||||
Q(first_name__icontains=bit)
|
|
||||||
| Q(last_name__icontains=bit)
|
|
||||||
| Q(username__icontains=bit))
|
|
||||||
queries['members'] = queries['members'].distinct()
|
|
||||||
queries['users'] = queries['users'].distinct()
|
|
||||||
|
|
||||||
# Clearing redundancies
|
|
||||||
usernames = (
|
|
||||||
set(queries['members'].values_list('profile__login_clipper',
|
|
||||||
flat='True'))
|
|
||||||
| set(queries['users'].values_list('profile__login_clipper',
|
|
||||||
flat='True'))
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fetching data from the SPI
|
|
||||||
if hasattr(settings, 'LDAP_SERVER_URL'):
|
|
||||||
# Fetching
|
|
||||||
ldap_query = '(|{:s})'.format(''.join(
|
|
||||||
['(cn=*{bit:s}*)(uid=*{bit:s}*)'.format(bit=bit)
|
|
||||||
for bit in bits]
|
|
||||||
))
|
|
||||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
|
||||||
conn.search(
|
|
||||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
|
||||||
attributes=['uid', 'cn']
|
|
||||||
)
|
|
||||||
queries['clippers'] = conn.entries
|
|
||||||
# Clearing redundancies
|
|
||||||
queries['clippers'] = [
|
|
||||||
Clipper(clipper.uid, clipper.cn)
|
|
||||||
for clipper in queries['clippers']
|
|
||||||
if str(clipper.uid) not in usernames
|
|
||||||
]
|
|
||||||
|
|
||||||
# Resulting data
|
|
||||||
data.update(queries)
|
|
||||||
data['options'] = any(bool(query) for query in queries.values())
|
|
||||||
|
|
||||||
return shortcuts.render(request, "cof/autocomplete_user.html", data)
|
|
|
@ -1,75 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import csv
|
|
||||||
from django.http import HttpResponse, HttpResponseForbidden
|
|
||||||
from django.template.defaultfilters import slugify
|
|
||||||
from django.apps import apps
|
|
||||||
|
|
||||||
|
|
||||||
def export(qs, fields=None):
|
|
||||||
model = qs.model
|
|
||||||
response = HttpResponse(content_type='text/csv')
|
|
||||||
response['Content-Disposition'] = 'attachment; filename=%s.csv' \
|
|
||||||
% slugify(model.__name__)
|
|
||||||
writer = csv.writer(response)
|
|
||||||
# Write headers to CSV file
|
|
||||||
if fields:
|
|
||||||
headers = fields
|
|
||||||
else:
|
|
||||||
headers = []
|
|
||||||
for field in model._meta.fields:
|
|
||||||
headers.append(field.name)
|
|
||||||
writer.writerow(headers)
|
|
||||||
# Write data to CSV file
|
|
||||||
for obj in qs:
|
|
||||||
row = []
|
|
||||||
for field in headers:
|
|
||||||
if field in headers:
|
|
||||||
val = getattr(obj, field)
|
|
||||||
if callable(val):
|
|
||||||
val = val()
|
|
||||||
row.append(val)
|
|
||||||
writer.writerow(row)
|
|
||||||
# Return CSV file to browser as download
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def admin_list_export(request, model_name, app_label, queryset=None,
|
|
||||||
fields=None, list_display=True):
|
|
||||||
"""
|
|
||||||
Put the following line in your urls.py BEFORE your admin include
|
|
||||||
(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
|
|
||||||
'util.csv_view.admin_list_export'),
|
|
||||||
"""
|
|
||||||
if not request.user.is_staff:
|
|
||||||
return HttpResponseForbidden()
|
|
||||||
if not queryset:
|
|
||||||
model = apps.get_model(app_label, model_name)
|
|
||||||
queryset = model.objects.all()
|
|
||||||
queryset = queryset.filter(profile__is_cof=True)
|
|
||||||
if not fields:
|
|
||||||
if list_display and len(queryset.model._meta.admin.list_display) > 1:
|
|
||||||
fields = queryset.model._meta.admin.list_display
|
|
||||||
else:
|
|
||||||
fields = None
|
|
||||||
return export(queryset, fields)
|
|
||||||
"""
|
|
||||||
Create your own change_list.html for your admin view and put something
|
|
||||||
like this in it:
|
|
||||||
{% block object-tools %}
|
|
||||||
<ul class="object-tools">
|
|
||||||
<li><a href="csv/{%if request.GET%}?{{request.GET.urlencode}}
|
|
||||||
{%endif%}" class="addlink">Export to CSV</a></li>
|
|
||||||
{% if has_add_permission %}
|
|
||||||
<li><a href="add/{% if is_popup %}?_popup=1{% endif %}"
|
|
||||||
class="addlink">
|
|
||||||
{% blocktrans with cl.opts.verbose_name|escape as name %}
|
|
||||||
Add {{ name }}{% endblocktrans %}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
"""
|
|
|
@ -1,8 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.contrib.auth.decorators import permission_required
|
|
||||||
|
|
||||||
cof_required = permission_required('cof.member')
|
|
||||||
cof_required_customdenied = permission_required('cof.member',
|
|
||||||
login_url="cof-denied")
|
|
||||||
buro_required = permission_required('cof.buro')
|
|
|
@ -1,199 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"old": false,
|
|
||||||
"details": "Il nous casse les oreilles, qu'est ce qu'on en fait\u00a0?",
|
|
||||||
"survey_open": true,
|
|
||||||
"title": "Sort du barde"
|
|
||||||
},
|
|
||||||
"model": "cof.survey",
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"question": "Sanction s'il chante",
|
|
||||||
"survey": 1,
|
|
||||||
"multi_answers": true
|
|
||||||
},
|
|
||||||
"model": "cof.surveyquestion",
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"question": "Est-ce qu'on le garde\u00a0?",
|
|
||||||
"survey": 1,
|
|
||||||
"multi_answers": false
|
|
||||||
},
|
|
||||||
"model": "cof.surveyquestion",
|
|
||||||
"pk": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"answer": "On l'ernestise",
|
|
||||||
"survey_question": 1
|
|
||||||
},
|
|
||||||
"model": "cof.surveyquestionanswer",
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"answer": "On ligote",
|
|
||||||
"survey_question": 1
|
|
||||||
},
|
|
||||||
"model": "cof.surveyquestionanswer",
|
|
||||||
"pk": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"answer": "On le prive de banquet",
|
|
||||||
"survey_question": 1
|
|
||||||
},
|
|
||||||
"model": "cof.surveyquestionanswer",
|
|
||||||
"pk": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"answer": "Oui",
|
|
||||||
"survey_question": 2
|
|
||||||
},
|
|
||||||
"model": "cof.surveyquestionanswer",
|
|
||||||
"pk": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"answer": "Non",
|
|
||||||
"survey_question": 2
|
|
||||||
},
|
|
||||||
"model": "cof.surveyquestionanswer",
|
|
||||||
"pk": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"old": false,
|
|
||||||
"description": "On va casser du romain.",
|
|
||||||
"end_date": "2016-09-12T00:00:00Z",
|
|
||||||
"title": "Bataille de Gergovie",
|
|
||||||
"image": "",
|
|
||||||
"location": "Gergovie",
|
|
||||||
"registration_open": true,
|
|
||||||
"start_date": "2016-09-09T00:00:00Z"
|
|
||||||
},
|
|
||||||
"model": "cof.event",
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"default": "",
|
|
||||||
"event": 1,
|
|
||||||
"fieldtype": "text",
|
|
||||||
"name": "Commentaires"
|
|
||||||
},
|
|
||||||
"model": "cof.eventcommentfield",
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"multi_choices": true,
|
|
||||||
"event": 1,
|
|
||||||
"name": "Potion magique"
|
|
||||||
},
|
|
||||||
"model": "cof.eventoption",
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"event_option": 1,
|
|
||||||
"value": "Je suis alergique"
|
|
||||||
},
|
|
||||||
"model": "cof.eventoptionchoice",
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"event_option": 1,
|
|
||||||
"value": "J'en veux"
|
|
||||||
},
|
|
||||||
"model": "cof.eventoptionchoice",
|
|
||||||
"pk": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"event_option": 1,
|
|
||||||
"value": "Je suis tomb\u00e9 dans la marmite quand j'\u00e9tais petit"
|
|
||||||
},
|
|
||||||
"model": "cof.eventoptionchoice",
|
|
||||||
"pk": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"name": "Bagarre"
|
|
||||||
},
|
|
||||||
"model": "cof.petitcourssubject",
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"name": "Lancer de menhir"
|
|
||||||
},
|
|
||||||
"model": "cof.petitcourssubject",
|
|
||||||
"pk": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"name": "Pr\u00e9paration de potions"
|
|
||||||
},
|
|
||||||
"model": "cof.petitcourssubject",
|
|
||||||
"pk": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"name": "Chant"
|
|
||||||
},
|
|
||||||
"model": "cof.petitcourssubject",
|
|
||||||
"pk": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"traitee": false,
|
|
||||||
"remarques": "En grande difficult\u00e9",
|
|
||||||
"quand": "weekend (dimanche) / soir apr\u00e8s les cours",
|
|
||||||
"name": "Jules C\u00e9sar",
|
|
||||||
"created": "2016-07-15T11:12:35Z",
|
|
||||||
"niveau": "prepa1styear",
|
|
||||||
"agrege_requis": false,
|
|
||||||
"phone": "",
|
|
||||||
"traitee_par": null,
|
|
||||||
"matieres": [
|
|
||||||
1
|
|
||||||
],
|
|
||||||
"lieu": "Al\u00e9sia",
|
|
||||||
"freq": "3 fois / semaine",
|
|
||||||
"email": "jules.cesar@polytechnique.edu",
|
|
||||||
"processed": null
|
|
||||||
},
|
|
||||||
"model": "cof.petitcoursdemande",
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"traitee": false,
|
|
||||||
"remarques": "",
|
|
||||||
"quand": "Weekends",
|
|
||||||
"name": "Jules C\u00e9sar",
|
|
||||||
"created": "2016-07-15T11:13:26Z",
|
|
||||||
"niveau": "lycee",
|
|
||||||
"agrege_requis": true,
|
|
||||||
"phone": "",
|
|
||||||
"traitee_par": null,
|
|
||||||
"matieres": [
|
|
||||||
3
|
|
||||||
],
|
|
||||||
"lieu": "\u00e0 domicile",
|
|
||||||
"freq": "toutes les semaines",
|
|
||||||
"email": "jules.cesar@polytechnique.edu",
|
|
||||||
"processed": null
|
|
||||||
},
|
|
||||||
"model": "cof.petitcoursdemande",
|
|
||||||
"pk": 2
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,10 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"fields": {
|
|
||||||
"domain": "localhost",
|
|
||||||
"name": "GestioCOF - dev - local"
|
|
||||||
},
|
|
||||||
"model": "sites.site",
|
|
||||||
"pk": 1
|
|
||||||
}
|
|
||||||
]
|
|
297
cof/forms.py
297
cof/forms.py
|
@ -1,297 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
|
|
||||||
from django.forms.formsets import BaseFormSet, formset_factory
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from bda.models import Spectacle
|
|
||||||
|
|
||||||
from .models import CofProfile, EventCommentValue, CalendarSubscription
|
|
||||||
from .widgets import TriStateCheckbox
|
|
||||||
|
|
||||||
|
|
||||||
class EventForm(forms.Form):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
event = kwargs.pop("event")
|
|
||||||
self.event = event
|
|
||||||
current_choices = kwargs.pop("current_choices", None)
|
|
||||||
super(EventForm, self).__init__(*args, **kwargs)
|
|
||||||
choices = {}
|
|
||||||
if current_choices:
|
|
||||||
for choice in current_choices.all():
|
|
||||||
if choice.event_option.id not in choices:
|
|
||||||
choices[choice.event_option.id] = [choice.id]
|
|
||||||
else:
|
|
||||||
choices[choice.event_option.id].append(choice.id)
|
|
||||||
all_choices = choices
|
|
||||||
for option in event.options.all():
|
|
||||||
choices = [(choice.id, choice.value)
|
|
||||||
for choice in option.choices.all()]
|
|
||||||
if option.multi_choices:
|
|
||||||
initial = [] if option.id not in all_choices \
|
|
||||||
else all_choices[option.id]
|
|
||||||
field = forms.MultipleChoiceField(
|
|
||||||
label=option.name,
|
|
||||||
choices=choices,
|
|
||||||
widget=CheckboxSelectMultiple,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
else:
|
|
||||||
initial = None if option.id not in all_choices \
|
|
||||||
else all_choices[option.id][0]
|
|
||||||
field = forms.ChoiceField(label=option.name,
|
|
||||||
choices=choices,
|
|
||||||
widget=RadioSelect,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
field.option_id = option.id
|
|
||||||
self.fields["option_%d" % option.id] = field
|
|
||||||
|
|
||||||
def choices(self):
|
|
||||||
for name, value in self.cleaned_data.items():
|
|
||||||
if name.startswith('option_'):
|
|
||||||
yield (self.fields[name].option_id, value)
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyForm(forms.Form):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
survey = kwargs.pop("survey")
|
|
||||||
current_answers = kwargs.pop("current_answers", None)
|
|
||||||
super(SurveyForm, self).__init__(*args, **kwargs)
|
|
||||||
answers = {}
|
|
||||||
if current_answers:
|
|
||||||
for answer in current_answers.all():
|
|
||||||
if answer.survey_question.id not in answers:
|
|
||||||
answers[answer.survey_question.id] = [answer.id]
|
|
||||||
else:
|
|
||||||
answers[answer.survey_question.id].append(answer.id)
|
|
||||||
for question in survey.questions.all():
|
|
||||||
choices = [(answer.id, answer.answer)
|
|
||||||
for answer in question.answers.all()]
|
|
||||||
if question.multi_answers:
|
|
||||||
initial = [] if question.id not in answers\
|
|
||||||
else answers[question.id]
|
|
||||||
field = forms.MultipleChoiceField(
|
|
||||||
label=question.question,
|
|
||||||
choices=choices,
|
|
||||||
widget=CheckboxSelectMultiple,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
else:
|
|
||||||
initial = None if question.id not in answers\
|
|
||||||
else answers[question.id][0]
|
|
||||||
field = forms.ChoiceField(label=question.question,
|
|
||||||
choices=choices,
|
|
||||||
widget=RadioSelect,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
field.question_id = question.id
|
|
||||||
self.fields["question_%d" % question.id] = field
|
|
||||||
|
|
||||||
def answers(self):
|
|
||||||
for name, value in self.cleaned_data.items():
|
|
||||||
if name.startswith('question_'):
|
|
||||||
yield (self.fields[name].question_id, value)
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyStatusFilterForm(forms.Form):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
survey = kwargs.pop("survey")
|
|
||||||
super(SurveyStatusFilterForm, self).__init__(*args, **kwargs)
|
|
||||||
for question in survey.questions.all():
|
|
||||||
for answer in question.answers.all():
|
|
||||||
name = "question_%d_answer_%d" % (question.id, answer.id)
|
|
||||||
if self.is_bound \
|
|
||||||
and self.data.get(self.add_prefix(name), None):
|
|
||||||
initial = self.data.get(self.add_prefix(name), None)
|
|
||||||
else:
|
|
||||||
initial = "none"
|
|
||||||
field = forms.ChoiceField(
|
|
||||||
label="%s : %s" % (question.question, answer.answer),
|
|
||||||
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
|
||||||
widget=TriStateCheckbox,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
field.question_id = question.id
|
|
||||||
field.answer_id = answer.id
|
|
||||||
self.fields[name] = field
|
|
||||||
|
|
||||||
def filters(self):
|
|
||||||
for name, value in self.cleaned_data.items():
|
|
||||||
if name.startswith('question_'):
|
|
||||||
yield (self.fields[name].question_id,
|
|
||||||
self.fields[name].answer_id, value)
|
|
||||||
|
|
||||||
|
|
||||||
class EventStatusFilterForm(forms.Form):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
event = kwargs.pop("event")
|
|
||||||
super(EventStatusFilterForm, self).__init__(*args, **kwargs)
|
|
||||||
for option in event.options.all():
|
|
||||||
for choice in option.choices.all():
|
|
||||||
name = "option_%d_choice_%d" % (option.id, choice.id)
|
|
||||||
if self.is_bound \
|
|
||||||
and self.data.get(self.add_prefix(name), None):
|
|
||||||
initial = self.data.get(self.add_prefix(name), None)
|
|
||||||
else:
|
|
||||||
initial = "none"
|
|
||||||
field = forms.ChoiceField(
|
|
||||||
label="%s : %s" % (option.name, choice.value),
|
|
||||||
choices=[("yes", "yes"), ("no", "no"), ("none", "none")],
|
|
||||||
widget=TriStateCheckbox,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
field.option_id = option.id
|
|
||||||
field.choice_id = choice.id
|
|
||||||
self.fields[name] = field
|
|
||||||
# has_paid
|
|
||||||
name = "event_has_paid"
|
|
||||||
if self.is_bound and self.data.get(self.add_prefix(name), None):
|
|
||||||
initial = self.data.get(self.add_prefix(name), None)
|
|
||||||
else:
|
|
||||||
initial = "none"
|
|
||||||
field = forms.ChoiceField(label="Événement payé",
|
|
||||||
choices=[("yes", "yes"), ("no", "no"),
|
|
||||||
("none", "none")],
|
|
||||||
widget=TriStateCheckbox,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
self.fields[name] = field
|
|
||||||
|
|
||||||
def filters(self):
|
|
||||||
for name, value in self.cleaned_data.items():
|
|
||||||
if name.startswith('option_'):
|
|
||||||
yield (self.fields[name].option_id,
|
|
||||||
self.fields[name].choice_id, value)
|
|
||||||
elif name == "event_has_paid":
|
|
||||||
yield ("has_paid", None, value)
|
|
||||||
|
|
||||||
|
|
||||||
class COFProfileRegistrationForm(forms.ModelForm):
|
|
||||||
member = forms.BooleanField(
|
|
||||||
label=_("Membre du COF"),
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def save(self, **kwargs):
|
|
||||||
super().save()
|
|
||||||
self.instance.is_cof = True
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = CofProfile
|
|
||||||
fields = [
|
|
||||||
"type_cotiz", "mailing", "mailing_bda", "mailing_bda_revente",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
STATUS_CHOICES = (('no', 'Non'),
|
|
||||||
('wait', 'Oui mais attente paiement'),
|
|
||||||
('paid', 'Oui payé'),)
|
|
||||||
|
|
||||||
|
|
||||||
class AdminEventForm(forms.Form):
|
|
||||||
status = forms.ChoiceField(label="Inscription", initial="no",
|
|
||||||
choices=STATUS_CHOICES, widget=RadioSelect)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.event = kwargs.pop("event")
|
|
||||||
registration = kwargs.pop("current_registration", None)
|
|
||||||
current_choices, paid = \
|
|
||||||
(registration.options.all(), registration.paid) \
|
|
||||||
if registration is not None else ([], None)
|
|
||||||
if paid is True:
|
|
||||||
kwargs["initial"] = {"status": "paid"}
|
|
||||||
elif paid is False:
|
|
||||||
kwargs["initial"] = {"status": "wait"}
|
|
||||||
else:
|
|
||||||
kwargs["initial"] = {"status": "no"}
|
|
||||||
super(AdminEventForm, self).__init__(*args, **kwargs)
|
|
||||||
choices = {}
|
|
||||||
for choice in current_choices:
|
|
||||||
if choice.event_option.id not in choices:
|
|
||||||
choices[choice.event_option.id] = [choice.id]
|
|
||||||
else:
|
|
||||||
choices[choice.event_option.id].append(choice.id)
|
|
||||||
all_choices = choices
|
|
||||||
for option in self.event.options.all():
|
|
||||||
choices = [(choice.id, choice.value)
|
|
||||||
for choice in option.choices.all()]
|
|
||||||
if option.multi_choices:
|
|
||||||
initial = [] if option.id not in all_choices\
|
|
||||||
else all_choices[option.id]
|
|
||||||
field = forms.MultipleChoiceField(
|
|
||||||
label=option.name,
|
|
||||||
choices=choices,
|
|
||||||
widget=CheckboxSelectMultiple,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
else:
|
|
||||||
initial = None if option.id not in all_choices\
|
|
||||||
else all_choices[option.id][0]
|
|
||||||
field = forms.ChoiceField(label=option.name,
|
|
||||||
choices=choices,
|
|
||||||
widget=RadioSelect,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
field.option_id = option.id
|
|
||||||
self.fields["option_%d" % option.id] = field
|
|
||||||
for commentfield in self.event.commentfields.all():
|
|
||||||
initial = commentfield.default
|
|
||||||
if registration is not None:
|
|
||||||
try:
|
|
||||||
initial = registration.comments \
|
|
||||||
.get(commentfield=commentfield).content
|
|
||||||
except EventCommentValue.DoesNotExist:
|
|
||||||
pass
|
|
||||||
widget = forms.Textarea if commentfield.fieldtype == "text" \
|
|
||||||
else forms.TextInput
|
|
||||||
field = forms.CharField(label=commentfield.name,
|
|
||||||
widget=widget,
|
|
||||||
required=False,
|
|
||||||
initial=initial)
|
|
||||||
field.comment_id = commentfield.id
|
|
||||||
self.fields["comment_%d" % commentfield.id] = field
|
|
||||||
|
|
||||||
def choices(self):
|
|
||||||
for name, value in self.cleaned_data.items():
|
|
||||||
if name.startswith('option_'):
|
|
||||||
yield (self.fields[name].option_id, value)
|
|
||||||
|
|
||||||
def comments(self):
|
|
||||||
for name, value in self.cleaned_data.items():
|
|
||||||
if name.startswith('comment_'):
|
|
||||||
yield (self.fields[name].comment_id, value)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseEventRegistrationFormset(BaseFormSet):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.events = kwargs.pop('events')
|
|
||||||
self.current_registrations = kwargs.pop('current_registrations', None)
|
|
||||||
self.extra = len(self.events)
|
|
||||||
super(BaseEventRegistrationFormset, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def _construct_form(self, index, **kwargs):
|
|
||||||
kwargs['event'] = self.events[index]
|
|
||||||
if self.current_registrations is not None:
|
|
||||||
kwargs['current_registration'] = self.current_registrations[index]
|
|
||||||
return super(BaseEventRegistrationFormset, self)._construct_form(
|
|
||||||
index, **kwargs)
|
|
||||||
EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset)
|
|
||||||
|
|
||||||
|
|
||||||
class CalendarForm(forms.ModelForm):
|
|
||||||
subscribe_to_events = forms.BooleanField(
|
|
||||||
initial=True,
|
|
||||||
label="Événements du COF")
|
|
||||||
subscribe_to_my_shows = forms.BooleanField(
|
|
||||||
initial=True,
|
|
||||||
label="Les spectacles pour lesquels j'ai obtenu une place")
|
|
||||||
other_shows = forms.ModelMultipleChoiceField(
|
|
||||||
label="Spectacles supplémentaires",
|
|
||||||
queryset=Spectacle.objects.filter(tirage__active=True),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = CalendarSubscription
|
|
||||||
fields = ['subscribe_to_events', 'subscribe_to_my_shows',
|
|
||||||
'other_shows']
|
|
|
@ -1,41 +0,0 @@
|
||||||
"""
|
|
||||||
Un mixin à utiliser avec BaseCommand pour charger des objets depuis un json
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
|
|
||||||
class MyBaseCommand(BaseCommand):
|
|
||||||
"""
|
|
||||||
Ajoute une méthode ``from_json`` qui charge des objets à partir d'un json.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def from_json(self, filename, data_dir, klass,
|
|
||||||
callback=lambda obj: obj):
|
|
||||||
"""
|
|
||||||
Charge les objets contenus dans le fichier json référencé par
|
|
||||||
``filename`` dans la base de donnée. La fonction callback est appelées
|
|
||||||
sur chaque objet avant enregistrement.
|
|
||||||
"""
|
|
||||||
self.stdout.write("Chargement de {:s}".format(filename))
|
|
||||||
with open(os.path.join(data_dir, filename), 'r') as file:
|
|
||||||
descriptions = json.load(file)
|
|
||||||
objects = []
|
|
||||||
nb_new = 0
|
|
||||||
for description in descriptions:
|
|
||||||
qset = klass.objects.filter(**description)
|
|
||||||
try:
|
|
||||||
objects.append(qset.get())
|
|
||||||
except klass.DoesNotExist:
|
|
||||||
obj = klass(**description)
|
|
||||||
obj = callback(obj)
|
|
||||||
obj.save()
|
|
||||||
objects.append(obj)
|
|
||||||
nb_new += 1
|
|
||||||
self.stdout.write("- {:d} objets créés".format(nb_new))
|
|
||||||
self.stdout.write("- {:d} objets gardés en l'état"
|
|
||||||
.format(len(objects)-nb_new))
|
|
||||||
return objects
|
|
|
@ -1,121 +0,0 @@
|
||||||
"""
|
|
||||||
Charge des données de test dans la BDD
|
|
||||||
- Utilisateurs
|
|
||||||
- Sondage
|
|
||||||
- Événement
|
|
||||||
- Petits cours
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.core.management import call_command
|
|
||||||
|
|
||||||
from cof.management.base import MyBaseCommand
|
|
||||||
from cof.petits_cours_models import (
|
|
||||||
PetitCoursAbility, PetitCoursSubject, LEVELS_CHOICES,
|
|
||||||
PetitCoursAttributionCounter
|
|
||||||
)
|
|
||||||
|
|
||||||
from cof.models import CofProfile
|
|
||||||
|
|
||||||
# Où sont stockés les fichiers json
|
|
||||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
|
||||||
'data')
|
|
||||||
|
|
||||||
|
|
||||||
class Command(MyBaseCommand):
|
|
||||||
help = "Charge des données de test dans la BDD"
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
"""
|
|
||||||
Permet de ne pas créer l'utilisateur "root".
|
|
||||||
"""
|
|
||||||
parser.add_argument(
|
|
||||||
'--no-root',
|
|
||||||
action='store_true',
|
|
||||||
dest='no-root',
|
|
||||||
default=False,
|
|
||||||
help='Ne crée pas l\'utilisateur "root"'
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
# ---
|
|
||||||
# Utilisateurs
|
|
||||||
# ---
|
|
||||||
|
|
||||||
# Gaulois
|
|
||||||
gaulois = self.from_json('gaulois.json', DATA_DIR, User)
|
|
||||||
for user in gaulois:
|
|
||||||
cofprofile = CofProfile.objects.create(
|
|
||||||
profile=user.profile,
|
|
||||||
)
|
|
||||||
cofprofile.is_cof = True
|
|
||||||
|
|
||||||
# Romains
|
|
||||||
self.from_json('romains.json', DATA_DIR, User)
|
|
||||||
|
|
||||||
# Root
|
|
||||||
no_root = options.get('no-root', False)
|
|
||||||
if not no_root:
|
|
||||||
self.stdout.write("Création de l'utilisateur root")
|
|
||||||
root, _ = User.objects.get_or_create(
|
|
||||||
username='root',
|
|
||||||
first_name='super',
|
|
||||||
last_name='user',
|
|
||||||
email='root@localhost')
|
|
||||||
root.set_password('root')
|
|
||||||
root.is_staff = True
|
|
||||||
root.is_superuser = True
|
|
||||||
root.save()
|
|
||||||
CofProfile.objects.create(
|
|
||||||
profile=root.profile,
|
|
||||||
is_cof=True,
|
|
||||||
is_buro=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# Petits cours
|
|
||||||
# ---
|
|
||||||
|
|
||||||
self.stdout.write("Inscriptions au système des petits cours")
|
|
||||||
levels = [id for (id, verbose) in LEVELS_CHOICES]
|
|
||||||
subjects = list(PetitCoursSubject.objects.all())
|
|
||||||
nb_of_teachers = 0
|
|
||||||
for user in gaulois:
|
|
||||||
if random.randint(0, 1):
|
|
||||||
nb_of_teachers += 1
|
|
||||||
# L'utilisateur reçoit les demandes de petits cours
|
|
||||||
user.profile.petits_cours_accept = True
|
|
||||||
user.save()
|
|
||||||
# L'utilisateur est compétent dans une matière
|
|
||||||
subject = random.choice(subjects)
|
|
||||||
if not PetitCoursAbility.objects.filter(
|
|
||||||
user=user,
|
|
||||||
matiere=subject).exists():
|
|
||||||
PetitCoursAbility.objects.create(
|
|
||||||
user=user,
|
|
||||||
matiere=subject,
|
|
||||||
niveau=random.choice(levels),
|
|
||||||
agrege=bool(random.randint(0, 1))
|
|
||||||
)
|
|
||||||
# On initialise son compteur d'attributions
|
|
||||||
PetitCoursAttributionCounter.objects.get_or_create(
|
|
||||||
user=user,
|
|
||||||
matiere=subject
|
|
||||||
)
|
|
||||||
self.stdout.write("- {:d} inscriptions".format(nb_of_teachers))
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# Le BdA
|
|
||||||
# ---
|
|
||||||
|
|
||||||
call_command('loadbdadevdata')
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# La K-Fêt
|
|
||||||
# ---
|
|
||||||
|
|
||||||
call_command('loadkfetdevdata')
|
|
|
@ -1,368 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"username": "Abraracourcix",
|
|
||||||
"email": "Abraracourcix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Abraracourcix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Acidenitrix",
|
|
||||||
"email": "Acidenitrix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Acidenitrix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Agecanonix",
|
|
||||||
"email": "Agecanonix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Agecanonix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alambix",
|
|
||||||
"email": "Alambix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Alambix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amerix",
|
|
||||||
"email": "Amerix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Amerix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amnesix",
|
|
||||||
"email": "Amnesix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Amnesix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Aniline",
|
|
||||||
"email": "Aniline.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Aniline"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Aplusbegalix",
|
|
||||||
"email": "Aplusbegalix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Aplusbegalix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Archeopterix",
|
|
||||||
"email": "Archeopterix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Archeopterix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Assurancetourix",
|
|
||||||
"email": "Assurancetourix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Assurancetourix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Asterix",
|
|
||||||
"email": "Asterix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Asterix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Astronomix",
|
|
||||||
"email": "Astronomix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Astronomix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Avoranfix",
|
|
||||||
"email": "Avoranfix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Avoranfix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Barometrix",
|
|
||||||
"email": "Barometrix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Barometrix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Beaufix",
|
|
||||||
"email": "Beaufix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Beaufix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Berlix",
|
|
||||||
"email": "Berlix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Berlix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Bonemine",
|
|
||||||
"email": "Bonemine.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Bonemine"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Boufiltre",
|
|
||||||
"email": "Boufiltre.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Boufiltre"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Catedralgotix",
|
|
||||||
"email": "Catedralgotix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Catedralgotix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "CesarLabeldecadix",
|
|
||||||
"email": "CesarLabeldecadix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "CesarLabeldecadix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Cetautomatix",
|
|
||||||
"email": "Cetautomatix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Cetautomatix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Cetyounix",
|
|
||||||
"email": "Cetyounix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Cetyounix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Changeledix",
|
|
||||||
"email": "Changeledix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Changeledix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Chanteclairix",
|
|
||||||
"email": "Chanteclairix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Chanteclairix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Cicatrix",
|
|
||||||
"email": "Cicatrix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Cicatrix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Comix",
|
|
||||||
"email": "Comix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Comix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Diagnostix",
|
|
||||||
"email": "Diagnostix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Diagnostix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Doublepolemix",
|
|
||||||
"email": "Doublepolemix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Doublepolemix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Eponine",
|
|
||||||
"email": "Eponine.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Eponine"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Falbala",
|
|
||||||
"email": "Falbala.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Falbala"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Fanzine",
|
|
||||||
"email": "Fanzine.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Fanzine"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Gelatine",
|
|
||||||
"email": "Gelatine.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Gelatine"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Goudurix",
|
|
||||||
"email": "Goudurix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Goudurix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Homeopatix",
|
|
||||||
"email": "Homeopatix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Homeopatix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Idefix",
|
|
||||||
"email": "Idefix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Idefix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Ielosubmarine",
|
|
||||||
"email": "Ielosubmarine.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Ielosubmarine"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Keskonrix",
|
|
||||||
"email": "Keskonrix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Keskonrix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Lentix",
|
|
||||||
"email": "Lentix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Lentix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Maestria",
|
|
||||||
"email": "Maestria.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Maestria"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "MaitrePanix",
|
|
||||||
"email": "MaitrePanix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "MaitrePanix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "MmeAgecanonix",
|
|
||||||
"email": "MmeAgecanonix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "MmeAgecanonix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Moralelastix",
|
|
||||||
"email": "Moralelastix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Moralelastix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Obelix",
|
|
||||||
"email": "Obelix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Obelix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Obelodalix",
|
|
||||||
"email": "Obelodalix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Obelodalix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Odalix",
|
|
||||||
"email": "Odalix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Odalix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Ordralfabetix",
|
|
||||||
"email": "Ordralfabetix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Ordralfabetix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Orthopedix",
|
|
||||||
"email": "Orthopedix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Orthopedix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Panoramix",
|
|
||||||
"email": "Panoramix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Panoramix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Plaintcontrix",
|
|
||||||
"email": "Plaintcontrix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Plaintcontrix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Praline",
|
|
||||||
"email": "Praline.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Praline"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Prefix",
|
|
||||||
"email": "Prefix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Prefix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Prolix",
|
|
||||||
"email": "Prolix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Prolix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Pronostix",
|
|
||||||
"email": "Pronostix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Pronostix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Quatredeusix",
|
|
||||||
"email": "Quatredeusix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Quatredeusix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Saingesix",
|
|
||||||
"email": "Saingesix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Saingesix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Segregationnix",
|
|
||||||
"email": "Segregationnix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Segregationnix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Septantesix",
|
|
||||||
"email": "Septantesix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Septantesix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Tournedix",
|
|
||||||
"email": "Tournedix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Tournedix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Tragicomix",
|
|
||||||
"email": "Tragicomix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Tragicomix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Coriza",
|
|
||||||
"email": "Coriza.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Coriza"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Zerozerosix",
|
|
||||||
"email": "Zerozerosix.gaulois@ens.fr",
|
|
||||||
"last_name": "Gaulois",
|
|
||||||
"first_name": "Zerozerosix"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,614 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"username": "Abel",
|
|
||||||
"email": "Abel.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Abel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Abelardus",
|
|
||||||
"email": "Abelardus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Abelardus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Abrahamus",
|
|
||||||
"email": "Abrahamus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Abrahamus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Acacius",
|
|
||||||
"email": "Acacius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Acacius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Accius",
|
|
||||||
"email": "Accius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Accius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Achaicus",
|
|
||||||
"email": "Achaicus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Achaicus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Achill",
|
|
||||||
"email": "Achill.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Achill"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Achilles",
|
|
||||||
"email": "Achilles.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Achilles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Achilleus",
|
|
||||||
"email": "Achilleus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Achilleus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Acrisius",
|
|
||||||
"email": "Acrisius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Acrisius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Actaeon",
|
|
||||||
"email": "Actaeon.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Actaeon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Acteon",
|
|
||||||
"email": "Acteon.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Acteon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Adalricus",
|
|
||||||
"email": "Adalricus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Adalricus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Adelfonsus",
|
|
||||||
"email": "Adelfonsus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Adelfonsus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Adelphus",
|
|
||||||
"email": "Adelphus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Adelphus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Adeodatus",
|
|
||||||
"email": "Adeodatus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Adeodatus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Adolfus",
|
|
||||||
"email": "Adolfus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Adolfus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Adolphus",
|
|
||||||
"email": "Adolphus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Adolphus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Adrastus",
|
|
||||||
"email": "Adrastus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Adrastus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Adrianus",
|
|
||||||
"email": "Adrianus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Adrianus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6gidius",
|
|
||||||
"email": "\u00c6gidius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6gidius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6lia",
|
|
||||||
"email": "\u00c6lia.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6lia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6lianus",
|
|
||||||
"email": "\u00c6lianus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6lianus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6milianus",
|
|
||||||
"email": "\u00c6milianus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6milianus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6milius",
|
|
||||||
"email": "\u00c6milius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6milius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Aeneas",
|
|
||||||
"email": "Aeneas.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Aeneas"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6olus",
|
|
||||||
"email": "\u00c6olus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6olus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6schylus",
|
|
||||||
"email": "\u00c6schylus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6schylus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6son",
|
|
||||||
"email": "\u00c6son.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6son"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6sop",
|
|
||||||
"email": "\u00c6sop.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6sop"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6ther",
|
|
||||||
"email": "\u00c6ther.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6ther"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "\u00c6tius",
|
|
||||||
"email": "\u00c6tius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "\u00c6tius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Agapetus",
|
|
||||||
"email": "Agapetus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Agapetus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Agapitus",
|
|
||||||
"email": "Agapitus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Agapitus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Agapius",
|
|
||||||
"email": "Agapius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Agapius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Agathangelus",
|
|
||||||
"email": "Agathangelus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Agathangelus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Aigidius",
|
|
||||||
"email": "Aigidius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Aigidius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Aiolus",
|
|
||||||
"email": "Aiolus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Aiolus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Ajax",
|
|
||||||
"email": "Ajax.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Ajax"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alair",
|
|
||||||
"email": "Alair.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alair"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alaricus",
|
|
||||||
"email": "Alaricus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alaricus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Albanus",
|
|
||||||
"email": "Albanus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Albanus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alberic",
|
|
||||||
"email": "Alberic.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alberic"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Albericus",
|
|
||||||
"email": "Albericus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Albericus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Albertus",
|
|
||||||
"email": "Albertus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Albertus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Albinus",
|
|
||||||
"email": "Albinus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Albinus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Albus",
|
|
||||||
"email": "Albus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Albus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alcaeus",
|
|
||||||
"email": "Alcaeus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alcaeus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alcander",
|
|
||||||
"email": "Alcander.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alcander"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alcimus",
|
|
||||||
"email": "Alcimus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alcimus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alcinder",
|
|
||||||
"email": "Alcinder.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alcinder"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alerio",
|
|
||||||
"email": "Alerio.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alerio"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alexandrus",
|
|
||||||
"email": "Alexandrus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alexandrus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alexis",
|
|
||||||
"email": "Alexis.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alexis"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alexius",
|
|
||||||
"email": "Alexius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alexius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alexus",
|
|
||||||
"email": "Alexus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alexus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alfonsus",
|
|
||||||
"email": "Alfonsus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alfonsus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alfredus",
|
|
||||||
"email": "Alfredus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alfredus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Almericus",
|
|
||||||
"email": "Almericus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Almericus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Aloisius",
|
|
||||||
"email": "Aloisius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Aloisius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Aloysius",
|
|
||||||
"email": "Aloysius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Aloysius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alphaeus",
|
|
||||||
"email": "Alphaeus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alphaeus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alpheaus",
|
|
||||||
"email": "Alpheaus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alpheaus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alpheus",
|
|
||||||
"email": "Alpheus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alpheus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alphoeus",
|
|
||||||
"email": "Alphoeus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alphoeus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alphonsus",
|
|
||||||
"email": "Alphonsus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alphonsus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alphonzus",
|
|
||||||
"email": "Alphonzus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alphonzus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alvinius",
|
|
||||||
"email": "Alvinius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alvinius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Alvredus",
|
|
||||||
"email": "Alvredus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Alvredus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amadeus",
|
|
||||||
"email": "Amadeus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amadeus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amaliricus",
|
|
||||||
"email": "Amaliricus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amaliricus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amandus",
|
|
||||||
"email": "Amandus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amandus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amantius",
|
|
||||||
"email": "Amantius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amantius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amarandus",
|
|
||||||
"email": "Amarandus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amarandus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amaranthus",
|
|
||||||
"email": "Amaranthus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amaranthus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amatus",
|
|
||||||
"email": "Amatus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amatus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Ambrosianus",
|
|
||||||
"email": "Ambrosianus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Ambrosianus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Ambrosius",
|
|
||||||
"email": "Ambrosius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Ambrosius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amedeus",
|
|
||||||
"email": "Amedeus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amedeus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Americus",
|
|
||||||
"email": "Americus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Americus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amlethus",
|
|
||||||
"email": "Amlethus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amlethus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amletus",
|
|
||||||
"email": "Amletus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amletus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amor",
|
|
||||||
"email": "Amor.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Ampelius",
|
|
||||||
"email": "Ampelius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Ampelius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Amphion",
|
|
||||||
"email": "Amphion.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Amphion"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Anacletus",
|
|
||||||
"email": "Anacletus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Anacletus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Anastasius",
|
|
||||||
"email": "Anastasius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Anastasius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Anastatius",
|
|
||||||
"email": "Anastatius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Anastatius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Anastius",
|
|
||||||
"email": "Anastius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Anastius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Anatolius",
|
|
||||||
"email": "Anatolius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Anatolius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Androcles",
|
|
||||||
"email": "Androcles.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Androcles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Andronicus",
|
|
||||||
"email": "Andronicus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Andronicus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Anencletus",
|
|
||||||
"email": "Anencletus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Anencletus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Angelicus",
|
|
||||||
"email": "Angelicus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Angelicus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Angelus",
|
|
||||||
"email": "Angelus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Angelus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Anicetus",
|
|
||||||
"email": "Anicetus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Anicetus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Antigonus",
|
|
||||||
"email": "Antigonus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Antigonus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Antipater",
|
|
||||||
"email": "Antipater.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Antipater"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Antoninus",
|
|
||||||
"email": "Antoninus.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Antoninus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Antonius",
|
|
||||||
"email": "Antonius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Antonius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Aphrodisius",
|
|
||||||
"email": "Aphrodisius.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Aphrodisius"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Apollinaris",
|
|
||||||
"email": "Apollinaris.Romain@ens.fr",
|
|
||||||
"last_name": "Romain",
|
|
||||||
"first_name": "Apollinaris"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,387 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Clipper',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('username', models.CharField(max_length=20, verbose_name=b'Identifiant')),
|
|
||||||
('fullname', models.CharField(max_length=200, verbose_name=b'Nom complet')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Club',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('name', models.CharField(max_length=200, verbose_name=b'Nom')),
|
|
||||||
('description', models.TextField(verbose_name=b'Description')),
|
|
||||||
('membres', models.ManyToManyField(related_name='clubs', to=settings.AUTH_USER_MODEL)),
|
|
||||||
('respos', models.ManyToManyField(related_name='clubs_geres', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CofProfile',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('login_clipper', models.CharField(max_length=8, verbose_name=b'Login clipper', blank=True)),
|
|
||||||
('is_cof', models.BooleanField(default=False, verbose_name=b'Membre du COF')),
|
|
||||||
('num', models.IntegerField(default=0, verbose_name=b"Num\xc3\xa9ro d'adh\xc3\xa9rent", blank=True)),
|
|
||||||
('phone', models.CharField(max_length=20, verbose_name=b'T\xc3\xa9l\xc3\xa9phone', blank=True)),
|
|
||||||
('occupation', models.CharField(default=b'1A', max_length=9, verbose_name='Occupation', choices=[(b'exterieur', 'Ext\xe9rieur'), (b'1A', '1A'), (b'2A', '2A'), (b'3A', '3A'), (b'4A', '4A'), (b'archicube', 'Archicube'), (b'doctorant', 'Doctorant'), (b'CST', 'CST')])),
|
|
||||||
('departement', models.CharField(max_length=50, verbose_name='D\xe9partement', blank=True)),
|
|
||||||
('type_cotiz', models.CharField(default=b'normalien', max_length=9, verbose_name='Type de cotisation', choices=[(b'etudiant', 'Normalien \xe9tudiant'), (b'normalien', 'Normalien \xe9l\xe8ve'), (b'exterieur', 'Ext\xe9rieur')])),
|
|
||||||
('mailing_cof', models.BooleanField(default=False, verbose_name=b'Recevoir les mails COF')),
|
|
||||||
('mailing_bda', models.BooleanField(default=False, verbose_name=b'Recevoir les mails BdA')),
|
|
||||||
('mailing_bda_revente', models.BooleanField(default=False, verbose_name=b'Recevoir les mails de revente de places BdA')),
|
|
||||||
('comments', models.TextField(verbose_name=b'Commentaires visibles uniquement par le Buro', blank=True)),
|
|
||||||
('is_buro', models.BooleanField(default=False, verbose_name=b'Membre du Bur\xc3\xb4')),
|
|
||||||
('petits_cours_accept', models.BooleanField(default=False, verbose_name=b'Recevoir des petits cours')),
|
|
||||||
('petits_cours_remarques', models.TextField(default=b'', verbose_name='Remarques et pr\xe9cisions pour les petits cours', blank=True)),
|
|
||||||
('user', models.OneToOneField(
|
|
||||||
related_name='profile',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Profil COF',
|
|
||||||
'verbose_name_plural': 'Profils COF',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CustomMail',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('shortname', models.SlugField()),
|
|
||||||
('title', models.CharField(max_length=200, verbose_name=b'Titre')),
|
|
||||||
('content', models.TextField(verbose_name=b'Contenu')),
|
|
||||||
('comments', models.TextField(verbose_name=b'Informations contextuelles sur le mail', blank=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Mails personnalisables',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Event',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('title', models.CharField(max_length=200, verbose_name=b'Titre')),
|
|
||||||
('location', models.CharField(max_length=200, verbose_name=b'Lieu')),
|
|
||||||
('start_date', models.DateField(null=True, verbose_name=b'Date de d\xc3\xa9but', blank=True)),
|
|
||||||
('end_date', models.DateField(null=True, verbose_name=b'Date de fin', blank=True)),
|
|
||||||
('description', models.TextField(verbose_name=b'Description', blank=True)),
|
|
||||||
('registration_open', models.BooleanField(default=True, verbose_name=b'Inscriptions ouvertes')),
|
|
||||||
('old', models.BooleanField(default=False, verbose_name=b'Archiver (\xc3\xa9v\xc3\xa9nement fini)')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '\xc9v\xe9nement',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EventCommentField',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('name', models.CharField(max_length=200, verbose_name=b'Champ')),
|
|
||||||
('fieldtype', models.CharField(default=b'text', max_length=10, verbose_name=b'Type', choices=[(b'text', 'Texte long'), (b'char', 'Texte court')])),
|
|
||||||
('default', models.TextField(verbose_name=b'Valeur par d\xc3\xa9faut', blank=True)),
|
|
||||||
('event', models.ForeignKey(
|
|
||||||
related_name='commentfields',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='cof.Event')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Champ',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EventCommentValue',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('content', models.TextField(null=True, verbose_name=b'Contenu', blank=True)),
|
|
||||||
('commentfield', models.ForeignKey(
|
|
||||||
related_name='values',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='cof.EventCommentField')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EventOption',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('name', models.CharField(max_length=200, verbose_name=b'Option')),
|
|
||||||
('multi_choices', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
|
|
||||||
('event', models.ForeignKey(
|
|
||||||
related_name='options',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='cof.Event')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Option',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EventOptionChoice',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('value', models.CharField(max_length=200, verbose_name=b'Valeur')),
|
|
||||||
('event_option', models.ForeignKey(
|
|
||||||
related_name='choices',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='cof.EventOption')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Choix',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='EventRegistration',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('paid', models.BooleanField(default=False, verbose_name=b'A pay\xc3\xa9')),
|
|
||||||
('event', models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='cof.Event')),
|
|
||||||
('filledcomments', models.ManyToManyField(to='cof.EventCommentField', through='cof.EventCommentValue')),
|
|
||||||
('options', models.ManyToManyField(to='cof.EventOptionChoice')),
|
|
||||||
('user', models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Inscription',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PetitCoursAbility',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('niveau', models.CharField(max_length=12, verbose_name='Niveau', choices=[(b'college', 'Coll\xe8ge'), (b'lycee', 'Lyc\xe9e'), (b'prepa1styear', 'Pr\xe9pa 1\xe8re ann\xe9e / L1'), (b'prepa2ndyear', 'Pr\xe9pa 2\xe8me ann\xe9e / L2'), (b'licence3', 'Licence 3'), (b'other', 'Autre (pr\xe9ciser dans les commentaires)')])),
|
|
||||||
('agrege', models.BooleanField(default=False, verbose_name='Agr\xe9g\xe9')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Comp\xe9tence petits cours',
|
|
||||||
'verbose_name_plural': 'Comp\xe9tences des petits cours',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PetitCoursAttribution',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('date', models.DateTimeField(auto_now_add=True, verbose_name="Date d'attribution")),
|
|
||||||
('rank', models.IntegerField(verbose_name=b"Rang dans l'email")),
|
|
||||||
('selected', models.BooleanField(default=False, verbose_name='S\xe9lectionn\xe9 par le demandeur')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Attribution de petits cours',
|
|
||||||
'verbose_name_plural': 'Attributions de petits cours',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PetitCoursAttributionCounter',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('count', models.IntegerField(default=0, verbose_name=b"Nombre d'envois")),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': "Compteur d'attribution de petits cours",
|
|
||||||
'verbose_name_plural': "Compteurs d'attributions de petits cours",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PetitCoursDemande',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('name', models.CharField(max_length=200, verbose_name='Nom/pr\xe9nom')),
|
|
||||||
('email', models.CharField(max_length=300, verbose_name='Adresse email')),
|
|
||||||
('phone', models.CharField(max_length=20, verbose_name='T\xe9l\xe9phone (facultatif)', blank=True)),
|
|
||||||
('quand', models.CharField(help_text='Indiquez ici la p\xe9riode d\xe9sir\xe9e pour les petits cours (vacances scolaires, semaine, week-end).', max_length=300, verbose_name='Quand ?', blank=True)),
|
|
||||||
('freq', models.CharField(help_text='Indiquez ici la fr\xe9quence envisag\xe9e (hebdomadaire, 2 fois par semaine, ...)', max_length=300, verbose_name='Fr\xe9quence', blank=True)),
|
|
||||||
('lieu', models.CharField(help_text='Si vous avez avez une pr\xe9f\xe9rence sur le lieu.', max_length=300, verbose_name='Lieu (si pr\xe9f\xe9rence)', blank=True)),
|
|
||||||
('agrege_requis', models.BooleanField(default=False, verbose_name='Agr\xe9g\xe9 requis')),
|
|
||||||
('niveau', models.CharField(default=b'', max_length=12, verbose_name='Niveau', choices=[(b'college', 'Coll\xe8ge'), (b'lycee', 'Lyc\xe9e'), (b'prepa1styear', 'Pr\xe9pa 1\xe8re ann\xe9e / L1'), (b'prepa2ndyear', 'Pr\xe9pa 2\xe8me ann\xe9e / L2'), (b'licence3', 'Licence 3'), (b'other', 'Autre (pr\xe9ciser dans les commentaires)')])),
|
|
||||||
('remarques', models.TextField(verbose_name='Remarques et pr\xe9cisions', blank=True)),
|
|
||||||
('traitee', models.BooleanField(default=False, verbose_name='Trait\xe9e')),
|
|
||||||
('processed', models.DateTimeField(verbose_name='Date de traitement', blank=True)),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True, verbose_name='Date de cr\xe9ation')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Demande de petits cours',
|
|
||||||
'verbose_name_plural': 'Demandes de petits cours',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PetitCoursSubject',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('name', models.CharField(max_length=30, verbose_name='Mati\xe8re')),
|
|
||||||
('users', models.ManyToManyField(related_name='petits_cours_matieres', through='cof.PetitCoursAbility', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Mati\xe8re de petits cours',
|
|
||||||
'verbose_name_plural': 'Mati\xe8res des petits cours',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Survey',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('title', models.CharField(max_length=200, verbose_name=b'Titre')),
|
|
||||||
('details', models.TextField(verbose_name=b'D\xc3\xa9tails', blank=True)),
|
|
||||||
('survey_open', models.BooleanField(default=True, verbose_name=b'Sondage ouvert')),
|
|
||||||
('old', models.BooleanField(default=False, verbose_name=b'Archiver (sondage fini)')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Sondage',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SurveyAnswer',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'R\xe9ponses',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SurveyQuestion',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('question', models.CharField(max_length=200, verbose_name=b'Question')),
|
|
||||||
('multi_answers', models.BooleanField(default=False, verbose_name=b'Choix multiples')),
|
|
||||||
('survey', models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='questions',
|
|
||||||
to='cof.Survey')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Question',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SurveyQuestionAnswer',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('answer', models.CharField(max_length=200, verbose_name=b'R\xc3\xa9ponse')),
|
|
||||||
('survey_question', models.ForeignKey(
|
|
||||||
related_name='answers',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='cof.SurveyQuestion')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'R\xe9ponse',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='surveyanswer',
|
|
||||||
name='answers',
|
|
||||||
field=models.ManyToManyField(related_name='selected_by', to='cof.SurveyQuestionAnswer'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='surveyanswer',
|
|
||||||
name='survey',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='cof.Survey'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='surveyanswer',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursdemande',
|
|
||||||
name='matieres',
|
|
||||||
field=models.ManyToManyField(related_name='demandes', verbose_name='Mati\xe8res', to='cof.PetitCoursSubject'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursdemande',
|
|
||||||
name='traitee_par',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
blank=True,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattributioncounter',
|
|
||||||
name='matiere',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
verbose_name='Matiere',
|
|
||||||
to='cof.PetitCoursSubject'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattributioncounter',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattribution',
|
|
||||||
name='demande',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name='Demande',
|
|
||||||
to='cof.PetitCoursDemande'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattribution',
|
|
||||||
name='matiere',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
verbose_name='Mati\xe8re',
|
|
||||||
to='cof.PetitCoursSubject'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattribution',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursability',
|
|
||||||
name='matiere',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name='Mati\xe8re',
|
|
||||||
to='cof.PetitCoursSubject'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursability',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='eventcommentvalue',
|
|
||||||
name='registration',
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='comments',
|
|
||||||
to='cof.EventRegistration'),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='surveyanswer',
|
|
||||||
unique_together=set([('user', 'survey')]),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='eventregistration',
|
|
||||||
unique_together=set([('user', 'event')]),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='petitcoursdemande',
|
|
||||||
name='processed',
|
|
||||||
field=models.DateTimeField(null=True, verbose_name='Date de traitement', blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0002_enable_unprocessed_demandes'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='event',
|
|
||||||
name='image',
|
|
||||||
field=models.ImageField(upload_to=b'imgs/events/', null=True, verbose_name=b'Image', blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,34 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def create_mail(apps, schema_editor):
|
|
||||||
CustomMail = apps.get_model("cof", "CustomMail")
|
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
if CustomMail.objects.filter(shortname="bienvenue").count() == 0:
|
|
||||||
CustomMail.objects.using(db_alias).bulk_create([
|
|
||||||
CustomMail(
|
|
||||||
shortname="bienvenue",
|
|
||||||
title="Bienvenue au COF",
|
|
||||||
content="Mail de bienvenue au COF, envoyé automatiquement à " \
|
|
||||||
+ "l'inscription.\n\n" \
|
|
||||||
+ "Les balises {{ ... }} sont interprétées comme expliqué " \
|
|
||||||
+ "ci-dessous à l'envoi.",
|
|
||||||
comments="{{ nom }} \t fullname de la personne.\n"\
|
|
||||||
+ "{{ prenom }} \t prénom de la personne.")
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0003_event_image'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
# Pas besoin de supprimer le mail lors de la migration dans l'autre
|
|
||||||
# sens.
|
|
||||||
migrations.RunPython(create_mail, migrations.RunPython.noop),
|
|
||||||
]
|
|
|
@ -1,67 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0004_registration_mail'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='custommail',
|
|
||||||
options={'verbose_name': 'Mail personnalisable', 'verbose_name_plural': 'Mails personnalisables'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='eventoptionchoice',
|
|
||||||
options={'verbose_name': 'Choix', 'verbose_name_plural': 'Choix'},
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='is_buro',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Membre du Bur\xf4'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='num',
|
|
||||||
field=models.IntegerField(default=0, verbose_name="Num\xe9ro d'adh\xe9rent", blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(max_length=20, verbose_name='T\xe9l\xe9phone', blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='old',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Archiver (\xe9v\xe9nement fini)'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='start_date',
|
|
||||||
field=models.DateField(null=True, verbose_name='Date de d\xe9but', blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventcommentfield',
|
|
||||||
name='default',
|
|
||||||
field=models.TextField(verbose_name='Valeur par d\xe9faut', blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventregistration',
|
|
||||||
name='paid',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='A pay\xe9'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='survey',
|
|
||||||
name='details',
|
|
||||||
field=models.TextField(verbose_name='D\xe9tails', blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='surveyquestionanswer',
|
|
||||||
name='answer',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='R\xe9ponse'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,52 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0004_mails-rappel'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
('cof', '0005_encoding'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CalendarSubscription',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
|
||||||
auto_created=True, primary_key=True)),
|
|
||||||
('token', models.UUIDField()),
|
|
||||||
('subscribe_to_events', models.BooleanField(default=True)),
|
|
||||||
('subscribe_to_my_shows', models.BooleanField(default=True)),
|
|
||||||
('other_shows', models.ManyToManyField(to='bda.Spectacle')),
|
|
||||||
('user', models.OneToOneField(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='custommail',
|
|
||||||
options={'verbose_name': 'Mail personnalisable',
|
|
||||||
'verbose_name_plural': 'Mails personnalisables'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='eventoptionchoice',
|
|
||||||
options={'verbose_name': 'Choix', 'verbose_name_plural': 'Choix'},
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='end_date',
|
|
||||||
field=models.DateTimeField(null=True, verbose_name=b'Date de fin',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='start_date',
|
|
||||||
field=models.DateTimeField(
|
|
||||||
null=True, verbose_name=b'Date de d\xc3\xa9but', blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,47 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0006_add_calendar'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='club',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(unique=True, max_length=200,
|
|
||||||
verbose_name='Nom')
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='club',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(verbose_name='Description', blank=True)
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='club',
|
|
||||||
name='membres',
|
|
||||||
field=models.ManyToManyField(related_name='clubs',
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='club',
|
|
||||||
name='respos',
|
|
||||||
field=models.ManyToManyField(related_name='clubs_geres',
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='start_date',
|
|
||||||
field=models.DateTimeField(null=True,
|
|
||||||
verbose_name='Date de d\xe9but',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,253 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
def forwards(apps, schema_editor):
|
|
||||||
Profile = apps.get_model("cof", "CofProfile")
|
|
||||||
Profile.objects.update(comments="")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0007_alter_club'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='clipper',
|
|
||||||
name='fullname',
|
|
||||||
field=models.CharField(verbose_name='Nom complet', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='clipper',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(verbose_name='Identifiant', max_length=20),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='comments',
|
|
||||||
field=models.TextField(
|
|
||||||
verbose_name="Commentaires visibles par l'utilisateur",
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='is_cof',
|
|
||||||
field=models.BooleanField(verbose_name='Membre du COF',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='login_clipper',
|
|
||||||
field=models.CharField(verbose_name='Login clipper', max_length=8,
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='mailing_bda',
|
|
||||||
field=models.BooleanField(verbose_name='Recevoir les mails BdA',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='mailing_bda_revente',
|
|
||||||
field=models.BooleanField(
|
|
||||||
verbose_name='Recevoir les mails de revente de places BdA',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='mailing_cof',
|
|
||||||
field=models.BooleanField(verbose_name='Recevoir les mails COF',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='occupation',
|
|
||||||
field=models.CharField(verbose_name='Occupation',
|
|
||||||
choices=[('exterieur', 'Extérieur'),
|
|
||||||
('1A', '1A'),
|
|
||||||
('2A', '2A'),
|
|
||||||
('3A', '3A'),
|
|
||||||
('4A', '4A'),
|
|
||||||
('archicube', 'Archicube'),
|
|
||||||
('doctorant', 'Doctorant'),
|
|
||||||
('CST', 'CST')],
|
|
||||||
max_length=9, default='1A'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='petits_cours_accept',
|
|
||||||
field=models.BooleanField(verbose_name='Recevoir des petits cours',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='petits_cours_remarques',
|
|
||||||
field=models.TextField(
|
|
||||||
blank=True,
|
|
||||||
verbose_name='Remarques et précisions pour les petits cours',
|
|
||||||
default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='type_cotiz',
|
|
||||||
field=models.CharField(
|
|
||||||
verbose_name='Type de cotisation',
|
|
||||||
choices=[('etudiant', 'Normalien étudiant'),
|
|
||||||
('normalien', 'Normalien élève'),
|
|
||||||
('exterieur', 'Extérieur')],
|
|
||||||
max_length=9, default='normalien'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='custommail',
|
|
||||||
name='comments',
|
|
||||||
field=models.TextField(
|
|
||||||
verbose_name='Informations contextuelles sur le mail',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='custommail',
|
|
||||||
name='content',
|
|
||||||
field=models.TextField(verbose_name='Contenu'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='custommail',
|
|
||||||
name='title',
|
|
||||||
field=models.CharField(verbose_name='Titre', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(verbose_name='Description', blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='end_date',
|
|
||||||
field=models.DateTimeField(null=True, verbose_name='Date de fin',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='image',
|
|
||||||
field=models.ImageField(upload_to='imgs/events/', null=True,
|
|
||||||
verbose_name='Image', blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='location',
|
|
||||||
field=models.CharField(verbose_name='Lieu', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='registration_open',
|
|
||||||
field=models.BooleanField(verbose_name='Inscriptions ouvertes',
|
|
||||||
default=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='event',
|
|
||||||
name='title',
|
|
||||||
field=models.CharField(verbose_name='Titre', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventcommentfield',
|
|
||||||
name='fieldtype',
|
|
||||||
field=models.CharField(verbose_name='Type',
|
|
||||||
choices=[('text', 'Texte long'),
|
|
||||||
('char', 'Texte court')],
|
|
||||||
max_length=10, default='text'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventcommentfield',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(verbose_name='Champ', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventcommentvalue',
|
|
||||||
name='content',
|
|
||||||
field=models.TextField(null=True, verbose_name='Contenu',
|
|
||||||
blank=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventoption',
|
|
||||||
name='multi_choices',
|
|
||||||
field=models.BooleanField(verbose_name='Choix multiples',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventoption',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(verbose_name='Option', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='eventoptionchoice',
|
|
||||||
name='value',
|
|
||||||
field=models.CharField(verbose_name='Valeur', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='petitcoursability',
|
|
||||||
name='niveau',
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[('college', 'Collège'), ('lycee', 'Lycée'),
|
|
||||||
('prepa1styear', 'Prépa 1ère année / L1'),
|
|
||||||
('prepa2ndyear', 'Prépa 2ème année / L2'),
|
|
||||||
('licence3', 'Licence 3'),
|
|
||||||
('other', 'Autre (préciser dans les commentaires)')],
|
|
||||||
max_length=12, verbose_name='Niveau'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='petitcoursattribution',
|
|
||||||
name='rank',
|
|
||||||
field=models.IntegerField(verbose_name="Rang dans l'email"),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='petitcoursattributioncounter',
|
|
||||||
name='count',
|
|
||||||
field=models.IntegerField(verbose_name="Nombre d'envois",
|
|
||||||
default=0),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='petitcoursdemande',
|
|
||||||
name='niveau',
|
|
||||||
field=models.CharField(
|
|
||||||
verbose_name='Niveau',
|
|
||||||
choices=[('college', 'Collège'), ('lycee', 'Lycée'),
|
|
||||||
('prepa1styear', 'Prépa 1ère année / L1'),
|
|
||||||
('prepa2ndyear', 'Prépa 2ème année / L2'),
|
|
||||||
('licence3', 'Licence 3'),
|
|
||||||
('other', 'Autre (préciser dans les commentaires)')],
|
|
||||||
max_length=12, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='survey',
|
|
||||||
name='old',
|
|
||||||
field=models.BooleanField(verbose_name='Archiver (sondage fini)',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='survey',
|
|
||||||
name='survey_open',
|
|
||||||
field=models.BooleanField(verbose_name='Sondage ouvert',
|
|
||||||
default=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='survey',
|
|
||||||
name='title',
|
|
||||||
field=models.CharField(verbose_name='Titre', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='surveyquestion',
|
|
||||||
name='multi_answers',
|
|
||||||
field=models.BooleanField(verbose_name='Choix multiples',
|
|
||||||
default=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='surveyquestion',
|
|
||||||
name='question',
|
|
||||||
field=models.CharField(verbose_name='Question', max_length=200),
|
|
||||||
),
|
|
||||||
migrations.RunPython(forwards, migrations.RunPython.noop),
|
|
||||||
]
|
|
|
@ -1,131 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
def create_profile(apps, schema_editor):
|
|
||||||
CofProfile = apps.get_model("cof", "CofProfile")
|
|
||||||
Profile = apps.get_model("gestion", "Profile")
|
|
||||||
for p in CofProfile.objects.all():
|
|
||||||
profile = Profile.objects.create(
|
|
||||||
id=p.id,
|
|
||||||
user=p.user,
|
|
||||||
login_clipper=p.login_clipper,
|
|
||||||
phone=p.phone,
|
|
||||||
occupation=p.occupation,
|
|
||||||
departement=p.departement,
|
|
||||||
comments=p.comments
|
|
||||||
)
|
|
||||||
p.profile = profile
|
|
||||||
p.save()
|
|
||||||
|
|
||||||
|
|
||||||
def preserve_perms(apps, schema_editor):
|
|
||||||
# from django.contrib.auth.management import create_permissions
|
|
||||||
|
|
||||||
# apps.models_module = True
|
|
||||||
# create_permissions(apps, verbosity=0)
|
|
||||||
# apps.models_module = None
|
|
||||||
|
|
||||||
CofProfile = apps.get_model("cof", "CofProfile")
|
|
||||||
# memberp = Permission.objects.get(codename='member')
|
|
||||||
# burop = Permission.objects.get(codename='buro')
|
|
||||||
|
|
||||||
# creates the groups for COF members and
|
|
||||||
member = Group.objects.create(name='cof_members')
|
|
||||||
buro = Group.objects.create(name='cof_buro')
|
|
||||||
|
|
||||||
# associate permissions to the respective groups.
|
|
||||||
# buro.permissions = [burop, memberp]
|
|
||||||
# member.permissions = [memberp]
|
|
||||||
|
|
||||||
for cofp in CofProfile.objects.filter(is_cof=True):
|
|
||||||
cofp.profile.user.groups.add(member)
|
|
||||||
for cofp in CofProfile.objects.filter(is_buro=True):
|
|
||||||
cofp.profile.user.groups.add(buro)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_profile(apps, schema_editor):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
fuckup_perms = remove_profile
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('gestion', '0001_initial'),
|
|
||||||
('cof', '0008_py3'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
old_name='mailing_cof',
|
|
||||||
new_name='mailing',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='profile',
|
|
||||||
field=models.OneToOneField(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='gestion.Profile',
|
|
||||||
null=True,
|
|
||||||
related_name='cof'
|
|
||||||
),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='cofprofile',
|
|
||||||
options={
|
|
||||||
'permissions': (('member', 'Is a COF member'),
|
|
||||||
('buro', 'Is part of COF staff')),
|
|
||||||
'verbose_name': 'Profil COF',
|
|
||||||
'verbose_name_plural': 'Profils COF'},
|
|
||||||
),
|
|
||||||
migrations.RunPython(create_profile, remove_profile),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='profile',
|
|
||||||
field=models.OneToOneField(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
to='gestion.Profile',
|
|
||||||
related_name='cof'
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='comments',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='departement',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='login_clipper',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='occupation',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='phone',
|
|
||||||
),
|
|
||||||
migrations.RunPython(preserve_perms, fuckup_perms),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='is_cof',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='is_buro',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='user',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,26 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def create_cof_group(apps, schema_editor):
|
|
||||||
Group = apps.get_model("auth", "Group")
|
|
||||||
Group.objects.get_or_create(name="cof_members")
|
|
||||||
|
|
||||||
|
|
||||||
def create_buro_group(apps, schema_editor):
|
|
||||||
Group = apps.get_model("auth", "Group")
|
|
||||||
Group.objects.get_or_create(name="cof_buro")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0009_generic_profiles'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(create_cof_group, migrations.RunPython.noop),
|
|
||||||
migrations.RunPython(create_buro_group, migrations.RunPython.noop),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0010_create_cof_group'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='Clipper',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='CustomMail',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,23 +0,0 @@
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0011_delete_clipper_and_custommail'),
|
|
||||||
('gestion', '0002_club_support')
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='club',
|
|
||||||
name='membres',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='club',
|
|
||||||
name='respos',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='Club',
|
|
||||||
),
|
|
||||||
]
|
|
279
cof/models.py
279
cof/models.py
|
@ -1,279 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.contrib.auth.models import Group, User
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
import django.utils.six as six
|
|
||||||
|
|
||||||
from gestion.models import Profile
|
|
||||||
from bda.models import Spectacle
|
|
||||||
|
|
||||||
from .petits_cours_models import choices_length
|
|
||||||
|
|
||||||
TYPE_COTIZ_CHOICES = (
|
|
||||||
('etudiant', _("Normalien étudiant")),
|
|
||||||
('normalien', _("Normalien élève")),
|
|
||||||
('exterieur', _("Extérieur")),
|
|
||||||
)
|
|
||||||
|
|
||||||
TYPE_COMMENT_FIELD = (
|
|
||||||
('text', _("Texte long")),
|
|
||||||
('char', _("Texte court")),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CofProfile(models.Model):
|
|
||||||
profile = models.OneToOneField(Profile,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="cof")
|
|
||||||
|
|
||||||
num = models.IntegerField("Numéro d'adhérent", blank=True, default=0)
|
|
||||||
type_cotiz = models.CharField(_("Type de cotisation"),
|
|
||||||
default="normalien",
|
|
||||||
choices=TYPE_COTIZ_CHOICES,
|
|
||||||
max_length=choices_length(
|
|
||||||
TYPE_COTIZ_CHOICES))
|
|
||||||
mailing = models.BooleanField("Recevoir les mails COF", default=False)
|
|
||||||
# XXX. remove the following and put in a BDA profile
|
|
||||||
mailing_bda = models.BooleanField("Recevoir les mails BdA", default=False)
|
|
||||||
mailing_bda_revente = models.BooleanField(
|
|
||||||
"Recevoir les mails de revente de places BdA", default=False)
|
|
||||||
|
|
||||||
petits_cours_accept = models.BooleanField(
|
|
||||||
"Recevoir des petits cours", default=False)
|
|
||||||
petits_cours_remarques = models.TextField(
|
|
||||||
_("Remarques et précisions pour les petits cours"),
|
|
||||||
blank=True, default="")
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Profil COF"
|
|
||||||
verbose_name_plural = "Profils COF"
|
|
||||||
permissions = (
|
|
||||||
('member', 'Is a COF member'),
|
|
||||||
('buro', 'Is part of COF staff'),
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_cof(self):
|
|
||||||
return self.profile.user.has_perm('cof.member')
|
|
||||||
|
|
||||||
@is_cof.setter
|
|
||||||
def is_cof(self, really):
|
|
||||||
if really:
|
|
||||||
g = Group.objects.get(name='cof_members')
|
|
||||||
self.profile.user.groups.add(g)
|
|
||||||
|
|
||||||
# XXX. remove the following and use django auth.
|
|
||||||
@property
|
|
||||||
def is_buro(self):
|
|
||||||
return self.profile.user.has_perm('cof.buro')
|
|
||||||
|
|
||||||
@is_buro.setter
|
|
||||||
def is_buro(self, really):
|
|
||||||
if really:
|
|
||||||
g = Group.objects.get(name='cof_buro')
|
|
||||||
self.profile.user.groups.add(g)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.profile.user.username
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Event(models.Model):
|
|
||||||
title = models.CharField("Titre", max_length=200)
|
|
||||||
location = models.CharField("Lieu", max_length=200)
|
|
||||||
start_date = models.DateTimeField("Date de début", blank=True, null=True)
|
|
||||||
end_date = models.DateTimeField("Date de fin", blank=True, null=True)
|
|
||||||
description = models.TextField("Description", blank=True)
|
|
||||||
image = models.ImageField("Image", blank=True, null=True,
|
|
||||||
upload_to="imgs/events/")
|
|
||||||
registration_open = models.BooleanField("Inscriptions ouvertes",
|
|
||||||
default=True)
|
|
||||||
old = models.BooleanField("Archiver (événement fini)", default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Événement"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return six.text_type(self.title)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class EventCommentField(models.Model):
|
|
||||||
event = models.ForeignKey(
|
|
||||||
Event,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="commentfields"
|
|
||||||
)
|
|
||||||
name = models.CharField("Champ", max_length=200)
|
|
||||||
fieldtype = models.CharField("Type", max_length=10,
|
|
||||||
choices=TYPE_COMMENT_FIELD, default="text")
|
|
||||||
default = models.TextField("Valeur par défaut", blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Champ"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return six.text_type(self.name)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class EventCommentValue(models.Model):
|
|
||||||
commentfield = models.ForeignKey(
|
|
||||||
EventCommentField,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="values"
|
|
||||||
)
|
|
||||||
registration = models.ForeignKey(
|
|
||||||
"EventRegistration",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="comments"
|
|
||||||
)
|
|
||||||
content = models.TextField("Contenu", blank=True, null=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Commentaire de %s" % self.commentfield
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class EventOption(models.Model):
|
|
||||||
event = models.ForeignKey(
|
|
||||||
Event,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="options"
|
|
||||||
)
|
|
||||||
name = models.CharField("Option", max_length=200)
|
|
||||||
multi_choices = models.BooleanField("Choix multiples", default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Option"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return six.text_type(self.name)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class EventOptionChoice(models.Model):
|
|
||||||
event_option = models.ForeignKey(
|
|
||||||
EventOption,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="choices"
|
|
||||||
)
|
|
||||||
value = models.CharField("Valeur", max_length=200)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Choix"
|
|
||||||
verbose_name_plural = "Choix"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return six.text_type(self.value)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class EventRegistration(models.Model):
|
|
||||||
user = models.ForeignKey(
|
|
||||||
User,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
event = models.ForeignKey(
|
|
||||||
Event,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
options = models.ManyToManyField(EventOptionChoice)
|
|
||||||
filledcomments = models.ManyToManyField(EventCommentField,
|
|
||||||
through=EventCommentValue)
|
|
||||||
paid = models.BooleanField("A payé", default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Inscription"
|
|
||||||
unique_together = ("user", "event")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Inscription de %s à %s" % (six.text_type(self.user),
|
|
||||||
six.text_type(self.event.title))
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Survey(models.Model):
|
|
||||||
title = models.CharField("Titre", max_length=200)
|
|
||||||
details = models.TextField("Détails", blank=True)
|
|
||||||
survey_open = models.BooleanField("Sondage ouvert", default=True)
|
|
||||||
old = models.BooleanField("Archiver (sondage fini)", default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Sondage"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return six.text_type(self.title)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class SurveyQuestion(models.Model):
|
|
||||||
survey = models.ForeignKey(
|
|
||||||
Survey,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="questions"
|
|
||||||
)
|
|
||||||
question = models.CharField("Question", max_length=200)
|
|
||||||
multi_answers = models.BooleanField("Choix multiples", default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Question"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return six.text_type(self.question)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class SurveyQuestionAnswer(models.Model):
|
|
||||||
survey_question = models.ForeignKey(
|
|
||||||
SurveyQuestion,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="answers"
|
|
||||||
)
|
|
||||||
answer = models.CharField("Réponse", max_length=200)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Réponse"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return six.text_type(self.answer)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class SurveyAnswer(models.Model):
|
|
||||||
user = models.ForeignKey(
|
|
||||||
User,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
survey = models.ForeignKey(
|
|
||||||
Survey,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
answers = models.ManyToManyField(SurveyQuestionAnswer,
|
|
||||||
related_name="selected_by")
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Réponses"
|
|
||||||
unique_together = ("user", "survey")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Réponse de %s sondage %s" % (
|
|
||||||
self.user.get_full_name(),
|
|
||||||
self.survey.title)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class CalendarSubscription(models.Model):
|
|
||||||
token = models.UUIDField()
|
|
||||||
user = models.OneToOneField(
|
|
||||||
User,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
other_shows = models.ManyToManyField(Spectacle)
|
|
||||||
subscribe_to_events = models.BooleanField(default=True)
|
|
||||||
subscribe_to_my_shows = models.BooleanField(default=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Calendrier de %s" % self.user.get_full_name()
|
|
|
@ -1,54 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from captcha.fields import ReCaptchaField
|
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.forms import ModelForm
|
|
||||||
from django.forms.models import inlineformset_factory, BaseInlineFormSet
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
from .petits_cours_models import PetitCoursDemande, PetitCoursAbility
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMatieresFormSet(BaseInlineFormSet):
|
|
||||||
def clean(self):
|
|
||||||
super(BaseMatieresFormSet, self).clean()
|
|
||||||
if any(self.errors):
|
|
||||||
# Don't bother validating the formset unless each form is
|
|
||||||
# valid on its own
|
|
||||||
return
|
|
||||||
matieres = []
|
|
||||||
for i in range(0, self.total_form_count()):
|
|
||||||
form = self.forms[i]
|
|
||||||
if not form.cleaned_data:
|
|
||||||
continue
|
|
||||||
matiere = form.cleaned_data['matiere']
|
|
||||||
niveau = form.cleaned_data['niveau']
|
|
||||||
delete = form.cleaned_data['DELETE']
|
|
||||||
if not delete and (matiere, niveau) in matieres:
|
|
||||||
raise forms.ValidationError(
|
|
||||||
"Vous ne pouvez pas vous inscrire deux fois pour la "
|
|
||||||
"même matiere avec le même niveau.")
|
|
||||||
matieres.append((matiere, niveau))
|
|
||||||
|
|
||||||
|
|
||||||
class DemandeForm(ModelForm):
|
|
||||||
captcha = ReCaptchaField(attrs={'theme': 'clean', 'lang': 'fr'})
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(DemandeForm, self).__init__(*args, **kwargs)
|
|
||||||
self.fields['matieres'].help_text = ''
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = PetitCoursDemande
|
|
||||||
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu',
|
|
||||||
'matieres', 'agrege_requis', 'niveau', 'remarques')
|
|
||||||
widgets = {'matieres': forms.CheckboxSelectMultiple}
|
|
||||||
|
|
||||||
|
|
||||||
MatieresFormSet = inlineformset_factory(
|
|
||||||
User,
|
|
||||||
PetitCoursAbility,
|
|
||||||
fields=("matiere", "niveau", "agrege"),
|
|
||||||
formset=BaseMatieresFormSet
|
|
||||||
)
|
|
|
@ -1,207 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from functools import reduce
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.db.models import Min
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
def choices_length(choices):
|
|
||||||
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
|
||||||
|
|
||||||
LEVELS_CHOICES = (
|
|
||||||
('college', _("Collège")),
|
|
||||||
('lycee', _("Lycée")),
|
|
||||||
('prepa1styear', _("Prépa 1ère année / L1")),
|
|
||||||
('prepa2ndyear', _("Prépa 2ème année / L2")),
|
|
||||||
('licence3', _("Licence 3")),
|
|
||||||
('other', _("Autre (préciser dans les commentaires)")),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursSubject(models.Model):
|
|
||||||
name = models.CharField(_("Matière"), max_length=30)
|
|
||||||
users = models.ManyToManyField(User, related_name="petits_cours_matieres",
|
|
||||||
through="PetitCoursAbility")
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Matière de petits cours"
|
|
||||||
verbose_name_plural = "Matières des petits cours"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAbility(models.Model):
|
|
||||||
user = models.ForeignKey(
|
|
||||||
User,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
matiere = models.ForeignKey(
|
|
||||||
PetitCoursSubject,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Matière")
|
|
||||||
)
|
|
||||||
niveau = models.CharField(_("Niveau"),
|
|
||||||
choices=LEVELS_CHOICES,
|
|
||||||
max_length=choices_length(LEVELS_CHOICES))
|
|
||||||
agrege = models.BooleanField(_("Agrégé"), default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Compétence petits cours"
|
|
||||||
verbose_name_plural = "Compétences des petits cours"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{:s} - {!s} - {:s}".format(
|
|
||||||
self.user.username, self.matiere, self.niveau
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursDemande(models.Model):
|
|
||||||
name = models.CharField(_("Nom/prénom"), max_length=200)
|
|
||||||
email = models.CharField(_("Adresse email"), max_length=300)
|
|
||||||
phone = models.CharField(_("Téléphone (facultatif)"),
|
|
||||||
max_length=20, blank=True)
|
|
||||||
quand = models.CharField(
|
|
||||||
_("Quand ?"),
|
|
||||||
help_text=_("Indiquez ici la période désirée pour les petits"
|
|
||||||
" cours (vacances scolaires, semaine, week-end)."),
|
|
||||||
max_length=300, blank=True)
|
|
||||||
freq = models.CharField(
|
|
||||||
_("Fréquence"),
|
|
||||||
help_text=_("Indiquez ici la fréquence envisagée "
|
|
||||||
"(hebdomadaire, 2 fois par semaine, ...)"),
|
|
||||||
max_length=300, blank=True)
|
|
||||||
lieu = models.CharField(
|
|
||||||
_("Lieu (si préférence)"),
|
|
||||||
help_text=_("Si vous avez avez une préférence sur le lieu."),
|
|
||||||
max_length=300, blank=True)
|
|
||||||
|
|
||||||
matieres = models.ManyToManyField(
|
|
||||||
PetitCoursSubject, verbose_name=_("Matières"),
|
|
||||||
related_name="demandes")
|
|
||||||
agrege_requis = models.BooleanField(_("Agrégé requis"), default=False)
|
|
||||||
niveau = models.CharField(_("Niveau"),
|
|
||||||
default="",
|
|
||||||
choices=LEVELS_CHOICES,
|
|
||||||
max_length=choices_length(LEVELS_CHOICES))
|
|
||||||
|
|
||||||
remarques = models.TextField(_("Remarques et précisions"), blank=True)
|
|
||||||
|
|
||||||
traitee = models.BooleanField(_("Traitée"), default=False)
|
|
||||||
traitee_par = models.ForeignKey(
|
|
||||||
User,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
blank=True, null=True
|
|
||||||
)
|
|
||||||
processed = models.DateTimeField(_("Date de traitement"),
|
|
||||||
blank=True, null=True)
|
|
||||||
created = models.DateTimeField(_("Date de création"), auto_now_add=True)
|
|
||||||
|
|
||||||
def get_candidates(self, redo=False):
|
|
||||||
"""
|
|
||||||
Donne la liste des profs disponibles pour chaque matière de la demande.
|
|
||||||
- On ne donne que les agrégés si c'est demandé
|
|
||||||
- Si ``redo`` vaut ``True``, cela signifie qu'on retraite la demande et
|
|
||||||
il ne faut pas proposer à nouveau des noms qui ont déjà été proposés
|
|
||||||
"""
|
|
||||||
for matiere in self.matieres.all():
|
|
||||||
candidates = PetitCoursAbility.objects.filter(
|
|
||||||
matiere=matiere,
|
|
||||||
niveau=self.niveau,
|
|
||||||
user__profile__is_cof=True,
|
|
||||||
user__profile__petits_cours_accept=True
|
|
||||||
)
|
|
||||||
if self.agrege_requis:
|
|
||||||
candidates = candidates.filter(agrege=True)
|
|
||||||
if redo:
|
|
||||||
attrs = self.petitcoursattribution_set.filter(matiere=matiere)
|
|
||||||
already_proposed = [
|
|
||||||
attr.user
|
|
||||||
for attr in attrs
|
|
||||||
]
|
|
||||||
candidates = candidates.exclude(user__in=already_proposed)
|
|
||||||
candidates = candidates.order_by('?').select_related().all()
|
|
||||||
yield (matiere, candidates)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Demande de petits cours"
|
|
||||||
verbose_name_plural = "Demandes de petits cours"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Demande {:d} du {:s}".format(
|
|
||||||
self.id, self.created.strftime("%d %b %Y")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttribution(models.Model):
|
|
||||||
user = models.ForeignKey(
|
|
||||||
User,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
demande = models.ForeignKey(
|
|
||||||
PetitCoursDemande,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Demande")
|
|
||||||
)
|
|
||||||
matiere = models.ForeignKey(
|
|
||||||
PetitCoursSubject,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Matière")
|
|
||||||
)
|
|
||||||
date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True)
|
|
||||||
rank = models.IntegerField("Rang dans l'email")
|
|
||||||
selected = models.BooleanField(_("Sélectionné par le demandeur"),
|
|
||||||
default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Attribution de petits cours"
|
|
||||||
verbose_name_plural = "Attributions de petits cours"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Attribution de la demande {:d} à {:s} pour {!s}".format(
|
|
||||||
self.demande.id, self.user.username, self.matiere
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttributionCounter(models.Model):
|
|
||||||
user = models.ForeignKey(
|
|
||||||
User,
|
|
||||||
on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
matiere = models.ForeignKey(
|
|
||||||
PetitCoursSubject,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Matiere")
|
|
||||||
)
|
|
||||||
count = models.IntegerField("Nombre d'envois", default=0)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_uptodate(cls, user, matiere):
|
|
||||||
"""
|
|
||||||
Donne le compteur de l'utilisateur pour cette matière. Si le compteur
|
|
||||||
n'existe pas encore, il est initialisé avec le minimum des valeurs des
|
|
||||||
compteurs de tout le monde.
|
|
||||||
"""
|
|
||||||
counter, created = cls.objects.get_or_create(
|
|
||||||
user=user, matiere=matiere)
|
|
||||||
if created:
|
|
||||||
mincount = (
|
|
||||||
cls.objects.filter(matiere=matiere).exclude(user=user)
|
|
||||||
.aggregate(Min('count'))
|
|
||||||
['count__min']
|
|
||||||
)
|
|
||||||
counter.count = mincount
|
|
||||||
counter.save()
|
|
||||||
return counter
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Compteur d'attribution de petits cours"
|
|
||||||
verbose_name_plural = "Compteurs d'attributions de petits cours"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{:d} demandes envoyées à {:s} pour {!s}".format(
|
|
||||||
self.count, self.user.username, self.matiere
|
|
||||||
)
|
|
|
@ -1,350 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import json
|
|
||||||
from custommail.shortcuts import render_custom_mail
|
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
|
||||||
from django.core import mail
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.views.generic import ListView, DetailView
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from .models import CofProfile
|
|
||||||
from .petits_cours_models import (
|
|
||||||
PetitCoursDemande, PetitCoursAttribution, PetitCoursAttributionCounter,
|
|
||||||
PetitCoursAbility, PetitCoursSubject
|
|
||||||
)
|
|
||||||
from .decorators import buro_required
|
|
||||||
from .petits_cours_forms import DemandeForm, MatieresFormSet
|
|
||||||
|
|
||||||
from gestion.shared import lock_table, unlock_tables
|
|
||||||
|
|
||||||
|
|
||||||
class DemandeListView(ListView):
|
|
||||||
model = PetitCoursDemande
|
|
||||||
template_name = "petits_cours_demandes_list.html"
|
|
||||||
paginate_by = 20
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return PetitCoursDemande.objects.order_by('traitee', '-id').all()
|
|
||||||
|
|
||||||
|
|
||||||
class DemandeDetailView(DetailView):
|
|
||||||
model = PetitCoursDemande
|
|
||||||
template_name = "cof/details_demande_petit_cours.html"
|
|
||||||
context_object_name = "demande"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(DemandeDetailView, self).get_context_data(**kwargs)
|
|
||||||
obj = self.object
|
|
||||||
context['attributions'] = obj.petitcoursattribution_set.all()
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
|
||||||
def traitement(request, demande_id, redo=False):
|
|
||||||
demande = get_object_or_404(PetitCoursDemande, id=demande_id)
|
|
||||||
if demande.niveau == "other":
|
|
||||||
return _traitement_other(request, demande, redo)
|
|
||||||
if request.method == "POST":
|
|
||||||
return _traitement_post(request, demande)
|
|
||||||
proposals = {}
|
|
||||||
proposed_for = {}
|
|
||||||
unsatisfied = []
|
|
||||||
attribdata = {}
|
|
||||||
for matiere, candidates in demande.get_candidates(redo):
|
|
||||||
if candidates:
|
|
||||||
tuples = []
|
|
||||||
for candidate in candidates:
|
|
||||||
user = candidate.user
|
|
||||||
tuples.append((
|
|
||||||
candidate,
|
|
||||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
|
||||||
))
|
|
||||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
|
||||||
candidates, _ = zip(*tuples)
|
|
||||||
candidates = candidates[0:min(3, len(candidates))]
|
|
||||||
attribdata[matiere.id] = []
|
|
||||||
proposals[matiere] = []
|
|
||||||
for candidate in candidates:
|
|
||||||
user = candidate.user
|
|
||||||
proposals[matiere].append(user)
|
|
||||||
attribdata[matiere.id].append(user.id)
|
|
||||||
if user not in proposed_for:
|
|
||||||
proposed_for[user] = [matiere]
|
|
||||||
else:
|
|
||||||
proposed_for[user].append(matiere)
|
|
||||||
else:
|
|
||||||
unsatisfied.append(matiere)
|
|
||||||
return _finalize_traitement(request, demande, proposals,
|
|
||||||
proposed_for, unsatisfied, attribdata, redo)
|
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
|
||||||
def retraitement(request, demande_id):
|
|
||||||
return traitement(request, demande_id, redo=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _finalize_traitement(request, demande, proposals, proposed_for,
|
|
||||||
unsatisfied, attribdata, redo=False, errors=None):
|
|
||||||
proposals = proposals.items()
|
|
||||||
proposed_for = proposed_for.items()
|
|
||||||
attribdata = list(attribdata.items())
|
|
||||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
|
||||||
mainmail = render_custom_mail("petits-cours-mail-demandeur", {
|
|
||||||
"proposals": proposals,
|
|
||||||
"unsatisfied": unsatisfied,
|
|
||||||
"extra":
|
|
||||||
'<textarea name="extra" '
|
|
||||||
'style="width:99%; height: 90px;">'
|
|
||||||
'</textarea>'
|
|
||||||
})
|
|
||||||
for error in errors:
|
|
||||||
messages.error(request, error)
|
|
||||||
return render(request, "cof/traitement_demande_petit_cours.html",
|
|
||||||
{"demande": demande,
|
|
||||||
"unsatisfied": unsatisfied,
|
|
||||||
"proposals": proposals,
|
|
||||||
"proposed_for": proposed_for,
|
|
||||||
"proposed_mails": proposed_mails,
|
|
||||||
"mainmail": mainmail,
|
|
||||||
"attribdata": json.dumps(attribdata),
|
|
||||||
"redo": redo,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_eleve_email(demande, proposed_for):
|
|
||||||
return [
|
|
||||||
(
|
|
||||||
user,
|
|
||||||
render_custom_mail('petit-cours-mail-eleve', {
|
|
||||||
"demande": demande,
|
|
||||||
"matieres": matieres
|
|
||||||
})
|
|
||||||
)
|
|
||||||
for user, matieres in proposed_for
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _traitement_other_preparing(request, demande):
|
|
||||||
redo = "redo" in request.POST
|
|
||||||
unsatisfied = []
|
|
||||||
proposals = {}
|
|
||||||
proposed_for = {}
|
|
||||||
attribdata = {}
|
|
||||||
errors = []
|
|
||||||
for matiere, candidates in demande.get_candidates(redo):
|
|
||||||
if candidates:
|
|
||||||
candidates = dict([(candidate.user.id, candidate.user)
|
|
||||||
for candidate in candidates])
|
|
||||||
attribdata[matiere.id] = []
|
|
||||||
proposals[matiere] = []
|
|
||||||
for choice_id in range(min(3, len(candidates))):
|
|
||||||
choice = int(
|
|
||||||
request.POST["proposal-{:d}-{:d}"
|
|
||||||
.format(matiere.id, choice_id)]
|
|
||||||
)
|
|
||||||
if choice == -1:
|
|
||||||
continue
|
|
||||||
if choice not in candidates:
|
|
||||||
errors.append("Choix invalide pour la proposition {:d}"
|
|
||||||
"en {!s}".format(choice_id + 1, matiere))
|
|
||||||
continue
|
|
||||||
user = candidates[choice]
|
|
||||||
if user in proposals[matiere]:
|
|
||||||
errors.append("La proposition {:d} en {!s} est un doublon"
|
|
||||||
.format(choice_id + 1, matiere))
|
|
||||||
continue
|
|
||||||
proposals[matiere].append(user)
|
|
||||||
attribdata[matiere.id].append(user.id)
|
|
||||||
if user not in proposed_for:
|
|
||||||
proposed_for[user] = [matiere]
|
|
||||||
else:
|
|
||||||
proposed_for[user].append(matiere)
|
|
||||||
if not proposals[matiere]:
|
|
||||||
errors.append("Aucune proposition pour {!s}".format(matiere))
|
|
||||||
elif len(proposals[matiere]) < 3:
|
|
||||||
errors.append("Seulement {:d} proposition{:s} pour {!s}"
|
|
||||||
.format(
|
|
||||||
len(proposals[matiere]),
|
|
||||||
"s" if len(proposals[matiere]) > 1 else "",
|
|
||||||
matiere))
|
|
||||||
else:
|
|
||||||
unsatisfied.append(matiere)
|
|
||||||
return _finalize_traitement(request, demande, proposals, proposed_for,
|
|
||||||
unsatisfied, attribdata, errors=errors)
|
|
||||||
|
|
||||||
|
|
||||||
def _traitement_other(request, demande, redo):
|
|
||||||
if request.method == "POST":
|
|
||||||
if "preparing" in request.POST:
|
|
||||||
return _traitement_other_preparing(request, demande)
|
|
||||||
else:
|
|
||||||
return _traitement_post(request, demande)
|
|
||||||
proposals = {}
|
|
||||||
proposed_for = {}
|
|
||||||
unsatisfied = []
|
|
||||||
attribdata = {}
|
|
||||||
for matiere, candidates in demande.get_candidates(redo):
|
|
||||||
if candidates:
|
|
||||||
tuples = []
|
|
||||||
for candidate in candidates:
|
|
||||||
user = candidate.user
|
|
||||||
tuples.append((
|
|
||||||
candidate,
|
|
||||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
|
||||||
))
|
|
||||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
|
||||||
candidates, _ = zip(*tuples)
|
|
||||||
attribdata[matiere.id] = []
|
|
||||||
proposals[matiere] = []
|
|
||||||
for candidate in candidates:
|
|
||||||
user = candidate.user
|
|
||||||
proposals[matiere].append(user)
|
|
||||||
attribdata[matiere.id].append(user.id)
|
|
||||||
if user not in proposed_for:
|
|
||||||
proposed_for[user] = [matiere]
|
|
||||||
else:
|
|
||||||
proposed_for[user].append(matiere)
|
|
||||||
else:
|
|
||||||
unsatisfied.append(matiere)
|
|
||||||
proposals = proposals.items()
|
|
||||||
proposed_for = proposed_for.items()
|
|
||||||
return render(request,
|
|
||||||
"gestiocof/traitement_demande_petit_cours_autre_niveau.html",
|
|
||||||
{"demande": demande,
|
|
||||||
"unsatisfied": unsatisfied,
|
|
||||||
"proposals": proposals,
|
|
||||||
"proposed_for": proposed_for,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def _traitement_post(request, demande):
|
|
||||||
proposals = {}
|
|
||||||
proposed_for = {}
|
|
||||||
unsatisfied = []
|
|
||||||
extra = request.POST["extra"].strip()
|
|
||||||
redo = "redo" in request.POST
|
|
||||||
attribdata = request.POST["attribdata"]
|
|
||||||
attribdata = dict(json.loads(attribdata))
|
|
||||||
for matiere in demande.matieres.all():
|
|
||||||
if matiere.id not in attribdata:
|
|
||||||
unsatisfied.append(matiere)
|
|
||||||
else:
|
|
||||||
proposals[matiere] = []
|
|
||||||
for user_id in attribdata[matiere.id]:
|
|
||||||
user = User.objects.get(pk=user_id)
|
|
||||||
proposals[matiere].append(user)
|
|
||||||
if user not in proposed_for:
|
|
||||||
proposed_for[user] = [matiere]
|
|
||||||
else:
|
|
||||||
proposed_for[user].append(matiere)
|
|
||||||
proposals_list = proposals.items()
|
|
||||||
proposed_for = proposed_for.items()
|
|
||||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
|
||||||
mainmail = render_custom_mail("petits-cours-mail-demandeur", {
|
|
||||||
"proposals": proposals_list,
|
|
||||||
"unsatisfied": unsatisfied,
|
|
||||||
"extra": extra,
|
|
||||||
})
|
|
||||||
frommail = settings.MAIL_DATA['petits_cours']['FROM']
|
|
||||||
bccaddress = settings.MAIL_DATA['petits_cours']['BCC']
|
|
||||||
replyto = settings.MAIL_DATA['petits_cours']['REPLYTO']
|
|
||||||
mails_to_send = []
|
|
||||||
for (user, msg) in proposed_mails:
|
|
||||||
msg = mail.EmailMessage("Petits cours ENS par le COF", msg,
|
|
||||||
frommail, [user.email],
|
|
||||||
[bccaddress], headers={'Reply-To': replyto})
|
|
||||||
mails_to_send.append(msg)
|
|
||||||
mails_to_send.append(mail.EmailMessage("Cours particuliers ENS", mainmail,
|
|
||||||
frommail, [demande.email],
|
|
||||||
[bccaddress],
|
|
||||||
headers={'Reply-To': replyto}))
|
|
||||||
connection = mail.get_connection(fail_silently=True)
|
|
||||||
connection.send_messages(mails_to_send)
|
|
||||||
lock_table(PetitCoursAttributionCounter, PetitCoursAttribution, User)
|
|
||||||
for matiere in proposals:
|
|
||||||
for rank, user in enumerate(proposals[matiere]):
|
|
||||||
counter = PetitCoursAttributionCounter.objects.get(user=user,
|
|
||||||
matiere=matiere)
|
|
||||||
counter.count += 1
|
|
||||||
counter.save()
|
|
||||||
attrib = PetitCoursAttribution(user=user, matiere=matiere,
|
|
||||||
demande=demande, rank=rank + 1)
|
|
||||||
attrib.save()
|
|
||||||
unlock_tables()
|
|
||||||
demande.traitee = True
|
|
||||||
demande.traitee_par = request.user
|
|
||||||
demande.processed = timezone.now()
|
|
||||||
demande.save()
|
|
||||||
return render(request,
|
|
||||||
"cof/traitement_demande_petit_cours_success.html",
|
|
||||||
{"demande": demande,
|
|
||||||
"redo": redo,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def inscription(request):
|
|
||||||
profile, created = CofProfile.objects.get_or_create(user=request.user)
|
|
||||||
if not profile.is_cof:
|
|
||||||
return redirect("cof-denied")
|
|
||||||
success = False
|
|
||||||
if request.method == "POST":
|
|
||||||
formset = MatieresFormSet(request.POST, instance=request.user)
|
|
||||||
if formset.is_valid():
|
|
||||||
formset.save()
|
|
||||||
profile.petits_cours_accept = "receive_proposals" in request.POST
|
|
||||||
profile.petits_cours_remarques = request.POST["remarques"]
|
|
||||||
profile.save()
|
|
||||||
lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
|
|
||||||
PetitCoursSubject)
|
|
||||||
abilities = (
|
|
||||||
PetitCoursAbility.objects.filter(user=request.user).all()
|
|
||||||
)
|
|
||||||
for ability in abilities:
|
|
||||||
PetitCoursAttributionCounter.get_uptodate(
|
|
||||||
ability.user,
|
|
||||||
ability.matiere
|
|
||||||
)
|
|
||||||
unlock_tables()
|
|
||||||
success = True
|
|
||||||
formset = MatieresFormSet(instance=request.user)
|
|
||||||
else:
|
|
||||||
formset = MatieresFormSet(instance=request.user)
|
|
||||||
return render(request, "inscription-petit-cours.html",
|
|
||||||
{"formset": formset, "success": success,
|
|
||||||
"receive_proposals": profile.petits_cours_accept,
|
|
||||||
"remarques": profile.petits_cours_remarques})
|
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def demande(request):
|
|
||||||
success = False
|
|
||||||
if request.method == "POST":
|
|
||||||
form = DemandeForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
form.save()
|
|
||||||
success = True
|
|
||||||
else:
|
|
||||||
form = DemandeForm()
|
|
||||||
return render(request, "demande-petit-cours.html", {"form": form,
|
|
||||||
"success": success})
|
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def demande_raw(request):
|
|
||||||
success = False
|
|
||||||
if request.method == "POST":
|
|
||||||
form = DemandeForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
form.save()
|
|
||||||
success = True
|
|
||||||
else:
|
|
||||||
form = DemandeForm()
|
|
||||||
return render(request, "demande-petit-cours-raw.html",
|
|
||||||
{"form": form, "success": success})
|
|
6
cof/static/css/bootstrap.min.css
vendored
6
cof/static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
Binary file not shown.
7
cof/static/js/bootstrap.min.js
vendored
7
cof/static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,19 +0,0 @@
|
||||||
Copyright (c) 2012 Joseph McCullough
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,166 +0,0 @@
|
||||||
Stupid jQuery Table Sort
|
|
||||||
========================
|
|
||||||
|
|
||||||
This is a stupid jQuery table sorting plugin. Nothing fancy, nothing really
|
|
||||||
impressive. Overall, stupidly simple.
|
|
||||||
|
|
||||||
[View the demo here][0]
|
|
||||||
|
|
||||||
See the example.html document to see how to implement it.
|
|
||||||
|
|
||||||
|
|
||||||
Example Usage
|
|
||||||
-------------
|
|
||||||
|
|
||||||
The JS:
|
|
||||||
|
|
||||||
$("table").stupidtable();
|
|
||||||
|
|
||||||
The HTML:
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-sort="int">int</th>
|
|
||||||
<th data-sort="float">float</th>
|
|
||||||
<th data-sort="string">string</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>15</td>
|
|
||||||
<td>-.18</td>
|
|
||||||
<td>banana</td>
|
|
||||||
</tr>
|
|
||||||
...
|
|
||||||
...
|
|
||||||
...
|
|
||||||
|
|
||||||
The thead and tbody tags must be used.
|
|
||||||
|
|
||||||
Add a `data-sort` attribute of "DATATYPE" to the th elements to make them sortable
|
|
||||||
by that data type. If you don't want that column to be sortable, just omit the
|
|
||||||
`data-sort` attribute.
|
|
||||||
|
|
||||||
|
|
||||||
Predefined data types
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Our aim is to keep this plugin as lightweight as possible. Consequently, the
|
|
||||||
only predefined datatypes that you can pass to the th elements are
|
|
||||||
|
|
||||||
* `int`
|
|
||||||
* `float`
|
|
||||||
* `string` (case-sensitive)
|
|
||||||
* `string-ins` (case-insensitive)
|
|
||||||
|
|
||||||
These data types will be sufficient for many simple tables. However, if you need
|
|
||||||
different data types for sorting, you can easily create your own!
|
|
||||||
|
|
||||||
|
|
||||||
Creating your own data types
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
Creating your own data type for sorting purposes is easy as long as you are
|
|
||||||
comfortable using custom functions for sorting. Consult [Mozilla's Docs][1]
|
|
||||||
if you're not.
|
|
||||||
|
|
||||||
Let's create an alphanum datatype for a User ID that takes strings in the
|
|
||||||
form "D10", "A40", and sorts them based on the number.
|
|
||||||
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-sort="string">Name</th>
|
|
||||||
<th data-sort="int">Age</th>
|
|
||||||
<th data-sort="alphanum">UserID</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Joseph McCullough</td>
|
|
||||||
<td>20</td>
|
|
||||||
<td>D10</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Justin Edwards</td>
|
|
||||||
<td>29</td>
|
|
||||||
<td>A40</td>
|
|
||||||
</tr>
|
|
||||||
...
|
|
||||||
...
|
|
||||||
...
|
|
||||||
|
|
||||||
Now we need to specify how the **alphanum** type will be sorted. To do that,
|
|
||||||
we do the following:
|
|
||||||
|
|
||||||
$("table").stupidtable({
|
|
||||||
"alphanum":function(a,b){
|
|
||||||
|
|
||||||
var pattern = "^[A-Z](\\d+)$";
|
|
||||||
var re = new RegExp(pattern);
|
|
||||||
|
|
||||||
var aNum = re.exec(a).slice(1);
|
|
||||||
var bNum = re.exec(b).slice(1);
|
|
||||||
|
|
||||||
return parseInt(aNum,10) - parseInt(bNum,10);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
This extracts the integers from the cell and compares them in the style
|
|
||||||
that sort functions use.
|
|
||||||
|
|
||||||
|
|
||||||
Callbacks
|
|
||||||
---------
|
|
||||||
|
|
||||||
To execute a callback function after a table column has been sorted, you can
|
|
||||||
bind on `aftertablesort`.
|
|
||||||
|
|
||||||
var table = $("table").stupidtable();
|
|
||||||
table.bind('aftertablesort', function (event, data) {
|
|
||||||
// data.column - the index of the column sorted after a click
|
|
||||||
// data.direction - the sorting direction (either asc or desc)
|
|
||||||
// $(this) - this table object
|
|
||||||
|
|
||||||
console.log("The sorting direction: " + data.direction);
|
|
||||||
console.log("The column index: " + data.column);
|
|
||||||
});
|
|
||||||
|
|
||||||
Similarly, to execute a callback before a table column has been sorted, you can
|
|
||||||
bind on `beforetablesort`.
|
|
||||||
|
|
||||||
See the complex_example.html file.
|
|
||||||
|
|
||||||
|
|
||||||
Data with multiple representations/predefined order
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
Often we find two distinct ways of offering data: In a machine friendly way,
|
|
||||||
and a Human-friendly way. A clear example is a Timestamp. Additionally,
|
|
||||||
arbitrary data values may already have a predefined sort order. In either case,
|
|
||||||
it's to our advantage to have a way to store the "sortable data" while letting
|
|
||||||
the viewer see the Human-friendly representation of that data. While the
|
|
||||||
purpose of the custom sort methods is to take data and make it sortable
|
|
||||||
(machine friendly), sometimes this is too hard or too expensive, computationally
|
|
||||||
speaking.
|
|
||||||
|
|
||||||
To solve this problem, you can specify a `data-sort-value` attribute to
|
|
||||||
table cells, and the attribute value will be the basis of the sort as opposed
|
|
||||||
to the text value of the table cell. See the complex_example.html file, where
|
|
||||||
we sort a column of letters based not on their alphabetical order, but by their
|
|
||||||
frequency in the English language. You'll still need to specify a sort type
|
|
||||||
or come up with your own custom sort function, but the presence of the
|
|
||||||
`data-sort-value` attribute tells the plugin to use the value of the
|
|
||||||
attribute as the basis of the sort.
|
|
||||||
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
The Stupid jQuery Plugin is licensed under the MIT license. See the LICENSE
|
|
||||||
file for full details.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[0]: http://joequery.github.com/Stupid-Table-Plugin/
|
|
||||||
[1]: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue