Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
006367b678 | ||
|
|
b810dd4417 | ||
|
|
15267a5ddf | ||
|
|
eeb1e4712b | ||
|
|
c973e9720e | ||
|
|
91cd10c4a7 | ||
|
|
4b25db2b42 | ||
|
|
4bdbf2b709 | ||
|
|
e9481b3e52 | ||
|
|
a613b53668 | ||
|
|
54d1d7bbd4 | ||
|
|
c5e105efda | ||
|
|
63a0ac1891 | ||
|
|
7175ce9558 | ||
|
|
f3fcf1c3ab | ||
|
|
f7839c51d6 | ||
|
|
8007ce3d2f | ||
|
|
2daeb7d63c | ||
|
|
9e7c0517df | ||
|
|
9f71f86fd9 | ||
|
|
1191c9693e | ||
|
|
41a8404032 | ||
|
|
a6b56a884e | ||
|
|
9863ecd8ed | ||
|
|
5f03ce7acb | ||
|
|
e6b5553d9e | ||
|
|
6be77fd2e8 | ||
|
|
68988aced4 | ||
|
|
577d8e1299 | ||
|
|
e8e9d41ca8 | ||
|
|
f7e0d19487 | ||
|
|
9830032180 | ||
|
|
872d588236 | ||
|
|
6eaa3b6c1b | ||
|
|
dc079e12b7 | ||
|
|
66140bd9d8 | ||
|
|
2923f0d919 | ||
|
|
93559da21f | ||
|
|
3ac77e20db | ||
|
|
50dee524a1 | ||
|
|
8964d77b87 | ||
|
|
09a82422c0 | ||
|
|
a210eb71d4 | ||
|
|
deeccbc83b | ||
|
|
0507212300 | ||
|
|
899a3d8cff | ||
|
|
675de13e6b | ||
|
|
25a6e11d66 | ||
|
|
33bf57dffa | ||
|
|
9381429cd6 | ||
|
|
4205eca24f | ||
|
|
d5e25f4992 | ||
|
|
32d9b73a42 | ||
|
|
591efd6b12 | ||
|
|
61dfbdb586 | ||
|
|
e9c1627941 | ||
|
|
91b799e4ab | ||
|
|
74ca478d49 | ||
|
|
6d2d14b3ca | ||
|
|
dc1c19eca0 | ||
|
|
fe29ed60d1 | ||
|
|
c5780a0df1 | ||
|
|
0b930e5547 | ||
|
|
9988097988 | ||
|
|
b1d1d50fac | ||
|
|
967b838d06 | ||
|
|
db8b1efcea | ||
|
|
ed0c72250a | ||
|
|
163baaf69d | ||
|
|
fc18e2cb40 | ||
|
|
94c295b4c8 | ||
|
|
5a84b0851e | ||
|
|
6042ce5ed3 | ||
|
|
061b91577a | ||
|
|
53d59f96f4 | ||
|
|
94fbf0b230 | ||
|
|
92b48869a9 | ||
|
|
32ba23ed6f | ||
|
|
dacc0ec9dc | ||
|
|
38e517b12f | ||
|
|
6cf1661396 | ||
|
|
917d768e4a | ||
|
|
b50dd7001d | ||
|
|
8cbb02c453 | ||
|
|
4c0539d2aa | ||
|
|
7429a71414 | ||
|
|
9ba5a857ae | ||
|
|
91fb1f19a7 | ||
|
|
17fc98d4d2 | ||
|
|
7fbb344766 | ||
|
|
569e5dc5bf | ||
|
|
9652ab4a9c | ||
|
|
7f91044b4a | ||
|
|
65a3127894 | ||
|
|
7a915f5b35 | ||
|
|
7d7d4336ba | ||
|
|
ee4e3e49cd | ||
|
|
6c8ebd802f | ||
|
|
b03ccef0c5 | ||
|
|
fd5956bef4 | ||
|
|
e02c29bde7 | ||
|
|
bf6fb4be5a | ||
|
|
d5daf79e9e | ||
|
|
fe553bcc45 | ||
|
|
bdfe0412ae | ||
|
|
0067571e25 | ||
|
|
cd12c2f6ad | ||
|
|
4d720a3c1f | ||
|
|
52d31944af | ||
|
|
53d33b73b3 | ||
|
|
dce435c4c3 | ||
|
|
2d024c799e | ||
|
|
ddd3921528 | ||
|
|
1a95aaa35b | ||
|
|
103574dca9 | ||
|
|
e1bb8d5a1a | ||
|
|
9c567c2f6b | ||
|
|
a5b521c5a7 | ||
|
|
c80cd1fdc6 | ||
|
|
161e8531dd | ||
|
|
cfee4ecdf5 | ||
|
|
6fb9ab0612 | ||
|
|
0aa2f9eb43 | ||
|
|
067460447d | ||
|
|
1d5d902ae5 | ||
|
|
53e4e88707 | ||
|
|
84ccbaf562 | ||
|
|
fbcf6d6a01 | ||
|
|
d3eb7b9d80 | ||
|
|
1f78303c1e | ||
|
|
fb276127bc | ||
|
|
f83fce559e | ||
|
|
1ff5bf9149 | ||
|
|
bd7a63bc6f | ||
|
|
0daae01f2f | ||
|
|
d7ee89da24 | ||
|
|
1b0eebfe51 | ||
|
|
454e070fd4 | ||
|
|
630a50361a | ||
|
|
e7bba3abae | ||
|
|
b7247b6004 | ||
|
|
bf27dcf8bc | ||
|
|
e1feb9cba0 | ||
|
|
52fb7e00e7 | ||
|
|
f18f993c04 | ||
|
|
3807136508 | ||
|
|
d19dfdd960 | ||
|
|
2159767efc | ||
|
|
06fd906e39 | ||
|
|
a4ff7062b9 | ||
|
|
53b218c4c5 | ||
|
|
2011892309 | ||
|
|
39442083f1 | ||
|
|
ba16eb9a1d | ||
|
|
19804319f8 | ||
|
|
a970819be8 | ||
|
|
270509699e | ||
|
|
47c71409bc | ||
|
|
da37f26fbf | ||
|
|
0f2428611a | ||
|
|
d9aac20865 | ||
|
|
e07e26c0b6 | ||
|
|
418b7f9844 | ||
|
|
df1edc14b5 | ||
|
|
a50a8945b5 | ||
|
|
ffa4c36a1a | ||
|
|
42946981cf | ||
|
|
b5070833f9 | ||
|
|
c9661ea79b | ||
|
|
7ed2907bbc | ||
|
|
70d1929954 | ||
|
|
cfbafa2963 | ||
|
|
a03c7ebcae | ||
|
|
5f2a19cf04 | ||
|
|
8c11e0df2b | ||
|
|
f8bb876268 | ||
|
|
d503b0fb39 | ||
|
|
9acbf6a281 | ||
|
|
e9d419f609 | ||
|
|
a019a48d72 | ||
|
|
fb77d60279 | ||
|
|
f3978baa56 | ||
|
|
30680ac0ea | ||
|
|
8ccafc3d46 | ||
|
|
f5ae6de1be | ||
|
|
4f62056f13 | ||
|
|
16b6dd86cd | ||
|
|
07c7572a75 | ||
|
|
85991b59f0 | ||
|
|
c0c1677d10 | ||
|
|
ec086bc771 | ||
|
|
9361aca524 | ||
|
|
acfa6889df | ||
|
|
70b88a24b2 | ||
|
|
f7af69f3e1 | ||
|
|
58afb80bb4 | ||
|
|
b5e344f9ed | ||
|
|
6be269707e | ||
|
|
d5ce5413af | ||
|
|
27071952c8 | ||
|
|
644c04a60f | ||
|
|
07ad704c27 | ||
|
|
6a426c23f8 | ||
|
|
5eb177fed1 | ||
|
|
2013321a14 | ||
|
|
4d1e0a4f87 | ||
|
|
1d92c52951 | ||
|
|
381450c897 | ||
|
|
f6fc0cc068 | ||
|
|
c5fbbea191 | ||
|
|
660e15efe8 | ||
|
|
72551e4dac | ||
|
|
9287f6907e | ||
|
|
ae5a5b7c71 | ||
|
|
1e875d7461 | ||
|
|
27a3c4672e | ||
|
|
6c8dccfc5e | ||
|
|
47968e1c7a | ||
|
|
4042770293 | ||
|
|
440ff84801 | ||
|
|
42614687ee | ||
|
|
6f0cd2eb91 | ||
|
|
74c055ebaf | ||
|
|
e531488a09 | ||
|
|
9f4c076d1f | ||
|
|
8003cd225e | ||
|
|
3b43feb317 | ||
|
|
069f75d8e2 | ||
|
|
cc06e0af42 | ||
|
|
927291f837 | ||
|
|
c56fff5d78 | ||
|
|
c1967bae9c | ||
|
|
74f17a10ec | ||
|
|
20ec493dee | ||
|
|
aa866336f2 | ||
|
|
990461f36a | ||
|
|
960f204906 | ||
|
|
982f911295 | ||
|
|
b740724656 | ||
|
|
d416cf1bb1 | ||
|
|
ee51877a04 | ||
|
|
7f44887d7f | ||
|
|
aac7ada5f6 | ||
|
|
9b6264b65b | ||
|
|
dc97d7442d | ||
|
|
9b50f2fc62 | ||
|
|
fbd21da92e | ||
|
|
3fe2140564 | ||
|
|
e944377b8e | ||
|
|
f896b17f6f | ||
|
|
7dbeab896e | ||
|
|
3b731784d8 | ||
|
|
5c854bdb74 | ||
|
|
2bc6563fc6 | ||
|
|
897af3ccc4 | ||
|
|
5653d92158 | ||
|
|
1028cf2192 | ||
|
|
e83afaa258 | ||
|
|
995fcf79de | ||
|
|
04f34dbf53 | ||
|
|
482fb1c1cd | ||
|
|
69cacec43f | ||
|
|
14bab38430 | ||
|
|
0fc0426b13 | ||
|
|
32a40e756f | ||
|
|
b29783483e | ||
|
|
36f24375ee | ||
|
|
4c265f3887 | ||
|
|
fd467924fb | ||
|
|
b19601fc08 | ||
|
|
9aa9a106ff | ||
|
|
3446ac3ef8 | ||
|
|
dd3670abc1 | ||
|
|
98405e85ac | ||
|
|
ff96803649 | ||
|
|
944740f40a | ||
|
|
1cb4b24453 | ||
|
|
9488d40175 | ||
|
|
57c00631e8 | ||
|
|
1e7c9fdbc4 | ||
|
|
e50ca51dac | ||
|
|
fb8fa4f676 | ||
|
|
e8ead3a3c4 | ||
|
|
c41962129a | ||
|
|
c9e7094a3e | ||
|
|
dae73a17c7 | ||
|
|
2e7b4182d2 | ||
|
|
76430c76df | ||
|
|
bd4c7ae336 | ||
|
|
79e9d846c5 | ||
|
|
c62de10b46 | ||
|
|
0834468ad1 | ||
|
|
07f6c05511 | ||
|
|
bff8e2bb84 | ||
|
|
0d25012fce | ||
|
|
895365a9f7 | ||
|
|
b06cb51c07 | ||
|
|
366f125b61 | ||
|
|
d90102f610 | ||
|
|
829f7e2248 | ||
|
|
cc3d14696e | ||
|
|
cc15e5ab6f | ||
|
|
57e8a7cae0 | ||
|
|
6fe8ff3682 | ||
|
|
0a34a9a930 | ||
|
|
080b598f8d | ||
|
|
92a719c4af | ||
|
|
8ed4d09ddf | ||
|
|
186eb70caa | ||
|
|
20c5bf0b5c | ||
|
|
ed9cdad983 | ||
|
|
da5313a2ac | ||
|
|
e181a1b668 | ||
|
|
b40e7ae6be | ||
|
|
a8289bea90 | ||
|
|
637927f49a | ||
|
|
b539fa7876 | ||
|
|
14c733b403 | ||
|
|
647d92d25e | ||
|
|
8f789642ec | ||
|
|
be3b7d0bca | ||
|
|
384fc7d860 | ||
|
|
cdfff9dd33 | ||
|
|
3e3a34f81e | ||
|
|
79425bed92 | ||
|
|
87bdcaadac | ||
|
|
1d46e18000 | ||
|
|
e8d37978ac | ||
|
|
c9fd903c88 | ||
|
|
6700e699ff | ||
|
|
252300ce31 | ||
|
|
7342535415 | ||
|
|
c330dae6db | ||
|
|
b252f53d1a | ||
|
|
63e51f37ea | ||
|
|
c9c4665b85 | ||
|
|
84c9b1bec7 | ||
|
|
ec42adb3c7 | ||
|
|
c195fc9fad | ||
|
|
8fabb5c3ef | ||
|
|
2321f0a545 | ||
|
|
bad722e15c | ||
|
|
1ef83443e7 | ||
|
|
cee33f76d6 | ||
|
|
1fe0ab8421 | ||
|
|
de736b7a06 | ||
|
|
1b67021ba8 | ||
|
|
99234309c7 | ||
|
|
7278db5201 | ||
|
|
e83a45016d | ||
|
|
a7ad5efb69 | ||
|
|
42d6d7b7ca | ||
|
|
580d06e0ad | ||
|
|
fac5767199 | ||
|
|
10c574891b | ||
|
|
ab62e7d139 | ||
|
|
3678ef5eeb | ||
|
|
097fe820a0 | ||
|
|
105fe2a343 | ||
|
|
47884a48b3 | ||
|
|
593968328e | ||
|
|
bc32c15a95 | ||
|
|
386a467e7c | ||
|
|
ebb7b96a75 | ||
|
|
e82cc3ff2e | ||
|
|
9e508ff1c4 | ||
|
|
a9efe5e98d | ||
|
|
291028b4a1 | ||
|
|
865087b904 | ||
|
|
4b8089b8b6 | ||
|
|
5e032e6933 | ||
|
|
a9e2a8d3b7 | ||
|
|
78bb22df5c | ||
|
|
138abbb7e5 | ||
|
|
ad20bd3eee | ||
|
|
c882e3a096 | ||
|
|
c4aaed3463 | ||
|
|
f0ca708e66 | ||
|
|
7e2e499574 | ||
|
|
b0de980a08 | ||
|
|
d83bb11c02 | ||
|
|
824f389ee4 | ||
|
|
ec4eb8309e | ||
|
|
1670ea9ef2 | ||
|
|
dd1b6ee9fc | ||
|
|
1619f4b0d1 | ||
|
|
76c8129bd3 | ||
|
|
b34aaba6be | ||
|
|
157cf7c374 | ||
|
|
956e4669e7 | ||
|
|
c0ead03359 | ||
|
|
37912ec8a7 | ||
|
|
4cdf61fd2a | ||
|
|
2770e13a25 | ||
|
|
3c1b15f50f | ||
|
|
9c79fae2f8 | ||
|
|
e077b5e450 | ||
|
|
819b5273df | ||
|
|
35553169f5 | ||
|
|
48ba718670 | ||
|
|
f4e2c68f47 | ||
|
|
ac41c3defd | ||
|
|
98f100b792 | ||
|
|
d1f6f8961b | ||
|
|
bd64c85f94 | ||
|
|
a63a6e378f | ||
|
|
2949e3f3c8 | ||
|
|
bf8561ad63 | ||
|
|
845c5fdd3d | ||
|
|
60d19cb729 | ||
|
|
26ff0be72b | ||
|
|
32d67ca54d | ||
|
|
85c1abe08a | ||
|
|
78613eab1e | ||
|
|
c9c46709a7 | ||
|
|
b6ea29627e | ||
|
|
210d7783e4 | ||
|
|
d49b5449af | ||
|
|
71554122f5 | ||
|
|
72be54842a | ||
|
|
ac0d2347fa | ||
|
|
ef4247f38a | ||
|
|
ee92bf955a | ||
|
|
40a401d6c7 | ||
|
|
e8e1c868a2 | ||
|
|
9962117093 | ||
|
|
ffeb6003a7 | ||
|
|
5a61d00bcf | ||
|
|
f6e39aea72 | ||
|
|
c4814a2894 | ||
|
|
9a341b50aa | ||
|
|
d0f7b38f98 | ||
|
|
cb9fa2b46a | ||
|
|
9b34f6342e | ||
|
|
d4b8d17e47 | ||
|
|
ac9ce10ad4 | ||
|
|
4e2ac3e738 | ||
|
|
9c7cb105c3 | ||
|
|
3b43f39953 | ||
|
|
7f50bb8b7c | ||
|
|
5108b173b5 | ||
|
|
62b638f815 | ||
|
|
a468385b5b | ||
|
|
791c7cece6 | ||
|
|
1ef28b9070 | ||
|
|
9cd62fa3e0 | ||
|
|
782b9905c7 | ||
|
|
52d80990e7 | ||
|
|
f4c8448a19 | ||
|
|
9e0dc69321 | ||
|
|
e897ba0bd9 | ||
|
|
ed72cf7f28 | ||
|
|
7c151371db | ||
|
|
55250cec8c | ||
|
|
7854103d04 | ||
|
|
615517665a | ||
|
|
cdf9867022 | ||
|
|
0e8832b300 | ||
|
|
a9910d7bf7 | ||
|
|
60d7202b73 | ||
|
|
9fc90eb04b | ||
|
|
a0e25894a0 | ||
|
|
a3a1b33ed3 | ||
|
|
7a54f8d5ae | ||
|
|
7374742260 | ||
|
|
b4ca4580aa | ||
|
|
16e3bbecb7 | ||
|
|
af21e0c352 | ||
|
|
e686f74542 | ||
|
|
355b7671b6 | ||
|
|
aabeb755ee | ||
|
|
db2b69ba80 | ||
|
|
c6e7ccd9c1 | ||
|
|
c7617f3150 | ||
|
|
59d303bb0b | ||
|
|
bc1ae40a84 | ||
|
|
4989c294bc | ||
|
|
1c92e02c8b | ||
|
|
035de0310f | ||
|
|
046dc3e937 | ||
|
|
70a5c61500 | ||
|
|
f96c80357c | ||
|
|
0136232ded | ||
|
|
8e2e049eaa | ||
|
|
153b3d5625 | ||
|
|
365e503f7e | ||
|
|
4fd0d66ecd | ||
|
|
a4c0cb5d99 | ||
|
|
d41b42316e | ||
|
|
08c51d30a0 | ||
|
|
9b4af053b1 | ||
|
|
9158768c85 | ||
|
|
5c09f1d731 | ||
|
|
689b29c255 | ||
|
|
c9d8d7fbba | ||
|
|
14a36fa9bd | ||
|
|
8cb2643eaa | ||
|
|
b42ff01985 | ||
|
|
562a9570b3 | ||
|
|
2a0f7211b8 | ||
|
|
cf46814afe | ||
|
|
5a7169c49c | ||
|
|
fa9459952c | ||
|
|
617c2c4088 | ||
|
|
0c1ef4ffbc | ||
|
|
d648239f0c | ||
|
|
81b779eba7 | ||
|
|
2a403115a6 | ||
|
|
d9edf75de6 | ||
|
|
94114bc0ab | ||
|
|
675276422a | ||
|
|
49092ff578 | ||
|
|
285500f2de | ||
|
|
32cb213a88 | ||
|
|
eb4fce255f | ||
|
|
d7d45049c5 | ||
|
|
d0aeecafed | ||
|
|
29fa0d2131 | ||
|
|
834f7d7a38 | ||
|
|
3691a21319 | ||
|
|
2c7664e251 | ||
|
|
031cb7c783 | ||
|
|
08357be70f | ||
|
|
d3986bef3b | ||
|
|
c0bd6459a2 | ||
|
|
38621b4b3e | ||
|
|
eef8578eaf | ||
|
|
a0eeea8b88 | ||
|
|
4f83dd1ef5 | ||
|
|
5220673d2e | ||
|
|
89baa76ec6 | ||
|
|
2dc0bb29b7 | ||
|
|
06e391c9a6 | ||
|
|
f3c3d61374 | ||
|
|
f0c12805fe | ||
|
|
ba1c269c0e | ||
|
|
5e1533a6c7 | ||
|
|
85aaa47b26 | ||
|
|
1c455a2cba | ||
|
|
0e799fcf5d | ||
|
|
ddcd2308a8 | ||
|
|
1ec6ef76c3 | ||
|
|
8fcb1f9607 | ||
|
|
7c73dd1e31 | ||
|
|
d91ea493d9 | ||
|
|
1ac063f803 | ||
|
|
960466fbb9 | ||
|
|
21ec5ecb79 | ||
|
|
1334c6d425 | ||
|
|
ca86368c7a | ||
|
|
27273f0a28 | ||
|
|
3a2157c0e2 | ||
|
|
d58e797a1c | ||
|
|
7c23fd6c13 | ||
|
|
0c1517acb6 | ||
|
|
5501a1846d | ||
|
|
975449425e | ||
|
|
d4096d18e7 | ||
|
|
631653b12a | ||
|
|
d735c4554f | ||
|
|
6ad1ecc1e2 | ||
|
|
48adc2c7a7 | ||
|
|
85a5885951 | ||
|
|
c3cb3961df | ||
|
|
08957915e0 | ||
|
|
4535135883 | ||
|
|
841d8c64d7 | ||
|
|
9ccb4d2637 | ||
|
|
b3eab693d1 | ||
|
|
599999d9ab | ||
|
|
5f61ea19d0 | ||
|
|
841021afc1 | ||
|
|
8c9f2af276 | ||
|
|
6c41c31634 | ||
|
|
9485daf76f | ||
|
|
a4c141cf26 | ||
|
|
7632db76ab | ||
|
|
8c1976e1c8 | ||
|
|
aaf3654bb9 | ||
|
|
7aadb310ba | ||
|
|
d65517e022 | ||
|
|
2db956d0fa | ||
|
|
ef906e95c1 | ||
|
|
a238849952 | ||
|
|
74f5fa50ad | ||
|
|
2d00b1f06f | ||
|
|
32fb17a9ed | ||
|
|
74c0304f64 | ||
|
|
5461a59e97 | ||
|
|
ff8515529d | ||
|
|
efc74846a7 | ||
|
|
c0b79087a6 | ||
|
|
1a83e97dad | ||
|
|
1186fb3f8a | ||
|
|
5336b672ed | ||
|
|
5e671475c2 | ||
|
|
79d707b706 | ||
|
|
34e78f52fb | ||
|
|
54dc1b49c3 | ||
|
|
65ed1c7801 | ||
|
|
8c4a0259b9 | ||
|
|
a31fd37571 | ||
|
|
39b5d6db06 | ||
|
|
b73c64cbb9 | ||
|
|
328d20473f | ||
|
|
cb1409f723 | ||
|
|
2d0778c2bf | ||
|
|
93a43ba873 | ||
|
|
5e845e1de3 | ||
|
|
5fb33bf676 | ||
|
|
225e3a23d0 | ||
|
|
64cb6f2e5f | ||
|
|
ff1e668187 | ||
|
|
ccfa8b7e65 | ||
|
|
f86c8f9d65 | ||
|
|
e40f84ec39 | ||
|
|
8255317bac | ||
|
|
3dfc71dcc2 | ||
|
|
a2fb98b6ba | ||
|
|
871a16433f | ||
|
|
ee836c4bd3 | ||
|
|
96a18245c9 | ||
|
|
75f8479c66 | ||
|
|
c1ef8c691d | ||
|
|
8be925817b | ||
|
|
6c17d1b640 | ||
|
|
a506e66004 | ||
|
|
82f43d88b1 | ||
|
|
2dc5d67f77 | ||
|
|
b6e4316fc7 | ||
|
|
dfe49744ca | ||
|
|
9c0e1c6403 | ||
|
|
154e2dc718 | ||
|
|
7c8d197257 | ||
|
|
5eeaa730eb | ||
|
|
5f9d7a7626 | ||
|
|
9e727f314a | ||
|
|
854a4c245d | ||
|
|
a44cf25d6d | ||
|
|
7b9ee2ab70 | ||
|
|
9d74faf3b6 | ||
|
|
5bcb54a5fc | ||
|
|
a4e66596eb | ||
|
|
cca826f8f1 | ||
|
|
ac8c732cff | ||
|
|
8ebe2c3f50 | ||
|
|
bb8d9d6563 | ||
|
|
e2b33a224f | ||
|
|
670a84223f | ||
|
|
35749d287e | ||
|
|
fe8215646c | ||
|
|
0d3f3c182e | ||
|
|
04907cf3dd | ||
|
|
2f27bbbbec | ||
|
|
7eebceebeb | ||
|
|
0e655fc9fc | ||
|
|
28f5628d5f | ||
|
|
8e68505684 | ||
|
|
eaed3e4b01 | ||
|
|
29f423c7f8 | ||
|
|
587cdbdf46 | ||
|
|
895bdeac5a | ||
|
|
490c3781e5 | ||
|
|
8e9fe4a32a | ||
|
|
c089f633ef | ||
|
|
ad0870e8ce | ||
|
|
3b30a2fc1b | ||
|
|
14e0dd735a | ||
|
|
b6c5ee3ccf | ||
|
|
1cd9946b9c | ||
|
|
6ee54ae2a7 | ||
|
|
bf7b97d206 | ||
|
|
0574e63e0b | ||
|
|
fd58d91224 | ||
|
|
9b89513083 | ||
|
|
7a947f30b7 | ||
|
|
457ae84b79 | ||
|
|
e3dd8d8cbe | ||
|
|
6c00aa6da5 | ||
|
|
016dcb12f5 | ||
|
|
def802a448 | ||
|
|
9bc3ee3ad4 | ||
|
|
2909462d1f | ||
|
|
7641ad9891 | ||
|
|
a05d5beaec | ||
|
|
aacade0fd3 | ||
|
|
172d3235e7 | ||
|
|
a6fa4337e9 | ||
|
|
c27bea3f7d | ||
|
|
c7e7b96fee | ||
|
|
25d06245a1 | ||
|
|
d353b89717 | ||
|
|
daae21c7dc | ||
|
|
05ba570131 | ||
|
|
fc476cca69 | ||
|
|
82f2026077 | ||
|
|
84a02b55c8 | ||
|
|
25f80b63cf | ||
|
|
f886ac6021 | ||
|
|
da7ba08c13 | ||
|
|
71a2ae0f97 | ||
|
|
f841dcc050 | ||
|
|
6ca461e271 | ||
|
|
4519aabf25 | ||
|
|
723be4d1bf | ||
|
|
17b679096e | ||
|
|
7763fc694f | ||
|
|
5b900d7a6a | ||
|
|
25c832a585 | ||
|
|
04b092dbd2 | ||
|
|
a1001f954c | ||
|
|
c09eb2455b | ||
|
|
832d60c774 | ||
|
|
a9d78277be | ||
|
|
9d96dd1880 | ||
|
|
df6d217de6 | ||
|
|
5f1bbffc1a | ||
|
|
64d97bb5d1 | ||
|
|
7d10c553b2 | ||
|
|
3fefa45278 | ||
|
|
014566289d | ||
|
|
26c57f5426 | ||
|
|
edf6b26679 | ||
|
|
66cf635820 | ||
|
|
07f6e02ea3 | ||
|
|
ead4211e38 | ||
|
|
15e37c7d68 | ||
|
|
53a90731a4 | ||
|
|
59a60a1162 | ||
|
|
aa049342b5 | ||
|
|
bfc6a40d05 | ||
|
|
f0e0388174 | ||
|
|
c1ae649c0a | ||
|
|
1f12c8115b | ||
|
|
76406948b6 | ||
|
|
1cfe02bf8e | ||
|
|
7f9b870183 | ||
|
|
d347ebd7e8 | ||
|
|
c50012a303 | ||
|
|
9f2ddabea6 | ||
|
|
887af7d410 | ||
|
|
3db5aac969 | ||
|
|
9cb5e0a5d8 | ||
|
|
f7d5f059a6 | ||
|
|
fa6afb0416 | ||
|
|
5414b4277d | ||
|
|
cda0cdfc1f | ||
|
|
eef3d2b583 | ||
|
|
dd8ee98901 | ||
|
|
3b7dd879ee | ||
|
|
513f69566d | ||
|
|
985ffd4e04 | ||
|
|
6612f30646 | ||
|
|
c06a07ba8d | ||
|
|
8ac4ade155 | ||
|
|
b32cedb448 | ||
|
|
bcea844652 | ||
|
|
792c871b80 | ||
|
|
1f4ff5c46a | ||
|
|
bb53c24d92 | ||
|
|
29e4cd1921 | ||
|
|
f6654e3448 | ||
|
|
5a13ca3557 | ||
|
|
ffac805430 | ||
|
|
115983aa75 | ||
|
|
d063c45bd0 | ||
|
|
9687281866 | ||
|
|
51ac7a259d | ||
|
|
22fedc2726 | ||
|
|
54edd397e8 | ||
|
|
442380da2c | ||
|
|
ffeb2ebe9b | ||
|
|
5723b1417a | ||
|
|
c6e3d313f8 | ||
|
|
7f41a37b7f | ||
|
|
42c578c9b8 | ||
|
|
6a9a9cf11f | ||
|
|
e687d76490 | ||
|
|
afbcc48218 | ||
|
|
caba0e0ec6 | ||
|
|
65e1f5982d | ||
|
|
e2444d1c9e | ||
|
|
3a25e72f23 | ||
|
|
1e04c8800e | ||
|
|
5356ba0d84 | ||
|
|
75cd1ffdb8 | ||
|
|
53684432b2 | ||
|
|
3e43260afc | ||
|
|
42105563b2 | ||
|
|
53c181166d | ||
|
|
4514bc7538 | ||
|
|
473afaff61 | ||
|
|
3692982edf | ||
|
|
1d306e309f | ||
|
|
b09cf10cfe | ||
|
|
34a02cb36e | ||
|
|
4e69677c94 | ||
|
|
5a49914cb0 | ||
|
|
9bc0b945ee | ||
|
|
a542c00b55 | ||
|
|
125fa18353 | ||
|
|
38076bf6c7 | ||
|
|
af8d18057b | ||
|
|
df8c12fa96 | ||
|
|
ea9eeb89b1 | ||
|
|
807bed5864 | ||
|
|
94202e9ad9 | ||
|
|
c8ee200ea8 | ||
|
|
f931e532ec | ||
|
|
663e3aedb6 | ||
|
|
0e8e350362 | ||
|
|
d8d614c43e | ||
|
|
b73159d89e | ||
|
|
5e9ffa88e9 | ||
|
|
35d4461424 | ||
|
|
66fa773d36 | ||
|
|
fda3fb5b6e | ||
|
|
1ae673018b | ||
|
|
196303fc44 | ||
|
|
7903a2ceb2 | ||
|
|
663a96a302 | ||
|
|
24128f05d9 | ||
|
|
5ad7231068 | ||
|
|
6ef71c6223 | ||
|
|
e34b7ce4b5 | ||
|
|
9fc00ec77e | ||
|
|
29c9f81c1f | ||
|
|
48b09d1a02 | ||
|
|
f302a405c8 | ||
|
|
c721416857 | ||
|
|
cd29188494 | ||
|
|
30828ca791 | ||
|
|
4f2ec2361b | ||
|
|
9b542f9a96 | ||
|
|
11c7c296ff | ||
|
|
c4ad6ff164 | ||
|
|
986c9c98d4 | ||
|
|
bcec76482e | ||
|
|
221b4a9c94 | ||
|
|
aad49da9da | ||
|
|
f8d9eee0a5 | ||
|
|
4222e6521b | ||
|
|
3823bd9a3e | ||
|
|
7e42205e67 | ||
|
|
d8112542e5 | ||
|
|
a2d77de0b6 | ||
|
|
dc6c0c3ada | ||
|
|
87091763f6 | ||
|
|
6a5aa6bffa | ||
|
|
4d6605cf90 | ||
|
|
a234c260fc | ||
|
|
f596f47dde | ||
|
|
f57763f652 | ||
|
|
32dc0416de | ||
|
|
05a74b32bc | ||
|
|
96cf04217c | ||
|
|
040d62ed75 | ||
|
|
c894fd8568 | ||
|
|
02046aa3bf | ||
|
|
20d55b882d | ||
|
|
fdb1bdddf3 | ||
|
|
a529bc6a7a | ||
|
|
4823c8c27d | ||
|
|
f589ebd3bb | ||
|
|
b8bc175c0f | ||
|
|
6f8e2808f1 | ||
|
|
b26146a6a5 | ||
|
|
8a83c7fe12 | ||
|
|
28336c11ea | ||
|
|
1c12a37d8d | ||
|
|
4e7b680413 | ||
|
|
c3a58ab14d | ||
|
|
0d66911647 | ||
|
|
16ab743563 | ||
|
|
365bc7efe8 | ||
|
|
bc6838c928 | ||
|
|
6ef22cfc3c | ||
|
|
641cd7f58e | ||
|
|
f825fc1589 | ||
|
|
e33f13cb60 | ||
|
|
3eaf6d7ea5 | ||
|
|
138f6cdb8e | ||
|
|
68d9397fd1 | ||
|
|
52154e20e7 | ||
|
|
5ef79c3acb | ||
|
|
d5c694c019 | ||
|
|
35f2c3968a | ||
|
|
0e79ef36e7 | ||
|
|
77765421d9 | ||
|
|
39163c9505 | ||
|
|
71961bceee | ||
|
|
289f1cd89e | ||
|
|
830cdce93f | ||
|
|
236233f39f | ||
|
|
dbb03b38fe | ||
|
|
12561c981e | ||
|
|
0b147cdf4e | ||
|
|
b2eacba153 | ||
|
|
a1fbc5b783 | ||
|
|
60cd72f428 | ||
|
|
38db3d4782 | ||
|
|
ff59010256 | ||
|
|
45b702f469 | ||
|
|
f9fcc7cd84 | ||
|
|
c7d590e0ef | ||
|
|
6dd4a763ad | ||
|
|
c6c82ae3db | ||
|
|
047633d7a3 | ||
|
|
1b580b8b49 | ||
|
|
f468744536 | ||
|
|
82bf224b6a | ||
|
|
fe7ddd64de | ||
|
|
36c60ae5cd | ||
|
|
eb8fe7568e | ||
|
|
2306862437 | ||
|
|
80228e35f2 | ||
|
|
3e1ad36a8c | ||
|
|
7d2ea1e51f | ||
|
|
2d40fa4a9e | ||
|
|
e2769f3563 | ||
|
|
fba7307e68 | ||
|
|
56dbaa3849 | ||
|
|
c574e8066b | ||
|
|
ebcd68c35b | ||
|
|
9187588aa2 | ||
|
|
61f05be884 | ||
|
|
b1436d9352 | ||
|
|
361499f167 | ||
|
|
603a2fa0ca | ||
|
|
a9e766b3d6 | ||
|
|
70cf343fde | ||
|
|
521689920d | ||
|
|
5d509ea7a3 | ||
|
|
d479a12e9e | ||
|
|
570a079ab8 | ||
|
|
5cbe72928a | ||
|
|
3589ad76f6 | ||
|
|
931189dd22 | ||
|
|
7c297f315d | ||
|
|
ca8be5306d | ||
|
|
98e211c879 | ||
|
|
616144e994 | ||
|
|
2e53fea374 | ||
|
|
5e83a46776 | ||
|
|
20d9bb04fd | ||
|
|
3dab77b6c1 | ||
|
|
7f46d4dceb | ||
|
|
89dc431fac | ||
|
|
8e91daffe4 | ||
|
|
0a29772cf1 | ||
|
|
402c6a1aa7 | ||
|
|
0f893f4ff2 | ||
|
|
055b971653 | ||
|
|
6436be1dd4 | ||
|
|
cc31be3780 | ||
|
|
0c482a9bfe | ||
|
|
2f197b6a37 | ||
|
|
f4c46747ca | ||
|
|
4bb3c00daf | ||
|
|
bf1f4379f4 | ||
|
|
266586a86c | ||
|
|
93b25a9f35 | ||
|
|
1d726e9196 | ||
|
|
56632974ca | ||
|
|
6d3877f63d | ||
|
|
3f2e7c14e1 | ||
|
|
c8ed67c673 | ||
|
|
f516c770d3 | ||
|
|
f56178bda7 | ||
|
|
1e1199d7dd | ||
|
|
2db2f98c55 | ||
|
|
485aba1de0 | ||
|
|
14e127c478 | ||
|
|
6e571570bd | ||
|
|
f69b2d277a | ||
|
|
36fef976a7 | ||
|
|
8ddabe2996 | ||
|
|
e48d54c06e | ||
|
|
2964d686a5 | ||
|
|
c8a245f80a | ||
|
|
80923fc00e | ||
|
|
3f8982d7fd | ||
|
|
8974cbb946 | ||
|
|
be60a6274f | ||
|
|
b5e7614c7a | ||
|
|
dd6cd73e67 | ||
|
|
5d609fbe16 | ||
|
|
5cecb0e2b9 | ||
|
|
9250f16fa4 | ||
|
|
c674c9ce66 | ||
|
|
6442660981 | ||
|
|
81c4f711a6 | ||
|
|
fc98280aef | ||
|
|
f5b346fdc0 | ||
|
|
94ecf8d94c | ||
|
|
f6df937611 | ||
|
|
69f6ffc7d3 | ||
|
|
3453eb8888 | ||
|
|
9e593f3e3d | ||
|
|
c218edca83 | ||
|
|
fdc3d74460 | ||
|
|
4afb8c1910 | ||
|
|
00974c3a08 | ||
|
|
87cd098949 | ||
|
|
ae88267218 | ||
|
|
3b6b2f681e | ||
|
|
db11f0bc7c | ||
|
|
17656af97a | ||
|
|
67017cb13c | ||
|
|
40db33851c | ||
|
|
fb2bb873e4 | ||
|
|
e4f0179e2d | ||
|
|
86f946a91c | ||
|
|
9fc12d6a03 | ||
|
|
db97913c2c | ||
|
|
59f4223414 | ||
|
|
e44a55fb69 | ||
|
|
2555f2d899 | ||
|
|
17fb80ad08 | ||
|
|
856eb349a4 | ||
|
|
1e7f1c89b8 | ||
|
|
1cd39111b1 | ||
|
|
9cec002a95 | ||
|
|
c549279d46 | ||
|
|
c62f8e71c1 | ||
|
|
e904463cff | ||
|
|
5ec8476d01 | ||
|
|
03ec3a61b6 | ||
|
|
90f9d9534c | ||
|
|
f5a200e08b | ||
|
|
44ca697bde | ||
|
|
ecc5bdb8f6 | ||
|
|
0bdb06a1c7 | ||
|
|
9655b04e19 | ||
|
|
763513d96f | ||
|
|
442924cf7e | ||
|
|
4d473b6fca | ||
|
|
cba1c98747 | ||
|
|
70d3c56f9c | ||
|
|
dafbc0bcc0 | ||
|
|
34734e9b43 | ||
|
|
6b1939c2d2 | ||
|
|
e7e3409d94 | ||
|
|
dc39f59874 | ||
|
|
74f8c4f436 | ||
|
|
7d238c40f3 | ||
|
|
a9a32f10bb | ||
|
|
8e1d09f181 | ||
|
|
3d2184388e | ||
|
|
c317ebbbd5 | ||
|
|
a0f8b32970 | ||
|
|
3fcdd77a5d | ||
|
|
5510851c50 | ||
|
|
a7341282ce | ||
|
|
19fd73c088 | ||
|
|
b9d1926ae2 | ||
|
|
eebb8ce757 | ||
|
|
3fda5a9ebb | ||
|
|
2cccd0cd6f | ||
|
|
2e4874b50a | ||
|
|
a10096e318 | ||
|
|
b9253d5d12 | ||
|
|
ac60e305cb | ||
|
|
b77a51283d | ||
|
|
2d8e1427df | ||
|
|
4117f80c83 | ||
|
|
2f716f3f98 | ||
|
|
90502d9df8 | ||
|
|
9408469d82 | ||
|
|
d3626a1d82 | ||
|
|
33148f1e09 | ||
|
|
24d2579bc7 | ||
|
|
08832a73a7 | ||
|
|
bbf3ec89e2 | ||
|
|
c57afbf592 | ||
|
|
061be05e27 | ||
|
|
137c65fe76 | ||
|
|
44fa955fb9 | ||
|
|
dcc853515c | ||
|
|
2d7701ed89 | ||
|
|
3f67dfe124 | ||
|
|
230a12270c | ||
|
|
7218a7339f | ||
|
|
f26e8f0fc6 | ||
|
|
5e849843ff | ||
|
|
b2cc92a90b | ||
|
|
a2991fec12 | ||
|
|
e70b3be713 | ||
|
|
3e44808851 | ||
|
|
4aaa3d01ea | ||
|
|
69ca95f39f | ||
|
|
8c9b77faac | ||
|
|
dad44c3286 | ||
|
|
9c0de10dd3 | ||
|
|
c57b4e825f | ||
|
|
5b9a99ed47 | ||
|
|
8d29a44576 | ||
|
|
3c32f8f64b | ||
|
|
86d0decacf | ||
|
|
33882e3b22 | ||
|
|
8b2a3d2b6f | ||
|
|
769686867a | ||
|
|
e867c2c8ec | ||
|
|
29911c2d81 | ||
|
|
677a0768c3 | ||
|
|
c6a9ba0cc5 | ||
|
|
c90fe46077 | ||
|
|
20dbf24690 | ||
|
|
074daf4fc0 | ||
|
|
2e7203e02f | ||
|
|
281ecd4aad | ||
|
|
ad82cfe2fb | ||
|
|
3ce6a53d23 | ||
|
|
5006a41787 | ||
|
|
4f69915d1a | ||
|
|
38342bfea3 | ||
|
|
aa3fae3682 | ||
|
|
ae1aab7b7d | ||
|
|
18dd07aee1 | ||
|
|
feca517202 | ||
|
|
5aff721f7a | ||
|
|
bca6863ad6 | ||
|
|
d61bb6d2ac | ||
|
|
3d3cd31913 | ||
|
|
997db52c15 | ||
|
|
52f0106cd4 | ||
|
|
5127c45d38 | ||
|
|
dc4828e313 | ||
|
|
496e963676 | ||
|
|
e90d6584a4 | ||
|
|
3b656c1f5d | ||
|
|
8a153616fb | ||
|
|
ef3abe3fb5 | ||
|
|
f72792704e | ||
|
|
d40e424139 | ||
|
|
f43522c370 | ||
|
|
5a1966a0b1 | ||
|
|
386c8d6e95 | ||
|
|
9691acb7c0 | ||
|
|
a435efed6d | ||
|
|
abbbd31a07 | ||
|
|
511df90ba9 | ||
|
|
e34fc26f46 | ||
|
|
2adfe22830 | ||
|
|
f9bdd34e9b | ||
|
|
86dc9338a2 | ||
|
|
deec8b358b | ||
|
|
a6b563bafe | ||
|
|
bd403b8ecc | ||
|
|
d007ae2cd5 | ||
|
|
2aa18e4681 | ||
|
|
2c87e0540d | ||
|
|
55d04d0b38 | ||
|
|
09d6924cc7 | ||
|
|
13ab64c0e7 | ||
|
|
c06c3b1b13 | ||
|
|
77398ed246 | ||
|
|
d528a5fcf1 | ||
|
|
92bf88e5dc | ||
|
|
b787a7cb0d | ||
|
|
927451f82a | ||
|
|
b23758d807 | ||
|
|
624ec1597d | ||
|
|
9209b6feae | ||
|
|
27c0d8d2f2 | ||
|
|
bfafc7eca5 | ||
|
|
1dc9daf45f | ||
|
|
3c8f701e57 | ||
|
|
d5dcdfb52d | ||
|
|
75c536bf63 | ||
|
|
77f6da310c | ||
|
|
a231699777 | ||
|
|
0b4517e909 | ||
|
|
c3d3959c47 | ||
|
|
f5ad2e9c17 | ||
|
|
f86a6c5116 | ||
|
|
c28f9737cd | ||
|
|
5b65204388 | ||
|
|
99722100be | ||
|
|
852c506222 | ||
|
|
b1aad777bf | ||
|
|
461eb47072 | ||
|
|
e5a9637af3 | ||
|
|
9650e4162b | ||
|
|
8fe3c248fb | ||
|
|
5aac9f9280 | ||
|
|
97c50fee71 | ||
|
|
22b7ba35ff | ||
|
|
e523e17edb | ||
|
|
7af18a5aef | ||
|
|
51722724c1 | ||
|
|
f9ec43c95b | ||
|
|
9ef4a5c881 | ||
|
|
904ffe653a | ||
|
|
7bae067bcf | ||
|
|
509fbf3f8f | ||
|
|
6cfaf50d01 | ||
|
|
4a497f03ab | ||
|
|
e8671c4331 | ||
|
|
2a96a5bb84 | ||
|
|
8f4591df78 | ||
|
|
6135abbf50 | ||
|
|
87efb9699c | ||
|
|
88dc458da6 | ||
|
|
8578ea0c83 | ||
|
|
5c66158c79 | ||
|
|
37e158bbd1 | ||
|
|
ca0cea9519 | ||
|
|
be6d1670e6 | ||
|
|
e4a9b497af | ||
|
|
a6a3331f15 | ||
|
|
0acf9f6150 | ||
|
|
1f322c24e8 | ||
|
|
38d079b445 | ||
|
|
fb9cd49283 | ||
|
|
f4eda25612 | ||
|
|
c7bca68e6f | ||
|
|
058919ae85 | ||
|
|
df939bae74 | ||
|
|
9d0a627a97 | ||
|
|
13f54033c6 | ||
|
|
34d2456864 | ||
|
|
812a61dcda | ||
|
|
b2eb9b64ae | ||
|
|
f95e31d36c | ||
|
|
25fa1fad53 | ||
|
|
82b794e60f | ||
|
|
81afba8ae7 | ||
|
|
a3e0dc0472 | ||
|
|
1e070c49b5 | ||
|
|
97c9e70af2 | ||
|
|
bcc32b79c4 | ||
|
|
604177d063 | ||
|
|
a2a3be4d52 | ||
|
|
c010a79d4c | ||
|
|
6394bda97c | ||
|
|
2610b62954 | ||
|
|
5e4c80750c | ||
|
|
d3eac4405d | ||
|
|
b2919929fe | ||
|
|
88a8c67f20 | ||
|
|
b64b07af50 | ||
|
|
00ba52cea7 | ||
|
|
acfcbc493c | ||
|
|
b06d29ed5d | ||
|
|
212845846a | ||
|
|
9a6b00ef3b | ||
|
|
d15fc64da4 | ||
|
|
72f2601a80 | ||
|
|
c9ac466b12 | ||
|
|
23a237f0db | ||
|
|
ad8f8982bd | ||
|
|
a984457ea3 | ||
|
|
91ea7a6b3d | ||
|
|
a6a104f8db | ||
|
|
a877eae1dd | ||
|
|
3e75da5286 | ||
|
|
ba894d1530 | ||
|
|
f33df7e6ae | ||
|
|
b08ff1ad73 | ||
|
|
83ed292433 | ||
|
|
2dfa3b703e | ||
|
|
fc4053246c | ||
|
|
e8286346fb | ||
|
|
6f5b91fef6 | ||
|
|
177fd2f591 | ||
|
|
a83841c44c | ||
|
|
4e4e00e4d3 | ||
|
|
98ad1f8ae7 | ||
|
|
3076da6ac7 | ||
|
|
5cf4721c59 | ||
|
|
6590c7b63c | ||
|
|
afe9061076 | ||
|
|
079a1646d5 | ||
|
|
750916130d | ||
|
|
931637685e | ||
|
|
8906fefcc0 | ||
|
|
5133143373 | ||
|
|
7e6b2a4a85 | ||
|
|
17458c8e24 | ||
|
|
fe1db2271a | ||
|
|
e930124ec3 | ||
|
|
a88d98df17 | ||
|
|
486b3facbb | ||
|
|
9010e9d07f | ||
|
|
b1a5a6fea8 | ||
|
|
5ffd5a552f | ||
|
|
7077b7cbd2 | ||
|
|
5940479562 | ||
|
|
9c29f320cf | ||
|
|
60b75f5611 | ||
|
|
21b72fdf53 | ||
|
|
bf2d520ca2 | ||
|
|
328c405280 | ||
|
|
ac9257f756 | ||
|
|
eaa4abfe0e | ||
|
|
4a162ffdb3 | ||
|
|
af289dec91 | ||
|
|
d1656007c9 | ||
|
|
61330584e0 | ||
|
|
fb38e3c2a5 | ||
|
|
c1a902665f | ||
|
|
582b1af912 | ||
|
|
4f86cf6baa | ||
|
|
98afb306bc | ||
|
|
8a08aebcc5 | ||
|
|
2312732460 | ||
|
|
6f23545b00 | ||
|
|
e753f72a31 | ||
|
|
2b9e1483b9 | ||
|
|
c64cc802d9 | ||
|
|
d67ec0eb87 | ||
|
|
240f77e59a | ||
|
|
8b5fd926f3 | ||
|
|
a082d96255 | ||
|
|
452c48e0ab | ||
|
|
d2e7a0a646 | ||
|
|
33f42539c4 | ||
|
|
accde63372 | ||
|
|
b24e4d0b1c | ||
|
|
31185dd3c2 | ||
|
|
f241a012b3 | ||
|
|
e12b333182 | ||
|
|
4c2864241f | ||
|
|
8b1e90bf31 | ||
|
|
11af255f2a | ||
|
|
6ba37be3d7 | ||
|
|
26a7cc0b84 | ||
|
|
f95ddbe73f | ||
|
|
9c92ccb1aa | ||
|
|
7fcdba4d23 | ||
|
|
e9dba90cf9 | ||
|
|
a13551eb73 | ||
|
|
d849f611b9 | ||
|
|
98e04bebfc | ||
|
|
348bc509c1 | ||
|
|
abc66b0872 | ||
|
|
0ce6b0e62d | ||
|
|
5220f0ae2a | ||
|
|
69885b6376 | ||
|
|
916dc5a417 | ||
|
|
cf88c871d4 | ||
|
|
1a0e51d497 | ||
|
|
4005d20e68 | ||
|
|
3edcbe2b19 | ||
|
|
fc902869cf | ||
|
|
02aac14b98 | ||
|
|
713e2e8b08 | ||
|
|
b7ed85c148 | ||
|
|
5620cd6b61 | ||
|
|
e0d50bea0d | ||
|
|
54571ad785 | ||
|
|
eb8a2641d2 | ||
|
|
973de62a61 | ||
|
|
ac236a37f6 | ||
|
|
54ad5e06ca | ||
|
|
2338dd8845 | ||
|
|
ba1db7057d | ||
|
|
fcf97e43ce | ||
|
|
c35d73f11d | ||
|
|
5fd7e4234b | ||
|
|
f4ed7c3df5 | ||
|
|
ba203192f4 | ||
|
|
75772f2281 | ||
|
|
7087006da4 | ||
|
|
2bdd9fa57a | ||
|
|
7a0c61fc16 | ||
|
|
2c8fd6ce2d | ||
|
|
77cab91091 | ||
|
|
189f5309ec | ||
|
|
c6e68caf4d | ||
|
|
cb905ad402 | ||
|
|
92e82b3f8c | ||
|
|
5646605513 | ||
|
|
bea398e005 | ||
|
|
f10daf71d2 | ||
|
|
eec6bb122f | ||
|
|
f3c3f076bc | ||
|
|
f96f5ab424 | ||
|
|
cfa5fd7d35 | ||
|
|
98fac388da | ||
|
|
26ead7d150 | ||
|
|
921886d652 | ||
|
|
3cb62c725f | ||
|
|
4cd94268ba | ||
|
|
00a829087b | ||
|
|
7142ba8535 | ||
|
|
41894afcf5 | ||
|
|
7f9c0953f9 | ||
|
|
4791826cf8 | ||
|
|
6b7eeea031 | ||
|
|
6d8eb40e50 | ||
|
|
8579167f95 | ||
|
|
aace197795 | ||
|
|
eead5e9230 | ||
|
|
eb040ee56c | ||
|
|
36488bd28d | ||
|
|
779ab0a33e | ||
|
|
b022388b02 | ||
|
|
20f0a62e13 | ||
|
|
8abbd2007c | ||
|
|
45bcc822ae | ||
|
|
7dad25b3ec | ||
|
|
391f2ec65d | ||
|
|
815ae8dbf2 | ||
|
|
d03072415d | ||
|
|
8f127714d8 | ||
|
|
8579be0031 | ||
|
|
b1f89f9d6c | ||
|
|
0136f3dea2 | ||
|
|
0b9996910a | ||
|
|
8b002c0691 | ||
|
|
0bb4202944 | ||
|
|
36784f92b6 | ||
|
|
77a436998d | ||
|
|
28c6ac1b81 | ||
|
|
d0f90e7382 | ||
|
|
c525f036b5 | ||
|
|
a6ef5eac9d | ||
|
|
17e4a1d3d6 | ||
|
|
27765b4c57 | ||
|
|
248daf972e | ||
|
|
4142a34e1f | ||
|
|
06a77374ce | ||
|
|
7979a06202 | ||
|
|
ef06ff0411 | ||
|
|
14d114cc2d | ||
|
|
e66ff5bdcb | ||
|
|
9c8c48e9b0 | ||
|
|
2888fb0769 | ||
|
|
cee592f85d | ||
|
|
90403908f1 | ||
|
|
2eca994d7a | ||
|
|
48b33d2b1e | ||
|
|
64e6f1c7ce | ||
|
|
8a697958a9 | ||
|
|
4cb55ebc58 | ||
|
|
ba17fc2e2c | ||
|
|
d869e56152 | ||
|
|
1e7a738704 | ||
|
|
da4af00f3f | ||
|
|
5322b4ad19 | ||
|
|
722af732cc | ||
|
|
33f187c1a2 | ||
|
|
bcda505b93 | ||
|
|
7091ea7e9a | ||
|
|
890451d3f9 | ||
|
|
d1ae39e91b | ||
|
|
dc0e79362c | ||
|
|
3c7b666d84 | ||
|
|
f06050ae8a | ||
|
|
ac22722e1e | ||
|
|
c089a988fb | ||
|
|
39097d470f | ||
|
|
eb212dc921 | ||
|
|
e7671d2661 | ||
|
|
e99a3db22d | ||
|
|
47c714cbb7 | ||
|
|
df60372a02 | ||
|
|
7176672bd0 | ||
|
|
0b868438fd | ||
|
|
d688aca69b | ||
|
|
e85e21c3af | ||
|
|
b701e578a7 | ||
|
|
0ba1cca7bb | ||
|
|
bac5fa001a | ||
|
|
d6afbe6e5c | ||
|
|
6868946e88 | ||
|
|
2adfe0dd67 | ||
|
|
043f037db2 | ||
|
|
382e51bd03 | ||
|
|
516665ca4d | ||
|
|
5257b057eb | ||
|
|
2b2dbb0878 | ||
|
|
bd3595037b | ||
|
|
74fa66c13f | ||
|
|
db9131ebe1 | ||
|
|
0b8d6fee5d | ||
|
|
9dd37bec0e | ||
|
|
3753613721 | ||
|
|
92352f12de | ||
|
|
d232c7f7f0 | ||
|
|
d6eabe1715 | ||
|
|
e5a7506220 | ||
|
|
5bf705a40d | ||
|
|
8a0e3ededa | ||
|
|
d22b32e6c9 | ||
|
|
e5acb71780 | ||
|
|
2bbc3ca791 | ||
|
|
7b23b2f0ef | ||
|
|
a59b6b9059 | ||
|
|
3cd93d3c03 | ||
|
|
45b7977b28 | ||
|
|
67865519d9 | ||
|
|
ec54d7991e | ||
|
|
b2c1349bbe | ||
|
|
d82b233b79 | ||
|
|
c7fa828d3e | ||
|
|
aa0991d938 | ||
|
|
fcaa2cc04f | ||
|
|
bdd507cd72 | ||
|
|
d7214364fb | ||
|
|
ce3a3f8b7d | ||
|
|
2257789aa1 | ||
|
|
5f6d1d4065 | ||
|
|
7a7e322bf3 | ||
|
|
893dd492ab | ||
|
|
b70502f5ca | ||
|
|
606bfc5cbc | ||
|
|
f6e3379c2e | ||
|
|
d18b844bd1 | ||
|
|
1479ff54d1 | ||
|
|
8fb727475c | ||
|
|
6476ad49cc | ||
|
|
c49821d5d7 | ||
|
|
93efdc1204 | ||
|
|
8c929f753a | ||
|
|
5a1727b682 | ||
|
|
a641581777 | ||
|
|
caf07584f7 | ||
|
|
34bafb2a86 | ||
|
|
40c0eab347 | ||
|
|
aa193caba4 | ||
|
|
b8598b4e08 | ||
|
|
b2c2f9d298 | ||
|
|
e0a4230cdf | ||
|
|
6f7b2cce84 | ||
|
|
485dba94af | ||
|
|
3af30622da | ||
|
|
4e25673514 | ||
|
|
9282433306 | ||
|
|
f472466d55 | ||
|
|
c020dfc24e | ||
|
|
5d06b09ca1 | ||
|
|
50110d907f | ||
|
|
678fb3a33d | ||
|
|
c647cb5fc8 | ||
|
|
95d6356af9 | ||
|
|
2fcc2b9008 | ||
|
|
90823aabdf | ||
|
|
ddbe92602d | ||
|
|
bd8304370e | ||
|
|
3bb3f6f064 | ||
|
|
621e0c9b1c | ||
|
|
d05495edc4 | ||
|
|
99c481db98 | ||
|
|
5d17bd79db | ||
|
|
8ce0b37cf3 | ||
|
|
e98243152a | ||
|
|
b596873cb8 | ||
|
|
dedb53ed33 | ||
|
|
102f112758 | ||
|
|
455bf230b1 | ||
|
|
c8db36eb31 | ||
|
|
7c2e4b4589 | ||
|
|
d04f1e24a0 | ||
|
|
29a1da8fbb | ||
|
|
15d7961ba0 | ||
|
|
bbd99a200a | ||
|
|
ab22010641 | ||
|
|
137f24c8aa | ||
|
|
5827e935e4 | ||
|
|
6cf8f3dcbe | ||
|
|
446d441fad | ||
|
|
dab826927a | ||
|
|
099daa6cc6 | ||
|
|
21b834a616 | ||
|
|
74c2c22b79 | ||
|
|
7f8a91edcf | ||
|
|
4b4d482d54 | ||
|
|
d55ab62ef3 | ||
|
|
e51839917c | ||
|
|
59ce25c8cf | ||
|
|
4f754efbd1 | ||
|
|
b3eac82a68 | ||
|
|
0e63746bc5 | ||
|
|
909314ab8b | ||
|
|
227e7c123c | ||
|
|
55443003f8 | ||
|
|
737d7aa19d | ||
|
|
24737bbbb4 | ||
|
|
58ca2004a8 | ||
|
|
f93442edae | ||
|
|
2e124f33dd | ||
|
|
20771dc937 | ||
|
|
8924515076 | ||
|
|
025febfc38 | ||
|
|
6374f79287 | ||
|
|
8be98e1671 | ||
|
|
e036898a4c | ||
|
|
92cddfea5e | ||
|
|
5ca0aa12d9 | ||
|
|
a44c693a62 | ||
|
|
c1926d574a | ||
|
|
cb71123e6b | ||
|
|
a3299ad39b | ||
|
|
ed5316e254 | ||
|
|
b7fbf56a5a | ||
|
|
fefcac0cd9 | ||
|
|
071bad68e3 | ||
|
|
23522562ea | ||
|
|
863f92dbc3 | ||
|
|
b82b20a71b | ||
|
|
f5b51b4d14 | ||
|
|
e565a9ea4c | ||
|
|
56eb77ed92 | ||
|
|
99528e9a61 | ||
|
|
0991f9d773 | ||
|
|
46cdb262c7 | ||
|
|
4b12d73a55 | ||
|
|
f0ba26a79e | ||
|
|
61e47b9721 | ||
|
|
84274a4802 | ||
|
|
2ed42eb0ca | ||
|
|
1ae10c9e38 | ||
|
|
5b22f7cc97 | ||
|
|
0e9d2d9b9f | ||
|
|
73377598bf | ||
|
|
5652b025f8 | ||
|
|
43d6d4a6e1 | ||
|
|
92d3106910 | ||
|
|
db6bea0d5d | ||
|
|
f4154fe2bc | ||
|
|
961a5743d6 | ||
|
|
0f6b299e0a | ||
|
|
637b17e0b9 | ||
|
|
3eb1b27d60 | ||
|
|
02f3230a9c | ||
|
|
a5ce3408b0 | ||
|
|
7683f1efb0 | ||
|
|
afa532a750 | ||
|
|
7ca5782e60 | ||
|
|
18143ab6b5 | ||
|
|
82f1ac4cb2 | ||
|
|
69b87c0ccb | ||
|
|
6f0cdb5b97 | ||
|
|
ef50c701cb | ||
|
|
a243cfb7ae | ||
|
|
9d89197936 | ||
|
|
bd7fa6e785 | ||
|
|
440654fb93 | ||
|
|
44070c196d | ||
|
|
04da7f5830 | ||
|
|
67aa288c25 | ||
|
|
a30fdb526f | ||
|
|
88869f3cd0 | ||
|
|
d2ca25b995 | ||
|
|
c890ab3aeb | ||
|
|
b01e4112fb | ||
|
|
e6045c5c97 | ||
|
|
7cbad4fbb2 | ||
|
|
3bdbc21c23 | ||
|
|
04af778b98 | ||
|
|
08f4efaadc | ||
|
|
3f6f41ffe8 | ||
|
|
a953b395d7 | ||
|
|
bd5b7001e3 | ||
|
|
94de2d3a94 | ||
|
|
93b10df76e | ||
|
|
cc1cab6448 | ||
|
|
0a77e5a946 | ||
|
|
135eaf200a | ||
|
|
76b377799f | ||
|
|
a2a92531bd | ||
|
|
289d14f370 | ||
|
|
05d30933b6 | ||
|
|
9b93b8d87e | ||
|
|
b533e7cf24 | ||
|
|
4c3963aab5 | ||
|
|
3875e9aa7f | ||
|
|
d6fed1bd00 | ||
|
|
07a113bdb9 | ||
|
|
9e3e84cf2a | ||
|
|
fdb4c48593 | ||
|
|
d5dc286d13 | ||
|
|
2b83ffad0b | ||
|
|
dbbdf83c39 | ||
|
|
348e89d8a3 | ||
|
|
4059cf50ed | ||
|
|
2ba1d40f19 | ||
|
|
37543416ed | ||
|
|
53ff9afe80 | ||
|
|
d0de3a7d97 | ||
|
|
eac04856ed | ||
|
|
a4a534944b | ||
|
|
5a67b32160 | ||
|
|
c817083b61 | ||
|
|
0eb838509b | ||
|
|
052f516918 | ||
|
|
20af349b1b | ||
|
|
1efaab92e8 | ||
|
|
636b3c4267 | ||
|
|
f2d646bb0c | ||
|
|
0338848a7f | ||
|
|
362e804fb6 | ||
|
|
0fc6ed8aa3 | ||
|
|
767a283f4b | ||
|
|
010f388df5 | ||
|
|
52f61ac4cb | ||
|
|
58b93fac84 | ||
|
|
4a56d834ed | ||
|
|
7d2c14cae3 | ||
|
|
101fe24d77 | ||
|
|
1882eb7b37 | ||
|
|
97e2743ae9 | ||
|
|
1b3fd0cf73 | ||
|
|
808c8dfc1a | ||
|
|
7867837834 | ||
|
|
db836f41a9 | ||
|
|
4d10c53e59 | ||
|
|
410ba0660b | ||
|
|
9d55638411 | ||
|
|
f1e04a539d | ||
|
|
043a7f94b0 | ||
|
|
487b386bee | ||
|
|
9658cc0859 | ||
|
|
23f358695b | ||
|
|
edd75f4bdb | ||
|
|
caba4b231b | ||
|
|
98c8af19ed | ||
|
|
0d952709b4 | ||
|
|
8778bf2b28 | ||
|
|
0277169641 | ||
|
|
a029c5d871 | ||
|
|
d6fb41bce6 | ||
|
|
0cacb9118f | ||
|
|
ed5c69af14 | ||
|
|
bcc6c6e2c6 | ||
|
|
0dd68b1e81 | ||
|
|
499218f976 | ||
|
|
af358c9965 | ||
|
|
33579dc934 | ||
|
|
23c28164c0 | ||
|
|
aee04a0f74 | ||
|
|
3a3503b632 | ||
|
|
d151498f85 | ||
|
|
5afe58542b | ||
|
|
4f32f4e0d7 | ||
|
|
e48f970abe | ||
|
|
b4b49144dc | ||
|
|
e91bbb75ff | ||
|
|
551790e5ae | ||
|
|
4c4e68487d | ||
|
|
5e7c3ee96a | ||
|
|
39130c55fa | ||
|
|
6119fc904c | ||
|
|
6d4e625e89 | ||
|
|
98f324761f | ||
|
|
d14c98e28f | ||
|
|
55b37e484c | ||
|
|
84cc382d48 | ||
|
|
2ec3e75dca | ||
|
|
28acab4b3a | ||
|
|
1b1a666ccf | ||
|
|
e926e1ee86 | ||
|
|
743c1e8853 | ||
|
|
4108c9dbaa | ||
|
|
25bfb56460 | ||
|
|
5708d4f31c | ||
|
|
a4a0d692f0 | ||
|
|
8804906b5d | ||
|
|
63eb81fbeb | ||
|
|
b73026b937 | ||
|
|
44a61721ee | ||
|
|
68f8e2ae5a | ||
|
|
0ee71dbd92 | ||
|
|
fc87a33255 | ||
|
|
4132e4cebc | ||
|
|
8916a88390 | ||
|
|
da4a769002 | ||
|
|
b14793005e | ||
|
|
dff035110f | ||
|
|
9f77e42f5f | ||
|
|
44dc341e4a | ||
|
|
9892bb6658 | ||
|
|
f68cfb9c58 | ||
|
|
9569847690 | ||
|
|
354920500f | ||
|
|
599882a885 | ||
|
|
192168ef15 | ||
|
|
bb33b5c8d0 | ||
|
|
201cfc587d | ||
|
|
af45d98df0 | ||
|
|
337ff2e6bd | ||
|
|
03b10ac5ef | ||
|
|
c6f43ef9cc | ||
|
|
09262868a3 | ||
|
|
86dd3b88c0 | ||
|
|
4bc176280d | ||
|
|
053493520e | ||
|
|
ee5f1a79fc | ||
|
|
704d9e15a2 | ||
|
|
5448f00a6d | ||
|
|
28eb3d449c | ||
|
|
1d172202f8 | ||
|
|
b4d30eaba5 | ||
|
|
c90a92fda6 | ||
|
|
b7422e9931 | ||
|
|
1e37e9ac1c | ||
|
|
0dfe7d149f | ||
|
|
6f2321b41c | ||
|
|
9f2b13aabe | ||
|
|
8aa5690acf | ||
|
|
0b0be56c0d | ||
|
|
8f1bd33807 | ||
|
|
630c5f2b6d | ||
|
|
7252ddfc0d | ||
|
|
bf53dea6df | ||
|
|
55dba4c0eb | ||
|
|
c5e6c40a06 | ||
|
|
398093ef4f | ||
|
|
59ae7414a7 | ||
|
|
600d1bd9e1 | ||
|
|
a3b15de38b | ||
|
|
b464ab3180 | ||
|
|
b04942dbcb | ||
|
|
6417022857 | ||
|
|
f91fbe52c0 | ||
|
|
b078f0a747 | ||
|
|
957539756e | ||
|
|
e5aff1e66b | ||
|
|
92d25bc9f3 | ||
|
|
ff73b0956d | ||
|
|
780ac6ec8a | ||
|
|
89b31c4967 | ||
|
|
fdcb82ad02 | ||
|
|
8a1c44e96e | ||
|
|
33e716d1e5 | ||
|
|
9065e47df6 | ||
|
|
64fec00ede | ||
|
|
83a76590cf | ||
|
|
787f1e8e08 | ||
|
|
595227c386 | ||
|
|
1ac3b1925f | ||
|
|
76c1df10b4 | ||
|
|
177988e86d | ||
|
|
f22af6e14b | ||
|
|
01d6cdfc43 | ||
|
|
f25c8a8df6 | ||
|
|
20b1954193 | ||
|
|
373e0a1a22 | ||
|
|
f288e5cc2c | ||
|
|
dbcf325eb1 | ||
|
|
faa97128a4 | ||
|
|
b94b4dba1a | ||
|
|
5347037637 | ||
|
|
2541eb1449 | ||
|
|
4da7095865 | ||
|
|
2f3f1a1b13 | ||
|
|
67623fd1a6 | ||
|
|
83fd0dda3b | ||
|
|
94d5b80d20 | ||
|
|
cbf022c73d | ||
|
|
7463f1fa2d | ||
|
|
9142ab218c | ||
|
|
15e548365b | ||
|
|
135c9b9afc | ||
|
|
f33c5f3ddc | ||
|
|
b9fe25b0a9 | ||
|
|
15e1a9408a | ||
|
|
62f0273571 | ||
|
|
315842b936 | ||
|
|
0526cdde81 | ||
|
|
c9a7bb05bd | ||
|
|
5c243e25e1 | ||
|
|
002fedeae6 | ||
|
|
74e4f6e1df | ||
|
|
26434c99d8 | ||
|
|
2584651030 | ||
|
|
8c691d0fd7 | ||
|
|
7f4cc5dfaf | ||
|
|
02c503576d | ||
|
|
9f556ad9a4 | ||
|
|
6ee2d15a90 | ||
|
|
9adc896f04 | ||
|
|
051a8a8e1c | ||
|
|
4f79a317b4 | ||
|
|
212ed8bddc | ||
|
|
ae1cd6da4d | ||
|
|
36349e1f01 | ||
|
|
a027f080b0 | ||
|
|
4b85515bac | ||
|
|
9a7b48f27e | ||
|
|
bed0fa793c | ||
|
|
7654be7977 | ||
|
|
f1ae226e32 | ||
|
|
50da547f67 | ||
|
|
7688ddf327 | ||
|
|
824c4092a7 | ||
|
|
71a94c5d62 | ||
|
|
4a2f77bfdb | ||
|
|
fe26c20882 | ||
|
|
dfe8d2ec06 | ||
|
|
07d423068a | ||
|
|
877a862d68 | ||
|
|
c068c4d929 | ||
|
|
1fe4b64ddd | ||
|
|
b29963ef26 | ||
|
|
98e09f1168 | ||
|
|
5648bed0a0 | ||
|
|
b030b73a14 | ||
|
|
f1af020f0b | ||
|
|
53d30d3321 | ||
|
|
119e53a66c | ||
|
|
51f3794e64 | ||
|
|
b26b46651d | ||
|
|
12f2dc7db6 | ||
|
|
aee2b8e40c | ||
|
|
6a8fac0ba2 | ||
|
|
9d4f2273d8 | ||
|
|
51d38c9391 | ||
|
|
6f5e2a2f1a | ||
|
|
228321454c | ||
|
|
be1982225c | ||
|
|
d4eea2ebc8 | ||
|
|
a519dc8f14 | ||
|
|
bc21b9c945 | ||
|
|
c887874961 | ||
|
|
b30e5bd34c | ||
|
|
d229fb7708 | ||
|
|
7b6f333c3f | ||
|
|
0d6e7a72a2 | ||
|
|
2a45783cc2 | ||
|
|
e562b65e66 | ||
|
|
2329287f3c | ||
|
|
1b8bb5448b | ||
|
|
fa5e2d7b3a | ||
|
|
b9ff08b9a5 | ||
|
|
5dc0432fc7 | ||
|
|
6290b4cf71 | ||
|
|
de8b6c5bfb | ||
|
|
93f6ee0412 | ||
|
|
63f28c106a | ||
|
|
f5c8b894ca | ||
|
|
b5c8f502ad | ||
|
|
c6eabcf7ce | ||
|
|
0cc56bc787 | ||
|
|
bfeb0970bc | ||
|
|
b3d35a601e | ||
|
|
353688ee5b | ||
|
|
a933191cd0 | ||
|
|
dde96079e3 | ||
|
|
7e6508267e | ||
|
|
4cc1e3f47b | ||
|
|
0945d33ff2 | ||
|
|
3aacbcdb37 | ||
|
|
ea5bc281f9 | ||
|
|
f0e8449149 | ||
|
|
703d80b719 | ||
|
|
67b41209c1 | ||
|
|
44ed806da1 | ||
|
|
fa59701f5c | ||
|
|
dbfdf63d22 | ||
|
|
841c98b858 | ||
|
|
015eab7974 | ||
|
|
a971e1ba67 | ||
|
|
65106a18f4 | ||
|
|
13191093e7 | ||
|
|
01052bbc0b | ||
|
|
dd369e18f2 | ||
|
|
4a3d912ba9 | ||
|
|
1f8cf2de0d | ||
|
|
f4faeb8479 | ||
|
|
4bf36831b4 | ||
|
|
4f58c70b00 | ||
|
|
5fe33269d2 | ||
|
|
771b041645 | ||
|
|
167f55f1d3 | ||
|
|
aabe093cfe | ||
|
|
db1d56a8ef | ||
|
|
95489c2539 | ||
|
|
d369a468ff | ||
|
|
a2530b7272 | ||
|
|
2a3e260234 | ||
|
|
493206c12a | ||
|
|
83d1197832 | ||
|
|
69b84b7584 | ||
|
|
3863cf4a42 | ||
|
|
388571cb5e | ||
|
|
f7315561f2 | ||
|
|
fbd080ecae | ||
|
|
df9952a4c8 | ||
|
|
1cd44ac9c0 | ||
|
|
c0d4f4ab99 | ||
|
|
3005e4a718 | ||
|
|
7bd113557b | ||
|
|
0eff6837dd | ||
|
|
a906f814fb | ||
|
|
acab725c7e | ||
|
|
a3b085d4b7 | ||
|
|
9ea8891a59 | ||
|
|
3602d45186 | ||
|
|
3139186505 | ||
|
|
3d2b5512ba | ||
|
|
b14ae4dcb4 | ||
|
|
996d3345f6 | ||
|
|
0986c6a783 | ||
|
|
a753fd4b1d | ||
|
|
0b947901d3 | ||
|
|
0518a87a79 | ||
|
|
9d486605d3 | ||
|
|
fa33704ff0 | ||
|
|
a527e55a74 | ||
|
|
b2ee697ab9 | ||
|
|
3ade7a2cac | ||
|
|
2ae898ea7c | ||
|
|
8d4eb0142f | ||
|
|
2aebb524d7 | ||
|
|
45762c3ccb | ||
|
|
df879412d5 | ||
|
|
3ef594796d | ||
|
|
cc401092a5 | ||
|
|
f5fc01366a | ||
|
|
c99b8aa559 | ||
|
|
a0cc8f2dda | ||
|
|
30df5a0963 | ||
|
|
3fa2ab6862 | ||
|
|
ece8f080d7 | ||
|
|
3e88c73140 | ||
|
|
701ea4215a | ||
|
|
97fdf21496 | ||
|
|
3ae0077d5e | ||
|
|
da54974341 | ||
|
|
bf00d4c687 | ||
|
|
b15aa7ba56 | ||
|
|
4c9b609f0c | ||
|
|
2c820ba0df | ||
|
|
f7a42dde5a | ||
|
|
acf0b25e8c | ||
|
|
2e3d97928b | ||
|
|
601c59d394 | ||
|
|
51c8b889ee | ||
|
|
ee879841ba | ||
|
|
d30bb929ff | ||
|
|
71bcefe350 | ||
|
|
ab31693964 | ||
|
|
7366bd79d1 | ||
|
|
937781d840 | ||
|
|
baf53769a6 | ||
|
|
b3c84c59af | ||
|
|
6b12c60a4a | ||
|
|
43468c4b90 | ||
|
|
20e539a883 | ||
|
|
f7a6b58a10 | ||
|
|
1a59e5fa11 | ||
|
|
22fb2b873b | ||
|
|
13f2535fab | ||
|
|
d39e6cbbd9 | ||
|
|
049d767ace | ||
|
|
e0559f3415 | ||
|
|
b72e3d6dd8 | ||
|
|
be833653e8 | ||
|
|
a995cc6401 | ||
|
|
28116a604f | ||
|
|
c5bef64daa | ||
|
|
0cd3097d7e | ||
|
|
3ff902eb56 | ||
|
|
f69eaf5405 | ||
|
|
874d839f47 | ||
|
|
407bca27a2 | ||
|
|
389a862757 | ||
|
|
c2fea75aae | ||
|
|
06e3a41095 | ||
|
|
a4d9dfd490 | ||
|
|
aa96ca4a62 | ||
|
|
d1c17f2c59 | ||
|
|
c44962a5b0 | ||
|
|
0f3d4cbafc | ||
|
|
deb1259149 | ||
|
|
a796effbdd | ||
|
|
f9a8c36158 | ||
|
|
7327b25b64 | ||
|
|
ea34d17fdd | ||
|
|
ba3cf1fe35 | ||
|
|
fdae722adc | ||
|
|
d4e67a01a0 | ||
|
|
5f1eaa4611 | ||
|
|
b2cc52610e | ||
|
|
2b9407e13d | ||
|
|
ee1fddf972 | ||
|
|
d09b64080e | ||
|
|
b1690d7f32 | ||
|
|
58e0d72c13 | ||
|
|
dfd31bd247 | ||
|
|
76cc4a32d2 | ||
|
|
f79008c18f | ||
|
|
f92bea4bfd | ||
|
|
f61b9d2035 | ||
|
|
efd609ebe7 | ||
|
|
859f428c3e | ||
|
|
e03f35797e | ||
|
|
bdda2470fb | ||
|
|
da21c610bd | ||
|
|
f8d3d8c6fc | ||
|
|
1423b428d8 | ||
|
|
0306962d6d | ||
|
|
c76ad39943 | ||
|
|
7dc62aff61 | ||
|
|
5440f82022 | ||
|
|
44f8628aea | ||
|
|
61b6f5694f | ||
|
|
c6899cb715 | ||
|
|
c6a294796c | ||
|
|
b83741bf21 | ||
|
|
8ae628e570 | ||
|
|
6bed3e959c | ||
|
|
80ce9edaec | ||
|
|
2f583f8967 | ||
|
|
472ca09dc5 | ||
|
|
88385f8736 | ||
|
|
359efdee4d | ||
|
|
1e717cffbe | ||
|
|
33d88815a6 | ||
|
|
abc09bbabb | ||
|
|
a382ba024f | ||
|
|
fc7e602724 | ||
|
|
daac754e42 | ||
|
|
6ea4362c53 | ||
|
|
12e1a5dbdf | ||
|
|
d82cd74380 | ||
|
|
d0bdf67685 | ||
|
|
4452a7ee06 | ||
|
|
4768eea4fa | ||
|
|
f41a8d57ca | ||
|
|
7daaefcf5c | ||
|
|
39dc770d88 | ||
|
|
f81e08e5fd | ||
|
|
bbdb6b10fc | ||
|
|
3d2b1daa96 | ||
|
|
79c4d05226 | ||
|
|
84845d7832 | ||
|
|
4515b1f6d2 | ||
|
|
25008c6454 | ||
|
|
b059448256 | ||
|
|
de32a541f1 | ||
|
|
b585ac4788 | ||
|
|
ee253f92e5 | ||
|
|
68bd6dc503 | ||
|
|
48b2a457c7 | ||
|
|
48e59ea8bb | ||
|
|
70d65f78d8 | ||
|
|
717639dc8d | ||
|
|
2e7ebeae81 | ||
|
|
db6bdd2311 | ||
|
|
e85c34844d | ||
|
|
17ec834d87 | ||
|
|
1b6ab4f3f0 | ||
|
|
7d3a9fff8d | ||
|
|
6a17935681 | ||
|
|
bc2667237a | ||
|
|
89df94327d | ||
|
|
bc56e2b4ba | ||
|
|
938a68bc2a | ||
|
|
4325ec2b59 | ||
|
|
1422a9e84c | ||
|
|
fa2500d8c9 | ||
|
|
344f3c012a | ||
|
|
4c8b98ea4c | ||
|
|
1cb5442ce3 | ||
|
|
054b257d39 | ||
|
|
e4e288ebcb | ||
|
|
4ad00ee725 | ||
|
|
143d7cd52e | ||
|
|
06fbbd1df1 | ||
|
|
33655d9e94 | ||
|
|
53c7e32216 | ||
|
|
3cd169e19f | ||
|
|
9a3c92dfdd | ||
|
|
2d7c2db7be | ||
|
|
e0bb135206 | ||
|
|
aefd1eeaa3 | ||
|
|
4b03b4313b | ||
|
|
5d04d2383b | ||
|
|
f709f8eb29 | ||
|
|
6a9cc7a7a1 | ||
|
|
86fe9c5037 | ||
|
|
5e833dab17 | ||
|
|
deba00eedf | ||
|
|
7219410546 | ||
|
|
d9652bb2f0 | ||
|
|
da2507f700 | ||
|
|
5612ca384a | ||
|
|
d39df4e5fa | ||
|
|
d358af9fbb | ||
|
|
66b6215a0e | ||
|
|
ab3475a25f | ||
|
|
738398df3d | ||
|
|
2f58de6038 | ||
|
|
6757b94bd1 | ||
|
|
9f4dc6ec2d | ||
|
|
47df868ed5 | ||
|
|
26dfcee77a | ||
|
|
24d0a5b796 | ||
|
|
5b6eedc607 | ||
|
|
02faeb18aa | ||
|
|
e330ebb78d | ||
|
|
851c3cb594 | ||
|
|
0e6630be74 | ||
|
|
1842d49f8d | ||
|
|
fd82bd75df | ||
|
|
dfaf0f6ca6 | ||
|
|
19664bea78 | ||
|
|
a723138d2f | ||
|
|
085709a53e | ||
|
|
2d1bf6b14b | ||
|
|
bd8d685e3c | ||
|
|
dd9682aab3 | ||
|
|
db6bfbd546 | ||
|
|
450dba9e81 | ||
|
|
f552786c23 | ||
|
|
e0a9a4a9fa | ||
|
|
b2029c6db9 | ||
|
|
d9eae78c3b | ||
|
|
eaeadfd7ba | ||
|
|
2d0668d1bf | ||
|
|
10ea473011 | ||
|
|
0946c002c0 | ||
|
|
0c6104c9d7 | ||
|
|
898736b25a | ||
|
|
f2fd13ecca | ||
|
|
ee4d9b03f9 | ||
|
|
5867cfc88b | ||
|
|
92a8b0a1fd | ||
|
|
c0d6d99043 | ||
|
|
eeab00cd0b | ||
|
|
2afa7e18e6 | ||
|
|
e86cde7512 | ||
|
|
4095fec3db | ||
|
|
1c108928af | ||
|
|
8830798da2 | ||
|
|
4b6e88cd29 | ||
|
|
b6dfeae48a | ||
|
|
1f90277915 | ||
|
|
3ae438ee6c | ||
|
|
e6b0f25f0a | ||
|
|
504871e109 | ||
|
|
2b8a5202eb | ||
|
|
341f0a062d | ||
|
|
7492be0fc9 | ||
|
|
93b578bb00 | ||
|
|
fc6b07dbe5 | ||
|
|
c6eda45864 | ||
|
|
8dd182e4d2 | ||
|
|
6e0b503c35 | ||
|
|
fa413e49b5 | ||
|
|
ed9180200e | ||
|
|
6f08294e79 | ||
|
|
bec1ec0259 | ||
|
|
7383531eab | ||
|
|
54693e7a12 | ||
|
|
a939d12e71 | ||
|
|
db8ac530df | ||
|
|
75c9cbc630 | ||
|
|
e513e46e11 | ||
|
|
55fb4bea9d | ||
|
|
ca2eba5a14 | ||
|
|
cec8e3867e | ||
|
|
516d64e4f2 | ||
|
|
b92a74bd6c | ||
|
|
3e58004ddd | ||
|
|
6e653f7387 | ||
|
|
df5b462925 | ||
|
|
7b2425228a | ||
|
|
a29b1b40f2 | ||
|
|
8f3754f872 | ||
|
|
d082200289 | ||
|
|
bc3f965b87 | ||
|
|
48ca97d905 | ||
|
|
2566410a72 | ||
|
|
77c5691c4c | ||
|
|
59c44e54ab | ||
|
|
893d63ed47 | ||
|
|
f828cb8a7d | ||
|
|
b5b9db6c75 | ||
|
|
196bfdfe5c | ||
|
|
7b8adc2d91 | ||
|
|
cb280bd480 | ||
|
|
e76c40c8b5 | ||
|
|
2fd476bb13 | ||
|
|
9abf6078b7 | ||
|
|
ac507dca84 | ||
|
|
1e12b93433 | ||
|
|
3d7dd59c99 | ||
|
|
17c86a1da2 | ||
|
|
609b055580 | ||
|
|
fd90fc86d1 | ||
|
|
30ef80b4ee | ||
|
|
327ddeea8b | ||
|
|
f51dbd1565 | ||
|
|
666b5af2d5 | ||
|
|
ef38f6aacc | ||
|
|
e2175c833f | ||
|
|
6bea7bde72 | ||
|
|
f37bf932ec | ||
|
|
2e8095fe44 | ||
|
|
76ff648a2a | ||
|
|
5a396d7d35 | ||
|
|
be364e12ca | ||
|
|
5a123f1a8f | ||
|
|
fea0d6a735 | ||
|
|
fe01d283db | ||
|
|
1f38b1931a | ||
|
|
523b8415df | ||
|
|
756cf4ec6a | ||
|
|
573ad0daf3 | ||
|
|
22cfe18c24 | ||
|
|
6ba5362659 | ||
|
|
af08b0b675 | ||
|
|
da682e5636 | ||
|
|
07f2fe5b56 | ||
|
|
09291f14f1 | ||
|
|
289b6b5a0f | ||
|
|
02baf6d923 | ||
|
|
86d0d41178 | ||
|
|
229c7fc663 | ||
|
|
cdb6c5425f | ||
|
|
c3e3953164 | ||
|
|
f863c8be7c | ||
|
|
53de8cb794 | ||
|
|
9001dfc1a6 | ||
|
|
d94f103add | ||
|
|
384797b80e | ||
|
|
83ffc615bc | ||
|
|
f371034116 | ||
|
|
75a3b5bcba | ||
|
|
23bc4d374b | ||
|
|
6edc90e4ea | ||
|
|
9c7c0bbf4c | ||
|
|
b819d901b7 | ||
|
|
031abf195d | ||
|
|
0f4c27c081 | ||
|
|
8acbe5cc0d | ||
|
|
f00ed27274 | ||
|
|
eedc58340d | ||
|
|
1d0f124cbc | ||
|
|
88599b6428 | ||
|
|
50ca4fad30 | ||
|
|
a8ca4f9fde | ||
|
|
65fbc99f4c | ||
|
|
697e8bf6f8 | ||
|
|
3f2c5a13af | ||
|
|
fac542db60 | ||
|
|
4f46462754 | ||
|
|
0621a48205 | ||
|
|
611e77898a | ||
|
|
6182fe891b | ||
|
|
c3af42efca | ||
|
|
664426f7e2 | ||
|
|
d98bb0f6d0 | ||
|
|
ae85d5a755 | ||
|
|
52b8397886 | ||
|
|
144c6f6b53 | ||
|
|
c32d5a126d | ||
|
|
9daaf002c2 | ||
|
|
260f978aff | ||
|
|
72cafc1cf8 | ||
|
|
f6e46acc41 | ||
|
|
8fea4bc38f | ||
|
|
8f6c34d044 | ||
|
|
9f6b9a9313 | ||
|
|
9bed2d0fca | ||
|
|
e21bd8e46c | ||
|
|
ef92bb6bd4 | ||
|
|
983b4008cc | ||
|
|
1ad834bfac | ||
|
|
b4fc1adeed | ||
|
|
4977987288 | ||
|
|
c53d1780c1 | ||
|
|
8f0e282e8f | ||
|
|
df8e233a89 | ||
|
|
80bb89ccf0 | ||
|
|
f2f426fbb2 | ||
|
|
2bfaa338b2 | ||
|
|
b25c8c8541 | ||
|
|
e119317edf | ||
|
|
e5aa7c54b5 | ||
|
|
7b3707171c | ||
|
|
3c09f6941a | ||
|
|
22099ccb66 | ||
|
|
eb829fc768 | ||
|
|
1166f561e5 | ||
|
|
969289cf28 | ||
|
|
4e3291287a | ||
|
|
b79172c18a | ||
|
|
f5f21f0009 | ||
|
|
43fc879fc4 | ||
|
|
35aad960a6 | ||
|
|
4546971f23 | ||
|
|
af4fd654bc | ||
|
|
8f8b373eb9 | ||
|
|
c85cdf714f | ||
|
|
ce58173950 | ||
|
|
b4776c3c14 | ||
|
|
fd0c8a4768 | ||
|
|
48b8342353 | ||
|
|
0551b2dfbc | ||
|
|
04d8e9cf36 | ||
|
|
6fbaf4c892 | ||
|
|
d1b4cd56b4 | ||
|
|
18fca52e4d | ||
|
|
cd9f83f865 | ||
|
|
2de22894b1 | ||
|
|
5c6efbf71d | ||
|
|
25976c0ed3 | ||
|
|
820a51a636 | ||
|
|
ae2095234f | ||
|
|
2451896037 | ||
|
|
c50c1521a3 | ||
|
|
31a5b7167e | ||
|
|
64136f5347 | ||
|
|
0a827833da | ||
|
|
b482760982 | ||
|
|
5106172ae6 | ||
|
|
54275a2506 | ||
|
|
e7e0f10fce | ||
|
|
412b2e84d7 | ||
|
|
0c628204a8 | ||
|
|
cd6833d3d6 | ||
|
|
b04df26c64 | ||
|
|
2b9984f041 | ||
|
|
c9e070f3f4 | ||
|
|
5dafda3b86 | ||
|
|
1e243c552a | ||
|
|
f27dc815f3 | ||
|
|
245f7e767b | ||
|
|
1fabd3245c | ||
|
|
aceeb6aaf2 | ||
|
|
8d7baac949 | ||
|
|
51d9424908 | ||
|
|
256b691b10 | ||
|
|
c8ec72d489 | ||
|
|
f18f3a9c85 | ||
|
|
b2c1a515be | ||
|
|
475120cf18 | ||
|
|
807e066f59 |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false
|
||||
}]
|
||||
],
|
||||
"plugins": ["transform-runtime"],
|
||||
"env": {
|
||||
"production": {
|
||||
"plugins": [
|
||||
"lodash"
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"plugins": [
|
||||
"rewire"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,21 @@ charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.py]
|
||||
max_line_length = 120
|
||||
quote_type = single
|
||||
|
||||
[*.{scss,js,html}]
|
||||
max_line_length = 120
|
||||
indent_style = space
|
||||
quote_type = double
|
||||
|
||||
[*.js]
|
||||
max_line_length = 120
|
||||
quote_type = single
|
||||
|
||||
[*.rst]
|
||||
max_line_length = 80
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
jquery: true,
|
||||
jasmine: true,
|
||||
es6: true
|
||||
},
|
||||
globals: {
|
||||
CMS: true,
|
||||
Promise: true,
|
||||
__CMS_VERSION__: true
|
||||
},
|
||||
root: true,
|
||||
ecmaFeatures: {
|
||||
modules: true
|
||||
},
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
plugins: ['compat'],
|
||||
settings: {
|
||||
polyfills: ['document-currentscript']
|
||||
},
|
||||
rules: {
|
||||
// Possible Errors
|
||||
'comma-dangle': [2, 'never'],
|
||||
'no-cond-assign': 2,
|
||||
'no-console': 1,
|
||||
'no-constant-condition': 2,
|
||||
'no-control-regex': 2,
|
||||
'no-debugger': 2,
|
||||
'no-dupe-args': 2,
|
||||
'no-dupe-keys': 2,
|
||||
'no-duplicate-case': 2,
|
||||
'no-empty-character-class': 2,
|
||||
'no-empty': ['error', { allowEmptyCatch: true }],
|
||||
'no-ex-assign': 2,
|
||||
'no-extra-boolean-cast': 2,
|
||||
'no-extra-parens': 0,
|
||||
'no-extra-semi': 2,
|
||||
'no-func-assign': 2,
|
||||
'no-inner-declarations': 2,
|
||||
'no-invalid-regexp': 2,
|
||||
'no-irregular-whitespace': 2,
|
||||
'no-negated-in-lhs': 2,
|
||||
'no-obj-calls': 2,
|
||||
'no-regex-spaces': 2,
|
||||
'no-sparse-arrays': 2,
|
||||
'no-unexpected-multiline': 2,
|
||||
'no-unreachable': 2,
|
||||
'use-isnan': 2,
|
||||
'valid-jsdoc': [
|
||||
2,
|
||||
{
|
||||
requireReturn: false,
|
||||
requireParamDescription: false,
|
||||
requireReturnDescription: false,
|
||||
prefer: {
|
||||
return: 'returns'
|
||||
}
|
||||
}
|
||||
],
|
||||
'valid-typeof': 2,
|
||||
|
||||
// Best Practices
|
||||
'accessor-pairs': 2,
|
||||
'block-scoped-var': 2,
|
||||
complexity: ['error', { max: 10 }],
|
||||
'consistent-return': 0,
|
||||
curly: 2,
|
||||
'default-case': 2,
|
||||
'dot-location': [2, 'property'],
|
||||
'dot-notation': 2,
|
||||
eqeqeq: 2,
|
||||
'guard-for-in': 2,
|
||||
'no-alert': 2,
|
||||
'no-caller': 2,
|
||||
'no-case-declarations': 2,
|
||||
'no-div-regex': 2,
|
||||
'no-else-return': 1,
|
||||
'no-empty-pattern': 2,
|
||||
'no-eq-null': 2,
|
||||
'no-eval': 2,
|
||||
'no-extend-native': 2,
|
||||
'no-extra-bind': 2,
|
||||
'no-fallthrough': 2,
|
||||
'no-floating-decimal': 2,
|
||||
'no-implicit-coercion': 0,
|
||||
'no-implied-eval': 2,
|
||||
'no-invalid-this': 0,
|
||||
'no-iterator': 2,
|
||||
'no-labels': 2,
|
||||
'no-lone-blocks': 2,
|
||||
'no-loop-func': 2,
|
||||
'no-magic-numbers': [
|
||||
'error',
|
||||
{
|
||||
ignore: [0, -1, 1, 2],
|
||||
ignoreArrayIndexes: true
|
||||
}
|
||||
],
|
||||
'no-multi-spaces': 2,
|
||||
'no-multi-str': 0,
|
||||
'no-native-reassign': 2,
|
||||
'no-new-func': 2,
|
||||
'no-new-wrappers': 2,
|
||||
'no-new': 0,
|
||||
'no-octal-escape': 2,
|
||||
'no-octal': 2,
|
||||
'no-param-reassign': 2,
|
||||
'no-process-env': 0,
|
||||
'no-proto': 2,
|
||||
'no-redeclare': 2,
|
||||
'no-return-assign': 2,
|
||||
'no-script-url': 2,
|
||||
'no-self-compare': 2,
|
||||
'no-sequences': 2,
|
||||
'no-throw-literal': 2,
|
||||
'no-unused-expressions': [2, { allowShortCircuit: true }],
|
||||
'no-useless-call': 2,
|
||||
'no-useless-concat': 2,
|
||||
'no-void': 2,
|
||||
'no-warning-comments': 0,
|
||||
'no-with': 2,
|
||||
radix: 2,
|
||||
'vars-on-top': 0, // FIXME should be enabled at some point
|
||||
'wrap-iife': [2, 'inside'],
|
||||
yoda: [2, 'never', { exceptRange: true }],
|
||||
|
||||
// Strict Mode
|
||||
strict: 0, // not required with webpack
|
||||
|
||||
// Variables
|
||||
'init-declarations': 0,
|
||||
'no-catch-shadow': 2,
|
||||
'no-delete-var': 2,
|
||||
'no-label-var': 2,
|
||||
'no-shadow-restricted-names': 2,
|
||||
'no-shadow': 2,
|
||||
'no-undef-init': 2,
|
||||
'no-undef': 2,
|
||||
'no-undefined': 0,
|
||||
'no-unused-vars': 2,
|
||||
'no-use-before-define': 2,
|
||||
|
||||
// Stylistic Issues
|
||||
'array-bracket-spacing': [2, 'never'],
|
||||
'block-spacing': 2,
|
||||
'brace-style': [2, '1tbs'],
|
||||
camelcase: 0,
|
||||
'comma-spacing': [2, { before: false, after: true }],
|
||||
'comma-style': [2, 'last'],
|
||||
'computed-property-spacing': [2, 'never'],
|
||||
'consistent-this': [2, 'that'],
|
||||
'eol-last': 2,
|
||||
'func-names': 0,
|
||||
'func-style': 0,
|
||||
'id-length': 0,
|
||||
'id-match': 0,
|
||||
indent: [
|
||||
'error',
|
||||
4,
|
||||
{
|
||||
SwitchCase: 1
|
||||
}
|
||||
],
|
||||
'jsx-quotes': 0,
|
||||
'key-spacing': [2, { beforeColon: false, afterColon: true }],
|
||||
'linebreak-style': [2, 'unix'],
|
||||
'lines-around-comment': 0,
|
||||
'max-nested-callbacks': [2, 5],
|
||||
'new-cap': 2,
|
||||
'new-parens': 2,
|
||||
'newline-after-var': 2,
|
||||
'no-array-constructor': 2,
|
||||
'no-continue': 2,
|
||||
'no-inline-comments': 0,
|
||||
'no-lonely-if': 2,
|
||||
'no-mixed-spaces-and-tabs': 2,
|
||||
'no-multiple-empty-lines': [2, { max: 2 }],
|
||||
'no-negated-condition': 2,
|
||||
'no-nested-ternary': 2,
|
||||
'no-new-object': 2,
|
||||
'no-restricted-syntax': 0,
|
||||
'no-spaced-func': 0,
|
||||
'no-ternary': 0,
|
||||
'no-trailing-spaces': 2,
|
||||
'no-underscore-dangle': 0,
|
||||
'no-unneeded-ternary': 2,
|
||||
'object-curly-spacing': [
|
||||
2,
|
||||
'always',
|
||||
{
|
||||
objectsInObjects: true,
|
||||
arraysInObjects: true
|
||||
}
|
||||
],
|
||||
'one-var': [2, 'never'],
|
||||
'operator-assignment': 2,
|
||||
'operator-linebreak': 0,
|
||||
'padded-blocks': 0,
|
||||
'quote-props': [2, 'as-needed'],
|
||||
quotes: [2, 'single', 'avoid-escape'],
|
||||
'require-jsdoc': 2,
|
||||
'semi-spacing': [2, { before: false, after: true }],
|
||||
semi: [2, 'always'],
|
||||
'sort-vars': 0,
|
||||
'keyword-spacing': 2,
|
||||
'space-before-blocks': 2,
|
||||
// FIXME reenable after running prettier on full codebase
|
||||
'space-before-function-paren': [0, { anonymous: 'never', named: 'never' }],
|
||||
'space-in-parens': [2, 'never'],
|
||||
'space-infix-ops': 2,
|
||||
'space-unary-ops': 2,
|
||||
'spaced-comment': 2,
|
||||
'wrap-regex': 0,
|
||||
|
||||
// ES6
|
||||
'arrow-parens': [2, 'as-needed'],
|
||||
|
||||
// Legacy
|
||||
'max-depth': [2, 4],
|
||||
'max-len': [2, 120],
|
||||
'max-params': [2, 3],
|
||||
'max-statements': 0,
|
||||
'no-bitwise': 2,
|
||||
'no-plusplus': 0,
|
||||
|
||||
'compat/compat': 2
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
##########################
|
||||
Contributing to django CMS
|
||||
##########################
|
||||
|
||||
Like every open-source project, django CMS always welcomes contributions.
|
||||
|
||||
|
||||
*******************************
|
||||
GitHub issues and pull requests
|
||||
*******************************
|
||||
|
||||
`GitHub issues <https://github.com/divio/django-cms/issues>`_ should only be used to report *suspected issues* in
|
||||
django CMS.
|
||||
|
||||
It should **not** be used for reporting *security issues* - please use `security@django-cms.org
|
||||
<security@django-cms.org>`_ for those.
|
||||
|
||||
For *help with django CMS*, please use our `users' email list <https://groups.google.com/forum/#!forum/django-cms>`_.
|
||||
|
||||
For *feature requests*, please use our `developers' email list
|
||||
<https://groups.google.com/forum/#!forum/django-cms-developers>`_.
|
||||
|
||||
|
||||
**************************
|
||||
Contribution documentation
|
||||
**************************
|
||||
|
||||
We maintain comprehensive `contribution documentation <http://docs.django-cms.org/en/latest/contributing/>`_ - please
|
||||
familiarise yourself with it.
|
||||
|
||||
|
||||
***************
|
||||
Security issues
|
||||
***************
|
||||
|
||||
Security issues should not be reported **anywhere** except via our dedicated and private `security@django-cms.org
|
||||
<security@django-cms.org>`_ email address.
|
||||
|
||||
If you think you have discovered a security issue in our code, please do not raise it on
|
||||
|
||||
* IRC
|
||||
* GitHub
|
||||
* Twitter
|
||||
* either of our email lists
|
||||
|
||||
or in any other public forum until we have had a chance to deal with it.
|
||||
|
||||
|
||||
***************
|
||||
Code of conduct
|
||||
***************
|
||||
|
||||
django CMS is governed by a `Code of Conduct
|
||||
<http://docs.django-cms.org/en/latest/contributing/code_of_conduct.html>`_. All participants in our community and its
|
||||
various forums are expected to abide by it.
|
||||
|
||||
|
||||
*********
|
||||
Community
|
||||
*********
|
||||
|
||||
You can join us online:
|
||||
|
||||
* in our IRC channel, #django-cms, on ``irc.freenode.net``. If you don't have an IRC client, you can
|
||||
`join our IRC channel using the KiwiIRC web client
|
||||
<https://kiwiirc.com/client/irc.freenode.net/django-cms>`_, which works pretty well.
|
||||
* on our `django CMS users email list <https://groups.google.com/forum/#!forum/django-cms>`_ for
|
||||
**general** django CMS questions and discussion
|
||||
* on our `django CMS developers email list
|
||||
<https://groups.google.com/forum/#!forum/django-cms-developers>`_ for discussions about the
|
||||
**development of django CMS**
|
||||
|
||||
You can also follow:
|
||||
|
||||
* the `Travis Continuous Integration build reports <https://travis-ci.org/divio/django-cms>`_
|
||||
* the `@djangocms`_ Twitter account for general announcements
|
||||
|
||||
.. _@djangocms : https://twitter.com/djangocms
|
||||
@@ -0,0 +1,30 @@
|
||||
<!-- Important!
|
||||
|
||||
Only use this form if you suspect that you have discovered an issue in django CMS.
|
||||
|
||||
Do not use this form for reporting security issues - please use security@django-cms.org for those.
|
||||
|
||||
For help with django CMS, please use our users' email list: https://groups.google.com/forum/#!forum/django-cms.
|
||||
|
||||
For feature requests, please use our developers' email list: https://groups.google.com/forum/#!forum/django-cms-developers.
|
||||
|
||||
-->
|
||||
|
||||
### Summary
|
||||
|
||||
<!-- If this is a security issue stop right here and email security@django-cms.org instead -->
|
||||
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
|
||||
|
||||
### Actual behaviour
|
||||
|
||||
|
||||
|
||||
### Environment
|
||||
|
||||
* Python version:
|
||||
* Django version:
|
||||
* django CMS version:
|
||||
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
If this is a security-related patch stop immediately!
|
||||
|
||||
See http://docs.django-cms.org/en/latest/contributing/development-policies.html
|
||||
-->
|
||||
|
||||
|
||||
### Summary
|
||||
|
||||
Fixes #
|
||||
|
||||
|
||||
|
||||
### Links to related discussion
|
||||
|
||||
|
||||
|
||||
### Proposed changes in this pull request
|
||||
|
||||
|
||||
### Documentation checklist
|
||||
|
||||
* [ ] I have updated CHANGELOG.txt if appropriate
|
||||
* [ ] I have updated the release notes document if appropriate, with:
|
||||
* [ ] general notes
|
||||
* [ ] bug-fixes
|
||||
* [ ] improvements/new features
|
||||
* [ ] backwards-incompatible changes
|
||||
* [ ] required upgrade steps
|
||||
* [ ] names of contributors
|
||||
* [ ] I have updated other documentation
|
||||
* [ ] I have added my name to the AUTHORS file
|
||||
* [ ] This PR's documentation has been approved by Daniele Procida
|
||||
@@ -23,6 +23,7 @@ dist
|
||||
.project
|
||||
.coverage
|
||||
.pydevproject
|
||||
.vscode
|
||||
.settings
|
||||
/*.egg-info/
|
||||
/*.egg/
|
||||
@@ -43,6 +44,8 @@ share/
|
||||
!.jshintrc
|
||||
cms/static/cms/compass_app_log.txt
|
||||
!cms/static/cms/js/dist
|
||||
cms/tests/frontend/coverage
|
||||
node_modules
|
||||
screenshots
|
||||
.idea
|
||||
.better_test.db
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"preset": "crockford",
|
||||
|
||||
"maximumLineLength": {
|
||||
"value": 120,
|
||||
"allowRegex": true,
|
||||
"allowUrlComments": true
|
||||
},
|
||||
"requireDotNotation": {
|
||||
"allExcept": ["keywords"]
|
||||
},
|
||||
"disallowQuotedKeysInObjects": null,
|
||||
"requireMultipleVarDecl": null,
|
||||
"disallowDanglingUnderscores": null,
|
||||
"requireVarDeclFirst": false
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"browser": true,
|
||||
"devel": true,
|
||||
"esnext": true,
|
||||
"expr": true,
|
||||
"bitwise": true,
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"indent": 4,
|
||||
"jquery": true,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"nonbsp": true,
|
||||
"quotmark": "single",
|
||||
"strict": true,
|
||||
"unused": true,
|
||||
"trailing": true,
|
||||
"undef": true
|
||||
}
|
||||
+117
-182
@@ -1,11 +1,10 @@
|
||||
language: python
|
||||
|
||||
python:
|
||||
- 3.6
|
||||
- 3.5
|
||||
- 3.4
|
||||
- 3.3
|
||||
- 2.7
|
||||
- 2.6
|
||||
|
||||
sudo: false
|
||||
|
||||
@@ -14,200 +13,136 @@ addons:
|
||||
packages:
|
||||
- enchant
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- $HOME/.pip/cache
|
||||
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: NWiSBCHFB6LbTMget2qLIdqZlx0zeu3j+Y7Lsqb8kuYXyT2IUBGFVedcGWuGv/9Mzypb80EQWtVTokA3/3QIbesqr29uG95pMPHiYWLdnTO6UHcLMcNXiSzhBGdRDZ40iHSVv2dDHs4GNwGOH5+UCA0z3j7SWmChuFbNXh+Vsqw=
|
||||
matrix:
|
||||
- DJANGO=1.9 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- DJANGO=1.9 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- DJANGO=1.9 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- DJANGO=1.9 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- DJANGO=1.9 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 AUTH_USER_MODEL='customuserapp.User'
|
||||
- DJANGO=1.8 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- DJANGO=1.8 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- DJANGO=1.8 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- DJANGO=1.8 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- DJANGO=1.8 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='customuserapp.User'
|
||||
- DJANGO=1.7 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0
|
||||
- DJANGO=1.7 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- DJANGO=1.7 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- DJANGO=1.7 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='customuserapp.User'
|
||||
# coveralls
|
||||
- secure: GzxbqfktWQkf6QQvz0OMk4zHbGI8QWcThTLUzEtktheInxwivOHnoJ1kQu2jVVrk0ZVPbOedE+Cn3QMfi6Wj+y6CREwIhfyzqZV+BIgYu/MpW7vT1BQGiN2suHjFOt/TJ90G41DlBRDc7FxGLqL1Mq8hsEdE0W+/Yszo3aMbp2w=
|
||||
# sauce labs username
|
||||
- secure: RotktnZ0AqeTDYfh2O472pPiolQJ2ZDRPUKGDajYWgnG2n94K7hBd4pnA1H/cd42sOyReC3lmmweWNSbG0DLD9+X+s0fwaqKcWGnYsLAcjOWaNXPMwvvgaivadT34JmS9Wv29zNudPL2A6zNw0CB+YVxUZIA4Cm9984AxbYJGSk=
|
||||
# sauce labs access token
|
||||
- secure: CbPfysSncBB2Ue+VOtLDa8xJvwKl73nJO647zt/9UvZ/3PilnZN9aZv2jHxGvCiFXcez+2AddKptMCcx/5EW5UfRkrWUDHrfLCULU2TfOjmufEGM1eOIXhiAun8WQ85LBzTAYy1r9D514cbU3Yzn3xGZwJljPE8JE4cx3MNN/qQ=
|
||||
# temporary solution until https://github.com/ariya/phantomjs/issues/13953 is resolved
|
||||
- PHANTOMJS_CDNURL=https://s3.amazonaws.com/aldryn-local-assets
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# FRONTEND
|
||||
- python: 3.6
|
||||
env: FRONTEND=1 UNIT=1
|
||||
- python: 3.6
|
||||
env: FRONTEND=1 LINT=1
|
||||
- python: 3.6
|
||||
env: FRONTEND=1 INTEGRATION=1 INTEGRATION_TESTS_BUCKET=1 DJANGO=1.11 DATABASE_URL='sqlite://localhost/testdb.sqlite'
|
||||
- python: 3.6
|
||||
env: FRONTEND=1 INTEGRATION=1 INTEGRATION_TESTS_BUCKET=2 DJANGO=1.11 DATABASE_URL='sqlite://localhost/testdb.sqlite'
|
||||
- python: 3.6
|
||||
env: FRONTEND=1 INTEGRATION=1 INTEGRATION_TESTS_BUCKET=3 DJANGO=1.11 DATABASE_URL='sqlite://localhost/testdb.sqlite'
|
||||
|
||||
# DJANGO 1.11
|
||||
- python: 2.7
|
||||
env: DJANGO=1.11 DATABASE_URL='sqlite://localhost/:memory:'
|
||||
- python: 3.4
|
||||
env: DJANGO=1.11 DATABASE_URL='sqlite://localhost/:memory:'
|
||||
- python: 3.5
|
||||
env: DJANGO=1.11 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test'
|
||||
- python: 3.6
|
||||
env: DJANGO=1.11 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test'
|
||||
- python: 3.6
|
||||
env: DJANGO=1.11 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- python: 3.6
|
||||
env: DJANGO=1.11 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='customuserapp.User'
|
||||
|
||||
# DJANGO 2.0
|
||||
- python: 3.4
|
||||
env: DJANGO=2.0 DATABASE_URL='sqlite://localhost/:memory:'
|
||||
- python: 3.5
|
||||
env: DJANGO=2.0 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test'
|
||||
- python: 3.6
|
||||
env: DJANGO=2.0 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test'
|
||||
- python: 3.6
|
||||
env: DJANGO=2.0 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- python: 3.6
|
||||
env: DJANGO=2.0 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='customuserapp.User'
|
||||
|
||||
# DJANGO 2.1
|
||||
- python: 3.5
|
||||
env: DJANGO=2.1 DATABASE_URL='sqlite://localhost/:memory:'
|
||||
- python: 3.5
|
||||
env: DJANGO=2.1 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test'
|
||||
- python: 3.6
|
||||
env: DJANGO=2.1 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test'
|
||||
- python: 3.6
|
||||
env: DJANGO=2.1 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- python: 3.6
|
||||
env: DJANGO=2.1 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='customuserapp.User'
|
||||
- python: 3.6
|
||||
env: TEST_DOCS=1 DJANGO=2.1 DATABASE_URL='sqlite://localhost/:memory:'
|
||||
|
||||
# DJANGO 2.2
|
||||
- python: 3.5
|
||||
env: DJANGO=2.2 DATABASE_URL='sqlite://localhost/:memory:'
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- python: 3.5
|
||||
env: DJANGO=2.2 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test'
|
||||
- python: 3.6
|
||||
env: DJANGO=2.2 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test'
|
||||
- python: 3.6
|
||||
env: DJANGO=2.2 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- python: 3.6
|
||||
env: DJANGO=2.2 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='customuserapp.User'
|
||||
- python: 3.6
|
||||
env: TEST_DOCS=1 DJANGO=2.2 DATABASE_URL='sqlite://localhost/:memory:'
|
||||
dist: xenial
|
||||
sudo: true
|
||||
allow_failures:
|
||||
- python: 2.7
|
||||
env: DJANGO=1.11 DATABASE_URL='sqlite://localhost/:memory:'
|
||||
exclude:
|
||||
- python: 2.7
|
||||
- python: 3.4
|
||||
- python: 3.5
|
||||
- python: 3.6
|
||||
fast_finish: true
|
||||
|
||||
before_script:
|
||||
- pip freeze
|
||||
- sh -c "if [ '$DATABASE_URL' = 'postgres://postgres@127.0.0.1/djangocms_test' ];
|
||||
then psql -c 'DROP DATABASE IF EXISTS djangocms_test;' -U postgres; fi"
|
||||
- sh -c "if [ '$DATABASE_URL' = 'postgres://postgres@127.0.0.1/djangocms_test' ];
|
||||
then psql -c 'create database djangocms_test;' -U postgres; fi"
|
||||
- sh -c "if [ '$DATABASE_URL' = 'mysql://root@127.0.0.1/djangocms_test' ]; then mysql
|
||||
-e 'create database IF NOT EXISTS djangocms_test CHARACTER SET utf8 COLLATE utf8_general_ci;';
|
||||
fi"
|
||||
- if [ "$DATABASE_URL" == "postgres://postgres@127.0.0.1/djangocms_test" ]; then psql -c "DROP DATABASE IF EXISTS djangocms_test;" -U postgres; fi
|
||||
- if [ "$DATABASE_URL" == "postgres://postgres@127.0.0.1/djangocms_test" ]; then psql -c "create database djangocms_test;" -U postgres; fi
|
||||
- if [ "$DATABASE_URL" == "mysql://root@127.0.0.1/djangocms_test" ]; then mysql -e 'create database IF NOT EXISTS djangocms_test CHARACTER SET utf8 COLLATE utf8_general_ci;'; fi
|
||||
|
||||
before_install:
|
||||
- "export DISPLAY=:99.0"
|
||||
# xvfb is started in this way to ensure the screen size is 1280x1024x8
|
||||
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/cucumber_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x8"
|
||||
- "export TRAVIS_COMMIT_MSG=\"$(git log --format=%B --no-merges -n 1)\""
|
||||
- pip install -U pip>=8.0
|
||||
- echo "$TRAVIS_COMMIT_MSG" | grep '\[skip saucelabs\]'; export USE_SAUCE_LABS=$?; true
|
||||
- echo "$TRAVIS_COMMIT_MSG" | grep '\[ci only docs\]'; export ONLY_DOCS=$?; true
|
||||
|
||||
install:
|
||||
- pip install -r "test_requirements/django-$DJANGO.txt"
|
||||
- if [ $DATABASE_URL == 'postgres://postgres@127.0.0.1/djangocms_test' ]; then pip install psycopg2 ; fi
|
||||
- if [ $DATABASE_URL == 'mysql://root@127.0.0.1/djangocms_test' ]; then pip install mysqlclient ; fi
|
||||
- if [ "$UNIT" != 1 ] && [ "$LINT" != 1 ]; then pip install -r "test_requirements/django-$DJANGO.txt"; pip freeze; fi
|
||||
- if [ "$FRONTEND" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then nvm install 6 && nvm use 6; fi
|
||||
# Disable the spinner, it looks bad on Travis
|
||||
- if [ "$FRONTEND" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then npm config set spin false; fi
|
||||
- if [ "$FRONTEND" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then npm install -g gulp@3.9.1; fi
|
||||
- if [ "$FRONTEND" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then scripts/install-npm-dependencies.sh; fi
|
||||
- if [ "$UNIT" != 1 ] && [ "$LINT" != 1 ] && [ "$DATABASE_URL" == 'postgres://postgres@127.0.0.1/djangocms_test' ]; then pip install psycopg2 ; fi
|
||||
- if [ "$UNIT" != 1 ] && [ "$LINT" != 1 ] && [ "$DATABASE_URL" == 'mysql://root@127.0.0.1/djangocms_test' ]; then pip install mysqlclient ; fi
|
||||
|
||||
script:
|
||||
- coverage run --parallel-mode manage.py test $MIGRATE_OPTION
|
||||
- coverage combine
|
||||
- if [ "$FRONTEND" == 1 ] && [ "$UNIT" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then gulp tests:unit; fi;
|
||||
- if [ "$FRONTEND" == 1 ] && [ "$LINT" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then gulp lint; fi;
|
||||
- if [ "$FRONTEND" == 1 ] && [ "$INTEGRATION" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then pip install -e .; pip freeze; gulp tests:integration; fi;
|
||||
- if [ "$FRONTEND" != 1 ] && ([ "$TEST_DOCS" == 1 ] && [ "$ONLY_DOCS" == 0 ] || [ "$ONLY_DOCS" != 0 ]); then coverage run --parallel-mode manage.py test; fi;
|
||||
- if [ "$FRONTEND" != 1 ] && ([ "$TEST_DOCS" == 1 ] && [ "$ONLY_DOCS" == 0 ] || [ "$ONLY_DOCS" != 0 ]); then coverage combine; fi;
|
||||
|
||||
after_success: coveralls
|
||||
after_success: if [ "$FRONTEND" != 1 ]; then coveralls; fi;
|
||||
|
||||
notifications:
|
||||
irc:
|
||||
- irc.freenode.org#django-cms
|
||||
- irc.freenode.org#django-cms-sprint
|
||||
webhooks:
|
||||
- http://addons.us-iad-rs.aldryn.io/en/travis-endpoint/
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
|
||||
- python: 2.6
|
||||
env: DJANGO=1.6 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 2.6
|
||||
env: DJANGO=1.6 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 2.6
|
||||
env: DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 2.6
|
||||
env: DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
|
||||
- python: 2.6
|
||||
env: DJANGO=1.7 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0
|
||||
- python: 2.6
|
||||
env: DJANGO=1.7 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 2.6
|
||||
env: DJANGO=1.7 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 2.6
|
||||
env: DJANGO=1.7 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
|
||||
- python: 2.6
|
||||
env: DJANGO=1.8 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 2.6
|
||||
env: DJANGO=1.8 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 2.6
|
||||
env: DJANGO=1.8 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 2.6
|
||||
env: DJANGO=1.8 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- python: 2.6
|
||||
env: DJANGO=1.8 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='customuserapp.User'
|
||||
|
||||
- python: 2.6
|
||||
env: DJANGO=1.9 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 2.6
|
||||
env: DJANGO=1.9 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 2.6
|
||||
env: DJANGO=1.9 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 2.6
|
||||
env: DJANGO=1.9 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- python: 2.6
|
||||
env: DJANGO=1.9 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 AUTH_USER_MODEL='customuserapp.User'
|
||||
|
||||
- python: 2.7
|
||||
env: DJANGO=1.6 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 2.7
|
||||
env: DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 2.7
|
||||
env: DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
|
||||
- python: 2.7
|
||||
env: DJANGO=1.7 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0
|
||||
- python: 2.7
|
||||
env: DJANGO=1.7 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
|
||||
- python: 3.3
|
||||
env: DJANGO=1.6 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 3.3
|
||||
env: DJANGO=1.6 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.3
|
||||
env: DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 3.3
|
||||
env: DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
|
||||
- python: 3.3
|
||||
env: DJANGO=1.7 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0
|
||||
- python: 3.3
|
||||
env: DJANGO=1.7 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.3
|
||||
env: DJANGO=1.7 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
|
||||
- python: 3.3
|
||||
env: DJANGO=1.8 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 3.3
|
||||
env: DJANGO=1.8 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.3
|
||||
env: DJANGO=1.8 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 3.3
|
||||
env: DJANGO=1.8 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
|
||||
- python: 3.3
|
||||
env: DJANGO=1.9 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 3.3
|
||||
env: DJANGO=1.9 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.3
|
||||
env: DJANGO=1.9 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 3.3
|
||||
env: DJANGO=1.9 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- python: 3.3
|
||||
env: DJANGO=1.9 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 AUTH_USER_MODEL='customuserapp.User'
|
||||
|
||||
- python: 3.5
|
||||
env: DJANGO=1.6 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 3.5
|
||||
env: DJANGO=1.6 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.5
|
||||
env: DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0 MIGRATE_OPTION='--migrate'
|
||||
- python: 3.5
|
||||
env: DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
- python: 3.5
|
||||
env: DJANGO=1.6 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='customuserapp.User'
|
||||
|
||||
- python: 3.5
|
||||
env: DJANGO=1.7 DATABASE_URL='sqlite://localhost/:memory:' SELENIUM=0
|
||||
- python: 3.5
|
||||
env: DJANGO=1.7 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.5
|
||||
env: DJANGO=1.7 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.5
|
||||
env: DJANGO=1.7 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' SELENIUM=1 AUTH_USER_MODEL='emailuserapp.EmailUser'
|
||||
|
||||
allow_failures:
|
||||
- python: 2.7
|
||||
env: DJANGO=1.7 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 2.7
|
||||
env: DJANGO=1.8 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 2.7
|
||||
env: DJANGO=1.9 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
|
||||
- python: 3.3
|
||||
env: DJANGO=1.6 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.3
|
||||
env: DJANGO=1.7 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.3
|
||||
env: DJANGO=1.8 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.3
|
||||
env: DJANGO=1.9 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
|
||||
- python: 3.4
|
||||
env: DJANGO=1.6 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.4
|
||||
env: DJANGO=1.7 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.4
|
||||
env: DJANGO=1.8 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.4
|
||||
env: DJANGO=1.9 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
|
||||
- python: 3.5
|
||||
env: DJANGO=1.8 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
- python: 3.5
|
||||
env: DJANGO=1.9 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test' SELENIUM=0
|
||||
|
||||
fast_finish: true
|
||||
|
||||
|
||||
@@ -21,17 +21,21 @@ Current and previous core designers:
|
||||
* Matthias Nüesch
|
||||
|
||||
|
||||
Contributors (based on gitlog):
|
||||
Contributors (based on gitlog, 537 unique authors):
|
||||
|
||||
* Антон Евжаков
|
||||
* A. Bram Neijt
|
||||
* AQNOUCH Mohammed
|
||||
* aaloy
|
||||
* Aaron Fay
|
||||
* Aaron Renner
|
||||
* Aaron Spike
|
||||
* aball
|
||||
* Adam Chainz
|
||||
* Adi Sieker
|
||||
* Admin Adminaras
|
||||
* Adrien Brunet
|
||||
* Adrián Ribao Martínez
|
||||
* Aidas Bendoraitis
|
||||
* Alberto Paro
|
||||
* Ales Kocjancic
|
||||
* Ales Zabala Alava (Shagi)
|
||||
@@ -39,19 +43,28 @@ Contributors (based on gitlog):
|
||||
* Alessandro Ronchi
|
||||
* Alex Cucu
|
||||
* Alex Marandon
|
||||
* Alexander Paramonov
|
||||
* Alexandre Leray
|
||||
* Alexey Subbotin
|
||||
* Aliaksei Urbanski
|
||||
* alskgj
|
||||
* Alvin Mites
|
||||
* Anatoly Ivanov
|
||||
* Andi Albrecht
|
||||
* Andras Gyömrey
|
||||
* Andre Bossard
|
||||
* Andrea Stagi
|
||||
* Andreas Elvers
|
||||
* Andrei Avram
|
||||
* Andrei Fokau
|
||||
* Andrew Schoen
|
||||
* André Cruz
|
||||
* Angelo Dini
|
||||
* angular_circle
|
||||
* Anthony Steinhauser
|
||||
* Antoine Catton
|
||||
* Anton Parkhomenko
|
||||
* AQNOUCH Mohammed
|
||||
* Arcady Usov
|
||||
* Arne Gellhaus
|
||||
* Arne Schauf
|
||||
@@ -59,7 +72,9 @@ Contributors (based on gitlog):
|
||||
* Arthur Debert
|
||||
* Artis Avotins
|
||||
* Aymeric Augustin
|
||||
* Bartosz Płóciennik
|
||||
* Basile LEGAL
|
||||
* behrooz
|
||||
* Behrooz Nobakht
|
||||
* Belegnar
|
||||
* Ben Northway
|
||||
@@ -67,9 +82,12 @@ Contributors (based on gitlog):
|
||||
* Benjamin
|
||||
* Benjamin Liles
|
||||
* Benjamin Wohlwend
|
||||
* benjaoming
|
||||
* benzkji
|
||||
* Bernardo Cabezas Serra
|
||||
* Bernd Zeimetz
|
||||
* Bertrand Bordage
|
||||
* beshrkayali
|
||||
* Bitdeli Chef
|
||||
* Bjorn Sandberg
|
||||
* Bob Karreman
|
||||
@@ -77,12 +95,18 @@ Contributors (based on gitlog):
|
||||
* Brad Walker
|
||||
* Braden MacDonald
|
||||
* Brian Schott
|
||||
* brightwhitefox
|
||||
* Calvin Cheng
|
||||
* Carlo Ascani
|
||||
* Carlos de Paula
|
||||
* casio
|
||||
* centralniak
|
||||
* Cezar Pendarovski
|
||||
* cezar77
|
||||
* Chanita Siridechkun
|
||||
* Charlie Denton
|
||||
* Charpentier Johan
|
||||
* Chematronix
|
||||
* Cheng-Chia Tseng
|
||||
* Chris Adams
|
||||
* Chris Franklin
|
||||
@@ -95,20 +119,24 @@ Contributors (based on gitlog):
|
||||
* Christof Hagedorn
|
||||
* Christopher Grebs
|
||||
* Christos Kopanos
|
||||
* cip
|
||||
* Claudio Bartolini
|
||||
* Colin Wirz
|
||||
* Conrado Buhrer
|
||||
* Corey Farwell
|
||||
* Craig Ricciuto
|
||||
* Craig de Stigter
|
||||
* DZ
|
||||
* DaNmarner
|
||||
* Craig Ricciuto
|
||||
* creakings
|
||||
* cworth-gh
|
||||
* Damian Moore
|
||||
* Damir Arbula
|
||||
* DamirArbula
|
||||
* Daniel Barden
|
||||
* daniele
|
||||
* Daniele Procida
|
||||
* Danilo Bargen
|
||||
* DaNmarner
|
||||
* darbula
|
||||
* Darii Denis
|
||||
* Darii Denis Home
|
||||
* Dario Albanesi
|
||||
@@ -116,13 +144,19 @@ Contributors (based on gitlog):
|
||||
* Darryl Woods
|
||||
* Dave Bell
|
||||
* Dave Hall
|
||||
* David D Lowe
|
||||
* David Grant
|
||||
* David Jean Louis
|
||||
* David Lam
|
||||
* David Lee
|
||||
* David Thompson
|
||||
* Davide Setti
|
||||
* derek73
|
||||
* deshack
|
||||
* digi604
|
||||
* divio
|
||||
* Diógenes Augusto Fernandes Hermínio
|
||||
* dlamotte
|
||||
* Dmitry Akinin
|
||||
* Dmitry Gorelik
|
||||
* Dmytro Gorelik
|
||||
@@ -130,51 +164,86 @@ Contributors (based on gitlog):
|
||||
* Donatas Kučinskas
|
||||
* Douwe van der Meij
|
||||
* Dries Desmet
|
||||
* dstufft
|
||||
* Dylann CORDEL
|
||||
* DylannCordel
|
||||
* DZ
|
||||
* eged
|
||||
* Egor V. Nazarkin
|
||||
* Ekrem SEREN
|
||||
* Elias Probst
|
||||
* Eraldo Energy
|
||||
* Eric Amador
|
||||
* Eric Eldredge
|
||||
* Eric Robitaille
|
||||
* ericr
|
||||
* Erik Allik
|
||||
* Erlend Dalen
|
||||
* Eugene MechanisM
|
||||
* Evandro Miquelito
|
||||
* evildmp
|
||||
* F. Gabriel Gosselin
|
||||
* f4nt
|
||||
* fcurella
|
||||
* febsn
|
||||
* Filip Kazimierczak
|
||||
* Filipe Waitman
|
||||
* FinalAngel
|
||||
* fivethreeo
|
||||
* fj
|
||||
* floppya
|
||||
* Florian Verdet
|
||||
* Frank
|
||||
* Frank Bieniek
|
||||
* FrankBie
|
||||
* frost-nzcr4
|
||||
* furiousdave
|
||||
* Gabe Jackson
|
||||
* Gabriel Hurley
|
||||
* GaretJax
|
||||
* Gavin Wahl
|
||||
* genti94
|
||||
* Gentian Mazrekaj
|
||||
* gentleShark
|
||||
* Geoffrey Fairchild
|
||||
* Geoffrey Hing
|
||||
* George Marshall
|
||||
* Gerard Świderski
|
||||
* Germano Gabbianelli
|
||||
* Gianluca Guarini
|
||||
* Gilles Lenfant
|
||||
* Gleb Chipiga
|
||||
* Gokmen Gorgen
|
||||
* Gregory Klupar
|
||||
* guandalino
|
||||
* Hamish Downer
|
||||
* Hans Andersen
|
||||
* hedberg
|
||||
* henning
|
||||
* Henning Sprang
|
||||
* heppstux
|
||||
* Herbert Poul
|
||||
* hjkelly
|
||||
* Horst Gutmann
|
||||
* hysia
|
||||
* Iacopo Spalletti
|
||||
* Ian Lewis
|
||||
* ikudryavtsev
|
||||
* indexofire
|
||||
* Ionel Maries Cristian
|
||||
* Ivan Vershigora
|
||||
* Iván Raskovsky
|
||||
* izi
|
||||
* J. Cliff Dyer
|
||||
* Jacob Rief
|
||||
* Jakob Hedman
|
||||
* Jakub Randák
|
||||
* jalaziz
|
||||
* Jameel Al-Aziz
|
||||
* James Richards
|
||||
* James Rutherford
|
||||
* Jana Deutschlaender
|
||||
* Jannik V
|
||||
* Jannis Leidel
|
||||
* Janusz Harkot
|
||||
* Jared Proffitt
|
||||
@@ -183,7 +252,9 @@ Contributors (based on gitlog):
|
||||
* Jason Jenkins
|
||||
* Jason Robinson
|
||||
* Jeffrey Goettsch
|
||||
* Jens Diemer
|
||||
* JensDiemer
|
||||
* Jeroen
|
||||
* Jeroen Noten
|
||||
* Jessica Tallon
|
||||
* Jimmy Lam
|
||||
@@ -194,43 +265,68 @@ Contributors (based on gitlog):
|
||||
* Jonas Obrist
|
||||
* Jonathan Liuti
|
||||
* Jonathan Stoppani
|
||||
* jordanjambazov
|
||||
* Jorge Vargas
|
||||
* Jos van Velzen
|
||||
* Joseph Bergantine
|
||||
* Joseph Lin
|
||||
* Joseph Melettukunnel
|
||||
* Josh Kalderimis
|
||||
* Josh Schneier
|
||||
* João Luiz Lorencetti
|
||||
* Julien Poissonnier
|
||||
* Julz
|
||||
* Júlio R. Lucchese
|
||||
* Kalle Bronsen
|
||||
* kater169
|
||||
* Katie McLaughlin
|
||||
* kblomqvist
|
||||
* Kegan Holtzhausen
|
||||
* Keryn Knight
|
||||
* Kevin Burton
|
||||
* Kevin Funk
|
||||
* Kevin Richardson
|
||||
* Kim Thoenen
|
||||
* kochin
|
||||
* Kristian Øllegaard
|
||||
* Krzysztof Socha
|
||||
* kunitoki
|
||||
* L-A Iscla
|
||||
* Lars Smit
|
||||
* Lars van de Kerkhof
|
||||
* Laurens Bosscher
|
||||
* Leon Smith
|
||||
* Leonardo
|
||||
* Lio Mendonca
|
||||
* Lionardo Mendonça
|
||||
* littlepea
|
||||
* Loriana Indelicato
|
||||
* lovmat
|
||||
* Lucas Vogelsang
|
||||
* Lucio Asnaghi
|
||||
* Ludwig Hähne
|
||||
* lug
|
||||
* Luis Diego Garcia
|
||||
* Luke Crooks
|
||||
* Luke Plant
|
||||
* MW
|
||||
* m000
|
||||
* Maik Lustenberger
|
||||
* maiklust
|
||||
* Manolis Stamatogiannakis
|
||||
* Manuel Schmidt
|
||||
* Marc-Olivier Titeux
|
||||
* Marco Badan
|
||||
* Marco Bonetti
|
||||
* Marco Federighi
|
||||
* Marco Paolini
|
||||
* Marco Rimoldi
|
||||
* marcor
|
||||
* Marijn Goedegebure
|
||||
* Mark
|
||||
* Mark Rogers
|
||||
* Mark Walker
|
||||
* Markus Holtermann
|
||||
* Markus Zapke-Gründemann
|
||||
* Markus Zapke-Gründemann
|
||||
* Marti Raudsepp
|
||||
* Martin
|
||||
* Martin Bambas
|
||||
@@ -239,100 +335,161 @@ Contributors (based on gitlog):
|
||||
* Martin Kosír
|
||||
* Martin Owens
|
||||
* Martin Pajuste
|
||||
* martinkosir
|
||||
* Matas Dailyda
|
||||
* Mateusz Dereniowski
|
||||
* Mateusz Kamycki
|
||||
* Mateusz Marzantowicz
|
||||
* mathijs
|
||||
* Matt Chisholm
|
||||
* Matteo Rosati
|
||||
* Matthias Cavigelli
|
||||
* Mattia Larentis
|
||||
* Max Shkurygin
|
||||
* Maxim Bodyansky
|
||||
* Maxime Haineault
|
||||
* mbouchar
|
||||
* mcosta
|
||||
* meers
|
||||
* Mel Collins
|
||||
* meomap
|
||||
* MerLex
|
||||
* Michael P. Jung
|
||||
* Michael Thornhill
|
||||
* Michal Danilak
|
||||
* Mike Dory
|
||||
* Mike Johnson
|
||||
* mikek
|
||||
* Mikko Ahlroth
|
||||
* Milo Price
|
||||
* Mitar
|
||||
* Mokys
|
||||
* Morgan Wahl
|
||||
* Motiejus Jakštys
|
||||
* motleytech
|
||||
* mrlundis
|
||||
* mvaerle
|
||||
* MW
|
||||
* Mykhailo Kolesnyk
|
||||
* Nelson Brochado
|
||||
* neoprolog
|
||||
* Nick Jones
|
||||
* Nicolas
|
||||
* Nicolas PASCAL
|
||||
* nikolas
|
||||
* Nina Zakharenko
|
||||
* nsh
|
||||
* o-zander
|
||||
* ojii
|
||||
* Oliver Thane
|
||||
* Oliver Zander
|
||||
* Olivier Larchevêque
|
||||
* Olivier Le Brouster
|
||||
* Orne Brocaar
|
||||
* Oyvind Saltvik
|
||||
* padelt
|
||||
* Pankrat
|
||||
* Paolo Leggio
|
||||
* Paolo Romolini
|
||||
* Pascal Mouret
|
||||
* pascal.beyeler
|
||||
* Patrick Arminio
|
||||
* Patrick Lauber
|
||||
* Patrick Toal
|
||||
* patricklauber
|
||||
* Patryk Zawadzki
|
||||
* paul
|
||||
* Paul van der Linden
|
||||
* Paulo
|
||||
* Paulo Alvarado
|
||||
* Pavel Puchkin
|
||||
* Pawel Markowski
|
||||
* pbgc
|
||||
* pcicman
|
||||
* Pedro Gracia
|
||||
* Pete Loggie
|
||||
* Peter Farrell
|
||||
* Peter J. Farrell
|
||||
* Peter Landry
|
||||
* Peter-Paul van Gemerden
|
||||
* peterfarrell
|
||||
* Petteri
|
||||
* pgcd
|
||||
* Philipp Bosch
|
||||
* Philipp Zedler
|
||||
* philomat
|
||||
* phuihock
|
||||
* Pigletto
|
||||
* Piotr Jakimiak
|
||||
* Piotr Kilczuk
|
||||
* piquadrat
|
||||
* pumalo
|
||||
* Rafal Radulski
|
||||
* raidsan
|
||||
* Rainer Koirikivi
|
||||
* Rajesh
|
||||
* Rajesh K.
|
||||
* Rajesh P.
|
||||
* Rebecca Breu
|
||||
* Remco Wendt
|
||||
* René Fleschenberg
|
||||
* requires.io
|
||||
* Restless Being
|
||||
* Richard Barran
|
||||
* rizumu
|
||||
* Robert Barsch
|
||||
* Robert Buchholz
|
||||
* Robert Clark
|
||||
* Robert Feldbinder
|
||||
* Robert Pogorzelski
|
||||
* Robert Stein
|
||||
* Roberto
|
||||
* Roberto Bampi
|
||||
* Robin Lewis
|
||||
* robint
|
||||
* Rodolfo Carvalho
|
||||
* Roman Kozlovskiy
|
||||
* root
|
||||
* rtpm
|
||||
* Ruslan Malogulko
|
||||
* Russ Ferriday
|
||||
* SachaMPS
|
||||
* Salmanul Farzy
|
||||
* Sam Manzi
|
||||
* Samuel Lahti
|
||||
* Samuel Luescher
|
||||
* Samuel Lüscher
|
||||
* sbussetti
|
||||
* Schoen
|
||||
* scottbarnham
|
||||
* sealibora
|
||||
* Sean Bleier
|
||||
* Sebastian Braun
|
||||
* Sergey Fedoseev
|
||||
* Seth Buntin
|
||||
* Seyhun Akyurek
|
||||
* Shahar Or
|
||||
* Shatalov Vadim
|
||||
* Shaun Brady
|
||||
* shed
|
||||
* Shinya Okano
|
||||
* shulcsm
|
||||
* Simon Charette
|
||||
* Simon Hedberg
|
||||
* Simon Meers
|
||||
* sleytr
|
||||
* Sophie Leroy
|
||||
* spookylukey
|
||||
* srj55
|
||||
* ssteinerX
|
||||
* Stavros Korokithakis
|
||||
* Stefan Foulis
|
||||
* Stefan Wehrmeyer
|
||||
* stefanfoulis
|
||||
* Stefano Brentegani
|
||||
* Stefano Crosta
|
||||
* Stefano Morandi
|
||||
* Stefano Probst
|
||||
* Steffen Jasper
|
||||
* Stephan
|
||||
* Stephan Hepper
|
||||
@@ -346,21 +503,30 @@ Contributors (based on gitlog):
|
||||
* Sylvain Fankhauser
|
||||
* Tadas Dailyda
|
||||
* Tanel Külaots
|
||||
* tdelam
|
||||
* tehfink
|
||||
* Thibaud Colas
|
||||
* Thomas Meitz
|
||||
* Thomas Parslow
|
||||
* Thomas Remmert
|
||||
* Thomas Woolford
|
||||
* thomas-d
|
||||
* Tim Anderegg
|
||||
* Tim Davies
|
||||
* Tim Graham
|
||||
* timesong
|
||||
* Tino de Bruijn
|
||||
* tiret
|
||||
* Tobias von Klipstein
|
||||
* Tom
|
||||
* Tom Berger
|
||||
* Tom de Simone
|
||||
* Tom S
|
||||
* Tom V
|
||||
* Tom Wardill
|
||||
* Tom de Simone
|
||||
* Ulrich Petri
|
||||
* unknown
|
||||
* uno
|
||||
* User
|
||||
* Vadim Lopatyuk
|
||||
* Vadim Sikora
|
||||
@@ -368,130 +534,29 @@ Contributors (based on gitlog):
|
||||
* Viktor Nagy
|
||||
* Viliam Segeda
|
||||
* Vinit (Vermicelli on IRC)
|
||||
* vinit kumar
|
||||
* Vinod Kurup
|
||||
* Vladimir Bolshakov
|
||||
* Vladyslav
|
||||
* Wayne Moore
|
||||
* Xavier Fernandez
|
||||
* Xavier Ordoquy
|
||||
* Yann Malet
|
||||
* Yosuke Ikeda
|
||||
* Yuri van der Meer
|
||||
* aaloy
|
||||
* aball
|
||||
* angular_circle
|
||||
* behrooz
|
||||
* benjaoming
|
||||
* benzkji
|
||||
* beshrkayali
|
||||
* brightwhitefox
|
||||
* casio
|
||||
* centralniak
|
||||
* cip
|
||||
* creakings
|
||||
* daniele
|
||||
* darbula
|
||||
* derek73
|
||||
* deshack
|
||||
* digi604
|
||||
* divio
|
||||
* dlamotte
|
||||
* dstufft
|
||||
* eged
|
||||
* ericr
|
||||
* evildmp
|
||||
* f4nt
|
||||
* fcurella
|
||||
* febsn
|
||||
* fivethreeo
|
||||
* fj
|
||||
* floppya
|
||||
* frost-nzcr4
|
||||
* furiousdave
|
||||
* genti94
|
||||
* gentleShark
|
||||
* guandalino
|
||||
* hedberg
|
||||
* henning
|
||||
* hjkelly
|
||||
* hysia
|
||||
* ikudryavtsev
|
||||
* indexofire
|
||||
* izi
|
||||
* jalaziz
|
||||
* jordanjambazov
|
||||
* kater169
|
||||
* kblomqvist
|
||||
* kochin
|
||||
* kunitoki
|
||||
* littlepea
|
||||
* lovmat
|
||||
* lug
|
||||
* m000
|
||||
* maiklust
|
||||
* marco federighi
|
||||
* marcor
|
||||
* martinkosir
|
||||
* mathijs
|
||||
* mbouchar
|
||||
* mcosta
|
||||
* meers
|
||||
* mikek
|
||||
* motleytech
|
||||
* mrlundis
|
||||
* mvaerle
|
||||
* neoprolog
|
||||
* nikolas
|
||||
* nsh
|
||||
* o-zander
|
||||
* ojii
|
||||
* padelt
|
||||
* pascal.beyeler
|
||||
* patricklauber
|
||||
* paul
|
||||
* pbgc
|
||||
* pcicman
|
||||
* peterfarrell
|
||||
* pgcd
|
||||
* philomat
|
||||
* phuihock
|
||||
* piquadrat
|
||||
* pumalo
|
||||
* raidsan
|
||||
* requires.io
|
||||
* rizumu
|
||||
* robint
|
||||
* root
|
||||
* rtpm
|
||||
* sbussetti
|
||||
* scottbarnham
|
||||
* sealibora
|
||||
* shed
|
||||
* shulcsm
|
||||
* sleytr
|
||||
* spookylukey
|
||||
* srj55
|
||||
* ssteinerX
|
||||
* stefanfoulis
|
||||
* tdelam
|
||||
* tehfink
|
||||
* timesong
|
||||
* tiret
|
||||
* unknown
|
||||
* vinit kumar
|
||||
* vvangelovski
|
||||
* wangJunjie
|
||||
* Wayne Moore
|
||||
* wid
|
||||
* wildermesser
|
||||
* WYSIATI
|
||||
* Xavier Fernandez
|
||||
* Xavier Ordoquy
|
||||
* xie wei
|
||||
* xray7224
|
||||
* Yann Malet
|
||||
* yann.malet@gmail.com
|
||||
* ychouinard
|
||||
* yedpodtrzitko
|
||||
* yohanboniface
|
||||
* Yosuke Ikeda
|
||||
* Yuri van der Meer
|
||||
* zandeez
|
||||
* zundoya
|
||||
* Žan Anderle
|
||||
* Óscar M. Lage
|
||||
* Øyvind Saltvik
|
||||
* Žan Anderle
|
||||
* Антон Евжаков
|
||||
|
||||
+444
-3
@@ -1,3 +1,424 @@
|
||||
=== 3.7.0 (unreleased) ===
|
||||
|
||||
* Introduced Django 2.2 support.
|
||||
* Fixed test suite.
|
||||
|
||||
|
||||
=== 3.6.0 (2019-01-29) ===
|
||||
|
||||
* Removed the ``cms moderator`` command.
|
||||
* Dropped Django < 1.11 support.
|
||||
* Removed the translatable content get / set methods from ``CMSPlugin`` model.
|
||||
* Removed signal handlers for ``Page``, ``Title``, ``Placeholder`` and ``CMSPlugin`` models.
|
||||
* Moved ``Title.meta_description`` length restriction from model to form
|
||||
and increased its max length to 320 characters.
|
||||
* Added ``page_title`` parameter for ``cms.api.create_page()`` and ``cms.api.create_title()``.
|
||||
* Introduced Django 2.0 support.
|
||||
* Introduced Django 2.1 support.
|
||||
|
||||
|
||||
=== 3.5.3 (2018-11-20) ===
|
||||
|
||||
* Fixed ``TreeNode.DoesNotExist`` exception raised when exporting
|
||||
and loading database contents via ``dumpdata`` and ``loaddata``.
|
||||
* Fixed a bug where ``request.current_page`` would always be the public page,
|
||||
regardless of the toolbar status (draft / live). This only affected custom
|
||||
urls from an apphook.
|
||||
* Removed extra quotation mark from the sideframe button template
|
||||
* Fixed a bug where structureboard tried to preload markup when using legacy
|
||||
renderer
|
||||
* Fixed a bug where updates on other tab are not correctly propagated if the
|
||||
operation was to move a plugin in the top level of same placeholder
|
||||
* Fixed a bug where xframe options were processed by clickjacking middleware
|
||||
when page was served from cache, rather then get this value from cache
|
||||
* Fixed a bug where cached page permissions overrides global permissions
|
||||
* Fixed a bug where plugins that are not rendered in content wouldn't be
|
||||
editable in structure board
|
||||
* Fixed a bug with expanding static placeholder by clicking on "Expand All" button
|
||||
* Fixed a bug where descendant pages with a custom url would lose the overwritten
|
||||
url on save.
|
||||
* Fixed a bug where setting the ``on_delete`` option on ``PlaceholderField``
|
||||
and ``PageField`` fields would be ignored.
|
||||
* Fixed a bug when deleting a modal from changelist inside a modal
|
||||
|
||||
|
||||
=== 3.5.2 (2018-04-11) ===
|
||||
|
||||
* Fixed a bug where shortcuts menu entry would stop working after toolbar reload
|
||||
* Fixed a race condition in frontend code that could lead to sideframe being
|
||||
opened with blank page
|
||||
* Fixed a bug where the direct children of the homepage would get a leading ``/``
|
||||
character when the homepage was moved or published.
|
||||
* Fixed a bug where non-staff user would be able to open empty structure board
|
||||
* Fixed a bug where a static file from Django admin was referenced that no
|
||||
longer existed in Django 1.9 and up.
|
||||
* Fixed a bug where the migration 0018 would fail under certain databases.
|
||||
|
||||
|
||||
=== 3.5.1 (2018-03-05) ===
|
||||
|
||||
* Fixed a bug where editing pages with primary keys greater than 999 would throw an
|
||||
exception.
|
||||
* Fixed a ``MultipleObjectsReturned`` exception raised on the page types migration
|
||||
with multiple page types per site.
|
||||
* Fixed a bug which prevented toolbar js from working correctly when rendered
|
||||
before toolbar.
|
||||
* Fixed a bug where CMS would incorrectly highlight plugin content when plugin
|
||||
contains invisible elements
|
||||
* Fixed a regression where templates which inherit from a template using an ``{% extends %}``
|
||||
tag with a default would raise an exception.
|
||||
|
||||
|
||||
=== 3.5.0 (2018-01-31) ===
|
||||
|
||||
* Fixed a bug which prevented users from seeing the welcome screen when debug is
|
||||
turned off.
|
||||
* Introduced improved repr for ``Page``, ``Title``, ``Placeholder`` and ``CMSPlugin`` models.
|
||||
* Rename publish buttons to no longer reference "page"
|
||||
* Page rendering will now use the draft page instead of public page for logged in
|
||||
users with change permissions, unless the ``preview`` GET parameter is used.
|
||||
* Fixed "Expand all / Collapse all" not reflecting real state of the placeholder tree
|
||||
* Fixed a bug where Aliased plugins would render if their host page was unpublished (and user was not on edit mode).
|
||||
* Fixed a bug where focusing inputs in modal would require 2 clicks in some browsers
|
||||
* Changed the language chooser to always show all configured languages to staff members
|
||||
and public-only languages to anon users.
|
||||
* Introduced logic to copy pages to different sites from the admin.
|
||||
* Removed "View on Site" button when adding a page
|
||||
* Welcome page no longer uses multilingual URLs when not required.
|
||||
* Prevent users from passing a public page as parent in ``create_page`` api function
|
||||
|
||||
|
||||
=== 3.4.5 (2017-10-12) ===
|
||||
|
||||
* Introduced Django 1.11 compatibility
|
||||
* Fixed a bug where slug wouldn't be generated in the creation wizard
|
||||
* Fixed a bug where the add page endpoint rendered ``Change page`` as the html title.
|
||||
* Fixed an issue where non-staff users could request the wizard create endpoint.
|
||||
* Fixed an issue where the ``Edit page`` toolbar button wouldn't show on non-cms pages
|
||||
with placeholders.
|
||||
* Fixed a bug where placeholder inheritance wouldn't work if the inherited placeholder
|
||||
is cached in an ancestor page.
|
||||
* Fixed a regression where the code following a ``{% placeholder x or %}`` declaration,
|
||||
was rendered before attempting to inherit content from parent pages.
|
||||
* Changed page/placeholder cache keys to use sha1 hash instead of md5 to be FIPS compliant.
|
||||
* Fixed a bug where the change of a slug would not propagate to all descendant pages
|
||||
* Fixed a ``ValueError`` raised when using ``ManifestStaticFilesStorage`` or similar for static files.
|
||||
This only affects Django >= 1.10
|
||||
|
||||
|
||||
=== 3.4.4 (2017-06-15) ===
|
||||
|
||||
* Fixed a bug in which cancelling the publishing dialog wasn't respected.
|
||||
* Fixed a bug causing post-login redirection to an incorrect URL on single-language sites.
|
||||
* Changed the signature for internal ``cms.plugin_base.CMSPluginBase`` methods ``get_child_classes``
|
||||
and ``get_parent_classes`` to take an optional ``instance`` parameter.
|
||||
* Fixed an error when retrieving placeholder label from configuration.
|
||||
* Fixed a bug which caused certain translations to display double-escaped text in the page
|
||||
list admin view.
|
||||
* Adjusted the toolbar JavaScript template to escape values coming from the request.
|
||||
* Added Dropdown class to toolbar items
|
||||
* Replaced all custom markup on the ``admin/cms/page/includes/fieldset.html`` template
|
||||
with an ``{% include %}`` call to Django's built-in ``fieldset.html`` template.
|
||||
* Fixed a bug which prevented a page from being marked as dirty when a placeholder was cleared.
|
||||
* Fixed an IntegrityError raised when publishing a page with no public version and whose publisher
|
||||
state was pending.
|
||||
* Fixed an issue with JavaScript not being able to determine correct path to the async bundle
|
||||
* Fixed a ``DoesNotExist`` database error raised when moving a page marked as published, but whose public
|
||||
translation did not exist.
|
||||
* Fixed a bug in which the menu rendered nodes using the site session variable (set in the admin),
|
||||
instead of the current request site.
|
||||
* Fixed a race condition bug in which the database cache keys were deleted without syncing with the
|
||||
cache server, and as a result old menu items would continue to be displayed.
|
||||
* Fixed a 404 raised when using the ``Delete`` button for a Page or Title extension on Django >= 1.9
|
||||
* Added "How to serve multiple languages" section to documentation
|
||||
* Fixed a performance issue with nested pages when using the ``inherit`` flag on the ``{% placeholder %}`` tag.
|
||||
* Removed the internal ``reset_to_public`` page method in favour of the ``revert_to_live`` method.
|
||||
* Fixed a bug in which the placeholder cache was not consistently cleared when a page was published.
|
||||
* Enhanced the plugin menu to not show plugins the user does not have permission to add.
|
||||
* Fixed a regression which prevented users from setting a redirect to the homepage.
|
||||
|
||||
|
||||
=== 3.4.3 (2017-04-24) ===
|
||||
|
||||
* Fixed a security vulnerability in the page redirect field which allowed users
|
||||
to insert JavaScript code.
|
||||
* Fixed a security vulnerability where the ``next`` parameter for the toolbar login
|
||||
was not sanitised and could point to another domain.
|
||||
|
||||
|
||||
=== 3.4.2 (2017-01-23) ===
|
||||
|
||||
* Escaped strings in ``close_frame`` JS template.
|
||||
* Fixed a bug with `text-transform` styles on inputs affecting CMS login
|
||||
* Fixed a typo in the confirmation message for copying plugins from a different
|
||||
language
|
||||
* Fixed a bug which prevented certain migrations from running in a multi-db setup.
|
||||
* Fixed a regression which prevented the ``Page`` model from rendering correctly
|
||||
when used in a ``raw_id_field``.
|
||||
* Fixed a regression which caused the CMS to cache the toolbar when ``CMS_PAGE_CACHE``
|
||||
was set to ``True`` and an anonymous user had ``cms_edit`` set to ``True`` on their session.
|
||||
* Fixed a regression which prevented users from overriding content in an inherited
|
||||
placeholder.
|
||||
* Added official support for Django 1.10.
|
||||
* Fixed a bug affecting Firefox for Macintosh users, in which use of the Command key later followed by Return would
|
||||
trigger a plugin save.
|
||||
* Fixed a bug where template inheritance setting creates spurious migration (see #3479)
|
||||
* Fixed a bug which prevented the page from being marked as dirty (pending changes)
|
||||
when changing the value of the overwrite url field.
|
||||
* Adjusted Ajax calls triggered when performing a placeholder operation (add plugin, etc..) to include
|
||||
a GET query called cms_path. This query points to the path where the operation originates from.
|
||||
* Added a deprecation warning to method ``render_plugin()`` in class ``CMSPlugin``.
|
||||
* Since ``get_parent_classes()`` became a classmethod, do not instantiate plugin before invocation.
|
||||
* Fixed a bug where the page tree would not update correctly when a sibling page was moved
|
||||
from left to right or right to left.
|
||||
* Improved the ``fix-tree`` command so that it also fixes non-root nodes (pages).
|
||||
* Removed the deprecated ``add_url()``, ``edit_url()``, ``move_url()``, ``delete_url()``, ``copy_url()`` properties of
|
||||
CMSPlugin model.
|
||||
* Deprecated ``frontend_edit_template`` attribute of ``CMSPluginBase``.
|
||||
* Introduced placeholder operation signals.
|
||||
* The ``post_`` methods in ```PlaceholderAdminMixin`` have been deprecated in favor of
|
||||
placeholder operation signals.
|
||||
* Re-introduced the "Revert to live" menu option.
|
||||
* Added support for django-reversion >= 2 (see #5830)
|
||||
* Rewrote manual installation how-to documentation
|
||||
|
||||
|
||||
=== 3.4.1 (2016-10-04) ===
|
||||
|
||||
* Fixed a regression when static placeholder was uneditable if it was present
|
||||
on the page multiple times
|
||||
* Removed globally unique constraint for Apphook configs.
|
||||
* Fixed a bug when keyboard shortcuts were triggered when form fields were
|
||||
focused
|
||||
* Fixed a bug when ``shift + space`` shortcut wouldn't correctly highlight a
|
||||
plugin in the structure board
|
||||
* Fixed a bug when plugins that have top-level svg element would break
|
||||
structure board
|
||||
* Fixed a bug where output from the ``show_admin_menu_for_pages`` template tag
|
||||
was escaped in Django 1.9
|
||||
* Fixed a bug where plugins would be rendered as editable if toolbar was shown
|
||||
but user was not in edit mode.
|
||||
* Fixed css reset issue with shortcuts modal
|
||||
|
||||
|
||||
=== 3.4.0 (2016-09-14) ===
|
||||
|
||||
* Changed the way CMS plugins are rendered. The div with `cms-plugin` class is
|
||||
no longer rendered around every CMS plugin, instead a combination of `template`
|
||||
tags and JavaScript is used to add event handlers and plugin data directly to
|
||||
the plugin markup. This fixes most of the rendering issues that were present
|
||||
because of the extra markup.
|
||||
* Changed cache-busting implementation, it is now handled by a path change,
|
||||
not by GET parameter.
|
||||
* Added a possibility to copy pages in the Page Tree by drag'n'drop.
|
||||
* Make it possible to use multi-table inheritance for Page/Title extensions.
|
||||
* Refactored plugin rendering functionality to speed up loading time in both
|
||||
structure and content mode.
|
||||
* Added ``Shift + Space`` shortcut that behaves similar to ``Space`` shortcut
|
||||
but takes into account currently hovered plugin.
|
||||
* Improved keyboard navigation
|
||||
* Added help modal about available shortcuts
|
||||
* Added fuzzy matching to plugin picker
|
||||
* Changed the ``downcast_plugins`` utility to return a generator instead of a list
|
||||
* Fixed a bug that caused an aliased placeholder to show in structure mode.
|
||||
* Fixed a bug which prevented aliased content from showing correctly without
|
||||
publishing the page first.
|
||||
* Added help text to an ``Alias`` plugin change form when attached to a page
|
||||
to show the content editor where the content is aliased from.
|
||||
* Removed revision support from djangoCMS core.
|
||||
As a result both ``CMS_MAX_PAGE_HISTORY_REVERSIONS`` and ``CMS_MAX_PAGE_PUBLISH_REVERSIONS``
|
||||
settings are no longer supported, as well as the ``with_revision`` parameter
|
||||
in ``cms.api.create_page`` and ``cms.api.create_title``.
|
||||
|
||||
|
||||
=== 3.3.3 (unreleased) ===
|
||||
|
||||
* Fixed a bug where where the plugin picker would display the plugin names
|
||||
translated in the request language instead of the user's language.
|
||||
* Fixed a bug which raised an exception when the ``AdvancedSettingsForm``
|
||||
failed validation on certain fields.
|
||||
* Fixed a bug with widgets not initialising correctly sometimes
|
||||
* Fixed a tree corruption when moving a published page under a published one.
|
||||
* Fixed a tree corruption caused by ``fix-tree`` when an unpublished page is parent
|
||||
to a published page.
|
||||
* Fixed an error when publishing a page that has an unpublished child page who is
|
||||
parent to a published page.
|
||||
* Fixed a bug where moving a published page under a page marked as pending publishing
|
||||
is left as published instead of being marked as pending publishing.
|
||||
* Fixed AttributeError when using ``create_page`` in management command
|
||||
* Fixed a bug in getting the language from current request which can cause error 500
|
||||
* API functions are now atomic by design (use the @atomic decorator)
|
||||
* Fixed a bug where a ``Page`` was created with it's languages field set to ``None``.
|
||||
|
||||
|
||||
=== 3.3.2 (2016-08-11) ===
|
||||
|
||||
* Fixed a bug where it wasn't possible to scroll the toolbar menu if scroll
|
||||
started on the disabled menu item on small screens.
|
||||
* Fixed a migration error (0014) that occurred under certain environments.
|
||||
* Fixed a regression when standalone CMS Widgets wouldn't work due to
|
||||
non-existing JavaScript dependencies.
|
||||
* Fixed a possible recursion error when using the ``Alias`` plugin.
|
||||
* Fixed a regression where submit handlers for modal form wouldn't be executed
|
||||
under certain circumstances
|
||||
|
||||
|
||||
=== 3.3.1 (2016-07-13) ===
|
||||
|
||||
* Added a warning for users who are leaving the page or closing the plugin
|
||||
modal by pressing ESC to prevent accidental loss of content.
|
||||
* Fixed a bug when clicking inside sideframe didn't close toolbar dropdowns
|
||||
* Fixed a bug where saving errors wouldn't be shown in the modal window.
|
||||
* Fixed a misleading message when modal iframe contents couldn't be accessed.
|
||||
* Added a workaround for a bug when plugins couldn't be deleted in Firefox
|
||||
with 1Password extension installed
|
||||
* Changed CMS JavaScript bundling from simple concatenation to webpack-based.
|
||||
Using CMS JavaScript modules directly is no longer possible.
|
||||
* Fixed an issue where plugins that have no immediate DOM representation
|
||||
wouldn't be editable or movable.
|
||||
* Fixed a regression in which plugins that defined ``parent_classes``
|
||||
would not show up in the structure mode.
|
||||
* Introduced new logic to leverage Django's dynamic related name
|
||||
functionality on ``CMSPlugin`` subclasses for the parent link field.
|
||||
* Backported a performance fix from Django to avoid extra queries when
|
||||
plugins access their parent via the parent link field ``cmsplugin_ptr``.
|
||||
* Fixed typo in ``AdvancedSettingsForm`` error messages.
|
||||
* Fixed long standing bug that prevented apphook endspoints from being
|
||||
CSRF exempt.
|
||||
* Changed default value for ``CMS_INTERNAL_IPS``.
|
||||
* Fixed an issue that prevented non superusers from copying all plugins
|
||||
in a placeholder.
|
||||
* Fixed an issue where plugin permissions where not checked when clearing
|
||||
a placeholder.
|
||||
* Fixed an issue where plugin permissions where not checked when deleting
|
||||
a page or page translation.
|
||||
* Added support for tiered ``CMS_PLACEHOLDER_CONF``.
|
||||
* Fixed a useless placeholders edit permissions checking when not in edit
|
||||
mode.
|
||||
* Fixed a bug where users with limited permissions could not interact with
|
||||
page tree dropdowns.
|
||||
* Fixed a bug where Django Compressor could not be used on the sekizai ``js``
|
||||
block.
|
||||
* Fixed an encoding error when running the ``publisher-publish`` command.
|
||||
* Fixed regression introduced in 3.3.0 when using the
|
||||
``render_plugin_toolbar_config`` template tag directly.
|
||||
* Fixed ``render_model`` template tags to work with models containing deferred
|
||||
fields.
|
||||
* Fixed error in retrieving placeholder label from configuration.
|
||||
|
||||
|
||||
=== 3.3.0 (2016-05-26) ===
|
||||
|
||||
* [no changes vs. rc4]
|
||||
|
||||
|
||||
=== 3.3.0.rc4 (2016-05-24) ===
|
||||
|
||||
* Fixed regression in management commands
|
||||
* Fixed documentation typo
|
||||
|
||||
|
||||
=== 3.3.0.rc3 (2016-05-23) ===
|
||||
|
||||
* Added contribution policies documentation
|
||||
* Corrected documentation in numerous places
|
||||
* Corrected an issue where someone could see and use the internal placeholder plugin in the structure board
|
||||
* Fixed a regression where the first page created was not automatically published
|
||||
* Corrected the instructions for using the ``delete-orphaned-plugins`` command
|
||||
* Re-pinned django-treebeard to >=4.0.1
|
||||
|
||||
|
||||
=== 3.3.0.rc2 (2016-05-19) ===
|
||||
|
||||
* Added CMS_WIZARD_CONTENT_PLACEHOLDER setting
|
||||
* Renamed the CMS_WIZARD_* settings to CMS_PAGE_WIZARD_*
|
||||
* Deprecated the old-style wizard-related settings
|
||||
* Improved documentation further
|
||||
* Improved handling of uninstalled apphooks
|
||||
* Fixed toolbar placement when foundation is installed
|
||||
* Fixed an issue which could lead to an apphook without a slug
|
||||
* Fixed numerous frontend issues
|
||||
|
||||
|
||||
=== 3.3.0.rc1 (2016-05-16) ===
|
||||
|
||||
* Removed support for Django 1.6, 1.7 and python 2.6
|
||||
* Changed the default value of CMSPlugin.position to 0 instead of null
|
||||
* Refactored the language menu to allow for better integration with many languages
|
||||
* Refactored management commands completely for better consistency
|
||||
* Fixed "failed to load resource" for favicon on welcome screen
|
||||
* Changed behaviour of toolbar CSS classes: ``cms-toolbar-expanded`` class is only added now when toolbar is fully
|
||||
expanded and not at the beginning of the animation. ``cms-toolbar-expanding`` and ``cms-toolbar-collapsing`` classes
|
||||
are added at the beginning of their respective animations.
|
||||
* Added unit tests for CMS JavaScript files
|
||||
* Added frontend integration tests (written with Casper JS)
|
||||
* Removed frontend integration tests (written with Selenium)
|
||||
* Added the ability to declare cache expiration periods on a per-plugin basis
|
||||
* Improved UI of page tree
|
||||
* Improved UI in various minor ways
|
||||
* Added a new setting CMS_INTERNAL_IPS for defining a set of IP addresses for which
|
||||
the toolbar will appear for authorized users. If left unset, retains the
|
||||
existing behavior of allowing toolbar for authorized users at any IP address.
|
||||
* Changed behaviour of sideframe; is no longer resizable, opens to 90% of the screen or 100% on
|
||||
small screens.
|
||||
* Removed some unnecessary reloads after closing sideframe.
|
||||
* Added the ability to make pagetree actions work on currently picked language
|
||||
* Removed deprecated CMS_TOOLBAR_SIMPLE_STRUCTURE_MODE setting
|
||||
* Introduced the method ``get_cache_expiration`` on CMSPluginBase to be used
|
||||
by plugins for declaring their rendered content's period of validity.
|
||||
* Introduced the method ``get_vary_cache_on`` on CMSPluginBase to be used
|
||||
by plugins for declaring ``VARY`` headers.
|
||||
* Improved performance of plugin moving; no longer saves all plugins inside the placeholder.
|
||||
* Fixed breadcrumbs of recently moved plugin reflecting previous position in
|
||||
the tree
|
||||
* Refactored plugin adding logic to no longer create the plugin before the user submits the form.
|
||||
* Improved the behaviour of the placeholder cache
|
||||
* Improved fix-tree command to sort by position and path when rebuilding positions.
|
||||
* Fixed several regressions and tree corruptions on page move.
|
||||
* Added new class method on CMSPlugin ``requires_parent_plugin``
|
||||
* Fixed behaviour of ``get_child_classes``; now correctly calculates child classes when not
|
||||
configured in the placeholder.
|
||||
* Removed internal ``ExtraMenuItems`` tag.
|
||||
* Removed internal ``PluginChildClasses`` tag.
|
||||
* Modified RenderPlugin tag; no longer renders the ``content.html`` template
|
||||
and instead just returns the results.
|
||||
* Added a ``get_cached_template`` method to the ``Toolbar()`` main class to reuse loaded templates per request. It
|
||||
works like Django's cached template loader, but on a request basis.
|
||||
* Added a new method ``get_urls()`` on the appbase class to get CMSApp.urls, to allow passing a page object to it.
|
||||
* Changed JavaScript linting from JSHint and JSCS to ESLint
|
||||
* Fixed a bug when it was possible to drag plugin into clipboard
|
||||
* Fixed a bug where clearing clipboard was closing any open modal
|
||||
|
||||
|
||||
=== 3.2.5 (2016-04-27) ===
|
||||
|
||||
- Fixed regression when page couldn't be copied if CMS_PERMISSION was False
|
||||
- Improved handling of uninstalled apphooks
|
||||
- Fix packaging problem with the wheel distribution
|
||||
|
||||
|
||||
=== 3.2.4 (2016-04-26) ===
|
||||
|
||||
- Fix cache settings
|
||||
- Fix user lookup for view restrictions/page permissions when using raw id field
|
||||
- Fixed regression when page couldn't be copied if CMS_PERMISSION was False
|
||||
- Fixes an issue relating to uninstalling a namespaced application
|
||||
- Adds "Can change page" permission
|
||||
- Fixes a number of page-tree issues the could lead data corruption under
|
||||
certain conditions
|
||||
- Addresses security vulnerabilities in the `render_model` template tag that
|
||||
could lead to escalation of privileges or other security issues.
|
||||
- Addresses a security vulnerability in the cms' usage of the messages framework
|
||||
- Fixes security vulnerabilities in custom FormFields that could lead to
|
||||
escalation of privileges or other security issues.
|
||||
|
||||
|
||||
=== 3.2.3 (2016-03-09) ===
|
||||
|
||||
- Fix the display of hyphenated language codes in the page tree
|
||||
- Fix a family of issues relating to unescaped translations in the page tree
|
||||
|
||||
|
||||
=== 3.2.2 (2016-03-02) ===
|
||||
|
||||
- Substantial improvements to the page tree and significant reduction of reloads
|
||||
@@ -42,7 +463,7 @@
|
||||
- Added tooltips for certain user interaction elements
|
||||
- Added full touch support and optimisations for mobile devices
|
||||
- Added gulp.js for linting, compressing and bundling
|
||||
- Added YuiDocs for Javascript documentation
|
||||
- Added YuiDocs for JavaScript documentation
|
||||
- Added ``CMS_TOOLBAR_SIMPLE_STRUCTURE_MODE`` to switch back to the old board rendering,
|
||||
this will be deprecated in 3.3.0
|
||||
- Added ``request.toolbars.placeholder_list`` this will replace
|
||||
@@ -89,9 +510,27 @@
|
||||
- Fixed an issue where ``allow_children`` and ``disable_child_plugins`` didn't work on dragitems
|
||||
|
||||
|
||||
=== 3.1.6 (unreleased) ===
|
||||
=== 3.1.8 (unreleased) ===
|
||||
|
||||
- Removed html5lib from setup.py
|
||||
|
||||
|
||||
=== 3.1.7 (2016-04-27) ===
|
||||
|
||||
- Fix packaging problem with the wheel distribution
|
||||
|
||||
|
||||
=== 3.1.6 (2016-04-26) ===
|
||||
|
||||
- Fix cache settings
|
||||
- Fix user lookup for view restrictions/page permissions when using raw id field
|
||||
- Fixes an issue relating to uninstalling a namespaced application
|
||||
- Adds "Can change page" permission
|
||||
- Addresses security vulnerabilities in the `render_model` template tag that
|
||||
could lead to escalation of privileges or other security issues.
|
||||
- Addresses a security vulnerability in the cms' usage of the messages framework
|
||||
- Fixes security vulnerabilities in custom FormFields that could lead to
|
||||
escalation of privileges or other security issues.
|
||||
|
||||
|
||||
=== 3.1.5 (2016-01-29) ===
|
||||
@@ -216,6 +655,8 @@
|
||||
|
||||
=== 3.0.17 (unreleased) ===
|
||||
|
||||
- Addresses security vulnerabilities in the `render_model` template tag that could
|
||||
lead to escalation of privileges or other security issues.
|
||||
- Fix ExtensionToolbar when language is removed but titles still exists…
|
||||
- Fix PageSelectWidget JS syntax
|
||||
- Fix cache settings
|
||||
@@ -632,7 +1073,7 @@ Please see Install/2.4 release notes *before* attempting to upgrade to version 2
|
||||
==== 2.1.0 (2011-01-26) ====
|
||||
|
||||
- language namespaces for apphooks (reverse("de:myview"), reverse("en:myview"))
|
||||
- video plugin switch to http://github.com/FlashJunior/OSFlashVideoPlayer
|
||||
- video plugin switch to https://github.com/FlashJunior/OSFlashVideoPlayer
|
||||
- frontediting added (cms.middlware.toolbar.ToolbarMiddleware)
|
||||
- testsuite works now under sqlite and postgres
|
||||
- orphaned text embed plugins get now deleted if not referenced in the text anymore
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
##########################
|
||||
Contributing to django CMS
|
||||
##########################
|
||||
|
||||
Like every open-source project, django CMS is always looking for motivated
|
||||
individuals to contribute to its source code.
|
||||
|
||||
There's more guidance on `how to contribute in our documentation <http://docs.django-cms.org/en/latest/introduction>`_.
|
||||
|
||||
Key points:
|
||||
|
||||
.. ATTENTION::
|
||||
|
||||
If you think you have discovered a security issue in our code, please report
|
||||
it **privately**, by emailing us at `security@django-cms.org`_.
|
||||
|
||||
Please **do not** raise it on:
|
||||
|
||||
* IRC
|
||||
* GitHub
|
||||
* either of our email lists
|
||||
|
||||
or in any other public forum until we have had a chance to deal with it.
|
||||
|
||||
|
||||
*********
|
||||
Community
|
||||
*********
|
||||
|
||||
People interested in developing for the django CMS should join the
|
||||
`django-cms-developers`_ mailing list as well as heading over to #django-cms on
|
||||
the `freenode`_ IRC network for help and to discuss the development.
|
||||
|
||||
You may also be interested in following `@djangocmsstatus`_ on twitter to get
|
||||
the GitHub commits as well as the hudson build reports. There is also a
|
||||
`@djangocms`_ account for less technical announcements.
|
||||
|
||||
|
||||
.. _security@django-cms.org: mailto:security@django-cms.org
|
||||
.. _fork: http://github.com/divio/django-cms
|
||||
.. _django-cms-developers: http://groups.google.com/group/django-cms-developers
|
||||
.. _freenode : http://freenode.net/
|
||||
.. _@djangocmsstatus : https://twitter.com/djangocmsstatus
|
||||
.. _@djangocms : https://twitter.com/djangocms
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2008, Batiste Bieler
|
||||
Copyright (c) 2008-present, Batiste Bieler
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
+47
-43
@@ -2,11 +2,9 @@
|
||||
django CMS
|
||||
##########
|
||||
.. image:: https://travis-ci.org/divio/django-cms.svg?branch=develop
|
||||
:target: http://travis-ci.org/divio/django-cms
|
||||
:target: https://travis-ci.org/divio/django-cms
|
||||
.. image:: https://img.shields.io/pypi/v/django-cms.svg
|
||||
:target: https://pypi.python.org/pypi/django-cms/
|
||||
.. image:: https://img.shields.io/pypi/dm/django-cms.svg
|
||||
:target: https://pypi.python.org/pypi/django-cms/
|
||||
.. image:: https://img.shields.io/badge/wheel-yes-green.svg
|
||||
:target: https://pypi.python.org/pypi/django-cms/
|
||||
.. image:: https://img.shields.io/pypi/l/django-cms.svg
|
||||
@@ -15,65 +13,68 @@ django CMS
|
||||
:target: https://codeclimate.com/github/divio/django-cms
|
||||
:alt: Code Climate
|
||||
|
||||
Open source enterprise content management system based on the django framework.
|
||||
Open source enterprise content management system based on the Django framework.
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/divio/django-cms/develop/docs/images/try-with-aldryn.png
|
||||
:target: http://demo.django-cms.org/
|
||||
:alt: Try demo with Aldryn
|
||||
.. image:: https://raw.githubusercontent.com/divio/django-cms/develop/docs/images/try-with-divio.png
|
||||
:target: http://try.django-cms.org/
|
||||
:alt: Try demo with Divio Cloud
|
||||
|
||||
.. ATTENTION:: To propose features, always open pull requests on the **develop** branch.
|
||||
It's the branch for features that will go into the next django CMS feature release.
|
||||
.. ATTENTION::
|
||||
|
||||
For fixes for 3.1.x releases, please work on **support/3.1.x** which contains
|
||||
the next patch release for 3.1.x series.
|
||||
To propose **significant new features**, open pull requests based on and made against the **develop** branch. It's
|
||||
the branch for features that will go into the next django CMS feature release.
|
||||
|
||||
For fixes for 3.0.x releases, please work on **support/3.0.x** which contains
|
||||
the next patch release for 3.0.x series.
|
||||
To propose **fixes and backwards-compatible improvements**, please work on the latest **release** branch. This is
|
||||
the branch that will become the next PyPI release ("the next version of django CMS").
|
||||
|
||||
The **master** branch is the current stable release, the one released on PyPI.
|
||||
Changes based on **master** will not be accepted.
|
||||
Security fixes will be backported to older branches by the core team as appropriate.
|
||||
|
||||
|
||||
********
|
||||
Features
|
||||
********
|
||||
|
||||
* Hierarchical pages
|
||||
* Extensive support for multilingual websites
|
||||
* Multi site support
|
||||
* Draft/Published workflows
|
||||
* Undo/Redo
|
||||
* Use the content blocks (placeholders) in your own apps (models)
|
||||
* Use the content blocks (static placeholders) anywhere in your templates
|
||||
* Edit content directly in the frontend on your pages
|
||||
* Hierarchical content plugins (columns, style changes etc)
|
||||
* Navigation rendering and extending from your apps
|
||||
* SEO friendly URLs
|
||||
* Highly integrative into your own apps
|
||||
* hierarchical pages
|
||||
* extensive built-in support for multilingual websites
|
||||
* multi-site support
|
||||
* draft/publish workflows
|
||||
* version control
|
||||
* a sophisticated publishing architecture, that's also usable in your own applications
|
||||
* frontend content editing
|
||||
* a hierarchical content structure for nested plugins
|
||||
* an extensible navigation system that your own applications can hook into
|
||||
* SEO-friendly URLs
|
||||
* designed to integrate thoroughly into other applications
|
||||
|
||||
Developing applications that integrate with and take advantage of django CMS features is easy and well-documented.
|
||||
|
||||
You can define editable areas, called placeholders, in your templates which you fill
|
||||
with many different so called CMS content plugins.
|
||||
A list of all the plugins can be found here:
|
||||
|
||||
`3rd party plugins <http://www.djangopackages.com/grids/g/django-cms/>`_
|
||||
|
||||
Should you be unable to find a suitable plugin for you needs, writing your own is very simple.
|
||||
|
||||
More information on `our website <http://www.django-cms.org>`_.
|
||||
More information on `our website <https://www.django-cms.org>`_.
|
||||
|
||||
************
|
||||
Requirements
|
||||
************
|
||||
|
||||
django CMS requires Django version 1.6.9 or later, 1.7 or 1.8, and Python 2.6, 2.7, 3.3 or 3.4.
|
||||
See the `Python/Django requirements for the current release version
|
||||
<http://docs.django-cms.org/en/latest/#software-version-requirements-and-release-notes>`_ in our documentation.
|
||||
|
||||
See the `installation how-to guide for an overview of some other requirements and dependencies of the current release
|
||||
<http://docs.django-cms.org/en/latest/how_to/install.html>`_
|
||||
|
||||
*************
|
||||
Documentation
|
||||
*************
|
||||
|
||||
Please head over to our `documentation <http://docs.django-cms.org/>`_ for all
|
||||
the details on how to install, extend and use the django CMS.
|
||||
We maintain documentation for several versions of the project. Key versions are:
|
||||
|
||||
* `stable <http://docs.django-cms.org>`_ (default), for the **current release** version
|
||||
* `latest <http://docs.django-cms.org/en/latest/>`_, representing the latest build of the **release-3.4.x branch**
|
||||
* `develop <http://docs.django-cms.org/en/develop/>`_, representing the latest build of the **develop branch**
|
||||
|
||||
For more information about our branch policy, see `Branches
|
||||
<http://docs.django-cms.org/en/latest/contributing/development-policies.html>`_.
|
||||
|
||||
Our documentation is hosted courtesy of `Read the Docs <https://readthedocs.org>`_.
|
||||
|
||||
|
||||
********
|
||||
Tutorial
|
||||
@@ -85,7 +86,7 @@ http://docs.django-cms.org/en/latest/introduction/index.html
|
||||
Quick Start
|
||||
***********
|
||||
|
||||
You can use the `django CMS installer <http://djangocms-installer.readthedocs.org>`_::
|
||||
You can use the `django CMS installer <https://djangocms-installer.readthedocs.io>`_::
|
||||
|
||||
$ pip install --upgrade virtualenv
|
||||
$ virtualenv env
|
||||
@@ -103,11 +104,14 @@ Getting Help
|
||||
Please head over to our IRC channel, #django-cms, on irc.freenode.net or write
|
||||
to our `mailing list <https://groups.google.com/forum/#!forum/django-cms>`_.
|
||||
|
||||
If you don't have an IRC client, you can `join our IRC channel using the KiwiIRC web client
|
||||
<https://kiwiirc.com/client/irc.freenode.net/django-cms>`_, which works pretty well.
|
||||
|
||||
******************
|
||||
Commercial support
|
||||
******************
|
||||
|
||||
This project is backed by `Divio <http://www.divio.com/>`_.
|
||||
This project is backed by `Divio <https://www.divio.com/en/commercial-support/>`_.
|
||||
If you need help implementing or hosting django CMS, please contact us:
|
||||
sales@divio.com.
|
||||
|
||||
@@ -118,7 +122,7 @@ Credits
|
||||
* Includes icons from `FamFamFam <http://www.famfamfam.com>`_.
|
||||
* Python tree engine powered by
|
||||
`django-treebeard <https://tabo.pe/projects/django-treebeard/>`_.
|
||||
* JavaScript tree in admin uses `jsTree <http://www.jstree.com>`_.
|
||||
* JavaScript tree in admin uses `jsTree <https://www.jstree.com>`_.
|
||||
* Many thanks to the
|
||||
`over 515 contributors <https://github.com/divio/django-cms/blob/master/AUTHORS>`_
|
||||
`over 515 contributors <https://github.com/divio/django-cms/blob/develop/AUTHORS>`_
|
||||
to the django CMS!
|
||||
|
||||
+4
-2
@@ -5,8 +5,10 @@
|
||||
- Build newest english JS translations (django-admin.py makemessages -l en -d djangojs)
|
||||
- Pull translations (tx pull -f -a)
|
||||
- Compile translations (django-admin.py compilemessages)
|
||||
- Make sure css files are compiled from latest sass source (gulp sass)
|
||||
- Make sure js files are bundled and minified propery from latest source (gulp bundle)
|
||||
- Make sure icons are recreated in the new place (gulp icons)
|
||||
- Make sure CSS files are compiled from latest sass source (gulp sass)
|
||||
- Make sure JS files are bundled and minified propery from latest source (gulp bundle)
|
||||
- Make sure to remove JS, CSS and font files from the older version of CMS
|
||||
- Check CHANGELOG that all closed PRs are present.
|
||||
- Prepare blog post, including "Release" tag
|
||||
- update AUTHORS file (python develop.py authors)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
last 2 versions
|
||||
> 1%
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '3.2.2'
|
||||
__version__ = '3.6.0'
|
||||
|
||||
default_app_config = 'cms.apps.CMSConfig'
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import bisect
|
||||
from cms.models import Title, Page, EmptyTitle
|
||||
from cms.utils import get_language_list
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.permissions import get_user_sites_queryset, load_view_restrictions
|
||||
from django.contrib.admin.views.main import ChangeList, ALL_VAR, IS_POPUP_VAR, \
|
||||
ORDER_TYPE_VAR, ORDER_VAR, SEARCH_VAR
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
|
||||
COPY_VAR = "copy"
|
||||
|
||||
|
||||
def cache_tree_children(queryset):
|
||||
"""
|
||||
For all items in the queryset, set the '_cached_children' attribute to a
|
||||
list. This attribute is in turn used by the 'get_children' method on the
|
||||
item, which would otherwise (if '_cached_children' is not set) cause a
|
||||
database query.
|
||||
|
||||
The queryset must be ordered by 'path', or the function will put the children
|
||||
in the wrong order.
|
||||
"""
|
||||
parents_dict = {}
|
||||
# Loop through the queryset twice, so that the function works even if the
|
||||
# mptt tree is broken. Since django caches querysets internally, the extra
|
||||
# computation time is minimal.
|
||||
for obj in queryset:
|
||||
parents_dict[obj.pk] = obj
|
||||
obj._cached_children = []
|
||||
for obj in queryset:
|
||||
parent = parents_dict.get(obj.parent_id)
|
||||
if parent:
|
||||
parent._cached_children.append(obj)
|
||||
|
||||
|
||||
class CMSChangeList(ChangeList):
|
||||
"""
|
||||
Renders a Changelist - In our case it looks like a tree - it's the list of
|
||||
*instances* in the Admin.
|
||||
It is usually responsible for pagination (not here though, we have a
|
||||
treeview)
|
||||
"""
|
||||
real_queryset = False
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
from cms.utils.helpers import current_site
|
||||
|
||||
self._current_site = current_site(request)
|
||||
super(CMSChangeList, self).__init__(request, *args, **kwargs)
|
||||
try:
|
||||
self.queryset = self.get_queryset(request)
|
||||
except: # pragma: no cover
|
||||
raise
|
||||
self.get_results(request)
|
||||
|
||||
if self._current_site:
|
||||
request.session['cms_admin_site'] = self._current_site.pk
|
||||
self.set_sites(request)
|
||||
|
||||
def get_queryset(self, request):
|
||||
if COPY_VAR in self.params:
|
||||
del self.params[COPY_VAR]
|
||||
if 'language' in self.params:
|
||||
del self.params['language']
|
||||
if 'page_id' in self.params:
|
||||
del self.params['page_id']
|
||||
qs = super(CMSChangeList, self).get_queryset(request).drafts()
|
||||
site = self.current_site()
|
||||
permissions = Page.permissions.get_change_id_list(request.user, site)
|
||||
if permissions != Page.permissions.GRANT_ALL:
|
||||
qs = qs.filter(pk__in=permissions)
|
||||
self.root_queryset = self.root_queryset.filter(pk__in=permissions)
|
||||
self.real_queryset = True
|
||||
qs = qs.filter(site=self._current_site)
|
||||
return qs
|
||||
|
||||
def is_filtered(self):
|
||||
from cms.utils.helpers import SITE_VAR
|
||||
lookup_params = self.params.copy() # a dictionary of the query string
|
||||
for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, SITE_VAR, 'language', 'page_id'):
|
||||
if i in lookup_params:
|
||||
del lookup_params[i]
|
||||
if not lookup_params.items() and not self.query:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_results(self, request):
|
||||
if self.real_queryset:
|
||||
super(CMSChangeList, self).get_results(request)
|
||||
if not self.is_filtered():
|
||||
self.full_result_count = self.result_count = self.root_queryset.drafts().count()
|
||||
else:
|
||||
self.full_result_count = self.root_queryset.drafts().count()
|
||||
|
||||
def set_items(self, request):
|
||||
site = self.current_site()
|
||||
# Get all the pages, ordered by tree ID (it's convenient to build the
|
||||
# tree using a stack now)
|
||||
pages = self.get_queryset(request).drafts().order_by('path').select_related('publisher_public')
|
||||
|
||||
# Get lists of page IDs for which the current user has
|
||||
# "permission to..." on the current site.
|
||||
if get_cms_setting('PERMISSION'):
|
||||
perm_edit_ids = Page.permissions.get_change_id_list(request.user, site)
|
||||
perm_publish_ids = Page.permissions.get_publish_id_list(request.user, site)
|
||||
perm_advanced_settings_ids = Page.permissions.get_advanced_settings_id_list(request.user, site)
|
||||
restricted_ids = Page.permissions.get_restricted_id_list(site)
|
||||
if perm_edit_ids and perm_edit_ids != Page.permissions.GRANT_ALL:
|
||||
pages = pages.filter(pk__in=perm_edit_ids)
|
||||
|
||||
root_pages = []
|
||||
# Cache view restrictions for the is_restricted template tag
|
||||
load_view_restrictions(request, pages)
|
||||
pages = list(pages)
|
||||
all_pages = pages[:] # That is, basically, a copy.
|
||||
# Unfortunately we cannot use the MPTT builtin code for pre-caching
|
||||
# the children here, because MPTT expects the tree to be 'complete'
|
||||
# and otherwise complaints about 'invalid item order'
|
||||
cache_tree_children(pages)
|
||||
ids = dict((page.id, page) for page in pages)
|
||||
parent_ids = {}
|
||||
for page in pages:
|
||||
if not page.parent_id in parent_ids:
|
||||
parent_ids[page.parent_id] = []
|
||||
parent_ids[page.parent_id].append(page)
|
||||
for page in pages:
|
||||
children = parent_ids.get(page.pk, [])
|
||||
# If the parent page is not among the nodes shown, this node should
|
||||
# be a "root node". The filtering for this has already been made, so
|
||||
# using the ids dictionary means this check is constant time
|
||||
page.root_node = page.parent_id not in ids
|
||||
|
||||
if get_cms_setting('PERMISSION'):
|
||||
# caching the permissions
|
||||
page.permission_edit_cache = perm_edit_ids == Page.permissions.GRANT_ALL or page.pk in perm_edit_ids
|
||||
page.permission_publish_cache = perm_publish_ids == Page.permissions.GRANT_ALL or page.pk in perm_publish_ids
|
||||
page.permission_advanced_settings_cache = perm_advanced_settings_ids == Page.permissions.GRANT_ALL or page.pk in perm_advanced_settings_ids
|
||||
page.permission_user_cache = request.user
|
||||
page.permission_restricted = page.pk in restricted_ids
|
||||
if page.root_node or self.is_filtered():
|
||||
page.last = True
|
||||
if len(children):
|
||||
# TODO: WTF!?!
|
||||
# The last one is not the last... wait, what?
|
||||
# children should NOT be a queryset. If it is, check that
|
||||
# your django-mptt version is 0.5.1
|
||||
children[-1].last = False
|
||||
page.menu_level = 0
|
||||
root_pages.append(page)
|
||||
if page.parent_id:
|
||||
page.get_cached_ancestors()
|
||||
else:
|
||||
page.ancestors_ascending = []
|
||||
|
||||
# Because 'children' is the reverse-FK accessor for the 'parent'
|
||||
# FK from Page->Page, we have to use wrong English here and set
|
||||
# an attribute called 'childrens'. We are aware that this is WRONG
|
||||
# but what should we do?
|
||||
|
||||
# If the queryset is filtered, do NOT set the 'childrens' attribute
|
||||
# since *ALL* pages will be in the 'root_pages' list and therefore
|
||||
# be displayed. (If the queryset is filtered, the result is not a
|
||||
# tree but rather a flat list).
|
||||
if self.is_filtered():
|
||||
page.childrens = []
|
||||
else:
|
||||
page.childrens = children
|
||||
|
||||
for page in all_pages:
|
||||
page.title_cache = {}
|
||||
page.all_languages = []
|
||||
if page.publisher_public_id:
|
||||
page.publisher_public.title_cache = {}
|
||||
page.publisher_public.all_languages = []
|
||||
ids[page.publisher_public_id] = page.publisher_public
|
||||
|
||||
titles = Title.objects.filter(page__in=ids)
|
||||
insort = bisect.insort # local copy to avoid globals lookup in the loop
|
||||
for title in titles:
|
||||
page = ids[title.page_id]
|
||||
page.title_cache[title.language] = title
|
||||
if not title.language in page.all_languages:
|
||||
insort(page.all_languages, title.language)
|
||||
site_id = self.current_site()
|
||||
languages = get_language_list(site_id)
|
||||
for page in all_pages:
|
||||
for lang in languages:
|
||||
if not lang in page.title_cache:
|
||||
page.title_cache[lang] = EmptyTitle(lang)
|
||||
self.root_pages = root_pages
|
||||
|
||||
def get_items(self):
|
||||
return self.root_pages
|
||||
|
||||
def set_sites(self, request):
|
||||
"""Sets sites property to current instance - used in tree view for
|
||||
sites combo.
|
||||
"""
|
||||
if get_cms_setting('PERMISSION'):
|
||||
self.sites = get_user_sites_queryset(request.user)
|
||||
else:
|
||||
self.sites = Site.objects.all()
|
||||
self.has_access_to_multiple_sites = len(self.sites) > 1
|
||||
|
||||
def current_site(self):
|
||||
return self._current_site
|
||||
@@ -1,11 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class PermissionForm(forms.Form):
|
||||
"""
|
||||
Holds the specific field for permissions
|
||||
"""
|
||||
copy_permissions = forms.BooleanField(label=_('Copy permissions'),
|
||||
required=False, initial=True)
|
||||
@@ -1,33 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
|
||||
from cms.models import Page
|
||||
from cms.utils import get_cms_setting
|
||||
|
||||
from cms.admin.dialog.forms import PermissionForm
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def get_copy_dialog(request, page_id):
|
||||
if not get_cms_setting('PERMISSION'):
|
||||
return HttpResponse('')
|
||||
|
||||
target_id = request.GET.get('target', False) or request.POST.get('target', False)
|
||||
callback = request.GET.get('callback', False) or request.POST.get('callback', False)
|
||||
page = get_object_or_404(Page, pk=page_id)
|
||||
if target_id:
|
||||
target = get_object_or_404(Page, pk=target_id)
|
||||
|
||||
if not page.has_change_permission(request) or (
|
||||
target_id and not target.has_add_permission(request)):
|
||||
raise Http404
|
||||
|
||||
context = {
|
||||
'dialog_id': 'dialog-copy',
|
||||
'form': PermissionForm(), # class needs to be instantiated
|
||||
'callback': callback,
|
||||
}
|
||||
|
||||
return render(request, "admin/cms/page/tree/copy_premissions.html", context)
|
||||
+1017
-310
File diff suppressed because it is too large
Load Diff
+1220
-1130
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from copy import deepcopy
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import site
|
||||
from django.contrib.auth import get_user_model, get_permission_codename
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db import OperationalError
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from cms.admin.forms import GlobalPagePermissionAdminForm, PagePermissionInlineAdminForm, ViewRestrictionInlineAdminForm
|
||||
from cms.exceptions import NoPermissionsException
|
||||
from cms.models import Page, PagePermission, GlobalPagePermission, PageUser
|
||||
from cms.models import PagePermission, GlobalPagePermission
|
||||
from cms.utils import permissions, page_permissions
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.helpers import classproperty
|
||||
from cms.utils.permissions import get_user_permission_level
|
||||
|
||||
|
||||
PERMISSION_ADMIN_INLINES = []
|
||||
|
||||
@@ -32,8 +35,20 @@ class PagePermissionInlineAdmin(TabularInline):
|
||||
# use special form, so we can override of user and group field
|
||||
form = PagePermissionInlineAdminForm
|
||||
classes = ['collapse', 'collapsed']
|
||||
exclude = ['can_view']
|
||||
extra = 0 # edit page load time boost
|
||||
show_with_view_permissions = False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
if not obj:
|
||||
return False
|
||||
return page_permissions.user_can_change_page_permissions(
|
||||
request.user,
|
||||
page=obj,
|
||||
site=obj.node.site,
|
||||
)
|
||||
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return self.has_change_permission(request, obj)
|
||||
|
||||
@classproperty
|
||||
def raw_id_fields(cls):
|
||||
@@ -55,18 +70,18 @@ class PagePermissionInlineAdmin(TabularInline):
|
||||
def get_queryset(self, request):
|
||||
"""
|
||||
Queryset change, so user with global change permissions can see
|
||||
all permissions. Otherwise can user see only permissions for
|
||||
all permissions. Otherwise user can see only permissions for
|
||||
peoples which are under him (he can't see his permissions, because
|
||||
this will lead to violation, when he can add more power to itself)
|
||||
this will lead to violation, when he can add more power to himself)
|
||||
"""
|
||||
# can see only permissions for users which are under him in tree
|
||||
site = Site.objects.get_current(request)
|
||||
|
||||
# here an exception can be thrown
|
||||
try:
|
||||
qs = self.model.objects.subordinate_to_user(request.user)
|
||||
return qs.filter(can_view=False)
|
||||
# can see only permissions for users which are under him in tree
|
||||
qs = self.model.objects.subordinate_to_user(request.user, site)
|
||||
except NoPermissionsException:
|
||||
return self.objects.get_empty_query_set()
|
||||
return self.model.objects.none()
|
||||
return qs.filter(can_view=self.show_with_view_permissions)
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
@@ -76,18 +91,20 @@ class PagePermissionInlineAdmin(TabularInline):
|
||||
"""
|
||||
exclude = self.exclude or []
|
||||
if obj:
|
||||
if not obj.has_add_permission(request):
|
||||
user = request.user
|
||||
if not obj.has_add_permission(user):
|
||||
exclude.append('can_add')
|
||||
if not obj.has_delete_permission(request):
|
||||
if not obj.has_delete_permission(user):
|
||||
exclude.append('can_delete')
|
||||
if not obj.has_publish_permission(request):
|
||||
if not obj.has_publish_permission(user):
|
||||
exclude.append('can_publish')
|
||||
if not obj.has_advanced_settings_permission(request):
|
||||
if not obj.has_advanced_settings_permission(user):
|
||||
exclude.append('can_change_advanced_settings')
|
||||
if not obj.has_move_page_permission(request):
|
||||
if not obj.has_move_page_permission(user):
|
||||
exclude.append('can_move_page')
|
||||
formset_cls = super(PagePermissionInlineAdmin, self
|
||||
).get_formset(request, obj=None, exclude=exclude, **kwargs)
|
||||
|
||||
kwargs['exclude'] = exclude
|
||||
formset_cls = super(PagePermissionInlineAdmin, self).get_formset(request, obj=obj, **kwargs)
|
||||
qs = self.get_queryset(request)
|
||||
if obj is not None:
|
||||
qs = qs.filter(page=obj)
|
||||
@@ -100,32 +117,7 @@ class ViewRestrictionInlineAdmin(PagePermissionInlineAdmin):
|
||||
form = ViewRestrictionInlineAdminForm
|
||||
verbose_name = _("View restriction")
|
||||
verbose_name_plural = _("View restrictions")
|
||||
exclude = [
|
||||
'can_add', 'can_change', 'can_delete', 'can_view',
|
||||
'can_publish', 'can_change_advanced_settings', 'can_move_page',
|
||||
'can_change_permissions'
|
||||
]
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
"""
|
||||
Some fields may be excluded here. User can change only permissions
|
||||
which are available for him. E.g. if user does not haves can_publish
|
||||
flag, he can't change assign can_publish permissions.
|
||||
"""
|
||||
formset_cls = super(PagePermissionInlineAdmin, self).get_formset(request, obj, **kwargs)
|
||||
qs = self.get_queryset(request)
|
||||
if obj is not None:
|
||||
qs = qs.filter(page=obj)
|
||||
formset_cls._queryset = qs
|
||||
return formset_cls
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""
|
||||
Returns a QuerySet of all model instances that can be edited by the
|
||||
admin site. This is used by changelist_view.
|
||||
"""
|
||||
qs = self.model.objects.subordinate_to_user(request.user)
|
||||
return qs.filter(can_view=True)
|
||||
show_with_view_permissions = True
|
||||
|
||||
|
||||
class GlobalPagePermissionAdmin(admin.ModelAdmin):
|
||||
@@ -138,8 +130,6 @@ class GlobalPagePermissionAdmin(admin.ModelAdmin):
|
||||
search_fields.append("user__%s" % field)
|
||||
search_fields.append('group__name')
|
||||
|
||||
exclude = []
|
||||
|
||||
list_display.append('can_change_advanced_settings')
|
||||
list_filter.append('can_change_advanced_settings')
|
||||
|
||||
@@ -154,6 +144,18 @@ class GlobalPagePermissionAdmin(admin.ModelAdmin):
|
||||
filter_copy.remove('user')
|
||||
return filter_copy
|
||||
|
||||
def has_add_permission(self, request):
|
||||
site = Site.objects.get_current(request)
|
||||
return permissions.user_can_add_global_permissions(request.user, site)
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
site = Site.objects.get_current(request)
|
||||
return permissions.user_can_change_global_permissions(request.user, site)
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
site = Site.objects.get_current(request)
|
||||
return permissions.user_can_delete_global_permissions(request.user, site)
|
||||
|
||||
@classproperty
|
||||
def raw_id_fields(cls):
|
||||
# Dynamically set raw_id_fields based on settings
|
||||
@@ -172,54 +174,6 @@ class GlobalPagePermissionAdmin(admin.ModelAdmin):
|
||||
return ['user'] if threshold else []
|
||||
|
||||
|
||||
class GenericCmsPermissionAdmin(object):
|
||||
"""
|
||||
Custom mixin for permission-enabled admin interfaces.
|
||||
"""
|
||||
|
||||
def update_permission_fieldsets(self, request, obj=None):
|
||||
"""
|
||||
Nobody can grant more than he haves, so check for user permissions
|
||||
to Page and User model and render fieldset depending on them.
|
||||
"""
|
||||
fieldsets = deepcopy(self.fieldsets)
|
||||
perm_models = (
|
||||
(Page, ugettext('Page permissions')),
|
||||
(PageUser, ugettext('User & Group permissions')),
|
||||
(PagePermission, ugettext('Page permissions management')),
|
||||
)
|
||||
for i, perm_model in enumerate(perm_models):
|
||||
model, title = perm_model
|
||||
opts, fields = model._meta, []
|
||||
name = model.__name__.lower()
|
||||
for key in ('add', 'change', 'delete'):
|
||||
perm_code = '%s.%s' % (opts.app_label, get_permission_codename(key, opts))
|
||||
if request.user.has_perm(perm_code):
|
||||
fields.append('can_%s_%s' % (key, name))
|
||||
if fields:
|
||||
fieldsets.insert(2 + i, (title, {'fields': (fields,)}))
|
||||
return fieldsets
|
||||
|
||||
def _has_change_permissions_permission(self, request):
|
||||
"""
|
||||
User is able to add/change objects only if he haves can change
|
||||
permission on some page.
|
||||
"""
|
||||
try:
|
||||
get_user_permission_level(request.user)
|
||||
except NoPermissionsException:
|
||||
return False
|
||||
return True
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return self._has_change_permissions_permission(request) and \
|
||||
super(self.__class__, self).has_add_permission(request)
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return self._has_change_permissions_permission(request) and \
|
||||
super(self.__class__, self).has_change_permission(request, obj)
|
||||
|
||||
|
||||
if get_cms_setting('PERMISSION'):
|
||||
admin.site.register(GlobalPagePermission, GlobalPagePermissionAdmin)
|
||||
PERMISSION_ADMIN_INLINES.extend([
|
||||
|
||||
+861
-420
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from functools import update_wrapper
|
||||
import copy
|
||||
import json
|
||||
|
||||
from django.conf.urls import url
|
||||
@@ -7,14 +8,20 @@ from django.contrib import admin
|
||||
from django.contrib.admin import ModelAdmin
|
||||
from django.contrib.auth.admin import csrf_protect_m
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseBadRequest
|
||||
from django.http.request import QueryDict
|
||||
from django.utils.translation import override
|
||||
from django.utils.six.moves.urllib.parse import urlparse
|
||||
|
||||
from cms.admin.forms import RequestToolbarForm
|
||||
from cms.models import UserSettings
|
||||
from cms.toolbar.toolbar import CMSToolbar
|
||||
from cms.utils.page import get_page_from_request
|
||||
from cms.utils.urlutils import admin_reverse
|
||||
|
||||
|
||||
class SettingsAdmin(ModelAdmin):
|
||||
|
||||
def get_urls(self):
|
||||
def wrap(view):
|
||||
def wrapper(*args, **kwargs):
|
||||
@@ -28,6 +35,9 @@ class SettingsAdmin(ModelAdmin):
|
||||
url(r'^session_store/$',
|
||||
self.session_store,
|
||||
name='%s_%s_session_store' % info),
|
||||
url(r'^cms-toolbar/$',
|
||||
wrap(self.get_toolbar),
|
||||
name='%s_%s_get_toolbar' % info),
|
||||
url(r'^$',
|
||||
wrap(self.change_view),
|
||||
name='%s_%s_change' % info),
|
||||
@@ -62,6 +72,34 @@ class SettingsAdmin(ModelAdmin):
|
||||
content_type="application/json"
|
||||
)
|
||||
|
||||
def get_toolbar(self, request):
|
||||
form = RequestToolbarForm(request.GET or None)
|
||||
|
||||
if not form.is_valid():
|
||||
return HttpResponseBadRequest('Invalid parameters')
|
||||
|
||||
form_data = form.cleaned_data
|
||||
cms_path = form_data.get('cms_path') or request.path_info
|
||||
origin_url = urlparse(cms_path)
|
||||
attached_obj = form_data.get('attached_obj')
|
||||
current_page = get_page_from_request(request, use_path=origin_url.path, clean_path=True)
|
||||
|
||||
if attached_obj and current_page and not (attached_obj == current_page):
|
||||
return HttpResponseBadRequest('Generic object does not match current page')
|
||||
|
||||
data = QueryDict(query_string=origin_url.query, mutable=True)
|
||||
placeholders = request.GET.getlist("placeholders[]")
|
||||
|
||||
if placeholders:
|
||||
data.setlist('placeholders[]', placeholders)
|
||||
|
||||
request = copy.copy(request)
|
||||
request.GET = data
|
||||
request.current_page = current_page
|
||||
request.toolbar = CMSToolbar(request, request_path=origin_url.path, _async=True)
|
||||
request.toolbar.set_object(attached_obj or current_page)
|
||||
return HttpResponse(request.toolbar.render())
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.user = request.user
|
||||
obj.save()
|
||||
|
||||
@@ -4,7 +4,7 @@ from cms.admin.placeholderadmin import PlaceholderAdminMixin
|
||||
|
||||
|
||||
class StaticPlaceholderAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
|
||||
list_display = ('name', 'code', 'site', 'creation_method')
|
||||
list_display = ('get_name', 'code', 'site', 'creation_method')
|
||||
search_fields = ('name', 'code',)
|
||||
exclude = ('creation_method',)
|
||||
list_filter = ('creation_method', 'site')
|
||||
|
||||
+122
-19
@@ -1,15 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import get_user_model
|
||||
from copy import deepcopy
|
||||
|
||||
from cms.admin.forms import PageUserForm, PageUserGroupForm
|
||||
from cms.admin.permissionadmin import GenericCmsPermissionAdmin
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import site
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils.translation import ugettext
|
||||
|
||||
from cms.admin.forms import PageUserChangeForm, PageUserGroupForm
|
||||
from cms.exceptions import NoPermissionsException
|
||||
from cms.models import PageUser, PageUserGroup
|
||||
from cms.models import Page, PagePermission, PageUser, PageUserGroup
|
||||
from cms.utils.compat.forms import UserAdmin
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.permissions import get_subordinate_users
|
||||
from django.contrib.admin import site
|
||||
from cms.utils.permissions import (
|
||||
get_model_permission_codename,
|
||||
get_subordinate_groups,
|
||||
get_subordinate_users,
|
||||
get_user_permission_level,
|
||||
)
|
||||
|
||||
|
||||
user_model = get_user_model()
|
||||
admin_class = UserAdmin
|
||||
@@ -17,21 +26,91 @@ for model, admin_instance in site._registry.items():
|
||||
if model == user_model:
|
||||
admin_class = admin_instance.__class__
|
||||
|
||||
class PageUserAdmin(admin_class, GenericCmsPermissionAdmin):
|
||||
form = PageUserForm
|
||||
add_form = PageUserForm
|
||||
model = PageUser
|
||||
|
||||
class GenericCmsPermissionAdmin(object):
|
||||
|
||||
def get_subordinates(self, user, site):
|
||||
raise NotImplementedError
|
||||
|
||||
def _has_change_permissions_permission(self, request):
|
||||
"""
|
||||
User is able to add/change objects only if he haves can change
|
||||
permission on some page.
|
||||
"""
|
||||
site = Site.objects.get_current(request)
|
||||
|
||||
try:
|
||||
get_user_permission_level(request.user, site)
|
||||
except NoPermissionsException:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form_class = super(GenericCmsPermissionAdmin, self).get_form(request, obj, **kwargs)
|
||||
form_class._current_user = request.user
|
||||
return form_class
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(PageUserAdmin, self).get_queryset(request)
|
||||
try:
|
||||
user_id_set = get_subordinate_users(request.user).values_list('id', flat=True)
|
||||
return qs.filter(pk__in=user_id_set)
|
||||
except NoPermissionsException:
|
||||
return self.model.objects.get_empty_query_set()
|
||||
queryset = super(GenericCmsPermissionAdmin, self).get_queryset(request)
|
||||
site = Site.objects.get_current(request)
|
||||
user_ids = self.get_subordinates(request.user, site).values_list('pk', flat=True)
|
||||
return queryset.filter(pk__in=user_ids)
|
||||
|
||||
def has_add_permission(self, request):
|
||||
has_model_perm = super(GenericCmsPermissionAdmin, self).has_add_permission(request)
|
||||
|
||||
if not has_model_perm:
|
||||
return False
|
||||
return self._has_change_permissions_permission(request)
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
has_model_perm = super(GenericCmsPermissionAdmin, self).has_change_permission(request, obj)
|
||||
|
||||
if not has_model_perm:
|
||||
return False
|
||||
return self._has_change_permissions_permission(request)
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
has_model_perm = super(GenericCmsPermissionAdmin, self).has_delete_permission(request, obj)
|
||||
|
||||
if not has_model_perm:
|
||||
return False
|
||||
return self._has_change_permissions_permission(request)
|
||||
|
||||
def has_view_permission(self, request, obj=None):
|
||||
# For django 2.1
|
||||
# Default is to return True if user got `change` perm, but we have to
|
||||
# get in consideration also cms permission system
|
||||
return self.has_change_permission(request, obj)
|
||||
|
||||
|
||||
class PageUserGroupAdmin(admin.ModelAdmin, GenericCmsPermissionAdmin):
|
||||
class PageUserAdmin(GenericCmsPermissionAdmin, admin_class):
|
||||
form = PageUserChangeForm
|
||||
model = PageUser
|
||||
|
||||
def get_subordinates(self, user, site):
|
||||
return get_subordinate_users(user, site).values_list('pk', flat=True)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
fields = super(PageUserAdmin, self).get_readonly_fields(request, obj)
|
||||
|
||||
if not request.user.is_superuser:
|
||||
# Non superusers can't set superuser status on
|
||||
# their subordinates.
|
||||
fields = list(fields) + ['is_superuser']
|
||||
return fields
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if not change:
|
||||
# By default set the staff flag to True
|
||||
# when a PageUser is first created
|
||||
obj.is_staff = True
|
||||
# Set the created_by field to the current user
|
||||
obj.created_by = request.user
|
||||
super(PageUserAdmin, self).save_model(request, obj, form, change)
|
||||
|
||||
|
||||
class PageUserGroupAdmin(GenericCmsPermissionAdmin, admin.ModelAdmin):
|
||||
form = PageUserGroupForm
|
||||
list_display = ('name', 'created_by')
|
||||
|
||||
@@ -40,7 +119,31 @@ class PageUserGroupAdmin(admin.ModelAdmin, GenericCmsPermissionAdmin):
|
||||
]
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
return self.update_permission_fieldsets(request, obj)
|
||||
"""
|
||||
Nobody can grant more than he haves, so check for user permissions
|
||||
to Page and User model and render fieldset depending on them.
|
||||
"""
|
||||
fieldsets = deepcopy(self.fieldsets)
|
||||
perm_models = (
|
||||
(Page, ugettext('Page permissions')),
|
||||
(PageUser, ugettext('User & Group permissions')),
|
||||
(PagePermission, ugettext('Page permissions management')),
|
||||
)
|
||||
for i, perm_model in enumerate(perm_models):
|
||||
fields = []
|
||||
model, title = perm_model
|
||||
name = model.__name__.lower()
|
||||
for key in ('add', 'change', 'delete'):
|
||||
perm_code = get_model_permission_codename(model, action=key)
|
||||
if request.user.has_perm(perm_code):
|
||||
fields.append('can_%s_%s' % (key, name))
|
||||
if fields:
|
||||
fieldsets.insert(2 + i, (title, {'fields': (fields,)}))
|
||||
return fieldsets
|
||||
|
||||
def get_subordinates(self, user, site):
|
||||
return get_subordinate_groups(user, site).values_list('pk', flat=True)
|
||||
|
||||
|
||||
if get_cms_setting('PERMISSION'):
|
||||
admin.site.register(PageUser, PageUserAdmin)
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from cms.models import Page, Title, CMSPlugin, Placeholder
|
||||
|
||||
|
||||
def revert_plugins(request, version_id, obj):
|
||||
from cms.utils.reversion_hacks import Version
|
||||
|
||||
version = get_object_or_404(Version, pk=version_id)
|
||||
revs = [related_version.object_version for related_version in version.revision.version_set.all()]
|
||||
cms_plugin_list = []
|
||||
placeholders = {}
|
||||
plugin_list = []
|
||||
titles = []
|
||||
others = []
|
||||
page = obj
|
||||
for rev in revs:
|
||||
obj = rev.object
|
||||
if obj.__class__ == Placeholder:
|
||||
placeholders[obj.pk] = obj
|
||||
if obj.__class__ == CMSPlugin:
|
||||
cms_plugin_list.append(obj)
|
||||
elif hasattr(obj, 'cmsplugin_ptr_id'):
|
||||
plugin_list.append(obj)
|
||||
elif obj.__class__ == Page:
|
||||
pass
|
||||
elif obj.__class__ == Title:
|
||||
titles.append(obj)
|
||||
else:
|
||||
others.append(rev)
|
||||
if not page.has_change_permission(request):
|
||||
raise Http404
|
||||
current_plugins = list(CMSPlugin.objects.filter(placeholder__page=page))
|
||||
for pk, placeholder in placeholders.items():
|
||||
# admin has already created the placeholders/ get them instead
|
||||
try:
|
||||
placeholders[pk] = page.placeholders.get(slot=placeholder.slot)
|
||||
except Placeholder.DoesNotExist:
|
||||
placeholders[pk].save()
|
||||
page.placeholders.add(placeholders[pk])
|
||||
for plugin in cms_plugin_list:
|
||||
# connect plugins to the correct placeholder
|
||||
plugin.placeholder = placeholders[plugin.placeholder_id]
|
||||
plugin.save(no_signals=True)
|
||||
for plugin in cms_plugin_list:
|
||||
plugin.save()
|
||||
for p in plugin_list:
|
||||
if int(p.cmsplugin_ptr_id) == int(plugin.pk):
|
||||
plugin.set_base_attr(p)
|
||||
p.save()
|
||||
for old in current_plugins:
|
||||
if old.pk == plugin.pk:
|
||||
plugin.save()
|
||||
current_plugins.remove(old)
|
||||
for title in titles:
|
||||
title.page = page
|
||||
try:
|
||||
title.save()
|
||||
except:
|
||||
title.pk = Title.objects.get(page=page, language=title.language).pk
|
||||
title.save()
|
||||
for other in others:
|
||||
other.object.save()
|
||||
for plugin in current_plugins:
|
||||
plugin.delete()
|
||||
+63
-78
@@ -13,6 +13,7 @@ from django.contrib.sites.models import Site
|
||||
from django.core.exceptions import FieldError
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.template.loader import get_template
|
||||
from django.utils import six
|
||||
@@ -30,10 +31,11 @@ from cms.models.pluginmodel import CMSPlugin
|
||||
from cms.models.titlemodels import Title
|
||||
from cms.plugin_base import CMSPluginBase
|
||||
from cms.plugin_pool import plugin_pool
|
||||
from cms.utils import copy_plugins
|
||||
from cms.utils import copy_plugins, get_current_site
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.i18n import get_language_list
|
||||
from cms.utils.permissions import _thread_locals, current_user, has_page_change_permission
|
||||
from cms.utils.page import get_available_slug
|
||||
from cms.utils.permissions import _thread_locals, current_user
|
||||
from menus.menu_pool import menu_pool
|
||||
|
||||
|
||||
@@ -41,26 +43,6 @@ from menus.menu_pool import menu_pool
|
||||
# Helpers/Internals
|
||||
#===============================================================================
|
||||
|
||||
def generate_valid_slug(source, parent, language):
|
||||
"""
|
||||
Generate a valid slug for a page from source for the given language.
|
||||
Parent is passed so we can make sure the slug is unique for this level in
|
||||
the page tree.
|
||||
"""
|
||||
if parent:
|
||||
qs = Title.objects.filter(language=language, page__parent=parent)
|
||||
else:
|
||||
qs = Title.objects.filter(language=language, page__parent__isnull=True)
|
||||
used = list(qs.values_list('slug', flat=True))
|
||||
baseslug = slugify(source)
|
||||
slug = baseslug
|
||||
i = 1
|
||||
if used:
|
||||
while slug in used:
|
||||
slug = '%s-%s' % (baseslug, i)
|
||||
i += 1
|
||||
return slug
|
||||
|
||||
|
||||
def _verify_apphook(apphook, namespace):
|
||||
"""
|
||||
@@ -97,7 +79,6 @@ def _verify_plugin_type(plugin_type):
|
||||
"""
|
||||
if (hasattr(plugin_type, '__module__') and
|
||||
issubclass(plugin_type, CMSPluginBase)):
|
||||
plugin_pool.set_plugin_meta()
|
||||
plugin_model = plugin_type.model
|
||||
assert plugin_type in plugin_pool.plugins.values()
|
||||
plugin_type = plugin_type.__name__
|
||||
@@ -117,6 +98,7 @@ def _verify_plugin_type(plugin_type):
|
||||
# Public API
|
||||
#===============================================================================
|
||||
|
||||
@transaction.atomic
|
||||
def create_page(title, template, language, menu_title=None, slug=None,
|
||||
apphook=None, apphook_namespace=None, redirect=None, meta_description=None,
|
||||
created_by='python-api', parent=None,
|
||||
@@ -124,20 +106,13 @@ def create_page(title, template, language, menu_title=None, slug=None,
|
||||
in_navigation=False, soft_root=False, reverse_id=None,
|
||||
navigation_extenders=None, published=False, site=None,
|
||||
login_required=False, limit_visibility_in_menu=constants.VISIBILITY_ALL,
|
||||
position="last-child", overwrite_url=None, xframe_options=Page.X_FRAME_OPTIONS_INHERIT):
|
||||
position="last-child", overwrite_url=None,
|
||||
xframe_options=Page.X_FRAME_OPTIONS_INHERIT, page_title=None):
|
||||
"""
|
||||
Create a CMS Page and it's title for the given language
|
||||
|
||||
See docs/extending_cms/api_reference.rst for more info
|
||||
"""
|
||||
# ugly permissions hack
|
||||
if created_by and isinstance(created_by, get_user_model()):
|
||||
_thread_locals.user = created_by
|
||||
|
||||
created_by = getattr(created_by, get_user_model().USERNAME_FIELD)
|
||||
else:
|
||||
_thread_locals.user = None
|
||||
|
||||
# validate template
|
||||
if not template == TEMPLATE_INHERITANCE_MAGIC:
|
||||
assert template in [tpl[0] for tpl in get_cms_setting('TEMPLATES')]
|
||||
@@ -145,21 +120,17 @@ def create_page(title, template, language, menu_title=None, slug=None,
|
||||
|
||||
# validate site
|
||||
if not site:
|
||||
site = Site.objects.get_current()
|
||||
site = get_current_site()
|
||||
else:
|
||||
assert isinstance(site, Site)
|
||||
|
||||
# validate language:
|
||||
assert language in get_language_list(site), get_cms_setting('LANGUAGES').get(site.pk)
|
||||
|
||||
# set default slug:
|
||||
if not slug:
|
||||
slug = generate_valid_slug(title, parent, language)
|
||||
|
||||
# validate parent
|
||||
if parent:
|
||||
assert isinstance(parent, Page)
|
||||
parent = Page.objects.get(pk=parent.pk)
|
||||
assert parent.publisher_is_draft
|
||||
|
||||
# validate publication date
|
||||
if publication_date:
|
||||
@@ -180,27 +151,28 @@ def create_page(title, template, language, menu_title=None, slug=None,
|
||||
|
||||
# validate position
|
||||
assert position in ('last-child', 'first-child', 'left', 'right')
|
||||
if parent:
|
||||
if position in ('last-child', 'first-child'):
|
||||
parent_id = parent.pk
|
||||
else:
|
||||
parent_id = parent.parent_id
|
||||
else:
|
||||
parent_id = None
|
||||
target_node = parent.node if parent else None
|
||||
|
||||
# validate and normalize apphook
|
||||
if apphook:
|
||||
application_urls = _verify_apphook(apphook, apphook_namespace)
|
||||
else:
|
||||
application_urls = None
|
||||
|
||||
# ugly permissions hack
|
||||
if created_by and isinstance(created_by, get_user_model()):
|
||||
_thread_locals.user = created_by
|
||||
created_by = getattr(created_by, get_user_model().USERNAME_FIELD)
|
||||
else:
|
||||
_thread_locals.user = None
|
||||
|
||||
if reverse_id:
|
||||
if Page.objects.drafts().filter(reverse_id=reverse_id, site=site).count():
|
||||
if Page.objects.drafts().filter(reverse_id=reverse_id, node__site=site).exists():
|
||||
raise FieldError('A page with the reverse_id="%s" already exist.' % reverse_id)
|
||||
|
||||
page = Page(
|
||||
created_by=created_by,
|
||||
changed_by=created_by,
|
||||
parent_id=parent_id,
|
||||
publication_date=publication_date,
|
||||
publication_end_date=publication_end_date,
|
||||
in_navigation=in_navigation,
|
||||
@@ -210,19 +182,18 @@ def create_page(title, template, language, menu_title=None, slug=None,
|
||||
template=template,
|
||||
application_urls=application_urls,
|
||||
application_namespace=apphook_namespace,
|
||||
site=site,
|
||||
login_required=login_required,
|
||||
limit_visibility_in_menu=limit_visibility_in_menu,
|
||||
xframe_options=xframe_options,
|
||||
)
|
||||
page = page.add_root(instance=page)
|
||||
|
||||
if parent:
|
||||
page = page.move(target=parent, pos=position)
|
||||
page.set_tree_node(site=site, target=target_node, position=position)
|
||||
page.save()
|
||||
page.rescan_placeholders()
|
||||
|
||||
create_title(
|
||||
language=language,
|
||||
title=title,
|
||||
page_title=page_title,
|
||||
menu_title=menu_title,
|
||||
slug=slug,
|
||||
redirect=redirect,
|
||||
@@ -234,13 +205,17 @@ def create_page(title, template, language, menu_title=None, slug=None,
|
||||
if published:
|
||||
page.publish(language)
|
||||
|
||||
if parent and position in ('last-child', 'first-child'):
|
||||
parent._clear_node_cache()
|
||||
|
||||
del _thread_locals.user
|
||||
return page.reload()
|
||||
return page
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def create_title(language, title, page, menu_title=None, slug=None,
|
||||
redirect=None, meta_description=None,
|
||||
parent=None, overwrite_url=None):
|
||||
redirect=None, meta_description=None, parent=None,
|
||||
overwrite_url=None, page_title=None, path=None):
|
||||
"""
|
||||
Create a title.
|
||||
|
||||
@@ -252,30 +227,39 @@ def create_title(language, title, page, menu_title=None, slug=None,
|
||||
assert isinstance(page, Page)
|
||||
|
||||
# validate language:
|
||||
assert language in get_language_list(page.site_id)
|
||||
assert language in get_language_list(page.node.site_id)
|
||||
|
||||
# set default slug:
|
||||
if not slug:
|
||||
slug = generate_valid_slug(title, parent, language)
|
||||
base = page.get_path_for_slug(slugify(title), language)
|
||||
slug = get_available_slug(page.node.site, base, language)
|
||||
|
||||
if overwrite_url:
|
||||
path = overwrite_url.strip('/')
|
||||
elif path is None:
|
||||
path = page.get_path_for_slug(slug, language)
|
||||
|
||||
title = Title.objects.create(
|
||||
language=language,
|
||||
title=title,
|
||||
menu_title=menu_title,
|
||||
page_title=page_title,
|
||||
slug=slug,
|
||||
path=path,
|
||||
redirect=redirect,
|
||||
meta_description=meta_description,
|
||||
page=page
|
||||
page=page,
|
||||
has_url_overwrite=bool(overwrite_url),
|
||||
)
|
||||
|
||||
if overwrite_url:
|
||||
title.has_url_overwrite = True
|
||||
title.path = overwrite_url
|
||||
title.save()
|
||||
page_languages = page.get_languages()
|
||||
|
||||
if language not in page_languages:
|
||||
page.update_languages(page_languages + [language])
|
||||
return title
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def add_plugin(placeholder, plugin_type, language, position='last-child',
|
||||
target=None, **data):
|
||||
"""
|
||||
@@ -424,7 +408,7 @@ def assign_user_to_page(page, user, grant_on=ACCESS_PAGE_AND_DESCENDANTS,
|
||||
page_permission = GlobalPagePermission(
|
||||
user=user, can_recover_page=can_recover_page, **data)
|
||||
page_permission.save()
|
||||
page_permission.sites.add(Site.objects.get_current())
|
||||
page_permission.sites.add(get_current_site())
|
||||
return page_permission
|
||||
|
||||
|
||||
@@ -437,12 +421,7 @@ def publish_page(page, user, language):
|
||||
"""
|
||||
page = page.reload()
|
||||
|
||||
class FakeRequest(object):
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
|
||||
request = FakeRequest(user)
|
||||
if not page.has_publish_permission(request):
|
||||
if not page.has_publish_permission(user):
|
||||
raise PermissionDenied()
|
||||
# Set the current_user to have the page's changed_by
|
||||
# attribute set correctly.
|
||||
@@ -457,10 +436,12 @@ def publish_pages(include_unpublished=False, language=None, site=None):
|
||||
Create published public version of selected drafts.
|
||||
"""
|
||||
qs = Page.objects.drafts()
|
||||
|
||||
if not include_unpublished:
|
||||
qs = qs.filter(title_set__published=True).distinct()
|
||||
|
||||
if site:
|
||||
qs = qs.filter(site=site)
|
||||
qs = qs.filter(node__site=site)
|
||||
|
||||
output_language = None
|
||||
for i, page in enumerate(qs):
|
||||
@@ -493,7 +474,7 @@ def get_page_draft(page):
|
||||
if page.publisher_is_draft:
|
||||
return page
|
||||
else:
|
||||
return page.publisher_draft
|
||||
return page.publisher_public
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -524,9 +505,9 @@ def copy_plugins_to_language(page, source_language, target_language,
|
||||
for placeholder in placeholders:
|
||||
# only_empty is True we check if the placeholder already has plugins and
|
||||
# we skip it if has some
|
||||
if not only_empty or not placeholder.cmsplugin_set.filter(language=target_language).exists():
|
||||
if not only_empty or not placeholder.get_plugins(language=target_language).exists():
|
||||
plugins = list(
|
||||
placeholder.cmsplugin_set.filter(language=source_language).order_by('path'))
|
||||
placeholder.get_plugins(language=source_language).order_by('path'))
|
||||
copied_plugins = copy_plugins.copy_plugins_to(plugins, placeholder, target_language)
|
||||
copied += len(copied_plugins)
|
||||
return copied
|
||||
@@ -539,9 +520,13 @@ def can_change_page(request):
|
||||
This will work across all permission-related setting, with a unified interface
|
||||
to permission checking.
|
||||
"""
|
||||
# check global permissions if CMS_PERMISSION is active
|
||||
global_permission = get_cms_setting('PERMISSION') and has_page_change_permission(request)
|
||||
# check if user has page edit permission
|
||||
page_permission = request.current_page and request.current_page.has_change_permission(request)
|
||||
from cms.utils import page_permissions
|
||||
|
||||
return global_permission or page_permission
|
||||
user = request.user
|
||||
current_page = request.current_page
|
||||
|
||||
if current_page:
|
||||
return page_permissions.user_can_change_page(user, current_page)
|
||||
|
||||
site = Site.objects.get_current(request)
|
||||
return page_permissions.user_can_change_all_pages(user, site)
|
||||
|
||||
+63
-2
@@ -1,11 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
class CMSApp(object):
|
||||
#: list of urlconfs: example: ``_urls = ["myapp.urls"]``
|
||||
_urls = []
|
||||
#: list of menu classes: example: ``_menus = [MyAppMenu]``
|
||||
_menus = []
|
||||
#: name of the apphook (required)
|
||||
name = None
|
||||
urls = None
|
||||
menus = []
|
||||
#: name of the app, this enables Django namespaces support (optional)
|
||||
app_name = None
|
||||
#: configuration model (optional)
|
||||
app_config = None
|
||||
#: if set to true, apphook inherits permissions from the current page
|
||||
permissions = True
|
||||
#: list of application names to exclude from inheriting CMS permissions
|
||||
exclude_permissions = []
|
||||
|
||||
def __new__(cls):
|
||||
@@ -25,10 +34,62 @@ class CMSApp(object):
|
||||
return super(CMSApp, cls).__new__(cls)
|
||||
|
||||
def get_configs(self):
|
||||
"""
|
||||
Returns all the apphook configuration instances.
|
||||
"""
|
||||
raise NotImplemented('Configurable AppHooks must implement this method')
|
||||
|
||||
def get_config(self, namespace):
|
||||
"""
|
||||
Returns the apphook configuration instance linked to the given namespace
|
||||
"""
|
||||
raise NotImplemented('Configurable AppHooks must implement this method')
|
||||
|
||||
def get_config_add_url(self):
|
||||
"""
|
||||
Returns the url to add a new apphook configuration instance
|
||||
(usually the model admin add view)
|
||||
"""
|
||||
raise NotImplemented('Configurable AppHooks must implement this method')
|
||||
|
||||
def get_menus(self, page=None, language=None, **kwargs):
|
||||
"""
|
||||
Returns the menus for the apphook instance, eventually selected according
|
||||
to the given arguments.
|
||||
|
||||
By default it returns the menus assigned to :py:attr:`CMSApp._menus`.
|
||||
|
||||
If no menus are returned, then the user will need to attach menus to pages
|
||||
manually in the admin.
|
||||
|
||||
This method must return all the menus used by this apphook if no arguments are
|
||||
provided. Example::
|
||||
|
||||
if page and page.reverse_id == 'page1':
|
||||
return [Menu1]
|
||||
elif page and page.reverse_id == 'page2':
|
||||
return [Menu2]
|
||||
else:
|
||||
return [Menu1, Menu2]
|
||||
|
||||
:param page: page the apphook is attached to
|
||||
:param language: current site language
|
||||
:return: list of menu classes
|
||||
"""
|
||||
return self._menus
|
||||
|
||||
def get_urls(self, page=None, language=None, **kwargs):
|
||||
"""
|
||||
Returns the urlconfs for the apphook instance, eventually selected
|
||||
according to the given arguments.
|
||||
|
||||
By default it returns the urls assigned to :py:attr:`CMSApp._urls`
|
||||
|
||||
This method **must** return a non empty list of urlconfs,
|
||||
even if no argument is passed.
|
||||
|
||||
:param page: page the apphook is attached to
|
||||
:param language: current site language
|
||||
:return: list of urlconfs strings
|
||||
"""
|
||||
return self._urls
|
||||
|
||||
+9
-13
@@ -2,11 +2,12 @@
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.module_loading import autodiscover_modules, import_string
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from cms.app_base import CMSApp
|
||||
from cms.exceptions import AppAlreadyRegistered
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.django_load import load, iterload_objects
|
||||
|
||||
|
||||
class ApphookPool(object):
|
||||
@@ -27,11 +28,6 @@ class ApphookPool(object):
|
||||
if app is None:
|
||||
return lambda app: self.register(app, discovering_apps)
|
||||
|
||||
if app.__module__.split('.')[-1] == 'cms_app':
|
||||
warnings.warn('cms_app.py filename is deprecated, '
|
||||
'and it will be removed in version 3.4; '
|
||||
'please rename it to cms_apps.py', DeprecationWarning)
|
||||
|
||||
if self.apphooks and not discovering_apps:
|
||||
return app
|
||||
|
||||
@@ -56,16 +52,15 @@ class ApphookPool(object):
|
||||
self.apphooks = get_cms_setting('APPHOOKS')
|
||||
|
||||
if self.apphooks:
|
||||
for cls in iterload_objects(self.apphooks):
|
||||
for path in self.apphooks:
|
||||
cls = import_string(path)
|
||||
try:
|
||||
self.register(cls, discovering_apps=True)
|
||||
except AppAlreadyRegistered:
|
||||
pass
|
||||
|
||||
else:
|
||||
# FIXME: Remove in 3.4
|
||||
load('cms_app')
|
||||
load('cms_apps')
|
||||
autodiscover_modules('cms_apps')
|
||||
|
||||
self.discovered = True
|
||||
|
||||
@@ -78,7 +73,7 @@ class ApphookPool(object):
|
||||
for app_name in self.apps:
|
||||
app = self.apps[app_name]
|
||||
|
||||
if app.urls:
|
||||
if app.get_urls():
|
||||
hooks.append((app_name, app.name))
|
||||
|
||||
# Unfortunately, we lose the ordering since we now have a list of
|
||||
@@ -97,10 +92,11 @@ class ApphookPool(object):
|
||||
# deprecated: return apphooks registered in db with urlconf name
|
||||
# instead of apphook class name
|
||||
for app in self.apps.values():
|
||||
if app_name in app.urls:
|
||||
if app_name in app.get_urls():
|
||||
return app
|
||||
|
||||
raise ImproperlyConfigured('No registered apphook %r found' % app_name)
|
||||
warnings.warn(_('No registered apphook "%r" found') % app_name)
|
||||
return None
|
||||
|
||||
|
||||
apphook_pool = ApphookPool()
|
||||
|
||||
+71
-63
@@ -1,32 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import with_statement
|
||||
|
||||
from django.db import OperationalError, ProgrammingError
|
||||
|
||||
from cms.utils.compat import DJANGO_1_8
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
# Python < 2.7
|
||||
from django.utils.datastructures import SortedDict as OrderedDict
|
||||
try:
|
||||
from importlib import import_module
|
||||
except ImportError:
|
||||
# Python < 2.7
|
||||
from django.utils.importlib import import_module
|
||||
from collections import OrderedDict
|
||||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import (RegexURLResolver, Resolver404, reverse,
|
||||
RegexURLPattern)
|
||||
from django.db import OperationalError, ProgrammingError
|
||||
from django.utils import six
|
||||
from django.utils.translation import get_language, override
|
||||
from django.urls import Resolver404, reverse
|
||||
|
||||
from cms.apphook_pool import apphook_pool
|
||||
from cms.models.pagemodel import Page
|
||||
from cms.utils import get_current_site
|
||||
from cms.utils.compat import DJANGO_1_11
|
||||
from cms.utils.compat.dj import RegexPattern, URLPattern, URLResolver
|
||||
from cms.utils.i18n import get_language_list
|
||||
from cms.utils.moderator import use_draft
|
||||
|
||||
|
||||
APP_RESOLVERS = []
|
||||
|
||||
@@ -50,6 +40,9 @@ def applications_page_check(request, current_page=None, path=None):
|
||||
for lang in get_language_list():
|
||||
if path.startswith(lang + "/"):
|
||||
path = path[len(lang + "/"):]
|
||||
|
||||
use_public = not use_draft(request)
|
||||
|
||||
for resolver in APP_RESOLVERS:
|
||||
try:
|
||||
page_id = resolver.resolve_page_id(path)
|
||||
@@ -58,14 +51,16 @@ def applications_page_check(request, current_page=None, path=None):
|
||||
# If current page was matched, then we have some override for
|
||||
# content from cms, but keep current page. Otherwise return page
|
||||
# to which was application assigned.
|
||||
return page
|
||||
return page if use_public else page.publisher_public
|
||||
except Resolver404:
|
||||
# Raised if the page is not managed by an apphook
|
||||
pass
|
||||
except Page.DoesNotExist:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
class AppRegexURLResolver(RegexURLResolver):
|
||||
class AppRegexURLResolver(URLResolver):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.page_id = None
|
||||
self.url_patterns_dict = {}
|
||||
@@ -85,7 +80,8 @@ class AppRegexURLResolver(RegexURLResolver):
|
||||
assigned.
|
||||
"""
|
||||
tried = []
|
||||
match = self.regex.search(path)
|
||||
pattern = getattr(self, 'pattern', self)
|
||||
match = pattern.regex.search(path)
|
||||
if match:
|
||||
new_path = path[match.end():]
|
||||
for pattern in self.url_patterns:
|
||||
@@ -105,61 +101,63 @@ class AppRegexURLResolver(RegexURLResolver):
|
||||
tried.extend([pattern])
|
||||
else:
|
||||
if sub_match:
|
||||
return pattern.page_id
|
||||
return getattr(pattern, 'page_id', None)
|
||||
pattern = getattr(pattern, 'pattern', pattern)
|
||||
tried.append(pattern.regex.pattern)
|
||||
raise Resolver404({'tried': tried, 'path': new_path})
|
||||
|
||||
|
||||
def recurse_patterns(path, pattern_list, page_id, default_args=None, nested=False):
|
||||
def recurse_patterns(path, pattern_list, page_id, default_args=None,
|
||||
nested=False):
|
||||
"""
|
||||
Recurse over a list of to-be-hooked patterns for a given path prefix
|
||||
"""
|
||||
newpatterns = []
|
||||
for pattern in pattern_list:
|
||||
app_pat = pattern.regex.pattern
|
||||
app_pat = getattr(pattern, 'pattern', pattern).regex.pattern
|
||||
# make sure we don't get patterns that start with more than one '^'!
|
||||
app_pat = app_pat.lstrip('^')
|
||||
path = path.lstrip('^')
|
||||
regex = r'^%s%s' % (path, app_pat) if not nested else r'^%s' % (app_pat)
|
||||
if isinstance(pattern, RegexURLResolver):
|
||||
if isinstance(pattern, URLResolver):
|
||||
# include default_args
|
||||
args = pattern.default_kwargs
|
||||
if default_args:
|
||||
args.update(default_args)
|
||||
if DJANGO_1_8:
|
||||
# this is an 'include', recurse!
|
||||
resolver = RegexURLResolver(regex, 'cms_appresolver',
|
||||
pattern.default_kwargs, pattern.app_name, pattern.namespace)
|
||||
# see lines 243 and 236 of urlresolvers.py to understand the next line
|
||||
resolver._urlconf_module = recurse_patterns(regex, pattern.url_patterns, page_id, args, nested=True)
|
||||
else:
|
||||
# see lines 243 and 236 of urlresolvers.py to understand the next line
|
||||
urlconf_module = recurse_patterns(regex, pattern.url_patterns, page_id, args, nested=True)
|
||||
# this is an 'include', recurse!
|
||||
resolver = RegexURLResolver(regex, urlconf_module,
|
||||
pattern.default_kwargs, pattern.app_name, pattern.namespace)
|
||||
resolver.page_id = page_id
|
||||
# see lines 243 and 236 of urlresolvers.py to understand the next line
|
||||
urlconf_module = recurse_patterns(regex, pattern.url_patterns, page_id, args, nested=True)
|
||||
# this is an 'include', recurse!
|
||||
regex_pattern = regex
|
||||
if not DJANGO_1_11:
|
||||
regex_pattern = RegexPattern(regex)
|
||||
resolver = URLResolver(regex_pattern, urlconf_module,
|
||||
pattern.default_kwargs, pattern.app_name,
|
||||
pattern.namespace)
|
||||
else:
|
||||
# Re-do the RegexURLPattern with the new regular expression
|
||||
# Re-do the URLPattern with the new regular expression
|
||||
args = pattern.default_args
|
||||
if default_args:
|
||||
args.update(default_args)
|
||||
resolver = RegexURLPattern(regex, pattern.callback,
|
||||
args, pattern.name)
|
||||
resolver.page_id = page_id
|
||||
|
||||
regex_pattern = regex
|
||||
if not DJANGO_1_11:
|
||||
regex_pattern = RegexPattern(regex, name=pattern.name)
|
||||
resolver = URLPattern(regex_pattern, pattern.callback, args,
|
||||
pattern.name)
|
||||
resolver.page_id = page_id
|
||||
newpatterns.append(resolver)
|
||||
return newpatterns
|
||||
|
||||
|
||||
def _set_permissions(patterns, exclude_permissions):
|
||||
for pattern in patterns:
|
||||
if isinstance(pattern, RegexURLResolver):
|
||||
if isinstance(pattern, URLResolver):
|
||||
if pattern.namespace in exclude_permissions:
|
||||
continue
|
||||
_set_permissions(pattern.url_patterns, exclude_permissions)
|
||||
else:
|
||||
from cms.utils.decorators import cms_perms
|
||||
pattern._callback = cms_perms(pattern.callback)
|
||||
pattern.callback = cms_perms(pattern.callback)
|
||||
|
||||
|
||||
def get_app_urls(urls):
|
||||
@@ -170,8 +168,10 @@ def get_app_urls(urls):
|
||||
raise ImproperlyConfigured(
|
||||
"URLConf `%s` has no urlpatterns attribute" % urlconf)
|
||||
yield getattr(mod, 'urlpatterns')
|
||||
else:
|
||||
elif isinstance(urlconf, (list, tuple)):
|
||||
yield urlconf
|
||||
else:
|
||||
yield [urlconf]
|
||||
|
||||
|
||||
def get_patterns_for_title(path, title):
|
||||
@@ -181,7 +181,7 @@ def get_patterns_for_title(path, title):
|
||||
"""
|
||||
app = apphook_pool.get_apphook(title.page.application_urls)
|
||||
url_patterns = []
|
||||
for pattern_list in get_app_urls(app.urls):
|
||||
for pattern_list in get_app_urls(app.get_urls(title.page, title.language)):
|
||||
if path and not path.endswith('/'):
|
||||
path += '/'
|
||||
page_id = title.page.id
|
||||
@@ -191,7 +191,8 @@ def get_patterns_for_title(path, title):
|
||||
|
||||
def get_app_patterns():
|
||||
try:
|
||||
return _get_app_patterns()
|
||||
site = get_current_site()
|
||||
return _get_app_patterns(site)
|
||||
except (OperationalError, ProgrammingError):
|
||||
# ignore if DB is not ready
|
||||
# Starting with Django 1.9 this code gets called even when creating
|
||||
@@ -199,62 +200,69 @@ def get_app_patterns():
|
||||
return []
|
||||
|
||||
|
||||
def _get_app_patterns():
|
||||
def _get_app_patterns(site):
|
||||
"""
|
||||
Get a list of patterns for all hooked apps.
|
||||
|
||||
How this works:
|
||||
|
||||
By looking through all titles with an app hook (application_urls) we find all
|
||||
urlconf modules we have to hook into titles.
|
||||
By looking through all titles with an app hook (application_urls) we find
|
||||
all urlconf modules we have to hook into titles.
|
||||
|
||||
If we use the ML URL Middleware, we namespace those patterns with the title
|
||||
language.
|
||||
|
||||
All 'normal' patterns from the urlconf get re-written by prefixing them with
|
||||
the title path and then included into the cms url patterns.
|
||||
|
||||
If the app is still configured, but is no longer installed/available, then
|
||||
this method returns a degenerate patterns object: patterns('')
|
||||
"""
|
||||
from cms.models import Title
|
||||
|
||||
try:
|
||||
current_site = Site.objects.get_current()
|
||||
except Site.DoesNotExist:
|
||||
current_site = None
|
||||
included = []
|
||||
|
||||
# we don't have a request here so get_page_queryset() can't be used,
|
||||
# so use public() queryset.
|
||||
# This can be done because url patterns are used just in frontend
|
||||
|
||||
title_qs = Title.objects.public().filter(page__site=current_site)
|
||||
title_qs = Title.objects.public().filter(page__node__site=site)
|
||||
|
||||
hooked_applications = OrderedDict()
|
||||
|
||||
# Loop over all titles with an application hooked to them
|
||||
titles = (title_qs.exclude(page__application_urls=None)
|
||||
.exclude(page__application_urls='')
|
||||
.order_by('-page__node__path').select_related())
|
||||
# TODO: Need to be fixed for django-treebeard when forward ported to 3.1
|
||||
for title in title_qs.exclude(page__application_urls=None).exclude(page__application_urls='').order_by('-page__path').select_related():
|
||||
for title in titles:
|
||||
path = title.path
|
||||
mix_id = "%s:%s:%s" % (path + "/", title.page.application_urls, title.language)
|
||||
mix_id = "%s:%s:%s" % (
|
||||
path + "/", title.page.application_urls, title.language)
|
||||
if mix_id in included:
|
||||
# don't add the same thing twice
|
||||
continue
|
||||
if not settings.APPEND_SLASH:
|
||||
path += '/'
|
||||
app = apphook_pool.get_apphook(title.page.application_urls)
|
||||
if not app:
|
||||
continue
|
||||
if title.page_id not in hooked_applications:
|
||||
hooked_applications[title.page_id] = {}
|
||||
app = apphook_pool.get_apphook(title.page.application_urls)
|
||||
app_ns = app.app_name, title.page.application_namespace
|
||||
with override(title.language):
|
||||
hooked_applications[title.page_id][title.language] = (app_ns, get_patterns_for_title(path, title), app)
|
||||
hooked_applications[title.page_id][title.language] = (
|
||||
app_ns, get_patterns_for_title(path, title), app)
|
||||
included.append(mix_id)
|
||||
# Build the app patterns to be included in the cms urlconfs
|
||||
app_patterns = []
|
||||
for page_id in hooked_applications.keys():
|
||||
resolver = None
|
||||
for lang in hooked_applications[page_id].keys():
|
||||
(app_ns, inst_ns), current_patterns, app = hooked_applications[page_id][lang]
|
||||
(app_ns, inst_ns), current_patterns, app = hooked_applications[page_id][lang] # nopyflakes
|
||||
if not resolver:
|
||||
resolver = AppRegexURLResolver(r'', 'app_resolver', app_name=app_ns, namespace=inst_ns)
|
||||
regex_pattern = RegexPattern(r'') if not DJANGO_1_11 else r''
|
||||
resolver = AppRegexURLResolver(
|
||||
regex_pattern, 'app_resolver', app_name=app_ns, namespace=inst_ns)
|
||||
resolver.page_id = page_id
|
||||
if app.permissions:
|
||||
_set_permissions(current_patterns, app.exclude_permissions)
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from cms.utils.setup import setup
|
||||
|
||||
|
||||
class CMSConfig(AppConfig):
|
||||
name = 'cms'
|
||||
verbose_name = _("django CMS")
|
||||
|
||||
def ready(self):
|
||||
from cms.utils.setup import setup
|
||||
|
||||
setup()
|
||||
|
||||
Vendored
+2
-2
@@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from cms.utils import get_cms_setting
|
||||
from cms.utils.conf import get_cms_setting
|
||||
|
||||
CMS_PAGE_CACHE_VERSION_KEY = get_cms_setting("CACHE_PREFIX") + 'CMS_PAGE_CACHE_VERSION'
|
||||
CMS_PAGE_CACHE_VERSION_KEY = get_cms_setting("CACHE_PREFIX") + '_PAGE_CACHE_VERSION'
|
||||
|
||||
|
||||
def _get_cache_version():
|
||||
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.conf import settings
|
||||
|
||||
from cms.utils import get_cms_setting
|
||||
from cms.utils.conf import get_cms_setting
|
||||
|
||||
|
||||
def _site_cache_key(lang):
|
||||
|
||||
Vendored
+70
-38
@@ -1,69 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import hashlib
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.cache import add_never_cache_headers
|
||||
from django.utils.encoding import iri_to_uri, force_text
|
||||
from django.utils.timezone import get_current_timezone_name
|
||||
from django.utils.cache import add_never_cache_headers, patch_response_headers, patch_vary_headers
|
||||
from django.utils.encoding import iri_to_uri
|
||||
from django.utils.timezone import now
|
||||
|
||||
from cms.cache import _get_cache_version, _set_cache_version, _get_cache_key
|
||||
from cms.utils import get_cms_setting
|
||||
from cms.constants import EXPIRE_NOW, MAX_EXPIRATION_TTL
|
||||
from cms.toolbar.utils import get_toolbar_from_request
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.helpers import get_timezone_name
|
||||
|
||||
|
||||
def _page_cache_key(request):
|
||||
#md5 key of current path
|
||||
#sha1 key of current path
|
||||
cache_key = "%s:%d:%s" % (
|
||||
get_cms_setting("CACHE_PREFIX"),
|
||||
settings.SITE_ID,
|
||||
hashlib.md5(iri_to_uri(request.get_full_path()).encode('utf-8')).hexdigest()
|
||||
hashlib.sha1(iri_to_uri(request.get_full_path()).encode('utf-8')).hexdigest()
|
||||
)
|
||||
if settings.USE_TZ:
|
||||
# The datetime module doesn't restrict the output of tzname().
|
||||
# Windows is known to use non-standard, locale-dependant names.
|
||||
# User-defined tzinfo classes may return absolutely anything.
|
||||
# Hence this paranoid conversion to create a valid cache key.
|
||||
tz_name = force_text(get_current_timezone_name(), errors='ignore')
|
||||
cache_key += '.%s' % tz_name.encode('ascii', 'ignore').decode('ascii').replace(' ', '_')
|
||||
cache_key += '.%s' % get_timezone_name()
|
||||
return cache_key
|
||||
|
||||
|
||||
def set_page_cache(response):
|
||||
from django.core.cache import cache
|
||||
|
||||
if not get_cms_setting('PAGE_CACHE'):
|
||||
return response
|
||||
request = response._request
|
||||
save_cache = True
|
||||
for placeholder in getattr(request, 'placeholders', []):
|
||||
if not placeholder.cache_placeholder:
|
||||
save_cache = False
|
||||
break
|
||||
if hasattr(request, 'toolbar'):
|
||||
if request.toolbar.edit_mode or request.toolbar.show_toolbar:
|
||||
save_cache = False
|
||||
if request.user.is_authenticated():
|
||||
save_cache = False
|
||||
if not save_cache:
|
||||
toolbar = get_toolbar_from_request(request)
|
||||
is_authenticated = request.user.is_authenticated
|
||||
|
||||
if is_authenticated or toolbar._cache_disabled or not get_cms_setting("PAGE_CACHE"):
|
||||
add_never_cache_headers(response)
|
||||
return response
|
||||
else:
|
||||
version = _get_cache_version()
|
||||
ttl = get_cms_setting('CACHE_DURATIONS')['content']
|
||||
|
||||
cache.set(
|
||||
_page_cache_key(request),
|
||||
(response.content, response._headers),
|
||||
ttl,
|
||||
version=version
|
||||
# This *must* be TZ-aware
|
||||
timestamp = now()
|
||||
|
||||
placeholders = toolbar.content_renderer.get_rendered_placeholders()
|
||||
# Checks if there's a plugin using the legacy "cache = False"
|
||||
placeholder_ttl_list = []
|
||||
vary_cache_on_set = set()
|
||||
for ph in placeholders:
|
||||
# get_cache_expiration() always returns:
|
||||
# EXPIRE_NOW <= int <= MAX_EXPIRATION_IN_SECONDS
|
||||
ttl = ph.get_cache_expiration(request, timestamp)
|
||||
vary_cache_on = ph.get_vary_cache_on(request)
|
||||
|
||||
placeholder_ttl_list.append(ttl)
|
||||
if ttl and vary_cache_on:
|
||||
# We're only interested in vary headers if they come from
|
||||
# a cache-able placeholder.
|
||||
vary_cache_on_set |= set(vary_cache_on)
|
||||
|
||||
if EXPIRE_NOW not in placeholder_ttl_list:
|
||||
if placeholder_ttl_list:
|
||||
min_placeholder_ttl = min(x for x in placeholder_ttl_list)
|
||||
else:
|
||||
# Should only happen when there are no placeholders at all
|
||||
min_placeholder_ttl = MAX_EXPIRATION_TTL
|
||||
ttl = min(
|
||||
get_cms_setting('CACHE_DURATIONS')['content'],
|
||||
min_placeholder_ttl
|
||||
)
|
||||
# See note in invalidate_cms_page_cache()
|
||||
_set_cache_version(version)
|
||||
|
||||
if ttl > 0:
|
||||
# Adds expiration, etc. to headers
|
||||
patch_response_headers(response, cache_timeout=ttl)
|
||||
patch_vary_headers(response, sorted(vary_cache_on_set))
|
||||
|
||||
version = _get_cache_version()
|
||||
# We also store the absolute expiration timestamp to avoid
|
||||
# recomputing it on cache-reads.
|
||||
expires_datetime = timestamp + timedelta(seconds=ttl)
|
||||
cache.set(
|
||||
_page_cache_key(request),
|
||||
(
|
||||
response.content,
|
||||
response._headers,
|
||||
expires_datetime,
|
||||
),
|
||||
ttl,
|
||||
version=version
|
||||
)
|
||||
# See note in invalidate_cms_page_cache()
|
||||
_set_cache_version(version)
|
||||
return response
|
||||
|
||||
|
||||
def get_page_cache(request):
|
||||
from django.core.cache import cache
|
||||
|
||||
return cache.get(_page_cache_key(request),
|
||||
version=_get_cache_version())
|
||||
return cache.get(_page_cache_key(request), version=_get_cache_version())
|
||||
|
||||
|
||||
def get_xframe_cache(page):
|
||||
|
||||
Vendored
+5
-5
@@ -1,14 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from cms.utils import get_cms_setting
|
||||
from cms.utils.conf import get_cms_setting
|
||||
|
||||
|
||||
PERMISSION_KEYS = [
|
||||
'can_change', 'can_add', 'can_delete',
|
||||
'can_change_advanced_settings', 'can_publish',
|
||||
'can_change_permissions', 'can_move_page',
|
||||
'can_moderate', 'can_view']
|
||||
'add_page', 'change_page', 'change_page_advanced_settings',
|
||||
'change_page_permissions', 'delete_page', 'move_page',
|
||||
'publish_page', 'view_page',
|
||||
]
|
||||
|
||||
|
||||
def get_cache_key(user, key):
|
||||
|
||||
Vendored
+162
-47
@@ -1,63 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.timezone import get_current_timezone_name
|
||||
from cms.cache import _get_cache_version, _set_cache_version, _clean_key, _get_cache_key
|
||||
from cms.utils import get_cms_setting
|
||||
|
||||
"""
|
||||
This module manages placeholder caching. We use a cache-versioning strategy
|
||||
in which each (placeholder x lang x site_id) manages its own version. The
|
||||
actual cache includes additional keys appropriate for the placeholders
|
||||
get_vary_cache_on().
|
||||
|
||||
Invalidation of a placeholder's cache simply increments the version number for
|
||||
the (placeholder x lang) pair, which renders any cache entries for that
|
||||
placeholder under that version inaccessible. Those cache entries will simply
|
||||
expire and will be purged according to the policy of the cache backend in-use.
|
||||
|
||||
The cache entries themselves may include additional sub-keys, according to the
|
||||
list of VARY header-names as returned by placeholder.get_vary_cache_on() and
|
||||
the current HTTPRequest object.
|
||||
|
||||
The vary-on header-names are also stored with the version. This enables us to
|
||||
check for cache hits without re-computing placeholder.get_vary_cache_on().
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
from django.utils.timezone import now
|
||||
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.helpers import get_header_name, get_timezone_name
|
||||
|
||||
|
||||
def _placeholder_cache_key(placeholder, lang):
|
||||
cache_key = '%srender_placeholder:%s.%s' % (get_cms_setting("CACHE_PREFIX"), placeholder.pk, str(lang))
|
||||
if settings.USE_TZ:
|
||||
tz_name = force_text(get_current_timezone_name(), errors='ignore')
|
||||
cache_key += '.%s' % tz_name.encode('ascii', 'ignore').decode('ascii').replace(' ', '_')
|
||||
def _get_placeholder_cache_version_key(placeholder, lang, site_id):
|
||||
"""
|
||||
Returns the version key for the given «placeholder», «lang» and «site_id».
|
||||
|
||||
Invalidating this (via clear_placeholder_cache by replacing the stored
|
||||
value with a new value) will effectively make all "sub-caches" relating to
|
||||
this (placeholder x lang) inaccessible. Sub-caches include caches per TZ
|
||||
and per VARY header.
|
||||
"""
|
||||
prefix = get_cms_setting('CACHE_PREFIX')
|
||||
key = '{prefix}|placeholder_cache_version|id:{id}|lang:{lang}|site:{site}'.format(
|
||||
prefix=prefix,
|
||||
id=placeholder.pk,
|
||||
lang=str(lang),
|
||||
site=site_id,
|
||||
)
|
||||
if len(key) > 250:
|
||||
key = '{prefix}|{hash}'.format(
|
||||
prefix=prefix,
|
||||
hash=hashlib.sha1(key.encode('utf-8')).hexdigest(),
|
||||
)
|
||||
return key
|
||||
|
||||
|
||||
def _get_placeholder_cache_version(placeholder, lang, site_id):
|
||||
"""
|
||||
Gets the (placeholder x lang)'s current version and vary-on header-names
|
||||
list, if present, otherwise resets to («timestamp», []).
|
||||
"""
|
||||
from django.core.cache import cache
|
||||
|
||||
key = _get_placeholder_cache_version_key(placeholder, lang, site_id)
|
||||
cached = cache.get(key)
|
||||
if cached:
|
||||
version, vary_on_list = cached
|
||||
else:
|
||||
version = int(time.time() * 1000000)
|
||||
vary_on_list = []
|
||||
_set_placeholder_cache_version(placeholder, lang, site_id, version, vary_on_list)
|
||||
return version, vary_on_list
|
||||
|
||||
|
||||
def _set_placeholder_cache_version(placeholder, lang, site_id, version, vary_on_list=None, duration=None):
|
||||
"""
|
||||
Sets the (placeholder x lang)'s version and vary-on header-names list.
|
||||
"""
|
||||
from django.core.cache import cache
|
||||
|
||||
key = _get_placeholder_cache_version_key(placeholder, lang, site_id)
|
||||
|
||||
if not version or version < 1:
|
||||
version = int(time.time() * 1000000)
|
||||
|
||||
if vary_on_list is None:
|
||||
vary_on_list = []
|
||||
|
||||
cache.set(key, (version, vary_on_list), duration)
|
||||
|
||||
|
||||
def _get_placeholder_cache_key(placeholder, lang, site_id, request, soft=False):
|
||||
"""
|
||||
Returns the fully-addressed cache key for the given placeholder and
|
||||
the request.
|
||||
|
||||
The kwarg «soft» should be set to True if getting the cache key to then
|
||||
read from the cache. If instead the key retrieval is to support a cache
|
||||
write, let «soft» be False.
|
||||
"""
|
||||
prefix = get_cms_setting('CACHE_PREFIX')
|
||||
version, vary_on_list = _get_placeholder_cache_version(placeholder, lang, site_id)
|
||||
main_key = '{prefix}|render_placeholder|id:{id}|lang:{lang}|site:{site}|tz:{tz}|v:{version}'.format(
|
||||
prefix=prefix,
|
||||
id=placeholder.pk,
|
||||
lang=lang,
|
||||
site=site_id,
|
||||
tz=get_timezone_name(),
|
||||
version=version,
|
||||
)
|
||||
|
||||
if not soft:
|
||||
# We are about to write to the cache, so we want to get the latest
|
||||
# vary_cache_on headers and the correct cache expiration, ignoring any
|
||||
# we already have. If the placeholder has already been rendered, this
|
||||
# will be very efficient (zero-additional queries) due to the caching
|
||||
# of all its plugins during the rendering process anyway.
|
||||
vary_on_list = placeholder.get_vary_cache_on(request)
|
||||
duration = placeholder.get_cache_expiration(request, now())
|
||||
# Update the main placeholder cache version
|
||||
_set_placeholder_cache_version(
|
||||
placeholder, lang, site_id, version, vary_on_list, duration)
|
||||
|
||||
sub_key_list = []
|
||||
for key in vary_on_list:
|
||||
value = request.META.get(get_header_name(key)) or '_'
|
||||
sub_key_list.append(key + ':' + value)
|
||||
|
||||
cache_key = main_key
|
||||
if sub_key_list:
|
||||
cache_key += '|' + '|'.join(sub_key_list)
|
||||
|
||||
if len(cache_key) > 250:
|
||||
cache_key = '{prefix}|{hash}'.format(
|
||||
prefix=prefix,
|
||||
hash=hashlib.sha1(cache_key.encode('utf-8')).hexdigest(),
|
||||
)
|
||||
|
||||
return cache_key
|
||||
|
||||
|
||||
def set_placeholder_cache(placeholder, lang, content):
|
||||
def set_placeholder_cache(placeholder, lang, site_id, content, request):
|
||||
"""
|
||||
Caches the rendering of a placeholder
|
||||
"""
|
||||
from django.core.cache import cache
|
||||
cache.set(_placeholder_cache_key(placeholder, lang),
|
||||
content,
|
||||
get_cms_setting('CACHE_DURATIONS')['content'],
|
||||
version=_get_cache_version())
|
||||
_set_cache_version(_get_cache_version())
|
||||
|
||||
|
||||
def get_placeholder_cache(placeholder, lang):
|
||||
"""
|
||||
Retrieves the cached content of a placeholder
|
||||
Sets the (correct) placeholder cache with the rendered placeholder.
|
||||
"""
|
||||
from django.core.cache import cache
|
||||
|
||||
return cache.get(_placeholder_cache_key(placeholder, lang),
|
||||
version=_get_cache_version())
|
||||
key = _get_placeholder_cache_key(placeholder, lang, site_id, request)
|
||||
|
||||
duration = min(
|
||||
get_cms_setting('CACHE_DURATIONS')['content'],
|
||||
placeholder.get_cache_expiration(request, now())
|
||||
)
|
||||
cache.set(key, content, duration)
|
||||
# "touch" the cache-version, so that it stays as fresh as this content.
|
||||
version, vary_on_list = _get_placeholder_cache_version(placeholder, lang, site_id)
|
||||
_set_placeholder_cache_version(
|
||||
placeholder, lang, site_id, version, vary_on_list, duration=duration)
|
||||
|
||||
|
||||
def clear_placeholder_cache(placeholder, lang):
|
||||
def get_placeholder_cache(placeholder, lang, site_id, request):
|
||||
"""
|
||||
Returns the placeholder from cache respecting the placeholder's
|
||||
VARY headers.
|
||||
"""
|
||||
from django.core.cache import cache
|
||||
|
||||
cache.delete(_placeholder_cache_key(placeholder, lang), version=_get_cache_version())
|
||||
key = _get_placeholder_cache_key(placeholder, lang, site_id, request, soft=True)
|
||||
content = cache.get(key)
|
||||
return content
|
||||
|
||||
|
||||
def _placeholder_page_cache_key(page_lookup, lang, site_id, placeholder_name):
|
||||
base_key = _get_cache_key('_show_placeholder_for_page', page_lookup, lang, site_id)
|
||||
return _clean_key('%s_placeholder:%s' % (base_key, placeholder_name))
|
||||
|
||||
|
||||
def get_placeholder_page_cache(page_lookup, lang, site_id, placeholder_name):
|
||||
from django.core.cache import cache
|
||||
|
||||
return cache.get(_placeholder_page_cache_key(page_lookup, lang, site_id, placeholder_name),
|
||||
version=_get_cache_version())
|
||||
|
||||
|
||||
def set_placeholder_page_cache(page_lookup, lang, site_id, placeholder_name, content):
|
||||
from django.core.cache import cache
|
||||
|
||||
cache.set(_placeholder_page_cache_key(page_lookup, lang, site_id, placeholder_name),
|
||||
content,
|
||||
get_cms_setting('CACHE_DURATIONS')['content'], version=_get_cache_version())
|
||||
_set_cache_version(_get_cache_version())
|
||||
def clear_placeholder_cache(placeholder, lang, site_id):
|
||||
"""
|
||||
Invalidates all existing cache entries for (placeholder x lang x site_id).
|
||||
We don't need to re-store the vary_on_list, because the cache is now
|
||||
effectively empty.
|
||||
"""
|
||||
version = int(time.time() * 1000000)
|
||||
_set_placeholder_cache_version(placeholder, lang, site_id, version, [])
|
||||
|
||||
+224
-160
@@ -1,110 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.utils.translation import get_language
|
||||
from django.db.models.query import Prefetch, prefetch_related_objects
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
from django.utils.translation import override as force_language
|
||||
|
||||
from cms import constants
|
||||
from cms.api import get_page_draft
|
||||
from cms.apphook_pool import apphook_pool
|
||||
from cms.utils.permissions import load_view_restrictions, has_global_page_permission
|
||||
from cms.utils import get_language_from_request
|
||||
from cms.models import EmptyTitle
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.helpers import current_site
|
||||
from cms.utils.i18n import get_fallback_languages, hide_untranslated
|
||||
from cms.utils.page_resolver import get_page_queryset
|
||||
from cms.utils.moderator import get_title_queryset, use_draft
|
||||
from cms.utils.i18n import (
|
||||
get_fallback_languages,
|
||||
get_public_languages,
|
||||
hide_untranslated,
|
||||
is_valid_site_language,
|
||||
)
|
||||
from cms.utils.permissions import get_view_restrictions
|
||||
from cms.utils.page import get_page_queryset
|
||||
from cms.utils.page_permissions import user_can_view_all_pages
|
||||
|
||||
from menus.base import Menu, NavigationNode, Modifier
|
||||
from menus.menu_pool import menu_pool
|
||||
|
||||
|
||||
def get_visible_page_objects(request, pages, site=None):
|
||||
def get_visible_nodes(request, pages, site):
|
||||
"""
|
||||
This code is basically a many-pages-at-once version of
|
||||
Page.has_view_permission.
|
||||
cms.utils.page_permissions.user_can_view_page
|
||||
pages contains all published pages
|
||||
check if there is ANY restriction
|
||||
that needs a permission page visibility calculation
|
||||
"""
|
||||
user = request.user
|
||||
_get_page_draft = get_page_draft
|
||||
public_for = get_cms_setting('PUBLIC_FOR')
|
||||
can_see_unrestricted = public_for == 'all' or (
|
||||
public_for == 'staff' and request.user.is_staff)
|
||||
is_auth_user = request.user.is_authenticated()
|
||||
can_see_unrestricted = public_for == 'all' or (public_for == 'staff' and user.is_staff)
|
||||
|
||||
if not user.is_authenticated and not can_see_unrestricted:
|
||||
# User is not authenticated and can't see unrestricted pages,
|
||||
# no need to check for page restrictions because if there's some,
|
||||
# user is anon and if there is not any, user can't see unrestricted.
|
||||
return []
|
||||
|
||||
if user_can_view_all_pages(user, site):
|
||||
return list(pages)
|
||||
|
||||
# Permissions are only attached to draft pages
|
||||
draft_pages = [_get_page_draft(page) for page in pages]
|
||||
restricted_pages = get_view_restrictions(draft_pages)
|
||||
|
||||
restricted_pages = load_view_restrictions(request, pages)
|
||||
if not restricted_pages:
|
||||
if can_see_unrestricted:
|
||||
return pages
|
||||
elif not is_auth_user:
|
||||
return [] # Unauth user can't acquire global or user perm to see pages
|
||||
# If there's no restrictions, let the user see all pages
|
||||
# only if he can see unrestricted, otherwise return no pages.
|
||||
return list(pages) if can_see_unrestricted else []
|
||||
|
||||
if get_cms_setting('PERMISSION') and not site:
|
||||
site = current_site(request) # avoid one extra query when possible
|
||||
if has_global_page_permission(request, site, can_view=True):
|
||||
return pages
|
||||
user_id = user.pk
|
||||
user_groups = SimpleLazyObject(lambda: frozenset(user.groups.values_list('pk', flat=True)))
|
||||
is_auth_user = user.is_authenticated
|
||||
|
||||
def has_global_perm():
|
||||
if has_global_perm.cache < 0:
|
||||
if request.user.has_perm('cms.view_page'):
|
||||
has_global_perm.cache = 1
|
||||
else:
|
||||
has_global_perm.cache = 0
|
||||
return bool(has_global_perm.cache)
|
||||
has_global_perm.cache = -1
|
||||
def user_can_see_page(page):
|
||||
page_id = page.pk if page.publisher_is_draft else page.publisher_public_id
|
||||
page_permissions = restricted_pages.get(page_id, [])
|
||||
|
||||
def has_permission_membership(page_id):
|
||||
"""
|
||||
PagePermission user group membership tests
|
||||
"""
|
||||
user_pk = request.user.pk
|
||||
for perm in restricted_pages[page_id]:
|
||||
if perm.user_id == user_pk:
|
||||
return True
|
||||
if not perm.group_id:
|
||||
continue
|
||||
if has_permission_membership.user_groups is None:
|
||||
has_permission_membership.user_groups = request.user.groups.all().values_list(
|
||||
'pk', flat=True)
|
||||
if perm.group_id in has_permission_membership.user_groups:
|
||||
if not page_permissions:
|
||||
# Page has no view restrictions, fallback to the project's
|
||||
# CMS_PUBLIC_FOR setting.
|
||||
return can_see_unrestricted
|
||||
|
||||
if not is_auth_user:
|
||||
return False
|
||||
|
||||
for perm in page_permissions:
|
||||
if perm.user_id == user_id or perm.group_id in user_groups:
|
||||
return True
|
||||
return False
|
||||
has_permission_membership.user_groups = None
|
||||
|
||||
visible_pages = []
|
||||
for page in pages:
|
||||
to_add = False
|
||||
page_id = page.pk
|
||||
is_restricted = page_id in restricted_pages
|
||||
# restricted_pages contains as key any page.pk that is
|
||||
# affected by a permission grant_on
|
||||
if not is_restricted and can_see_unrestricted:
|
||||
to_add = True
|
||||
elif is_auth_user:
|
||||
# setting based handling of unrestricted pages
|
||||
# check group and user memberships to restricted pages
|
||||
if is_restricted and has_permission_membership(page_id):
|
||||
to_add = True
|
||||
elif has_global_perm():
|
||||
to_add = True
|
||||
if to_add:
|
||||
visible_pages.append(page)
|
||||
|
||||
return visible_pages
|
||||
return [page for page in pages if user_can_see_page(page)]
|
||||
|
||||
|
||||
def get_visible_pages(request, pages, site=None):
|
||||
"""Returns the IDs of all visible pages"""
|
||||
pages = get_visible_page_objects(request, pages, site)
|
||||
return [page.pk for page in pages]
|
||||
|
||||
|
||||
def page_to_node(page, home, cut):
|
||||
def get_menu_node_for_page(renderer, page, language, fallbacks=None):
|
||||
"""
|
||||
Transform a CMS page into a navigation node.
|
||||
|
||||
:param renderer: MenuRenderer instance bound to the request
|
||||
:param page: the page you wish to transform
|
||||
:param home: a reference to the "home" page (the page with path="0001")
|
||||
:param cut: Should we cut page from its parent pages? This means the node will not
|
||||
have a parent anymore.
|
||||
:param language: The current language used to render the menu
|
||||
"""
|
||||
if fallbacks is None:
|
||||
fallbacks = []
|
||||
|
||||
# Theses are simple to port over, since they are not calculated.
|
||||
# Other attributes will be added conditionnally later.
|
||||
# Other attributes will be added conditionally later.
|
||||
attr = {
|
||||
'is_page': True,
|
||||
'soft_root': page.soft_root,
|
||||
@@ -112,15 +95,6 @@ def page_to_node(page, home, cut):
|
||||
'reverse_id': page.reverse_id,
|
||||
}
|
||||
|
||||
parent_id = page.parent_id
|
||||
# Should we cut the Node from its parents?
|
||||
if home and page.parent_id == home.pk and cut:
|
||||
parent_id = None
|
||||
|
||||
# possible fix for a possible problem
|
||||
# if parent_id and not page.parent.get_calculated_status():
|
||||
# parent_id = None # ????
|
||||
|
||||
if page.limit_visibility_in_menu is constants.VISIBILITY_ALL:
|
||||
attr['visible_for_authenticated'] = True
|
||||
attr['visible_for_anonymous'] = True
|
||||
@@ -131,20 +105,20 @@ def page_to_node(page, home, cut):
|
||||
# Extenders can be either navigation extenders or from apphooks.
|
||||
extenders = []
|
||||
if page.navigation_extenders:
|
||||
if page.navigation_extenders in menu_pool.menus:
|
||||
if page.navigation_extenders in renderer.menus:
|
||||
extenders.append(page.navigation_extenders)
|
||||
elif "{0}:{1}".format(page.navigation_extenders, page.pk) in menu_pool.menus:
|
||||
elif "{0}:{1}".format(page.navigation_extenders, page.pk) in renderer.menus:
|
||||
extenders.append("{0}:{1}".format(page.navigation_extenders, page.pk))
|
||||
# Is this page an apphook? If so, we need to handle the apphooks's nodes
|
||||
lang = get_language()
|
||||
# Only run this if we have a translation in the requested language for this
|
||||
# object. The title cache should have been prepopulated in CMSMenu.get_nodes
|
||||
# but otherwise, just request the title normally
|
||||
if not hasattr(page, 'title_cache') or lang in page.title_cache:
|
||||
app_name = page.get_application_urls(fallback=False)
|
||||
if app_name: # it means it is an apphook
|
||||
app = apphook_pool.get_apphook(app_name)
|
||||
extenders += app.menus
|
||||
if page.title_cache.get(language) and page.application_urls:
|
||||
# it means it is an apphook
|
||||
app = apphook_pool.get_apphook(page.application_urls)
|
||||
|
||||
if app:
|
||||
extenders += app.get_menus(page, language)
|
||||
exts = []
|
||||
for ext in extenders:
|
||||
if hasattr(ext, "get_instances"):
|
||||
@@ -158,88 +132,177 @@ def page_to_node(page, home, cut):
|
||||
if exts:
|
||||
attr['navigation_extenders'] = exts
|
||||
|
||||
# Do we have a redirectURL?
|
||||
attr['redirect_url'] = page.get_redirect() # save redirect URL if any
|
||||
for lang in [language] + fallbacks:
|
||||
translation = page.title_cache[lang]
|
||||
|
||||
# Now finally, build the NavigationNode object and return it.
|
||||
ret_node = NavigationNode(
|
||||
page.get_menu_title(),
|
||||
page.get_absolute_url(),
|
||||
page.pk,
|
||||
parent_id,
|
||||
attr=attr,
|
||||
visible=page.in_navigation,
|
||||
)
|
||||
return ret_node
|
||||
if translation:
|
||||
# Do we have a redirectURL?
|
||||
attr['redirect_url'] = translation.redirect # save redirect URL if any
|
||||
|
||||
# Now finally, build the NavigationNode object and return it.
|
||||
# The parent_id is manually set by the menu get_nodes method.
|
||||
ret_node = CMSNavigationNode(
|
||||
title=translation.menu_title or translation.title,
|
||||
url='',
|
||||
id=page.pk,
|
||||
attr=attr,
|
||||
visible=page.in_navigation,
|
||||
path=translation.path or translation.slug,
|
||||
language=(translation.language if translation.language != language else None),
|
||||
)
|
||||
return ret_node
|
||||
else:
|
||||
raise RuntimeError('Unable to render cms menu. There is a language misconfiguration.')
|
||||
|
||||
|
||||
class CMSNavigationNode(NavigationNode):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.path = kwargs.pop('path')
|
||||
# language is only used when we're dealing with a fallback
|
||||
self.language = kwargs.pop('language', None)
|
||||
super(CMSNavigationNode, self).__init__(*args, **kwargs)
|
||||
|
||||
def is_selected(self, request):
|
||||
try:
|
||||
page_id = request.current_page.pk
|
||||
except AttributeError:
|
||||
return False
|
||||
return page_id == self.id
|
||||
|
||||
def _get_absolute_url(self):
|
||||
if self.attr['is_home']:
|
||||
return reverse('pages-root')
|
||||
return reverse('pages-details-by-slug', kwargs={"slug": self.path})
|
||||
|
||||
def get_absolute_url(self):
|
||||
if self.language:
|
||||
with force_language(self.language):
|
||||
return self._get_absolute_url()
|
||||
return self._get_absolute_url()
|
||||
|
||||
|
||||
class CMSMenu(Menu):
|
||||
|
||||
def get_nodes(self, request):
|
||||
page_queryset = get_page_queryset(request)
|
||||
site = current_site(request)
|
||||
lang = get_language_from_request(request)
|
||||
from cms.models import Title
|
||||
|
||||
filters = {
|
||||
'site': site,
|
||||
}
|
||||
if hide_untranslated(lang, site.pk):
|
||||
filters['title_set__language'] = lang
|
||||
if not use_draft(request):
|
||||
filters['title_set__published'] = True
|
||||
site = self.renderer.site
|
||||
lang = self.renderer.request_language
|
||||
pages = get_page_queryset(
|
||||
site,
|
||||
draft=self.renderer.draft_mode_active,
|
||||
published=not self.renderer.draft_mode_active,
|
||||
)
|
||||
|
||||
if not use_draft(request):
|
||||
page_queryset = page_queryset.published()
|
||||
pages = page_queryset.filter(**filters).order_by("path")
|
||||
ids = {}
|
||||
nodes = []
|
||||
first = True
|
||||
home_cut = False
|
||||
home_children = []
|
||||
home = None
|
||||
actual_pages = []
|
||||
if is_valid_site_language(lang, site_id=site.pk):
|
||||
_valid_language = True
|
||||
_hide_untranslated = hide_untranslated(lang, site.pk)
|
||||
else:
|
||||
_valid_language = False
|
||||
_hide_untranslated = False
|
||||
|
||||
if _valid_language:
|
||||
# The request language has been explicitly configured
|
||||
# for the current site.
|
||||
if _hide_untranslated:
|
||||
fallbacks = []
|
||||
else:
|
||||
fallbacks = get_fallback_languages(lang, site_id=site.pk)
|
||||
languages = [lang] + [_lang for _lang in fallbacks if _lang != lang]
|
||||
else:
|
||||
# The request language is not configured for the current site.
|
||||
# Fallback to all configured public languages for the current site.
|
||||
languages = get_public_languages(site.pk)
|
||||
fallbacks = languages
|
||||
|
||||
pages = (
|
||||
pages
|
||||
.filter(title_set__language__in=languages)
|
||||
.select_related('node')
|
||||
.order_by('node__path')
|
||||
.distinct()
|
||||
)
|
||||
|
||||
if not self.renderer.draft_mode_active:
|
||||
# we're dealing with public pages.
|
||||
# prefetch the draft versions.
|
||||
pages = pages.select_related('publisher_public__node')
|
||||
pages = get_visible_nodes(request, pages, site)
|
||||
|
||||
if not pages:
|
||||
return []
|
||||
|
||||
try:
|
||||
homepage = [page for page in pages if page.is_home][0]
|
||||
except IndexError:
|
||||
homepage = None
|
||||
|
||||
titles = Title.objects.filter(
|
||||
language__in=languages,
|
||||
publisher_is_draft=self.renderer.draft_mode_active,
|
||||
)
|
||||
|
||||
lookup = Prefetch(
|
||||
'title_set',
|
||||
to_attr='filtered_translations',
|
||||
queryset=titles,
|
||||
)
|
||||
prefetch_related_objects(pages, lookup)
|
||||
# Build the blank title instances only once
|
||||
blank_title_cache = {language: EmptyTitle(language=language) for language in languages}
|
||||
|
||||
if lang not in blank_title_cache:
|
||||
blank_title_cache[lang] = EmptyTitle(language=lang)
|
||||
|
||||
# Maps a node id to its page id
|
||||
node_id_to_page = {}
|
||||
|
||||
def _page_to_node(page):
|
||||
# EmptyTitle is used to prevent the cms from trying
|
||||
# to find a translation in the database
|
||||
page.title_cache = blank_title_cache.copy()
|
||||
|
||||
for trans in page.filtered_translations:
|
||||
page.title_cache[trans.language] = trans
|
||||
menu_node = get_menu_node_for_page(
|
||||
self.renderer,
|
||||
page,
|
||||
language=lang,
|
||||
fallbacks=fallbacks,
|
||||
)
|
||||
return menu_node
|
||||
|
||||
menu_nodes = []
|
||||
|
||||
# cache view perms
|
||||
visible_pages = get_visible_pages(request, pages, site)
|
||||
for page in pages:
|
||||
# Pages are ordered by path, therefore the first page is the root
|
||||
# of the page tree (a.k.a "home")
|
||||
if page.pk not in visible_pages:
|
||||
# Don't include pages the user doesn't have access to
|
||||
node = page.node
|
||||
parent_id = node_id_to_page.get(node.parent_id)
|
||||
|
||||
if node.parent_id and not parent_id:
|
||||
# If the parent page is not available (unpublished, etc..)
|
||||
# don't bother creating menu nodes for its descendants.
|
||||
continue
|
||||
if not home:
|
||||
home = page
|
||||
if first and page.pk != home.pk:
|
||||
home_cut = True
|
||||
if (home_cut and (page.parent_id == home.pk or
|
||||
page.parent_id in home_children)):
|
||||
home_children.append(page.pk)
|
||||
if ((page.pk == home.pk and home.in_navigation)
|
||||
or page.pk != home.pk):
|
||||
first = False
|
||||
ids[page.id] = page
|
||||
actual_pages.append(page)
|
||||
page.title_cache = {}
|
||||
|
||||
langs = [lang]
|
||||
if not hide_untranslated(lang):
|
||||
langs.extend(get_fallback_languages(lang))
|
||||
menu_node = _page_to_node(page)
|
||||
cut_homepage = homepage and not homepage.in_navigation
|
||||
|
||||
titles = list(get_title_queryset(request).filter(
|
||||
page__in=ids, language__in=langs))
|
||||
for title in titles: # add the title and slugs and some meta data
|
||||
page = ids[title.page_id]
|
||||
page.title_cache[title.language] = title
|
||||
|
||||
for page in actual_pages:
|
||||
if page.title_cache:
|
||||
nodes.append(page_to_node(page, home, home_cut))
|
||||
return nodes
|
||||
if cut_homepage and parent_id == homepage.pk:
|
||||
# When the homepage is hidden from navigation,
|
||||
# we need to cut all its direct children from it.
|
||||
menu_node.parent_id = None
|
||||
else:
|
||||
menu_node.parent_id = parent_id
|
||||
node_id_to_page[node.pk] = page.pk
|
||||
menu_nodes.append(menu_node)
|
||||
return menu_nodes
|
||||
|
||||
|
||||
menu_pool.register_menu(CMSMenu)
|
||||
|
||||
|
||||
class NavExtender(Modifier):
|
||||
|
||||
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
|
||||
if post_cut:
|
||||
return nodes
|
||||
@@ -268,8 +331,9 @@ class NavExtender(Modifier):
|
||||
extnode.parent = node
|
||||
node.children.append(extnode)
|
||||
removed = []
|
||||
|
||||
# find all not assigned nodes
|
||||
for menu in menu_pool.menus.items():
|
||||
for menu in self.renderer.menus.items():
|
||||
if (hasattr(menu[1], 'cms_enabled')
|
||||
and menu[1].cms_enabled and not menu[0] in exts):
|
||||
for node in nodes:
|
||||
|
||||
+48
-22
@@ -4,18 +4,16 @@ from cms.models.aliaspluginmodel import AliasPluginModel
|
||||
from cms.models.placeholderpluginmodel import PlaceholderReference
|
||||
from cms.plugin_base import CMSPluginBase, PluginMenuItem
|
||||
from cms.plugin_pool import plugin_pool
|
||||
from cms.plugin_rendering import render_placeholder
|
||||
from cms.utils.urlutils import admin_reverse
|
||||
from django.conf.urls import url
|
||||
from django.http import HttpResponseForbidden, HttpResponseBadRequest, HttpResponse
|
||||
from django.middleware.csrf import get_token
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _, get_language
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _, get_language
|
||||
|
||||
|
||||
class PlaceholderPlugin(CMSPluginBase):
|
||||
name = _("Placeholder")
|
||||
parent_classes = [0] # so you will not be able to add it something
|
||||
parent_classes = ['0'] # so you will not be able to add it something
|
||||
#require_parent = True
|
||||
render_plugin = False
|
||||
admin_preview = False
|
||||
@@ -34,23 +32,13 @@ class AliasPlugin(CMSPluginBase):
|
||||
render_template = "cms/plugins/alias.html"
|
||||
system = True
|
||||
|
||||
def render(self, context, instance, placeholder):
|
||||
from cms.utils.plugins import downcast_plugins, build_plugin_tree
|
||||
context['instance'] = instance
|
||||
context['placeholder'] = placeholder
|
||||
if instance.plugin_id:
|
||||
plugins = instance.plugin.get_descendants().order_by('placeholder', 'path')
|
||||
plugins = [instance.plugin] + list(plugins)
|
||||
plugins = downcast_plugins(plugins)
|
||||
plugins[0].parent_id = None
|
||||
plugins = build_plugin_tree(plugins)
|
||||
context['plugins'] = plugins
|
||||
if instance.alias_placeholder_id:
|
||||
content = render_placeholder(instance.alias_placeholder, context)
|
||||
context['content'] = mark_safe(content)
|
||||
return context
|
||||
@classmethod
|
||||
def get_render_queryset(cls):
|
||||
queryset = super(AliasPlugin, cls).get_render_queryset()
|
||||
return queryset.select_related('plugin', 'alias_placeholder')
|
||||
|
||||
def get_extra_global_plugin_menu_items(self, request, plugin):
|
||||
@classmethod
|
||||
def get_extra_plugin_menu_items(cls, request, plugin):
|
||||
return [
|
||||
PluginMenuItem(
|
||||
_("Create Alias"),
|
||||
@@ -59,7 +47,8 @@ class AliasPlugin(CMSPluginBase):
|
||||
)
|
||||
]
|
||||
|
||||
def get_extra_placeholder_menu_items(self, request, placeholder):
|
||||
@classmethod
|
||||
def get_extra_placeholder_menu_items(cls, request, placeholder):
|
||||
return [
|
||||
PluginMenuItem(
|
||||
_("Create Alias"),
|
||||
@@ -73,6 +62,43 @@ class AliasPlugin(CMSPluginBase):
|
||||
url(r'^create_alias/$', self.create_alias, name='cms_create_alias'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_empty_change_form_text(cls, obj=None):
|
||||
original = super(AliasPlugin, cls).get_empty_change_form_text(obj=obj)
|
||||
|
||||
if not obj:
|
||||
return original
|
||||
|
||||
instance = obj.get_plugin_instance()[0]
|
||||
|
||||
if not instance:
|
||||
# Ghost plugin
|
||||
return original
|
||||
|
||||
aliased_placeholder_id = instance.get_aliased_placeholder_id()
|
||||
|
||||
if not aliased_placeholder_id:
|
||||
# Corrupt (sadly) Alias plugin
|
||||
return original
|
||||
|
||||
aliased_placeholder = Placeholder.objects.get(pk=aliased_placeholder_id)
|
||||
|
||||
origin_page = aliased_placeholder.page
|
||||
|
||||
if not origin_page:
|
||||
# Placeholder is not attached to a page
|
||||
return original
|
||||
|
||||
# I have a feeling this could fail with a NoReverseMatch error
|
||||
# if this is the case, then it's likely a corruption.
|
||||
page_url = origin_page.get_absolute_url(language=obj.language)
|
||||
page_title = origin_page.get_title(language=obj.language)
|
||||
|
||||
message = ugettext('This is an alias reference, '
|
||||
'you can edit the content only on the '
|
||||
'<a href="%(page_url)s?edit" target="_parent">%(page_title)s</a> page.')
|
||||
return message % {'page_url': page_url, 'page_title': page_title}
|
||||
|
||||
def create_alias(self, request):
|
||||
if not request.user.is_staff:
|
||||
return HttpResponseForbidden("not enough privileges")
|
||||
@@ -92,7 +118,7 @@ class AliasPlugin(CMSPluginBase):
|
||||
placeholder = Placeholder.objects.get(pk=pk)
|
||||
except Placeholder.DoesNotExist:
|
||||
return HttpResponseBadRequest("placeholder with id %s not found." % pk)
|
||||
if not placeholder.has_change_permission(request):
|
||||
if not placeholder.has_change_permission(request.user):
|
||||
return HttpResponseBadRequest("You do not have enough permission to alias this placeholder.")
|
||||
clipboard = request.toolbar.clipboard
|
||||
clipboard.cmsplugin_set.all().delete()
|
||||
|
||||
+421
-267
@@ -1,29 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from classytags.utils import flatten_context
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import get_permission_codename, get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch, resolve, Resolver404
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import Q
|
||||
from django.urls import NoReverseMatch, Resolver404, resolve, reverse
|
||||
from django.utils.translation import override as force_language, ugettext_lazy as _
|
||||
|
||||
from cms.api import get_page_draft, can_change_page
|
||||
from cms.constants import TEMPLATE_INHERITANCE_MAGIC, PUBLISHER_STATE_PENDING
|
||||
from cms.models import CMSPlugin, Title, Page
|
||||
from cms.toolbar.items import TemplateItem, REFRESH_PAGE
|
||||
from cms.models import Placeholder, Title, Page, PageType, StaticPlaceholder
|
||||
from cms.toolbar.items import ButtonList, TemplateItem, REFRESH_PAGE
|
||||
from cms.toolbar_base import CMSToolbar
|
||||
from cms.toolbar_pool import toolbar_pool
|
||||
from cms.utils.i18n import get_language_tuple, force_language, get_language_dict
|
||||
from cms.utils.compat.dj import is_installed
|
||||
from cms.utils import get_cms_setting
|
||||
from cms.utils.permissions import (
|
||||
get_user_sites_queryset,
|
||||
has_auth_page_permission,
|
||||
from cms.utils import get_language_from_request, page_permissions
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.i18n import get_language_tuple, get_language_dict
|
||||
from cms.utils.page_permissions import (
|
||||
user_can_change_page,
|
||||
user_can_delete_page,
|
||||
user_can_publish_page,
|
||||
)
|
||||
from cms.utils.urlutils import add_url_parameters, admin_reverse
|
||||
|
||||
from menus.utils import DefaultLanguageChanger
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ PAGE_MENU_SECOND_BREAK = 'Page Menu Second Break'
|
||||
PAGE_MENU_THIRD_BREAK = 'Page Menu Third Break'
|
||||
PAGE_MENU_FOURTH_BREAK = 'Page Menu Fourth Break'
|
||||
PAGE_MENU_LAST_BREAK = 'Page Menu Last Break'
|
||||
HISTORY_MENU_IDENTIFIER = 'history'
|
||||
HISTORY_MENU_BREAK = 'History Menu Break'
|
||||
MANAGE_PAGES_BREAK = 'Manage Pages Break'
|
||||
ADMIN_SITES_BREAK = 'Admin Sites Break'
|
||||
@@ -49,6 +48,7 @@ ADD_PAGE_LANGUAGE_BREAK = "Add page language Break"
|
||||
REMOVE_PAGE_LANGUAGE_BREAK = "Remove page language Break"
|
||||
COPY_PAGE_LANGUAGE_BREAK = "Copy page language Break"
|
||||
TOOLBAR_DISABLE_BREAK = 'Toolbar disable Break'
|
||||
SHORTCUTS_BREAK = 'Shortcuts Break'
|
||||
|
||||
|
||||
@toolbar_pool.register
|
||||
@@ -57,66 +57,36 @@ class PlaceholderToolbar(CMSToolbar):
|
||||
Adds placeholder edit buttons if placeholders or static placeholders are detected in the template
|
||||
"""
|
||||
|
||||
def init_from_request(self):
|
||||
def populate(self):
|
||||
self.page = get_page_draft(self.request.current_page)
|
||||
|
||||
def init_placeholders_from_request(self):
|
||||
self.placeholders = getattr(self.request, 'placeholders', [])
|
||||
self.statics = getattr(self.request, 'static_placeholders', [])
|
||||
|
||||
def populate(self):
|
||||
self.init_from_request()
|
||||
|
||||
def post_template_populate(self):
|
||||
self.init_placeholders_from_request()
|
||||
|
||||
super(PlaceholderToolbar, self).post_template_populate()
|
||||
self.add_wizard_button()
|
||||
self.add_structure_mode()
|
||||
|
||||
def add_structure_mode(self):
|
||||
if self.page and not self.page.application_urls:
|
||||
if self.page.has_change_permission(self.request):
|
||||
return self.add_structure_mode_item()
|
||||
|
||||
elif self.placeholders:
|
||||
return self.add_structure_mode_item()
|
||||
|
||||
for sp in self.statics:
|
||||
if sp.has_change_permission(self.request):
|
||||
return self.add_structure_mode_item()
|
||||
|
||||
def add_structure_mode_item(self, extra_classes=('cms-toolbar-item-cms-mode-switcher',)):
|
||||
build_mode = self.toolbar.build_mode
|
||||
build_url = '?%s' % get_cms_setting('CMS_TOOLBAR_URL__BUILD')
|
||||
edit_url = '?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')
|
||||
|
||||
if self.request.user.has_perm("cms.use_structure"):
|
||||
switcher = self.toolbar.add_button_list('Mode Switcher', side=self.toolbar.RIGHT,
|
||||
extra_classes=extra_classes)
|
||||
switcher.add_button(_('Structure'), build_url, active=build_mode, disabled=False)
|
||||
switcher.add_button(_('Content'), edit_url, active=not build_mode, disabled=False)
|
||||
|
||||
def add_wizard_button(self):
|
||||
from cms.wizards.wizard_pool import entry_choices
|
||||
title = _("Create")
|
||||
try:
|
||||
page_pk = self.page.pk
|
||||
except AttributeError:
|
||||
|
||||
if self.page:
|
||||
user = self.request.user
|
||||
page_pk = self.page.pk
|
||||
disabled = len(list(entry_choices(user, self.page))) == 0
|
||||
else:
|
||||
page_pk = ''
|
||||
disabled = True
|
||||
|
||||
user = getattr(self.request, "user", None)
|
||||
disabled = user and hasattr(self, "page") and len(
|
||||
list(entry_choices(user, self.page))) == 0
|
||||
|
||||
url = '{url}?page={page}&edit'.format(
|
||||
url = '{url}?page={page}&language={lang}&edit'.format(
|
||||
url=reverse("cms_wizard_create"),
|
||||
page=page_pk
|
||||
page=page_pk,
|
||||
lang=self.toolbar.site_language,
|
||||
)
|
||||
self.toolbar.add_modal_button(title, url,
|
||||
side=self.toolbar.RIGHT,
|
||||
disabled=disabled,
|
||||
on_close=REFRESH_PAGE)
|
||||
|
||||
|
||||
@toolbar_pool.register
|
||||
class BasicToolbar(CMSToolbar):
|
||||
"""
|
||||
@@ -132,9 +102,7 @@ class BasicToolbar(CMSToolbar):
|
||||
def populate(self):
|
||||
if not self.page:
|
||||
self.init_from_request()
|
||||
|
||||
user_settings = self.request.toolbar.get_user_settings()
|
||||
self.clipboard = user_settings.clipboard
|
||||
self.clipboard = self.request.toolbar.user_settings.clipboard
|
||||
self.add_admin_menu()
|
||||
self.add_language_menu()
|
||||
|
||||
@@ -145,10 +113,7 @@ class BasicToolbar(CMSToolbar):
|
||||
self.add_users_button(self._admin_menu)
|
||||
|
||||
# sites menu
|
||||
if get_cms_setting('PERMISSION'):
|
||||
sites_queryset = get_user_sites_queryset(self.request.user)
|
||||
else:
|
||||
sites_queryset = Site.objects.all()
|
||||
sites_queryset = Site.objects.order_by('name')
|
||||
|
||||
if len(sites_queryset) > 1:
|
||||
sites_menu = self._admin_menu.get_or_create_menu('sites', _('Sites'))
|
||||
@@ -167,9 +132,9 @@ class BasicToolbar(CMSToolbar):
|
||||
self._admin_menu.add_break(USER_SETTINGS_BREAK)
|
||||
|
||||
# clipboard
|
||||
if self.toolbar.edit_mode or self.toolbar.build_mode:
|
||||
if self.toolbar.edit_mode_active:
|
||||
# True if the clipboard exists and there's plugins in it.
|
||||
clipboard_is_bound = self.get_clipboard_plugins().exists()
|
||||
clipboard_is_bound = self.toolbar.clipboard_plugin
|
||||
|
||||
self._admin_menu.add_link_item(_('Clipboard...'), url='#',
|
||||
extra_classes=['cms-clipboard-trigger'],
|
||||
@@ -182,6 +147,9 @@ class BasicToolbar(CMSToolbar):
|
||||
# Disable toolbar
|
||||
self._admin_menu.add_link_item(_('Disable toolbar'), url='?%s' % get_cms_setting('CMS_TOOLBAR_URL__DISABLE'))
|
||||
self._admin_menu.add_break(TOOLBAR_DISABLE_BREAK)
|
||||
self._admin_menu.add_link_item(_('Shortcuts...'), url='#',
|
||||
extra_classes=('cms-show-shortcuts',))
|
||||
self._admin_menu.add_break(SHORTCUTS_BREAK)
|
||||
|
||||
# logout
|
||||
self.add_logout_button(self._admin_menu)
|
||||
@@ -202,12 +170,18 @@ class BasicToolbar(CMSToolbar):
|
||||
# * unpublished page: redirect to the home page
|
||||
# * published page with login_required: redirect to the home page
|
||||
# * published page with view permissions: redirect to the home page
|
||||
page_is_published = self.page and self.page.is_published(self.current_lang)
|
||||
|
||||
if (self.page and self.page.is_published(self.current_lang) and not self.page.login_required and
|
||||
self.page.has_view_permission(self.request, AnonymousUser())):
|
||||
on_success = self.toolbar.REFRESH_PAGE
|
||||
if page_is_published and not self.page.login_required:
|
||||
anon_can_access = page_permissions.user_can_view_page(
|
||||
user=AnonymousUser(),
|
||||
page=self.page,
|
||||
site=self.current_site,
|
||||
)
|
||||
else:
|
||||
on_success = '/'
|
||||
anon_can_access = False
|
||||
|
||||
on_success = self.toolbar.REFRESH_PAGE if anon_can_access else '/'
|
||||
|
||||
# We'll show "Logout Joe Bloggs" if the name fields in auth.User are completed, else "Logout jbloggs". If
|
||||
# anything goes wrong, it'll just be "Logout".
|
||||
@@ -215,11 +189,17 @@ class BasicToolbar(CMSToolbar):
|
||||
user_name = self.get_username()
|
||||
logout_menu_text = _('Logout %s') % user_name if user_name else _('Logout')
|
||||
|
||||
parent.add_ajax_item(logout_menu_text, action=admin_reverse('logout'), active=True, on_success=on_success)
|
||||
parent.add_ajax_item(
|
||||
logout_menu_text,
|
||||
action=admin_reverse('logout'),
|
||||
active=True,
|
||||
on_success=on_success,
|
||||
method='GET',
|
||||
)
|
||||
|
||||
def add_language_menu(self):
|
||||
if settings.USE_I18N and not self._language_menu:
|
||||
self._language_menu = self.toolbar.get_or_create_menu(LANGUAGE_MENU_IDENTIFIER, _('Language'))
|
||||
self._language_menu = self.toolbar.get_or_create_menu(LANGUAGE_MENU_IDENTIFIER, _('Language'), position=-1)
|
||||
language_changer = getattr(self.request, '_language_changer', DefaultLanguageChanger(self.request))
|
||||
for code, name in get_language_tuple(self.current_site.pk):
|
||||
try:
|
||||
@@ -239,37 +219,61 @@ class BasicToolbar(CMSToolbar):
|
||||
except (AttributeError, NotImplementedError):
|
||||
return default
|
||||
|
||||
def get_clipboard_plugins(self):
|
||||
self.populate()
|
||||
|
||||
if not hasattr(self, "clipboard"):
|
||||
return CMSPlugin.objects.none()
|
||||
return self.clipboard.get_plugins()
|
||||
|
||||
def render_addons(self, context):
|
||||
context.push()
|
||||
context['local_toolbar'] = self
|
||||
clipboard = mark_safe(render_to_string('cms/toolbar/clipboard.html', flatten_context(context)))
|
||||
context.pop()
|
||||
return [clipboard]
|
||||
|
||||
|
||||
@toolbar_pool.register
|
||||
class PageToolbar(CMSToolbar):
|
||||
_changed_admin_menu = None
|
||||
watch_models = [Page]
|
||||
watch_models = [Page, PageType]
|
||||
|
||||
# Helpers
|
||||
def init_placeholders(self):
|
||||
request = self.request
|
||||
toolbar = self.toolbar
|
||||
|
||||
def init_from_request(self):
|
||||
self.page = get_page_draft(self.request.current_page)
|
||||
self.title = self.get_title()
|
||||
self.permissions_activated = get_cms_setting('PERMISSION')
|
||||
if toolbar._async and 'placeholders[]' in request.GET:
|
||||
# AJAX request to reload page structure
|
||||
placeholder_ids = request.GET.getlist("placeholders[]")
|
||||
self.placeholders = Placeholder.objects.filter(pk__in=placeholder_ids)
|
||||
self.statics = StaticPlaceholder.objects.filter(
|
||||
Q(draft__in=placeholder_ids) | Q(public__in=placeholder_ids)
|
||||
)
|
||||
self.dirty_statics = [sp for sp in self.statics if sp.dirty]
|
||||
else:
|
||||
if toolbar.structure_mode_active and not toolbar.uses_legacy_structure_mode:
|
||||
# User has explicitly requested structure mode
|
||||
# and the object (page, blog, etc..) allows for the non-legacy structure mode
|
||||
renderer = toolbar.structure_renderer
|
||||
else:
|
||||
renderer = toolbar.get_content_renderer()
|
||||
|
||||
def init_placeholders_from_request(self):
|
||||
self.placeholders = getattr(self.request, 'placeholders', [])
|
||||
self.statics = getattr(self.request, 'static_placeholders', [])
|
||||
self.dirty_statics = [sp for sp in self.statics if sp.dirty]
|
||||
self.placeholders = renderer.get_rendered_placeholders()
|
||||
self.statics = renderer.get_rendered_static_placeholders()
|
||||
self.dirty_statics = [sp for sp in self.statics if sp.dirty]
|
||||
|
||||
def add_structure_mode(self):
|
||||
if self.page and not self.page.application_urls:
|
||||
if user_can_change_page(self.request.user, page=self.page):
|
||||
return self.add_structure_mode_item()
|
||||
|
||||
elif any(ph for ph in self.placeholders if ph.has_change_permission(self.request.user)):
|
||||
return self.add_structure_mode_item()
|
||||
|
||||
for sp in self.statics:
|
||||
if sp.has_change_permission(self.request):
|
||||
return self.add_structure_mode_item()
|
||||
|
||||
def add_structure_mode_item(self, extra_classes=('cms-toolbar-item-cms-mode-switcher',)):
|
||||
structure_active = self.toolbar.structure_mode_active
|
||||
edit_mode_active = (not structure_active and self.toolbar.edit_mode_active)
|
||||
build_url = '{}?{}'.format(self.toolbar.request_path, get_cms_setting('CMS_TOOLBAR_URL__BUILD'))
|
||||
edit_url = '{}?{}'.format(self.toolbar.request_path, get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON'))
|
||||
|
||||
if self.request.user.has_perm("cms.use_structure"):
|
||||
switcher = self.toolbar.add_button_list('Mode Switcher', side=self.toolbar.RIGHT,
|
||||
extra_classes=extra_classes)
|
||||
switcher.add_button(_('Structure'), build_url, active=structure_active, disabled=False,
|
||||
extra_classes='cms-structure-btn')
|
||||
switcher.add_button(_('Content'), edit_url, active=edit_mode_active, disabled=False,
|
||||
extra_classes='cms-content-btn')
|
||||
|
||||
def get_title(self):
|
||||
try:
|
||||
@@ -278,31 +282,25 @@ class PageToolbar(CMSToolbar):
|
||||
return None
|
||||
|
||||
def has_publish_permission(self):
|
||||
if not hasattr(self, 'publish_permission'):
|
||||
publish_permission = bool(self.page or self.statics)
|
||||
if self.page:
|
||||
publish_permission = page_permissions.user_can_publish_page(
|
||||
self.request.user,
|
||||
page=self.page,
|
||||
site=self.current_site
|
||||
)
|
||||
else:
|
||||
publish_permission = False
|
||||
|
||||
if self.page:
|
||||
publish_permission = self.page.has_publish_permission(self.request)
|
||||
if publish_permission and self.statics:
|
||||
publish_permission = all(sp.has_publish_permission(self.request) for sp in self.dirty_statics)
|
||||
return publish_permission
|
||||
|
||||
if self.statics:
|
||||
publish_permission &= all(sp.has_publish_permission(self.request) for sp in self.dirty_statics)
|
||||
|
||||
self.publish_permission = publish_permission
|
||||
|
||||
return self.publish_permission
|
||||
def has_unpublish_permission(self):
|
||||
return self.has_publish_permission()
|
||||
|
||||
def has_page_change_permission(self):
|
||||
if not hasattr(self, 'page_change_permission'):
|
||||
if not self.page and not get_cms_setting('PERMISSION'):
|
||||
# We can't check permissions for an individual page
|
||||
# and can't check global cms permissions because
|
||||
# user opted out of them.
|
||||
# So just check django auth permissions.
|
||||
user = self.request.user
|
||||
can_change = has_auth_page_permission(user, action='change')
|
||||
else:
|
||||
can_change = can_change_page(self.request)
|
||||
self.page_change_permission = can_change
|
||||
self.page_change_permission = can_change_page(self.request)
|
||||
return self.page_change_permission
|
||||
|
||||
def page_is_pending(self, page, language):
|
||||
@@ -310,22 +308,35 @@ class PageToolbar(CMSToolbar):
|
||||
page.publisher_public.get_publisher_state(language) == PUBLISHER_STATE_PENDING)
|
||||
|
||||
def in_apphook(self):
|
||||
with force_language(self.toolbar.language):
|
||||
with force_language(self.toolbar.request_language):
|
||||
try:
|
||||
resolver = resolve(self.request.path_info)
|
||||
resolver = resolve(self.toolbar.request_path)
|
||||
except Resolver404:
|
||||
return False
|
||||
else:
|
||||
from cms.views import details
|
||||
return resolver.func != details
|
||||
|
||||
def in_apphook_root(self):
|
||||
"""
|
||||
Returns True if the request is for a page handled by an apphook, but
|
||||
is also the page it is attached to.
|
||||
:return: Boolean
|
||||
"""
|
||||
page = getattr(self.request, 'current_page', False)
|
||||
if page:
|
||||
language = get_language_from_request(self.request)
|
||||
return self.toolbar.request_path == page.get_absolute_url(language=language)
|
||||
return False
|
||||
|
||||
def get_on_delete_redirect_url(self):
|
||||
parent, language = self.page.parent, self.current_lang
|
||||
language = self.current_lang
|
||||
parent_page = self.page.parent_page if self.page else None
|
||||
|
||||
# if the current page has a parent in the request's current language redirect to it
|
||||
if parent and language in parent.get_languages():
|
||||
if parent_page and language in parent_page.get_languages():
|
||||
with force_language(language):
|
||||
return parent.get_absolute_url(language=language)
|
||||
return parent_page.get_absolute_url(language=language)
|
||||
|
||||
# else redirect to root, do not redirect to Page.objects.get_home() because user could have deleted the last
|
||||
# page, if DEBUG == False this could cause a 404
|
||||
@@ -334,61 +345,92 @@ class PageToolbar(CMSToolbar):
|
||||
# Populate
|
||||
|
||||
def populate(self):
|
||||
self.init_from_request()
|
||||
|
||||
self.page = get_page_draft(self.request.current_page)
|
||||
self.title = self.get_title()
|
||||
self.permissions_activated = get_cms_setting('PERMISSION')
|
||||
self.change_admin_menu()
|
||||
self.add_page_menu()
|
||||
self.add_history_menu()
|
||||
self.change_language_menu()
|
||||
|
||||
def post_template_populate(self):
|
||||
self.init_placeholders_from_request()
|
||||
self.init_placeholders()
|
||||
self.add_draft_live()
|
||||
self.add_publish_button()
|
||||
self.add_structure_mode()
|
||||
|
||||
def has_dirty_objects(self):
|
||||
language = self.current_lang
|
||||
|
||||
if self.page:
|
||||
if self.dirty_statics:
|
||||
# There's dirty static placeholders on this page.
|
||||
# Only show the page as dirty (publish button) if the page
|
||||
# translation has been configured.
|
||||
dirty = self.page.has_translation(language)
|
||||
else:
|
||||
dirty = (self.page.is_dirty(language) or self.page_is_pending(self.page, language))
|
||||
else:
|
||||
dirty = bool(self.dirty_statics)
|
||||
return dirty
|
||||
|
||||
# Buttons
|
||||
|
||||
def add_publish_button(self, classes=('cms-btn-action', 'cms-btn-publish',)):
|
||||
# only do dirty lookups if publish permission is granted else button isn't added anyway
|
||||
if self.toolbar.edit_mode and self.has_publish_permission():
|
||||
classes = list(classes or [])
|
||||
pk = self.page.pk if self.page else 0
|
||||
if self.user_can_publish():
|
||||
button = self.get_publish_button(classes=classes)
|
||||
self.toolbar.add_item(button)
|
||||
|
||||
dirty = (bool(self.dirty_statics) or
|
||||
(self.page and (self.page.is_dirty(self.current_lang) or
|
||||
self.page_is_pending(self.page, self.current_lang))))
|
||||
def user_can_publish(self):
|
||||
if self.page and self.page.is_page_type:
|
||||
# By design, page-types are not publishable.
|
||||
return False
|
||||
|
||||
if dirty:
|
||||
classes.append('cms-btn-publish-active')
|
||||
if not self.toolbar.edit_mode_active:
|
||||
return False
|
||||
return self.has_publish_permission() and self.has_dirty_objects()
|
||||
|
||||
if self.dirty_statics or (self.page and self.page.is_published(self.current_lang)):
|
||||
title = _('Publish changes')
|
||||
else:
|
||||
title = _('Publish page now')
|
||||
classes.append('cms-publish-page')
|
||||
def get_publish_button(self, classes=None):
|
||||
dirty = self.has_dirty_objects()
|
||||
classes = list(classes or [])
|
||||
|
||||
params = {}
|
||||
if dirty and 'cms-btn-publish-active' not in classes:
|
||||
classes.append('cms-btn-publish-active')
|
||||
|
||||
if self.dirty_statics:
|
||||
params['statics'] = ','.join(str(sp.pk) for sp in self.dirty_statics)
|
||||
if self.dirty_statics or (self.page and self.page.is_published(self.current_lang)):
|
||||
title = _('Publish page changes')
|
||||
else:
|
||||
title = _('Publish page now')
|
||||
classes.append('cms-publish-page')
|
||||
|
||||
if self.in_apphook():
|
||||
params['redirect'] = self.request.path_info
|
||||
item = ButtonList(side=self.toolbar.RIGHT)
|
||||
item.add_button(
|
||||
title,
|
||||
url=self.get_publish_url(),
|
||||
disabled=not dirty,
|
||||
extra_classes=classes,
|
||||
)
|
||||
return item
|
||||
|
||||
with force_language(self.current_lang):
|
||||
url = admin_reverse('cms_page_publish_page', args=(pk, self.current_lang))
|
||||
def get_publish_url(self):
|
||||
pk = self.page.pk if self.page else 0
|
||||
params = {}
|
||||
|
||||
url = add_url_parameters(url, params)
|
||||
if self.dirty_statics:
|
||||
params['statics'] = ','.join(str(sp.pk) for sp in self.dirty_statics)
|
||||
|
||||
self.toolbar.add_button(title, url=url, extra_classes=classes,
|
||||
side=self.toolbar.RIGHT, disabled=not dirty)
|
||||
if self.in_apphook():
|
||||
params['redirect'] = self.toolbar.request_path
|
||||
|
||||
with force_language(self.current_lang):
|
||||
url = admin_reverse('cms_page_publish_page', args=(pk, self.current_lang))
|
||||
return add_url_parameters(url, params)
|
||||
|
||||
def add_draft_live(self):
|
||||
if self.page:
|
||||
if self.toolbar.edit_mode and not self.title:
|
||||
if self.toolbar.edit_mode_active and not self.title:
|
||||
self.add_page_settings_button()
|
||||
|
||||
if self.page.has_change_permission(self.request) and self.page.is_published(self.current_lang):
|
||||
if user_can_change_page(self.request.user, page=self.page) and self.page.is_published(self.current_lang):
|
||||
return self.add_draft_live_item()
|
||||
|
||||
elif self.placeholders:
|
||||
@@ -399,19 +441,27 @@ class PageToolbar(CMSToolbar):
|
||||
return self.add_draft_live_item()
|
||||
|
||||
def add_draft_live_item(self, template='cms/toolbar/items/live_draft.html', extra_context=None):
|
||||
context = {'request': self.request}
|
||||
context = {'cms_toolbar': self.toolbar}
|
||||
context.update(extra_context or {})
|
||||
pos = len(self.toolbar.right_items)
|
||||
self.toolbar.add_item(TemplateItem(template, extra_context=context, side=self.toolbar.RIGHT), position=pos)
|
||||
|
||||
def add_page_settings_button(self, extra_classes=('cms-btn-action',)):
|
||||
url = '%s?language=%s' % (admin_reverse('cms_page_change', args=[self.page.pk]), self.toolbar.language)
|
||||
url = '%s?language=%s' % (admin_reverse('cms_page_change', args=[self.page.pk]), self.toolbar.request_language)
|
||||
self.toolbar.add_modal_button(_('Page settings'), url, side=self.toolbar.RIGHT, extra_classes=extra_classes)
|
||||
|
||||
# Menus
|
||||
|
||||
def change_language_menu(self):
|
||||
if self.toolbar.edit_mode and self.page:
|
||||
if self.toolbar.edit_mode_active and self.page:
|
||||
can_change = page_permissions.user_can_change_page(
|
||||
user=self.request.user,
|
||||
page=self.page,
|
||||
site=self.current_site,
|
||||
)
|
||||
else:
|
||||
can_change = False
|
||||
|
||||
if can_change:
|
||||
language_menu = self.toolbar.get_menu(LANGUAGE_MENU_IDENTIFIER)
|
||||
if not language_menu:
|
||||
return None
|
||||
@@ -421,38 +471,65 @@ class PageToolbar(CMSToolbar):
|
||||
remove = [(code, languages.get(code, code)) for code in self.page.get_languages() if code in languages]
|
||||
add = [l for l in languages.items() if l not in remove]
|
||||
copy = [(code, name) for code, name in languages.items() if code != self.current_lang and (code, name) in remove]
|
||||
if add:
|
||||
|
||||
if add or remove or copy:
|
||||
language_menu.add_break(ADD_PAGE_LANGUAGE_BREAK)
|
||||
page_change_url = admin_reverse('cms_page_change', args=(self.page.pk,))
|
||||
title = _('Add %(language)s Translation')
|
||||
|
||||
if add:
|
||||
add_plugins_menu = language_menu.get_or_create_menu('{0}-add'.format(LANGUAGE_MENU_IDENTIFIER), _('Add Translation'))
|
||||
|
||||
if self.page.is_page_type:
|
||||
page_change_url = admin_reverse('cms_pagetype_change', args=(self.page.pk,))
|
||||
else:
|
||||
page_change_url = admin_reverse('cms_page_change', args=(self.page.pk,))
|
||||
|
||||
for code, name in add:
|
||||
url = add_url_parameters(page_change_url, language=code)
|
||||
language_menu.add_modal_item(title % {'language': name}, url=url)
|
||||
add_plugins_menu.add_modal_item(name, url=url)
|
||||
|
||||
if remove:
|
||||
language_menu.add_break(REMOVE_PAGE_LANGUAGE_BREAK)
|
||||
translation_delete_url = admin_reverse('cms_page_delete_translation', args=(self.page.pk,))
|
||||
title = _('Delete %(language)s Translation')
|
||||
if self.page.is_page_type:
|
||||
translation_delete_url = admin_reverse('cms_pagetype_delete_translation', args=(self.page.pk,))
|
||||
else:
|
||||
translation_delete_url = admin_reverse('cms_page_delete_translation', args=(self.page.pk,))
|
||||
|
||||
remove_plugins_menu = language_menu.get_or_create_menu('{0}-del'.format(LANGUAGE_MENU_IDENTIFIER), _('Delete Translation'))
|
||||
disabled = len(remove) == 1
|
||||
for code, name in remove:
|
||||
url = add_url_parameters(translation_delete_url, language=code)
|
||||
language_menu.add_modal_item(title % {'language': name}, url=url, disabled=disabled)
|
||||
remove_plugins_menu.add_modal_item(name, url=url, disabled=disabled)
|
||||
|
||||
if copy:
|
||||
language_menu.add_break(COPY_PAGE_LANGUAGE_BREAK)
|
||||
page_copy_url = admin_reverse('cms_page_copy_language', args=(self.page.pk,))
|
||||
title = _('Copy all plugins from %s')
|
||||
question = _('Are you sure you want copy all plugins from %s?')
|
||||
copy_plugins_menu = language_menu.get_or_create_menu('{0}-copy'.format(LANGUAGE_MENU_IDENTIFIER), _('Copy all plugins'))
|
||||
title = _('from %s')
|
||||
question = _('Are you sure you want to copy all plugins from %s?')
|
||||
|
||||
if self.page.is_page_type:
|
||||
page_copy_url = admin_reverse('cms_pagetype_copy_language', args=(self.page.pk,))
|
||||
else:
|
||||
page_copy_url = admin_reverse('cms_page_copy_language', args=(self.page.pk,))
|
||||
|
||||
for code, name in copy:
|
||||
language_menu.add_ajax_item(title % name, action=page_copy_url,
|
||||
data={'source_language': code, 'target_language': self.current_lang},
|
||||
question=question % name, on_success=self.toolbar.REFRESH_PAGE)
|
||||
copy_plugins_menu.add_ajax_item(
|
||||
title % name, action=page_copy_url,
|
||||
data={'source_language': code, 'target_language': self.current_lang},
|
||||
question=question % name, on_success=self.toolbar.REFRESH_PAGE
|
||||
)
|
||||
|
||||
def change_admin_menu(self):
|
||||
if not self._changed_admin_menu and self.has_page_change_permission():
|
||||
can_change_page = self.has_page_change_permission()
|
||||
|
||||
if not can_change_page:
|
||||
# Check if the user has permissions to change at least one page
|
||||
can_change_page = page_permissions.user_can_change_at_least_one_page(
|
||||
user=self.request.user,
|
||||
site=self.current_site,
|
||||
)
|
||||
|
||||
if not self._changed_admin_menu and can_change_page:
|
||||
admin_menu = self.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER)
|
||||
url = admin_reverse('cms_page_changelist') # cms page admin
|
||||
params = {'language': self.toolbar.language}
|
||||
params = {'language': self.toolbar.request_language}
|
||||
if self.page:
|
||||
params['page_id'] = self.page.pk
|
||||
url = add_url_parameters(url, params)
|
||||
@@ -461,26 +538,80 @@ class PageToolbar(CMSToolbar):
|
||||
self._changed_admin_menu = True
|
||||
|
||||
def add_page_menu(self):
|
||||
if self.page and self.has_page_change_permission():
|
||||
edit_mode = self.toolbar.edit_mode
|
||||
if self.page:
|
||||
edit_mode = self.toolbar.edit_mode_active
|
||||
refresh = self.toolbar.REFRESH_PAGE
|
||||
|
||||
# menu for current page
|
||||
current_page_menu = self.toolbar.get_or_create_menu(PAGE_MENU_IDENTIFIER, _('Page'), position=1)
|
||||
|
||||
# page operations menu
|
||||
add_page_menu = current_page_menu.get_or_create_menu(PAGE_MENU_ADD_IDENTIFIER, _('Add Page'))
|
||||
app_page_url = admin_reverse('cms_page_add')
|
||||
|
||||
add_page_menu_modal_items = (
|
||||
(_('New Page'), {'edit': 1, 'position': 'last-child', 'target': self.page.parent_id or ''}),
|
||||
(_('New Sub Page'), {'edit': 1, 'position': 'last-child', 'target': self.page.pk}),
|
||||
(_('Duplicate this Page'), {'copy_target': self.page.pk})
|
||||
can_change = user_can_change_page(
|
||||
user=self.request.user,
|
||||
page=self.page,
|
||||
site=self.current_site,
|
||||
)
|
||||
|
||||
for title, params in add_page_menu_modal_items:
|
||||
params.update(language=self.toolbar.language)
|
||||
add_page_menu.add_modal_item(title, url=add_url_parameters(app_page_url, params))
|
||||
# menu for current page
|
||||
# NOTE: disabled if the current path is "deeper" into the
|
||||
# application's url patterns than its root. This is because
|
||||
# when the Content Manager is at the root of the app-hook,
|
||||
# some of the page options still make sense.
|
||||
current_page_menu = self.toolbar.get_or_create_menu(
|
||||
PAGE_MENU_IDENTIFIER, _('Page'), position=1, disabled=self.in_apphook() and not self.in_apphook_root())
|
||||
|
||||
new_page_params = {'edit': 1}
|
||||
new_sub_page_params = {'edit': 1, 'parent_node': self.page.node_id}
|
||||
|
||||
if self.page.is_page_type:
|
||||
add_page_url = admin_reverse('cms_pagetype_add')
|
||||
advanced_url = admin_reverse('cms_pagetype_advanced', args=(self.page.pk,))
|
||||
page_settings_url = admin_reverse('cms_pagetype_change', args=(self.page.pk,))
|
||||
duplicate_page_url = admin_reverse('cms_pagetype_duplicate', args=[self.page.pk])
|
||||
else:
|
||||
add_page_url = admin_reverse('cms_page_add')
|
||||
advanced_url = admin_reverse('cms_page_advanced', args=(self.page.pk,))
|
||||
page_settings_url = admin_reverse('cms_page_change', args=(self.page.pk,))
|
||||
duplicate_page_url = admin_reverse('cms_page_duplicate', args=[self.page.pk])
|
||||
|
||||
can_add_root_page = page_permissions.user_can_add_page(
|
||||
user=self.request.user,
|
||||
site=self.current_site,
|
||||
)
|
||||
|
||||
if self.page.parent_page:
|
||||
new_page_params['parent_node'] = self.page.parent_page.node_id
|
||||
can_add_sibling_page = page_permissions.user_can_add_subpage(
|
||||
user=self.request.user,
|
||||
target=self.page.parent_page,
|
||||
)
|
||||
else:
|
||||
can_add_sibling_page = can_add_root_page
|
||||
|
||||
can_add_sub_page = page_permissions.user_can_add_subpage(
|
||||
user=self.request.user,
|
||||
target=self.page,
|
||||
)
|
||||
|
||||
# page operations menu
|
||||
add_page_menu = current_page_menu.get_or_create_menu(
|
||||
PAGE_MENU_ADD_IDENTIFIER,
|
||||
_('Create Page'),
|
||||
)
|
||||
|
||||
add_page_menu_modal_items = (
|
||||
(_('New Page'), new_page_params, can_add_sibling_page),
|
||||
(_('New Sub Page'), new_sub_page_params, can_add_sub_page),
|
||||
)
|
||||
|
||||
for title, params, has_perm in add_page_menu_modal_items:
|
||||
params.update(language=self.toolbar.request_language)
|
||||
add_page_menu.add_modal_item(
|
||||
title,
|
||||
url=add_url_parameters(add_page_url, params),
|
||||
disabled=not has_perm,
|
||||
)
|
||||
|
||||
add_page_menu.add_modal_item(
|
||||
_('Duplicate this Page'),
|
||||
url=add_url_parameters(duplicate_page_url, {'language': self.toolbar.request_language}),
|
||||
disabled=not can_add_sibling_page,
|
||||
)
|
||||
|
||||
# first break
|
||||
current_page_menu.add_break(PAGE_MENU_FIRST_BREAK)
|
||||
@@ -490,106 +621,129 @@ class PageToolbar(CMSToolbar):
|
||||
current_page_menu.add_link_item(_('Edit this Page'), disabled=edit_mode, url=page_edit_url)
|
||||
|
||||
# page settings
|
||||
page_settings_url = admin_reverse('cms_page_change', args=(self.page.pk,))
|
||||
page_settings_url = add_url_parameters(page_settings_url, language=self.toolbar.language)
|
||||
current_page_menu.add_modal_item(_('Page settings'), url=page_settings_url, disabled=not edit_mode,
|
||||
page_settings_url = add_url_parameters(page_settings_url, language=self.toolbar.request_language)
|
||||
settings_disabled = not edit_mode or not can_change
|
||||
current_page_menu.add_modal_item(_('Page settings'), url=page_settings_url, disabled=settings_disabled,
|
||||
on_close=refresh)
|
||||
|
||||
# templates menu
|
||||
if self.toolbar.build_mode or edit_mode:
|
||||
templates_menu = current_page_menu.get_or_create_menu('templates', _('Templates'))
|
||||
action = admin_reverse('cms_page_change_template', args=(self.page.pk,))
|
||||
for path, name in get_cms_setting('TEMPLATES'):
|
||||
active = self.page.template == path
|
||||
if path == TEMPLATE_INHERITANCE_MAGIC:
|
||||
templates_menu.add_break(TEMPLATE_MENU_BREAK)
|
||||
templates_menu.add_ajax_item(name, action=action, data={'template': path}, active=active,
|
||||
on_success=refresh)
|
||||
|
||||
# second break
|
||||
current_page_menu.add_break(PAGE_MENU_SECOND_BREAK)
|
||||
|
||||
# advanced settings
|
||||
advanced_url = admin_reverse('cms_page_advanced', args=(self.page.pk,))
|
||||
advanced_url = add_url_parameters(advanced_url, language=self.toolbar.language)
|
||||
advanced_disabled = not self.page.has_advanced_settings_permission(self.request) or not edit_mode
|
||||
advanced_url = add_url_parameters(advanced_url, language=self.toolbar.request_language)
|
||||
can_change_advanced = self.page.has_advanced_settings_permission(self.request.user)
|
||||
advanced_disabled = not edit_mode or not can_change_advanced
|
||||
current_page_menu.add_modal_item(_('Advanced settings'), url=advanced_url, disabled=advanced_disabled)
|
||||
|
||||
# templates menu
|
||||
if edit_mode:
|
||||
if self.page.is_page_type:
|
||||
action = admin_reverse('cms_pagetype_change_template', args=(self.page.pk,))
|
||||
else:
|
||||
action = admin_reverse('cms_page_change_template', args=(self.page.pk,))
|
||||
|
||||
if can_change_advanced:
|
||||
templates_menu = current_page_menu.get_or_create_menu(
|
||||
'templates',
|
||||
_('Templates'),
|
||||
disabled=not can_change,
|
||||
)
|
||||
|
||||
for path, name in get_cms_setting('TEMPLATES'):
|
||||
active = self.page.template == path
|
||||
if path == TEMPLATE_INHERITANCE_MAGIC:
|
||||
templates_menu.add_break(TEMPLATE_MENU_BREAK)
|
||||
templates_menu.add_ajax_item(name, action=action, data={'template': path}, active=active,
|
||||
on_success=refresh)
|
||||
|
||||
# page type
|
||||
if not self.page.is_page_type:
|
||||
page_type_url = admin_reverse('cms_pagetype_add')
|
||||
page_type_url = add_url_parameters(page_type_url, source=self.page.pk, language=self.toolbar.request_language)
|
||||
page_type_disabled = not edit_mode or not can_add_root_page
|
||||
current_page_menu.add_modal_item(_('Save as Page Type'), page_type_url, disabled=page_type_disabled)
|
||||
|
||||
# second break
|
||||
current_page_menu.add_break(PAGE_MENU_SECOND_BREAK)
|
||||
|
||||
# permissions
|
||||
if self.permissions_activated:
|
||||
permissions_url = admin_reverse('cms_page_permissions', args=(self.page.pk,))
|
||||
permission_disabled = not edit_mode or not self.page.has_change_permissions_permission(self.request)
|
||||
permission_disabled = not edit_mode
|
||||
|
||||
if not permission_disabled:
|
||||
permission_disabled = not page_permissions.user_can_change_page_permissions(
|
||||
user=self.request.user,
|
||||
page=self.page,
|
||||
)
|
||||
current_page_menu.add_modal_item(_('Permissions'), url=permissions_url, disabled=permission_disabled)
|
||||
|
||||
# dates settings
|
||||
dates_url = admin_reverse('cms_page_dates', args=(self.page.pk,))
|
||||
current_page_menu.add_modal_item(_('Publishing dates'), url=dates_url, disabled=not edit_mode)
|
||||
if not self.page.is_page_type:
|
||||
# dates settings
|
||||
dates_url = admin_reverse('cms_page_dates', args=(self.page.pk,))
|
||||
current_page_menu.add_modal_item(
|
||||
_('Publishing dates'),
|
||||
url=dates_url,
|
||||
disabled=(not edit_mode or not can_change),
|
||||
)
|
||||
|
||||
# third break
|
||||
current_page_menu.add_break(PAGE_MENU_THIRD_BREAK)
|
||||
# third break
|
||||
current_page_menu.add_break(PAGE_MENU_THIRD_BREAK)
|
||||
|
||||
# navigation toggle
|
||||
nav_title = _('Hide in navigation') if self.page.in_navigation else _('Display in navigation')
|
||||
nav_action = admin_reverse('cms_page_change_innavigation', args=(self.page.pk,))
|
||||
current_page_menu.add_ajax_item(nav_title, action=nav_action, disabled=not edit_mode, on_success=refresh)
|
||||
# navigation toggle
|
||||
nav_title = _('Hide in navigation') if self.page.in_navigation else _('Display in navigation')
|
||||
nav_action = admin_reverse('cms_page_change_innavigation', args=(self.page.pk,))
|
||||
current_page_menu.add_ajax_item(
|
||||
nav_title,
|
||||
action=nav_action,
|
||||
disabled=(not edit_mode or not can_change),
|
||||
on_success=refresh,
|
||||
)
|
||||
|
||||
# publisher
|
||||
if self.title:
|
||||
if self.title and not self.page.is_page_type:
|
||||
if self.title.published:
|
||||
publish_title = _('Unpublish page')
|
||||
publish_url = admin_reverse('cms_page_unpublish', args=(self.page.pk, self.current_lang))
|
||||
else:
|
||||
publish_title = _('Publish page')
|
||||
publish_url = admin_reverse('cms_page_publish_page', args=(self.page.pk, self.current_lang))
|
||||
current_page_menu.add_ajax_item(publish_title, action=publish_url, disabled=not edit_mode,
|
||||
on_success=refresh)
|
||||
|
||||
# fourth break
|
||||
current_page_menu.add_break(PAGE_MENU_FOURTH_BREAK)
|
||||
user_can_publish = user_can_publish_page(self.request.user, page=self.page)
|
||||
current_page_menu.add_ajax_item(
|
||||
publish_title,
|
||||
action=publish_url,
|
||||
disabled=not edit_mode or not user_can_publish,
|
||||
on_success=refresh,
|
||||
)
|
||||
|
||||
if self.current_lang and not self.page.is_page_type:
|
||||
# revert to live
|
||||
current_page_menu.add_break(PAGE_MENU_FOURTH_BREAK)
|
||||
revert_action = admin_reverse('cms_page_revert_to_live', args=(self.page.pk, self.current_lang))
|
||||
revert_question = _('Are you sure you want to revert to live?')
|
||||
# Only show this action if the page has pending changes and a public version
|
||||
is_enabled = (
|
||||
edit_mode
|
||||
and can_change
|
||||
and self.page.is_dirty(self.current_lang)
|
||||
and self.page.publisher_public
|
||||
)
|
||||
current_page_menu.add_ajax_item(
|
||||
_('Revert to live'),
|
||||
action=revert_action,
|
||||
question=revert_question,
|
||||
disabled=not is_enabled,
|
||||
on_success=refresh,
|
||||
extra_classes=('cms-toolbar-revert',),
|
||||
)
|
||||
|
||||
# last break
|
||||
current_page_menu.add_break(PAGE_MENU_LAST_BREAK)
|
||||
|
||||
# delete
|
||||
delete_url = admin_reverse('cms_page_delete', args=(self.page.pk,))
|
||||
if self.page.is_page_type:
|
||||
delete_url = admin_reverse('cms_pagetype_delete', args=(self.page.pk,))
|
||||
else:
|
||||
delete_url = admin_reverse('cms_page_delete', args=(self.page.pk,))
|
||||
delete_disabled = not edit_mode or not user_can_delete_page(self.request.user, page=self.page)
|
||||
on_delete_redirect_url = self.get_on_delete_redirect_url()
|
||||
current_page_menu.add_modal_item(_('Delete page'), url=delete_url, on_close=on_delete_redirect_url,
|
||||
disabled=not edit_mode)
|
||||
|
||||
# last break
|
||||
current_page_menu.add_break(PAGE_MENU_LAST_BREAK)
|
||||
|
||||
# page type
|
||||
page_type_url = admin_reverse('cms_page_add_page_type')
|
||||
page_type_url = add_url_parameters(page_type_url, copy_target=self.page.pk, language=self.toolbar.language)
|
||||
current_page_menu.add_modal_item(_('Save as Page Type'), page_type_url, disabled=not edit_mode)
|
||||
|
||||
def add_history_menu(self):
|
||||
if self.toolbar.edit_mode and self.page:
|
||||
refresh = self.toolbar.REFRESH_PAGE
|
||||
history_menu = self.toolbar.get_or_create_menu(HISTORY_MENU_IDENTIFIER, _('History'), position=2)
|
||||
|
||||
if is_installed('reversion'):
|
||||
from cms.utils.reversion_hacks import reversion, Revision
|
||||
|
||||
versions = reversion.get_for_object(self.page)
|
||||
if self.page.revision_id:
|
||||
current_revision = Revision.objects.get(pk=self.page.revision_id)
|
||||
has_undo = versions.filter(revision__pk__lt=current_revision.pk).exists()
|
||||
has_redo = versions.filter(revision__pk__gt=current_revision.pk).exists()
|
||||
else:
|
||||
has_redo = False
|
||||
has_undo = versions.count() > 1
|
||||
|
||||
undo_action = admin_reverse('cms_page_undo', args=(self.page.pk,))
|
||||
redo_action = admin_reverse('cms_page_redo', args=(self.page.pk,))
|
||||
|
||||
history_menu.add_ajax_item(_('Undo'), action=undo_action, disabled=not has_undo, on_success=refresh)
|
||||
history_menu.add_ajax_item(_('Redo'), action=redo_action, disabled=not has_redo, on_success=refresh)
|
||||
|
||||
history_menu.add_break(HISTORY_MENU_BREAK)
|
||||
|
||||
revert_action = admin_reverse('cms_page_revert_page', args=(self.page.pk, self.current_lang))
|
||||
revert_question = _('Are you sure you want to revert to live?')
|
||||
is_enabled = self.page.is_dirty(self.current_lang) and self.page.publisher_public
|
||||
history_menu.add_ajax_item(_('Revert to live'), action=revert_action, question=revert_question,
|
||||
disabled=not is_enabled,
|
||||
on_success=refresh, extra_classes=('cms-toolbar-revert',))
|
||||
history_menu.add_modal_item(_('View history'), url=admin_reverse('cms_page_history', args=(self.page.pk,)))
|
||||
disabled=delete_disabled)
|
||||
|
||||
+14
-15
@@ -1,10 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from cms.models import Page
|
||||
from cms.utils import permissions
|
||||
from cms.utils.page_permissions import user_can_add_page, user_can_add_subpage
|
||||
|
||||
from .wizards.wizard_pool import wizard_pool
|
||||
from .wizards.wizard_base import Wizard
|
||||
@@ -15,27 +13,28 @@ from .forms.wizards import CreateCMSPageForm, CreateCMSSubPageForm
|
||||
class CMSPageWizard(Wizard):
|
||||
|
||||
def user_has_add_permission(self, user, page=None, **kwargs):
|
||||
if not page or not page.site_id:
|
||||
site = Site.objects.get_current()
|
||||
if page:
|
||||
parent_page = page.get_parent_page()
|
||||
else:
|
||||
site = Site.objects.get(pk=page.site_id)
|
||||
return permissions.has_page_add_permission(
|
||||
user, page, position="right", site=site)
|
||||
parent_page = None
|
||||
|
||||
if page and page.get_parent_page():
|
||||
# User is adding a page which will be a right
|
||||
# sibling to the current page.
|
||||
has_perm = user_can_add_subpage(user, target=parent_page)
|
||||
else:
|
||||
has_perm = user_can_add_page(user)
|
||||
return has_perm
|
||||
|
||||
|
||||
class CMSSubPageWizard(Wizard):
|
||||
|
||||
def user_has_add_permission(self, user, page=None, **kwargs):
|
||||
if not page or page.application_urls:
|
||||
# We can't really add a sub-page to a non-existant page. Or to an
|
||||
# We can't really add a sub-page to a non-existent page. Or to an
|
||||
# app-hooked page.
|
||||
return False
|
||||
if not page.site_id:
|
||||
site = Site.objects.get_current()
|
||||
else:
|
||||
site = Site.objects.get(pk=page.site_id)
|
||||
return permissions.has_page_add_permission(
|
||||
user, page, position="last-child", site=site)
|
||||
return user_can_add_subpage(user, target=page)
|
||||
|
||||
|
||||
cms_page_wizard = CMSPageWizard(
|
||||
|
||||
+26
-5
@@ -1,27 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
TEMPLATE_INHERITANCE_MAGIC = 'INHERIT'
|
||||
REFRESH_PAGE = 'REFRESH_PAGE'
|
||||
FOLLOW_REDIRECT = 'FOLLOW_REDIRECT'
|
||||
URL_CHANGE = 'URL_CHANGE'
|
||||
RIGHT = object() # this is a trick so "foo is RIGHT" will only ever work for this, same goes for LEFT.
|
||||
LEFT = object()
|
||||
|
||||
# Plugin actions
|
||||
PLUGIN_MOVE_ACTION = 'move'
|
||||
PLUGIN_COPY_ACTION = 'copy'
|
||||
|
||||
PUBLISHER_STATE_DEFAULT = 0
|
||||
PUBLISHER_STATE_DIRTY = 1
|
||||
# Page was marked published, but some of page parents are not.
|
||||
PUBLISHER_STATE_PENDING = 4
|
||||
|
||||
PAGE_TYPES_ID = "page_types"
|
||||
PAGE_TREE_POSITIONS = ('last-child', 'first-child', 'left', 'right')
|
||||
|
||||
VISIBILITY_ALL = None
|
||||
VISIBILITY_USERS = 1
|
||||
VISIBILITY_ANONYMOUS = 2
|
||||
|
||||
X_FRAME_OPTIONS_INHERIT = 0
|
||||
X_FRAME_OPTIONS_DENY = 1
|
||||
X_FRAME_OPTIONS_SAMEORIGIN = 2
|
||||
X_FRAME_OPTIONS_ALLOW = 3
|
||||
|
||||
PAGE_USERNAME_MAX_LENGTH = 255
|
||||
|
||||
SLUG_REGEXP = '[0-9A-Za-z-_.//]+'
|
||||
|
||||
EXPIRE_NOW = 0
|
||||
# HTTP Specification says max caching should only be up to one year.
|
||||
MAX_EXPIRATION_TTL = 365 * 24 * 3600
|
||||
|
||||
PLUGIN_TOOLBAR_JS = "CMS._plugins.push([\"cms-plugin-%(pk)s\", %(config)s]);\n"
|
||||
|
||||
PLACEHOLDER_TOOLBAR_JS = "CMS._plugins.push([\"cms-placeholder-%(pk)s\", %(config)s]);"
|
||||
|
||||
# In the permissions system we use user levels to determine
|
||||
# the depth in which the user has permissions.
|
||||
# This constant represents a user that can see pages at all depths.
|
||||
ROOT_USER_LEVEL = -1
|
||||
|
||||
GRANT_ALL_PERMISSIONS = 'All'
|
||||
|
||||
PUBLISH_COMMENT = "Publish"
|
||||
|
||||
SCRIPT_USERNAME = 'script'
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.utils import lru_cache
|
||||
from django.utils.functional import lazy
|
||||
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils import get_template_from_request
|
||||
from cms.utils.page import get_page_template_from_request
|
||||
|
||||
|
||||
def cms_settings(request):
|
||||
"""
|
||||
Adds cms-related variables to the context.
|
||||
"""
|
||||
from menus.menu_pool import MenuRenderer
|
||||
|
||||
@lru_cache.lru_cache(maxsize=None)
|
||||
def _get_menu_renderer():
|
||||
# We use lru_cache to avoid getting the manager
|
||||
# every time this function is called.
|
||||
from menus.menu_pool import menu_pool
|
||||
return menu_pool.get_renderer(request)
|
||||
|
||||
# Now use lazy() to avoid getting the menu renderer
|
||||
# up until the point is needed.
|
||||
# lazy() does not memoize results, is why lru_cache is needed.
|
||||
_get_menu_renderer = lazy(_get_menu_renderer, MenuRenderer)
|
||||
|
||||
return {
|
||||
'cms_menu_renderer': _get_menu_renderer(),
|
||||
'CMS_MEDIA_URL': get_cms_setting('MEDIA_URL'),
|
||||
'CMS_TEMPLATE': lambda: get_template_from_request(request),
|
||||
'CMS_TEMPLATE': lambda: get_page_template_from_request(request),
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from cms.models import Page, Title
|
||||
from cms.utils.page_permissions import user_can_change_page
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.options import csrf_protect_m
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class ExtensionAdmin(admin.ModelAdmin):
|
||||
@@ -19,12 +21,12 @@ class PageExtensionAdmin(ExtensionAdmin):
|
||||
page = Page.objects.get(pk=request.GET['extended_object'])
|
||||
else:
|
||||
page = obj.extended_object
|
||||
if not page.has_change_permission(request):
|
||||
if not user_can_change_page(request.user, page):
|
||||
raise PermissionDenied()
|
||||
super(PageExtensionAdmin, self).save_model(request, obj, form, change)
|
||||
|
||||
def delete_model(self, request, obj):
|
||||
if not obj.extended_object.has_change_permission(request):
|
||||
if not obj.extended_object.has_change_permission(request.user):
|
||||
raise PermissionDenied()
|
||||
obj.delete()
|
||||
|
||||
@@ -66,12 +68,14 @@ class TitleExtensionAdmin(ExtensionAdmin):
|
||||
title = Title.objects.get(pk=request.GET['extended_object'])
|
||||
else:
|
||||
title = obj.extended_object
|
||||
if not title.page.has_change_permission(request):
|
||||
if not user_can_change_page(request.user, page=title.page):
|
||||
raise PermissionDenied()
|
||||
super(TitleExtensionAdmin, self).save_model(request, obj, form, change)
|
||||
|
||||
def delete_model(self, request, obj):
|
||||
if not obj.extended_object.page.has_change_permission(request):
|
||||
page = obj.extended_object.page
|
||||
|
||||
if not user_can_change_page(request.user, page):
|
||||
raise PermissionDenied()
|
||||
obj.delete()
|
||||
|
||||
|
||||
@@ -12,6 +12,20 @@ class ExtensionPool(object):
|
||||
def register(self, extension):
|
||||
"""
|
||||
Registers the given extension.
|
||||
|
||||
Example::
|
||||
|
||||
class MyExtension(PageExtension):
|
||||
pass
|
||||
|
||||
extension_pool.register(MyExtension)
|
||||
|
||||
or as decorator::
|
||||
|
||||
@extension_pool.register
|
||||
class MyExtension(PageExtension):
|
||||
pass
|
||||
|
||||
"""
|
||||
|
||||
if issubclass(extension, PageExtension):
|
||||
@@ -23,6 +37,7 @@ class ExtensionPool(object):
|
||||
'Extension has to subclass either %r or %r. %r does not!' % (PageExtension, TitleExtension, extension)
|
||||
)
|
||||
self._activate_signaling()
|
||||
return extension
|
||||
|
||||
def unregister(self, extension):
|
||||
"""
|
||||
|
||||
+32
-13
@@ -4,11 +4,16 @@ from cms.constants import PUBLISHER_STATE_DIRTY
|
||||
from django.db import models
|
||||
|
||||
from cms.models import Page, Title
|
||||
from cms.utils.compat import DJANGO_1_7
|
||||
|
||||
|
||||
class BaseExtension(models.Model):
|
||||
public_extension = models.OneToOneField('self', null=True, editable=False, related_name='draft_extension')
|
||||
public_extension = models.OneToOneField(
|
||||
'self',
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
editable=False,
|
||||
related_name='draft_extension',
|
||||
)
|
||||
extended_object = None
|
||||
|
||||
class Meta:
|
||||
@@ -28,15 +33,12 @@ class BaseExtension(models.Model):
|
||||
|
||||
@classmethod
|
||||
def _get_related_objects(cls):
|
||||
if DJANGO_1_7:
|
||||
return list(cls._meta.get_all_related_objects())
|
||||
else:
|
||||
fields = cls._meta._get_fields(
|
||||
forward=False, reverse=True,
|
||||
include_parents=True,
|
||||
include_hidden=False,
|
||||
)
|
||||
return list(obj for obj in fields if not isinstance(obj.field, ManyToManyField))
|
||||
fields = cls._meta._get_fields(
|
||||
forward=False, reverse=True,
|
||||
include_parents=True,
|
||||
include_hidden=False,
|
||||
)
|
||||
return list(obj for obj in fields if not isinstance(obj.field, ManyToManyField))
|
||||
|
||||
def copy(self, target, language):
|
||||
"""
|
||||
@@ -48,6 +50,12 @@ class BaseExtension(models.Model):
|
||||
clone.pk = None
|
||||
clone.public_extension = None
|
||||
clone.extended_object = target # set the new public object
|
||||
|
||||
# Nullify all concrete parent primary keys. See issue #5494
|
||||
for parent, field in clone._meta.parents.items():
|
||||
if field:
|
||||
setattr(clone, parent._meta.pk.attname, None)
|
||||
|
||||
clone.save(mark_page=False)
|
||||
|
||||
# If the target we're copying already has a publisher counterpart, then
|
||||
@@ -79,8 +87,19 @@ class BaseExtension(models.Model):
|
||||
if public_extension:
|
||||
this.pk = public_extension.pk # overwrite current public extension
|
||||
this.public_extension = None # remove public extension or it will point to itself and raise duplicate entry
|
||||
|
||||
# Set public_extension concrete parents PKs. See issue #5494
|
||||
for parent, field in this._meta.parents.items():
|
||||
if field:
|
||||
setattr(this, parent._meta.pk.attname, getattr(public_extension, parent._meta.pk.attname))
|
||||
else:
|
||||
this.pk = None # create new public extension
|
||||
|
||||
# Nullify all concrete parent primary keys. See issue #5494
|
||||
for parent, field in this._meta.parents.items():
|
||||
if field:
|
||||
setattr(this, parent._meta.pk.attname, None)
|
||||
|
||||
this.save(mark_page=False)
|
||||
self.public_extension = this
|
||||
self.save(mark_page=False)
|
||||
@@ -92,7 +111,7 @@ class BaseExtension(models.Model):
|
||||
|
||||
|
||||
class PageExtension(BaseExtension):
|
||||
extended_object = models.OneToOneField(Page, editable=False)
|
||||
extended_object = models.OneToOneField(Page, on_delete=models.CASCADE, editable=False)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@@ -112,7 +131,7 @@ class PageExtension(BaseExtension):
|
||||
|
||||
|
||||
class TitleExtension(BaseExtension):
|
||||
extended_object = models.OneToOneField(Title, editable=False)
|
||||
extended_object = models.OneToOneField(Title, on_delete=models.CASCADE, editable=False)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -2,49 +2,13 @@
|
||||
from cms.utils.urlutils import admin_reverse
|
||||
from cms.api import get_page_draft
|
||||
from cms.toolbar_base import CMSToolbar
|
||||
from cms.utils import get_cms_setting, get_language_list
|
||||
from cms.utils.permissions import has_page_change_permission
|
||||
from django.core.urlresolvers import NoReverseMatch
|
||||
from cms.utils import get_language_list
|
||||
from cms.utils.page_permissions import user_can_change_page
|
||||
|
||||
from django.urls import NoReverseMatch
|
||||
|
||||
|
||||
class ExtensionToolbar(CMSToolbar):
|
||||
"""
|
||||
ExtensionToolbar provides utility functions to handle much of the boilerplate involved in creating a toolbar for
|
||||
PageExtension and TitleExtension.
|
||||
|
||||
The basic implementation of an extension toolbar using this class is::
|
||||
|
||||
@toolbar_pool.register
|
||||
class SampleExtension(ExtensionToolbar):
|
||||
model = ExtModel # The PageExtension / TitleExtension you are working with
|
||||
|
||||
def populate(self):
|
||||
current_page_menu = self._setup_extension_toolbar()
|
||||
if current_page_menu:
|
||||
position = 0
|
||||
page_extension, url = self.get_page_extension_admin()
|
||||
if url:
|
||||
current_page_menu.add_modal_item('Item label', url=url,
|
||||
disabled=not self.toolbar.edit_mode,
|
||||
position=position)
|
||||
|
||||
For TitleExtension use ``get_title_extension_admin`` and cycle on the resulting title extensions and urls
|
||||
|
||||
@toolbar_pool.register
|
||||
class SampleExtension(ExtensionToolbar):
|
||||
model = ExtModel # The PageExtension / TitleExtension you are working with
|
||||
|
||||
def populate(self):
|
||||
current_page_menu = self._setup_extension_toolbar()
|
||||
if current_page_menu:
|
||||
position = 0
|
||||
urls = self.get_title_extension_admin()
|
||||
for title_extension, url in urls:
|
||||
current_page_menu.add_modal_item('Item label', url=url,
|
||||
disabled=not self.toolbar.edit_mode,
|
||||
position=position)
|
||||
|
||||
"""
|
||||
model = None
|
||||
page = None
|
||||
|
||||
@@ -58,22 +22,10 @@ class ExtensionToolbar(CMSToolbar):
|
||||
It returns the page menu or None if the above conditions are not met
|
||||
"""
|
||||
page = self._get_page()
|
||||
if not page:
|
||||
# Nothing to do
|
||||
return
|
||||
# check global permissions if CMS_PERMISSION is active
|
||||
if get_cms_setting('PERMISSION'):
|
||||
has_global_current_page_change_permission = has_page_change_permission(self.request)
|
||||
else:
|
||||
has_global_current_page_change_permission = True
|
||||
# check if user has page edit permission
|
||||
can_change = (self.request.current_page and
|
||||
self.request.current_page.has_change_permission(self.request))
|
||||
current_page_menu = self.toolbar.get_or_create_menu('page')
|
||||
if can_change and has_global_current_page_change_permission:
|
||||
return current_page_menu
|
||||
else:
|
||||
return
|
||||
|
||||
if page and user_can_change_page(self.request.user, page=page):
|
||||
return self.toolbar.get_or_create_menu('page')
|
||||
return
|
||||
|
||||
def _get_page(self):
|
||||
"""
|
||||
@@ -126,7 +78,7 @@ class ExtensionToolbar(CMSToolbar):
|
||||
if language:
|
||||
titles = page.get_title_obj(language),
|
||||
else:
|
||||
titles = page.title_set.filter(language__in=get_language_list(page.site_id))
|
||||
titles = page.title_set.filter(language__in=get_language_list(page.node.site_id))
|
||||
# Titles
|
||||
for title in titles:
|
||||
try:
|
||||
|
||||
+9
-6
@@ -5,9 +5,9 @@ from django.forms.fields import EMPTY_VALUES
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from cms.forms.utils import get_site_choices, get_page_choices
|
||||
from cms.forms.validators import validate_url
|
||||
from cms.forms.widgets import PageSelectWidget, PageSmartLinkWidget
|
||||
from cms.models.pagemodel import Page
|
||||
from cms.utils.compat import DJANGO_1_7
|
||||
|
||||
|
||||
class SuperLazyIterator(object):
|
||||
@@ -71,25 +71,28 @@ class PageSelectFormField(forms.MultiValueField):
|
||||
# this will cause django to always return True because of the '1'
|
||||
# so we simply follow django's default behavior when initial is None and data is "empty"
|
||||
data = ['' for x in range(0, len(data))]
|
||||
if DJANGO_1_7:
|
||||
return super(PageSelectFormField, self)._has_changed(initial, data)
|
||||
else:
|
||||
return super(PageSelectFormField, self).has_changed(initial, data)
|
||||
return super(PageSelectFormField, self).has_changed(initial, data)
|
||||
|
||||
def _has_changed(self, initial, data):
|
||||
return self.has_changed(initial, data)
|
||||
|
||||
|
||||
class PageSmartLinkField(forms.CharField):
|
||||
widget = PageSmartLinkWidget
|
||||
default_validators = [validate_url]
|
||||
|
||||
def __init__(self, max_length=None, min_length=None, placeholder_text=None,
|
||||
ajax_view=None, *args, **kwargs):
|
||||
self.placeholder_text = placeholder_text
|
||||
widget = self.widget(ajax_view=ajax_view)
|
||||
super(PageSmartLinkField, self).__init__(max_length, min_length,
|
||||
super(PageSmartLinkField, self).__init__(max_length=max_length, min_length=min_length,
|
||||
widget=widget, *args, **kwargs)
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
attrs = super(PageSmartLinkField, self).widget_attrs(widget)
|
||||
attrs.update({'placeholder_text': self.placeholder_text})
|
||||
return attrs
|
||||
|
||||
def clean(self, value):
|
||||
value = self.to_python(value).strip()
|
||||
return super(PageSmartLinkField, self).clean(value)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
|
||||
|
||||
class CMSToolbarLoginForm(AuthenticationForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CMSToolbarLoginForm, self).__init__(*args, **kwargs)
|
||||
kwargs['prefix'] = kwargs.get('prefix', 'cms')
|
||||
self.fields['username'].widget = forms.TextInput(
|
||||
attrs = { 'required': 'required' })
|
||||
self.fields['password'].widget = forms.PasswordInput(
|
||||
attrs = { 'required': 'required' })
|
||||
+64
-49
@@ -1,67 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from collections import defaultdict
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from django.utils.datastructures import SortedDict as OrderedDict
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db.models import Prefetch
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from cms.cache.choices import (clean_site_choices_cache, clean_page_choices_cache,
|
||||
_site_cache_key, _page_cache_key)
|
||||
from cms.exceptions import LanguageError
|
||||
from cms.cache.choices import (
|
||||
clean_site_choices_cache, clean_page_choices_cache,
|
||||
_site_cache_key, _page_cache_key)
|
||||
from cms.models import Page, Title
|
||||
from cms.utils import i18n
|
||||
|
||||
|
||||
def update_site_and_page_choices(lang=None):
|
||||
lang = lang or i18n.get_current_language()
|
||||
SITE_CHOICES_KEY = _site_cache_key(lang)
|
||||
PAGE_CHOICES_KEY = _page_cache_key(lang)
|
||||
title_queryset = (Title.objects.drafts()
|
||||
.select_related('page', 'page__site')
|
||||
.order_by('page__path'))
|
||||
pages = defaultdict(OrderedDict)
|
||||
sites = {}
|
||||
for title in title_queryset:
|
||||
page = pages[title.page.site.pk].get(title.page.pk, {})
|
||||
page[title.language] = title
|
||||
pages[title.page.site.pk][title.page.pk] = page
|
||||
sites[title.page.site.pk] = title.page.site.name
|
||||
def get_sites():
|
||||
sites = (
|
||||
Site
|
||||
.objects
|
||||
.filter(djangocms_nodes__isnull=False)
|
||||
.order_by('name')
|
||||
.distinct()
|
||||
)
|
||||
return sites
|
||||
|
||||
|
||||
def get_page_choices_for_site(site, language):
|
||||
fallbacks = i18n.get_fallback_languages(language, site_id=site.pk)
|
||||
languages = [language] + fallbacks
|
||||
translation_lookup = Prefetch(
|
||||
'title_set',
|
||||
to_attr='filtered_translations',
|
||||
queryset=Title.objects.filter(language__in=languages).only('pk', 'page', 'language', 'title')
|
||||
)
|
||||
pages = (
|
||||
Page
|
||||
.objects
|
||||
.drafts()
|
||||
.on_site(site)
|
||||
.select_related('node')
|
||||
.prefetch_related(translation_lookup)
|
||||
.order_by('node__path')
|
||||
.only('pk', 'node')
|
||||
)
|
||||
|
||||
for page in pages:
|
||||
translations = page.filtered_translations
|
||||
titles_by_language = {trans.language: trans.title for trans in translations}
|
||||
|
||||
for language in languages:
|
||||
# EmptyTitle is used to prevent the cms from trying
|
||||
# to find a translation in the database
|
||||
if language in titles_by_language:
|
||||
title = titles_by_language[language]
|
||||
indent = " " * (page.node.depth - 1)
|
||||
label = mark_safe("%s%s" % (indent, escape(title)))
|
||||
yield (page.pk, label)
|
||||
break
|
||||
|
||||
|
||||
def update_site_and_page_choices(language=None):
|
||||
if language is None:
|
||||
language = i18n.get_current_language()
|
||||
|
||||
site_choices = []
|
||||
page_choices = [('', '----')]
|
||||
site_choices_key = _site_cache_key(language)
|
||||
page_choices_key = _page_cache_key(language)
|
||||
|
||||
try:
|
||||
fallbacks = i18n.get_fallback_languages(lang)
|
||||
except LanguageError:
|
||||
fallbacks = []
|
||||
language_order = [lang] + fallbacks
|
||||
for site in get_sites():
|
||||
_page_choices = list(get_page_choices_for_site(site, language))
|
||||
site_choices.append((site.pk, site.name))
|
||||
page_choices.append((site.name, _page_choices))
|
||||
|
||||
for sitepk, sitename in sites.items():
|
||||
site_choices.append((sitepk, sitename))
|
||||
|
||||
site_page_choices = []
|
||||
for titles in pages[sitepk].values():
|
||||
title = None
|
||||
for language in language_order:
|
||||
title = titles.get(language)
|
||||
if title:
|
||||
break
|
||||
if not title:
|
||||
continue
|
||||
|
||||
indent = u" " * (title.page.depth - 1)
|
||||
page_title = mark_safe(u"%s%s" % (indent, title.title))
|
||||
site_page_choices.append((title.page.pk, page_title))
|
||||
|
||||
page_choices.append((sitename, site_page_choices))
|
||||
from django.core.cache import cache
|
||||
# We set it to 1 day here because we actively invalidate this cache.
|
||||
cache.set(SITE_CHOICES_KEY, site_choices, 86400)
|
||||
cache.set(PAGE_CHOICES_KEY, page_choices, 86400)
|
||||
cache.set(site_choices_key, site_choices, 86400)
|
||||
cache.set(page_choices_key, page_choices, 86400)
|
||||
return site_choices, page_choices
|
||||
|
||||
|
||||
@@ -70,7 +85,7 @@ def get_site_choices(lang=None):
|
||||
lang = lang or i18n.get_current_language()
|
||||
site_choices = cache.get(_site_cache_key(lang))
|
||||
if site_choices is None:
|
||||
site_choices, page_choices = update_site_and_page_choices(lang)
|
||||
site_choices = update_site_and_page_choices(lang)[0]
|
||||
return site_choices
|
||||
|
||||
|
||||
@@ -79,7 +94,7 @@ def get_page_choices(lang=None):
|
||||
lang = lang or i18n.get_current_language()
|
||||
page_choices = cache.get(_page_cache_key(lang))
|
||||
if page_choices is None:
|
||||
site_choices, page_choices = update_site_and_page_choices(lang)
|
||||
page_choices = update_site_and_page_choices(lang)[1]
|
||||
return page_choices
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import RegexValidator, URLValidator
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext
|
||||
|
||||
from cms.utils.page import get_all_pages_from_path
|
||||
from cms.utils.urlutils import admin_reverse, relative_url_regex
|
||||
|
||||
|
||||
def validate_relative_url(value):
|
||||
RegexValidator(regex=relative_url_regex)(value)
|
||||
|
||||
|
||||
def validate_url(value):
|
||||
try:
|
||||
# Validate relative urls first
|
||||
validate_relative_url(value)
|
||||
except ValidationError:
|
||||
# Fallback to absolute urls
|
||||
URLValidator()(value)
|
||||
|
||||
|
||||
def validate_url_uniqueness(site, path, language, exclude_page=None):
|
||||
""" Checks for conflicting urls
|
||||
"""
|
||||
if '/' in path:
|
||||
validate_url(path)
|
||||
|
||||
path = path.strip('/')
|
||||
pages = get_all_pages_from_path(site, path, language)
|
||||
pages = pages.select_related('publisher_public')
|
||||
|
||||
if exclude_page:
|
||||
pages = pages.exclude(pk=exclude_page.pk)
|
||||
|
||||
if exclude_page.publisher_public_id:
|
||||
pages = pages.exclude(pk=exclude_page.publisher_public_id)
|
||||
|
||||
try:
|
||||
conflict_page = pages[0]
|
||||
except IndexError:
|
||||
return True
|
||||
|
||||
if conflict_page.publisher_is_draft:
|
||||
page_id = conflict_page.pk
|
||||
else:
|
||||
# rare case where draft points to one url
|
||||
# and live points to another which conflicts.
|
||||
# Use the draft ID because public page is not editable.
|
||||
page_id = conflict_page.publisher_public_id
|
||||
|
||||
if conflict_page.is_page_type:
|
||||
change_url = admin_reverse('cms_pagetype_change', args=[page_id])
|
||||
else:
|
||||
change_url = admin_reverse('cms_page_change', args=[page_id])
|
||||
|
||||
conflict_url = '<a href="%(change_url)s" target="_blank">%(page_title)s</a>' % {
|
||||
'change_url': change_url,
|
||||
'page_title': force_text(conflict_page),
|
||||
}
|
||||
|
||||
if exclude_page:
|
||||
message = ugettext('Page %(conflict_page)s has the same url \'%(url)s\' as current page "%(instance)s".')
|
||||
else:
|
||||
message = ugettext('Page %(conflict_page)s has the same url \'%(url)s\' as current page.')
|
||||
message = message % {'conflict_page': conflict_url, 'url': path, 'instance': exclude_page}
|
||||
raise ValidationError(mark_safe(message))
|
||||
+85
-105
@@ -1,17 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from django.contrib.admin.templatetags.admin_static import static
|
||||
from django.contrib.auth import get_permission_codename
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.urlresolvers import NoReverseMatch, reverse_lazy
|
||||
from django.forms.widgets import Select, MultiWidget, TextInput
|
||||
from django.forms.widgets import MultiWidget, Select, TextInput
|
||||
from django.urls import NoReverseMatch, reverse_lazy
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import escape, escapejs
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from cms.utils.urlutils import admin_reverse
|
||||
from cms.utils.urlutils import admin_reverse, static_with_version
|
||||
from cms.forms.utils import get_site_choices, get_page_choices
|
||||
from cms.models import Page, PageUser
|
||||
|
||||
@@ -20,13 +16,11 @@ class PageSelectWidget(MultiWidget):
|
||||
"""A widget that allows selecting a page by first selecting a site and then
|
||||
a page on that site in a two step process.
|
||||
"""
|
||||
template_name = 'cms/widgets/pageselectwidget.html'
|
||||
|
||||
class Media:
|
||||
js = (
|
||||
'cms/js/modules/jquery.noconflict.pre.js',
|
||||
'cms/js/dist/bundle.admin.base.min.js',
|
||||
'cms/js/widgets/forms.pageselectwidget.js',
|
||||
'cms/js/modules/jquery.noconflict.post.js'
|
||||
static_with_version('cms/js/dist/bundle.forms.pageselectwidget.min.js'),
|
||||
)
|
||||
|
||||
def __init__(self, site_choices=None, page_choices=None, attrs=None):
|
||||
@@ -43,9 +37,8 @@ class PageSelectWidget(MultiWidget):
|
||||
of that page or the current site_id and None if no page_id is given.
|
||||
"""
|
||||
if value:
|
||||
page = Page.objects.get(pk=value)
|
||||
site = page.site
|
||||
return [site.pk, page.pk, page.pk]
|
||||
page = Page.objects.select_related('node').get(pk=value)
|
||||
return [page.node.site_id, page.pk, page.pk]
|
||||
site = Site.objects.get_current()
|
||||
return [site.pk,None,None]
|
||||
|
||||
@@ -71,13 +64,7 @@ class PageSelectWidget(MultiWidget):
|
||||
return True
|
||||
return False
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
# THIS IS A COPY OF django.forms.widgets.MultiWidget.render()
|
||||
# (except for the last line)
|
||||
|
||||
# value is a list of values, each corresponding to a widget
|
||||
# in self.widgets.
|
||||
|
||||
def _build_widgets(self):
|
||||
site_choices = get_site_choices()
|
||||
page_choices = get_page_choices()
|
||||
self.site_choices = site_choices
|
||||
@@ -87,35 +74,31 @@ class PageSelectWidget(MultiWidget):
|
||||
Select(choices=self.choices, attrs={'style': "display:none;"} ),
|
||||
)
|
||||
|
||||
if not isinstance(value, list):
|
||||
value = self.decompress(value)
|
||||
output = []
|
||||
final_attrs = self.build_attrs(attrs)
|
||||
id_ = final_attrs.get('id', None)
|
||||
for i, widget in enumerate(self.widgets):
|
||||
try:
|
||||
widget_value = value[i]
|
||||
except IndexError:
|
||||
widget_value = None
|
||||
if id_:
|
||||
final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
|
||||
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs))
|
||||
output.append(r'''<script type="text/javascript">
|
||||
CMS.$(function () {
|
||||
new CMS.PageSelectWidget({
|
||||
def _build_script(self, name, value, attrs={}):
|
||||
return r"""<script type="text/javascript">
|
||||
var CMS = window.CMS || {};
|
||||
|
||||
CMS.Widgets = CMS.Widgets || {};
|
||||
CMS.Widgets._pageSelectWidgets = CMS.Widgets._pageSelectWidgets || [];
|
||||
CMS.Widgets._pageSelectWidgets.push({
|
||||
name: '%(name)s'
|
||||
});
|
||||
});
|
||||
</script>''' % {
|
||||
'name': name
|
||||
})
|
||||
return mark_safe(self.format_output(output))
|
||||
</script>""" % {
|
||||
'name': name
|
||||
}
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
self._build_widgets()
|
||||
context = super(PageSelectWidget, self).get_context(name, value, attrs)
|
||||
context['widget']['script_init'] = self._build_script(name, value, context['widget']['attrs'])
|
||||
return context
|
||||
|
||||
def format_output(self, rendered_widgets):
|
||||
return u' '.join(rendered_widgets)
|
||||
|
||||
|
||||
class PageSmartLinkWidget(TextInput):
|
||||
template_name = 'cms/widgets/pagesmartlinkwidget.html'
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
@@ -125,9 +108,7 @@ class PageSmartLinkWidget(TextInput):
|
||||
)
|
||||
}
|
||||
js = (
|
||||
'cms/js/dist/bundle.admin.base.min.js',
|
||||
'cms/js/select2/select2.js',
|
||||
'cms/js/widgets/forms.pagesmartlinkwidget.js',
|
||||
static_with_version('cms/js/dist/bundle.forms.pagesmartlinkwidget.min.js'),
|
||||
)
|
||||
|
||||
def __init__(self, attrs=None, ajax_view=None):
|
||||
@@ -142,28 +123,29 @@ class PageSmartLinkWidget(TextInput):
|
||||
'You should provide an ajax_view argument that can be reversed to the PageSmartLinkWidget'
|
||||
)
|
||||
|
||||
def render(self, name=None, value=None, attrs=None):
|
||||
final_attrs = self.build_attrs(attrs)
|
||||
id_ = final_attrs.get('id', None)
|
||||
def _build_script(self, name, value, attrs={}):
|
||||
return r"""<script type="text/javascript">
|
||||
var CMS = window.CMS || {};
|
||||
|
||||
output = [r'''<script type="text/javascript">
|
||||
CMS.$(function () {
|
||||
new CMS.PageSmartLinkWidget({
|
||||
id: '%(element_id)s',
|
||||
text: '%(placeholder_text)s',
|
||||
lang: '%(language_code)s',
|
||||
url: '%(ajax_url)s'
|
||||
});
|
||||
CMS.Widgets = CMS.Widgets || {};
|
||||
CMS.Widgets._pageSmartLinkWidgets = CMS.Widgets._pageSmartLinkWidgets || [];
|
||||
CMS.Widgets._pageSmartLinkWidgets.push({
|
||||
id: '%(element_id)s',
|
||||
text: '%(placeholder_text)s',
|
||||
lang: '%(language_code)s',
|
||||
url: '%(ajax_url)s'
|
||||
});
|
||||
</script>''' % {
|
||||
'element_id': id_,
|
||||
'placeholder_text': final_attrs.get('placeholder_text', ''),
|
||||
</script>""" % {
|
||||
'element_id': attrs.get('id', ''),
|
||||
'placeholder_text': attrs.get('placeholder_text', ''),
|
||||
'language_code': self.language,
|
||||
'ajax_url': force_text(self.ajax_url)
|
||||
}]
|
||||
}
|
||||
|
||||
output.append(super(PageSmartLinkWidget, self).render(name, value, attrs))
|
||||
return mark_safe(u''.join(output))
|
||||
def get_context(self, name, value, attrs):
|
||||
context = super(PageSmartLinkWidget, self).get_context(name, value, attrs)
|
||||
context['widget']['script_init'] = self._build_script(name, value, context['widget']['attrs'])
|
||||
return context
|
||||
|
||||
|
||||
class UserSelectAdminWidget(Select):
|
||||
@@ -174,8 +156,8 @@ class UserSelectAdminWidget(Select):
|
||||
Current user should be assigned to widget in form constructor as an user
|
||||
attribute.
|
||||
"""
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
output = [super(UserSelectAdminWidget, self).render(name, value, attrs, choices)]
|
||||
def render(self, name, value, attrs=None, choices=(), renderer=None):
|
||||
output = [super(UserSelectAdminWidget, self).render(name, value, attrs, renderer=renderer)]
|
||||
if hasattr(self, 'user') and (self.user.is_superuser or \
|
||||
self.user.has_perm(PageUser._meta.app_label + '.' + get_permission_codename('add', PageUser._meta))):
|
||||
# append + icon
|
||||
@@ -194,17 +176,20 @@ class AppHookSelect(Select):
|
||||
|
||||
class Media:
|
||||
js = (
|
||||
'cms/js/modules/jquery.noconflict.pre.js',
|
||||
'cms/js/dist/bundle.admin.base.min.js',
|
||||
'cms/js/widgets/forms.apphookselect.js',
|
||||
'cms/js/modules/jquery.noconflict.post.js'
|
||||
static_with_version('cms/js/dist/bundle.forms.apphookselect.min.js'),
|
||||
)
|
||||
|
||||
def __init__(self, attrs=None, choices=(), app_namespaces={}):
|
||||
self.app_namespaces = app_namespaces
|
||||
super(AppHookSelect, self).__init__(attrs, choices)
|
||||
|
||||
def render_option(self, selected_choices, option_value, option_label):
|
||||
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
|
||||
option = super(AppHookSelect, self).create_option(name, value, label, selected, index, subindex, attrs)
|
||||
if value in self.app_namespaces:
|
||||
option['attrs']['data-namespace'] = escape(self.app_namespaces[value])
|
||||
return option
|
||||
|
||||
def _build_option(self, selected_choices, option_value, option_label):
|
||||
if option_value is None:
|
||||
option_value = ''
|
||||
option_value = force_text(option_value)
|
||||
@@ -217,23 +202,14 @@ class AppHookSelect(Select):
|
||||
selected_html = ''
|
||||
|
||||
if option_value in self.app_namespaces:
|
||||
data_html = mark_safe(' data-namespace="%s"' % self.app_namespaces[option_value])
|
||||
data_html = mark_safe(' data-namespace="%s"' % escape(self.app_namespaces[option_value]))
|
||||
else:
|
||||
data_html = ''
|
||||
return option_value, selected_html, data_html, force_text(option_label)
|
||||
|
||||
return '<option value="%s"%s%s>%s</option>' % (
|
||||
option_value,
|
||||
selected_html,
|
||||
data_html,
|
||||
force_text(option_label),
|
||||
)
|
||||
|
||||
def render_options(self, choices, selected_choices):
|
||||
selected_choices = set(force_text(v) for v in selected_choices)
|
||||
output = []
|
||||
for option_value, option_label in chain(self.choices, choices):
|
||||
output.append(self.render_option(selected_choices, option_value, option_label))
|
||||
return '\n'.join(output)
|
||||
def render_option(self, selected_choices, option_value, option_label):
|
||||
option_data = self._build_option(selected_choices, option_value, option_label)
|
||||
return '<option value="%s"%s%s>%s</option>' % option_data
|
||||
|
||||
|
||||
class ApplicationConfigSelect(Select):
|
||||
@@ -247,36 +223,40 @@ class ApplicationConfigSelect(Select):
|
||||
A stub 'add-another' link is created and filled in with the correct URL by the same
|
||||
javascript.
|
||||
"""
|
||||
template_name = 'cms/widgets/applicationconfigselect.html'
|
||||
|
||||
class Media:
|
||||
js = (
|
||||
'cms/js/modules/jquery.noconflict.pre.js',
|
||||
'cms/js/dist/bundle.admin.base.min.js',
|
||||
'cms/js/widgets/forms.apphookselect.js',
|
||||
'cms/js/modules/jquery.noconflict.post.js'
|
||||
static_with_version('cms/js/dist/bundle.forms.apphookselect.min.js'),
|
||||
)
|
||||
|
||||
def __init__(self, attrs=None, choices=(), app_configs={}):
|
||||
self.app_configs = app_configs
|
||||
super(ApplicationConfigSelect, self).__init__(attrs, choices)
|
||||
|
||||
def render(self, name, value, attrs=None, choices=()):
|
||||
output = [super(ApplicationConfigSelect, self).render(name, value, attrs, choices)]
|
||||
output.append('<script>\n')
|
||||
output.append('var apphooks_configuration = {\n')
|
||||
def _build_script(self, name, value, attrs={}):
|
||||
configs = []
|
||||
urls = []
|
||||
for application, cms_app in self.app_configs.items():
|
||||
output.append("'%s': [%s]," % (application, ",".join(["['%s', '%s']" % (config.pk, force_text(config)) for config in cms_app.get_configs()])))
|
||||
output.append('\n};\n')
|
||||
output.append('var apphooks_configuration_url = {\n')
|
||||
configs.append("'%s': [%s]" % (application, ",".join(
|
||||
["['%s', '%s']" % (config.pk, escapejs(escape(config))) for config in cms_app.get_configs()]))) # noqa
|
||||
for application, cms_app in self.app_configs.items():
|
||||
output.append("'%s': '%s'," % (application, cms_app.get_config_add_url()))
|
||||
output.append('\n};\n')
|
||||
output.append('var apphooks_configuration_value = \'%s\';\n' % value)
|
||||
output.append('</script>')
|
||||
urls.append("'%s': '%s'" % (application, cms_app.get_config_add_url()))
|
||||
return r"""<script type="text/javascript">
|
||||
var apphooks_configuration = {
|
||||
%(apphooks_configurations)s
|
||||
};
|
||||
var apphooks_configuration_url = {
|
||||
%(apphooks_url)s
|
||||
};
|
||||
var apphooks_configuration_value = '%(apphooks_value)s';
|
||||
</script>""" % {
|
||||
'apphooks_configurations': ','.join(configs),
|
||||
'apphooks_url': ','.join(urls),
|
||||
'apphooks_value': value,
|
||||
}
|
||||
|
||||
related_url = ''
|
||||
output.append('<a href="%s" class="add-another" id="add_%s" onclick="return showAddAnotherPopup(this);"> '
|
||||
% (related_url, name))
|
||||
output.append('<img src="%s" width="10" height="10" alt="%s"/></a>'
|
||||
% (static('admin/img/icon_addlink.gif'), _('Add Another')))
|
||||
return mark_safe(''.join(output))
|
||||
def get_context(self, name, value, attrs):
|
||||
context = super(ApplicationConfigSelect, self).get_context(name, value, attrs)
|
||||
context['widget']['script_init'] = self._build_script(name, value, context['widget']['attrs'])
|
||||
return context
|
||||
|
||||
+129
-231
@@ -3,25 +3,24 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.encoding import smart_text
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import (
|
||||
ugettext,
|
||||
ugettext_lazy as _,
|
||||
get_language,
|
||||
)
|
||||
|
||||
from cms.api import generate_valid_slug
|
||||
from cms.constants import PAGE_TYPES_ID
|
||||
from cms.exceptions import NoPermissionsException
|
||||
from cms.models import Page, Title
|
||||
from cms.models.titlemodels import EmptyTitle
|
||||
from cms.admin.forms import AddPageForm
|
||||
from cms.plugin_pool import plugin_pool
|
||||
from cms.utils import permissions
|
||||
from cms.utils.compat.dj import is_installed
|
||||
from cms.utils.urlutils import static_with_version
|
||||
from cms.utils import get_current_site, permissions
|
||||
from cms.utils.page import get_available_slug
|
||||
from cms.utils.page_permissions import (
|
||||
user_can_add_page,
|
||||
user_can_add_subpage,
|
||||
)
|
||||
from cms.utils.conf import get_cms_setting
|
||||
from cms.utils.urlutils import static_with_version
|
||||
|
||||
try:
|
||||
# djangocms_text_ckeditor is not guaranteed to be available
|
||||
@@ -31,142 +30,66 @@ except ImportError:
|
||||
text_widget = forms.Textarea
|
||||
|
||||
|
||||
def user_has_view_permission(user, page=None):
|
||||
class SlugWidget(forms.widgets.TextInput):
|
||||
"""
|
||||
This code largely duplicates Page.has_view_permission(). We do this because
|
||||
the source method requires a request object, which isn't appropriate in
|
||||
this case. Fortunately, the source method (and its dependencies) use the
|
||||
request object only to get the user object, when it isn't explicitly
|
||||
provided and for caching permissions. We don't require caching here and we
|
||||
can explicitly provide the user object.
|
||||
"""
|
||||
if not user:
|
||||
return False
|
||||
|
||||
class FakeRequest(object):
|
||||
pass
|
||||
fake_request = FakeRequest()
|
||||
|
||||
can_see_unrestricted = get_cms_setting('PUBLIC_FOR') == 'all' or (
|
||||
get_cms_setting('PUBLIC_FOR') == 'staff' and user.is_staff)
|
||||
|
||||
# Inherited and direct view permissions
|
||||
is_restricted = bool(
|
||||
permissions.get_any_page_view_permissions(fake_request, page))
|
||||
|
||||
if not is_restricted and can_see_unrestricted:
|
||||
return True
|
||||
elif not user.is_authenticated():
|
||||
return False
|
||||
|
||||
if not is_restricted:
|
||||
# a global permission was given to the request's user
|
||||
if permissions.has_global_page_permission(
|
||||
fake_request, page.site_id, user=user, can_view=True):
|
||||
return True
|
||||
else:
|
||||
# a specific permission was granted to the request's user
|
||||
if page.get_draft_object().has_generic_permission(
|
||||
fake_request, "view", user=user):
|
||||
return True
|
||||
|
||||
# The user has a normal django permission to view pages globally
|
||||
opts = page._meta
|
||||
codename = '%s.view_%s' % (opts.app_label, opts.object_name.lower())
|
||||
return user.has_perm(codename)
|
||||
|
||||
|
||||
class PageTypeSelect(forms.widgets.Select):
|
||||
"""
|
||||
Special widget for the page_type choice-field. This simply adds some JS for
|
||||
hiding/showing the content field based on the selection of this select.
|
||||
Special widget for the slug field that requires Title field to be there.
|
||||
Adds the js for the slugifying.
|
||||
"""
|
||||
class Media:
|
||||
js = (
|
||||
'cms/js/modules/jquery.noconflict.pre.js',
|
||||
'cms/js/dist/bundle.admin.base.min.js',
|
||||
'cms/js/modules/cms.base.js',
|
||||
'cms/js/widgets/wizard.pagetypeselect.js',
|
||||
'cms/js/modules/jquery.noconflict.post.js',
|
||||
'admin/js/urlify.js',
|
||||
static_with_version('cms/js/dist/bundle.forms.slugwidget.min.js'),
|
||||
)
|
||||
|
||||
js = tuple(map(static_with_version, js))
|
||||
|
||||
class CreateCMSPageForm(AddPageForm):
|
||||
page = None
|
||||
sub_page_form = False
|
||||
|
||||
# Field overrides
|
||||
menu_title = None
|
||||
page_title = None
|
||||
meta_description = None
|
||||
|
||||
class BaseCMSPageForm(forms.Form):
|
||||
title = forms.CharField(
|
||||
label=_(u'Title'), max_length=255,
|
||||
help_text=_(u"Provide a title for the new page."))
|
||||
slug = forms.SlugField(
|
||||
label=_(u'Slug'), max_length=255, required=False,
|
||||
help_text=_(u"Leave empty for automatic slug, or override as required.")
|
||||
)
|
||||
page_type = forms.ChoiceField(
|
||||
label=_(u'Page type'), required=False, widget=PageTypeSelect())
|
||||
content = forms.CharField(
|
||||
label=_(u'Content'), widget=text_widget, required=False,
|
||||
help_text=_(u"Optional. If supplied, will be automatically added "
|
||||
u"within a new text plugin."))
|
||||
u"within a new text plugin.")
|
||||
)
|
||||
|
||||
def __init__(self, instance=None, *args, **kwargs):
|
||||
# Expect instance argument here, as we have to accept some of the
|
||||
# ModelForm __init__() arguments here for the ModelFormMixin cbv
|
||||
self.instance = instance
|
||||
super(BaseCMSPageForm, self).__init__(*args, **kwargs)
|
||||
class Media:
|
||||
js = (
|
||||
# This simply adds some JS for
|
||||
# hiding/showing the content field based on the selection of this select.
|
||||
'cms/js/widgets/wizard.pagetypeselect.js',
|
||||
)
|
||||
|
||||
if self.page:
|
||||
site = self.page.site_id
|
||||
else:
|
||||
site = Site.objects.get_current()
|
||||
|
||||
# Either populate, or remove the page_type field
|
||||
if 'page_type' in self.fields:
|
||||
root = Page.objects.filter(publisher_is_draft=True,
|
||||
reverse_id=PAGE_TYPES_ID,
|
||||
site=site).first()
|
||||
if root:
|
||||
page_types = root.get_descendants()
|
||||
else:
|
||||
page_types = Page.objects.none()
|
||||
|
||||
if root and page_types:
|
||||
# Set the choicefield's choices to the various page_types
|
||||
language = get_language()
|
||||
type_ids = page_types.values_list('pk', flat=True)
|
||||
titles = Title.objects.filter(page__in=type_ids,
|
||||
language=language)
|
||||
choices = [('', '---------')]
|
||||
for title in titles:
|
||||
choices.append((title.page_id, title.title))
|
||||
self.fields['page_type'].choices = choices
|
||||
else:
|
||||
# There are no page_types, so don't bother the user with an
|
||||
# empty choice field.
|
||||
del self.fields['page_type']
|
||||
|
||||
|
||||
class CreateCMSPageForm(BaseCMSPageForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._site = get_current_site()
|
||||
self._user = self.user
|
||||
self._language = self.language_code
|
||||
super(CreateCMSPageForm, self).__init__(*args, **kwargs)
|
||||
self.fields['title'].help_text = _(u"Provide a title for the new page.")
|
||||
self.fields['slug'].required = False
|
||||
self.fields['slug'].widget = SlugWidget()
|
||||
self.fields['slug'].help_text = _(u"Leave empty for automatic slug, or override as required.")
|
||||
|
||||
@staticmethod
|
||||
def create_page_titles(page, title, languages):
|
||||
# Import here due to potential circular dependency issues
|
||||
from cms.api import create_title
|
||||
|
||||
for language in languages:
|
||||
title_obj = page.get_title_obj(language=language, fallback=False)
|
||||
if isinstance(title_obj, EmptyTitle):
|
||||
create_title(language, title, page)
|
||||
|
||||
@staticmethod
|
||||
def get_first_placeholder(page):
|
||||
def get_placeholder(page, slot=None):
|
||||
"""
|
||||
Returns the first editable, non-static placeholder or None.
|
||||
Returns the named placeholder or, if no «slot» provided, the first
|
||||
editable, non-static placeholder or None.
|
||||
"""
|
||||
for placeholder in page.get_placeholders():
|
||||
if not placeholder.is_static and placeholder.is_editable:
|
||||
return placeholder
|
||||
else:
|
||||
return None
|
||||
placeholders = page.get_placeholders()
|
||||
|
||||
if slot:
|
||||
placeholders = placeholders.filter(slot=slot)
|
||||
|
||||
for ph in placeholders:
|
||||
if not ph.is_static and ph.is_editable:
|
||||
return ph
|
||||
|
||||
return None
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
@@ -174,129 +97,104 @@ class CreateCMSPageForm(BaseCMSPageForm):
|
||||
`title` produces a valid slug.
|
||||
:return:
|
||||
"""
|
||||
cleaned_data = super(CreateCMSPageForm, self).clean()
|
||||
data = self.cleaned_data
|
||||
|
||||
slug = cleaned_data.get("slug")
|
||||
sub_page = cleaned_data.get("sub_page")
|
||||
title = cleaned_data.get("title")
|
||||
if self._errors:
|
||||
return data
|
||||
|
||||
if self.page:
|
||||
if sub_page:
|
||||
parent = self.page
|
||||
else:
|
||||
parent = self.page.parent
|
||||
slug = data.get('slug') or slugify(data['title'])
|
||||
|
||||
parent_node = data.get('parent_node')
|
||||
|
||||
if parent_node:
|
||||
base = parent_node.item.get_path(self._language)
|
||||
path = u'%s/%s' % (base, slug) if base else slug
|
||||
else:
|
||||
parent = None
|
||||
base = ''
|
||||
path = slug
|
||||
|
||||
if slug:
|
||||
starting_point = slug
|
||||
elif title:
|
||||
starting_point = title
|
||||
else:
|
||||
starting_point = _("page")
|
||||
slug = generate_valid_slug(starting_point, parent, self.language_code)
|
||||
if not slug:
|
||||
data['slug'] = get_available_slug(self._site, path, self._language, suffix=None)
|
||||
data['path'] = '%s/%s' % (base, data['slug']) if base else data['slug']
|
||||
|
||||
if not data['slug']:
|
||||
raise forms.ValidationError("Please provide a valid slug.")
|
||||
cleaned_data["slug"] = slug
|
||||
return cleaned_data
|
||||
|
||||
def save(self, **kwargs):
|
||||
from cms.api import create_page, add_plugin
|
||||
from cms.utils.permissions import has_page_add_permission
|
||||
return data
|
||||
|
||||
def clean_parent_node(self):
|
||||
# Check to see if this user has permissions to make this page. We've
|
||||
# already checked this when producing a list of wizard entries, but this
|
||||
# is to prevent people from possible form-hacking.
|
||||
|
||||
if 'sub_page' in self.cleaned_data:
|
||||
sub_page = self.cleaned_data['sub_page']
|
||||
if self.page and self.sub_page_form:
|
||||
# User is adding a page which will be a direct
|
||||
# child of the current page.
|
||||
parent_page = self.page
|
||||
elif self.page and self.page.parent_page:
|
||||
# User is adding a page which will be a right
|
||||
# sibling to the current page.
|
||||
parent_page = self.page.parent_page
|
||||
else:
|
||||
sub_page = False
|
||||
parent_page = None
|
||||
|
||||
if self.page:
|
||||
if sub_page:
|
||||
parent = self.page
|
||||
position = "last-child"
|
||||
else:
|
||||
parent = self.page.parent
|
||||
position = "right"
|
||||
if parent_page:
|
||||
has_perm = user_can_add_subpage(self.user, target=parent_page)
|
||||
else:
|
||||
parent = None
|
||||
position = "last-child"
|
||||
has_perm = user_can_add_page(self.user)
|
||||
|
||||
# Before we do this, verify this user has perms to do so.
|
||||
if not (self.user.is_superuser or
|
||||
has_page_add_permission(self.user, self.page,
|
||||
position=position,
|
||||
site=self.page.site)):
|
||||
raise NoPermissionsException(
|
||||
_(u"User does not have permission to add page."))
|
||||
if not has_perm:
|
||||
message = ugettext('You don\'t have the permissions required to add a page.')
|
||||
raise ValidationError(message)
|
||||
return parent_page.node if parent_page else None
|
||||
|
||||
page = create_page(
|
||||
title=self.cleaned_data['title'],
|
||||
slug=self.cleaned_data['slug'],
|
||||
template=get_cms_setting('WIZARD_DEFAULT_TEMPLATE'),
|
||||
language=self.language_code,
|
||||
created_by=smart_text(self.user),
|
||||
parent=parent,
|
||||
in_navigation=True,
|
||||
published=False
|
||||
)
|
||||
def clean_slug(self):
|
||||
# Don't let the PageAddForm validate this
|
||||
# on the wizard it is not a required field
|
||||
return self.cleaned_data['slug']
|
||||
|
||||
page_type = self.cleaned_data.get("page_type")
|
||||
if page_type:
|
||||
copy_target = Page.objects.filter(pk=page_type).first()
|
||||
else:
|
||||
copy_target = None
|
||||
def get_template(self):
|
||||
return get_cms_setting('PAGE_WIZARD_DEFAULT_TEMPLATE')
|
||||
|
||||
if copy_target:
|
||||
# If the user selected a page type, copy that.
|
||||
if not user_has_view_permission(self.user, copy_target):
|
||||
raise PermissionDenied()
|
||||
@transaction.atomic
|
||||
def save(self, **kwargs):
|
||||
from cms.api import add_plugin
|
||||
|
||||
# Copy page attributes
|
||||
copy_target._copy_attributes(page, clean=True)
|
||||
page.save()
|
||||
new_page = super(CreateCMSPageForm, self).save(**kwargs)
|
||||
|
||||
# Copy contents (for each language)
|
||||
for lang in copy_target.get_languages():
|
||||
copy_target._copy_contents(page, lang)
|
||||
if self.cleaned_data.get("page_type"):
|
||||
return new_page
|
||||
|
||||
# Copy extensions
|
||||
from cms.extensions import extension_pool
|
||||
extension_pool.copy_extensions(copy_target, page)
|
||||
parent_node = self.cleaned_data.get('parent_node')
|
||||
|
||||
else:
|
||||
# If the user provided content, then use that instead.
|
||||
content = self.cleaned_data.get('content')
|
||||
plugin_type = get_cms_setting('WIZARD_CONTENT_PLUGIN')
|
||||
plugin_body = get_cms_setting('WIZARD_CONTENT_PLUGIN_BODY')
|
||||
if plugin_type in plugin_pool.plugins and plugin_body:
|
||||
if content and permissions.has_plugin_permission(
|
||||
self.user, plugin_type, "add"):
|
||||
placeholder = self.get_first_placeholder(page)
|
||||
if placeholder:
|
||||
add_plugin(**{
|
||||
'placeholder': placeholder,
|
||||
'plugin_type': plugin_type,
|
||||
'language': self.language_code,
|
||||
plugin_body: content,
|
||||
})
|
||||
if parent_node and new_page.parent_page.is_page_type:
|
||||
# the new page was created under a page-type page
|
||||
# set the new page as a page-type too
|
||||
new_page.update(
|
||||
draft_only=True,
|
||||
is_page_type=True,
|
||||
in_navigation=False,
|
||||
)
|
||||
|
||||
if is_installed('reversion'):
|
||||
from cms.utils.helpers import make_revision_with_plugins
|
||||
from cms.admin.pageadmin import INITIAL_COMMENT
|
||||
from cms.utils.reversion_hacks import create_revision
|
||||
# If the user provided content, then use that instead.
|
||||
content = self.cleaned_data.get('content')
|
||||
plugin_type = get_cms_setting('PAGE_WIZARD_CONTENT_PLUGIN')
|
||||
plugin_body = get_cms_setting('PAGE_WIZARD_CONTENT_PLUGIN_BODY')
|
||||
slot = get_cms_setting('PAGE_WIZARD_CONTENT_PLACEHOLDER')
|
||||
|
||||
with create_revision():
|
||||
make_revision_with_plugins(
|
||||
obj=page,
|
||||
user=self.user,
|
||||
message=ugettext(INITIAL_COMMENT),
|
||||
)
|
||||
return page
|
||||
if plugin_type in plugin_pool.plugins and plugin_body:
|
||||
if content and permissions.has_plugin_permission(
|
||||
self.user, plugin_type, "add"):
|
||||
new_page.rescan_placeholders()
|
||||
placeholder = self.get_placeholder(new_page, slot=slot)
|
||||
if placeholder:
|
||||
opts = {
|
||||
'placeholder': placeholder,
|
||||
'plugin_type': plugin_type,
|
||||
'language': self.language_code,
|
||||
plugin_body: content,
|
||||
}
|
||||
add_plugin(**opts)
|
||||
return new_page
|
||||
|
||||
|
||||
class CreateCMSSubPageForm(CreateCMSPageForm):
|
||||
|
||||
sub_page = forms.BooleanField(initial=True, widget=forms.HiddenInput)
|
||||
sub_page_form = True
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,23 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"Language-Team: Afrikaans (http://www.transifex.com/divio/django-cms/language/af/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: af\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr ""
|
||||
Binary file not shown.
+1054
-1178
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -3,15 +3,18 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Translators:
|
||||
# Abdelkader Maarouf <dep2g2@gmail.com>, 2015
|
||||
# Ahmed H <info@draco-003.com>, 2013
|
||||
# Bashar Al-Abdulhadi, 2017
|
||||
# Bashar Ghadanfar <10.tens@gmail.com>, 2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Project-Id-Version: django CMS\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
|
||||
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
|
||||
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
|
||||
"Language-Team: Arabic (http://www.transifex.com/divio/django-cms/language/ar/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -19,7 +22,5 @@ msgstr ""
|
||||
"Language: ar\n"
|
||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr "هل أنت متأكد من الإنتقال الى jfويب أخر دون حفظ الصفحة أولاً؟"
|
||||
msgstr "هل أنت متأكد من الإنتقال الى تبويب أخر دون حفظ الصفحة أولاً؟"
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,23 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"Language-Team: Belarusian (http://www.transifex.com/divio/django-cms/language/be/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: be\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr ""
|
||||
Binary file not shown.
+974
-1095
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -3,16 +3,17 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Translators:
|
||||
# Boris Chervenkov <office@sentido.bg>, 2012
|
||||
# Boyko Amarov <boyko.amarov@gmail.com>, 2013
|
||||
# suleyman <posta@suleymans.com>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Project-Id-Version: django CMS\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
|
||||
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
|
||||
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
|
||||
"Language-Team: Bulgarian (http://www.transifex.com/divio/django-cms/language/bg/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -20,7 +21,5 @@ msgstr ""
|
||||
"Language: bg\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr "Наистина ли искате да промените раздели без първо да запазите страницата?"
|
||||
|
||||
Binary file not shown.
+942
-1063
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -3,14 +3,15 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Translators:
|
||||
# Aniruddha Adhikary <aniruddha@adhikary.net>, 2013
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Project-Id-Version: django CMS\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
|
||||
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
|
||||
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
|
||||
"Language-Team: Bengali (http://www.transifex.com/divio/django-cms/language/bn/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -18,7 +19,5 @@ msgstr ""
|
||||
"Language: bn\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr "আপনি কি নিশ্চিত যে, আপনি পৃষ্ঠাটি সংরক্ষণ না করেই ট্যাব পরিবর্তন করতে চান?"
|
||||
|
||||
Binary file not shown.
+985
-1106
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -3,14 +3,15 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Translators:
|
||||
# Roger Pons <rogerpons@gmail.com>, 2012,2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Project-Id-Version: django CMS\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
|
||||
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
|
||||
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
|
||||
"Language-Team: Catalan (http://www.transifex.com/divio/django-cms/language/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -18,7 +19,5 @@ msgstr ""
|
||||
"Language: ca\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr "Esteu segur que voleu canviar de secció sense guardar la primera pàgina?"
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,23 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2016-01-19 20:20+0000\n"
|
||||
"Last-Translator: Martin Koistinen <mkoistinen@gmail.com>\n"
|
||||
"Language-Team: Chinese (Mandarin) (http://www.transifex.com/divio/django-cms/language/cmn/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: cmn\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr ""
|
||||
Binary file not shown.
+892
-1009
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -3,24 +3,23 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Jakub Dorňák <jdornak@redhat.com>, 2013
|
||||
# Translators:
|
||||
# Jakub Dorňák <jakub.dornak@misli.cz>, 2013
|
||||
# xlu <xlu@seznam.cz>, 2013
|
||||
# xlu <xlu@seznam.cz>, 2013
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Project-Id-Version: django CMS\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
|
||||
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
|
||||
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
|
||||
"Language-Team: Czech (http://www.transifex.com/divio/django-cms/language/cs/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: cs\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr "Určitě chcete změnit záložku bez uložení změn?"
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,23 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"Language-Team: Welsh (http://www.transifex.com/divio/django-cms/language/cy/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: cy\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr ""
|
||||
Binary file not shown.
+995
-1115
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -3,16 +3,17 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Translators:
|
||||
# Kristian Øllegaard <kristian@oellegaard.com>, 2011
|
||||
# Kristian Øllegaard <kristian@oellegaard.com>, 2011
|
||||
# Kristian Øllegaard <kristian@oellegaard.com>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Project-Id-Version: django CMS\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
|
||||
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
|
||||
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
|
||||
"Language-Team: Danish (http://www.transifex.com/divio/django-cms/language/da/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -20,7 +21,5 @@ msgstr ""
|
||||
"Language: da\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr "Er du sikker på du vil ændre tabs uden at gemme siden først?"
|
||||
|
||||
Binary file not shown.
+1000
-1116
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -3,16 +3,17 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Translators:
|
||||
# Jonas Obrist <me@ojii.ch>, 2011
|
||||
# Michael P. Jung <michael.jung@terreon.de>, 2013
|
||||
# Stefan T. Oertel <me@schneck.cc>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-cms\n"
|
||||
"Project-Id-Version: django CMS\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-11-05 10:16-0500\n"
|
||||
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
|
||||
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
|
||||
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
|
||||
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
|
||||
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
|
||||
"Language-Team: German (http://www.transifex.com/divio/django-cms/language/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -20,7 +21,5 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: static/cms/js/dist/bundle.admin.changeform.min.js:1
|
||||
#: static/cms/js/modules/cms.changeform.js:75
|
||||
msgid "Are you sure you want to change tabs without saving the page first?"
|
||||
msgstr "Sind Sie sicher Sie wollen die Sprache ändern ohne vorher zu speichern?"
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user