forked from DGNum/gestioCOF
Compare commits
947 commits
aureplop/r
...
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 | ||
|
954a6fdb53 | ||
|
6d6ba70bd7 | ||
|
6d72644ee3 | ||
|
fc37c5a8a0 | ||
|
7512454825 | ||
|
3d091f50b5 | ||
|
347497602c | ||
|
6d6c995563 | ||
|
d57c75d2a0 | ||
|
63ce694b4d | ||
|
59116f2d46 | ||
|
88e911ff9d | ||
|
ba87044638 | ||
|
f8952225d6 | ||
|
c11ccf2ecc | ||
|
8488beeb4e | ||
|
8551ffcfd3 | ||
|
ea495e8f29 | ||
|
7bf2f73e72 | ||
|
0921f32e4c | ||
|
7160a9c954 | ||
|
79fccc136a | ||
|
5086128f16 | ||
|
c80e63415b | ||
|
e9b901337e | ||
|
adf43889e1 | ||
|
5a22b1cd37 | ||
|
0e19abb51a | ||
|
38af50a866 | ||
|
7853987ebb | ||
|
09e63bf00c | ||
|
53658589f8 | ||
|
f5778fed2a | ||
|
66fc364739 | ||
|
6023211ab0 | ||
|
65d7a66eb8 |
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -5,12 +5,19 @@ cof/settings.py
|
||||||
settings.py
|
settings.py
|
||||||
*~
|
*~
|
||||||
venv/
|
venv/
|
||||||
|
.venv/
|
||||||
.vagrant
|
.vagrant
|
||||||
/src
|
/src
|
||||||
media/
|
media/
|
||||||
*.log
|
*.log
|
||||||
|
.sass-cache/
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
.coverage
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
.idea
|
.idea
|
||||||
.cache
|
.cache
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
|
.direnv
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
image: "python:3.5"
|
image: "python:3.7"
|
||||||
|
|
||||||
services:
|
|
||||||
- postgres:latest
|
|
||||||
- redis:latest
|
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
# GestioCOF settings
|
# GestioCOF settings
|
||||||
DJANGO_SETTINGS_MODULE: "gestioCOF.settings.prod"
|
|
||||||
DBHOST: "postgres"
|
DBHOST: "postgres"
|
||||||
REDIS_HOST: "redis"
|
REDIS_HOST: "redis"
|
||||||
REDIS_PASSWD: "dummy"
|
REDIS_PASSWD: "dummy"
|
||||||
|
|
||||||
# Cached packages
|
# Cached packages
|
||||||
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/vendor/pip"
|
||||||
|
|
||||||
# postgres service configuration
|
# postgres service configuration
|
||||||
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||||
|
@ -22,23 +17,79 @@ variables:
|
||||||
# psql password authentication
|
# psql password authentication
|
||||||
PGPASSWORD: $POSTGRES_PASSWORD
|
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
|
|
||||||
|
|
||||||
before_script:
|
.test_template:
|
||||||
- mkdir -p vendor/{python,pip,apt}
|
before_script:
|
||||||
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client
|
- mkdir -p vendor/{pip,apt}
|
||||||
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' gestioCOF/settings/secret_example.py > gestioCOF/settings/secret.py
|
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client libldap2-dev libsasl2-dev
|
||||||
- sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' gestioCOF/settings/secret.py
|
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' gestioasso/settings/secret_example.py > gestioasso/settings/secret.py
|
||||||
|
- sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' gestioasso/settings/secret.py
|
||||||
# Remove the old test database if it has not been done yet
|
# 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"
|
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
|
||||||
- pip install --upgrade --cache-dir vendor/pip -t vendor/python -r requirements.txt
|
- pip install --upgrade -r requirements-prod.txt coverage tblib
|
||||||
- python --version
|
- 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
|
33
README.md
33
README.md
|
@ -1,6 +1,7 @@
|
||||||
# GestioCOF
|
# GestioCOF / GestioBDS
|
||||||
|
|
||||||
![build_status](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/build.svg)
|
[![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
|
||||||
|
|
||||||
|
@ -37,11 +38,21 @@ Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
||||||
pip install -U pip # parfois nécessaire la première fois
|
pip install -U pip # parfois nécessaire la première fois
|
||||||
pip install -r requirements-devel.txt
|
pip install -r requirements-devel.txt
|
||||||
|
|
||||||
Pour terminer, copier le fichier `cof/settings/secret_example.py` vers
|
Pour terminer, copier le fichier `gestioasso/settings/secret_example.py` vers
|
||||||
`cof/settings/secret.py`. Sous Linux ou Mac, préférez plutôt un lien symbolique
|
`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:
|
pour profiter de façon transparente des mises à jour du fichier:
|
||||||
|
|
||||||
ln -s secret_example.py cof/settings/secret.py
|
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
|
#### Fin d'installation
|
||||||
|
@ -175,6 +186,18 @@ Pour lancer les tests :
|
||||||
python manage.py test
|
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
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
- Changer les urls dans les mails "bda-revente" et "bda-shotgun"
|
|
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
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
317
bda/admin.py
317
bda/admin.py
|
@ -1,317 +0,0 @@
|
||||||
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 dal.autocomplete import ModelSelect2
|
|
||||||
|
|
||||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
|
||||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyMixin(object):
|
|
||||||
readonly_fields_update = ()
|
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
|
||||||
readonly_fields = super().get_readonly_fields(request, obj)
|
|
||||||
if obj is None:
|
|
||||||
return readonly_fields
|
|
||||||
else:
|
|
||||||
return readonly_fields + self.readonly_fields_update
|
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleAdminForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
widgets = {
|
|
||||||
'participant': ModelSelect2(url='bda-participant-autocomplete'),
|
|
||||||
'spectacle': ModelSelect2(url='bda-spectacle-autocomplete'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleInline(admin.TabularInline):
|
|
||||||
model = ChoixSpectacle
|
|
||||||
form = ChoixSpectacleAdminForm
|
|
||||||
sortable_field_name = "priority"
|
|
||||||
|
|
||||||
|
|
||||||
class AttributionTabularAdminForm(forms.ModelForm):
|
|
||||||
listing = None
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
spectacles = Spectacle.objects.select_related('location')
|
|
||||||
if self.listing is not None:
|
|
||||||
spectacles = spectacles.filter(listing=self.listing)
|
|
||||||
self.fields['spectacle'].queryset = spectacles
|
|
||||||
|
|
||||||
|
|
||||||
class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
|
||||||
listing = False
|
|
||||||
|
|
||||||
|
|
||||||
class WithListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
|
||||||
listing = True
|
|
||||||
|
|
||||||
|
|
||||||
class AttributionInline(admin.TabularInline):
|
|
||||||
model = Attribution
|
|
||||||
extra = 0
|
|
||||||
listing = None
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
qs = super().get_queryset(request)
|
|
||||||
if self.listing is not None:
|
|
||||||
qs = qs.filter(spectacle__listing=self.listing)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class WithListingAttributionInline(AttributionInline):
|
|
||||||
exclude = ('given', )
|
|
||||||
form = WithListingAttributionTabularAdminForm
|
|
||||||
listing = True
|
|
||||||
|
|
||||||
|
|
||||||
class WithoutListingAttributionInline(AttributionInline):
|
|
||||||
form = WithoutListingAttributionTabularAdminForm
|
|
||||||
listing = False
|
|
||||||
|
|
||||||
|
|
||||||
class ParticipantAdminForm(forms.ModelForm):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['choicesrevente'].queryset = (
|
|
||||||
Spectacle.objects
|
|
||||||
.select_related('location')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|
||||||
inlines = [WithListingAttributionInline, WithoutListingAttributionInline]
|
|
||||||
|
|
||||||
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",)
|
|
||||||
readonly_fields_update = ('user', 'tirage')
|
|
||||||
form = ParticipantAdminForm
|
|
||||||
|
|
||||||
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 __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
if 'spectacle' in self.fields:
|
|
||||||
self.fields['spectacle'].queryset = (
|
|
||||||
Spectacle.objects
|
|
||||||
.select_related('location')
|
|
||||||
)
|
|
||||||
if 'participant' in self.fields:
|
|
||||||
self.fields['participant'].queryset = (
|
|
||||||
Participant.objects
|
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super().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(ReadOnlyMixin, 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
|
|
||||||
readonly_fields_update = ('spectacle', 'participant')
|
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
|
||||||
form = ChoixSpectacleAdminForm
|
|
||||||
|
|
||||||
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 SpectacleReventeAdminForm(forms.ModelForm):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['confirmed_entry'].queryset = (
|
|
||||||
Participant.objects
|
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
|
||||||
self.fields['seller'].queryset = (
|
|
||||||
Participant.objects
|
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
|
||||||
self.fields['soldTo'].queryset = (
|
|
||||||
Participant.objects
|
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
form = SpectacleReventeAdminForm
|
|
||||||
|
|
||||||
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.reset(new_date=timezone.now() - timedelta(hours=1))
|
|
||||||
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)
|
|
100
bda/algorithm.py
100
bda/algorithm.py
|
@ -1,100 +0,0 @@
|
||||||
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*max(choice.priority for choice in choices)
|
|
||||||
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
|
|
180
bda/forms.py
180
bda/forms.py
|
@ -1,180 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.forms.models import BaseInlineFormSet
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from bda.models import Attribution, Spectacle, SpectacleRevente
|
|
||||||
|
|
||||||
|
|
||||||
class InscriptionInlineFormSet(BaseInlineFormSet):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# self.instance is a Participant object
|
|
||||||
tirage = self.instance.tirage
|
|
||||||
|
|
||||||
# set once for all "spectacle" field choices
|
|
||||||
# - restrict choices to the spectacles of this tirage
|
|
||||||
# - force_choices avoid many db requests
|
|
||||||
spectacles = tirage.spectacle_set.select_related('location')
|
|
||||||
choices = [(sp.pk, str(sp)) for sp in spectacles]
|
|
||||||
self.force_choices('spectacle', choices)
|
|
||||||
|
|
||||||
def force_choices(self, name, choices):
|
|
||||||
"""Set choices of a field.
|
|
||||||
|
|
||||||
As ModelChoiceIterator (default use to get choices of a
|
|
||||||
ModelChoiceField), it appends an empty selection if requested.
|
|
||||||
|
|
||||||
"""
|
|
||||||
for form in self.forms:
|
|
||||||
field = form.fields[name]
|
|
||||||
if field.empty_label is not None:
|
|
||||||
field.choices = [('', field.empty_label)] + choices
|
|
||||||
else:
|
|
||||||
field.choices = choices
|
|
||||||
|
|
||||||
|
|
||||||
class TokenForm(forms.Form):
|
|
||||||
token = forms.CharField(widget=forms.widgets.Textarea())
|
|
||||||
|
|
||||||
|
|
||||||
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|
||||||
def label_from_instance(self, obj):
|
|
||||||
return str(obj.spectacle)
|
|
||||||
|
|
||||||
|
|
||||||
class ReventeModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|
||||||
def __init__(self, *args, own=True, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.own = own
|
|
||||||
|
|
||||||
def label_from_instance(self, obj):
|
|
||||||
label = "{show}{suffix}"
|
|
||||||
suffix = ""
|
|
||||||
if self.own:
|
|
||||||
# C'est notre propre revente : pas besoin de spécifier le vendeur
|
|
||||||
if obj.soldTo is not None:
|
|
||||||
suffix = " -- Vendue à {firstname} {lastname}".format(
|
|
||||||
firstname=obj.soldTo.user.first_name,
|
|
||||||
lastname=obj.soldTo.user.last_name,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Ce n'est pas à nous : on ne voit jamais l'acheteur
|
|
||||||
suffix = " -- Vendue par {firstname} {lastname}".format(
|
|
||||||
firstname=obj.seller.user.first_name,
|
|
||||||
lastname=obj.seller.user.last_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
return label.format(show=str(obj.attribution.spectacle),
|
|
||||||
suffix=suffix)
|
|
||||||
|
|
||||||
|
|
||||||
class ResellForm(forms.Form):
|
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
|
||||||
label='',
|
|
||||||
queryset=Attribution.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['attributions'].queryset = (
|
|
||||||
participant.attribution_set
|
|
||||||
.filter(spectacle__date__gte=timezone.now())
|
|
||||||
.exclude(revente__seller=participant)
|
|
||||||
.select_related('spectacle', 'spectacle__location',
|
|
||||||
'participant__user')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AnnulForm(forms.Form):
|
|
||||||
reventes = ReventeModelMultipleChoiceField(
|
|
||||||
own=True,
|
|
||||||
label='',
|
|
||||||
queryset=Attribution.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['reventes'].queryset = (
|
|
||||||
participant.original_shows
|
|
||||||
.filter(attribution__spectacle__date__gte=timezone.now(),
|
|
||||||
notif_sent=False,
|
|
||||||
soldTo__isnull=True)
|
|
||||||
.select_related('attribution__spectacle',
|
|
||||||
'attribution__spectacle__location')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class InscriptionReventeForm(forms.Form):
|
|
||||||
spectacles = forms.ModelMultipleChoiceField(
|
|
||||||
queryset=Spectacle.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, tirage, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['spectacles'].queryset = (
|
|
||||||
tirage.spectacle_set
|
|
||||||
.select_related('location')
|
|
||||||
.filter(date__gte=timezone.now())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ReventeTirageAnnulForm(forms.Form):
|
|
||||||
reventes = ReventeModelMultipleChoiceField(
|
|
||||||
own=False,
|
|
||||||
label='',
|
|
||||||
queryset=SpectacleRevente.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['reventes'].queryset = (
|
|
||||||
participant.entered.filter(soldTo__isnull=True)
|
|
||||||
.select_related('attribution__spectacle',
|
|
||||||
'seller__user')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ReventeTirageForm(forms.Form):
|
|
||||||
reventes = ReventeModelMultipleChoiceField(
|
|
||||||
own=False,
|
|
||||||
label='',
|
|
||||||
queryset=SpectacleRevente.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['reventes'].queryset = (
|
|
||||||
SpectacleRevente.objects.filter(
|
|
||||||
notif_sent=True,
|
|
||||||
shotgun=False,
|
|
||||||
tirage_done=False
|
|
||||||
).exclude(confirmed_entry=participant)
|
|
||||||
.select_related('attribution__spectacle')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SoldForm(forms.Form):
|
|
||||||
reventes = ReventeModelMultipleChoiceField(
|
|
||||||
own=True,
|
|
||||||
label='',
|
|
||||||
queryset=Attribution.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.fields['reventes'].queryset = (
|
|
||||||
participant.original_shows
|
|
||||||
.filter(soldTo__isnull=False)
|
|
||||||
.exclude(soldTo=participant)
|
|
||||||
.select_related('attribution__spectacle',
|
|
||||||
'attribution__spectacle__location')
|
|
||||||
)
|
|
|
@ -1,107 +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 User
|
|
||||||
|
|
||||||
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 = []
|
|
||||||
for user in User.objects.filter(profile__is_cof=True):
|
|
||||||
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,51 +0,0 @@
|
||||||
"""
|
|
||||||
Gestion en ligne de commande des reventes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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:
|
|
||||||
# Le spectacle est bientôt et on a pas encore envoyé de mail :
|
|
||||||
# on met la place au shotgun et on prévient.
|
|
||||||
if revente.is_urgent and not revente.notif_sent:
|
|
||||||
if revente.can_notif:
|
|
||||||
self.stdout.write(str(now))
|
|
||||||
revente.mail_shotgun()
|
|
||||||
self.stdout.write(
|
|
||||||
"Mails de disponibilité immédiate envoyés "
|
|
||||||
"pour la revente [%s]" % revente
|
|
||||||
)
|
|
||||||
|
|
||||||
# Le spectacle est dans plus longtemps : on prévient
|
|
||||||
elif (revente.can_notif and not revente.notif_sent):
|
|
||||||
self.stdout.write(str(now))
|
|
||||||
revente.send_notif()
|
|
||||||
self.stdout.write(
|
|
||||||
"Mails d'inscription à la revente [%s] envoyés"
|
|
||||||
% revente
|
|
||||||
)
|
|
||||||
|
|
||||||
# On fait le tirage
|
|
||||||
elif (now >= revente.date_tirage and not revente.tirage_done):
|
|
||||||
self.stdout.write(str(now))
|
|
||||||
winner = revente.tirage()
|
|
||||||
self.stdout.write(
|
|
||||||
"Tirage effectué pour la revente [%s]"
|
|
||||||
% revente
|
|
||||||
)
|
|
||||||
|
|
||||||
if winner:
|
|
||||||
self.stdout.write("Gagnant : %s" % winner.user)
|
|
||||||
else:
|
|
||||||
self.stdout.write("Pas de gagnant ; place au shotgun")
|
|
|
@ -1,29 +0,0 @@
|
||||||
"""
|
|
||||||
Gestion en ligne de commande des mails de rappel.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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,108 +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(to='bda.Salle', on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
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(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='choixspectacle',
|
|
||||||
name='participant',
|
|
||||||
field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='choixspectacle',
|
|
||||||
name='spectacle',
|
|
||||||
field=models.ForeignKey(related_name='participants', to='bda.Spectacle', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='attribution',
|
|
||||||
name='participant',
|
|
||||||
field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='attribution',
|
|
||||||
name='spectacle',
|
|
||||||
field=models.ForeignKey(related_name='attribues', to='bda.Spectacle', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='choixspectacle',
|
|
||||||
unique_together=set([('participant', 'spectacle')]),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,84 +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 fill_tirage_fields(apps, schema_editor):
|
|
||||||
"""
|
|
||||||
Create a `Tirage` to fill new field `tirage` of `Participant`
|
|
||||||
and `Spectacle` already existing.
|
|
||||||
"""
|
|
||||||
Participant = apps.get_model("bda", "Participant")
|
|
||||||
Spectacle = apps.get_model("bda", "Spectacle")
|
|
||||||
Tirage = apps.get_model("bda", "Tirage")
|
|
||||||
|
|
||||||
# These querysets only contains instances not linked to any `Tirage`.
|
|
||||||
participants = Participant.objects.filter(tirage=None)
|
|
||||||
spectacles = Spectacle.objects.filter(tirage=None)
|
|
||||||
|
|
||||||
if not participants.count() and not spectacles.count():
|
|
||||||
# No need to create a "trash" tirage.
|
|
||||||
return
|
|
||||||
|
|
||||||
tirage = Tirage.objects.create(
|
|
||||||
title="Tirage de test (migration)",
|
|
||||||
active=False,
|
|
||||||
ouverture=timezone.now(),
|
|
||||||
fermeture=timezone.now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
participants.update(tirage=tirage)
|
|
||||||
spectacles.update(tirage=tirage)
|
|
||||||
|
|
||||||
|
|
||||||
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.AlterField(
|
|
||||||
model_name='participant',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
# Create fields `spectacle` for `Participant` and `Spectacle` models.
|
|
||||||
# These fields are not nullable, but we first create them as nullable
|
|
||||||
# to give a default value for existing instances of these models.
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='participant',
|
|
||||||
name='tirage',
|
|
||||||
field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='tirage',
|
|
||||||
field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='participant',
|
|
||||||
name='tirage',
|
|
||||||
field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='tirage',
|
|
||||||
field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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,91 +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(blank=True, to='bda.CategorieSpectacle',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
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(to='bda.Spectacle',
|
|
||||||
on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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,69 +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(to='bda.Participant',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name='Vendeur',
|
|
||||||
related_name='original_shows'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectaclerevente',
|
|
||||||
name='soldTo',
|
|
||||||
field=models.ForeignKey(to='bda.Participant',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
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),
|
|
||||||
]
|
|
|
@ -1,22 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0010_spectaclerevente_shotgun'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='tirage',
|
|
||||||
name='appear_catalogue',
|
|
||||||
field=models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
verbose_name='Tirage à afficher dans le catalogue'
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,29 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0011_tirage_appear_catalogue'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='spectaclerevente',
|
|
||||||
old_name='answered_mail',
|
|
||||||
new_name='confirmed_entry',
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='spectaclerevente',
|
|
||||||
name='confirmed_entry',
|
|
||||||
field=models.ManyToManyField(blank=True, related_name='entered', to='bda.Participant'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='spectaclerevente',
|
|
||||||
name='notif_time',
|
|
||||||
field=models.DateTimeField(blank=True, verbose_name="Moment d'envoi de la notification", null=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,53 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
def swap_double_choice(apps, schema_editor):
|
|
||||||
choices = apps.get_model("bda", "ChoixSpectacle").objects
|
|
||||||
|
|
||||||
choices.filter(double_choice="double").update(double_choice="tmp")
|
|
||||||
choices.filter(double_choice="autoquit").update(double_choice="double")
|
|
||||||
choices.filter(double_choice="tmp").update(double_choice="autoquit")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0011_tirage_appear_catalogue'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
# Temporarily allow an extra "tmp" value for the `double_choice` field
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='choixspectacle',
|
|
||||||
name='double_choice',
|
|
||||||
field=models.CharField(
|
|
||||||
verbose_name='Nombre de places',
|
|
||||||
max_length=10,
|
|
||||||
default='1',
|
|
||||||
choices=[
|
|
||||||
('tmp', 'tmp'),
|
|
||||||
('1', '1 place'),
|
|
||||||
('double', '2 places si possible, 1 sinon'),
|
|
||||||
('autoquit', '2 places sinon rien')
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.RunPython(swap_double_choice, migrations.RunPython.noop),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='choixspectacle',
|
|
||||||
name='double_choice',
|
|
||||||
field=models.CharField(
|
|
||||||
verbose_name='Nombre de places',
|
|
||||||
max_length=10,
|
|
||||||
default='1',
|
|
||||||
choices=[
|
|
||||||
('1', '1 place'),
|
|
||||||
('double', '2 places si possible, 1 sinon'),
|
|
||||||
('autoquit', '2 places sinon rien')
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,16 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.13 on 2018-05-24 19:23
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('bda', '0012_notif_time'),
|
|
||||||
('bda', '0012_swap_double_choice'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
]
|
|
442
bda/models.py
442
bda/models.py
|
@ -1,442 +0,0 @@
|
||||||
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.core import mail
|
|
||||||
from django.db import models
|
|
||||||
from django.db.models import Count
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone, formats
|
|
||||||
|
|
||||||
from custommail.models import CustomMail
|
|
||||||
|
|
||||||
|
|
||||||
def get_generic_user():
|
|
||||||
generic, _ = User.objects.get_or_create(
|
|
||||||
username="bda_generic",
|
|
||||||
defaults={"email": "bda@ens.fr", "first_name": "Bureau des arts"}
|
|
||||||
)
|
|
||||||
return generic
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
appear_catalogue = models.BooleanField(
|
|
||||||
"Tirage à afficher dans le catalogue",
|
|
||||||
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 getImgUrl(self):
|
|
||||||
"""
|
|
||||||
Cette fonction permet d'obtenir l'URL de l'image, si elle existe
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self.image.url
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
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 + le BdA
|
|
||||||
members = list(
|
|
||||||
User.objects
|
|
||||||
.filter(participant__attributions=self)
|
|
||||||
.annotate(nb_attr=Count("id")).order_by()
|
|
||||||
)
|
|
||||||
bda_generic = get_generic_user()
|
|
||||||
bda_generic.nb_attr = 1
|
|
||||||
members.append(bda_generic)
|
|
||||||
# On écrit un mail personnalisé à chaque participant
|
|
||||||
datatuple = [(
|
|
||||||
'bda-rappel',
|
|
||||||
{'member': member, "nb_attr": member.nb_attr, 'show': self},
|
|
||||||
settings.MAIL_DATA['rappels']['FROM'],
|
|
||||||
[member.email])
|
|
||||||
for member in members
|
|
||||||
]
|
|
||||||
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
|
|
||||||
|
|
||||||
@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"),
|
|
||||||
("double", "2 places si possible, 1 sinon"),
|
|
||||||
("autoquit", "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)
|
|
||||||
confirmed_entry = models.ManyToManyField(Participant,
|
|
||||||
related_name="entered",
|
|
||||||
blank=True)
|
|
||||||
seller = models.ForeignKey(
|
|
||||||
Participant, on_delete=models.CASCADE,
|
|
||||||
verbose_name="Vendeur",
|
|
||||||
related_name="original_shows",
|
|
||||||
)
|
|
||||||
soldTo = models.ForeignKey(
|
|
||||||
Participant, on_delete=models.CASCADE,
|
|
||||||
verbose_name="Vendue à",
|
|
||||||
blank=True, null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
notif_sent = models.BooleanField("Notification envoyée",
|
|
||||||
default=False)
|
|
||||||
|
|
||||||
notif_time = models.DateTimeField("Moment d'envoi de la notification",
|
|
||||||
blank=True, null=True)
|
|
||||||
|
|
||||||
tirage_done = models.BooleanField("Tirage effectué",
|
|
||||||
default=False)
|
|
||||||
|
|
||||||
shotgun = models.BooleanField("Disponible immédiatement",
|
|
||||||
default=False)
|
|
||||||
####
|
|
||||||
# Some class attributes
|
|
||||||
###
|
|
||||||
# TODO : settings ?
|
|
||||||
|
|
||||||
# Temps minimum entre le tirage et le spectacle
|
|
||||||
min_margin = timedelta(days=5)
|
|
||||||
|
|
||||||
# Temps entre la création d'une revente et l'envoi du mail
|
|
||||||
remorse_time = timedelta(hours=1)
|
|
||||||
|
|
||||||
# Temps min/max d'attente avant le tirage
|
|
||||||
max_wait_time = timedelta(days=3)
|
|
||||||
min_wait_time = timedelta(days=1)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def real_notif_time(self):
|
|
||||||
if self.notif_time:
|
|
||||||
return self.notif_time
|
|
||||||
else:
|
|
||||||
return self.date + self.remorse_time
|
|
||||||
|
|
||||||
@property
|
|
||||||
def date_tirage(self):
|
|
||||||
"""Renvoie la date du tirage au sort de la revente."""
|
|
||||||
|
|
||||||
remaining_time = (self.attribution.spectacle.date
|
|
||||||
- self.real_notif_time - self.min_margin)
|
|
||||||
|
|
||||||
delay = min(remaining_time, self.max_wait_time)
|
|
||||||
|
|
||||||
return self.real_notif_time + delay
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_urgent(self):
|
|
||||||
"""
|
|
||||||
Renvoie True iff la revente doit être mise au shotgun directement.
|
|
||||||
Plus précisément, on doit avoir min_margin + min_wait_time de marge.
|
|
||||||
"""
|
|
||||||
spectacle_date = self.attribution.spectacle.date
|
|
||||||
return (spectacle_date <= timezone.now() + self.min_margin
|
|
||||||
+ self.min_wait_time)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def can_notif(self):
|
|
||||||
return (timezone.now() >= self.date + self.remorse_time)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s -- %s" % (self.seller,
|
|
||||||
self.attribution.spectacle.title)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Revente"
|
|
||||||
|
|
||||||
def reset(self, new_date=timezone.now()):
|
|
||||||
"""Réinitialise la revente pour permettre une remise sur le marché"""
|
|
||||||
self.seller = self.attribution.participant
|
|
||||||
self.date = new_date
|
|
||||||
self.confirmed_entry.clear()
|
|
||||||
self.soldTo = None
|
|
||||||
self.notif_sent = False
|
|
||||||
self.notif_time = None
|
|
||||||
self.tirage_done = False
|
|
||||||
self.shotgun = False
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
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.notif_time = timezone.now()
|
|
||||||
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
|
|
||||||
self.notif_time = timezone.now()
|
|
||||||
# Flag inutile, sauf si l'horloge interne merde
|
|
||||||
self.tirage_done = True
|
|
||||||
self.shotgun = True
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def tirage(self, send_mails=True):
|
|
||||||
"""
|
|
||||||
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.confirmed_entry.all())
|
|
||||||
spectacle = self.attribution.spectacle
|
|
||||||
seller = self.seller
|
|
||||||
winner = None
|
|
||||||
|
|
||||||
if inscrits:
|
|
||||||
# Envoie un mail au gagnant et au vendeur
|
|
||||||
winner = random.choice(inscrits)
|
|
||||||
self.soldTo = winner
|
|
||||||
if send_mails:
|
|
||||||
mails = []
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'acheteur': winner.user,
|
|
||||||
'vendeur': seller.user,
|
|
||||||
'show': spectacle,
|
|
||||||
}
|
|
||||||
|
|
||||||
c_mails_qs = CustomMail.objects.filter(shortname__in=[
|
|
||||||
'bda-revente-winner', 'bda-revente-loser',
|
|
||||||
'bda-revente-seller',
|
|
||||||
])
|
|
||||||
|
|
||||||
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
|
||||||
|
|
||||||
mails.append(
|
|
||||||
c_mails['bda-revente-winner'].get_message(
|
|
||||||
context,
|
|
||||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
to=[winner.user.email],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
mails.append(
|
|
||||||
c_mails['bda-revente-seller'].get_message(
|
|
||||||
context,
|
|
||||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
to=[seller.user.email],
|
|
||||||
reply_to=[winner.user.email],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Envoie un mail aux perdants
|
|
||||||
for inscrit in inscrits:
|
|
||||||
if inscrit != winner:
|
|
||||||
new_context = dict(context)
|
|
||||||
new_context['acheteur'] = inscrit.user
|
|
||||||
|
|
||||||
mails.append(
|
|
||||||
c_mails['bda-revente-loser'].get_message(
|
|
||||||
new_context,
|
|
||||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
to=[inscrit.user.email],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
mail_conn = mail.get_connection()
|
|
||||||
mail_conn.send_messages(mails)
|
|
||||||
# Si personne ne veut de la place, elle part au shotgun
|
|
||||||
else:
|
|
||||||
self.shotgun = True
|
|
||||||
self.tirage_done = True
|
|
||||||
self.save()
|
|
||||||
return winner
|
|
|
@ -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,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,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,123 +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);
|
|
||||||
});
|
|
||||||
// Cloning <select> element doesn't properly propagate the default
|
|
||||||
// selected <option>, so we set it manually.
|
|
||||||
newElement.find('select').each(function (index, select) {
|
|
||||||
var defaultValue = $(select).find('option[selected]').val();
|
|
||||||
if (typeof defaultValue !== 'undefined') {
|
|
||||||
$(select).val(defaultValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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,48 +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>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="empty-form">
|
|
||||||
{% 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 %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr \>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<em>Note :</em> le template de ce mail peut être modifié à
|
|
||||||
<a href="{% url 'admin:custommail_custommail_change' custommail.pk %}">cette adresse</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<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,73 +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>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<button class="btn btn-default" type="button" onclick="toggle('export-mails')">Afficher/Cacher mails participants</button>
|
|
||||||
<pre id="export-mails" style="display:none">{% spaceless %}
|
|
||||||
{% for participant in participants %}{{ participant.email }}, {% endfor %}
|
|
||||||
{% endspaceless %}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
|
|
||||||
<pre id="export-salle" style="display:none">{% spaceless %}
|
|
||||||
{% for participant in participants %}{{ participant.name }} : {{ participant.nb_places }} place{{ participant.nb_places|pluralize }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endspaceless %}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<a href="{% url 'bda-rappels' spectacle.id %}">Page d'envoi manuel des mails de rappel</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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,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,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,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,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,90 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
|
|
||||||
<h2>Gestion des places que je revends</h2>
|
|
||||||
{% with resell_attributions=resellform.attributions annul_reventes=annulform.reventes sold_reventes=soldform.reventes %}
|
|
||||||
|
|
||||||
{% if resellform.attributions %}
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<h3>Places non revendues</h3>
|
|
||||||
<form class="form-horizontal" action="" method="post">
|
|
||||||
<div class="bg-info text-info center-block">
|
|
||||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
|
||||||
Cochez les places que vous souhaitez revendre, et validez. Vous aurez
|
|
||||||
ensuite 1h pour changer d'avis avant que la revente soit confirmée et
|
|
||||||
que les notifications soient envoyées aux intéressé·e·s.
|
|
||||||
</div>
|
|
||||||
<div class="bootstrap-form-reduce">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ resellform|bootstrap }}
|
|
||||||
</div>
|
|
||||||
<div class="form-actions">
|
|
||||||
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if annul_reventes or overdue %}
|
|
||||||
<h3>Places en cours de revente</h3>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% if annul_reventes %}
|
|
||||||
<div class="bg-info text-info center-block">
|
|
||||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
|
||||||
Vous pouvez annuler les places mises en vente il y a moins d'une heure.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class='form-group'>
|
|
||||||
<div class='multiple-checkbox'>
|
|
||||||
<ul>
|
|
||||||
{% for revente in annul_reventes %}
|
|
||||||
<li>{{ revente.tag }} {{ revente.choice_label }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% for attrib in overdue %}
|
|
||||||
<li>
|
|
||||||
<input type="checkbox" style="visibility:hidden">
|
|
||||||
{{ attrib.spectacle }}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if annul_reventes %}
|
|
||||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if sold_reventes %}
|
|
||||||
<h3>Places revendues</h3>
|
|
||||||
<form action="" method="post">
|
|
||||||
<div class="bg-info text-info center-block">
|
|
||||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
|
||||||
Pour chaque revente, vous devez soit l'annuler soit la confirmer pour
|
|
||||||
transférer la place la place à la personne tirée au sort.
|
|
||||||
|
|
||||||
L'annulation sert par exemple à pouvoir remettre la place en jeu si
|
|
||||||
vous ne parvenez pas à entrer en contact avec la personne tirée au
|
|
||||||
sort.
|
|
||||||
</div>
|
|
||||||
<div class="bootstrap-form-reduce">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ soldform|bootstrap }}
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary" name="transfer">Transférer</button>
|
|
||||||
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% if not resell_attributions and not annul_attributions and not overdue and not sold_reventes %}
|
|
||||||
<p>Plus de reventes possibles !</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endwith %}
|
|
||||||
{% 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,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-revente-buy" spectacle.id %}">{{spectacle}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<p> Pas de places disponibles immédiatement, désolé !</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -1,46 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Inscriptions pour BdA-Revente</h2>
|
|
||||||
<form action="" class="form-horizontal" method="post">
|
|
||||||
<div class="bg-info text-info center-block">
|
|
||||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
|
||||||
Cochez les spectacles pour lesquels vous souhaitez recevoir un
|
|
||||||
notification quand une place est disponible en revente. <br />
|
|
||||||
Lorsque vous validez vos choix, si un tirage au sort est en cours pour
|
|
||||||
un des spectacles que vous avez sélectionné, vous serez automatiquement
|
|
||||||
inscrit à ce tirage.
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<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,52 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
|
|
||||||
<h2>Tirages au sort de reventes</h2>
|
|
||||||
|
|
||||||
{% if annulform.reventes %}
|
|
||||||
<h3>Les reventes auxquelles vous êtes inscrit·e</h3>
|
|
||||||
<form class="form-horizontal" action="" method="post">
|
|
||||||
<div class="bg-info text-info center-block">
|
|
||||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
|
||||||
Vous pouvez vous désinscrire des reventes suivantes tant que le tirage n'a
|
|
||||||
pas eu lieu.
|
|
||||||
</div>
|
|
||||||
<div class="bootstrap-form-reduce">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ annulform|bootstrap }}
|
|
||||||
</div>
|
|
||||||
<div class="form-actions">
|
|
||||||
<input type="submit"
|
|
||||||
class="btn btn-primary"
|
|
||||||
name="annul"
|
|
||||||
value="Se désinscrire des tirages sélectionnés">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if subform.reventes %}
|
|
||||||
|
|
||||||
<h3>Tirages en cours</h3>
|
|
||||||
<form class="form-horizontal" action="" method="post">
|
|
||||||
<div class="bg-info text-info center-block">
|
|
||||||
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
|
||||||
Vous pouvez vous inscrire aux tirage en cours suivants.
|
|
||||||
</div>
|
|
||||||
<div class="bootstrap-form-reduce">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ subform|bootstrap }}
|
|
||||||
</div>
|
|
||||||
<div class="form-actions">
|
|
||||||
<input type="submit"
|
|
||||||
class="btn btn-primary"
|
|
||||||
name="subscribe"
|
|
||||||
value="S'inscrire aux tirages sélectionnés">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% 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-revente-buy" 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,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,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 %}
|
|
|
@ -1,100 +0,0 @@
|
||||||
from datetime import timedelta
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.core import mail
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from bda.models import (
|
|
||||||
Attribution, Participant, Salle, Spectacle, SpectacleRevente, Tirage,
|
|
||||||
)
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class SpectacleReventeTests(TestCase):
|
|
||||||
fixtures = ['cof/management/data/custommail.json']
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
now = timezone.now()
|
|
||||||
|
|
||||||
self.t = Tirage.objects.create(
|
|
||||||
title='Tirage',
|
|
||||||
ouverture=now - timedelta(days=7),
|
|
||||||
fermeture=now - timedelta(days=3),
|
|
||||||
active=True,
|
|
||||||
)
|
|
||||||
self.s = Spectacle.objects.create(
|
|
||||||
title='Spectacle',
|
|
||||||
date=now + timedelta(days=20),
|
|
||||||
location=Salle.objects.create(name='Salle', address='Address'),
|
|
||||||
price=10.5,
|
|
||||||
slots=5,
|
|
||||||
tirage=self.t,
|
|
||||||
listing=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.seller = Participant.objects.create(
|
|
||||||
user=User.objects.create(
|
|
||||||
username='seller', email='seller@mail.net'),
|
|
||||||
tirage=self.t,
|
|
||||||
)
|
|
||||||
self.p1 = Participant.objects.create(
|
|
||||||
user=User.objects.create(username='part1', email='part1@mail.net'),
|
|
||||||
tirage=self.t,
|
|
||||||
)
|
|
||||||
self.p2 = Participant.objects.create(
|
|
||||||
user=User.objects.create(username='part2', email='part2@mail.net'),
|
|
||||||
tirage=self.t,
|
|
||||||
)
|
|
||||||
self.p3 = Participant.objects.create(
|
|
||||||
user=User.objects.create(username='part3', email='part3@mail.net'),
|
|
||||||
tirage=self.t,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.attr = Attribution.objects.create(
|
|
||||||
participant=self.seller,
|
|
||||||
spectacle=self.s,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.rev = SpectacleRevente.objects.create(
|
|
||||||
attribution=self.attr,
|
|
||||||
seller=self.seller,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_tirage(self):
|
|
||||||
revente = self.rev
|
|
||||||
|
|
||||||
wanted_by = [self.p1, self.p2, self.p3]
|
|
||||||
revente.confirmed_entry = wanted_by
|
|
||||||
|
|
||||||
with mock.patch('bda.models.random.choice') as mc:
|
|
||||||
# Set winner to self.p1.
|
|
||||||
mc.return_value = self.p1
|
|
||||||
|
|
||||||
revente.tirage()
|
|
||||||
|
|
||||||
# Call to random.choice used participants in wanted_by.
|
|
||||||
mc_args, _ = mc.call_args
|
|
||||||
|
|
||||||
self.assertEqual(set(mc_args[0]), set(wanted_by))
|
|
||||||
|
|
||||||
self.assertEqual(revente.soldTo, self.p1)
|
|
||||||
self.assertTrue(revente.tirage_done)
|
|
||||||
|
|
||||||
mails = {m.to[0]: m for m in mail.outbox}
|
|
||||||
|
|
||||||
self.assertEqual(len(mails), 4)
|
|
||||||
|
|
||||||
m_seller = mails['seller@mail.net']
|
|
||||||
self.assertListEqual(m_seller.to, ['seller@mail.net'])
|
|
||||||
self.assertListEqual(m_seller.reply_to, ['part1@mail.net'])
|
|
||||||
|
|
||||||
m_winner = mails['part1@mail.net']
|
|
||||||
self.assertListEqual(m_winner.to, ['part1@mail.net'])
|
|
||||||
|
|
||||||
self.assertCountEqual(
|
|
||||||
[mails['part2@mail.net'].to, mails['part3@mail.net'].to],
|
|
||||||
[['part2@mail.net'], ['part3@mail.net']],
|
|
||||||
)
|
|
|
@ -1,69 +0,0 @@
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.test import TestCase, Client
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from bda.models import (Tirage, Spectacle, Salle, CategorieSpectacle,
|
|
||||||
SpectacleRevente, Attribution, Participant)
|
|
||||||
|
|
||||||
|
|
||||||
class TestModels(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.tirage = Tirage.objects.create(
|
|
||||||
title="Tirage test",
|
|
||||||
appear_catalogue=True,
|
|
||||||
ouverture=timezone.now(),
|
|
||||||
fermeture=timezone.now()
|
|
||||||
)
|
|
||||||
self.category = CategorieSpectacle.objects.create(name="Category")
|
|
||||||
self.location = Salle.objects.create(name="here")
|
|
||||||
self.spectacle_soon = Spectacle.objects.create(
|
|
||||||
title="foo", date=timezone.now()+timedelta(days=1),
|
|
||||||
location=self.location, price=0, slots=42,
|
|
||||||
tirage=self.tirage, listing=False, category=self.category
|
|
||||||
)
|
|
||||||
self.spectacle_later = Spectacle.objects.create(
|
|
||||||
title="bar", date=timezone.now()+timedelta(days=30),
|
|
||||||
location=self.location, price=0, slots=42,
|
|
||||||
tirage=self.tirage, listing=False, category=self.category
|
|
||||||
)
|
|
||||||
|
|
||||||
user_buyer = User.objects.create_user(
|
|
||||||
username="bda_buyer", password="testbuyer"
|
|
||||||
)
|
|
||||||
user_seller = User.objects.create_user(
|
|
||||||
username="bda_seller", password="testseller"
|
|
||||||
)
|
|
||||||
self.buyer = Participant.objects.create(
|
|
||||||
user=user_buyer, tirage=self.tirage
|
|
||||||
)
|
|
||||||
self.seller = Participant.objects.create(
|
|
||||||
user=user_seller, tirage=self.tirage
|
|
||||||
)
|
|
||||||
|
|
||||||
self.attr_soon = Attribution.objects.create(
|
|
||||||
participant=self.seller, spectacle=self.spectacle_soon
|
|
||||||
)
|
|
||||||
self.attr_later = Attribution.objects.create(
|
|
||||||
participant=self.seller, spectacle=self.spectacle_later
|
|
||||||
)
|
|
||||||
self.revente_soon = SpectacleRevente.objects.create(
|
|
||||||
seller=self.seller,
|
|
||||||
attribution=self.attr_soon
|
|
||||||
)
|
|
||||||
self.revente_later = SpectacleRevente.objects.create(
|
|
||||||
seller=self.seller,
|
|
||||||
attribution=self.attr_later
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_urgent(self):
|
|
||||||
self.assertTrue(self.revente_soon.is_urgent)
|
|
||||||
self.assertFalse(self.revente_later.is_urgent)
|
|
||||||
|
|
||||||
def test_tirage(self):
|
|
||||||
self.revente_soon.confirmed_entry.add(self.buyer)
|
|
||||||
|
|
||||||
self.assertEqual(self.revente_soon.tirage(send_mails=False),
|
|
||||||
self.buyer)
|
|
||||||
self.assertIsNone(self.revente_later.tirage(send_mails=False))
|
|
|
@ -1,105 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.test import TestCase, Client
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from bda.models import Tirage, Spectacle, Salle, CategorieSpectacle
|
|
||||||
|
|
||||||
|
|
||||||
class TestBdAViews(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.tirage = Tirage.objects.create(
|
|
||||||
title="Test tirage",
|
|
||||||
appear_catalogue=True,
|
|
||||||
ouverture=timezone.now(),
|
|
||||||
fermeture=timezone.now(),
|
|
||||||
)
|
|
||||||
self.category = CategorieSpectacle.objects.create(name="Category")
|
|
||||||
self.location = Salle.objects.create(name="here")
|
|
||||||
Spectacle.objects.bulk_create([
|
|
||||||
Spectacle(
|
|
||||||
title="foo", date=timezone.now(), location=self.location,
|
|
||||||
price=0, slots=42, tirage=self.tirage, listing=False,
|
|
||||||
category=self.category
|
|
||||||
),
|
|
||||||
Spectacle(
|
|
||||||
title="bar", date=timezone.now(), location=self.location,
|
|
||||||
price=1, slots=142, tirage=self.tirage, listing=False,
|
|
||||||
category=self.category
|
|
||||||
),
|
|
||||||
Spectacle(
|
|
||||||
title="baz", date=timezone.now(), location=self.location,
|
|
||||||
price=2, slots=242, tirage=self.tirage, listing=False,
|
|
||||||
category=self.category
|
|
||||||
),
|
|
||||||
])
|
|
||||||
|
|
||||||
self.bda_user = User.objects.create_user(
|
|
||||||
username="bda_user", password="bda4ever"
|
|
||||||
)
|
|
||||||
self.bda_user.profile.is_cof = True
|
|
||||||
self.bda_user.profile.is_buro = True
|
|
||||||
self.bda_user.profile.save()
|
|
||||||
|
|
||||||
def bda_participants(self):
|
|
||||||
"""The BdA participants views can be queried"""
|
|
||||||
client = Client()
|
|
||||||
show = self.tirage.spectacle_set.first()
|
|
||||||
|
|
||||||
client.login(self.bda_user.username, "bda4ever")
|
|
||||||
tirage_resp = client.get("/bda/spectacles/{}".format(self.tirage.id))
|
|
||||||
show_resp = client.get(
|
|
||||||
"/bda/spectacles/{}/{}".format(self.tirage.id, show.id)
|
|
||||||
)
|
|
||||||
reminder_url = "/bda/mails-rappel/{}".format(show.id)
|
|
||||||
reminder_get_resp = client.get(reminder_url)
|
|
||||||
reminder_post_resp = client.post(reminder_url)
|
|
||||||
self.assertEqual(200, tirage_resp.status_code)
|
|
||||||
self.assertEqual(200, show_resp.status_code)
|
|
||||||
self.assertEqual(200, reminder_get_resp.status_code)
|
|
||||||
self.assertEqual(200, reminder_post_resp.status_code)
|
|
||||||
|
|
||||||
def test_catalogue(self):
|
|
||||||
"""Test the catalogue JSON API"""
|
|
||||||
client = Client()
|
|
||||||
|
|
||||||
# The `list` hook
|
|
||||||
resp = client.get("/bda/catalogue/list")
|
|
||||||
self.assertJSONEqual(
|
|
||||||
resp.content.decode("utf-8"),
|
|
||||||
[{"id": self.tirage.id, "title": self.tirage.title}]
|
|
||||||
)
|
|
||||||
|
|
||||||
# The `details` hook
|
|
||||||
resp = client.get(
|
|
||||||
"/bda/catalogue/details?id={}".format(self.tirage.id)
|
|
||||||
)
|
|
||||||
self.assertJSONEqual(
|
|
||||||
resp.content.decode("utf-8"),
|
|
||||||
{
|
|
||||||
"categories": [{
|
|
||||||
"id": self.category.id,
|
|
||||||
"name": self.category.name
|
|
||||||
}],
|
|
||||||
"locations": [{
|
|
||||||
"id": self.location.id,
|
|
||||||
"name": self.location.name
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# The `descriptions` hook
|
|
||||||
resp = client.get(
|
|
||||||
"/bda/catalogue/descriptions?id={}".format(self.tirage.id)
|
|
||||||
)
|
|
||||||
raw = resp.content.decode("utf-8")
|
|
||||||
try:
|
|
||||||
results = json.loads(raw)
|
|
||||||
except ValueError:
|
|
||||||
self.fail("Not valid JSON: {}".format(raw))
|
|
||||||
self.assertEqual(len(results), 3)
|
|
||||||
self.assertEqual(
|
|
||||||
{(s["title"], s["price"], s["slots"]) for s in results},
|
|
||||||
{("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)}
|
|
||||||
)
|
|
62
bda/urls.py
62
bda/urls.py
|
@ -1,62 +0,0 @@
|
||||||
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'^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'^spectacles/autocomplete$',
|
|
||||||
views.spectacle_autocomplete,
|
|
||||||
name="bda-spectacle-autocomplete"),
|
|
||||||
url(r'^participants/autocomplete$',
|
|
||||||
views.participant_autocomplete,
|
|
||||||
name="bda-participant-autocomplete"),
|
|
||||||
|
|
||||||
# Urls BdA-Revente
|
|
||||||
|
|
||||||
url(r'^revente/(?P<tirage_id>\d+)/manage$',
|
|
||||||
views.revente_manage,
|
|
||||||
name='bda-revente-manage'),
|
|
||||||
url(r'^revente/(?P<tirage_id>\d+)/subscribe$',
|
|
||||||
views.revente_subscribe,
|
|
||||||
name="bda-revente-subscribe"),
|
|
||||||
url(r'^revente/(?P<tirage_id>\d+)/tirages$',
|
|
||||||
views.revente_tirages,
|
|
||||||
name="bda-revente-tirages"),
|
|
||||||
url(r'^revente/(?P<spectacle_id>\d+)/buy$',
|
|
||||||
views.revente_buy,
|
|
||||||
name="bda-revente-buy"),
|
|
||||||
url(r'^revente/(?P<revente_id>\d+)/confirm$',
|
|
||||||
views.revente_confirm,
|
|
||||||
name='bda-revente-confirm'),
|
|
||||||
url(r'^revente/(?P<tirage_id>\d+)/shotgun$',
|
|
||||||
views.revente_shotgun,
|
|
||||||
name="bda-revente-shotgun"),
|
|
||||||
|
|
||||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
|
|
||||||
views.send_rappel,
|
|
||||||
name="bda-rappels"
|
|
||||||
),
|
|
||||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
|
||||||
name='bda-descriptions'),
|
|
||||||
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
|
|
||||||
name='bda-catalogue'),
|
|
||||||
]
|
|
889
bda/views.py
889
bda/views.py
|
@ -1,889 +0,0 @@
|
||||||
from collections import defaultdict
|
|
||||||
import random
|
|
||||||
import hashlib
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
from custommail.shortcuts import send_mass_custom_mail, send_custom_mail
|
|
||||||
from custommail.models import CustomMail
|
|
||||||
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 transaction
|
|
||||||
from django.core import serializers
|
|
||||||
from django.db.models import Count, Q, Prefetch
|
|
||||||
from django.template.defaultfilters import pluralize
|
|
||||||
from django.forms.models import inlineformset_factory
|
|
||||||
from django.http import (
|
|
||||||
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
|
|
||||||
)
|
|
||||||
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, Salle, CategorieSpectacle
|
|
||||||
)
|
|
||||||
from bda.algorithm import Algorithm
|
|
||||||
from bda.forms import (
|
|
||||||
TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
|
|
||||||
InscriptionInlineFormSet, ReventeTirageForm, ReventeTirageAnnulForm
|
|
||||||
)
|
|
||||||
|
|
||||||
from utils.views.autocomplete import Select2QuerySetView
|
|
||||||
|
|
||||||
|
|
||||||
@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)
|
|
||||||
|
|
||||||
spectacles = tirage.spectacle_set.select_related('location')
|
|
||||||
spectacles_dict = {} # index of spectacle by id
|
|
||||||
|
|
||||||
for spectacle in spectacles:
|
|
||||||
spectacle.total = 0 # init total requests
|
|
||||||
spectacles_dict[spectacle.id] = spectacle
|
|
||||||
|
|
||||||
choices = (
|
|
||||||
ChoixSpectacle.objects
|
|
||||||
.filter(spectacle__in=spectacles)
|
|
||||||
.values('spectacle')
|
|
||||||
.annotate(total=Count('spectacle'))
|
|
||||||
)
|
|
||||||
|
|
||||||
# choices *by spectacles* whose only 1 place is requested
|
|
||||||
choices1 = choices.filter(double_choice="1")
|
|
||||||
# choices *by spectacles* whose 2 places is requested
|
|
||||||
choices2 = choices.exclude(double_choice="1")
|
|
||||||
|
|
||||||
for spectacle in choices1:
|
|
||||||
pk = spectacle['spectacle']
|
|
||||||
spectacles_dict[pk].total += spectacle['total']
|
|
||||||
for spectacle in choices2:
|
|
||||||
pk = spectacle['spectacle']
|
|
||||||
spectacles_dict[pk].total += 2*spectacle['total']
|
|
||||||
|
|
||||||
# here, each spectacle.total contains the number of requests
|
|
||||||
|
|
||||||
slots = 0 # proposed slots
|
|
||||||
total = 0 # requests
|
|
||||||
for spectacle in spectacles:
|
|
||||||
slots += spectacle.slots
|
|
||||||
total += spectacle.total
|
|
||||||
spectacle.ratio = spectacle.total / spectacle.slots
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"proposed": slots,
|
|
||||||
"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, _ = (
|
|
||||||
Participant.objects
|
|
||||||
.get_or_create(user=request.user, tirage=tirage)
|
|
||||||
)
|
|
||||||
places = (
|
|
||||||
participant.attribution_set
|
|
||||||
.order_by("spectacle__date", "spectacle")
|
|
||||||
.select_related("spectacle", "spectacle__location")
|
|
||||||
)
|
|
||||||
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', {})
|
|
||||||
|
|
||||||
participant, _ = (
|
|
||||||
Participant.objects.select_related('tirage')
|
|
||||||
.get_or_create(user=request.user, tirage=tirage)
|
|
||||||
)
|
|
||||||
|
|
||||||
if timezone.now() > tirage.fermeture:
|
|
||||||
# Le tirage est fermé.
|
|
||||||
choices = participant.choixspectacle_set.order_by("priority")
|
|
||||||
messages.error(request,
|
|
||||||
" C'est fini : tirage au sort dans la journée !")
|
|
||||||
return render(request, "bda/resume-inscription-tirage.html",
|
|
||||||
{"choices": choices})
|
|
||||||
|
|
||||||
BdaFormSet = inlineformset_factory(
|
|
||||||
Participant,
|
|
||||||
ChoixSpectacle,
|
|
||||||
fields=("spectacle", "double_choice", "priority"),
|
|
||||||
formset=InscriptionInlineFormSet,
|
|
||||||
)
|
|
||||||
|
|
||||||
success = False
|
|
||||||
stateerror = False
|
|
||||||
if request.method == "POST":
|
|
||||||
# use *this* queryset
|
|
||||||
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)
|
|
||||||
# use *this* queryset
|
|
||||||
dbstate = _hash_queryset(participant.choixspectacle_set.all())
|
|
||||||
total_price = 0
|
|
||||||
choices = (
|
|
||||||
participant.choixspectacle_set
|
|
||||||
.select_related('spectacle')
|
|
||||||
)
|
|
||||||
for choice in choices:
|
|
||||||
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('location'),
|
|
||||||
'token': token,
|
|
||||||
'members': tirage_elt.participant_set.select_related('user'),
|
|
||||||
'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('participant', 'participant__user', 'spectacle')
|
|
||||||
)
|
|
||||||
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
|
|
||||||
ChoixRevente = Participant.choicesrevente.through
|
|
||||||
|
|
||||||
# Suppression des reventes demandées/enregistrées
|
|
||||||
# (si le tirage est relancé)
|
|
||||||
(
|
|
||||||
ChoixRevente.objects
|
|
||||||
.filter(spectacle__tirage=tirage_elt)
|
|
||||||
.delete()
|
|
||||||
)
|
|
||||||
(
|
|
||||||
SpectacleRevente.objects
|
|
||||||
.filter(attribution__spectacle__tirage=tirage_elt)
|
|
||||||
.delete()
|
|
||||||
)
|
|
||||||
|
|
||||||
lost_by = defaultdict(set)
|
|
||||||
for show, _, losers in results:
|
|
||||||
for loser, _, _, _ in losers:
|
|
||||||
lost_by[loser].add(show)
|
|
||||||
|
|
||||||
ChoixRevente.objects.bulk_create(
|
|
||||||
ChoixRevente(participant=member, spectacle=show)
|
|
||||||
for member, shows in lost_by.items()
|
|
||||||
for show in shows
|
|
||||||
)
|
|
||||||
|
|
||||||
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_manage(request, tirage_id):
|
|
||||||
"""
|
|
||||||
Gestion de ses propres reventes :
|
|
||||||
- Création d'une revente
|
|
||||||
- Annulation d'une revente
|
|
||||||
- Confirmation d'une revente = transfert de la place à la personne qui
|
|
||||||
rachète
|
|
||||||
- Annulation d'une revente après que le tirage a eu lieu
|
|
||||||
"""
|
|
||||||
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/revente/notpaid.html", {})
|
|
||||||
|
|
||||||
resellform = ResellForm(participant, prefix='resell')
|
|
||||||
annulform = AnnulForm(participant, prefix='annul')
|
|
||||||
soldform = SoldForm(participant, prefix='sold')
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
# On met en vente une place
|
|
||||||
if 'resell' in request.POST:
|
|
||||||
resellform = ResellForm(participant, request.POST, prefix='resell')
|
|
||||||
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.reset()
|
|
||||||
|
|
||||||
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')
|
|
||||||
if annulform.is_valid():
|
|
||||||
reventes = annulform.cleaned_data["reventes"]
|
|
||||||
for revente in reventes:
|
|
||||||
revente.delete()
|
|
||||||
# On confirme une vente en transférant la place à la personne qui a
|
|
||||||
# gagné le tirage
|
|
||||||
elif 'transfer' in request.POST:
|
|
||||||
soldform = SoldForm(participant, request.POST, prefix='sold')
|
|
||||||
if soldform.is_valid():
|
|
||||||
reventes = soldform.cleaned_data['reventes']
|
|
||||||
for revente in reventes:
|
|
||||||
revente.attribution.participant = revente.soldTo
|
|
||||||
revente.attribution.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:
|
|
||||||
soldform = SoldForm(participant, request.POST, prefix='sold')
|
|
||||||
if soldform.is_valid():
|
|
||||||
reventes = soldform.cleaned_data['reventes']
|
|
||||||
for revente in reventes:
|
|
||||||
if revente.attribution.spectacle.date > timezone.now():
|
|
||||||
# On antidate pour envoyer le mail plus vite
|
|
||||||
new_date = (timezone.now()
|
|
||||||
- SpectacleRevente.remorse_time)
|
|
||||||
revente.reset(new_date=new_date)
|
|
||||||
|
|
||||||
overdue = participant.attribution_set.filter(
|
|
||||||
spectacle__date__gte=timezone.now(),
|
|
||||||
revente__isnull=False,
|
|
||||||
revente__seller=participant,
|
|
||||||
revente__notif_sent=True)\
|
|
||||||
.filter(
|
|
||||||
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
|
|
||||||
|
|
||||||
return render(request, "bda/revente/manage.html",
|
|
||||||
{'tirage': tirage, 'overdue': overdue, "soldform": soldform,
|
|
||||||
"annulform": annulform, "resellform": resellform})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def revente_tirages(request, tirage_id):
|
|
||||||
"""
|
|
||||||
Affiche à un participant la liste de toutes les reventes en cours (pour un
|
|
||||||
tirage donné) et lui permet de s'inscrire et se désinscrire à ces reventes.
|
|
||||||
"""
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
participant, _ = Participant.objects.get_or_create(
|
|
||||||
user=request.user, tirage=tirage)
|
|
||||||
subform = ReventeTirageForm(participant, prefix="subscribe")
|
|
||||||
annulform = ReventeTirageAnnulForm(participant, prefix="annul")
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
if "subscribe" in request.POST:
|
|
||||||
subform = ReventeTirageForm(participant, request.POST,
|
|
||||||
prefix="subscribe")
|
|
||||||
if subform.is_valid():
|
|
||||||
reventes = subform.cleaned_data['reventes']
|
|
||||||
count = reventes.count()
|
|
||||||
for revente in reventes:
|
|
||||||
revente.confirmed_entry.add(participant)
|
|
||||||
if count > 0:
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
"Tu as bien été inscrit à {} revente{}"
|
|
||||||
.format(count, pluralize(count))
|
|
||||||
)
|
|
||||||
elif "annul" in request.POST:
|
|
||||||
annulform = ReventeTirageAnnulForm(participant, request.POST,
|
|
||||||
prefix="annul")
|
|
||||||
if annulform.is_valid():
|
|
||||||
reventes = annulform.cleaned_data['reventes']
|
|
||||||
count = reventes.count()
|
|
||||||
for revente in reventes:
|
|
||||||
revente.confirmed_entry.remove(participant)
|
|
||||||
if count > 0:
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
"Tu as bien été désinscrit de {} revente{}"
|
|
||||||
.format(count, pluralize(count))
|
|
||||||
)
|
|
||||||
|
|
||||||
return render(request, "bda/revente/tirages.html",
|
|
||||||
{"annulform": annulform, "subform": subform})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def revente_confirm(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 not revente.notif_sent or revente.shotgun:
|
|
||||||
return render(request, "bda/revente/wrongtime.html",
|
|
||||||
{"revente": revente})
|
|
||||||
|
|
||||||
revente.confirmed_entry.add(participant)
|
|
||||||
return render(request, "bda/revente/confirmed.html",
|
|
||||||
{"spectacle": revente.attribution.spectacle,
|
|
||||||
"date": revente.date_tirage})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def revente_subscribe(request, tirage_id):
|
|
||||||
"""
|
|
||||||
Permet à un participant de sélectionner ses préférences pour les reventes.
|
|
||||||
Il recevra des notifications pour les spectacles qui l'intéressent et il
|
|
||||||
est automatiquement inscrit aux reventes en cours au moment où il ajoute un
|
|
||||||
spectacle à la liste des spectacles qui l'intéressent.
|
|
||||||
"""
|
|
||||||
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('confirmed_entry'))
|
|
||||||
.order_by('nb_subscribers')
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if min_resell is not None:
|
|
||||||
min_resell.confirmed_entry.add(participant)
|
|
||||||
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/revente/subscribe.html", {"form": form})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def revente_buy(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.
|
|
||||||
own_reventes = reventes.filter(seller=participant)
|
|
||||||
if len(own_reventes) > 0:
|
|
||||||
own_reventes[0].delete()
|
|
||||||
return HttpResponseRedirect(reverse("bda-revente-shotgun",
|
|
||||||
args=[tirage.id]))
|
|
||||||
|
|
||||||
reventes_shotgun = reventes.filter(shotgun=True)
|
|
||||||
|
|
||||||
if not reventes_shotgun:
|
|
||||||
return render(request, "bda/revente/none.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/revente/mail-success.html",
|
|
||||||
{"seller": revente.attribution.participant.user,
|
|
||||||
"spectacle": spectacle})
|
|
||||||
|
|
||||||
return render(request, "bda/revente/confirm-shotgun.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())
|
|
||||||
.select_related('location')
|
|
||||||
.prefetch_related(Prefetch(
|
|
||||||
'attribues',
|
|
||||||
queryset=(
|
|
||||||
Attribution.objects
|
|
||||||
.filter(revente__shotgun=True,
|
|
||||||
revente__soldTo__isnull=True)
|
|
||||||
),
|
|
||||||
to_attr='shotguns',
|
|
||||||
))
|
|
||||||
)
|
|
||||||
shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0]
|
|
||||||
|
|
||||||
return render(request, "bda/revente/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
|
|
||||||
.select_related('participant', 'participant__user')
|
|
||||||
)
|
|
||||||
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
|
|
||||||
.select_related('location')
|
|
||||||
)
|
|
||||||
return categories
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().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)
|
|
||||||
.select_related('user')
|
|
||||||
)
|
|
||||||
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
|
|
||||||
custommail = CustomMail.objects.get(shortname="bda-rappel")
|
|
||||||
exemple_mail_1place = custommail.render({
|
|
||||||
'member': request.user,
|
|
||||||
'show': show,
|
|
||||||
'nb_attr': 1
|
|
||||||
})
|
|
||||||
exemple_mail_2places = custommail.render({
|
|
||||||
'member': request.user,
|
|
||||||
'show': show,
|
|
||||||
'nb_attr': 2
|
|
||||||
})
|
|
||||||
# Contexte
|
|
||||||
ctxt = {
|
|
||||||
'show': show,
|
|
||||||
'exemple_mail_1place': exemple_mail_1place,
|
|
||||||
'exemple_mail_2places': exemple_mail_2places,
|
|
||||||
'custommail': custommail,
|
|
||||||
}
|
|
||||||
# Envoi confirmé
|
|
||||||
if request.method == 'POST':
|
|
||||||
members = show.send_rappel()
|
|
||||||
ctxt['sent'] = True
|
|
||||||
ctxt['members'] = members
|
|
||||||
# Demande de confirmation
|
|
||||||
else:
|
|
||||||
ctxt['sent'] = False
|
|
||||||
if show.rappel_sent:
|
|
||||||
messages.warning(
|
|
||||||
request,
|
|
||||||
"Attention, un mail de rappel pour ce spectale a déjà été "
|
|
||||||
"envoyé le {}".format(formats.localize(
|
|
||||||
timezone.template_localtime(show.rappel_sent)
|
|
||||||
))
|
|
||||||
)
|
|
||||||
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
|
|
||||||
.select_related('location')
|
|
||||||
.prefetch_related('quote_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})
|
|
||||||
|
|
||||||
|
|
||||||
def catalogue(request, request_type):
|
|
||||||
"""
|
|
||||||
Vue destinée à communiquer avec un client AJAX, fournissant soit :
|
|
||||||
- la liste des tirages
|
|
||||||
- les catégories et salles d'un tirage
|
|
||||||
- les descriptions d'un tirage (filtrées selon la catégorie et la salle)
|
|
||||||
"""
|
|
||||||
if request_type == "list":
|
|
||||||
# Dans ce cas on retourne la liste des tirages et de leur id en JSON
|
|
||||||
data_return = list(
|
|
||||||
Tirage.objects.filter(appear_catalogue=True).values('id', 'title')
|
|
||||||
)
|
|
||||||
return JsonResponse(data_return, safe=False)
|
|
||||||
if request_type == "details":
|
|
||||||
# Dans ce cas on retourne une liste des catégories et des salles
|
|
||||||
tirage_id = request.GET.get('id', None)
|
|
||||||
if tirage_id is None:
|
|
||||||
return HttpResponseBadRequest(
|
|
||||||
"Missing GET parameter: id <int>"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
tirage = get_object_or_404(Tirage, id=int(tirage_id))
|
|
||||||
except ValueError:
|
|
||||||
return HttpResponseBadRequest(
|
|
||||||
"Bad format: int expected for `id`"
|
|
||||||
)
|
|
||||||
shows = tirage.spectacle_set.values_list("id", flat=True)
|
|
||||||
categories = list(
|
|
||||||
CategorieSpectacle.objects
|
|
||||||
.filter(spectacle__in=shows)
|
|
||||||
.distinct()
|
|
||||||
.values('id', 'name')
|
|
||||||
)
|
|
||||||
locations = list(
|
|
||||||
Salle.objects
|
|
||||||
.filter(spectacle__in=shows)
|
|
||||||
.distinct()
|
|
||||||
.values('id', 'name')
|
|
||||||
)
|
|
||||||
data_return = {'categories': categories, 'locations': locations}
|
|
||||||
return JsonResponse(data_return, safe=False)
|
|
||||||
if request_type == "descriptions":
|
|
||||||
# Ici on retourne les descriptions correspondant à la catégorie et
|
|
||||||
# à la salle spécifiées
|
|
||||||
|
|
||||||
tirage_id = request.GET.get('id', '')
|
|
||||||
categories = request.GET.get('category', '[]')
|
|
||||||
locations = request.GET.get('location', '[]')
|
|
||||||
try:
|
|
||||||
tirage_id = int(tirage_id)
|
|
||||||
categories_id = json.loads(categories)
|
|
||||||
locations_id = json.loads(locations)
|
|
||||||
# Integers expected
|
|
||||||
if not all(isinstance(id, int) for id in categories_id):
|
|
||||||
raise ValueError
|
|
||||||
if not all(isinstance(id, int) for id in locations_id):
|
|
||||||
raise ValueError
|
|
||||||
except ValueError: # Contient JSONDecodeError
|
|
||||||
return HttpResponseBadRequest(
|
|
||||||
"Parse error, please ensure the GET parameters have the "
|
|
||||||
"following types:\n"
|
|
||||||
"id: int, category: [int], location: [int]\n"
|
|
||||||
"Data received:\n"
|
|
||||||
"id = {}, category = {}, locations = {}"
|
|
||||||
.format(request.GET.get('id', ''),
|
|
||||||
request.GET.get('category', '[]'),
|
|
||||||
request.GET.get('location', '[]'))
|
|
||||||
)
|
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
|
||||||
|
|
||||||
shows_qs = (
|
|
||||||
tirage.spectacle_set
|
|
||||||
.select_related('location')
|
|
||||||
.prefetch_related('quote_set')
|
|
||||||
)
|
|
||||||
if categories_id and 0 not in categories_id:
|
|
||||||
shows_qs = shows_qs.filter(category__id__in=categories_id)
|
|
||||||
if locations_id and 0 not in locations_id:
|
|
||||||
shows_qs = shows_qs.filter(location__id__in=locations_id)
|
|
||||||
|
|
||||||
# On convertit les descriptions à envoyer en une liste facilement
|
|
||||||
# JSONifiable (il devrait y avoir un moyen plus efficace en
|
|
||||||
# redéfinissant le serializer de JSON)
|
|
||||||
data_return = [{
|
|
||||||
'title': spectacle.title,
|
|
||||||
'category': str(spectacle.category),
|
|
||||||
'date': str(formats.date_format(
|
|
||||||
timezone.localtime(spectacle.date),
|
|
||||||
"SHORT_DATETIME_FORMAT")),
|
|
||||||
'location': str(spectacle.location),
|
|
||||||
'vips': spectacle.vips,
|
|
||||||
'description': spectacle.description,
|
|
||||||
'slots_description': spectacle.slots_description,
|
|
||||||
'quotes': [dict(author=quote.author,
|
|
||||||
text=quote.text)
|
|
||||||
for quote in spectacle.quote_set.all()],
|
|
||||||
'image': spectacle.getImgUrl(),
|
|
||||||
'ext_link': spectacle.ext_link,
|
|
||||||
'price': spectacle.price,
|
|
||||||
'slots': spectacle.slots
|
|
||||||
}
|
|
||||||
for spectacle in shows_qs
|
|
||||||
]
|
|
||||||
return JsonResponse(data_return, safe=False)
|
|
||||||
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Autocomplete views
|
|
||||||
#
|
|
||||||
# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view
|
|
||||||
##
|
|
||||||
|
|
||||||
|
|
||||||
class ParticipantAutocomplete(Select2QuerySetView):
|
|
||||||
model = Participant
|
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name')
|
|
||||||
|
|
||||||
|
|
||||||
participant_autocomplete = buro_required(ParticipantAutocomplete.as_view())
|
|
||||||
|
|
||||||
|
|
||||||
class SpectacleAutocomplete(Select2QuerySetView):
|
|
||||||
model = Spectacle
|
|
||||||
search_fields = ('title',)
|
|
||||||
|
|
||||||
|
|
||||||
spectacle_autocomplete = buro_required(SpectacleAutocomplete.as_view())
|
|
|
@ -1 +0,0 @@
|
||||||
default_app_config = 'cof.apps.CofConfig'
|
|
300
cof/admin.py
300
cof/admin.py
|
@ -1,300 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from cof.models import SurveyQuestionAnswer, SurveyQuestion, \
|
|
||||||
CofProfile, EventOption, EventOptionChoice, Event, Club, \
|
|
||||||
Survey, EventCommentField, EventRegistration
|
|
||||||
from cof.petits_cours_models import PetitCoursDemande, \
|
|
||||||
PetitCoursSubject, PetitCoursAbility, PetitCoursAttribution, \
|
|
||||||
PetitCoursAttributionCounter
|
|
||||||
from django.contrib.auth.models import User, Group, Permission
|
|
||||||
from django.contrib.auth.admin import UserAdmin
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from dal.autocomplete import ModelSelect2
|
|
||||||
|
|
||||||
|
|
||||||
def add_link_field(target_model='', field='', link_text=str,
|
|
||||||
desc_text=str):
|
|
||||||
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 CofProfileInline(admin.StackedInline):
|
|
||||||
model = CofProfile
|
|
||||||
inline_classes = ("collapse open",)
|
|
||||||
|
|
||||||
|
|
||||||
class FkeyLookup(object):
|
|
||||||
def __init__(self, fkeydecl, short_description=None,
|
|
||||||
admin_order_field=None):
|
|
||||||
self.fk, fkattrs = fkeydecl.split('__', 1)
|
|
||||||
self.fkattrs = fkattrs.split('__')
|
|
||||||
|
|
||||||
self.short_description = short_description or self.fkattrs[-1]
|
|
||||||
self.admin_order_field = admin_order_field or fkeydecl
|
|
||||||
|
|
||||||
def __get__(self, obj, klass):
|
|
||||||
if obj is None:
|
|
||||||
"""
|
|
||||||
hack required to make Django validate (if obj is
|
|
||||||
None, then we're a class, and classes are callable
|
|
||||||
<wink>)
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
item = getattr(obj, self.fk)
|
|
||||||
for attr in self.fkattrs:
|
|
||||||
item = getattr(item, attr)
|
|
||||||
return item
|
|
||||||
|
|
||||||
|
|
||||||
def ProfileInfo(field, short_description, boolean=False):
|
|
||||||
def getter(self):
|
|
||||||
try:
|
|
||||||
return getattr(self.profile, field)
|
|
||||||
except CofProfile.DoesNotExist:
|
|
||||||
return ""
|
|
||||||
getter.short_description = short_description
|
|
||||||
getter.boolean = boolean
|
|
||||||
return getter
|
|
||||||
|
|
||||||
User.profile_login_clipper = FkeyLookup("profile__login_clipper",
|
|
||||||
"Login clipper")
|
|
||||||
User.profile_phone = ProfileInfo("phone", "Téléphone")
|
|
||||||
User.profile_occupation = ProfileInfo("occupation", "Occupation")
|
|
||||||
User.profile_departement = ProfileInfo("departement", "Departement")
|
|
||||||
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
|
|
||||||
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True)
|
|
||||||
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente",
|
|
||||||
"ML BdA-R", True)
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfileAdmin(UserAdmin):
|
|
||||||
def is_buro(self, obj):
|
|
||||||
try:
|
|
||||||
return obj.profile.is_buro
|
|
||||||
except CofProfile.DoesNotExist:
|
|
||||||
return False
|
|
||||||
is_buro.short_description = 'Membre du Buro'
|
|
||||||
is_buro.boolean = True
|
|
||||||
|
|
||||||
def is_cof(self, obj):
|
|
||||||
try:
|
|
||||||
return obj.profile.is_cof
|
|
||||||
except CofProfile.DoesNotExist:
|
|
||||||
return False
|
|
||||||
is_cof.short_description = 'Membre du COF'
|
|
||||||
is_cof.boolean = True
|
|
||||||
|
|
||||||
list_display = (
|
|
||||||
UserAdmin.list_display
|
|
||||||
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation',
|
|
||||||
'profile_mailing_cof', 'profile_mailing_bda',
|
|
||||||
'profile_mailing_bda_revente', 'is_cof', 'is_buro', )
|
|
||||||
)
|
|
||||||
list_display_links = ('username', 'email', 'first_name', 'last_name')
|
|
||||||
list_filter = UserAdmin.list_filter \
|
|
||||||
+ ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
|
|
||||||
'profile__mailing_bda')
|
|
||||||
search_fields = UserAdmin.search_fields + ('profile__phone',)
|
|
||||||
inlines = [
|
|
||||||
CofProfileInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
staff_fieldsets = [
|
|
||||||
(None, {'fields': ['username', 'password']}),
|
|
||||||
(_('Personal info'), {'fields': ['first_name', 'last_name', 'email']}),
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_fieldsets(self, request, user=None):
|
|
||||||
if not request.user.is_superuser:
|
|
||||||
return self.staff_fieldsets
|
|
||||||
return super().get_fieldsets(request, user)
|
|
||||||
|
|
||||||
def save_model(self, request, user, form, change):
|
|
||||||
cof_group, created = Group.objects.get_or_create(name='COF')
|
|
||||||
if created:
|
|
||||||
# Si le groupe COF n'était pas déjà dans la bdd
|
|
||||||
# On lui assigne les bonnes permissions
|
|
||||||
perms = Permission.objects.filter(
|
|
||||||
Q(content_type__app_label='cof')
|
|
||||||
| Q(content_type__app_label='bda')
|
|
||||||
| (Q(content_type__app_label='auth')
|
|
||||||
& Q(content_type__model='user')))
|
|
||||||
cof_group.permissions = perms
|
|
||||||
# On y associe les membres du Burô
|
|
||||||
cof_group.user_set = User.objects.filter(profile__is_buro=True)
|
|
||||||
# Sauvegarde
|
|
||||||
cof_group.save()
|
|
||||||
# le Burô est staff et appartient au groupe COF
|
|
||||||
if user.profile.is_buro:
|
|
||||||
user.is_staff = True
|
|
||||||
user.groups.add(cof_group)
|
|
||||||
else:
|
|
||||||
user.is_staff = False
|
|
||||||
user.groups.remove(cof_group)
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: This is absolutely horrible.
|
|
||||||
def user_str(self):
|
|
||||||
if self.first_name and self.last_name:
|
|
||||||
return "{} ({})".format(self.get_full_name(), self.username)
|
|
||||||
else:
|
|
||||||
return self.username
|
|
||||||
User.__str__ = user_str
|
|
||||||
|
|
||||||
|
|
||||||
class EventRegistrationAdminForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
widgets = {
|
|
||||||
'user': ModelSelect2(url='cof-user-autocomplete'),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class EventRegistrationAdmin(admin.ModelAdmin):
|
|
||||||
form = EventRegistrationAdminForm
|
|
||||||
|
|
||||||
list_display = ('__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')
|
|
||||||
|
|
||||||
|
|
||||||
class ClubAdminForm(forms.ModelForm):
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super().clean()
|
|
||||||
respos = cleaned_data.get('respos')
|
|
||||||
members = cleaned_data.get('membres')
|
|
||||||
for respo in respos.all():
|
|
||||||
if respo not in members:
|
|
||||||
raise forms.ValidationError(
|
|
||||||
"Erreur : le respo %s n'est pas membre du club."
|
|
||||||
% respo.get_full_name())
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
class ClubAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['name']
|
|
||||||
form = ClubAdminForm
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Survey, SurveyAdmin)
|
|
||||||
admin.site.register(SurveyQuestion, SurveyQuestionAdmin)
|
|
||||||
admin.site.register(Event, EventAdmin)
|
|
||||||
admin.site.register(EventOption, EventOptionAdmin)
|
|
||||||
admin.site.unregister(User)
|
|
||||||
admin.site.register(User, UserProfileAdmin)
|
|
||||||
admin.site.register(CofProfile)
|
|
||||||
admin.site.register(Club, ClubAdmin)
|
|
||||||
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)
|
|
15
cof/apps.py
15
cof/apps.py
|
@ -1,15 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class CofConfig(AppConfig):
|
|
||||||
name = 'cof'
|
|
||||||
verbose_name = "Gestion des adhérents du COF"
|
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
from . import signals
|
|
||||||
self.register_config()
|
|
||||||
|
|
||||||
def register_config(self):
|
|
||||||
import djconfig
|
|
||||||
from .forms import CofConfigForm
|
|
||||||
djconfig.register(CofConfigForm)
|
|
|
@ -1,86 +0,0 @@
|
||||||
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
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from cof.models import CofProfile
|
|
||||||
from cof.decorators import buro_required
|
|
||||||
|
|
||||||
|
|
||||||
class Clipper(object):
|
|
||||||
def __init__(self, clipper, fullname):
|
|
||||||
if fullname is None:
|
|
||||||
fullname = ""
|
|
||||||
assert isinstance(clipper, str)
|
|
||||||
assert isinstance(fullname, str)
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
queries = {}
|
|
||||||
bits = q.split()
|
|
||||||
|
|
||||||
# Fetching data from User and CofProfile tables
|
|
||||||
queries['members'] = CofProfile.objects.filter(is_cof=True)
|
|
||||||
queries['users'] = User.objects.filter(profile__is_cof=False)
|
|
||||||
for bit in bits:
|
|
||||||
queries['members'] = queries['members'].filter(
|
|
||||||
Q(user__first_name__icontains=bit)
|
|
||||||
| Q(user__last_name__icontains=bit)
|
|
||||||
| Q(user__username__icontains=bit)
|
|
||||||
| Q(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('login_clipper', flat='True'))
|
|
||||||
| set(queries['users'].values_list('profile__login_clipper',
|
|
||||||
flat='True'))
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fetching data from the SPI
|
|
||||||
if getattr(settings, 'LDAP_SERVER_URL', None):
|
|
||||||
# Fetching
|
|
||||||
ldap_query = '(&{:s})'.format(''.join(
|
|
||||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
|
||||||
for bit in bits if bit.isalnum()
|
|
||||||
))
|
|
||||||
if ldap_query != "(&)":
|
|
||||||
# If none of the bits were legal, we do not perform the query
|
|
||||||
entries = None
|
|
||||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
|
||||||
conn.search(
|
|
||||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
|
||||||
attributes=['uid', 'cn']
|
|
||||||
)
|
|
||||||
entries = conn.entries
|
|
||||||
# Clearing redundancies
|
|
||||||
queries['clippers'] = [
|
|
||||||
Clipper(entry.uid.value, entry.cn.value)
|
|
||||||
for entry in entries
|
|
||||||
if entry.uid.value
|
|
||||||
and entry.uid.value not in usernames
|
|
||||||
]
|
|
||||||
|
|
||||||
# Resulting data
|
|
||||||
data.update(queries)
|
|
||||||
data['options'] = sum(len(query) for query in queries)
|
|
||||||
|
|
||||||
return shortcuts.render(request, "autocomplete_user.html", data)
|
|
|
@ -1,69 +0,0 @@
|
||||||
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,21 +0,0 @@
|
||||||
from django.contrib.auth.decorators import user_passes_test
|
|
||||||
|
|
||||||
|
|
||||||
def is_cof(user):
|
|
||||||
try:
|
|
||||||
profile = user.profile
|
|
||||||
return profile.is_cof
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
cof_required = user_passes_test(is_cof)
|
|
||||||
|
|
||||||
|
|
||||||
def is_buro(user):
|
|
||||||
try:
|
|
||||||
profile = user.profile
|
|
||||||
return profile.is_buro
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
buro_required = user_passes_test(is_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
|
|
||||||
}
|
|
||||||
]
|
|
394
cof/forms.py
394
cof/forms.py
|
@ -1,394 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
|
|
||||||
from django.forms.formsets import BaseFormSet, formset_factory
|
|
||||||
|
|
||||||
from djconfig.forms import ConfigForm
|
|
||||||
|
|
||||||
from cof.models import CofProfile, EventCommentValue, \
|
|
||||||
CalendarSubscription, Club
|
|
||||||
from cof.widgets import TriStateCheckbox
|
|
||||||
|
|
||||||
from bda.models import Spectacle
|
|
||||||
|
|
||||||
|
|
||||||
class EventForm(forms.Form):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
event = kwargs.pop("event")
|
|
||||||
self.event = event
|
|
||||||
current_choices = kwargs.pop("current_choices", None)
|
|
||||||
super().__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().__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().__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().__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 UserForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = ["first_name", "last_name", "email"]
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = CofProfile
|
|
||||||
fields = [
|
|
||||||
"phone",
|
|
||||||
"mailing_cof",
|
|
||||||
"mailing_bda",
|
|
||||||
"mailing_bda_revente",
|
|
||||||
"mailing_unernestaparis"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationUserForm(forms.ModelForm):
|
|
||||||
def __init__(self, *args, **kw):
|
|
||||||
super().__init__(*args, **kw)
|
|
||||||
self.fields['username'].help_text = ""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = ("username", "first_name", "last_name", "email")
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationPassUserForm(RegistrationUserForm):
|
|
||||||
"""
|
|
||||||
Formulaire pour changer le mot de passe d'un utilisateur.
|
|
||||||
"""
|
|
||||||
password1 = forms.CharField(label=_('Mot de passe'),
|
|
||||||
widget=forms.PasswordInput)
|
|
||||||
password2 = forms.CharField(label=_('Confirmation du mot de passe'),
|
|
||||||
widget=forms.PasswordInput)
|
|
||||||
|
|
||||||
def clean_password2(self):
|
|
||||||
pass1 = self.cleaned_data['password1']
|
|
||||||
pass2 = self.cleaned_data['password2']
|
|
||||||
if pass1 and pass2:
|
|
||||||
if pass1 != pass2:
|
|
||||||
raise forms.ValidationError(_('Mots de passe non identiques.'))
|
|
||||||
return pass2
|
|
||||||
|
|
||||||
def save(self, commit=True, *args, **kwargs):
|
|
||||||
user = super().save(commit, *args, **kwargs)
|
|
||||||
user.set_password(self.cleaned_data['password2'])
|
|
||||||
if commit:
|
|
||||||
user.save()
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationProfileForm(forms.ModelForm):
|
|
||||||
def __init__(self, *args, **kw):
|
|
||||||
super().__init__(*args, **kw)
|
|
||||||
self.fields['mailing_cof'].initial = True
|
|
||||||
self.fields['mailing_bda'].initial = True
|
|
||||||
self.fields['mailing_bda_revente'].initial = True
|
|
||||||
self.fields['mailing_unernestaparis'].initial = True
|
|
||||||
|
|
||||||
self.fields.keyOrder = [
|
|
||||||
'login_clipper',
|
|
||||||
'phone',
|
|
||||||
'occupation',
|
|
||||||
'departement',
|
|
||||||
'is_cof',
|
|
||||||
'type_cotiz',
|
|
||||||
'mailing_cof',
|
|
||||||
'mailing_bda',
|
|
||||||
'mailing_bda_revente',
|
|
||||||
"mailing_unernestaparis",
|
|
||||||
'comments'
|
|
||||||
]
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = CofProfile
|
|
||||||
fields = ("login_clipper", "phone", "occupation",
|
|
||||||
"departement", "is_cof", "type_cotiz", "mailing_cof",
|
|
||||||
"mailing_bda", "mailing_bda_revente",
|
|
||||||
"mailing_unernestaparis", "comments")
|
|
||||||
|
|
||||||
|
|
||||||
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().__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().__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()._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",
|
|
||||||
required=False)
|
|
||||||
subscribe_to_my_shows = forms.BooleanField(
|
|
||||||
initial=True,
|
|
||||||
label="Les spectacles pour lesquels j'ai obtenu une place",
|
|
||||||
required=False)
|
|
||||||
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']
|
|
||||||
|
|
||||||
|
|
||||||
class ClubsForm(forms.Form):
|
|
||||||
"""
|
|
||||||
Formulaire d'inscription d'un membre à plusieurs clubs du COF.
|
|
||||||
"""
|
|
||||||
clubs = forms.ModelMultipleChoiceField(
|
|
||||||
label="Inscriptions aux clubs du COF",
|
|
||||||
queryset=Club.objects.all(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# Announcements banner
|
|
||||||
# TODO: move this to the `gestion` app once the supportBDS branch is merged
|
|
||||||
# ---
|
|
||||||
|
|
||||||
class CofConfigForm(ConfigForm):
|
|
||||||
gestion_banner = forms.CharField(
|
|
||||||
label=_("Announcements banner"),
|
|
||||||
help_text=_("An empty banner disables annoucements"),
|
|
||||||
max_length=2048
|
|
||||||
)
|
|
|
@ -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,115 +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
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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:
|
|
||||||
user.profile.is_cof = True
|
|
||||||
user.profile.save()
|
|
||||||
|
|
||||||
# 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.profile.is_cof = True
|
|
||||||
root.profile.is_buro = True
|
|
||||||
root.profile.save()
|
|
||||||
root.save()
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# 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,87 +0,0 @@
|
||||||
"""
|
|
||||||
Import des mails de GestioCOF dans la base de donnée
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from custommail.models import Type, CustomMail, Variable
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = ("Va chercher les données mails de GestioCOF stocké au format json "
|
|
||||||
"dans /cof/management/data/custommails.json. Le format des "
|
|
||||||
"données est celui donné par la commande :"
|
|
||||||
" `python manage.py dumpdata custommail --natural-foreign` "
|
|
||||||
"La bonne façon de mettre à jour ce fichier est donc de le "
|
|
||||||
"charger à l'aide de syncmails, le faire les modifications à "
|
|
||||||
"l'aide de l'interface administration et/ou du shell puis de le "
|
|
||||||
"remplacer par le nouveau résultat de la commande précédente.")
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
path = os.path.join(
|
|
||||||
os.path.dirname(os.path.dirname(__file__)),
|
|
||||||
'data', 'custommail.json')
|
|
||||||
with open(path, 'r') as jsonfile:
|
|
||||||
mail_data = json.load(jsonfile)
|
|
||||||
|
|
||||||
# On se souvient à quel objet correspond quel pk du json
|
|
||||||
assoc = {'types': {}, 'mails': {}}
|
|
||||||
status = {'synced': 0, 'unchanged': 0}
|
|
||||||
|
|
||||||
for obj in mail_data:
|
|
||||||
fields = obj['fields']
|
|
||||||
|
|
||||||
# Pour les trois types d'objets :
|
|
||||||
# - On récupère les objets référencés par les clefs étrangères
|
|
||||||
# - On crée l'objet si nécessaire
|
|
||||||
# - On le stocke éventuellement dans les deux dictionnaires définis
|
|
||||||
# plus haut
|
|
||||||
|
|
||||||
# Variable types
|
|
||||||
if obj['model'] == 'custommail.variabletype':
|
|
||||||
fields['inner1'] = assoc['types'].get(fields['inner1'])
|
|
||||||
fields['inner2'] = assoc['types'].get(fields['inner2'])
|
|
||||||
if fields['kind'] == 'model':
|
|
||||||
fields['content_type'] = (
|
|
||||||
ContentType.objects
|
|
||||||
.get_by_natural_key(*fields['content_type'])
|
|
||||||
)
|
|
||||||
var_type, _ = Type.objects.get_or_create(**fields)
|
|
||||||
assoc['types'][obj['pk']] = var_type
|
|
||||||
|
|
||||||
# Custom mails
|
|
||||||
if obj['model'] == 'custommail.custommail':
|
|
||||||
mail = None
|
|
||||||
try:
|
|
||||||
mail = CustomMail.objects.get(
|
|
||||||
shortname=fields['shortname'])
|
|
||||||
status['unchanged'] += 1
|
|
||||||
except CustomMail.DoesNotExist:
|
|
||||||
mail = CustomMail.objects.create(**fields)
|
|
||||||
status['synced'] += 1
|
|
||||||
if options['verbosity']:
|
|
||||||
self.stdout.write(
|
|
||||||
'SYNCED {:s}'.format(fields['shortname']))
|
|
||||||
assoc['mails'][obj['pk']] = mail
|
|
||||||
|
|
||||||
# Variables
|
|
||||||
if obj['model'] == 'custommail.custommailvariable':
|
|
||||||
fields['custommail'] = assoc['mails'].get(fields['custommail'])
|
|
||||||
fields['type'] = assoc['types'].get(fields['type'])
|
|
||||||
try:
|
|
||||||
Variable.objects.get(
|
|
||||||
custommail=fields['custommail'],
|
|
||||||
name=fields['name']
|
|
||||||
)
|
|
||||||
except Variable.DoesNotExist:
|
|
||||||
Variable.objects.create(**fields)
|
|
||||||
|
|
||||||
if options['verbosity']:
|
|
||||||
# C'est agréable d'avoir le résultat affiché
|
|
||||||
self.stdout.write(
|
|
||||||
'{synced:d} mails synchronized {unchanged:d} unchanged'
|
|
||||||
.format(**status)
|
|
||||||
)
|
|
|
@ -1,600 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "model",
|
|
||||||
"content_type": [
|
|
||||||
"auth",
|
|
||||||
"user"
|
|
||||||
],
|
|
||||||
"inner1": null,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "int",
|
|
||||||
"content_type": null,
|
|
||||||
"inner1": null,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "model",
|
|
||||||
"content_type": [
|
|
||||||
"bda",
|
|
||||||
"spectacle"
|
|
||||||
],
|
|
||||||
"inner1": null,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "model",
|
|
||||||
"content_type": [
|
|
||||||
"bda",
|
|
||||||
"spectaclerevente"
|
|
||||||
],
|
|
||||||
"inner1": null,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "model",
|
|
||||||
"content_type": [
|
|
||||||
"sites",
|
|
||||||
"site"
|
|
||||||
],
|
|
||||||
"inner1": null,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "model",
|
|
||||||
"content_type": [
|
|
||||||
"cof",
|
|
||||||
"petitcoursdemande"
|
|
||||||
],
|
|
||||||
"inner1": null,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "list",
|
|
||||||
"content_type": null,
|
|
||||||
"inner1": 12,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "list",
|
|
||||||
"content_type": null,
|
|
||||||
"inner1": 1,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "pair",
|
|
||||||
"content_type": null,
|
|
||||||
"inner1": 12,
|
|
||||||
"inner2": 8
|
|
||||||
},
|
|
||||||
"pk": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "list",
|
|
||||||
"content_type": null,
|
|
||||||
"inner1": 9,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "list",
|
|
||||||
"content_type": null,
|
|
||||||
"inner1": 3,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.type",
|
|
||||||
"fields": {
|
|
||||||
"kind": "model",
|
|
||||||
"content_type": [
|
|
||||||
"cof",
|
|
||||||
"petitcourssubject"
|
|
||||||
],
|
|
||||||
"inner1": null,
|
|
||||||
"inner2": null
|
|
||||||
},
|
|
||||||
"pk": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "welcome",
|
|
||||||
"subject": "Bienvenue au COF",
|
|
||||||
"body": "Bonjour {{ member.first_name }} et bienvenue au COF !\r\n\r\nTu trouveras plein de trucs cool sur le site du COF : https://www.cof.ens.fr/ et notre page Facebook : https://www.facebook.com/cof.ulm\r\nEt n'oublie pas d'aller d\u00e9couvrir GestioCOF, la plateforme de gestion du COF !\r\nSi tu as des questions, tu peux nous envoyer un mail \u00e0 cof@ens.fr (on aime le spam), ou passer nous voir au Bur\u00f4 pr\u00e8s de la Cour\u00f4 du lundi au vendredi de 12h \u00e0 14h et de 18h \u00e0 20h.\r\n\r\nRetrouvez les \u00e9v\u00e8nements de rentr\u00e9e pour les conscrit.e.s et les vieux/vieilles organis\u00e9s par le COF et ses clubs ici : http://www.cof.ens.fr/depot/Rentree.pdf \r\n\r\nAmicalement,\r\n\r\nTon COF qui t'aime.",
|
|
||||||
"description": "Mail de bienvenue au COF envoy\u00e9 automatiquement \u00e0 l'inscription d'un nouveau membre"
|
|
||||||
},
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-rappel",
|
|
||||||
"subject": "{{ show }}",
|
|
||||||
"body": "Bonjour {{ member.first_name }},\r\n\r\nNous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:\"une place,deux places\" }}\r\npour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !\r\n{% if nb_attr == 2 %}\r\nTu as obtenu deux places pour ce spectacle. Nous te rappelons que\r\nces places sont strictement r\u00e9serv\u00e9es aux personnes de moins de 28 ans.\r\n{% endif %}\r\n{% if show.listing %}Pour ce spectacle, tu as re\u00e7u des places sur\r\nlisting. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la repr\u00e9sentation\r\npour retirer {{ nb_attr|pluralize:\"ta place,tes places\" }}.\r\n{% else %}Pour assister \u00e0 ce spectacle, tu dois pr\u00e9senter les billets qui ont\r\n\u00e9t\u00e9 distribu\u00e9s au bur\u00f4.\r\n{% endif %}\r\n\r\nSi tu ne peux plus assister \u00e0 cette repr\u00e9sentation, tu peux\r\nrevendre ta place via BdA-revente, accessible directement sur\r\nGestioCOF (lien \"revendre une place du premier tirage\" sur la page\r\nd'accueil https://www.cof.ens.fr/gestion/).\r\n\r\nEn te souhaitant un excellent spectacle,\r\n\r\nLe Bureau des Arts",
|
|
||||||
"description": "Mail de rappel pour les spectacles BdA"
|
|
||||||
},
|
|
||||||
"pk": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-revente",
|
|
||||||
"subject": "{{ show }}",
|
|
||||||
"description": "Notification envoy\u00e9e \u00e0 toutes les personnes int\u00e9ress\u00e9es par un spectacle pour leur signaler qu'une place vient d'\u00eatre mise en vente.",
|
|
||||||
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nSi ce spectacle t'int\u00e9resse toujours, merci de nous le signaler en cliquant\r\nsur ce lien : http://{{ site }}{% url \"bda-revente-confirm\" revente.id %}.\r\nDans le cas o\u00f9 plusieurs personnes seraient int\u00e9ress\u00e9es, nous proc\u00e8derons \u00e0\r\nun tirage au sort le {{ revente.date_tirage|date:\"DATE_FORMAT\" }}.\r\n\r\nChaleureusement,\r\nLe BdA"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-shotgun",
|
|
||||||
"subject": "{{ show }}",
|
|
||||||
"description": "Notification signalant qu'une place est au shotgun aux personnes int\u00e9ress\u00e9es.",
|
|
||||||
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nPuisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour\r\ncette place : elle est disponible imm\u00e9diatement \u00e0 l'adresse\r\nhttp://{{ site }}{% url \"bda-revente-buy\" show.id %}, \u00e0 la disposition de tous.\r\n\r\nChaleureusement,\r\nLe BdA"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-revente-winner",
|
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
|
||||||
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu as \u00e9t\u00e9 tir\u00e9-e au sort pour racheter une place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nTu peux contacter le/la vendeur-se \u00e0 l'adresse {{ vendeur.email }}.\r\n\r\nChaleureusement,\r\nLe BdA",
|
|
||||||
"description": "Mail envoy\u00e9 au gagnant d'un tirage BdA-Revente"
|
|
||||||
},
|
|
||||||
"pk": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-revente-loser",
|
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
|
||||||
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu t'\u00e9tais inscrit-e pour la revente de la place de {{ vendeur.get_full_name }}\r\npour {{ show.title }}.\r\nMalheureusement, une autre personne a \u00e9t\u00e9 tir\u00e9e au sort pour racheter la place.\r\nTu pourras certainement retenter ta chance pour une autre revente !\r\n\r\n\u00c0 tr\u00e8s bient\u00f4t,\r\nLe Bureau des Arts",
|
|
||||||
"description": "Notification envoy\u00e9e aux perdants d'un tirage de revente."
|
|
||||||
},
|
|
||||||
"pk": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-revente-seller",
|
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
|
||||||
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nLa personne tir\u00e9e au sort pour racheter ta place pour {{ show.title }} est {{ acheteur.get_full_name }}.\r\nTu peux le/la contacter \u00e0 l'adresse {{ acheteur.email }}, ou en r\u00e9pondant \u00e0 ce mail.\r\n\r\nChaleureusement,\r\nLe BdA",
|
|
||||||
"description": "Notification envoy\u00e9e au vendeur d'une place pour lui indiquer qu'elle vient d'\u00eatre attribu\u00e9e"
|
|
||||||
},
|
|
||||||
"pk": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-revente-new",
|
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
|
||||||
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nTu t\u2019es bien inscrit-e pour la revente de {{ show.title }}.\r\n\r\n{% with revente.date_tirage as time %}\r\nLe tirage au sort entre tout-e-s les racheteuse-eur-s potentiel-le-s aura lieu\r\nle {{ time|date:\"DATE_FORMAT\" }} \u00e0 {{ time|time:\"TIME_FORMAT\" }} (dans {{time|timeuntil }}).\r\nSi personne ne s\u2019est inscrit pour racheter la place, celle-ci apparaitra parmi\r\nles \u00ab Places disponibles imm\u00e9diatement \u00e0 la revente \u00bb sur GestioCOF.\r\n{% endwith %}\r\n\r\nBonne revente !\r\nLe Bureau des Arts",
|
|
||||||
"description": "Notification signalant au vendeur d'une place que sa mise en vente a bien eu lieu et lui donnant quelques informations compl\u00e9mentaires."
|
|
||||||
},
|
|
||||||
"pk": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-buy-shotgun",
|
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
|
||||||
"body": "Bonjour {{ vendeur.first_name }} !\r\n\r\nJe souhaiterais racheter ta place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nContacte-moi si tu es toujours int\u00e9ress\u00e9\u00b7e !\r\n\r\n{{ acheteur.get_full_name }} ({{ acheteur.email }})",
|
|
||||||
"description": "Mail envoy\u00e9 au revendeur lors d'un achat au shotgun."
|
|
||||||
},
|
|
||||||
"pk": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "petit-cours-mail-eleve",
|
|
||||||
"subject": "Petits cours ENS par le COF",
|
|
||||||
"body": "Salut,\r\n\r\nLe COF a re\u00e7u une demande de petit cours qui te correspond. Tu es en haut de la liste d'attente donc on a transmis tes coordonn\u00e9es, ainsi que celles de 2 autres qui correspondaient aussi (c'est la vie, on donne les num\u00e9ros 3 par 3 pour que ce soit plus souple). Voici quelques infos sur l'annonce en question :\r\n\r\n\u00a4 Nom : {{ demande.name }}\r\n\r\n\u00a4 P\u00e9riode : {{ demande.quand }}\r\n\r\n\u00a4 Fr\u00e9quence : {{ demande.freq }}\r\n\r\n\u00a4 Lieu (si pr\u00e9f\u00e9r\u00e9) : {{ demande.lieu }}\r\n\r\n\u00a4 Niveau : {{ demande.get_niveau_display }}\r\n\r\n\u00a4 Remarques diverses (d\u00e9sol\u00e9 pour les balises HTML) : {{ demande.remarques }}\r\n\r\n{% if matieres|length > 1 %}\u00a4 Mati\u00e8res :\r\n{% for matiere in matieres %} \u00a4 {{ matiere }}\r\n{% endfor %}{% else %}\u00a4 Mati\u00e8re : {% for matiere in matieres %}{{ matiere }}\r\n{% endfor %}{% endif %}\r\nVoil\u00e0, cette personne te contactera peut-\u00eatre sous peu, tu pourras voir les d\u00e9tails directement avec elle (prix, modalit\u00e9s, ...). Pour indication, 30 Euro/h semble \u00eatre la moyenne.\r\n\r\nSi tu te rends compte qu'en fait tu ne peux pas/plus donner de cours en ce moment, \u00e7a serait cool que tu d\u00e9coches la case \"Recevoir des propositions de petits cours\" sur GestioCOF. Ensuite d\u00e8s que tu voudras r\u00e9appara\u00eetre tu pourras recocher la case et tu seras \u00e0 nouveau sur la liste.\r\n\r\n\u00c0 bient\u00f4t,\r\n\r\n--\r\nLe COF, pour les petits cours",
|
|
||||||
"description": "Mail envoy\u00e9 aux personnes dont ont a donn\u00e9 les contacts \u00e0 des demandeurs de petits cours"
|
|
||||||
},
|
|
||||||
"pk": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "petits-cours-mail-demandeur",
|
|
||||||
"subject": "Cours particuliers ENS",
|
|
||||||
"body": "Bonjour,\r\n\r\nJe vous contacte au sujet de votre annonce pass\u00e9e sur le site du COF pour rentrer en contact avec un \u00e9l\u00e8ve normalien pour des cours particuliers. Voici les coordonn\u00e9es d'\u00e9l\u00e8ves qui sont motiv\u00e9s par de tels cours et correspondent aux crit\u00e8res que vous nous aviez transmis :\r\n\r\n{% for matiere, proposed in proposals %}\u00a4 {{ matiere }} :{% for user in proposed %}\r\n \u00a4 {{ user.get_full_name }}{% if user.profile.phone %}, {{ user.profile.phone }}{% endif %}{% if user.email %}, {{ user.email }}{% endif %}{% endfor %}\r\n\r\n{% endfor %}{% if unsatisfied %}Nous n'avons cependant pas pu trouver d'\u00e9l\u00e8ve disponible pour des cours de {% for matiere in unsatisfied %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}.\r\n\r\n{% endif %}Si pour une raison ou une autre ces num\u00e9ros ne suffisaient pas, n'h\u00e9sitez pas \u00e0 r\u00e9pondre \u00e0 cet e-mail et je vous en ferai parvenir d'autres sans probl\u00e8me.\r\n{% if extra|length > 0 %}\r\n{{ extra|safe }}\r\n{% endif %}\r\nCordialement,\r\n\r\n--\r\nLe COF, BdE de l'ENS",
|
|
||||||
"description": "Mail envoy\u00e9 aux personnes qui demandent des petits cours lorsque leur demande est trait\u00e9e.\r\n\r\n(Ne pas toucher \u00e0 {{ extra|safe }})"
|
|
||||||
},
|
|
||||||
"pk": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-attributions",
|
|
||||||
"subject": "R\u00e9sultats du tirage au sort",
|
|
||||||
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Tu as \u00e9t\u00e9 s\u00e9lectionn\u00e9-e\r\npour les spectacles suivants :\r\n{% for place in places %}\r\n- 1 place pour {{ place }}{% endfor %}\r\n\r\n*Paiement*\r\nL'int\u00e9gralit\u00e9 de ces places de spectacles est \u00e0 r\u00e9gler d\u00e8s maintenant et AVANT\r\nvendredi prochain, au bureau du COF pendant les heures de permanences (du lundi au vendredi\r\nentre 12h et 14h, et entre 18h et 20h). Des facilit\u00e9s de paiement sont bien\r\n\u00e9videmment possibles : nous pouvons ne pas encaisser le ch\u00e8que imm\u00e9diatement,\r\nou bien d\u00e9couper votre paiement en deux fois. Pour ceux qui ne pourraient pas\r\nvenir payer au bureau, merci de nous contacter par mail.\r\n\r\n*Mode de retrait des places*\r\nAu moment du paiement, certaines places vous seront remises directement,\r\nd'autres seront \u00e0 r\u00e9cup\u00e9rer au cours de l'ann\u00e9e, d'autres encore seront\r\nnominatives et \u00e0 retirer le soir m\u00eame dans les the\u00e2tres correspondants.\r\nPour chaque spectacle, vous recevrez un mail quelques jours avant la\r\nrepr\u00e9sentation vous indiquant le mode de retrait.\r\n\r\nNous vous rappelons que l'obtention de places du BdA vous engage \u00e0\r\nrespecter les r\u00e8gles de fonctionnement :\r\nhttp://www.cof.ens.fr/bda/?page_id=1370\r\nUn syst\u00e8me de revente des places via les mails BdA-revente disponible\r\ndirectement sur votre compte GestioCOF.\r\n\r\nEn vous souhaitant de tr\u00e8s beaux spectacles tout au long de l'ann\u00e9e,\r\n--\r\nLe Bureau des Arts",
|
|
||||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux gagnants d'une ou plusieurs places"
|
|
||||||
},
|
|
||||||
"pk": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommail",
|
|
||||||
"fields": {
|
|
||||||
"shortname": "bda-attributions-decus",
|
|
||||||
"subject": "R\u00e9sultats du tirage au sort",
|
|
||||||
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as\r\nobtenu aucune place.\r\n\r\nNous proposons cependant de nombreuses offres hors-tirage tout au long de\r\nl'ann\u00e9e, et nous t'invitons \u00e0 nous contacter si l'une d'entre elles\r\nt'int\u00e9resse !\r\n--\r\nLe Bureau des Arts",
|
|
||||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux personnes n'ayant pas obtenu de place"
|
|
||||||
},
|
|
||||||
"pk": 13
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 1,
|
|
||||||
"type": 1,
|
|
||||||
"name": "member",
|
|
||||||
"description": "Utilisateur de GestioCOF"
|
|
||||||
},
|
|
||||||
"pk": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 2,
|
|
||||||
"type": 1,
|
|
||||||
"name": "member",
|
|
||||||
"description": "Utilisateur ayant eu une place pour ce spectacle"
|
|
||||||
},
|
|
||||||
"pk": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 2,
|
|
||||||
"type": 3,
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle"
|
|
||||||
},
|
|
||||||
"pk": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 2,
|
|
||||||
"type": 2,
|
|
||||||
"name": "nb_attr",
|
|
||||||
"description": "Nombre de places obtenues"
|
|
||||||
},
|
|
||||||
"pk": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 3,
|
|
||||||
"type": 4,
|
|
||||||
"name": "revente",
|
|
||||||
"description": "Revente mentionn\u00e9e dans le mail"
|
|
||||||
},
|
|
||||||
"pk": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 3,
|
|
||||||
"type": 1,
|
|
||||||
"name": "member",
|
|
||||||
"description": "Personne int\u00e9ress\u00e9e par la place"
|
|
||||||
},
|
|
||||||
"pk": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 3,
|
|
||||||
"type": 3,
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle"
|
|
||||||
},
|
|
||||||
"pk": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 3,
|
|
||||||
"type": 5,
|
|
||||||
"name": "site",
|
|
||||||
"description": "Site web (gestioCOF)"
|
|
||||||
},
|
|
||||||
"pk": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 4,
|
|
||||||
"type": 5,
|
|
||||||
"name": "site",
|
|
||||||
"description": "Site web (gestioCOF)"
|
|
||||||
},
|
|
||||||
"pk": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 4,
|
|
||||||
"type": 3,
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle"
|
|
||||||
},
|
|
||||||
"pk": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 4,
|
|
||||||
"type": 1,
|
|
||||||
"name": "member",
|
|
||||||
"description": "Personne int\u00e9ress\u00e9e par la place"
|
|
||||||
},
|
|
||||||
"pk": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 5,
|
|
||||||
"type": 1,
|
|
||||||
"name": "acheteur",
|
|
||||||
"description": "Gagnant-e du tirage"
|
|
||||||
},
|
|
||||||
"pk": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 5,
|
|
||||||
"type": 1,
|
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend une place"
|
|
||||||
},
|
|
||||||
"pk": 13
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 5,
|
|
||||||
"type": 3,
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle"
|
|
||||||
},
|
|
||||||
"pk": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 6,
|
|
||||||
"type": 3,
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle"
|
|
||||||
},
|
|
||||||
"pk": 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 6,
|
|
||||||
"type": 1,
|
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend une place"
|
|
||||||
},
|
|
||||||
"pk": 16
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 6,
|
|
||||||
"type": 1,
|
|
||||||
"name": "acheteur",
|
|
||||||
"description": "Personne inscrite au tirage qui n'a pas eu la place"
|
|
||||||
},
|
|
||||||
"pk": 17
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 7,
|
|
||||||
"type": 1,
|
|
||||||
"name": "acheteur",
|
|
||||||
"description": "Gagnant-e du tirage"
|
|
||||||
},
|
|
||||||
"pk": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 7,
|
|
||||||
"type": 1,
|
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend une place"
|
|
||||||
},
|
|
||||||
"pk": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 7,
|
|
||||||
"type": 3,
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle"
|
|
||||||
},
|
|
||||||
"pk": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 8,
|
|
||||||
"type": 3,
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle"
|
|
||||||
},
|
|
||||||
"pk": 21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 8,
|
|
||||||
"type": 1,
|
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend la place"
|
|
||||||
},
|
|
||||||
"pk": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 8,
|
|
||||||
"type": 4,
|
|
||||||
"name": "revente",
|
|
||||||
"description": "Revente mentionn\u00e9e dans le mail"
|
|
||||||
},
|
|
||||||
"pk": 23
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 9,
|
|
||||||
"type": 1,
|
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend la place"
|
|
||||||
},
|
|
||||||
"pk": 24
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 9,
|
|
||||||
"type": 3,
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle"
|
|
||||||
},
|
|
||||||
"pk": 25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 9,
|
|
||||||
"type": 1,
|
|
||||||
"name": "acheteur",
|
|
||||||
"description": "Personne qui prend la place au shotgun"
|
|
||||||
},
|
|
||||||
"pk": 26
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 10,
|
|
||||||
"type": 6,
|
|
||||||
"name": "demande",
|
|
||||||
"description": "Demande de petit cours"
|
|
||||||
},
|
|
||||||
"pk": 27
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 10,
|
|
||||||
"type": 7,
|
|
||||||
"name": "matieres",
|
|
||||||
"description": "Liste des mati\u00e8res concern\u00e9es par la demande"
|
|
||||||
},
|
|
||||||
"pk": 28
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 11,
|
|
||||||
"type": 10,
|
|
||||||
"name": "proposals",
|
|
||||||
"description": "Liste associant une liste d'enseignants \u00e0 chaque mati\u00e8re"
|
|
||||||
},
|
|
||||||
"pk": 29
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 11,
|
|
||||||
"type": 7,
|
|
||||||
"name": "unsatisfied",
|
|
||||||
"description": "Liste des mati\u00e8res pour lesquelles on n'a pas d'enseigant \u00e0 proposer"
|
|
||||||
},
|
|
||||||
"pk": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 12,
|
|
||||||
"type": 11,
|
|
||||||
"name": "places",
|
|
||||||
"description": "Places de spectacle du participant"
|
|
||||||
},
|
|
||||||
"pk": 31
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 12,
|
|
||||||
"type": 1,
|
|
||||||
"name": "member",
|
|
||||||
"description": "Participant du tirage au sort"
|
|
||||||
},
|
|
||||||
"pk": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.variable",
|
|
||||||
"fields": {
|
|
||||||
"custommail": 13,
|
|
||||||
"type": 1,
|
|
||||||
"name": "member",
|
|
||||||
"description": "Participant du tirage au sort"
|
|
||||||
},
|
|
||||||
"pk": 33
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -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,333 +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', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
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', to='cof.Event', on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
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', to='cof.EventCommentField', on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
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', to='cof.Event', on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
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', to='cof.EventOption', on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
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(to='cof.Event', on_delete=models.CASCADE)),
|
|
||||||
('filledcomments', models.ManyToManyField(to='cof.EventCommentField', through='cof.EventCommentValue')),
|
|
||||||
('options', models.ManyToManyField(to='cof.EventOptionChoice')),
|
|
||||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
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(related_name='questions', to='cof.Survey', on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
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', to='cof.SurveyQuestion', on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
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(to='cof.Survey', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='surveyanswer',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
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(blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattributioncounter',
|
|
||||||
name='matiere',
|
|
||||||
field=models.ForeignKey(verbose_name='Matiere', to='cof.PetitCoursSubject', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattributioncounter',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattribution',
|
|
||||||
name='demande',
|
|
||||||
field=models.ForeignKey(verbose_name='Demande', to='cof.PetitCoursDemande', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattribution',
|
|
||||||
name='matiere',
|
|
||||||
field=models.ForeignKey(verbose_name='Mati\xe8re', to='cof.PetitCoursSubject', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursattribution',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursability',
|
|
||||||
name='matiere',
|
|
||||||
field=models.ForeignKey(verbose_name='Mati\xe8re', to='cof.PetitCoursSubject', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='petitcoursability',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='eventcommentvalue',
|
|
||||||
name='registration',
|
|
||||||
field=models.ForeignKey(related_name='comments', to='cof.EventRegistration', on_delete=models.CASCADE),
|
|
||||||
),
|
|
||||||
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,51 +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(to=settings.AUTH_USER_MODEL,
|
|
||||||
on_delete=models.CASCADE)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
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,17 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0008_py3'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='Clipper',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,16 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0009_delete_clipper'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='CustomMail',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,19 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0010_delete_custommail'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='login_clipper',
|
|
||||||
field=models.CharField(verbose_name='Login clipper', blank=True, max_length=32),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,15 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0011_remove_cofprofile_num'),
|
|
||||||
('cof', '0011_longer_clippers'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
]
|
|
|
@ -1,47 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0012_merge'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='occupation',
|
|
||||||
field=models.CharField(
|
|
||||||
verbose_name='Occupation',
|
|
||||||
max_length=9,
|
|
||||||
default='1A',
|
|
||||||
choices=[
|
|
||||||
('exterieur', 'Extérieur'),
|
|
||||||
('1A', '1A'),
|
|
||||||
('2A', '2A'),
|
|
||||||
('3A', '3A'),
|
|
||||||
('4A', '4A'),
|
|
||||||
('archicube', 'Archicube'),
|
|
||||||
('doctorant', 'Doctorant'),
|
|
||||||
('CST', 'CST'),
|
|
||||||
('PEI', 'PEI')
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='type_cotiz',
|
|
||||||
field=models.CharField(
|
|
||||||
verbose_name='Type de cotisation',
|
|
||||||
max_length=9,
|
|
||||||
default='normalien',
|
|
||||||
choices=[
|
|
||||||
('etudiant', 'Normalien étudiant'),
|
|
||||||
('normalien', 'Normalien élève'),
|
|
||||||
('exterieur', 'Extérieur'),
|
|
||||||
('gratis', 'Gratuit')
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.15 on 2018-09-02 21:13
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('cof', '0013_pei'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='cofprofile',
|
|
||||||
name='mailing_unernestaparis',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Recevoir les mails unErnestAParis'),
|
|
||||||
),
|
|
||||||
]
|
|
280
cof/models.py
280
cof/models.py
|
@ -1,280 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.db.models.signals import post_save, post_delete
|
|
||||||
|
|
||||||
from cof.petits_cours_models import choices_length
|
|
||||||
|
|
||||||
from bda.models import Spectacle
|
|
||||||
|
|
||||||
TYPE_COMMENT_FIELD = (
|
|
||||||
('text', _("Texte long")),
|
|
||||||
('char', _("Texte court")),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CofProfile(models.Model):
|
|
||||||
STATUS_EXTE = "exterieur"
|
|
||||||
STATUS_1A = "1A"
|
|
||||||
STATUS_2A = "2A"
|
|
||||||
STATUS_3A = "3A"
|
|
||||||
STATUS_4A = "4A"
|
|
||||||
STATUS_ARCHI = "archicube"
|
|
||||||
STATUS_DOCTORANT = "doctorant"
|
|
||||||
STATUS_CST = "CST"
|
|
||||||
STATUS_PEI = "PEI"
|
|
||||||
|
|
||||||
OCCUPATION_CHOICES = (
|
|
||||||
(STATUS_EXTE, _("Extérieur")),
|
|
||||||
(STATUS_1A, _("1A")),
|
|
||||||
(STATUS_2A, _("2A")),
|
|
||||||
(STATUS_3A, _("3A")),
|
|
||||||
(STATUS_4A, _("4A")),
|
|
||||||
(STATUS_ARCHI, _("Archicube")),
|
|
||||||
(STATUS_DOCTORANT, _("Doctorant")),
|
|
||||||
(STATUS_CST, _("CST")),
|
|
||||||
(STATUS_PEI, _("PEI")),
|
|
||||||
)
|
|
||||||
|
|
||||||
COTIZ_ETUDIANT = "etudiant"
|
|
||||||
COTIZ_NORMALIEN = "normalien"
|
|
||||||
COTIZ_EXTE = "exterieur"
|
|
||||||
COTIZ_GRATIS = "gratis"
|
|
||||||
|
|
||||||
TYPE_COTIZ_CHOICES = (
|
|
||||||
(COTIZ_ETUDIANT, _("Normalien étudiant")),
|
|
||||||
(COTIZ_NORMALIEN, _("Normalien élève")),
|
|
||||||
(COTIZ_EXTE, _("Extérieur")),
|
|
||||||
(COTIZ_GRATIS, _("Gratuit")),
|
|
||||||
)
|
|
||||||
|
|
||||||
user = models.OneToOneField(
|
|
||||||
User, on_delete=models.CASCADE,
|
|
||||||
related_name="profile",
|
|
||||||
)
|
|
||||||
login_clipper = models.CharField(
|
|
||||||
"Login clipper", max_length=32, blank=True
|
|
||||||
)
|
|
||||||
is_cof = models.BooleanField("Membre du COF", default=False)
|
|
||||||
phone = models.CharField("Téléphone", max_length=20, blank=True)
|
|
||||||
occupation = models.CharField(_("Occupation"),
|
|
||||||
default="1A",
|
|
||||||
choices=OCCUPATION_CHOICES,
|
|
||||||
max_length=choices_length(
|
|
||||||
OCCUPATION_CHOICES))
|
|
||||||
departement = models.CharField(_("Département"), max_length=50,
|
|
||||||
blank=True)
|
|
||||||
type_cotiz = models.CharField(_("Type de cotisation"),
|
|
||||||
default="normalien",
|
|
||||||
choices=TYPE_COTIZ_CHOICES,
|
|
||||||
max_length=choices_length(
|
|
||||||
TYPE_COTIZ_CHOICES))
|
|
||||||
mailing_cof = models.BooleanField("Recevoir les mails COF", default=False)
|
|
||||||
mailing_bda = models.BooleanField("Recevoir les mails BdA", default=False)
|
|
||||||
mailing_unernestaparis = models.BooleanField("Recevoir les mails unErnestAParis", default=False)
|
|
||||||
mailing_bda_revente = models.BooleanField(
|
|
||||||
"Recevoir les mails de revente de places BdA", default=False)
|
|
||||||
comments = models.TextField(
|
|
||||||
"Commentaires visibles par l'utilisateur", blank=True)
|
|
||||||
is_buro = models.BooleanField("Membre du Burô", 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"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.user.username
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
|
||||||
def create_user_profile(sender, instance, created, **kwargs):
|
|
||||||
if created:
|
|
||||||
CofProfile.objects.get_or_create(user=instance)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=CofProfile)
|
|
||||||
def post_delete_user(sender, instance, *args, **kwargs):
|
|
||||||
instance.user.delete()
|
|
||||||
|
|
||||||
|
|
||||||
class Club(models.Model):
|
|
||||||
name = models.CharField("Nom", max_length=200, unique=True)
|
|
||||||
description = models.TextField("Description", blank=True)
|
|
||||||
respos = models.ManyToManyField(User, related_name="clubs_geres",
|
|
||||||
blank=True)
|
|
||||||
membres = models.ManyToManyField(User, related_name="clubs", blank=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
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 self.title
|
|
||||||
|
|
||||||
|
|
||||||
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 self.name
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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 self.name
|
|
||||||
|
|
||||||
|
|
||||||
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 self.value
|
|
||||||
|
|
||||||
|
|
||||||
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 {} à {}".format(self.user, self.event.title)
|
|
||||||
|
|
||||||
|
|
||||||
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 self.title
|
|
||||||
|
|
||||||
|
|
||||||
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 self.question
|
|
||||||
|
|
||||||
|
|
||||||
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 self.answer
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
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,52 +0,0 @@
|
||||||
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 cof.petits_cours_models import PetitCoursDemande, PetitCoursAbility
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMatieresFormSet(BaseInlineFormSet):
|
|
||||||
def clean(self):
|
|
||||||
super().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().__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
|
|
||||||
)
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue