Compare commits
796 Commits
8b4b06c0f7
...
feature/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15c9d589fe | ||
|
|
fa116c08c1 | ||
|
|
4f9129e718 | ||
|
|
df45f3a704 | ||
|
|
90ef093fca | ||
| 4331b26542 | |||
| dd8a8ddd53 | |||
| abb0d488eb | |||
| be97b848d0 | |||
| 6bc112d689 | |||
| dc534a0c51 | |||
| e71ed05e6c | |||
| 2048ed6baf | |||
| c146bd6155 | |||
| 98665abf4d | |||
| 9954140d3a | |||
| fafee63a64 | |||
| c623d69767 | |||
| 670eb28bc0 | |||
| 144ff286f6 | |||
| 05f94de0b4 | |||
| c4650ce603 | |||
| 3971a8ee23 | |||
| a3ba6bc783 | |||
| b87df02714 | |||
| 713695cf7d | |||
| a25f30eda7 | |||
| eddb3a1858 | |||
| 5a89200f1d | |||
| 2f47a9e3db | |||
| 155b6272ec | |||
| 1479584bfc | |||
| f86be5bd97 | |||
| c87a7095ad | |||
| ac4df7a5f7 | |||
| c48839ff8c | |||
| ea296e3f05 | |||
| 8124ed62fe | |||
| cc643d2641 | |||
| 6de42c5ba9 | |||
| 36b7f4dee7 | |||
| 2328f09023 | |||
| 60faeeace9 | |||
| abe53dd88b | |||
| c76a18c5ff | |||
| 3725ce1882 | |||
| 6a69ff02b1 | |||
| 6ba41af305 | |||
| 5815a08b55 | |||
| 7805161829 | |||
| 6fa31f3866 | |||
| 0763faf224 | |||
| a3d3e12467 | |||
| ab22a3ec88 | |||
| 9d0a059909 | |||
| 1011f112b2 | |||
| d07ab2c71f | |||
| 96bfef04a8 | |||
| 779dd7e93d | |||
| 0abcb34829 | |||
| 33c028207a | |||
| 64b3e40ed0 | |||
| 0023676b28 | |||
| b90039f21a | |||
| b5f24ebf2e | |||
| f99a010e4a | |||
| a4944360a6 | |||
| a2b5d81c98 | |||
| 13b7c2572d | |||
| 47b12882ba | |||
| c008aa585b | |||
| 406b5e8480 | |||
| 584a196784 | |||
| 618f7751d1 | |||
| faaea1129a | |||
| dd7afc28f7 | |||
| 9ecf5ce073 | |||
| 422373f00e | |||
| 60636daeb8 | |||
| 9d47f4c2bc | |||
| 4d6dbddd28 | |||
| 3f00ff39d2 | |||
| bff6a81586 | |||
| 9cb8036d3c | |||
| a43de1fa91 | |||
| 93717bee2d | |||
| 8db1d6fdce | |||
| b41f8c7eca | |||
| c19405d32a | |||
| 7b75c533a8 | |||
| 649dbab901 | |||
| 1a4498d19f | |||
| 2786ef454d | |||
| 961c0dd2a5 | |||
| 0a0835d3a6 | |||
| bc87d10d59 | |||
| 792693848b | |||
| de229c5f78 | |||
| 03f9a836e6 | |||
| 91751574cc | |||
| d5453dada6 | |||
| 22adb18a39 | |||
| 0a3c40dd5d | |||
| b29825d62b | |||
| c9bda8aab2 | |||
| 284a4b064a | |||
| 6f20f53d75 | |||
| 473a047af9 | |||
| 481f9a881f | |||
| 6ceedddbc1 | |||
| bd9e0fad48 | |||
| 6b118d00b6 | |||
| 01e221196f | |||
| ccf016999b | |||
| cb79e47796 | |||
| 4076bd6065 | |||
| e54c258007 | |||
| c3b7401255 | |||
| 9af72d30f4 | |||
| dbe948ae5f | |||
| 885d36a90c | |||
| 7acfbc0113 | |||
| 43c5faf988 | |||
| 437142fba0 | |||
| acf7b702ee | |||
| 74ca1be884 | |||
| 26b987d6ce | |||
| 3f7f1d88ed | |||
| 59225b1688 | |||
| 108aee2c43 | |||
| 9062ce32a1 | |||
| 7c74a0f3fe | |||
| 4fb3a12f47 | |||
| 59bc25f31c | |||
| 87fbe7852c | |||
| 793d283d97 | |||
| 651ae18345 | |||
| 67eb32968e | |||
| c6d41513c0 | |||
| 67ba698b60 | |||
| 15d1c00ac6 | |||
| 9e9a82ffc0 | |||
| a0eb5210ab | |||
| 3dacc0316e | |||
| e2c347c912 | |||
| 758f8b1f55 | |||
| b446a8f519 | |||
| c8ba0dd770 | |||
| f13a1329ca | |||
| 4b3604098f | |||
| defbf6746f | |||
| bf3f26ec4f | |||
| 76018333b0 | |||
| e6e345b9fe | |||
| eafccabb66 | |||
| f2e0628de1 | |||
| 2e1f3a10ab | |||
| bf18c96dbd | |||
| 23718a5f3f | |||
| c6a1d5bdcf | |||
| f75e94f706 | |||
| 11c9bb6c23 | |||
| b1ff9c47da | |||
| 35c09dc70b | |||
| f0efae5987 | |||
| 65120cd2d4 | |||
| 231d062814 | |||
| 615922a881 | |||
| 61b2b824d5 | |||
| 317445998a | |||
| 4ad3813499 | |||
| 9a88243323 | |||
| df0f32c71a | |||
| 4ddd402442 | |||
| a05c4b3898 | |||
| a3ab0973c9 | |||
| 0f801aa44b | |||
| 7bc386bc44 | |||
| e148c60d70 | |||
| 4667352ec4 | |||
| e7cf694d88 | |||
| b2abb3046b | |||
| 6c1011e59e | |||
| ad909b98bf | |||
| 9d2e35246a | |||
| 48dc573c0f | |||
| e559660912 | |||
| aa3bec304a | |||
| c7bc22813f | |||
| 46e73db10a | |||
| ad451c2ae0 | |||
| 0b5b557a35 | |||
| fb9228c432 | |||
| fbcbe93042 | |||
| ca44deb077 | |||
| 61403bfac2 | |||
| 190efa4b74 | |||
| ca06836b11 | |||
| b31241872f | |||
| f80e2844bf | |||
| 1dae86f0e7 | |||
| 0c146caeef | |||
| 410733211b | |||
| 14df8969c2 | |||
| 4388414ac2 | |||
| 791ac8d436 | |||
| a3d6f498b1 | |||
| b8fdd61948 | |||
| 4437372d7c | |||
| f8d29d80b7 | |||
| 36fd9599af | |||
| 65eccde487 | |||
| 037b4cc562 | |||
| 9c971a6fa4 | |||
| a3faa17754 | |||
| 085f905125 | |||
| dc16ced786 | |||
| d5cba26098 | |||
| 001dd2cb87 | |||
| 16e860a29f | |||
| 6a5331d8eb | |||
| aff0b0fe98 | |||
| fd3612c370 | |||
| d14a46d3d7 | |||
| d7ace77de8 | |||
| 885e4722af | |||
| 72ed6369d8 | |||
| b23e440efd | |||
| 75ddb002fd | |||
| 28a36335ce | |||
| fba225aa70 | |||
| af800ac84c | |||
| 87e90d7152 | |||
| c25942a6ca | |||
| 692419816f | |||
| 3a109340fd | |||
| 5925ecb975 | |||
| 325acd3580 | |||
| 3ac85784a9 | |||
| 5c4e715970 | |||
| 89b57feb4e | |||
| fc194d3f85 | |||
| 12af0ea238 | |||
| 6e758cf62e | |||
| f7783c070b | |||
| fb3cd30db4 | |||
| ff949a8205 | |||
| 624653f581 | |||
| f3e5f02f07 | |||
| d0792e9e84 | |||
| f752eb5d4a | |||
| f0861fde84 | |||
| 4e7aa09c21 | |||
| e68a0dc151 | |||
| fc37bea98a | |||
| 8a91e611ac | |||
| 7208db6981 | |||
| 447a78cd3a | |||
| 95dff519b8 | |||
| 62aeebae95 | |||
| c2e03728f8 | |||
| a64fbab270 | |||
| 00ea16f631 | |||
| e3aafcdfe9 | |||
| 421a3a2dab | |||
| 61e4f7e450 | |||
| 3b52cab162 | |||
| fe9be61772 | |||
| 0818f880cc | |||
| cca062efec | |||
| 2eb7ac85d3 | |||
| 3469a3923c | |||
| 20fb49b3e4 | |||
| afe8feebc2 | |||
| ae6a68d85b | |||
| d80c45acd5 | |||
| 3a9ac2c289 | |||
| 2d68836724 | |||
| 0deb85b5a3 | |||
| 7b38f6e3b3 | |||
| 43f050f070 | |||
| 098b7c4fb5 | |||
| eae56199e0 | |||
| 71e8f0f465 | |||
| 66d7183278 | |||
| 92b92f2d65 | |||
| d5ab7d82eb | |||
| 1ebd9f9c0b | |||
| 7fd2c60ddb | |||
| f068182557 | |||
| 02945b0691 | |||
| 21b959e6a3 | |||
| 747091c744 | |||
| 7b70052ff1 | |||
| 49fd26bc4c | |||
| 77f365ef45 | |||
| 6d34d46e38 | |||
| 970c2e0837 | |||
| b87549d567 | |||
| 78abdb2fef | |||
| f73ebb08c9 | |||
| 628e8eec09 | |||
| 66ddcaec5b | |||
| 3901c82aae | |||
| 9f682b66a5 | |||
| c74aff7b91 | |||
| 1be50b1277 | |||
| af00d1a54b | |||
| dd4ad82248 | |||
| 38c08cbced | |||
| ed2c74a280 | |||
| 90ab1bb6b4 | |||
| 3e2220e5f2 | |||
| 33e27620b1 | |||
| fbdef2a1ec | |||
| 1834639b50 | |||
| f9606a3c88 | |||
| c803d7abbb | |||
| efd75817bc | |||
| 36fff15e2b | |||
| 7573bfb344 | |||
| 0c33d638ba | |||
| 7d3da34e22 | |||
| 0e2723a367 | |||
| b102c44031 | |||
| 4b480c35e5 | |||
| 8e7bcd8fd8 | |||
| c60c545031 | |||
| 51d075f799 | |||
| db54ec1650 | |||
| 14b362442f | |||
| fcfbeece87 | |||
| aea501aedb | |||
| f1d175fe84 | |||
| 0f432c4fd9 | |||
| b53e19c439 | |||
| bebd20abf0 | |||
| 5dc2293212 | |||
| 37f480c01e | |||
| f16efa24b0 | |||
| e11d05eb02 | |||
| 32cd45106d | |||
| 7b95893b34 | |||
| b6769f6350 | |||
| 5650b2d6b9 | |||
| f0fe8edf80 | |||
| b30df5b6ed | |||
| 0ef26556a2 | |||
| ea92feab27 | |||
| 1720e156d2 | |||
| 9ed06e741e | |||
| d39a3a78d0 | |||
| 09dc2984f3 | |||
| 660e3f8a99 | |||
| cc9797eb71 | |||
| 6a8aa73e63 | |||
| 689495e410 | |||
| 415fad4f4e | |||
| 396db7a439 | |||
| 61bea5639b | |||
| c9d6812d11 | |||
| 18dd435678 | |||
| a7a4112312 | |||
| cf8cb9a6a4 | |||
| b31f1ddb0f | |||
| 382ddd48e4 | |||
| 0b57fa9238 | |||
| fe7721270b | |||
| 49b35abb0b | |||
| 41dbb6bad7 | |||
| fde640e997 | |||
| fd1f4ff9b1 | |||
| 3a4c1b12a6 | |||
| ce68dbee8a | |||
| 1976a4c1dc | |||
| 0dc3419651 | |||
| 7769608e03 | |||
| 9772d7670b | |||
| 36e9edb51b | |||
| 6191980dc7 | |||
| 7179b67137 | |||
| 65362e34b8 | |||
| ec76d336f3 | |||
| 12d5e4546d | |||
| 4a97b02010 | |||
| aa492f6ca4 | |||
| 7547d2cb89 | |||
| 3febf729b6 | |||
| fbfdec2380 | |||
| fb665b409c | |||
| f86e76615d | |||
| 7cfe3f699a | |||
| 03ab2ae2cd | |||
| ad1016a427 | |||
| 6fc40452d9 | |||
| de098bdf55 | |||
| dcfd66324e | |||
| ff962b9658 | |||
| 006ff44858 | |||
| f9fdcc5314 | |||
| e49605dea6 | |||
| e429f48320 | |||
| 43ef016751 | |||
| 4a59adc73f | |||
| 2ebc28519e | |||
| 23de94b8cd | |||
| 42cf20137b | |||
| d5bb15e695 | |||
| 039985fe5f | |||
| d618c10e8b | |||
| 069f2094f0 | |||
| 2b6bbba265 | |||
| f7e032a5bc | |||
| 0d394fb6b7 | |||
| 9635a1dcf2 | |||
| 6ae3a88d7b | |||
| 512499ab12 | |||
| b8e3092fc4 | |||
| 4b9e5e5f34 | |||
| a7d9dfe79a | |||
| cc503ec6d1 | |||
| 7eb8edbe1f | |||
| 1f4ccaec4a | |||
| 66e0edebc5 | |||
| ebdc84f7e1 | |||
| cd1b8d4579 | |||
| a733649521 | |||
| 01d72c5f45 | |||
| 55681c7fb8 | |||
| 4a19726a99 | |||
| 61f26299b8 | |||
| 8a6317da2b | |||
| 1124149cf8 | |||
| 426071fe15 | |||
| c97aa44723 | |||
| de6bd9682e | |||
| 16e7ea3109 | |||
| 41fa14351a | |||
| 35f135a10b | |||
| 037abac188 | |||
| fecd32a254 | |||
| 446af5e438 | |||
| 71ecd65814 | |||
| 3e101493dc | |||
| 453d4b7349 | |||
| 40a8bc158a | |||
| 2c711ed370 | |||
| fc1654dedb | |||
| dfec56fef9 | |||
| 942db49daf | |||
| d4562e7ca4 | |||
| 3a0845fe2a | |||
| 3740ac642f | |||
| c1ccf00302 | |||
| 4400a6b13f | |||
| 66fbf984e9 | |||
| 30ebc6037a | |||
| ac60b08f2d | |||
| f32e127148 | |||
| 8e3b798bce | |||
| 57dddf528b | |||
| 35d5e05f00 | |||
| 4ecc89a4e7 | |||
| 87bc1be15a | |||
| b1c599e71d | |||
| da3fb36a22 | |||
| ded7761c91 | |||
| d4de2ef739 | |||
| 7637f6fdd1 | |||
| f663954f03 | |||
| f2fd95b04e | |||
| 3880b97cae | |||
| 2fb00ee112 | |||
| dc92877beb | |||
| d9103e62a7 | |||
| e5a8c3cb8f | |||
| 97b00d94cf | |||
| 381f0a8490 | |||
| b8b3fd02fa | |||
| 3b244f4de0 | |||
| c4d101275a | |||
| c9b21ded98 | |||
| f5115f82fb | |||
| d6a6b4f319 | |||
| f3e418cf68 | |||
| a74a0c1f57 | |||
| 0f10e274e6 | |||
| 57a419eeaa | |||
| aa18a3c542 | |||
| dea8d064d4 | |||
| d022fb231f | |||
| 415784bf06 | |||
| 246974c1b8 | |||
| 34d2fea31f | |||
| 0571de3dd0 | |||
| 4e2d453ff1 | |||
| f4ba38654b | |||
| 7831973cdb | |||
| 4f2c7aeb2c | |||
| 237666d96f | |||
| f6d0486a19 | |||
| 200a9c308c | |||
| 28d3138c43 | |||
| 0d41c95e3d | |||
| e5ea92df3d | |||
| 9f7550eaae | |||
| 3e88458fce | |||
| a5cbf8c128 | |||
| 704b88320a | |||
| 0aae04eca8 | |||
| 3f1cd5431f | |||
| 5081b6ce3e | |||
| 7e7fb437a6 | |||
| addca29f0f | |||
| f1775237fb | |||
| f09ff3f7df | |||
| 4259f84764 | |||
| 707bbf1863 | |||
| a989f85b2b | |||
| b62396a907 | |||
| 998c91c727 | |||
| 64b8e74612 | |||
| 55eff16b9c | |||
| 31857131be | |||
| 77de950af8 | |||
| 176c84cf2d | |||
| 15ea6a982c | |||
| 6230b77473 | |||
| 2f81a73078 | |||
| cf83ae059e | |||
| 4418d4ad6f | |||
| 239b6a0da1 | |||
| cc02495459 | |||
| 153d0380de | |||
| 87fb930c63 | |||
| 38a8cef3d9 | |||
| 6f7551abbf | |||
| 64d6860e58 | |||
| 88e6c7eee8 | |||
| 75c5553852 | |||
| 9d1815c21f | |||
| c93cda4eaf | |||
| cce969d870 | |||
| 0b177d845c | |||
| 42f288e0e4 | |||
| 3948c79891 | |||
| c8a28de46a | |||
| c660d8cb72 | |||
| a2688c4a15 | |||
| 9d2e9b7acd | |||
| f37ed72b39 | |||
| 6fd3c11f06 | |||
| 414ffc703d | |||
| f7a587b7a5 | |||
| 534defa36b | |||
| 1fbf846270 | |||
| e6a1cd967e | |||
| c9adf1a785 | |||
| 8380b7e66e | |||
| e01c7e81f2 | |||
| 4751dd9079 | |||
| f82ae16e31 | |||
| 8a00446b5c | |||
| 39f37bcd23 | |||
| c8e4df8150 | |||
| 1e9e2cb048 | |||
| 06661cb187 | |||
| fade021048 | |||
| 9d90138847 | |||
| b7a677d79d | |||
| 3660f5af6d | |||
| 61e1c35f02 | |||
| 89721ce797 | |||
| 55b4c0ac11 | |||
| 70df8a53c8 | |||
| 3311ab5b37 | |||
| 49ad367a70 | |||
| cc3a5c4a4f | |||
| f7af979b7e | |||
| 6f06f96a94 | |||
| a3ef86ba37 | |||
| d6da292d5f | |||
| ccb8c6585c | |||
| ea567008c6 | |||
| 6cbde10b11 | |||
| 7e23776501 | |||
| e6a91ebc72 | |||
| 06a2e883fb | |||
| 00b0e00fd3 | |||
| 8086d5344e | |||
| ea76733305 | |||
| f0f8af48b3 | |||
| 9bc02c7752 | |||
| 3067535fe0 | |||
| cc6bd781e0 | |||
| 10a1cb096d | |||
| 47c228d048 | |||
| 11c35c9abd | |||
| ba8bde1153 | |||
| 7c991024b7 | |||
| 710d500980 | |||
| cde0b55aab | |||
| 6660db1a89 | |||
| 8f76b484ba | |||
| b8790be7fe | |||
| bd4a2e7507 | |||
| 70002f4efb | |||
| d40eed3c5d | |||
| 130d601e54 | |||
| 7ae8d0ce48 | |||
| 494ff26fdd | |||
| 330a582a26 | |||
| 54d5669b3b | |||
| a7491c545c | |||
| 6515979251 | |||
| a97d9ec2bc | |||
| 2685e80161 | |||
| ea94031545 | |||
| 298e3861cf | |||
| ce230e80a5 | |||
| b12ae238d3 | |||
| d482570221 | |||
| dde8c5fdd9 | |||
| 259d6662fe | |||
| e4849ec659 | |||
| 88f679cd41 | |||
| f97eeb83c2 | |||
| 155e5bdf57 | |||
| e78c9a1efb | |||
| 4b74dd8f19 | |||
| c3be6d3e83 | |||
| 50baa907bd | |||
| 40207ab256 | |||
| 0733851e4e | |||
| 1aea397f9a | |||
| 335af09596 | |||
| 95c3c062fd | |||
| f47e3e550c | |||
| d3d0dc5a22 | |||
| ff4c573941 | |||
| 8d52620d32 | |||
| 5856fbb670 | |||
| 7cb8a2de39 | |||
| d57be37c34 | |||
| 28a14aaf76 | |||
| 359ed5770d | |||
| fe093aa9ac | |||
| 338a9ed08a | |||
| a8c954e8d9 | |||
| 50ee432965 | |||
| 4b3e3b8401 | |||
| 2abf0f62e9 | |||
| 69dd70d42d | |||
| 405cae84fe | |||
| ae9902965a | |||
| 0c20d7d0f4 | |||
| d4e3b75bcb | |||
| bbd3fd51dc | |||
| 9ecc961e42 | |||
| dd2cc62d01 | |||
| 7e316bbc73 | |||
| e455a19ff3 | |||
| 618064d16a | |||
| 05705a91cf | |||
| ec8ede8e28 | |||
| 6131d2534a | |||
| 775ffe42d7 | |||
| 400621572f | |||
| fe4539c9ed | |||
| a19017351b | |||
| e1a6fdd21f | |||
| 264c5ae372 | |||
| 0a00e950b6 | |||
| b12f2e784f | |||
| bc170e982f | |||
| c68c7b6109 | |||
| ac95ac04bc | |||
| a23ccb7ac4 | |||
| 09c74d8f85 | |||
| 4c747a0c5e | |||
| 3aa4b774bd | |||
| 93cf3aa695 | |||
| e75dd6c552 | |||
| 6ea03098b2 | |||
| 2cef14d2d8 | |||
| b7b6750ad2 | |||
| a729070654 | |||
| af42956f60 | |||
| ad8518a4c3 | |||
| 1c1867610d | |||
| 8c3ebb00e5 | |||
| abba3e6e10 | |||
| 058fe769b9 | |||
| 4b037443a6 | |||
| 28db905e14 | |||
| 41d7a996fd | |||
| 114a65746c | |||
| 6dd0cf7724 | |||
| 8e5a018c2a | |||
| 052f236206 | |||
| da3774527f | |||
| 93401402ca | |||
| c1ea4788a4 | |||
| b32a79f966 | |||
| cd748dc371 | |||
| defce7a85c | |||
| 63d5b62229 | |||
| 1acda2bb11 | |||
| db8f5e16e3 | |||
| c5c8fb8689 | |||
| b11478b4c0 | |||
| 26463c1853 | |||
| b0b6513bb7 | |||
| 0c6135d786 | |||
| 0167baec84 | |||
| cb5ab935ee | |||
| 96efd045b4 | |||
| e1846e0564 | |||
| ed809290ca | |||
| 0b2b9e184f | |||
| 4dd32820a3 | |||
| ffa6de7138 | |||
| 189b9233ab | |||
| 8c05b987ed | |||
| ed00864447 | |||
| ad9e6aa6b9 | |||
| 04daddefa7 | |||
| acf51fe626 | |||
| 9d44384d22 | |||
| a435e85d4c | |||
| c6edd2c533 | |||
| 928c737ead | |||
| 794d30ebf1 | |||
| 0864a07cdf | |||
| b64f58c237 | |||
| cc86400957 | |||
| b8c282f95f | |||
| 47e39a1d9c | |||
| 308e9c1df2 | |||
| d3fd7b7428 | |||
| 31833000bb | |||
| 02f7edade5 | |||
| 371457735a | |||
| ea65d62be8 | |||
| d1b8a7a4aa | |||
| 9b94b56c6e | |||
| e07cf3f9d6 | |||
| 906856b01b | |||
| 4ac3349377 | |||
| 3a9b421503 | |||
| c844389f09 | |||
| 444f23f9b2 | |||
| 721f2916cd | |||
| 5d707853b7 | |||
| 19621752b5 | |||
| f0e8cb5101 | |||
| ac2de4ddcc | |||
| 5471696d62 | |||
| adee3383ce | |||
| 0ab9631e0f | |||
| cc377963e0 | |||
| 5c22478108 | |||
| 51598dc022 | |||
| f620cd26af | |||
| 1f3a8a258b | |||
| 19983bf7a1 | |||
| 47ffae4b82 | |||
| 487647029a | |||
| 89ee4960c2 | |||
| 9c95358305 | |||
| 71c4ce41f0 | |||
| f60c96e9fd | |||
| 9ec605bed1 | |||
| 6ac082c7df | |||
| f552d6c0f2 | |||
| 52c65f598d | |||
| 9b12ca8ff3 | |||
| 31ba8a86bf | |||
| fe90ab8f50 | |||
| 58acad0eb8 | |||
| 3ce22bcb32 | |||
| 71846ade42 | |||
| 9c24034776 | |||
| 735a84bf9a | |||
| b65a668e77 | |||
| ac2b1e43df | |||
| 63cea80a20 | |||
| 67a51c882a | |||
| 21b873c80d | |||
| c86ebc7aa3 | |||
| 32f65de458 | |||
| 63d3ffddd4 | |||
| 70f6ccc0da | |||
| 5fa470bd9c | |||
| 01414ca4c6 | |||
| c781453055 |
1
.gitignore
vendored
@@ -414,4 +414,5 @@ fabric.properties
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
celerybeat-schedule.db
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib import admin
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from ArticlesApp.models import ArticleModel, UserPageModel
|
||||
from sets.admin import *
|
||||
|
||||
@@ -68,7 +68,7 @@ class Admin_Article(Admin_Trans_BaseModelViewPage):
|
||||
# ('devices'),
|
||||
)
|
||||
}),
|
||||
(u'Статья', {
|
||||
(_('Статья'), {
|
||||
'classes': ['wide'],
|
||||
'fields': (
|
||||
'description', 'text',
|
||||
@@ -114,7 +114,7 @@ class Admin_UserPage(Admin_Trans_BaseModelViewPage):
|
||||
# ('devices'),
|
||||
)
|
||||
}),
|
||||
(u'Статья', {
|
||||
(_('Статья'), {
|
||||
'classes': ['wide'],
|
||||
'fields': ('picture', 'description', 'text')
|
||||
}),
|
||||
@@ -149,7 +149,7 @@ class Admin_UserPage(Admin_Trans_BaseModelViewPage):
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
|
||||
if obj and obj.url in ('for-partners', 'dealers', 'about-truenergy', 'contacts'):
|
||||
if obj and obj.url in ('for-partners', 'dealers', 'contacts'):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from .models import *
|
||||
|
||||
|
||||
elements_on_page = 10
|
||||
|
||||
def get_articles(art_kwargs, request_Data=None, from_el=None, to_el=None):
|
||||
|
||||
if request_Data:
|
||||
@@ -20,16 +22,24 @@ def get_articles(art_kwargs, request_Data=None, from_el=None, to_el=None):
|
||||
elif to_el:
|
||||
arts = arts[:to_el]
|
||||
else:
|
||||
to_el = 3
|
||||
arts = arts[:3]
|
||||
to_el = elements_on_page
|
||||
arts = arts[:to_el]
|
||||
|
||||
last_block = False
|
||||
if not to_el or to_el >= el_count:
|
||||
last_block = True
|
||||
|
||||
if el_count - to_el > elements_on_page:
|
||||
next_page_els_count = elements_on_page
|
||||
else:
|
||||
next_page_els_count = el_count - to_el
|
||||
|
||||
Dict = {
|
||||
'articles': arts,
|
||||
'last_block': last_block
|
||||
'last_block': last_block,
|
||||
'last_el': to_el,
|
||||
'next_page_els_count': next_page_els_count,
|
||||
'elements_on_page': elements_on_page
|
||||
}
|
||||
|
||||
return Dict
|
||||
|
||||
@@ -33,11 +33,12 @@ def get_articles_block_ajax(request):
|
||||
if 'errors' in Dict:
|
||||
return JsonResponse(Dict, status=400)
|
||||
|
||||
html = render_to_string('blocks/b_news_elements.html', Dict, request=request)
|
||||
html = render_to_string('blocks/articles/b_news_elements.html', Dict, request=request)
|
||||
|
||||
res_Dict = {
|
||||
'html': html,
|
||||
'last_block': Dict['last_block']
|
||||
'last_block': Dict['last_block'],
|
||||
'next_page_els_count': Dict['next_page_els_count'],
|
||||
# 'form': RouteForm(initial=data)
|
||||
}
|
||||
|
||||
@@ -45,9 +46,10 @@ def get_articles_block_ajax(request):
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
||||
errors_Dict = {
|
||||
'errors': {
|
||||
'all__': f'ошибка в запросе = {str(e)}'
|
||||
'all__': f'{_("ошибка в запросе")} = {str(e)}'
|
||||
}
|
||||
}
|
||||
return JsonResponse(errors_Dict, status=400)
|
||||
@@ -19,11 +19,11 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
class UserPageModel(BaseModelViewPage):
|
||||
# pub_DT = models.DateTimeField(verbose_name=u'Дата и время публикации', auto_created=True)
|
||||
text = RichTextUploadingField(verbose_name=u'Текст')
|
||||
text = RichTextUploadingField(verbose_name=_('Текст'))
|
||||
|
||||
class Meta:
|
||||
verbose_name=u'Пользовательская страница'
|
||||
verbose_name_plural =u'Пользовательские страницы'
|
||||
verbose_name=_("Пользовательская страница")
|
||||
verbose_name_plural =_("Пользовательские страницы")
|
||||
# unique_together = ('url', 'region')
|
||||
# managed=True
|
||||
# app_label = u'ArticlesApp'
|
||||
@@ -31,8 +31,8 @@ class UserPageModel(BaseModelViewPage):
|
||||
|
||||
class ArticleModel(BaseModelViewPage):
|
||||
# pub_DT = models.DateTimeField(verbose_name=u'Дата и время публикации', auto_created=True)
|
||||
text = RichTextUploadingField(verbose_name=u'Текст')
|
||||
text = RichTextUploadingField(verbose_name=_("Текст"))
|
||||
|
||||
class Meta:
|
||||
verbose_name='Статья'
|
||||
verbose_name_plural ='Статьи'
|
||||
verbose_name=_("Статья")
|
||||
verbose_name_plural =_("Статьи")
|
||||
1
ArticlesApp/templatetags/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'SDE'
|
||||
48
ArticlesApp/templatetags/arts_tags_extra.py
Normal file
@@ -0,0 +1,48 @@
|
||||
__author__ = 'SDE'
|
||||
|
||||
from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
# @register.filter('get_side_art')
|
||||
@register.simple_tag
|
||||
def get_side_art(last_el, counter, els_on_page):
|
||||
cur_el = last_el - els_on_page + counter
|
||||
if els_on_page < 4:
|
||||
first_left_el = els_on_page + 1
|
||||
else:
|
||||
first_left_el = 4
|
||||
if (cur_el - first_left_el) % 2:
|
||||
return 'even'
|
||||
else:
|
||||
return 'odd'
|
||||
|
||||
# @register.filter()
|
||||
# def get_numbers_list(from_el, to_el):
|
||||
# res = range(from_el, to_el+1)
|
||||
# return res
|
||||
#
|
||||
#
|
||||
# def val_type(value):
|
||||
# res = type(value)
|
||||
# return res.__name__
|
||||
# register.filter('val_type', val_type)
|
||||
#
|
||||
# @register.filter()
|
||||
# def get_cols_table_data_for_row_when_cols3(value, row):
|
||||
# el_count = 3
|
||||
# from_el = (row-1) * el_count
|
||||
# to_el = row * el_count
|
||||
# part = list(value)[from_el:to_el]
|
||||
# return part
|
||||
# # register.filter('val_type', val_type)
|
||||
#
|
||||
#
|
||||
# @register.filter
|
||||
# @stringfilter
|
||||
# def correct_for_tables(value):
|
||||
# if value in ['None', '0.0']:
|
||||
# return '-'
|
||||
# return value
|
||||
@@ -6,6 +6,8 @@ from datetime import datetime, date
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.template import loader
|
||||
from .funcs import *
|
||||
from GeneralApp.funcs import get_inter_http_respose
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# from django.contrib.auth.decorators import login_required
|
||||
# from BaseModels.search_optimization.ld_json.ld_article_news import get_ld_article_news
|
||||
@@ -16,7 +18,7 @@ from .funcs import *
|
||||
def get_flat_pages_links_Dict(site):
|
||||
|
||||
flat_pages_links = UserPageModel.objects.filter(
|
||||
url__in=('about-truenergy', 'for-partners', 'contacts'),
|
||||
url__in=('for-partners', 'contacts'),
|
||||
sites=site
|
||||
).values_list('url', flat=True)
|
||||
|
||||
@@ -26,20 +28,23 @@ def get_flat_pages_links_Dict(site):
|
||||
|
||||
def get_article_breadcrumbs(request, art):
|
||||
# print('get_article_breadcrumbs')
|
||||
articles_add_count = 1
|
||||
articles_add_count = 3
|
||||
# half_count = articlesCountInBlock / 2
|
||||
|
||||
arts_top = ArticleModel.objects.filter(enable=True, createDT__gt=art.createDT).order_by(
|
||||
'createDT')[:articles_add_count]
|
||||
arts_down = ArticleModel.objects.filter(enable=True, createDT__lt=art.createDT).order_by(
|
||||
'-createDT')[:articles_add_count]
|
||||
# arts_top = ArticleModel.objects.filter(enable=True, createDT__gt=art.createDT).order_by(
|
||||
# 'createDT')[:articles_add_count]
|
||||
# arts_down = ArticleModel.objects.filter(enable=True, createDT__lt=art.createDT).order_by(
|
||||
# '-createDT')[:articles_add_count]
|
||||
# if len(artListDown)<half_count:
|
||||
# art_List = ArticleModel.objects.filter(enable=True, article_DT__gte=art.article_DT).order_by(
|
||||
# 'article_DT')[:art.articlesCountInBlock-len(artListDown)]
|
||||
|
||||
breadcrumbs_arts = ArticleModel.objects.exclude(id=art.id).order_by('?')[:3]
|
||||
|
||||
Dict = {
|
||||
'arts_top': arts_top,
|
||||
'arts_down': arts_down
|
||||
'breadcrumbs_arts': breadcrumbs_arts
|
||||
# 'arts_top': arts_top,
|
||||
# 'arts_down': arts_down
|
||||
}
|
||||
return Dict
|
||||
|
||||
@@ -78,9 +83,17 @@ def ArticlesPageView(request, year=None):
|
||||
|
||||
|
||||
Dict = get_articles(art_kwargs=kwargs)
|
||||
Dict.update({
|
||||
'page': {
|
||||
'title': _('Страница списка новостей'),
|
||||
'description': _('Все новости сайта tripwb.com'),
|
||||
'keywords': _('Все новости сайта tripwb.com'),
|
||||
}
|
||||
})
|
||||
|
||||
t = loader.get_template('pages/p_articles.html')
|
||||
return HttpResponse(t.render(Dict, request))
|
||||
return get_inter_http_respose(t, Dict, request)
|
||||
# return HttpResponse(t.render(Dict, request))
|
||||
|
||||
|
||||
|
||||
@@ -103,7 +116,7 @@ def UserPageView(request, page_url):
|
||||
# print('article_breadcrumbs',article_breadcrumbs)
|
||||
|
||||
Dict = {
|
||||
'art' : art,
|
||||
'page' : art,
|
||||
'user_page' : True,
|
||||
# 'service' : service,
|
||||
# 'article_breadcrumbs' : article_breadcrumbs
|
||||
@@ -112,7 +125,8 @@ def UserPageView(request, page_url):
|
||||
|
||||
|
||||
t = loader.get_template('pages/p_user_page.html')
|
||||
return HttpResponse(t.render(Dict, request))
|
||||
return get_inter_http_respose(t, Dict, request)
|
||||
# return HttpResponse(t.render(Dict, request))
|
||||
|
||||
|
||||
|
||||
@@ -133,7 +147,7 @@ def ArticlesOnePageView(request, art_url):
|
||||
# print('article_breadcrumbs',article_breadcrumbs)
|
||||
|
||||
Dict = {
|
||||
'art' : art,
|
||||
'page' : art,
|
||||
}
|
||||
|
||||
Dict.update(get_article_breadcrumbs(request, art))
|
||||
@@ -141,4 +155,5 @@ def ArticlesOnePageView(request, art_url):
|
||||
|
||||
|
||||
t = loader.get_template('pages/p_article.html')
|
||||
return HttpResponse(t.render(Dict, request))
|
||||
return get_inter_http_respose(t, Dict, request)
|
||||
# return HttpResponse(t.render(Dict, request))
|
||||
|
||||
@@ -39,16 +39,17 @@ class Admin_ProfileInline(admin.StackedInline):
|
||||
(None, {
|
||||
'classes': ['wide'],
|
||||
'fields': (
|
||||
('account_type',),
|
||||
# ('account_type',),
|
||||
('enable',),
|
||||
('phone',),
|
||||
('country', 'city'),
|
||||
('mailing_on', ),
|
||||
('authMailCode',),
|
||||
('birthdate'),
|
||||
'comment', 'creator'
|
||||
)
|
||||
}),
|
||||
('Дополнительно', {
|
||||
(_('Дополнительно'), {
|
||||
'classes': ['wide'],
|
||||
'fields': (
|
||||
('json_data',)
|
||||
@@ -71,6 +72,10 @@ class Admin_ProfileInline(admin.StackedInline):
|
||||
|
||||
class Admin_User(UserAdmin):
|
||||
|
||||
def mailing_on(self, obj):
|
||||
return obj.user_profile.mailing_on
|
||||
mailing_on.boolean = True
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'classes': ['wide'],
|
||||
@@ -91,61 +96,63 @@ class Admin_User(UserAdmin):
|
||||
|
||||
save_on_top = True
|
||||
|
||||
list_display = ['id', 'last_name', 'first_name', 'email', 'is_staff',
|
||||
list_display = ['id', 'last_name', 'first_name', 'mailing_on', 'email', 'is_staff',
|
||||
'is_active']
|
||||
list_editable = ['is_staff', 'is_active']
|
||||
list_display_links = ['first_name', 'last_name', 'email']
|
||||
search_fields = ['first_name', 'last_name', 'email']
|
||||
|
||||
list_filter = ['user_profile__mailing_on', 'is_staff', 'is_active']
|
||||
|
||||
inlines = (Admin_ProfileInline,)
|
||||
# actions = ['del_all_temp_users', ]
|
||||
|
||||
ordering = ['is_staff', 'last_name', 'first_name']
|
||||
ordering = ['is_staff', '-id', 'last_name', 'first_name']
|
||||
|
||||
# Re-register UserAdmin
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, Admin_User)
|
||||
|
||||
|
||||
class Admin_UserProfile(Admin_BaseIconModel):
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'classes': ['wide'],
|
||||
'fields': (
|
||||
'user', 'enable',
|
||||
('account_type',),
|
||||
('phone',),
|
||||
('country', 'city'),
|
||||
('authMailCode',),
|
||||
('birthdate'),
|
||||
'creator'
|
||||
)
|
||||
}),
|
||||
('1С', {
|
||||
'classes': ['wide'],
|
||||
'fields': (
|
||||
('name',),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
save_on_top = True
|
||||
|
||||
list_display = [
|
||||
'id', 'user', 'enable', 'birthdate', 'modifiedDT', 'createDT'
|
||||
]
|
||||
list_editable = ['enable', 'birthdate']
|
||||
list_display_links = ['id', ] # 'user__last_name', 'user__first_name']
|
||||
search_fields = [
|
||||
'id', 'user__last_name', 'user__first_name', 'user__email',
|
||||
]
|
||||
|
||||
list_filter = ['enable', 'account_type']
|
||||
|
||||
# filter_horizontal = ['connected_mailings']
|
||||
# raw_id_fields = ("favourites",)
|
||||
verbose_name_plural = _(u'Профиль пользователя')
|
||||
|
||||
|
||||
admin.site.register(UserProfile, Admin_UserProfile)
|
||||
# class Admin_UserProfile(Admin_BaseIconModel):
|
||||
#
|
||||
# fieldsets = (
|
||||
# (None, {
|
||||
# 'classes': ['wide'],
|
||||
# 'fields': (
|
||||
# 'user', 'enable',
|
||||
# ('account_type',),
|
||||
# ('phone',),
|
||||
# ('country', 'city'),
|
||||
# ('authMailCode',),
|
||||
# ('birthdate'),
|
||||
# 'creator'
|
||||
# )
|
||||
# }),
|
||||
# ('1С', {
|
||||
# 'classes': ['wide'],
|
||||
# 'fields': (
|
||||
# ('name',),
|
||||
# )
|
||||
# }),
|
||||
# )
|
||||
#
|
||||
# save_on_top = True
|
||||
#
|
||||
# list_display = [
|
||||
# 'id', 'user', 'enable', 'birthdate', 'modifiedDT', 'createDT'
|
||||
# ]
|
||||
# list_editable = ['enable', 'birthdate']
|
||||
# list_display_links = ['id', ] # 'user__last_name', 'user__first_name']
|
||||
# search_fields = [
|
||||
# 'id', 'user__last_name', 'user__first_name', 'user__email',
|
||||
# ]
|
||||
#
|
||||
# list_filter = ['enable', 'account_type']
|
||||
#
|
||||
# # filter_horizontal = ['connected_mailings']
|
||||
# # raw_id_fields = ("favourites",)
|
||||
# verbose_name_plural = _(u'Профиль пользователя')
|
||||
#
|
||||
#
|
||||
# admin.site.register(UserProfile, Admin_UserProfile)
|
||||
|
||||
@@ -14,17 +14,78 @@ class LoginForm(forms.Form):
|
||||
username = forms.CharField(required=True)
|
||||
password = forms.CharField(required=True)
|
||||
|
||||
|
||||
|
||||
class RegistrationForm(forms.Form):
|
||||
from .models import account_type_choices
|
||||
account_type = forms.ChoiceField(choices=account_type_choices, initial='sender', required=True)
|
||||
# account_type = forms.ChoiceField(choices=account_type_choices, initial='sender', required=True)
|
||||
firstname = forms.CharField(required=False)
|
||||
lastname = forms.CharField(required=False)
|
||||
country = forms.CharField(required=False)
|
||||
city = forms.CharField(required=False)
|
||||
email = forms.EmailField()
|
||||
password = forms.CharField(widget=forms.PasswordInput())
|
||||
confirm_password = forms.CharField(widget=forms.PasswordInput())
|
||||
tel = forms.CharField()
|
||||
agreement = forms.BooleanField(initial=False, required=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
required_password = True
|
||||
required_agreement = True
|
||||
required_email = True
|
||||
create_new_account = True
|
||||
if 'not_required_password' in kwargs.keys() and kwargs['not_required_password']:
|
||||
required_password = False
|
||||
del kwargs['not_required_password']
|
||||
if 'not_required_agreement' in kwargs.keys() and kwargs['not_required_agreement']:
|
||||
required_agreement = False
|
||||
del kwargs['not_required_agreement']
|
||||
if 'not_required_email' in kwargs.keys() and kwargs['not_required_email']:
|
||||
required_email = False
|
||||
del kwargs['not_required_email']
|
||||
if 'create_new_account' in kwargs.keys() and not kwargs['create_new_account']:
|
||||
create_new_account = False
|
||||
del kwargs['create_new_account']
|
||||
|
||||
super(RegistrationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['password'].required = required_password
|
||||
self.fields['confirm_password'].required = required_password
|
||||
|
||||
self.fields['agreement'].required = required_agreement
|
||||
|
||||
self.fields['email'].required = required_email
|
||||
|
||||
self.create_new_account = create_new_account
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
# i = 0
|
||||
# names = list(cleaned_data.keys())
|
||||
# while i < len(names):
|
||||
# if not cleaned_data[names[i]]:
|
||||
# if self.fields[names[i]].required:
|
||||
# self.add_error(names[i], _('Обязательное поле'))
|
||||
# i += 1
|
||||
|
||||
if 'tel' in cleaned_data and 'tel' in cleaned_data:
|
||||
from BaseModels.validators.form_field_validators import get_phone_valid_error
|
||||
error = get_phone_valid_error(cleaned_data["tel"])
|
||||
if error:
|
||||
self.add_error('tel', error)
|
||||
|
||||
if cleaned_data and 'confirm_password' in cleaned_data and 'password' in cleaned_data:
|
||||
if cleaned_data['confirm_password'] != cleaned_data['password']:
|
||||
self.add_error("password", _('Пароль и подтверждение пароля не совпадают'))
|
||||
self.add_error("confirm_password", _('Пароль и подтверждение пароля не совпадают'))
|
||||
|
||||
if self.create_new_account and cleaned_data and 'email' in cleaned_data:
|
||||
users = User.objects.filter(email=cleaned_data['email'])
|
||||
if users:
|
||||
self.add_error('email', _("Пользователь с указанным email уже существует"))
|
||||
|
||||
|
||||
|
||||
|
||||
# # class PersonForm(NgModelFormMixin, NgFormValidationMixin, NgModelForm, Bootstrap3ModelForm):
|
||||
# #
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
def get_user_timezone_Dict(user, request=None):
|
||||
tz = None
|
||||
if request:
|
||||
tz = request.COOKIES.get("user_tz")
|
||||
if not tz and user.is_authenticated:
|
||||
tz = user.user_profile.get_timezone()
|
||||
|
||||
if not tz:
|
||||
from django.conf import settings
|
||||
tz = settings.TIME_ZONE
|
||||
|
||||
return {'user_tz': tz}
|
||||
|
||||
def get_dashboard_page_content_html(request):
|
||||
|
||||
from ChatServiceApp.funcs import get_unanswered_msgs_count_for_user
|
||||
|
||||
Dict = {
|
||||
'unanswered_msgs_count': get_unanswered_msgs_count_for_user(request.user)
|
||||
}
|
||||
|
||||
html = render_to_string('blocks/profile/b_profile_first_page.html', Dict, request=request)
|
||||
return html
|
||||
|
||||
def get_profile_page_content_html(request, page_name, data):
|
||||
|
||||
if page_name == 'chat':
|
||||
@@ -7,10 +32,10 @@ def get_profile_page_content_html(request, page_name, data):
|
||||
return get_chat_page_content_html(request, data)
|
||||
elif page_name == 'create_route_for_customer':
|
||||
from RoutesApp.funcs import get_profile_new_route_page_html
|
||||
return get_profile_new_route_page_html(request, {})
|
||||
return get_profile_new_route_page_html(request, {'owner_type': 'customer'})
|
||||
elif page_name == 'create_route_for_mover':
|
||||
from RoutesApp.funcs import get_profile_new_route_page_html
|
||||
return get_profile_new_route_page_html(request, {})
|
||||
return get_profile_new_route_page_html(request, {'owner_type': 'mover'})
|
||||
elif page_name == 'my_routes':
|
||||
from RoutesApp.funcs import get_profile_my_routes_page_content_html
|
||||
return get_profile_my_routes_page_content_html(request)
|
||||
@@ -21,12 +46,30 @@ def get_profile_page_content_html(request, page_name, data):
|
||||
return get_profile_subscribe_page_content_html(request)
|
||||
elif page_name == 'change_profile':
|
||||
return get_profile_change_page_content_html(request)
|
||||
elif page_name == 'dashboard':
|
||||
return get_dashboard_page_content_html(request)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_profile_change_page_content_html(request):
|
||||
|
||||
init_Dict = {
|
||||
'firstname': request.user.first_name,
|
||||
'lastname': request.user.last_name,
|
||||
'email': request.user.email,
|
||||
'tel': request.user.user_profile.phone,
|
||||
'country': request.user.user_profile.country,
|
||||
'city': request.user.user_profile.city,
|
||||
}
|
||||
from .forms import RegistrationForm
|
||||
form = RegistrationForm(initial=init_Dict)
|
||||
|
||||
from SubscribesApp.funcs import get_cur_user_subscribe
|
||||
|
||||
Dict = {
|
||||
'profileForm': form,
|
||||
'user_subscribe': get_cur_user_subscribe(request.user)
|
||||
}
|
||||
|
||||
html = render_to_string('blocks/profile/b_profile.html', Dict, request=request)
|
||||
@@ -47,5 +90,7 @@ def get_profile_support_page_content_html(request, data=None):
|
||||
}
|
||||
tpl_name = 'blocks/profile/b_support_tickets.html'
|
||||
|
||||
Dict.update(get_user_timezone_Dict(request.user, request=request))
|
||||
|
||||
html = render_to_string(tpl_name, Dict, request=request)
|
||||
return html
|
||||
@@ -14,6 +14,8 @@ urlpatterns = [
|
||||
|
||||
path('new_route_view/', new_route_view_ajax, name='new_route_view_ajax'),
|
||||
|
||||
path('dashboard/', dashboard_ajax, name='dashboard_ajax'),
|
||||
|
||||
|
||||
path('my_routes/', my_routes_ajax, name='my_routes_ajax'),
|
||||
# path('subscribe/', subscribe_ajax, name='subscribe_ajax'),
|
||||
@@ -23,4 +25,11 @@ urlpatterns = [
|
||||
|
||||
|
||||
path('change_profile/', change_profile_ajax, name='change_profile_ajax'),
|
||||
path('change_profile_confirm/', change_profile_confirm_ajax, name='change_profile_confirm_ajax'),
|
||||
path('change_avatar_confirm/', change_avatar_confirm_ajax, name='change_avatar_confirm_ajax'),
|
||||
|
||||
path('send_message/', send_message_ajax, name='send_message_ajax'),
|
||||
|
||||
path('mailing_subscribe/', mailing_subscribe_ajax, name='mailing_subscribe_ajax')
|
||||
|
||||
]
|
||||
@@ -12,6 +12,12 @@ from datetime import datetime
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from .funcs import *
|
||||
from django.core.exceptions import ValidationError
|
||||
import json
|
||||
from django.core.files import File
|
||||
import base64
|
||||
from django.core.validators import validate_email
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
# @login_required(login_url='/profile/login/')
|
||||
@@ -25,6 +31,182 @@ from .funcs import *
|
||||
# html = render_to_string('blocks/profile/b_subscribe.html', Dict, request=request)
|
||||
# return JsonResponse({'html': html}, status=200)
|
||||
|
||||
|
||||
def mailing_subscribe_ajax(request):
|
||||
if request.method != 'POST':
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
|
||||
email = request.POST['email']
|
||||
user = None
|
||||
if request.user and request.user.is_authenticated:
|
||||
user = request.user
|
||||
user.user_profile.mailing_on = True
|
||||
user.user_profile.save(update_fields=['mailing_on'])
|
||||
|
||||
return JsonResponse({
|
||||
'status': 'sended',
|
||||
'del_form': True,
|
||||
'html': _('Подписка на рассылку для адреса ') + user.email + _(' одобрена')
|
||||
})
|
||||
|
||||
if not user:
|
||||
try:
|
||||
user = User.objects.get(email=email)
|
||||
except User.DoesNotExist:
|
||||
user = None
|
||||
|
||||
if user:
|
||||
redirect_url = f"{reverse('login_profile')}?mailingSubscribeRequired=true"
|
||||
else:
|
||||
redirect_url = f"{reverse('registration_page')}?mailingSubscribeRequired=true"
|
||||
|
||||
return JsonResponse({
|
||||
'status': 'sended',
|
||||
'redirect_url': redirect_url,
|
||||
'email': email
|
||||
})
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({
|
||||
'status': 'error',
|
||||
'html': str(e)
|
||||
}, status=400)
|
||||
|
||||
|
||||
def send_message_ajax(request):
|
||||
if request.method != 'POST':
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
|
||||
data = request.POST
|
||||
if not data and request.body:
|
||||
data = request.body
|
||||
|
||||
from GeneralApp.funcs_options import get_options_by_opt_types, get_mail_send_options
|
||||
sets = get_options_by_opt_types(['domain', 'project_name'], only_vals=True)
|
||||
|
||||
request_type = None
|
||||
subject = _('Получен запрос')
|
||||
if 'form_name' in data:
|
||||
if data['form_name'] == 'msg_from_advertisement':
|
||||
subject = _('Получен запрос на рекламу')
|
||||
request_type = _('запрос на рекламу')
|
||||
if data['form_name'] == 'msg_from_partners':
|
||||
subject = _('Получен запрос на подключение к партнерской сети')
|
||||
request_type = _('запрос на партнерство')
|
||||
if data['form_name'] == 'msg_from_customer_service':
|
||||
subject = _('Получен запрос в службу техподдержки')
|
||||
request_type = _('запрос в техподдержку')
|
||||
if data['form_name'] == 'msg_from_contacts':
|
||||
subject = _('Получен запрос со страницы контактов')
|
||||
request_type = _('запрос со страницы контактов')
|
||||
if data['form_name'] == 'msg_from_about_service':
|
||||
subject = _('Получен запрос со страницы О сервисе')
|
||||
request_type = _('запрос со страницы о сервисе')
|
||||
if data['form_name'] == 'msg_from_footer':
|
||||
subject = _('Получен запрос на рассылку')
|
||||
request_type = _('запрос на рассылку')
|
||||
|
||||
request_type_str = ''
|
||||
name_str = ''
|
||||
phone_str = ''
|
||||
email_str = ''
|
||||
msg_str = ''
|
||||
form_type = 'one_field'
|
||||
errors = {}
|
||||
|
||||
for name, val in data.items():
|
||||
if not val:
|
||||
errors.update({name: _('Обязательное поле')})
|
||||
|
||||
if name == 'form_name':
|
||||
request_type_str = f'<b>{_("Тип запроса")}:</b> {request_type}<br>'
|
||||
|
||||
if name == 'name':
|
||||
name_str = f'<b>{_("Имя")}:</b> {data["name"]}<br>'
|
||||
if form_type == 'one_field':
|
||||
form_type = 'two_fields'
|
||||
|
||||
if name =='phone':
|
||||
from BaseModels.validators.form_field_validators import get_phone_valid_error
|
||||
error = get_phone_valid_error(data["phone"])
|
||||
if error:
|
||||
errors.update({name: _(error)})
|
||||
phone_str = f'<b>{_("Телефон")}:</b> {data["phone"]}<br>'
|
||||
|
||||
if name =='email':
|
||||
try:
|
||||
error = validate_email(data["email"])
|
||||
except ValidationError as e:
|
||||
error = e.message
|
||||
if error:
|
||||
errors.update({name: _(error)})
|
||||
email_str = f'<b>{_("email")}:</b> {data["email"]}<br>'
|
||||
|
||||
if name =='text_msg':
|
||||
msg_str = (f'<b>{_("Сообщение")}:</b><br>'
|
||||
f'<div style="margin-left: 40px; line-height: 20px;">{data["text_msg"]}</div><br>')
|
||||
form_type = 'full'
|
||||
|
||||
if errors:
|
||||
Dict = {
|
||||
'form': data.dict()
|
||||
}
|
||||
Dict['form'].update({
|
||||
'errors': errors
|
||||
})
|
||||
|
||||
tpl = 'forms/f_one_field_form.html'
|
||||
if form_type == 'full':
|
||||
tpl = 'forms/f_feedback_form.html'
|
||||
elif form_type == 'two_fields':
|
||||
tpl = 'forms/f_commercial_offer.html'
|
||||
|
||||
html = render_to_string(tpl, Dict, request)
|
||||
return JsonResponse({'html': html}, status=400)
|
||||
|
||||
Dict = {
|
||||
'logo': f'{sets["domain"]}/static/img/svg/LogoMobile.svg',
|
||||
'project_name': sets['project_name'],
|
||||
'message_title': subject,
|
||||
'message_text': f'<p><b>{_("ДАННЫЕ ЗАПРОСА")}</b></p>'
|
||||
f'<p style="padding-left: 20px; line-height: 30px;">'
|
||||
f'{request_type_str}'
|
||||
f'{name_str}'
|
||||
f'{phone_str}'
|
||||
f'{email_str}'
|
||||
f'{msg_str}'
|
||||
f'</p>'
|
||||
}
|
||||
|
||||
html = render_to_string('mail/m_request_offer.html', Dict, request)
|
||||
from BaseModels.mailSender import admin_send_mail_by_SMTPlib
|
||||
mail_sets = get_mail_send_options()
|
||||
to = [mail_sets['sender_email'], 'web@syncsystems.net']
|
||||
res = admin_send_mail_by_SMTPlib(
|
||||
mail_sets,
|
||||
subject=subject,
|
||||
from_email=mail_sets['sender_email'], to=to,
|
||||
html_content=html
|
||||
)
|
||||
|
||||
html = render_to_string('widgets/w_msg_send_success.html', Dict, request)
|
||||
|
||||
return JsonResponse({
|
||||
'status': 'sended',
|
||||
'html': html
|
||||
})
|
||||
except Exception as e:
|
||||
return JsonResponse({
|
||||
'status': 'error',
|
||||
'html': str(e)
|
||||
}, status=400)
|
||||
|
||||
|
||||
@login_required(login_url='/profile/login/')
|
||||
def chats_ajax(request):
|
||||
if request.method != 'POST':
|
||||
@@ -50,6 +232,7 @@ def chats_ajax(request):
|
||||
'receivers': receivers,
|
||||
# 'messages': cur_chat_msgs
|
||||
}
|
||||
Dict.update(get_user_timezone_Dict(request.user, request=request))
|
||||
|
||||
html = render_to_string('blocks/profile/b_chats.html', Dict, request=request)
|
||||
return JsonResponse({'html': html}, status=200)
|
||||
@@ -64,12 +247,132 @@ def support_tickets_ajax(request):
|
||||
return JsonResponse({'html': html}, status=200)
|
||||
|
||||
|
||||
@login_required(login_url='/profile/login/')
|
||||
def change_avatar_confirm_ajax(request):
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.exceptions import RequestDataTooBig
|
||||
|
||||
if request.method != 'POST':
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
|
||||
file_data = json.loads(request.body)
|
||||
head, content = file_data['file'].split(',')
|
||||
content = base64.b64decode(content)
|
||||
file = ContentFile(content)
|
||||
request.user.user_profile.avatar.save(file_data['file_name'], file)
|
||||
request.user.user_profile.save(update_fields=['avatar'])
|
||||
except RequestDataTooBig:
|
||||
msg = _('Слишком большой размер файла. Размер файла не должен быть больше 3МБ')
|
||||
print(msg)
|
||||
return JsonResponse({'error': msg}, status=400)
|
||||
|
||||
except Exception as e:
|
||||
msg = f'change_avatar_confirm_ajax Error = {str(e)}'
|
||||
print(msg)
|
||||
return JsonResponse({'error': msg}, status=400)
|
||||
|
||||
return JsonResponse({'url': request.user.user_profile.avatar.url})
|
||||
|
||||
|
||||
@login_required(login_url='/profile/login/')
|
||||
def change_profile_confirm_ajax(request):
|
||||
if request.method != 'POST':
|
||||
raise Http404
|
||||
|
||||
data = request.POST
|
||||
if not data:
|
||||
data = json.loads(request.body)
|
||||
|
||||
from .forms import RegistrationForm
|
||||
kwargs = {
|
||||
'not_required_password': True,
|
||||
'not_required_agreement': True,
|
||||
'not_required_email': True,
|
||||
'create_new_account': False,
|
||||
}
|
||||
form = RegistrationForm(data, **kwargs)
|
||||
if not form.is_valid():
|
||||
form.initial = data
|
||||
Dict = {'profileForm': form}
|
||||
|
||||
html = render_to_string('blocks/profile/b_profile.html', Dict, request=request)
|
||||
return JsonResponse({'html': html}, status=400)
|
||||
|
||||
data_for_save = {}
|
||||
users = User.objects.filter(id=request.user.id)
|
||||
if 'firstname' in data:
|
||||
data_for_save.update({'first_name': data['firstname']})
|
||||
if 'lastname' in data:
|
||||
data_for_save.update({'last_name': data['lastname']})
|
||||
if 'email' in data:
|
||||
data_for_save.update({'email': data['email']})
|
||||
data_for_save.update({'username': data['email']})
|
||||
|
||||
if data_for_save:
|
||||
users.update(**data_for_save)
|
||||
|
||||
data_for_save = {}
|
||||
|
||||
password = None
|
||||
confirm_password = None
|
||||
if 'password' in data:
|
||||
password = data['password']
|
||||
if 'confirm_password' in data:
|
||||
confirm_password = data['confirm_password']
|
||||
if password and confirm_password:
|
||||
if password != confirm_password:
|
||||
errors = {
|
||||
'password': _("Не совпадают пароли"),
|
||||
'confirm_password': _("Не совпадают пароли"),
|
||||
}
|
||||
raise ValidationError(errors)
|
||||
|
||||
request.user.set_password(password)
|
||||
request.user.save()
|
||||
|
||||
|
||||
data_for_save = {}
|
||||
user_profiles = UserProfile.objects.filter(user__in=users)
|
||||
if 'country' in data:
|
||||
data_for_save.update({'country': data['country']})
|
||||
if 'city' in data:
|
||||
data_for_save.update({'city': data['city']})
|
||||
if 'tel' in data:
|
||||
data_for_save.update({'phone': data['tel']})
|
||||
|
||||
if data_for_save:
|
||||
user_profiles.update(**data_for_save)
|
||||
|
||||
html = get_profile_change_page_content_html(request)
|
||||
return JsonResponse({'html': html}, status=200)
|
||||
|
||||
|
||||
@login_required(login_url='/profile/login/')
|
||||
def dashboard_ajax(request):
|
||||
if request.method != 'POST':
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
|
||||
from .funcs import get_dashboard_page_content_html
|
||||
html = get_dashboard_page_content_html(request)
|
||||
|
||||
except Exception as e:
|
||||
msg = f'dashboard_ajax Error = {str(e)}'
|
||||
print(msg)
|
||||
html = msg
|
||||
|
||||
return JsonResponse({'html': html}, status=200)
|
||||
|
||||
|
||||
@login_required(login_url='/profile/login/')
|
||||
def change_profile_ajax(request):
|
||||
if request.method != 'POST':
|
||||
raise Http404
|
||||
|
||||
|
||||
html = get_profile_change_page_content_html(request)
|
||||
return JsonResponse({'html': html}, status=200)
|
||||
|
||||
@@ -108,10 +411,13 @@ def login_ajax(request):
|
||||
user = authenticate(username=form.data['username'], password=form.data['password'])
|
||||
if user is not None:
|
||||
auth.login(request, user)
|
||||
if 'mailingSubscribeRequired' in data and data['mailingSubscribeRequired'] == 'true':
|
||||
user.user_profile.mailing_on = True
|
||||
user.user_profile.save(update_fields=['mailing_on'])
|
||||
else:
|
||||
errors_Dict = {
|
||||
'errors': {
|
||||
'all__': f'неверный логин и\или пароль'
|
||||
'all__': _("неверный логин и\или пароль")
|
||||
}
|
||||
}
|
||||
Dict = {'form': errors_Dict}
|
||||
@@ -120,7 +426,7 @@ def login_ajax(request):
|
||||
|
||||
|
||||
res_Dict = {
|
||||
'redirect_url': reverse('user_profile')
|
||||
'redirect_url': reverse('profile_page', args=['dashboard'])
|
||||
}
|
||||
|
||||
return JsonResponse(res_Dict)
|
||||
@@ -129,7 +435,7 @@ def login_ajax(request):
|
||||
|
||||
errors_Dict = {
|
||||
'errors': {
|
||||
'all__': f'ошибка в запросе = {str(e)}'
|
||||
'all__': f'{_("ошибка в запросе")} = {str(e)}'
|
||||
}
|
||||
}
|
||||
Dict = {'form': errors_Dict}
|
||||
@@ -137,6 +443,42 @@ def login_ajax(request):
|
||||
return JsonResponse({'html': html}, status=400)
|
||||
|
||||
|
||||
|
||||
|
||||
def send_registration_mail(data_Dict, user):
|
||||
|
||||
try:
|
||||
|
||||
from GeneralApp.funcs_options import get_options_by_opt_types, get_mail_send_options
|
||||
sets = get_options_by_opt_types(['domain', 'project_name'], only_vals=True)
|
||||
|
||||
subject = _('Добро пожаловать в Trip With Bonus!')
|
||||
|
||||
Dict = {
|
||||
'logo': f'{sets["domain"]}/static/img/svg/LogoMobile.svg',
|
||||
'project_name': sets['project_name'],
|
||||
'message_title': subject,
|
||||
}
|
||||
Dict.update(data_Dict)
|
||||
|
||||
html = render_to_string('mail/m_registration.html', Dict)
|
||||
from BaseModels.mailSender import admin_send_mail_by_SMTPlib
|
||||
mail_sets = get_mail_send_options()
|
||||
to = [user.email, 'web@syncsystems.net', 'sa@a3-global.com', 'sysadmin.hax@gmail.com']
|
||||
res = admin_send_mail_by_SMTPlib(
|
||||
mail_sets,
|
||||
subject=subject,
|
||||
from_email=mail_sets['sender_email'], to=to,
|
||||
html_content=html
|
||||
)
|
||||
|
||||
return res
|
||||
except Exception as e:
|
||||
print(f'send_registration_mail Error = {str(e)}')
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def registration_ajax(request):
|
||||
if request.method != 'POST':
|
||||
raise Http404
|
||||
@@ -152,17 +494,20 @@ def registration_ajax(request):
|
||||
html = render_to_string('forms/f_registration.html', Dict, request=request)
|
||||
return JsonResponse({'html': html}, status=400)
|
||||
|
||||
users = User.objects.filter(email=form.data['email'])
|
||||
if users:
|
||||
form.errors['email'] = 'Пользователь с указанным email уже существует'
|
||||
Dict = {'form': form}
|
||||
html = render_to_string('forms/f_registration.html', Dict, request=request)
|
||||
return JsonResponse({'html': html}, status=400)
|
||||
# users = User.objects.filter(email=form.data['email'])
|
||||
# if users:
|
||||
# form.errors['email'] = _("Пользователь с указанным email уже существует")
|
||||
# Dict = {'form': form}
|
||||
# html = render_to_string('forms/f_registration.html', Dict, request=request)
|
||||
# return JsonResponse({'html': html}, status=400)
|
||||
|
||||
user = User.objects.create_user(username=form.data['email'], email=form.data['email'], password=form.data['password'])
|
||||
# user = auth.authenticate(username=new_user_Dict['name'], password=new_user_Dict['pass'])
|
||||
if user:
|
||||
auth.login(request, user)
|
||||
auth.login(request, user, backend='django.contrib.auth.backends.ModelBackend')
|
||||
|
||||
if 'mailingSubscribeRequired' in data and data['mailingSubscribeRequired'] == 'true':
|
||||
user.user_profile.mailing_on = True
|
||||
|
||||
user.last_name = form.data['lastname']
|
||||
user.first_name = form.data['firstname']
|
||||
@@ -170,8 +515,14 @@ def registration_ajax(request):
|
||||
user.user_profile.phone = form.data['tel']
|
||||
user.user_profile.save()
|
||||
|
||||
mail_Dict = {
|
||||
'user': user,
|
||||
'pass': form.data['password']
|
||||
}
|
||||
res = send_registration_mail(mail_Dict, user)
|
||||
|
||||
res_Dict = {
|
||||
'redirect_url': reverse('user_profile')
|
||||
'redirect_url': reverse('profile_page', args=['dashboard'])
|
||||
}
|
||||
|
||||
return JsonResponse(res_Dict)
|
||||
@@ -180,7 +531,7 @@ def registration_ajax(request):
|
||||
|
||||
errors_Dict = {
|
||||
'errors': {
|
||||
'__all__': f'ошибка в запросе = {str(e)}'
|
||||
'__all__': f'{_("ошибка в запросе")} = {str(e)}'
|
||||
}
|
||||
}
|
||||
Dict = {'form': errors_Dict}
|
||||
|
||||
@@ -31,10 +31,12 @@ class ResponseInterceptionMiddleware:
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
for_save_to_session = None
|
||||
|
||||
try:
|
||||
if type(response) == JsonResponse:
|
||||
for_save_to_session = request.user.user_profile.pop_node_by_name('for_save_to_session')
|
||||
if request.user and not request.user.is_anonymous and request.user.user_profile:
|
||||
for_save_to_session = request.user.user_profile.pop_node_by_name('for_save_to_session')
|
||||
if for_save_to_session:
|
||||
data = json.loads(response.content)
|
||||
data.update(for_save_to_session)
|
||||
|
||||
18
AuthApp/migrations/0005_userprofile_mailing_on.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.2 on 2024-02-01 17:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('AuthApp', '0004_alter_userprofile_account_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userprofile',
|
||||
name='mailing_on',
|
||||
field=models.BooleanField(default=False, verbose_name='Рассылка'),
|
||||
),
|
||||
]
|
||||
@@ -51,6 +51,14 @@ class UserProfile(BaseModel):
|
||||
on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
mailing_on = models.BooleanField(default=False, verbose_name=_('Рассылка'))
|
||||
|
||||
def get_timezone(self):
|
||||
tz = None
|
||||
if 'user_timezone' in self.json_data:
|
||||
tz = self.json_data['user_timezone']
|
||||
return tz
|
||||
|
||||
def save_user_alerts_to_session(self, request):
|
||||
for_save_to_session = self.get_node_by_name('for_save_to_session')
|
||||
if for_save_to_session:
|
||||
@@ -74,8 +82,23 @@ class UserProfile(BaseModel):
|
||||
|
||||
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
user_profile = None
|
||||
if created:
|
||||
UserProfile.objects.create(user=instance)
|
||||
user_profile = UserProfile.objects.create(user=instance)
|
||||
|
||||
# if user_profile and not user_profile.avatar:
|
||||
# from allauth.socialaccount.models import SocialAccount
|
||||
# # try:
|
||||
# social_accounts = SocialAccount.objects.filter(user=instance)
|
||||
# if social_accounts:
|
||||
# for social_account in social_accounts:
|
||||
# if 'picture' in social_account.account.extra_data and social_account.account.extra_data['picture']:
|
||||
# with open(social_account.account.extra_data['picture'], 'rb') as fd:
|
||||
# user_profile.avatar.save(f'avatar_{instance.id}.jpeg', fd.read(), True)
|
||||
#
|
||||
# # except Exception as e:
|
||||
# # msg = f'post_save create_user_profile Error = {str(e)}'
|
||||
# # print(msg)
|
||||
|
||||
|
||||
post_save.connect(create_user_profile, sender=User, dispatch_uid='post_save_connect')
|
||||
@@ -85,6 +108,7 @@ def preSaveUser(sender, instance, **kwargs):
|
||||
if not instance.email:
|
||||
instance.email = str(instance.username).lower()
|
||||
|
||||
|
||||
try:
|
||||
instance.user_profile.modifiedDT = datetime.now()
|
||||
except:
|
||||
|
||||
@@ -8,7 +8,7 @@ from django.contrib.auth import views
|
||||
urlpatterns = [
|
||||
|
||||
path('registration/', registration_View, name='registration_page'),
|
||||
path('', user_profile_View, name='user_profile'),
|
||||
# path('', user_profile_View, name='user_profile'),
|
||||
# path('page/chat/<int:user_id>/', chat_w_user_View, name='chat_w_user'),
|
||||
# path('page/chat/', chat_w_user_View, name='chat_w_user_wo_user_id'),
|
||||
|
||||
@@ -21,6 +21,8 @@ urlpatterns = [
|
||||
path('login/', login_View, name='login_profile'),
|
||||
path('logout/', logout_View, name='logout_profile'),
|
||||
|
||||
path('account/signup/', login_View, name="custom_singup" ),
|
||||
|
||||
|
||||
# ajax ----------------
|
||||
# url(r'^login$', user_login_View_ajax, name='user_login_View_ajax'),
|
||||
|
||||
@@ -13,14 +13,20 @@ from django.utils.translation import gettext as _
|
||||
from datetime import datetime
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .funcs import *
|
||||
|
||||
from GeneralApp.funcs import get_inter_http_respose
|
||||
|
||||
def registration_View(request):
|
||||
|
||||
Dict = {}
|
||||
|
||||
if request.GET and 'mailingSubscribeRequired' in request.GET and request.GET['mailingSubscribeRequired'] == 'true':
|
||||
request.session['mailingSubscribeRequired'] = 'true'
|
||||
|
||||
# if request.p
|
||||
|
||||
t = loader.get_template('pages/profile/p_registration.html')
|
||||
return HttpResponse(t.render(Dict, request))
|
||||
return get_inter_http_respose(t, Dict, request)
|
||||
# return HttpResponse(t.render(Dict, request))
|
||||
|
||||
|
||||
# def create_route_for_customer_View(request):
|
||||
@@ -39,11 +45,33 @@ def registration_View(request):
|
||||
def profile_page_View(request, page_name, id=None):
|
||||
Dict = {
|
||||
'page_html': get_profile_page_content_html(request, page_name, id),
|
||||
'page_name': page_name
|
||||
'page_name': page_name,
|
||||
'page_type': 'profile'
|
||||
}
|
||||
|
||||
if request.session and 'mailingSubscribeRequired' in request.session and request.session['mailingSubscribeRequired'] == 'true':
|
||||
request.user.user_profile.mailing_on = True
|
||||
request.user.user_profile.save(update_fields=['mailing_on'])
|
||||
del request.session['mailingSubscribeRequired']
|
||||
|
||||
title = f"{_('Личный кабинет пользователя')} {request.user.first_name} {request.user.last_name}"
|
||||
|
||||
Dict.update({
|
||||
'page': {
|
||||
'title': title,
|
||||
'description': title,
|
||||
'keywords': title,
|
||||
}
|
||||
})
|
||||
|
||||
# if request.GET and 'mobile' in request.GET and request.GET['mobile'] == 'true':
|
||||
# Dict.update({
|
||||
# 'mobile': True
|
||||
# })
|
||||
|
||||
t = loader.get_template('pages/profile/p_user_profile.html')
|
||||
return HttpResponse(t.render(Dict, request))
|
||||
return get_inter_http_respose(t, Dict, request)
|
||||
# return HttpResponse(t.render(Dict, request))
|
||||
|
||||
|
||||
# @login_required(login_url='/profile/login/')
|
||||
@@ -84,7 +112,8 @@ def user_profile_View(request):
|
||||
# request.COOKIES['user_id'] = request.user.id
|
||||
|
||||
t = loader.get_template('pages/profile/p_user_profile.html')
|
||||
response = HttpResponse(t.render(Dict, request))
|
||||
response = get_inter_http_respose(t, Dict, request)
|
||||
# response = HttpResponse(t.render(Dict, request))
|
||||
response.set_cookie('user_id', request.user.id)
|
||||
return response
|
||||
|
||||
@@ -98,10 +127,18 @@ def logout_View(request):
|
||||
|
||||
def login_View(request):
|
||||
|
||||
Dict = {}
|
||||
from allauth.socialaccount.models import SocialApp
|
||||
auth_google_allow = SocialApp.objects.filter(provider='google')
|
||||
Dict = {
|
||||
'auth_google_allow': auth_google_allow
|
||||
}
|
||||
|
||||
if request.GET and 'mailingSubscribeRequired' in request.GET and request.GET['mailingSubscribeRequired'] == 'true':
|
||||
request.session['mailingSubscribeRequired'] = 'true'
|
||||
|
||||
t = loader.get_template('pages/profile/p_login.html')
|
||||
return HttpResponse(t.render(Dict, request))
|
||||
return get_inter_http_respose(t, Dict, request)
|
||||
# return HttpResponse(t.render(Dict, request))
|
||||
|
||||
|
||||
|
||||
@@ -148,7 +185,7 @@ def create_personal_user(data, creator):
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'error': 'Ошибка добавление нового пользователя = {0}'.format(str(e)),
|
||||
'error': f'{_("Ошибка добавление нового пользователя")} = {str(e)}',
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +222,7 @@ def create_temporary_user():
|
||||
from django.utils.translation import gettext as _
|
||||
user_id = str(uuid1().hex)[:10]
|
||||
user_name = u'user'+user_id
|
||||
mail = user_id+u'@truenergy.by'
|
||||
mail = user_id+u'@domain'
|
||||
user = User.objects.create_user(mail, mail,user_id)
|
||||
user.first_name = _(u'незарег. пользователь')
|
||||
user.last_name = u''
|
||||
|
||||
70
BaseModels/PIL/pillow_backend.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.functional import cached_property
|
||||
from PIL import Image
|
||||
|
||||
from ckeditor_uploader import utils
|
||||
|
||||
|
||||
THUMBNAIL_SIZE = getattr(settings, "CKEDITOR_THUMBNAIL_SIZE", (75, 75))
|
||||
|
||||
|
||||
class PillowBackend:
|
||||
def __init__(self, storage_engine, file_object):
|
||||
self.file_object = file_object
|
||||
self.storage_engine = storage_engine
|
||||
|
||||
@cached_property
|
||||
def is_image(self):
|
||||
try:
|
||||
Image.open(
|
||||
BytesIO(self.file_object.read())
|
||||
).verify() # verify closes the file
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
finally:
|
||||
self.file_object.seek(0)
|
||||
|
||||
def _compress_image(self, image):
|
||||
quality = getattr(settings, "CKEDITOR_IMAGE_QUALITY", 75)
|
||||
image = image.resize(image.size, Image.ANTIALIAS).convert("RGB")
|
||||
image_tmp = BytesIO()
|
||||
image.save(image_tmp, format="JPEG", quality=quality, optimize=True)
|
||||
return image_tmp
|
||||
|
||||
def save_as(self, filepath):
|
||||
if not self.is_image:
|
||||
saved_path = self.storage_engine.save(filepath, self.file_object)
|
||||
return saved_path
|
||||
|
||||
image = Image.open(self.file_object)
|
||||
|
||||
should_compress = getattr(settings, "CKEDITOR_FORCE_JPEG_COMPRESSION", False)
|
||||
is_animated = hasattr(image, "is_animated") and image.is_animated
|
||||
if should_compress and not is_animated:
|
||||
file_object = self._compress_image(image)
|
||||
filepath = f"{os.path.splitext(filepath)[0]}.jpg"
|
||||
saved_path = self.storage_engine.save(filepath, file_object)
|
||||
else:
|
||||
file_object = self.file_object
|
||||
saved_path = self.storage_engine.save(filepath, self.file_object)
|
||||
|
||||
if not is_animated:
|
||||
self.create_thumbnail(file_object, saved_path)
|
||||
return saved_path
|
||||
|
||||
def create_thumbnail(self, file_object, file_path):
|
||||
thumbnail_filename = utils.get_thumb_filename(file_path)
|
||||
thumbnail_io = BytesIO()
|
||||
# File object after saving e.g. to S3 can be closed.
|
||||
try:
|
||||
image = Image.open(file_object).convert("RGBA")
|
||||
except ValueError:
|
||||
file_object = self.storage_engine.open(file_path)
|
||||
image = Image.open(file_object).convert("RGBA")
|
||||
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
|
||||
image.save(thumbnail_io, format="PNG", optimize=True)
|
||||
return self.storage_engine.save(thumbnail_filename, thumbnail_io)
|
||||
@@ -26,9 +26,9 @@ def send_SMS(phone, text, urgent=False, staff=False):
|
||||
phone.encode('utf-8')
|
||||
|
||||
http_request = 'http://cp.websms.by/?r=api/msg_send' \
|
||||
'&user=administrator@baldenini.by' \
|
||||
'&apikey=zTwevODOYl' \
|
||||
'&sender=Baldenini'
|
||||
'&user=administrator@site.by' \
|
||||
'&apikey=key' \
|
||||
'&sender=company'
|
||||
# '&test=1'
|
||||
|
||||
if urgent:
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.utils.safestring import mark_safe
|
||||
from django.db import models
|
||||
from django.contrib import admin
|
||||
from django.contrib.contenttypes.admin import GenericTabularInline, GenericStackedInline
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
import re
|
||||
# from modeltranslation.admin import TranslationAdmin
|
||||
@@ -36,14 +37,14 @@ def get_base_fieldsets():
|
||||
)
|
||||
}],
|
||||
|
||||
(u'Описание и текст', {
|
||||
(_('Описание и текст'), {
|
||||
'classes': ['wide', 'collapse'],
|
||||
'fields': (
|
||||
'description', 'text',
|
||||
)
|
||||
}),
|
||||
|
||||
(u'Промо ФОН', {
|
||||
(_('Промо ФОН'), {
|
||||
'classes': ['wide', 'collapse'],
|
||||
'fields': (
|
||||
'background_promo_show', 'background_promo_inherits',
|
||||
@@ -63,7 +64,7 @@ def get_base_fieldsets():
|
||||
)
|
||||
}),
|
||||
|
||||
(u'Партнерские ссылки', {
|
||||
(_('Партнерские ссылки'), {
|
||||
'classes': ['wide', 'collapse'],
|
||||
'fields': (
|
||||
'link_left_promo_show',
|
||||
@@ -218,7 +219,7 @@ class Admin_GenericBaseIconStackedInline(GenericStackedInline):
|
||||
def image_thumb(self, obj):
|
||||
return get_image_thumb(self, obj)
|
||||
|
||||
image_thumb.short_description = u'Миниатюра'
|
||||
image_thumb.short_description = _('Миниатюра')
|
||||
image_thumb.allow_tags = True
|
||||
|
||||
|
||||
@@ -231,7 +232,7 @@ class Admin_BaseIconStackedInline(admin.StackedInline):
|
||||
def image_thumb(self, obj):
|
||||
return get_image_thumb(self, obj)
|
||||
|
||||
image_thumb.short_description = u'Миниатюра'
|
||||
image_thumb.short_description = _('Миниатюра')
|
||||
image_thumb.allow_tags = True
|
||||
|
||||
|
||||
@@ -243,7 +244,7 @@ class Admin_BaseIconTabularModel(admin.TabularInline):
|
||||
def image_thumb(self, obj):
|
||||
return get_image_thumb(self, obj)
|
||||
|
||||
image_thumb.short_description = u'Миниатюра'
|
||||
image_thumb.short_description = _('Миниатюра')
|
||||
image_thumb.allow_tags = True
|
||||
|
||||
|
||||
@@ -258,7 +259,7 @@ class Admin_BaseIconModel(admin.ModelAdmin):
|
||||
|
||||
return s
|
||||
|
||||
description_exists.short_description = u'Описание'
|
||||
description_exists.short_description = _('Описание')
|
||||
description_exists.allow_tags = True
|
||||
|
||||
def formfield_for_dbfield (self, db_field, request, **kwargs):
|
||||
@@ -271,7 +272,7 @@ class Admin_BaseIconModel(admin.ModelAdmin):
|
||||
def image_thumb(self, obj):
|
||||
return get_image_thumb(self, obj)
|
||||
|
||||
image_thumb.short_description = u'Миниатюра'
|
||||
image_thumb.short_description = _('Миниатюра')
|
||||
image_thumb.allow_tags = True
|
||||
|
||||
|
||||
|
||||
@@ -112,6 +112,23 @@ class BaseModelViewPage(BaseModel):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def get_title(self):
|
||||
if self.seo_title:
|
||||
return self.seo_title
|
||||
elif self.title:
|
||||
return self.title
|
||||
else:
|
||||
return self.name
|
||||
|
||||
def get_description(self):
|
||||
if self.seo_description:
|
||||
return self.seo_description
|
||||
else:
|
||||
return self.description
|
||||
|
||||
def get_FAQ_items(self):
|
||||
return self.FAQ_items.filter(enable=True).order_by('order')
|
||||
|
||||
def get_description_exists(self):
|
||||
if self.description:
|
||||
return True
|
||||
|
||||
@@ -96,27 +96,27 @@ def sortByLength(inputStr):
|
||||
return len(inputStr)
|
||||
|
||||
|
||||
def add_domain(request, url, add_lang=False):
|
||||
domain = get_domain_by_request(request)
|
||||
if add_lang:
|
||||
cur_lang = get_cur_lang_by_request(request)
|
||||
return '{0}/{1}/{2}'.format(domain, cur_lang, url)
|
||||
else:
|
||||
return '{0}{1}'.format(domain, url)
|
||||
# def add_domain(request, url, add_lang=False):
|
||||
# domain = get_domain_by_request(request)
|
||||
# if add_lang:
|
||||
# cur_lang = get_cur_lang_by_request(request)
|
||||
# return '{0}/{1}/{2}'.format(domain, cur_lang, url)
|
||||
# else:
|
||||
# return '{0}{1}'.format(domain, url)
|
||||
|
||||
|
||||
def get_domain_by_request(request):
|
||||
from project_sets import domain
|
||||
if request.query_params and 'domain' in request.query_params:
|
||||
return request.query_params['domain']
|
||||
return domain
|
||||
|
||||
|
||||
def get_cur_lang_by_request(request):
|
||||
from project_sets import lang
|
||||
if request.query_params and 'cur_lang' in request.query_params:
|
||||
return request.query_params['cur_lang']
|
||||
return lang
|
||||
# def get_domain_by_request(request):
|
||||
# from project_sets import domain
|
||||
# if request.query_params and 'domain' in request.query_params:
|
||||
# return request.query_params['domain']
|
||||
# return domain
|
||||
#
|
||||
#
|
||||
# def get_cur_lang_by_request(request):
|
||||
# from project_sets import lang
|
||||
# if request.query_params and 'cur_lang' in request.query_params:
|
||||
# return request.query_params['cur_lang']
|
||||
# return lang
|
||||
|
||||
|
||||
def get_img_type_by_request(request):
|
||||
@@ -416,6 +416,8 @@ def kill_pretexts(txt):
|
||||
return ' '.join(words)
|
||||
|
||||
|
||||
|
||||
|
||||
def stay_only_text_and_numbers(txt):
|
||||
bad_symbols = '"~`{}[]|!@#$%^&*()_+№;:?= '
|
||||
nums = '0123456789'
|
||||
|
||||
@@ -20,11 +20,7 @@ import random
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
# tech@truenergy.by
|
||||
# k7n2d3ZFZo4@CU5$4YDk
|
||||
|
||||
# administrator@truenergy.by
|
||||
# 6&#WfW8$qR2w8uv69e5$
|
||||
|
||||
|
||||
# def fix_mailing_links_in_mail(html):
|
||||
@@ -86,34 +82,20 @@ def prepare_xls_attach_by_xls_virtual_file(virtual_file, filename):
|
||||
return file
|
||||
|
||||
|
||||
def admin_send_mail_by_SMTPlib(subject, from_email, to, html_content, attachments=None):
|
||||
def admin_send_mail_by_SMTPlib(sets, subject, from_email, to, html_content, attachments=None):
|
||||
res = None
|
||||
|
||||
try:
|
||||
# smtp_server = 'mail.cln.by' # 'mail.truenergy.by'
|
||||
# smtp_port = 2525 # 587
|
||||
# smtp_password = 'clNdt6a8a' # u'98q3$IjxH%RUIxySw8R2'
|
||||
# smtp_login = 'support@cln.by' # 'support@truenergy.by'
|
||||
# from_email = smtp_login
|
||||
|
||||
try:
|
||||
smtp_server = 'mail.truenergy.by'
|
||||
smtp_port = 587
|
||||
smtp_password = 'eg4$#95Xp0T*V%ig5BbR'
|
||||
smtp_login = 'support@truenergy.by'
|
||||
res = send_mail_by_SMTPlib(subject, from_email, to, html_content, smtp_server, smtp_port, smtp_login,
|
||||
smtp_password, attachments)
|
||||
except:
|
||||
smtp_server = 'mail.truenergy.by'
|
||||
smtp_port = 25
|
||||
smtp_password = 'PowH@aL0a4%$iz0Uo5V$'
|
||||
smtp_login = 'tech@truenergy.by'
|
||||
res = send_mail_by_SMTPlib(subject, smtp_login, to, html_content, smtp_server, smtp_port, smtp_login,
|
||||
smtp_password, attachments)
|
||||
smtp_server = sets['mail_server_url']
|
||||
smtp_port = sets['mail_server_smtp_port']
|
||||
smtp_password = sets['sender_mail_password']
|
||||
smtp_login = sets['sender_mail_login']
|
||||
res = send_mail_by_SMTPlib(sets, subject, from_email, to, html_content, smtp_server, smtp_port, smtp_login,
|
||||
smtp_password, attachments)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
# from Baldenini_site.SMS_sender import send_SMS
|
||||
# send_SMS(u'375296177827', u'send_mail_by_SMTPlib error = {0}'.format(str(e)), urgent=True)
|
||||
msg = 'admin_send_mail_by_SMTPlib error = {0}'.format(str(e))
|
||||
print(msg)
|
||||
# techSendMail(msg)
|
||||
@@ -121,17 +103,17 @@ def admin_send_mail_by_SMTPlib(subject, from_email, to, html_content, attachment
|
||||
return str(res)
|
||||
|
||||
|
||||
def send_mail_by_SMTPlib(subject, from_email, to_init, html_content, smtp_server, smtp_port, smtp_login, smtp_password,
|
||||
def send_mail_by_SMTPlib(sets, subject, from_email, to_init, html_content, smtp_server, smtp_port, smtp_login, smtp_password,
|
||||
attachments=None):
|
||||
to = to_init
|
||||
if not settings.prod_server:
|
||||
to = 'web@syncsystems.net'
|
||||
else:
|
||||
to = to_init
|
||||
try:
|
||||
from settings_local import DEBUG
|
||||
except:
|
||||
print('get settings_local fail')
|
||||
# if not settings.prod_server:
|
||||
# to = 'web@syncsystems.net'
|
||||
# else:
|
||||
# to = to_init
|
||||
# try:
|
||||
# from settings_local import DEBUG
|
||||
# except:
|
||||
# print('get settings_local fail')
|
||||
|
||||
res = None
|
||||
mail_lib = None
|
||||
@@ -163,16 +145,16 @@ def send_mail_by_SMTPlib(subject, from_email, to_init, html_content, smtp_server
|
||||
res = None
|
||||
|
||||
if type(to) in (list, tuple):
|
||||
if 'support@truenergy.by' in to:
|
||||
to.remove('support@truenergy.by')
|
||||
if sets['sender_email'] in to:
|
||||
to.remove(sets['sender_email'])
|
||||
|
||||
if len(to) > 1:
|
||||
to_str = u', '.join(to)
|
||||
else:
|
||||
to_str = to[0]
|
||||
else:
|
||||
if to == 'support@truenergy.by':
|
||||
return None
|
||||
# if to == sets['sender_email']:
|
||||
# return None
|
||||
to_str = to
|
||||
to = []
|
||||
to.append(to_str)
|
||||
@@ -216,8 +198,6 @@ def send_mail_by_SMTPlib(subject, from_email, to_init, html_content, smtp_server
|
||||
# print('mail_lib.quit = {0}'.format(str(msg)))
|
||||
|
||||
except Exception as e:
|
||||
# from Baldenini_site.SMS_sender import send_SMS
|
||||
# send_SMS(u'375296177827', u'send_mail_by_SMTPlib error = {0}'.format(str(e)), urgent=True)
|
||||
msg = 'send_mail_by_SMTPlib error = {0}'.format(str(e))
|
||||
print(msg)
|
||||
try:
|
||||
@@ -241,10 +221,10 @@ def send_mail_by_SMTPlib(subject, from_email, to_init, html_content, smtp_server
|
||||
return msg
|
||||
|
||||
|
||||
def sendMail(subject, text_content, from_email, to, html_content):
|
||||
def sendMail(sets, subject, text_content, from_email, to, html_content):
|
||||
print('sendMail to {0}'.format(str(to)))
|
||||
|
||||
admin_send_mail_by_SMTPlib(subject, from_email, [to], html_content)
|
||||
admin_send_mail_by_SMTPlib(sets, subject, from_email, [to], html_content)
|
||||
|
||||
# msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
|
||||
# msg.attach_alternative(html_content, "text/html")
|
||||
@@ -253,52 +233,10 @@ def sendMail(subject, text_content, from_email, to, html_content):
|
||||
return u'Accept'
|
||||
|
||||
|
||||
# def techSendMail_for_top_management(html_content, title=None):
|
||||
# try:
|
||||
#
|
||||
# # if not prod_server:
|
||||
# # msg = '{0}. Not sended because is local'.format(html_content)
|
||||
# # print(msg)
|
||||
# # return msg
|
||||
# from AuthApp.models import User
|
||||
# from django.db.models import Q
|
||||
#
|
||||
# # to = ['web@syncsystems.net']
|
||||
# to = User.objects.filter(
|
||||
# Q(is_superuser=True) | Q(groups__name__in=[
|
||||
# 'Отдел продаж: Начальник отдела продаж', 'Управляющий',
|
||||
# 'Бухгалтерия: Главный бухгалтер'
|
||||
# ]),
|
||||
# is_active=True,
|
||||
# is_staff=True
|
||||
# ).values_list('email', flat=True)
|
||||
# to = list(to)
|
||||
# to.append('office@truenergy.by')
|
||||
#
|
||||
# print('techSendMail_for_top_management')
|
||||
# if title:
|
||||
# subject = title
|
||||
# else:
|
||||
# subject = u'truEnergy Data техническое оповещение'
|
||||
# from_email = 'support@truenergy.by'
|
||||
#
|
||||
# res = admin_send_mail_by_SMTPlib(subject, from_email, to, html_content)
|
||||
#
|
||||
# # msg = EmailMultiAlternatives(subject, text_content, from_email, to)
|
||||
# # msg.attach_alternative(html_content, "text/html")
|
||||
# # msg.send()
|
||||
# print(res)
|
||||
# return u'Accept'
|
||||
#
|
||||
# except Exception as e:
|
||||
# msg = 'techSendMail_for_top_management error={0}'.format(str(e))
|
||||
# techSendMail(msg)
|
||||
# print(msg)
|
||||
#
|
||||
# return 'Fail'
|
||||
|
||||
|
||||
def techSendMail_for_specified_email_list(html_content, email_list, title=None):
|
||||
|
||||
def techSendMail_for_specified_email_list(sets, html_content, email_list, title=None):
|
||||
try:
|
||||
|
||||
print('techSendMail_for_specified_email_list')
|
||||
@@ -308,41 +246,44 @@ def techSendMail_for_specified_email_list(html_content, email_list, title=None):
|
||||
subject = u'truEnergy Data техническое оповещение'
|
||||
from_email = 'support@truenergy.by'
|
||||
|
||||
res = admin_send_mail_by_SMTPlib(subject, from_email, email_list, html_content)
|
||||
res = admin_send_mail_by_SMTPlib(sets, subject, from_email, email_list, html_content)
|
||||
|
||||
print(res)
|
||||
return u'Accept'
|
||||
|
||||
except Exception as e:
|
||||
msg = 'techSendMail_for_specified_email_list error={0}'.format(str(e))
|
||||
techSendMail(msg)
|
||||
techSendMail(sets, msg)
|
||||
print(msg)
|
||||
|
||||
return 'Fail'
|
||||
|
||||
|
||||
def techSendMail(html_content, title=None, add_emails=None):
|
||||
def techSendMail(sets, html_content, title=None, add_emails=None):
|
||||
# if not prod_server:
|
||||
# msg = '{0}. Not sended because is local'.format(html_content)
|
||||
# print(msg)
|
||||
# return msg
|
||||
|
||||
print('techSendMail')
|
||||
project_name = ''
|
||||
if 'project_name' in sets:
|
||||
project_name = sets['project_name']
|
||||
|
||||
try:
|
||||
# subject = u'truEnergy Data техническое оповещение'
|
||||
from_email = 'support@truenergy.by'
|
||||
from_email = sets['sender_email']
|
||||
to = ['web@syncsystems.net']
|
||||
if add_emails:
|
||||
to.extend(add_emails)
|
||||
text_content = 'Technical message from truEnergy.'
|
||||
text_content = f'Technical message from {project_name}'
|
||||
|
||||
if title:
|
||||
subject = title
|
||||
else:
|
||||
subject = u'truEnergy Data техническое оповещение'
|
||||
subject = f'{project_name} - техническое оповещение'
|
||||
|
||||
res = admin_send_mail_by_SMTPlib(subject, from_email, to, html_content)
|
||||
res = admin_send_mail_by_SMTPlib(sets, subject, from_email, to, html_content)
|
||||
|
||||
print(res)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
## -*- coding: utf-8 -*-
|
||||
__author__ = 'SDE'
|
||||
# from Baldenini_site.inter import jsonify
|
||||
|
||||
def get_error_message_Dict(show_icon=None):
|
||||
print('get_error_message_Dict')
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
__author__ = 'SDE'
|
||||
|
||||
def get_paging_Dict(request, elements_count, elements_on_page, from_page, to_page=None):
|
||||
def get_paging_Dict(request, elements_count, next_page_els_count, from_page, to_page=None):
|
||||
|
||||
|
||||
|
||||
pages_count = elements_count / elements_on_page
|
||||
if elements_count % elements_on_page > 0:
|
||||
pages_count = elements_count / next_page_els_count
|
||||
if elements_count % next_page_els_count > 0:
|
||||
pages_count = pages_count + 1
|
||||
|
||||
pages = []
|
||||
|
||||
@@ -8,7 +8,7 @@ def get_ld_search(domain):
|
||||
data = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"url": domain, #"https://truenergy.by/",
|
||||
"url": domain,
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": {
|
||||
|
||||
@@ -1,258 +1,75 @@
|
||||
from BaseModels.inter import cut_to_number_w_point
|
||||
|
||||
|
||||
def generate_seotext_by_properties(product_data_Dict):
|
||||
|
||||
power_txt = ''
|
||||
ip_txt = ''
|
||||
lm_txt = ''
|
||||
temp_txt = ''
|
||||
install_txt = ''
|
||||
diametr_txt = ''
|
||||
|
||||
try:
|
||||
|
||||
if 'diameter' in product_data_Dict:
|
||||
val = int(product_data_Dict['diameter'])
|
||||
else:
|
||||
val = int(product_data_Dict['width'])
|
||||
|
||||
diametr_txt = '{0} truEnergy {1} серии {2}.<br>'.format(
|
||||
product_data_Dict['product_type']['name'].upper(),
|
||||
product_data_Dict['article'],
|
||||
product_data_Dict['product_series']['name'].upper()
|
||||
)
|
||||
|
||||
# if product_data_Dict['product_type']['name'] == 'Светильник светодиодный':
|
||||
#
|
||||
# if val < 100:
|
||||
# diametr_txt = '{0} truEnergy {1} серии {2} - это хорошее решение для дома.<br>'.format(
|
||||
# product_data_Dict['product_type']['name'].upper(),
|
||||
# product_data_Dict['article'],
|
||||
# product_data_Dict['product_series']['name'].upper()
|
||||
# )
|
||||
#
|
||||
# elif val < 150:
|
||||
# diametr_txt = '{0} truEnergy {1} серии {2} отлично подойдет для освещения вашей квартиры, дома или офиса.<br>'.format(
|
||||
# product_data_Dict['product_type']['name'].upper(),
|
||||
# product_data_Dict['article'],
|
||||
# product_data_Dict['product_series']['name'].upper()
|
||||
# )
|
||||
#
|
||||
# else:
|
||||
# diametr_txt = '{0} truEnergy {1} серии {2} - это энергоэффективное освещение для различных площадей и объектов.<br>'.format(
|
||||
# product_data_Dict['product_type']['name'].upper(),
|
||||
# product_data_Dict['article'],
|
||||
# product_data_Dict['product_series']['name'].upper()
|
||||
# )
|
||||
# # не светильник
|
||||
# else:
|
||||
# diametr_txt = '{0} truEnergy {1} серии {2} - это энергоэффективное решение для освещения различных пространств.<br>'.format(
|
||||
# product_data_Dict['product_type']['name'].upper(),
|
||||
# product_data_Dict['article'],
|
||||
# product_data_Dict['product_series']['name'].upper()
|
||||
# )
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# ---------
|
||||
for property in product_data_Dict['properties_w_values_filtred']:
|
||||
|
||||
# ------
|
||||
|
||||
try:
|
||||
|
||||
if property['property']['name'] == 'Мощность':
|
||||
power = int(property['property_value'])
|
||||
|
||||
if power < 7:
|
||||
power_txt = 'Обладая низким энергопотреблением, этот {0} является заменой лампочки накаливания мощностью до 40 Ватт.<br>'.format(
|
||||
product_data_Dict['product_type']['name'].lower(),
|
||||
)
|
||||
|
||||
elif power < 13:
|
||||
power_txt = 'Энергоэффективность этого устройства позволяет использовть его в местах, ' \
|
||||
'где ранее использовались светильники с лампами накаливания мощностью до 75 Ватт.<br>'.format(
|
||||
)
|
||||
elif power < 19:
|
||||
power_txt = 'Этот {0} мощностью {1} Ватт легко заменит старые лампы накаливания мощностью до 100 Ватт ' \
|
||||
'или люминесцентные лампы мощностью до 40 Ватт.<br>'.format(
|
||||
product_data_Dict['product_type']['name'].lower(),
|
||||
str(power)
|
||||
)
|
||||
|
||||
elif power < 37:
|
||||
power_txt = 'Данная модель подходит для освещения больших пространств. ' \
|
||||
'Она не только поможет решить вопрос освещения, но и существенно сэкономит бюджет, ' \
|
||||
'выделенный на решение этой задачи.<br>'.format(
|
||||
product_data_Dict['product_type']['name'].lower(),
|
||||
)
|
||||
else:
|
||||
power_txt = '{0} Ватт, в данной модели обеспечивает мощный световой поток. ' \
|
||||
'Это дает возможность установки одного изделия для освещения помещений с большой ' \
|
||||
'площадью или открытых пространств.<br>'.format(
|
||||
str(power),
|
||||
product_data_Dict['product_type']['name'].lower(),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# ------
|
||||
|
||||
try:
|
||||
|
||||
if property['property']['name'] == 'Световой поток' and product_data_Dict['article'] != '11043':
|
||||
val = int(property['property_value'])
|
||||
|
||||
if product_data_Dict['product_type']['name'] == 'Светильник светодиодный':
|
||||
lm_txt = 'Один {0} данной модели способен осветить до {1} м.кв. площади ' \
|
||||
'для рабочих зон и жилых комнат, и до {2} м.кв. площади для проходных и подсобных помещений ' \
|
||||
'(при стандартной высоте потолка и нормальной освещенности помещения).<br>'.format(
|
||||
product_data_Dict['product_type']['name'].lower(),
|
||||
str(round(val / 300,2)),
|
||||
str(round(val / 120, 2)),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# -------
|
||||
|
||||
try:
|
||||
|
||||
if property['property']['name'] == 'IP (пылевлагозащита)':
|
||||
val = int(property['property_value'])
|
||||
|
||||
if val > 66:
|
||||
ip_txt = 'Максимальная защита IP{0} способна выдержать самые сильные испытания водой. ' \
|
||||
'Освещение с такой защитой используют для фонтанов и бассейнов.<br>'.format(
|
||||
str(val),
|
||||
)
|
||||
|
||||
elif val > 64:
|
||||
ip_txt = 'Данный продукт имеет высокую степень пылевлагозащиты - IP{0}. В связи с этим данная модель прекрасно подходит как ' \
|
||||
'для отапливаемых помещений с нормальным уровнем влажности, так и для помещений неотапливаемых, ' \
|
||||
'а также для эксплуатации на улице. Устройство с данной степенью защиты не боится пыли и влаги' \
|
||||
'а так же имеет защиту от струй воды со всех направлений.<br>'.format(
|
||||
str(val),
|
||||
)
|
||||
|
||||
elif val > 60:
|
||||
ip_txt = 'Степень защиты IP{0} обозначает полную защиту от брызг с любых сторон и имеет полную пылинепроницаемость ' \
|
||||
'(никакая пыль не может проникнуть внутрь корпуса устройства). ' \
|
||||
'Светильники подходят для установки в помещении и на улице, при рабочих температурах -20 до +40 градусов.<br>'.format(
|
||||
str(val),
|
||||
)
|
||||
|
||||
elif val > 53:
|
||||
ip_txt = 'У изделия с степенью защиты IP{0} снижена возможность попадания пыли внутрь корпуса ' \
|
||||
'и обеспечена полная защита расположенной внутри устройстав электроники.' \
|
||||
'Часто используют для рабочих помещений с повышенным содержанием пыли и влаги, а также под навесами.<br>'.format(
|
||||
str(val),
|
||||
product_data_Dict['product_type']['name'].lower(),
|
||||
product_data_Dict['product_type']['name_plural'].lower(),
|
||||
)
|
||||
|
||||
elif val > 40:
|
||||
ip_txt = 'Могут устанавливаться в помещения с повышенным уровнем пыли.'.format(
|
||||
product_data_Dict['product_type']['name'].lower(),
|
||||
)
|
||||
else:
|
||||
ip_txt = 'IP{0} - степень защиты данной модели, в связи с этим могут устанавливаться в' \
|
||||
' отапливаемые помещения с умеренным уровнем влажности.<br>'.format(
|
||||
str(val),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# -------
|
||||
|
||||
try:
|
||||
|
||||
if property['property']['name'] == 'Цветовая температура':
|
||||
val = int(property['property_value'])
|
||||
|
||||
if val < 3001:
|
||||
temp_txt = 'Теплый свет, генерируемый этой моделью способствует отдыху и расслаблению. ' \
|
||||
'Он приятен для глаз. В связи с этим рекомендуется устанавливать {0} ' \
|
||||
'с температурой {1}К в зоны отдыха, жилые комнаты и спальни, кафе, лаундж зоны. ' \
|
||||
'Очень удачное решение для обеденных и гостинных комнат.<br>'.format(
|
||||
product_data_Dict['product_type']['name_plural'].lower(),
|
||||
str(val),
|
||||
)
|
||||
|
||||
elif val < 4601:
|
||||
temp_txt = 'Модель обладает нейтральным цветом свечения, который прекрасно подходит и как для жилых помещений и комнат, ' \
|
||||
'так и для рабочих зон (офисов, кабинетов, производств) . ' \
|
||||
'Данный свет стимулирует к работе не вызывая перенапряжения глаз и не искажая цветопередачу. ' \
|
||||
'Универсальное и наиболее распространенное решение.<br>'.format(
|
||||
str(val),
|
||||
)
|
||||
|
||||
elif val < 7001:
|
||||
temp_txt = 'Цветовая температура {0}К - наиболее оптимально использование в помещениях промышленного назначения, ' \
|
||||
'административных зданиях, на производствах, складах, гаражах, паркингах. ' \
|
||||
'Однако могут применяться и в интерьере для создания акцентов в дизайне, ' \
|
||||
'либо если предпочтения потребителя отданы в пользу белого света. <br>'.format(
|
||||
str(val),
|
||||
)
|
||||
|
||||
|
||||
|
||||
else:
|
||||
temp_txt = 'От показателя цветовой температуры зависит то, как Вы будут воспринимать предметы и другие объекты освещенные устройством. ' \
|
||||
'С помощью цветовой температуры можно сделать более приятным отдых и улучшить эффективность работы. ' \
|
||||
'Отниситесь внимательно к выбору устройства по этому параметру.<br>'.format(
|
||||
str(val),
|
||||
)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# -------
|
||||
|
||||
try:
|
||||
|
||||
if property['property']['name'] == 'Тип монтажа':
|
||||
val = property['property_value']
|
||||
|
||||
if val == 'встраиваемый':
|
||||
install_txt = 'Устройство устанавливается в предварительно вырезанное в поверхности отверстие. ' \
|
||||
'Этот вариант монтажа используется для подвесных и натяжных потолков, а так же для фальш-стен и ниш.'.format(
|
||||
str(val),
|
||||
)
|
||||
|
||||
elif val == 'накладной':
|
||||
install_txt = 'Способ крепления - накладной. Значит эта модель может быть закреплена на любую ровную поверхность.'.format(
|
||||
str(val),
|
||||
)
|
||||
|
||||
elif val == 'встраиваемый/накладной':
|
||||
install_txt = '{0} обладает возможностью монтажа как в отверстия на поверхности плоскостей, так и на любую ровную поверхность.'.format(
|
||||
product_data_Dict['article'],
|
||||
)
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if 'height_visible_part' in product_data_Dict and product_data_Dict['height_visible_part']:
|
||||
install_txt = install_txt + ' Высота видимой части устройства после монтажа составит {0}мм.<br>'.format(
|
||||
str(round(product_data_Dict['height_visible_part']))
|
||||
)
|
||||
else:
|
||||
install_txt = install_txt + '<br>'
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
product_data_Dict['seo_text'] = '{0}{1}{2}{3}{4}{5}'.format(
|
||||
diametr_txt,
|
||||
power_txt,
|
||||
lm_txt,
|
||||
ip_txt,
|
||||
temp_txt,
|
||||
install_txt
|
||||
)
|
||||
|
||||
return product_data_Dict
|
||||
# def generate_seotext_by_properties(product_data_Dict):
|
||||
#
|
||||
# power_txt = ''
|
||||
# ip_txt = ''
|
||||
# lm_txt = ''
|
||||
# temp_txt = ''
|
||||
# install_txt = ''
|
||||
# diametr_txt = ''
|
||||
#
|
||||
# try:
|
||||
#
|
||||
#
|
||||
#
|
||||
# except Exception as e:
|
||||
# pass
|
||||
#
|
||||
# # ---------
|
||||
# for property in product_data_Dict['properties_w_values_filtred']:
|
||||
#
|
||||
# # ------
|
||||
#
|
||||
# try:
|
||||
#
|
||||
#
|
||||
# except Exception as e:
|
||||
# pass
|
||||
#
|
||||
# # ------
|
||||
#
|
||||
# try:
|
||||
#
|
||||
#
|
||||
#
|
||||
# except Exception as e:
|
||||
# pass
|
||||
#
|
||||
# # -------
|
||||
#
|
||||
# try:
|
||||
#
|
||||
#
|
||||
#
|
||||
# except Exception as e:
|
||||
# pass
|
||||
#
|
||||
# # -------
|
||||
#
|
||||
# try:
|
||||
#
|
||||
#
|
||||
# except Exception as e:
|
||||
# pass
|
||||
#
|
||||
# # -------
|
||||
#
|
||||
# try:
|
||||
#
|
||||
#
|
||||
#
|
||||
# except Exception as e:
|
||||
# pass
|
||||
#
|
||||
# product_data_Dict['seo_text'] = '{0}{1}{2}{3}{4}{5}'.format(
|
||||
# diametr_txt,
|
||||
# power_txt,
|
||||
# lm_txt,
|
||||
# ip_txt,
|
||||
# temp_txt,
|
||||
# install_txt
|
||||
# )
|
||||
#
|
||||
# return product_data_Dict
|
||||
24
BaseModels/validators/form_field_validators.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.safestring import mark_safe
|
||||
def get_phone_valid_error(val):
|
||||
allow_chars = '01234567890()+ -'
|
||||
|
||||
error_msg = mark_safe(_('Некорректные символы в номере, введите номер в международном формате с кодом страны'))
|
||||
|
||||
if not val:
|
||||
return None
|
||||
|
||||
if len(val) < 10:
|
||||
return error_msg
|
||||
|
||||
if '+' in val and val[0] != '+':
|
||||
return error_msg
|
||||
|
||||
i = 0
|
||||
while i < len(val):
|
||||
if val[i] not in allow_chars:
|
||||
return error_msg
|
||||
|
||||
i += 1
|
||||
|
||||
return None
|
||||
@@ -1,6 +1,11 @@
|
||||
from sets.admin import *
|
||||
from .models import *
|
||||
from django.contrib import admin
|
||||
from django import forms
|
||||
from django.forms import widgets
|
||||
|
||||
|
||||
|
||||
|
||||
class Admin_MsgGroup(Admin_BaseModel):
|
||||
list_display = [
|
||||
@@ -11,8 +16,33 @@ class Admin_MsgGroup(Admin_BaseModel):
|
||||
admin.site.register(MsgGroup,Admin_MsgGroup)
|
||||
|
||||
class Admin_Message(Admin_BaseModel):
|
||||
|
||||
def cut_group_text(self, obj):
|
||||
if obj.group:
|
||||
return obj.group.name[:10]
|
||||
return '-'
|
||||
cut_group_text.allow_tags = True
|
||||
cut_group_text.short_description = 'ticket'
|
||||
|
||||
|
||||
def cut_text(self, obj):
|
||||
if obj.text:
|
||||
if len(obj.text) > 20:
|
||||
return f'{obj.text[:20]}...'
|
||||
else:
|
||||
return obj.text
|
||||
return '-'
|
||||
cut_text.allow_tags = True
|
||||
cut_text.short_description = 'сообщение'
|
||||
|
||||
search_fields = ['group__name', 'text', 'name', 'id']
|
||||
|
||||
list_filter = ['status']
|
||||
|
||||
list_display = [
|
||||
'id', 'msg_type', 'group', 'status', 'sender', 'receiver', 'text',
|
||||
'id',
|
||||
# 'msg_type',
|
||||
'cut_group_text', 'status', 'sender', 'receiver', 'cut_text',
|
||||
'name',
|
||||
'order', 'modifiedDT', 'createDT'
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import copy
|
||||
|
||||
from .models import *
|
||||
from django.db.models import Q, Value as V, Count, OuterRef, Subquery
|
||||
from django.http import HttpResponse, Http404, JsonResponse
|
||||
@@ -10,6 +12,13 @@ from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
import json
|
||||
from datetime import datetime, time
|
||||
from django.conf import settings
|
||||
from AuthApp.funcs import get_user_timezone_Dict
|
||||
|
||||
|
||||
def get_unanswered_msgs_count_for_user(user):
|
||||
msgs = Message.objects.filter(receiver=user, status='sended', group=None)
|
||||
return msgs.count()
|
||||
|
||||
|
||||
|
||||
@@ -45,11 +54,22 @@ def get_update_chat_Dict(data):
|
||||
receiver = User.objects.get(id=data['receiver'])
|
||||
|
||||
if data['sender'] == data['cur_user']:
|
||||
context_Dict.update({'user': sender})
|
||||
user = copy.copy(sender)
|
||||
cur_receiver = copy.copy(receiver)
|
||||
else:
|
||||
context_Dict.update({'user': receiver})
|
||||
user = copy.copy(receiver)
|
||||
cur_receiver = copy.copy(sender)
|
||||
# context_Dict.update({'user': receiver})
|
||||
|
||||
context_Dict.update({'cur_receiver': receiver})
|
||||
# context_Dict.update({'cur_receiver': receiver})
|
||||
context_Dict.update({
|
||||
'cur_receiver': cur_receiver,
|
||||
'user': user,
|
||||
})
|
||||
context_Dict.update(get_user_timezone_Dict(user))
|
||||
|
||||
if sender == receiver:
|
||||
print('!')
|
||||
|
||||
required_beep = data['required_beep']
|
||||
|
||||
@@ -63,7 +83,10 @@ def get_update_chat_Dict(data):
|
||||
|
||||
|
||||
# формируем правую панель
|
||||
context_Dict.update({'receivers': receivers})
|
||||
context_Dict.update({
|
||||
'receivers': receivers,
|
||||
'MEDIA_URL': settings.MEDIA_URL
|
||||
})
|
||||
users_list_html = render_to_string(
|
||||
'blocks/profile/b_list_of_users_messenger.html', context_Dict)
|
||||
res_Dict.update({
|
||||
@@ -87,7 +110,10 @@ def get_update_chat_Dict(data):
|
||||
if ticket.manager:
|
||||
context_Dict.update({'new_msg_allow': True})
|
||||
|
||||
context_Dict.update({'messages': msgs})
|
||||
context_Dict.update({
|
||||
'messages': msgs,
|
||||
'MEDIA_URL': settings.MEDIA_URL,
|
||||
})
|
||||
html = render_to_string(tpl_name, context_Dict)
|
||||
if required_full_support_chat_html:
|
||||
res_Dict.update({'support_chat_html': html})
|
||||
@@ -262,6 +288,7 @@ def send_msg(data):
|
||||
|
||||
def get_chat_page_content_html(request, receiver_id=None):
|
||||
from AuthApp.models import User
|
||||
from AuthApp.funcs import get_user_timezone_Dict
|
||||
|
||||
msgs = []
|
||||
try:
|
||||
@@ -281,6 +308,7 @@ def get_chat_page_content_html(request, receiver_id=None):
|
||||
'receivers': receivers,
|
||||
'page': 'chat',
|
||||
}
|
||||
Dict.update(get_user_timezone_Dict(request.user, request=request))
|
||||
tpl_name = 'blocks/profile/b_chats.html'
|
||||
|
||||
html = render_to_string(tpl_name, Dict, request=request)
|
||||
|
||||
@@ -53,6 +53,7 @@ def show_chat_w_user_ajax(request):
|
||||
|
||||
data = json.loads(request.body)
|
||||
Dict = get_chat_page_content_Dict(request, data['user_id'])
|
||||
Dict.update(get_user_timezone_Dict(request.user, request=request))
|
||||
|
||||
tpl_name = 'blocks/profile/b_chats.html'
|
||||
|
||||
@@ -99,7 +100,7 @@ def update_chat_ajax2(request):
|
||||
receiver = User.objects.get(id=data['receiver'])
|
||||
|
||||
context_Dict.update({'cur_receiver': receiver})
|
||||
|
||||
context_Dict.update(get_user_timezone_Dict(request.user, request=request))
|
||||
|
||||
if not ticket:
|
||||
|
||||
@@ -123,6 +124,7 @@ def update_chat_ajax2(request):
|
||||
|
||||
# формируем правую панель
|
||||
context_Dict.update({'receivers': receivers})
|
||||
|
||||
users_list_html = render_to_string(
|
||||
'blocks/profile/b_list_of_users_messenger.html', context_Dict, request=request)
|
||||
res_Dict.update({
|
||||
@@ -190,6 +192,7 @@ def update_chat_ajax(request):
|
||||
receiver = User.objects.get(id=data['receiver'])
|
||||
|
||||
context_Dict.update({'cur_receiver': receiver})
|
||||
context_Dict.update(get_user_timezone_Dict(request.user, request=request))
|
||||
|
||||
if ticket:
|
||||
|
||||
@@ -201,6 +204,8 @@ def update_chat_ajax(request):
|
||||
context_Dict.update({'messages': msgs})
|
||||
context_Dict = get_ticketsDict_for_staff(request.user)
|
||||
|
||||
context_Dict.update(get_user_timezone_Dict(request.user))
|
||||
|
||||
tickets_list_html = render_to_string(
|
||||
'blocks/profile/b_list_of_tickets_support_chat.html', context_Dict, request=request)
|
||||
res_Dict.update({
|
||||
@@ -220,6 +225,8 @@ def update_chat_ajax(request):
|
||||
required_beep = True
|
||||
|
||||
context_Dict.update({'receivers': receivers})
|
||||
context_Dict.update(get_user_timezone_Dict(request.user))
|
||||
|
||||
users_list_html = render_to_string(
|
||||
'blocks/profile/b_list_of_users_messenger.html', context_Dict, request=request)
|
||||
res_Dict.update({
|
||||
@@ -402,12 +409,16 @@ def support_show_chat_by_ticket_ajax(request):
|
||||
'ticket': ticket,
|
||||
'messages': msgs,
|
||||
'cur_receiver': cur_receiver,
|
||||
'new_msg_allow': new_msg_allow
|
||||
'new_msg_allow': new_msg_allow,
|
||||
'staff': request.user.is_staff,
|
||||
# 'mobile': data['mobile']
|
||||
}
|
||||
Dict.update(get_ticketsDict_for_staff(request.user))
|
||||
|
||||
tpl_name = 'blocks/profile/b_support_chat.html'
|
||||
Dict.update(get_user_timezone_Dict(request.user))
|
||||
|
||||
tpl_name = 'blocks/profile/b_support_chat.html'
|
||||
Dict.update(get_user_timezone_Dict(request.user, request=request))
|
||||
html = render_to_string(tpl_name, Dict, request=request)
|
||||
return JsonResponse({'html': html}, status=200)
|
||||
|
||||
@@ -427,6 +438,9 @@ def support_create_ticket_form_ajax(request):
|
||||
Dict = {
|
||||
'form': TicketForm()
|
||||
}
|
||||
|
||||
Dict.update(get_user_timezone_Dict(request.user))
|
||||
|
||||
tpl_name = 'blocks/profile/b_create_ticket.html'
|
||||
|
||||
html = render_to_string(tpl_name, Dict, request=request)
|
||||
@@ -448,6 +462,8 @@ def create_ticket_ajax(request):
|
||||
if not form.is_valid():
|
||||
form.initial = form.cleaned_data
|
||||
Dict = {'form': form}
|
||||
Dict.update(get_user_timezone_Dict(request.user))
|
||||
|
||||
html = render_to_string('blocks/profile/b_create_ticket.html', Dict, request=request)
|
||||
return JsonResponse({'html': html}, status=400)
|
||||
|
||||
@@ -488,6 +504,8 @@ def create_ticket_ajax(request):
|
||||
'messages': msgs_for_ticket
|
||||
}
|
||||
|
||||
Dict.update(get_user_timezone_Dict(request.user))
|
||||
|
||||
html = render_to_string('blocks/profile/b_support_chat.html', Dict, request=request)
|
||||
|
||||
res_Dict = {
|
||||
@@ -498,9 +516,11 @@ def create_ticket_ajax(request):
|
||||
|
||||
except Exception as e:
|
||||
|
||||
msg = f'{_("ошибка в запросе")} = {str(e)}'
|
||||
|
||||
errors_Dict = {
|
||||
'errors': {
|
||||
'all__': f'ошибка в запросе = {str(e)}'
|
||||
'all__': msg
|
||||
}
|
||||
}
|
||||
Dict = {'form': errors_Dict}
|
||||
|
||||
@@ -3,41 +3,53 @@ from BaseModels.base_models import BaseModel
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
msg_type_choices = (
|
||||
('support', 'техподдержка'),
|
||||
('private', 'личное')
|
||||
('support', _('техподдержка')),
|
||||
('private', _('личное'))
|
||||
)
|
||||
|
||||
msg_status_choices = (
|
||||
('sended', 'Отправлено'),
|
||||
('seen', 'Просмотрено')
|
||||
('sended', _('Отправлено')),
|
||||
('seen', _('Просмотрено'))
|
||||
)
|
||||
|
||||
grp_msg_status = (
|
||||
('open', 'Открыт'),
|
||||
('answered', 'Отвечен'),
|
||||
('closed', 'Закрыт')
|
||||
('open', _('Открыт')),
|
||||
('answered', _('Отвечен')),
|
||||
('closed', _('Закрыт'))
|
||||
)
|
||||
|
||||
|
||||
grp_msg_department = (
|
||||
('support', 'Отдел: Техническая поддержка'),
|
||||
('finance', 'Отдел: Финансовый департамент'),
|
||||
('support', _('Отдел: Техническая поддержка')),
|
||||
('finance', _('Отдел: Финансовый департамент')),
|
||||
)
|
||||
|
||||
|
||||
class MsgGroup(BaseModel):
|
||||
from AuthApp.models import User
|
||||
|
||||
department = models.CharField(verbose_name='Отдел', default='support', choices=grp_msg_department)
|
||||
status = models.CharField(verbose_name='Статус', default='open', choices=grp_msg_status)
|
||||
department = models.CharField(verbose_name=_('Отдел'), default='support', choices=grp_msg_department)
|
||||
status = models.CharField(verbose_name=_('Статус'), default='open', choices=grp_msg_status)
|
||||
|
||||
text = models.TextField(verbose_name='Сообщение')
|
||||
text = models.TextField(verbose_name=_('Сообщение'))
|
||||
|
||||
owner = models.ForeignKey(User, verbose_name=_('Владелец'), related_name='rel_msgGroups_for_owner',
|
||||
on_delete=models.SET_NULL, null=True)
|
||||
manager = models.ForeignKey(User, verbose_name=_('Менеджер'), related_name='rel_msgGroups_for_manager',
|
||||
on_delete=models.SET_NULL, null=True)
|
||||
|
||||
def get_last_msg_txt(self):
|
||||
msg = self.rel_messages_for_group.all().order_by('-createDT').first()
|
||||
if not msg:
|
||||
return self.text
|
||||
|
||||
if msg.text:
|
||||
return msg.text
|
||||
elif msg.files:
|
||||
return msg.files[0]['file_name']
|
||||
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Тикет')
|
||||
verbose_name_plural = _('Тикеты')
|
||||
@@ -46,23 +58,23 @@ class MsgGroup(BaseModel):
|
||||
class Message(BaseModel):
|
||||
from AuthApp.models import User
|
||||
|
||||
msg_type = models.CharField(max_length=50, verbose_name='Тип сообщения', default='private', choices=msg_type_choices)
|
||||
msg_type = models.CharField(max_length=50, verbose_name=_('Тип сообщения'), default='private', choices=msg_type_choices)
|
||||
group = models.ForeignKey(
|
||||
MsgGroup, verbose_name='Группа сообщений', related_name='rel_messages_for_group',
|
||||
MsgGroup, verbose_name=_('Группа сообщений'), related_name='rel_messages_for_group',
|
||||
on_delete=models.SET_NULL, null=True)
|
||||
|
||||
sender = models.ForeignKey(
|
||||
User, verbose_name='Отправитель', on_delete=models.CASCADE, related_name='rel_messages_for_sender'
|
||||
User, verbose_name=_('Отправитель'), on_delete=models.CASCADE, related_name='rel_messages_for_sender'
|
||||
)
|
||||
receiver = models.ForeignKey(
|
||||
User, verbose_name='Получатель', on_delete=models.CASCADE, related_name='rel_messages_for_receiver'
|
||||
User, verbose_name=_('Получатель'), on_delete=models.CASCADE, related_name='rel_messages_for_receiver'
|
||||
)
|
||||
|
||||
text = models.TextField(verbose_name='Сообщение')
|
||||
text = models.TextField(verbose_name=_('Сообщение'))
|
||||
|
||||
status = models.CharField(verbose_name='Статус', default='sended', choices=msg_status_choices)
|
||||
status = models.CharField(verbose_name=_('Статус'), default='sended', choices=msg_status_choices)
|
||||
|
||||
files = models.JSONField(verbose_name='Прикрепленные файлы', default=dict)
|
||||
files = models.JSONField(verbose_name=_('Прикрепленные файлы'), default=dict)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Сообщение')
|
||||
|
||||
@@ -41,6 +41,10 @@ def get_filesize(size):
|
||||
@register.simple_tag
|
||||
def get_msg_side(cur_user, ticket, msg):
|
||||
if msg:
|
||||
# if msg.sender == cur_user:
|
||||
# return 'left'
|
||||
# else:
|
||||
# return 'right'
|
||||
if msg.sender == cur_user:
|
||||
return 'right'
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from sets.admin import *
|
||||
from .models import *
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
class Admin_StaticPage(Admin_Trans_BaseModelViewPage):
|
||||
|
||||
@@ -14,6 +15,12 @@ class Admin_StaticPage(Admin_Trans_BaseModelViewPage):
|
||||
'order',
|
||||
)
|
||||
}),
|
||||
(_('Настройки'), {
|
||||
'classes': ['wide', 'collapse'],
|
||||
'fields': (
|
||||
'FAQ_title',
|
||||
)
|
||||
}),
|
||||
('SEO', {
|
||||
'classes': ['wide', 'collapse'],
|
||||
'fields': (
|
||||
@@ -45,46 +52,61 @@ class Admin_StaticPage(Admin_Trans_BaseModelViewPage):
|
||||
|
||||
admin.site.register(StaticPage,Admin_StaticPage)
|
||||
|
||||
class Admin_Block(Admin_BaseBlock):
|
||||
# class Admin_Block(Admin_Trans_BaseModel):
|
||||
# pass
|
||||
#
|
||||
# # def get_fieldsets(self, request, obj=None):
|
||||
# # fieldsets = super(type(self), self).get_fieldsets(request, obj)
|
||||
# # if not request.user.is_superuser and obj.name and obj.name in ('About US', 'machines', 'works'):
|
||||
# # fieldsets[0][1]['fields'].pop(0)
|
||||
# # fieldsets.insert(
|
||||
# # 1, (_('Контент'), {
|
||||
# # 'classes': ['wide'],
|
||||
# # 'fields': (
|
||||
# # 'title', 'description', 'text',
|
||||
# # 'picture',
|
||||
# # )
|
||||
# #
|
||||
# # })
|
||||
# # )
|
||||
# # return fieldsets
|
||||
# #
|
||||
# # def has_delete_permission(self, request, obj=None):
|
||||
# # if request.user.is_superuser:
|
||||
# # return True
|
||||
# #
|
||||
# # if obj.name in ('About US', 'machines', 'works'):
|
||||
# # return False
|
||||
#
|
||||
# admin.site.register(Block,Admin_Block)
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
fieldsets = super(type(self), self).get_fieldsets(request, obj)
|
||||
if not request.user.is_superuser and obj.name and obj.name in ('About US', 'machines', 'works'):
|
||||
fieldsets[0][1]['fields'].pop(0)
|
||||
fieldsets.insert(
|
||||
1, ('Контент', {
|
||||
'classes': ['wide'],
|
||||
'fields': (
|
||||
'title', 'description', 'text',
|
||||
'picture',
|
||||
)
|
||||
class Admin_Option(Admin_Trans_BaseModel):
|
||||
|
||||
})
|
||||
)
|
||||
return fieldsets
|
||||
# def get_fieldsets(self, request, obj=None):
|
||||
# fieldsets = super(type(self), self).get_fieldsets(request, obj)
|
||||
# fieldsets.insert(
|
||||
# 1, ('Контент', {
|
||||
# 'classes': ['wide'],
|
||||
# 'fields': (
|
||||
# 'opt_type', 'prefix', 'value', 'picture'
|
||||
# )
|
||||
#
|
||||
# })
|
||||
# )
|
||||
# return fieldsets
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
|
||||
if obj.name in ('About US', 'machines', 'works'):
|
||||
return False
|
||||
fieldsets = [
|
||||
(_('Контент'), {
|
||||
'classes': ['wide'],
|
||||
'fields': (
|
||||
'name', 'opt_type', 'prefix', 'value', 'picture'
|
||||
)
|
||||
}),
|
||||
]
|
||||
|
||||
admin.site.register(Block,Admin_Block)
|
||||
|
||||
class Admin_Option(Admin_BaseModel):
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
fieldsets = super(type(self), self).get_fieldsets(request, obj)
|
||||
fieldsets.insert(
|
||||
1, ('Контент', {
|
||||
'classes': ['wide'],
|
||||
'fields': (
|
||||
'opt_type', 'prefix', 'value', 'picture'
|
||||
)
|
||||
|
||||
})
|
||||
)
|
||||
return fieldsets
|
||||
list_display = ['image_thumb', 'opt_type', 'name', 'value', 'prefix']
|
||||
list_editable = ['value', 'prefix']
|
||||
list_filter = ['opt_type']
|
||||
|
||||
admin.site.register(Option,Admin_Option)
|
||||
|
||||
51
GeneralApp/allauth_funcs.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||
from allauth.account.utils import user_field
|
||||
from django.conf import settings
|
||||
from allauth.account.adapter import DefaultAccountAdapter
|
||||
import requests
|
||||
from django.core.files import File
|
||||
from django.core.files.temp import NamedTemporaryFile
|
||||
|
||||
class MyAccountAdapter(DefaultAccountAdapter):
|
||||
|
||||
def get_login_redirect_url(self, request):
|
||||
path = super(MyAccountAdapter, self).get_login_redirect_url(request)
|
||||
|
||||
try:
|
||||
user = request.user
|
||||
user_profile = user.user_profile
|
||||
if user_profile and not user_profile.avatar:
|
||||
social_accounts = user.socialaccount_set.all()
|
||||
if social_accounts:
|
||||
for social_account in social_accounts:
|
||||
if 'picture' in social_account.extra_data and social_account.extra_data['picture']:
|
||||
r = requests.get(social_account.extra_data['picture'])
|
||||
|
||||
img_temp = NamedTemporaryFile()
|
||||
img_temp.write(r.content)
|
||||
img_temp.flush()
|
||||
user_profile.avatar.save(f'avatar_{user.id}.jpeg', File(img_temp), True)
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
msg = f'post_save create_user_profile Error = {str(e)}'
|
||||
print(msg)
|
||||
|
||||
return path
|
||||
|
||||
# class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
# def populate_user(self, request, sociallogin, data):
|
||||
# from AuthApp.models import UserProfile
|
||||
#
|
||||
# user = super().populate_user(request, sociallogin, data)
|
||||
# try:
|
||||
# picture = sociallogin.account.extra_data['picture']
|
||||
# user_profile = UserProfile.objects.get_or_create(user=user)
|
||||
# with open(picture, 'rb') as fd:
|
||||
# user_profile.avatar.save(f'user_{user.id}_avatar.jpeg', fd.read(), True)
|
||||
# # user_field(user, "profile_photo", picture)
|
||||
# except Exception as e:
|
||||
# msg = f'CustomSocialAccountAdapter populate_user Error = {str(e)}'
|
||||
# print(msg)
|
||||
#
|
||||
# return user
|
||||
45
GeneralApp/funcs.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from django.http import HttpResponse, Http404, FileResponse
|
||||
from django.conf import settings
|
||||
|
||||
def get_inter_Dict(user):
|
||||
|
||||
from SubscribesApp.funcs import get_cur_user_subscribe
|
||||
user_subscribe = get_cur_user_subscribe(user)
|
||||
|
||||
|
||||
Dict = {
|
||||
'user_subscribe': user_subscribe,
|
||||
'prod': True
|
||||
}
|
||||
|
||||
if settings.WS_ADDRESS == 'localhost:8000':
|
||||
Dict.update({'prod': False})
|
||||
|
||||
|
||||
from PushMessages.views import get_key_Dict
|
||||
Dict.update(get_key_Dict())
|
||||
|
||||
return Dict
|
||||
|
||||
def get_inter_http_respose(template_obj, context_Dict, request):
|
||||
|
||||
context_Dict.update(get_inter_Dict(request.user))
|
||||
|
||||
from PushMessages.views import send_push
|
||||
if request and 'page' in context_Dict:
|
||||
text = None
|
||||
title = None
|
||||
|
||||
if context_Dict['page'] == dict:
|
||||
if 'title' in context_Dict['page']:
|
||||
title = context_Dict['page']['title']
|
||||
if 'description' in context_Dict['page']:
|
||||
text = context_Dict['page']['description']
|
||||
else:
|
||||
title = getattr(context_Dict['page'], 'title', None)
|
||||
text = getattr(context_Dict['page'], 'description', None)
|
||||
|
||||
# if text and title and not request.user.is_anonymous:
|
||||
# send_push(user=request.user, title=title, text=text)
|
||||
|
||||
return HttpResponse(template_obj.render(context_Dict, request))
|
||||
44
GeneralApp/funcs_options.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from .models import *
|
||||
|
||||
def get_options_by_opt_types(opt_types, only_vals=False):
|
||||
if type(opt_types) == str:
|
||||
kwargs = {'opt_type': opt_types}
|
||||
elif type(opt_types) == dict:
|
||||
kwargs = opt_types
|
||||
else:
|
||||
kwargs = {'opt_type__in': opt_types}
|
||||
|
||||
opts = Option.objects.filter(**kwargs)
|
||||
if opts and only_vals:
|
||||
res = {}
|
||||
opts = opts.values('opt_type', 'value', 'prefix')
|
||||
for item in opts:
|
||||
if item['opt_type'] == 'domain':
|
||||
|
||||
try:
|
||||
from django.contrib.sites.models import Site
|
||||
current_site = Site.objects.get_current()
|
||||
res.update({item['opt_type']: current_site.domain})
|
||||
continue
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
if item['prefix']:
|
||||
res.update({item['opt_type']: f"{item['prefix']}{item['value']}"})
|
||||
else:
|
||||
res.update({item['opt_type']: f"{item['value']}"})
|
||||
return res
|
||||
|
||||
return opts
|
||||
|
||||
def get_first_option_value_by_opt_type(opt_type):
|
||||
opts = get_options_by_opt_types(opt_type)
|
||||
if opts:
|
||||
return opts[0].value
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_mail_send_options():
|
||||
opt_types = ['mail_server_url', 'mail_server_smtp_port', 'sender_mail_login', 'sender_mail_password', 'sender_email', 'project_name']
|
||||
|
||||
return get_options_by_opt_types(opt_types, only_vals=True)
|
||||
66
GeneralApp/init_options.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from .models import *
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
required_options_Dict = {
|
||||
'mail_server_url': {
|
||||
'name_ru': 'Адрес почтового сервера',
|
||||
'opt_type': 'mail_server_url',
|
||||
'value': '213.142.147.40',
|
||||
},
|
||||
'mail_server_smtp_port': {
|
||||
'name_ru': 'SMTP порт почтового сервера',
|
||||
'opt_type': 'mail_server_smtp_port',
|
||||
'value': 587,
|
||||
},
|
||||
'sender_mail_login': {
|
||||
'name_ru': 'email для отправки писем с сайта',
|
||||
'opt_type': 'sender_mail_login',
|
||||
'value': 'admin@tripwb.com',
|
||||
},
|
||||
'sender_email': {
|
||||
'name_ru': 'email для отправки',
|
||||
'opt_type': 'sender_email',
|
||||
'value': 'admin@tripwb.com',
|
||||
},
|
||||
'sender_mail_password': {
|
||||
'name_ru': 'пароль для отправки писем с сайта',
|
||||
'opt_type': 'sender_mail_password',
|
||||
'value': 't5Fdcah^gdajY',
|
||||
},
|
||||
'project_name': {
|
||||
'name_ru': 'Название проекта',
|
||||
'opt_type': 'project_name',
|
||||
'value': 'TWB',
|
||||
},
|
||||
'domain': {
|
||||
'name_ru': 'Адрес сайта',
|
||||
'opt_type': 'domain',
|
||||
'value': 'tripwb.com',
|
||||
},
|
||||
'support_email': {
|
||||
'name_ru': 'email техподдержки',
|
||||
'opt_type': 'support_email',
|
||||
'value': 'admin@tripwb.com',
|
||||
},
|
||||
'corp_email': {
|
||||
'name_ru': 'корпоративный email',
|
||||
'opt_type': 'corp_email',
|
||||
'value': 'admin@tripwb.com',
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
def init_options():
|
||||
options = Option.objects.all()
|
||||
option_types_list = options.values_list('opt_type', flat=True)
|
||||
|
||||
opts_for_create = []
|
||||
for opt_type, data_Dict in required_options_Dict.items():
|
||||
if not opt_type in option_types_list:
|
||||
opt = Option(**data_Dict)
|
||||
opts_for_create.append(opt)
|
||||
|
||||
Option.objects.bulk_create(opts_for_create)
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 4.2.2 on 2023-11-30 13:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('GeneralApp', '0004_alter_block_description_alter_block_description_en_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='option',
|
||||
name='name_en',
|
||||
field=models.TextField(blank=True, help_text='Название', null=True, verbose_name='Название'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='option',
|
||||
name='name_ru',
|
||||
field=models.TextField(blank=True, help_text='Название', null=True, verbose_name='Название'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='option',
|
||||
name='prefix_en',
|
||||
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Префикс'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='option',
|
||||
name='prefix_ru',
|
||||
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Префикс'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='option',
|
||||
name='value_en',
|
||||
field=models.CharField(max_length=250, null=True, verbose_name='Значение'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='option',
|
||||
name='value_ru',
|
||||
field=models.CharField(max_length=250, null=True, verbose_name='Значение'),
|
||||
),
|
||||
]
|
||||
@@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from ckeditor_uploader.fields import RichTextUploadingField
|
||||
|
||||
class StaticPage(BaseModelViewPage):
|
||||
promo_header = models.BooleanField(verbose_name='Промо-хэдер', default=False)
|
||||
promo_header = models.BooleanField(verbose_name=_('Промо-хэдер'), default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Статическая страница')
|
||||
@@ -17,9 +17,9 @@ class Block(BaseModelViewPage):
|
||||
verbose_name_plural = _('Блоки на страницах')
|
||||
|
||||
class Option(BaseModel):
|
||||
opt_type = models.CharField(max_length=250, verbose_name='Тип', blank=True, null=True)
|
||||
prefix = models.CharField(max_length=250, verbose_name='Префикс', blank=True, null=True)
|
||||
value = models.CharField(max_length=250, verbose_name='Значение')
|
||||
opt_type = models.CharField(max_length=250, verbose_name=_('Тип'), blank=True, null=True)
|
||||
prefix = models.CharField(max_length=250, verbose_name=_('Префикс'), blank=True, null=True)
|
||||
value = models.CharField(max_length=250, verbose_name=_('Значение'))
|
||||
picture = models.ImageField(upload_to='uploads/', verbose_name=_('Миниатюра'), null=True, blank=True,
|
||||
help_text=u'')
|
||||
|
||||
@@ -36,8 +36,8 @@ class FAQitem(BaseModel):
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
question = models.TextField(verbose_name='Вопрос')
|
||||
answer = RichTextUploadingField(verbose_name='Ответ')
|
||||
question = models.TextField(verbose_name=_('Вопрос'))
|
||||
answer = RichTextUploadingField(verbose_name=_('Ответ'))
|
||||
|
||||
def __str__(self):
|
||||
if self.question:
|
||||
|
||||
1
GeneralApp/templatetags/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'SDE'
|
||||
160
GeneralApp/templatetags/base_tags_extra.py
Normal file
@@ -0,0 +1,160 @@
|
||||
__author__ = 'SDE'
|
||||
|
||||
from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
|
||||
register = template.Library()
|
||||
|
||||
from django.core.serializers import serialize
|
||||
from django.db.models.query import QuerySet
|
||||
# import simplejson
|
||||
from django.template import Library
|
||||
from django.utils.html import mark_safe
|
||||
|
||||
@register.filter('get_value_from_dict')
|
||||
def get_value_from_dict(dict_data, key):
|
||||
"""
|
||||
usage example {{ your_dict|get_value_from_dict:your_key }}
|
||||
"""
|
||||
|
||||
if key in dict_data:
|
||||
res = dict_data[key]
|
||||
return res
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@register.filter()
|
||||
def get_rows_count_by_cols_count(data, cols_count):
|
||||
rows_count = len(data) // cols_count
|
||||
if len(data) % cols_count:
|
||||
rows_count += 1
|
||||
return rows_count
|
||||
|
||||
@register.filter()
|
||||
def get_numbers_list(from_el, to_el):
|
||||
res = range(from_el, to_el+1)
|
||||
return res
|
||||
|
||||
|
||||
def val_type(value):
|
||||
res = type(value)
|
||||
return res.__name__
|
||||
register.filter('val_type', val_type)
|
||||
|
||||
@register.filter()
|
||||
def get_cols_table_data_for_row_when_cols3(value, row):
|
||||
el_count = 3
|
||||
from_el = (row-1) * el_count
|
||||
to_el = row * el_count
|
||||
part = list(value)[from_el:to_el]
|
||||
return part
|
||||
# register.filter('val_type', val_type)
|
||||
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def correct_for_tables(value):
|
||||
if value in ['None', '0.0']:
|
||||
return '-'
|
||||
return value
|
||||
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def del_bad_symbols(value):
|
||||
from BaseModels.functions import del_bad_symbols
|
||||
return del_bad_symbols(value)
|
||||
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def del_amp_symbols(value):
|
||||
from BaseModels.functions import del_nbsp
|
||||
return del_nbsp(value)
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def del_lang_from_path(value):
|
||||
path_list = value.split('/')
|
||||
path = '/' + '/'.join(path_list[2:])
|
||||
|
||||
# for i in path_list[1:]:
|
||||
# path.join(i + '/')
|
||||
return path
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def get_color_by_number(value, arg=None):
|
||||
|
||||
color = None
|
||||
try:
|
||||
val = float(value)
|
||||
|
||||
if not color and arg == u'%':
|
||||
|
||||
color = u'black'
|
||||
if val > 50:
|
||||
color = u'green'
|
||||
elif val <= 50 and val >= 25:
|
||||
color = u'#6c8107'
|
||||
elif val <= 25 and val >= 10:
|
||||
color = u'#a89803'
|
||||
elif val <= 10 and val >= 5:
|
||||
color = u'#e6a707'
|
||||
elif val <= 5 and val >= 0:
|
||||
color = u'#e67307'
|
||||
elif val <= 0:
|
||||
color = u'red'
|
||||
|
||||
|
||||
# val_range = val_max - val_min
|
||||
# # val_percent = (val_range * 100 / val) - 100
|
||||
# offset = -(val_min + -(val))
|
||||
# if val <0:
|
||||
# val = offset
|
||||
# if val > val_max:
|
||||
# val = val_max
|
||||
# elif val < 0:
|
||||
# val = 0
|
||||
#
|
||||
# color_range = 16711680 - 1211136
|
||||
# val_1unit = float(color_range) / float(val_range)
|
||||
# dec_color = 16711680 - int(val_1unit * val)
|
||||
|
||||
if not color:
|
||||
color = u'black'
|
||||
if val > 1000:
|
||||
color = u'green'
|
||||
elif val <= 1000 and val >= 500:
|
||||
color = u'#6c8107'
|
||||
elif val <= 500 and val >= 250:
|
||||
color = u'#a89803'
|
||||
elif val <= 250 and val >= 125:
|
||||
color = u'#e6a707'
|
||||
elif val <= 125 and val >= 50:
|
||||
color = u'#e67307'
|
||||
elif val <= 50:
|
||||
color = u'red'
|
||||
|
||||
# s = u'style="color: #{0}12;"'.format(str(hex(dec_color))[2:6])
|
||||
s = u'style="color: {0};"'.format(color)
|
||||
return s
|
||||
except:
|
||||
return u''
|
||||
|
||||
|
||||
# @register.filter
|
||||
# @stringfilter
|
||||
# def check_aprox_compare_strings(search_phrase, txt):
|
||||
# from ProductApp.search import get_highlight_string
|
||||
#
|
||||
# s = get_highlight_string(search_phrase, txt)
|
||||
#
|
||||
# return s
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,12 @@ class Block_TranslationOptions(TranslationOptions):
|
||||
)
|
||||
translator.register(Block, Block_TranslationOptions)
|
||||
|
||||
class Option_TranslationOptions(TranslationOptions):
|
||||
fields = (
|
||||
'name', 'value', 'prefix'
|
||||
)
|
||||
translator.register(Option, Option_TranslationOptions)
|
||||
|
||||
|
||||
class FAQitem_TranslationOptions(TranslationOptions):
|
||||
fields = (
|
||||
|
||||
@@ -5,42 +5,89 @@ from django.template import loader, RequestContext
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .models import *
|
||||
from django.conf import settings
|
||||
|
||||
from .funcs import get_inter_http_respose
|
||||
from django.http.response import JsonResponse, HttpResponse
|
||||
from django.views.decorators.http import require_GET, require_POST
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.contrib.auth.models import User
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from webpush import send_user_notification
|
||||
import json
|
||||
|
||||
def test_code(request):
|
||||
from RoutesApp.funcs import get_city_by_type_transport_and_address_point
|
||||
from RoutesApp.models import Route
|
||||
from ReferenceDataApp.models import Airport, City
|
||||
|
||||
routes = Route.objects.all()
|
||||
# import allauth
|
||||
# from allauth.socialaccount.models import SocialApp
|
||||
# apps = SocialApp.objects.all()
|
||||
# apps.delete()
|
||||
|
||||
for route in routes:
|
||||
print(route.id)
|
||||
required_save = False
|
||||
if not route.from_city:
|
||||
route.from_city = get_city_by_type_transport_and_address_point(route.type_transport, route.from_address_point)
|
||||
required_save = True
|
||||
from RoutesApp.search_matches import search_matches
|
||||
search_matches()
|
||||
|
||||
if not route.to_city:
|
||||
route.to_city = get_city_by_type_transport_and_address_point(route.type_transport,
|
||||
route.to_address_point)
|
||||
required_save = True
|
||||
# try:
|
||||
# # body = request.body
|
||||
# # data = json.loads(body)
|
||||
# # if 'head' not in data or 'body' not in data or 'id' not in data:
|
||||
# # return JsonResponse(status=400, data={"message": "Invalid data format"})
|
||||
# # user_id = data['id']
|
||||
# user = request.user
|
||||
# payload = {'head': '123', 'body': 'qwerty'}
|
||||
# send_user_notification(user=user, payload=payload, ttl=1000)
|
||||
# return JsonResponse(status=200, data={"message": "Web push successful"})
|
||||
# except TypeError:
|
||||
# return JsonResponse(status=500, data={"message": "An error occurred"})
|
||||
|
||||
if required_save:
|
||||
route.save()
|
||||
# routes = Route.objects.all()
|
||||
#
|
||||
# for route in routes:
|
||||
# print(route.id)
|
||||
# required_save = False
|
||||
# if not route.from_city:
|
||||
# route.from_city = get_city_by_type_transport_and_address_point(route.type_transport, route.from_address_point)
|
||||
# required_save = True
|
||||
#
|
||||
# if not route.to_city:
|
||||
# route.to_city = get_city_by_type_transport_and_address_point(route.type_transport,
|
||||
# route.to_address_point)
|
||||
# required_save = True
|
||||
#
|
||||
# if required_save:
|
||||
# route.save()
|
||||
|
||||
return HttpResponse('finished')
|
||||
|
||||
|
||||
|
||||
def Page404(request, exeption=None):
|
||||
|
||||
Dict = {}
|
||||
|
||||
t = loader.get_template('404.html')
|
||||
try:
|
||||
res = get_inter_http_respose(t, Dict, request)
|
||||
return HttpResponse(res, status=404)
|
||||
except Exception as e:
|
||||
return HttpResponse(str(e))
|
||||
|
||||
|
||||
|
||||
|
||||
def MainPage(request):
|
||||
from RoutesApp.forms import RouteForm
|
||||
|
||||
from .init_options import init_options
|
||||
init_options()
|
||||
|
||||
|
||||
print(f'LOCALE_PATHS = {str(settings.LOCALE_PATHS)}')
|
||||
|
||||
page = StaticPage.objects.get(url='main')
|
||||
|
||||
from ArticlesApp.models import ArticleModel
|
||||
arts = ArticleModel.objects.filter(enable=True).order_by('-createDT')[:4]
|
||||
arts = ArticleModel.objects.filter(enable=True).order_by('-createDT')[:3]
|
||||
|
||||
Dict = {
|
||||
'page': page,
|
||||
@@ -50,12 +97,15 @@ def MainPage(request):
|
||||
'owner_type': 'mover'
|
||||
}
|
||||
|
||||
|
||||
|
||||
breadcrumbs_Dict = {
|
||||
}
|
||||
Dict.update({'breadcrumbs': breadcrumbs_Dict})
|
||||
|
||||
t = loader.get_template('pages/p_main.html')
|
||||
return HttpResponse(t.render(Dict, request))
|
||||
return get_inter_http_respose(t, Dict, request)
|
||||
# return HttpResponse(t.render(Dict, request))
|
||||
|
||||
|
||||
|
||||
@@ -99,6 +149,7 @@ def StaticPageView(request, url):
|
||||
})
|
||||
|
||||
t = loader.get_template('pages/p_static_page.html')
|
||||
return HttpResponse(t.render(Dict, request))
|
||||
return get_inter_http_respose(t, Dict, request)
|
||||
# return HttpResponse(t.render(Dict, request))
|
||||
|
||||
|
||||
|
||||
0
PushMessages/__init__.py
Normal file
3
PushMessages/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
PushMessages/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PushmessagesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'PushMessages'
|
||||
0
PushMessages/migrations/__init__.py
Normal file
3
PushMessages/models.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
PushMessages/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
16
PushMessages/urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# coding=utf-8
|
||||
from django.urls import path, include
|
||||
# from AuthApp.js_views import *
|
||||
# from AuthApp.import_funcs import *
|
||||
from .views import *
|
||||
from django.contrib.auth import views
|
||||
from RoutesApp.js_views import new_route_view_ajax
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
path('send_push', send_push),
|
||||
path('webpush/', include('webpush.urls')),
|
||||
path('sw.js', TemplateView.as_view(template_name='sw.js', content_type='application/x-javascript')),
|
||||
|
||||
]
|
||||
49
PushMessages/views.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from django.http.response import JsonResponse, HttpResponse
|
||||
from django.views.decorators.http import require_GET, require_POST
|
||||
from django.contrib.auth.models import User
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from webpush import send_user_notification
|
||||
import json
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
def get_key_Dict():
|
||||
webpush_settings = getattr(settings, 'WEBPUSH_SETTINGS', {})
|
||||
vapid_key = webpush_settings.get('VAPID_PUBLIC_KEY')
|
||||
Dict = {
|
||||
'vapid_key': vapid_key
|
||||
}
|
||||
return Dict
|
||||
|
||||
def send_push(user, title, text, url=None, button_name=None, img=None):
|
||||
try:
|
||||
# body = request.body
|
||||
# data = json.loads(body)
|
||||
#
|
||||
# if 'head' not in data or 'body' not in data or 'id' not in data:
|
||||
# return JsonResponse(status=400, data={"message": "Invalid data format"})
|
||||
#
|
||||
# user_id = data['id']
|
||||
# user = get_object_or_404(User, pk=user_id)
|
||||
Dict = {
|
||||
'head': title,
|
||||
'body': text,
|
||||
}
|
||||
if url:
|
||||
Dict['url'] = url
|
||||
if button_name:
|
||||
Dict['button_name'] = button_name
|
||||
else:
|
||||
Dict['button_name'] = _('Перейти'),
|
||||
|
||||
if img:
|
||||
Dict['img'] = img
|
||||
|
||||
# payload = {'head': data['head'], 'body': data['body']}
|
||||
send_user_notification(user=user, payload=Dict, ttl=1000)
|
||||
|
||||
return JsonResponse(status=200, data={"message": "Web push successful"})
|
||||
except TypeError:
|
||||
return JsonResponse(status=500, data={"message": "An error occurred"})
|
||||
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
celery -A TWB.celery:app worker -l info
|
||||
|
||||
celery -A TWB.celery:app beat -l info
|
||||
@@ -13,6 +13,7 @@ from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.db.models import Q
|
||||
import json
|
||||
from GeneralApp.funcs import get_inter_http_respose
|
||||
|
||||
def get_address_point_ajax(request):
|
||||
from .funcs import search_cities_in_db, search_airports_in_db
|
||||
@@ -54,7 +55,7 @@ def get_address_point_ajax(request):
|
||||
else:
|
||||
item['city_name'] = item['name']
|
||||
item['country_name'] = item['country__name']
|
||||
item['fullname'] = f'{item["country_name"]} / {item["city_name"]}'
|
||||
item['fullname'] = f'{item["city_name"]} / {item["country_name"]}'
|
||||
html = f"{html}{render_to_string('widgets/w_ac_input_address_point.html', item)}"
|
||||
i += 1
|
||||
|
||||
|
||||
@@ -3,21 +3,21 @@ from BaseModels.base_models import BaseModel
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
class Country(BaseModel):
|
||||
international_name = models.CharField(max_length=250, verbose_name='Международное название', blank=True, null=True)
|
||||
official_name = models.CharField(max_length=250, verbose_name='Официальное название', blank=True, null=True)
|
||||
international_name = models.CharField(max_length=250, verbose_name=_('Международное название'), blank=True, null=True)
|
||||
official_name = models.CharField(max_length=250, verbose_name=_('Официальное название'), blank=True, null=True)
|
||||
|
||||
short_code = models.CharField(max_length=2, verbose_name='Код страны по ISO3166-1:alpha2')
|
||||
code = models.CharField(max_length=3, verbose_name='Код страны по ISO3166-1:alpha3')
|
||||
num_code = models.CharField(max_length=3, verbose_name='Код страны по ISO3166-1:numeric', blank=True, null=True)
|
||||
short_code = models.CharField(max_length=2, verbose_name=_('Код страны по ISO3166-1:alpha2'))
|
||||
code = models.CharField(max_length=3, verbose_name=_('Код страны по ISO3166-1:alpha3'))
|
||||
num_code = models.CharField(max_length=3, verbose_name=_('Код страны по ISO3166-1:numeric'), blank=True, null=True)
|
||||
|
||||
flag_img_url = models.URLField(verbose_name='Ссылка на изображение флага', blank=True, null=True)
|
||||
flag_img_url = models.URLField(verbose_name=_('Ссылка на изображение флага'), blank=True, null=True)
|
||||
|
||||
geo_lat = models.CharField(max_length=20, verbose_name='GPS широта', blank=True, null=True)
|
||||
geo_lon = models.CharField(max_length=20, verbose_name='GPS долгота', blank=True, null=True)
|
||||
geo_lat = models.CharField(max_length=20, verbose_name=_('GPS широта'), blank=True, null=True)
|
||||
geo_lon = models.CharField(max_length=20, verbose_name=_('GPS долгота'), blank=True, null=True)
|
||||
|
||||
area_id = models.BigIntegerField(blank=True, null=True)
|
||||
|
||||
parsing_finished_DT = models.DateTimeField(verbose_name='Дата и время завершения парсинга', blank=True, null=True)
|
||||
parsing_finished_DT = models.DateTimeField(verbose_name=_('Дата и время завершения парсинга'), blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
if self.name:
|
||||
@@ -39,14 +39,14 @@ class Country(BaseModel):
|
||||
class City(BaseModel):
|
||||
|
||||
country = models.ForeignKey(
|
||||
Country, verbose_name='Страна', related_name='rel_cities_for_country', on_delete=models.CASCADE)
|
||||
Country, verbose_name=_('Страна'), related_name='rel_cities_for_country', on_delete=models.CASCADE)
|
||||
|
||||
geo_lat = models.CharField(max_length=20, verbose_name='GPS широта', blank=True, null=True)
|
||||
geo_lon = models.CharField(max_length=20, verbose_name='GPS долгота', blank=True, null=True)
|
||||
geo_lat = models.CharField(max_length=20, verbose_name=_('GPS широта'), blank=True, null=True)
|
||||
geo_lon = models.CharField(max_length=20, verbose_name=_('GPS долгота'), blank=True, null=True)
|
||||
|
||||
area_id = models.BigIntegerField(blank=True, null=True)
|
||||
|
||||
parsing_finished_DT = models.DateTimeField(verbose_name='Дата и время завершения парсинга', blank=True, null=True)
|
||||
parsing_finished_DT = models.DateTimeField(verbose_name=_('Дата и время завершения парсинга'), blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
if self.name:
|
||||
@@ -55,7 +55,7 @@ class City(BaseModel):
|
||||
return f'{self.id}'
|
||||
|
||||
def get_country_n_city_str(self):
|
||||
country = 'Неизвестно'
|
||||
country = _('Неизвестно')
|
||||
city = self.name
|
||||
if self.country:
|
||||
country = self.country
|
||||
@@ -72,20 +72,20 @@ class City(BaseModel):
|
||||
class Airport(BaseModel):
|
||||
|
||||
city = models.ForeignKey(
|
||||
City, verbose_name='Город', related_name='rel_airports_for_city', on_delete=models.CASCADE,
|
||||
City, verbose_name=_('Город'), related_name='rel_airports_for_city', on_delete=models.CASCADE,
|
||||
blank=True, null=True)
|
||||
|
||||
international_name = models.CharField(max_length=250, verbose_name='Международное название', blank=True, null=True)
|
||||
international_name = models.CharField(max_length=250, verbose_name=_('Международное название'), blank=True, null=True)
|
||||
|
||||
iata_code = models.CharField(max_length=3, verbose_name='IATA')
|
||||
icao_code = models.CharField(max_length=4, verbose_name='ICAO')
|
||||
|
||||
geo_lat = models.CharField(max_length=20, verbose_name='GPS широта', blank=True, null=True)
|
||||
geo_lon = models.CharField(max_length=20, verbose_name='GPS долгота', blank=True, null=True)
|
||||
geo_lat = models.CharField(max_length=20, verbose_name=_('GPS широта'), blank=True, null=True)
|
||||
geo_lon = models.CharField(max_length=20, verbose_name=_('GPS долгота'), blank=True, null=True)
|
||||
|
||||
area_id = models.BigIntegerField(blank=True, null=True)
|
||||
|
||||
parsing_finished_DT = models.DateTimeField(verbose_name='Дата и время завершения парсинга', blank=True, null=True)
|
||||
parsing_finished_DT = models.DateTimeField(verbose_name=_('Дата и время завершения парсинга'), blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
if self.name:
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.contrib import admin
|
||||
|
||||
class Admin_Route(Admin_Trans_BaseModel):
|
||||
list_display = [
|
||||
'id', 'owner_type', 'type_transport', 'cargo_type',
|
||||
'id', 'owner_type', 'receive_msg_by_email', 'type_transport', 'cargo_type',
|
||||
'departure_DT', 'from_city', 'from_place',
|
||||
'arrival_DT', 'to_city', 'to_place', 'owner',
|
||||
'order', 'modifiedDT', 'createDT'
|
||||
@@ -14,5 +14,6 @@ class Admin_Route(Admin_Trans_BaseModel):
|
||||
|
||||
list_filter = ['owner_type', 'type_transport', 'cargo_type', 'from_place', 'arrival_DT', 'modifiedDT', 'createDT']
|
||||
search_fields = ['owner__first_name', 'owner__last_name']
|
||||
raw_id_fields = ['from_city', 'to_city']
|
||||
|
||||
admin.site.register(Route,Admin_Route)
|
||||
|
||||
@@ -6,6 +6,47 @@ from django.core.exceptions import ValidationError
|
||||
from .models import *
|
||||
|
||||
|
||||
def routeForm_assign_choices_by_type_transport(form, type_transport):
|
||||
if type_transport == 'avia':
|
||||
transfer_location_choices = (
|
||||
('airport', _('В аэропорту')),
|
||||
('city', _('По городу')),
|
||||
('other', _('По договоренности'))
|
||||
)
|
||||
|
||||
cargo_type_choices = (
|
||||
('cargo', _('Груз')),
|
||||
('parcel', _('Бандероль')),
|
||||
('package', _('Посылка')),
|
||||
('letter', _('Письмо\Документ'))
|
||||
)
|
||||
|
||||
else:
|
||||
transfer_location_choices = (
|
||||
('city', _('По городу')),
|
||||
('other', _('По договоренности'))
|
||||
)
|
||||
|
||||
cargo_type_choices = (
|
||||
('passenger', _('Пассажир')),
|
||||
('cargo', _('Груз')),
|
||||
('parcel', _('Бандероль')),
|
||||
('package', _('Посылка')),
|
||||
('letter', _('Письмо\Документ'))
|
||||
)
|
||||
|
||||
form.fields['from_place'].choices = transfer_location_choices
|
||||
form.fields['to_place'].choices = transfer_location_choices
|
||||
form.fields['cargo_type'].choices = cargo_type_choices
|
||||
|
||||
form.base_fields['from_place'].choices = transfer_location_choices
|
||||
form.base_fields['to_place'].choices = transfer_location_choices
|
||||
form.base_fields['cargo_type'].choices = cargo_type_choices
|
||||
|
||||
return form
|
||||
|
||||
|
||||
|
||||
class RouteForm(forms.ModelForm):
|
||||
from_address_point_txt = forms.CharField(required=True)
|
||||
to_address_point_txt = forms.CharField(required=True)
|
||||
@@ -24,6 +65,22 @@ class RouteForm(forms.ModelForm):
|
||||
|
||||
try:
|
||||
|
||||
if 'phone' in cleaned_data and 'phone' in cleaned_data:
|
||||
from BaseModels.validators.form_field_validators import get_phone_valid_error
|
||||
error = get_phone_valid_error(cleaned_data["phone"])
|
||||
if error:
|
||||
self.add_error('phone', error)
|
||||
|
||||
if 'extra_phone' in cleaned_data and 'extra_phone' in cleaned_data:
|
||||
from BaseModels.validators.form_field_validators import get_phone_valid_error
|
||||
error = get_phone_valid_error(cleaned_data["extra_phone"])
|
||||
if error:
|
||||
self.add_error('extra_phone', error)
|
||||
|
||||
if 'departure_DT' in cleaned_data and 'arrival_DT' in cleaned_data and cleaned_data['arrival_DT'] < cleaned_data['departure_DT']:
|
||||
self.add_error('arrival_DT', _('Указана неверная дата прибытия'))
|
||||
|
||||
|
||||
if 'from_place' in self.data and self.data['from_place'] not in [item[0] for item in self.fields['from_place'].choices]:
|
||||
cleaned_data['from_place'] = self.fields['from_place'].choices[0][0]
|
||||
if 'from_place' in self.errors and self.errors['from_place'].data[0].code == 'invalid_choice':
|
||||
|
||||
@@ -4,12 +4,22 @@ from django.utils.translation import gettext as _
|
||||
from django.template.loader import render_to_string
|
||||
from datetime import datetime
|
||||
|
||||
elements_on_page = 25
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_profile_new_route_page_html(request, data):
|
||||
|
||||
form = RouteForm()
|
||||
Dict = {
|
||||
'form': form
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
|
||||
errors_off = True
|
||||
@@ -24,59 +34,71 @@ def get_profile_new_route_page_html(request, data):
|
||||
if 'to_address_point_txt' in data:
|
||||
del data['to_address_point_txt']
|
||||
|
||||
|
||||
# if data['type_transport'] == 'avia':
|
||||
# transfer_location_choices = (
|
||||
# ('airport', _('В аэропорту')),
|
||||
# ('city', _('По городу')),
|
||||
# ('other', _('По договоренности'))
|
||||
# )
|
||||
#
|
||||
# cargo_type_choices = (
|
||||
# ('cargo', _('Груз')),
|
||||
# ('parcel', _('Бандероль')),
|
||||
# ('package', _('Посылка')),
|
||||
# ('letter', _('Письмо\Документ'))
|
||||
# )
|
||||
#
|
||||
#
|
||||
# else:
|
||||
# transfer_location_choices = (
|
||||
# ('city', _('По городу')),
|
||||
# ('other', _('По договоренности'))
|
||||
# )
|
||||
#
|
||||
# cargo_type_choices = (
|
||||
# ('passenger', _('Пассажир')),
|
||||
# ('cargo', _('Груз')),
|
||||
# ('parcel', _('Бандероль')),
|
||||
# ('package', _('Посылка')),
|
||||
# ('letter', _('Письмо\Документ'))
|
||||
# )
|
||||
|
||||
form = RouteForm(data)
|
||||
if not form.is_valid():
|
||||
pass
|
||||
form = RouteForm(initial=form.cleaned_data)
|
||||
|
||||
if 'type_transport' in data:
|
||||
if data['type_transport'] == 'avia':
|
||||
transfer_location_choices = (
|
||||
('airport', _('В аэропорту')),
|
||||
('city', _('По городу')),
|
||||
('other', _('По договоренности'))
|
||||
)
|
||||
form = routeForm_assign_choices_by_type_transport(form, data['type_transport'])
|
||||
|
||||
cargo_type_choices = (
|
||||
('passenger', _('Пассажир')),
|
||||
('cargo', _('Груз')),
|
||||
('parcel', _('Бандероль')),
|
||||
('package', _('Посылка')),
|
||||
('letter', _('Письмо\Документ'))
|
||||
)
|
||||
|
||||
|
||||
else:
|
||||
transfer_location_choices = (
|
||||
('city', _('По городу')),
|
||||
('other', _('По договоренности'))
|
||||
)
|
||||
|
||||
cargo_type_choices = (
|
||||
('cargo', _('Груз')),
|
||||
('parcel', _('Бандероль')),
|
||||
('package', _('Посылка')),
|
||||
('letter', _('Письмо\Документ'))
|
||||
)
|
||||
|
||||
form = RouteForm(data)
|
||||
if not form.is_valid():
|
||||
pass
|
||||
form = RouteForm(initial=form.cleaned_data)
|
||||
|
||||
form.fields['from_place'].choices = transfer_location_choices
|
||||
form.fields['to_place'].choices = transfer_location_choices
|
||||
form.fields['cargo_type'].choices = cargo_type_choices
|
||||
|
||||
form.base_fields['from_place'].choices = transfer_location_choices
|
||||
form.base_fields['to_place'].choices = transfer_location_choices
|
||||
form.base_fields['cargo_type'].choices = cargo_type_choices
|
||||
# form.fields['from_place'].choices = transfer_location_choices
|
||||
# form.fields['to_place'].choices = transfer_location_choices
|
||||
# form.fields['cargo_type'].choices = cargo_type_choices
|
||||
#
|
||||
# form.base_fields['from_place'].choices = transfer_location_choices
|
||||
# form.base_fields['to_place'].choices = transfer_location_choices
|
||||
# form.base_fields['cargo_type'].choices = cargo_type_choices
|
||||
|
||||
# form = CreateRouteForm(initial=data)
|
||||
|
||||
# if not form.is_valid():
|
||||
# pass
|
||||
|
||||
if 'owner_type' in data:
|
||||
form.initial['owner_type'] = data['owner_type']
|
||||
|
||||
if request.user and request.user.is_authenticated and request.user.user_profile and request.user.user_profile.phone:
|
||||
form.initial.update({'phone': request.user.user_profile.phone})
|
||||
|
||||
Dict = {
|
||||
'form': form,
|
||||
'errors_off': errors_off
|
||||
}
|
||||
|
||||
if 'owner_type' in data:
|
||||
Dict.update({'owner_type': data['owner_type']})
|
||||
|
||||
# print(form)
|
||||
except Exception as e:
|
||||
# form.errors.append({'__all__': f'Ошибка: {str(e)}'})
|
||||
@@ -138,18 +160,31 @@ def get_routes_Dict(user=None, data=None):
|
||||
res_Dict = {}
|
||||
|
||||
if data:
|
||||
|
||||
type_transport = None
|
||||
if 'type_transport' in data and data['type_transport']:
|
||||
items_list = data['type_transport'].split(',')
|
||||
kwargs.update({f'type_transport__in': items_list})
|
||||
|
||||
if len(items_list) == 1:
|
||||
type_transport = items_list[0]
|
||||
|
||||
|
||||
for key, val in data.items():
|
||||
if val:
|
||||
if key == 'weight':
|
||||
weight_list = val.split(';')
|
||||
if weight_list[0]:
|
||||
kwargs.update({f'{key}__gte': int(weight_list[0])})
|
||||
if weight_list[1]:
|
||||
kwargs.update({f'{key}__lte': int(weight_list[1])})
|
||||
if len(weight_list) > 1:
|
||||
if weight_list[0]:
|
||||
kwargs.update({f'{key}__gte': int(weight_list[0])})
|
||||
if weight_list[1]:
|
||||
kwargs.update({f'{key}__lte': int(weight_list[1])})
|
||||
else:
|
||||
kwargs.update({f'{key}__lte': int(weight_list[0])})
|
||||
|
||||
if key == 'type_transport':
|
||||
items_list = val.split(',')
|
||||
kwargs.update({f'{key}__in': items_list})
|
||||
# if key == 'type_transport':
|
||||
# items_list = val.split(',')
|
||||
# kwargs.update({f'{key}__in': items_list})
|
||||
|
||||
|
||||
if key in (
|
||||
@@ -169,19 +204,18 @@ def get_routes_Dict(user=None, data=None):
|
||||
kwargs.update({key: val})
|
||||
|
||||
if key == 'from_address_point':
|
||||
kwargs.update({f'from_city__id': val})
|
||||
city = get_city_by_type_transport_and_address_point(type_transport, val)
|
||||
kwargs.update({f'from_city': city})
|
||||
|
||||
res_Dict.update({
|
||||
'from_address_point_txt': get_country_n_city_str_by_type_transport_and_address_point(
|
||||
'road', val
|
||||
)
|
||||
'from_address_point_txt': city.get_country_n_city_str()
|
||||
})
|
||||
|
||||
if key == 'to_address_point':
|
||||
kwargs.update({f'to_city__id': val})
|
||||
city = get_city_by_type_transport_and_address_point(type_transport, val)
|
||||
kwargs.update({f'to_city': city})
|
||||
res_Dict.update({
|
||||
'to_address_point_txt': get_country_n_city_str_by_type_transport_and_address_point(
|
||||
'road', val
|
||||
)
|
||||
'to_address_point_txt': city.get_country_n_city_str()
|
||||
})
|
||||
|
||||
if key == 'from_el':
|
||||
@@ -189,7 +223,7 @@ def get_routes_Dict(user=None, data=None):
|
||||
if key == 'to_el':
|
||||
to_el = int(val)
|
||||
|
||||
routes = Route.objects.filter(**kwargs).order_by('departure_DT', 'arrival_DT', '-modifiedDT')
|
||||
routes = Route.objects.filter(**kwargs).order_by('-departure_DT', '-arrival_DT', '-modifiedDT')
|
||||
routes_count = routes.count()
|
||||
|
||||
if from_el and to_el:
|
||||
@@ -199,14 +233,17 @@ def get_routes_Dict(user=None, data=None):
|
||||
elif to_el:
|
||||
routes = routes[:to_el]
|
||||
else:
|
||||
to_el = 25
|
||||
routes = routes[:25]
|
||||
to_el = elements_on_page
|
||||
routes = routes[:to_el]
|
||||
|
||||
last_block_routes = False
|
||||
last_block = False
|
||||
if not to_el or to_el >= routes_count:
|
||||
last_block_routes = True
|
||||
|
||||
last_block = True
|
||||
|
||||
if routes_count - to_el > elements_on_page:
|
||||
next_page_els_count = elements_on_page
|
||||
else:
|
||||
next_page_els_count = routes_count - to_el
|
||||
|
||||
try:
|
||||
|
||||
@@ -231,7 +268,9 @@ def get_routes_Dict(user=None, data=None):
|
||||
|
||||
res_Dict.update({
|
||||
'routes': routes,
|
||||
'last_block_routes': last_block_routes
|
||||
'last_block': last_block,
|
||||
'last_el': to_el,
|
||||
'next_page_els_count': next_page_els_count
|
||||
})
|
||||
return res_Dict
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ def edit_route_ajax(request):
|
||||
route = Route.objects.get(id=data['route_id'])
|
||||
|
||||
form = RouteForm(instance=route)
|
||||
form = routeForm_assign_choices_by_type_transport(form, route.type_transport)
|
||||
|
||||
route_address_points_Dict = route.get_address_points()
|
||||
form.initial.update({
|
||||
'from_address_point_txt': route_address_points_Dict['from_address_point_txt'],
|
||||
@@ -90,6 +92,9 @@ def edit_route_ajax(request):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def new_route_view_ajax(request):
|
||||
if request.method != 'POST':
|
||||
raise Http404
|
||||
@@ -140,12 +145,15 @@ def find_routes_ajax(request):
|
||||
if 'errors' in routes_Dict:
|
||||
return JsonResponse(routes_Dict, status=400)
|
||||
|
||||
|
||||
html = render_to_string('blocks/b_search_routes.html', routes_Dict, request=request)
|
||||
if routes_Dict['routes']:
|
||||
html = render_to_string('blocks/b_search_routes.html', routes_Dict, request=request)
|
||||
else:
|
||||
html = render_to_string('templates_js_translate/not_found_find_routes.html', routes_Dict, request=request)
|
||||
|
||||
res_Dict = {
|
||||
'html': html,
|
||||
'last_block_routes': routes_Dict['last_block_routes']
|
||||
'last_block': routes_Dict['last_block'],
|
||||
'next_page_els_count': routes_Dict['next_page_els_count'],
|
||||
# 'form': RouteForm(initial=data)
|
||||
}
|
||||
|
||||
@@ -224,7 +232,7 @@ def create_or_change_route_ajax(request, route_id=None):
|
||||
return JsonResponse({'html': html}, status=400)
|
||||
|
||||
obj = form.save(commit=False)
|
||||
if 'owner_type' in data:
|
||||
if 'owner_type' in data and data['owner_type']:
|
||||
obj.owner_type = data['owner_type']
|
||||
|
||||
if obj.from_address_point:
|
||||
@@ -236,6 +244,8 @@ def create_or_change_route_ajax(request, route_id=None):
|
||||
obj.owner = request.user
|
||||
obj.save()
|
||||
|
||||
route_id = obj.id
|
||||
|
||||
routes_Dict = get_routes_Dict(request.user)
|
||||
|
||||
if 'errors' in routes_Dict:
|
||||
@@ -247,7 +257,8 @@ def create_or_change_route_ajax(request, route_id=None):
|
||||
html = render_to_string('blocks/profile/b_my_routes.html', routes_Dict, request=request)
|
||||
|
||||
res_Dict = {
|
||||
'html': html
|
||||
'html': html,
|
||||
'route_id': route_id
|
||||
}
|
||||
|
||||
return JsonResponse(res_Dict)
|
||||
|
||||
0
RoutesApp/management/__init__.py
Normal file
0
RoutesApp/management/commands/__init__.py
Normal file
36
RoutesApp/management/commands/every_1hour_start.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from datetime import datetime
|
||||
from BaseModels.mailSender import techSendMail
|
||||
from GeneralApp.funcs_options import get_options_by_opt_types, get_mail_send_options
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
mail_sets = get_mail_send_options()
|
||||
|
||||
log = ''
|
||||
log_begin_DT = datetime.now()
|
||||
msg = str(log_begin_DT)
|
||||
print('-------------')
|
||||
print(msg)
|
||||
|
||||
try:
|
||||
from ...search_matches import search_matches
|
||||
msg = search_matches()
|
||||
if msg:
|
||||
print(msg)
|
||||
except Exception as e:
|
||||
msg = f'every_1hour_start search_matches fail = {str(e)}'
|
||||
print(msg)
|
||||
techSendMail(mail_sets, msg, title='every_1hour_start search_matches')
|
||||
|
||||
|
||||
|
||||
if msg:
|
||||
techSendMail(mail_sets, str(msg), title='every_1hour_start get_competitors_prices')
|
||||
|
||||
|
||||
|
||||
print(f'- processing time = {str(datetime.now() - log_begin_DT)} -')
|
||||
|
||||
|
||||
13
RoutesApp/management/commands/fix_code.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from datetime import datetime
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
# from B2BApp.funcs import assign_contracts_for_orders_by_tmp_data
|
||||
# log = assign_contracts_for_orders_by_tmp_data()
|
||||
from GeneralApp.views import test_code
|
||||
test_code(None)
|
||||
|
||||
self.stdout.write(u'fix_code end')
|
||||
@@ -24,8 +24,8 @@ cargo_type_choices = (
|
||||
)
|
||||
|
||||
owner_type_choices = (
|
||||
('customer', 'Заказчик'),
|
||||
('mover', 'Перевозчик')
|
||||
('customer', _('Заказчик')),
|
||||
('mover', _('Перевозчик'))
|
||||
)
|
||||
|
||||
|
||||
@@ -75,14 +75,14 @@ class Route(BaseModel):
|
||||
ordering = ('name',)
|
||||
|
||||
def from_country_n_city_str(self):
|
||||
res = 'Неизвестно'
|
||||
res = _('Неизвестно')
|
||||
if self.from_city:
|
||||
res = self.from_city.get_country_n_city_str()
|
||||
|
||||
return res
|
||||
|
||||
def to_country_n_city_str(self):
|
||||
res = 'Неизвестно'
|
||||
res = _('Неизвестно')
|
||||
if self.to_city:
|
||||
res = self.to_city.get_country_n_city_str()
|
||||
|
||||
@@ -111,8 +111,8 @@ class Route(BaseModel):
|
||||
to_address_point_Dict = to_address_point_objs.values(
|
||||
'id', 'name', 'country__name')[0]
|
||||
|
||||
from_address_point_txt = f'{from_address_point_Dict["country__name"]} / {from_address_point_Dict["name"]}'
|
||||
to_address_point_txt = f'{to_address_point_Dict["country__name"]} / {to_address_point_Dict["name"]}'
|
||||
from_address_point_txt = f'{from_address_point_Dict["name"]} / {from_address_point_Dict["country__name"]}'
|
||||
to_address_point_txt = f'{to_address_point_Dict["name"]} / {to_address_point_Dict["country__name"]}'
|
||||
|
||||
return {
|
||||
'from_address_point_obj': from_address_point_objs[0],
|
||||
|
||||
145
RoutesApp/search_matches.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from .models import *
|
||||
from datetime import datetime, timedelta
|
||||
from django.utils.translation import gettext as _
|
||||
from django.template.loader import render_to_string
|
||||
from GeneralApp.funcs_options import get_options_by_opt_types, get_mail_send_options
|
||||
from BaseModels.mailSender import admin_send_mail_by_SMTPlib, techSendMail
|
||||
|
||||
|
||||
|
||||
def get_Dict_for_send_msgs(kwargs, search_owner_type):
|
||||
print('get_Dict_for_send_msgs')
|
||||
|
||||
Dict = {
|
||||
'search_owner_type': search_owner_type
|
||||
}
|
||||
sets = get_options_by_opt_types(['domain', 'project_name'], only_vals=True)
|
||||
Dict.update(sets)
|
||||
|
||||
|
||||
Dict.update({'logo': f'{sets["domain"]}/static/img/svg/LogoMobile.svg', })
|
||||
|
||||
find_routes_page_url = f'{sets["domain"]}/routes/route_search_results/?'
|
||||
kwargs_list = [f'{key}={value}' for key, value in kwargs.items()]
|
||||
kwargs_list.append(f'owner_type={search_owner_type}')
|
||||
find_routes_page_url += f'{"&".join(kwargs_list)}'
|
||||
|
||||
Dict.update({'find_routes_page_url': find_routes_page_url})
|
||||
|
||||
return Dict
|
||||
|
||||
|
||||
|
||||
def send_push_message_for_found_matches_routes(route, data_Dict):
|
||||
print(f'send_push_message_for_found_matches_routes to route id = {route.id}')
|
||||
|
||||
if not route.owner.is_authenticated:
|
||||
return None
|
||||
|
||||
from PushMessages.views import send_push
|
||||
title = 'Мы нашли исполнителя по Вашему объявлению!'
|
||||
text = 'Для просмотра результата нажмите на кнопку ниже'
|
||||
send_push(route.owner, title, text, url=data_Dict['find_routes_page_url'], button_name=_('Перейти к найденному'))
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def send_mail_found_matches_routes(route, data_Dict):
|
||||
print(f'send_mail_found_matches_routes to route id = {route.id}')
|
||||
|
||||
Dict = {
|
||||
'route': route,
|
||||
}
|
||||
Dict.update(data_Dict)
|
||||
|
||||
html = render_to_string('mail/m_found_matched_routes.html', Dict)
|
||||
|
||||
|
||||
mail_sets = get_mail_send_options()
|
||||
to = [route.owner.email, 'web@syncsystems.net']
|
||||
subject = _('Мы нашли исполнителя по Вашему объявлению!')
|
||||
res = admin_send_mail_by_SMTPlib(
|
||||
mail_sets,
|
||||
subject=subject,
|
||||
from_email=mail_sets['sender_email'], to=to,
|
||||
html_content=html
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def search_matches(for_routes=None):
|
||||
print('search_matches')
|
||||
|
||||
log = ''
|
||||
|
||||
try:
|
||||
if not for_routes:
|
||||
for_routes = Route.objects.filter(
|
||||
createDT__gte=datetime.now() - timedelta(hours=1),
|
||||
receive_msg_by_email=True
|
||||
)
|
||||
|
||||
check_fields = [
|
||||
'type_transport', 'departure_DT', 'arrival_DT', 'from_address_point', 'to_address_point',
|
||||
'from_place', 'to_place', 'cargo_type', 'weight'
|
||||
]
|
||||
|
||||
if for_routes:
|
||||
msg = f'last hour create routes count = {for_routes.count()}'
|
||||
else:
|
||||
msg = f'last hour not create routes'
|
||||
print(msg)
|
||||
|
||||
for route in for_routes:
|
||||
kwargs = {}
|
||||
params = {}
|
||||
|
||||
for field_name in check_fields:
|
||||
field_val = getattr(route, field_name, None)
|
||||
if field_val:
|
||||
if type(field_val) == datetime:
|
||||
params.update({f"{field_name}": f'{field_val.strftime("%d.%m.%Y")} - {field_val.strftime("%d.%m.%Y")}'})
|
||||
kwargs.update({f"{field_name}__date": field_val.date()})
|
||||
elif field_name == 'weight':
|
||||
# print(field_name)
|
||||
params.update({f"{field_name}": field_val})
|
||||
if route.owner_type == 'mover':
|
||||
kwargs.update({f"{field_name}__lte": field_val})
|
||||
else:
|
||||
kwargs.update({f"{field_name}__gte": field_val})
|
||||
else:
|
||||
kwargs.update({field_name: field_val})
|
||||
params.update({field_name: field_val})
|
||||
|
||||
found_routes = Route.objects.exclude(
|
||||
id=route.id,
|
||||
).exclude(
|
||||
owner=route.owner
|
||||
).exclude(
|
||||
owner_type=route.owner_type
|
||||
).filter(
|
||||
**kwargs
|
||||
# ).count(
|
||||
)
|
||||
|
||||
if found_routes:
|
||||
msg = f'found routes for send messages = {found_routes.count()}'
|
||||
|
||||
data_Dict = get_Dict_for_send_msgs(params, found_routes[0].owner_type)
|
||||
msg = send_push_message_for_found_matches_routes(route, data_Dict)
|
||||
if msg:
|
||||
log += msg
|
||||
msg = send_mail_found_matches_routes(route, data_Dict)
|
||||
if msg:
|
||||
log += msg
|
||||
|
||||
except Exception as e:
|
||||
msg = f'<br>\n! search_matches Error = {str(e)}'
|
||||
print(msg)
|
||||
log += msg
|
||||
|
||||
mail_sets = get_mail_send_options()
|
||||
techSendMail(mail_sets, log, title='search_matches fail')
|
||||
|
||||
return log
|
||||
@@ -11,7 +11,7 @@ from django.utils.translation import gettext as _
|
||||
from datetime import datetime
|
||||
from .funcs import *
|
||||
from .forms import *
|
||||
|
||||
from GeneralApp.funcs import get_inter_http_respose
|
||||
|
||||
|
||||
|
||||
@@ -28,9 +28,12 @@ def route_search_results_View(request):
|
||||
if routes_Dict:
|
||||
Dict = {
|
||||
'routes': routes_Dict['routes'],
|
||||
'last_block_routes': routes_Dict['last_block_routes'],
|
||||
'last_block': routes_Dict['last_block'],
|
||||
'show_filter_and_results': True,
|
||||
'owner_type': data['owner_type']
|
||||
'owner_type': data['owner_type'],
|
||||
'last_el': routes_Dict['last_el'],
|
||||
'page_type': 'routes',
|
||||
'next_page_els_count': routes_Dict['next_page_els_count'],
|
||||
}
|
||||
if 'from_address_point_txt' in routes_Dict:
|
||||
data.update({'from_address_point_txt': routes_Dict['from_address_point_txt']})
|
||||
@@ -38,5 +41,20 @@ def route_search_results_View(request):
|
||||
data.update({'to_address_point_txt': routes_Dict['to_address_point_txt']})
|
||||
Dict.update({'route_form': RouteForm(initial=data)})
|
||||
|
||||
title = _('Результат поиска маршрутов')
|
||||
if 'from_address_point_txt' in data:
|
||||
title = f'{title} из {data["from_address_point_txt"]}'
|
||||
if 'to_address_point_txt' in data:
|
||||
title = f'{title} в {data["to_address_point_txt"]}'
|
||||
|
||||
Dict.update({
|
||||
'page': {
|
||||
'title': title,
|
||||
'description': title,
|
||||
'keywords': title,
|
||||
}
|
||||
})
|
||||
|
||||
t = loader.get_template('pages/p_results_find_route.html')
|
||||
return HttpResponse(t.render(Dict, request))
|
||||
return get_inter_http_respose(t, Dict, request)
|
||||
# return HttpResponse(t.render(Dict, request))
|
||||
|
||||
@@ -6,7 +6,7 @@ class Admin_Subscribe(Admin_Trans_BaseModel):
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'classes': ['wide'],
|
||||
'fields': ('name',
|
||||
'fields': ('name', 'enable',
|
||||
('price'),
|
||||
'options',
|
||||
'period_name', 'period',
|
||||
@@ -18,12 +18,13 @@ class Admin_Subscribe(Admin_Trans_BaseModel):
|
||||
|
||||
list_display = [
|
||||
'id',
|
||||
'name', 'price',
|
||||
'name', 'enable', 'price',
|
||||
'period_name', 'period',
|
||||
'order', 'modifiedDT', 'createDT'
|
||||
]
|
||||
|
||||
list_display_links = ['id']
|
||||
list_editable = ['enable']
|
||||
|
||||
list_filter = ['modifiedDT', 'createDT']
|
||||
search_fields = ['name', 'period_name']
|
||||
@@ -37,16 +38,18 @@ class Admin_SubscribeOption(Admin_Trans_BaseModel):
|
||||
(None, {
|
||||
'classes': ['wide'],
|
||||
'fields': ('name',
|
||||
'order'
|
||||
'order',
|
||||
'enable'
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
list_display = [
|
||||
'id',
|
||||
'id', 'enable',
|
||||
'name',
|
||||
'order', 'modifiedDT', 'createDT'
|
||||
]
|
||||
list_editable = ['enable']
|
||||
|
||||
list_display_links = ['id']
|
||||
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
from .models import *
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
def get_cur_user_subscribe(user):
|
||||
|
||||
user_subscribe = None
|
||||
try:
|
||||
user_subscribe = SubscribeForUser.objects.get(user=user)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return user_subscribe
|
||||
|
||||
|
||||
def get_subsribes_w_options():
|
||||
all_options = SubscribeOption.objects.filter(enable=True)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.2.2 on 2023-11-30 13:42
|
||||
|
||||
import colorfield.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('SubscribesApp', '0002_alter_subscribeoption_options_subscribe_bg_color_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='subscribe',
|
||||
name='bg_color',
|
||||
field=colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=None, verbose_name='Цвет фона'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subscribe',
|
||||
name='text_color',
|
||||
field=colorfield.fields.ColorField(default='#000000', image_field=None, max_length=25, samples=None, verbose_name='Цвет текста'),
|
||||
),
|
||||
]
|
||||
7
SubscribesApp/serializers.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class SubscribersSerializer(serializers.Serializer):
|
||||
email = serializers.EmailField(error_messages={'invalid': 'Invalid email'})
|
||||
email_notification = serializers.BooleanField()
|
||||
auto_subscribe = serializers.BooleanField()
|
||||
9
SubscribesApp/urls.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
|
||||
from SubscribesApp.views import SubscribersView
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
path('auto-subscribe/', SubscribersView.as_view(), name='auto_subscribe'),
|
||||
|
||||
]
|
||||
@@ -1,3 +1,43 @@
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import JsonResponse
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.views import APIView
|
||||
|
||||
# Create your views here.
|
||||
from SubscribesApp.models import SubscribeForUser
|
||||
from SubscribesApp.serializers import SubscribersSerializer
|
||||
|
||||
|
||||
class SubscribersView(APIView):
|
||||
# permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
serializer = SubscribersSerializer(data=request.data)
|
||||
|
||||
if serializer.is_valid():
|
||||
validated_data = serializer.validated_data
|
||||
email = validated_data['email']
|
||||
email_notification = validated_data['email_notification']
|
||||
auto_subscribe = validated_data['auto_subscribe']
|
||||
|
||||
user = User.objects.filter(email=email)
|
||||
|
||||
if user:
|
||||
|
||||
subscribe_for_user = SubscribeForUser.objects.filter(user_id=user[0].id)
|
||||
|
||||
if email_notification:
|
||||
subscribe_for_user.update(receive_finish_subscribe_msg=True)
|
||||
else:
|
||||
subscribe_for_user.update(receive_finish_subscribe_msg=False)
|
||||
|
||||
if auto_subscribe:
|
||||
subscribe_for_user.update(auto_continue=True)
|
||||
else:
|
||||
subscribe_for_user.update(auto_continue=False)
|
||||
|
||||
return JsonResponse({"message": "Subscriptions updated successfully"}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return JsonResponse({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
else:
|
||||
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
13
TWB/asgi.py
@@ -22,11 +22,14 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TWB.settings')
|
||||
|
||||
application = ProtocolTypeRouter({
|
||||
'http': get_asgi_application(),
|
||||
"websocket": QueryAuthMiddleware(
|
||||
URLRouter(
|
||||
websocket_urlpatterns
|
||||
)
|
||||
),
|
||||
"websocket":
|
||||
# AllowedHostsOriginValidator(
|
||||
QueryAuthMiddleware(
|
||||
URLRouter(
|
||||
websocket_urlpatterns
|
||||
)
|
||||
# )
|
||||
),
|
||||
# 'websocket': AuthMiddlewareStack(
|
||||
# URLRouter(
|
||||
# websocket_urlpatterns
|
||||
|
||||
27
TWB/celery.py
Executable file
@@ -0,0 +1,27 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
|
||||
from celery import Celery
|
||||
from celery.schedules import crontab
|
||||
from django.conf import settings
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "TWB.settings")
|
||||
|
||||
app = Celery('bo', include=['TWB.tasks'])
|
||||
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
|
||||
app.conf.beat_schedule = {
|
||||
'update-currency-rates': {
|
||||
'task': 'TWB.tasks.check_auto_subscribe',
|
||||
'schedule': crontab(minute=0, hour='*/1'),
|
||||
},
|
||||
'subscription_expiration_check': {
|
||||
'task': 'TWB.tasks.subscription_expiration_check',
|
||||
'schedule': crontab(hour=0, minute=0),
|
||||
# 'schedule': crontab(minute='*', hour='*'),
|
||||
},
|
||||
}
|
||||
|
||||
app.conf.broker_url = settings.CELERY_BROKER_URL
|
||||
@@ -11,10 +11,13 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from decouple import config
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
CSRF_TRUSTED_ORIGINS = ['https://tripwb.com']
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||
@@ -28,6 +31,50 @@ DEBUG = True
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
|
||||
WEBPUSH_SETTINGS = {
|
||||
"VAPID_PUBLIC_KEY": "BKS8byh3MucwCF2h06JY9oey1s1RYII09j-j3ehI3qTYhs965UHv0qNPl-jFjQBbIJCvjVXm9RW6t_oJJK8yMOk",
|
||||
"VAPID_PRIVATE_KEY": "f5NMgOntBtRqsyeKwEzloK-051ggMnZGF_GFimERY0w",
|
||||
"VAPID_ADMIN_EMAIL": "admin@tripwb.com"
|
||||
}
|
||||
|
||||
|
||||
SOCIALACCOUNT_LOGIN_ON_GET=True
|
||||
ACCOUNT_DEFAULT_HTTP_PROTOCOL='https'
|
||||
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
ACCOUNT_USERNAME_REQUIRED = False
|
||||
ACCOUNT_AUTHENTICATION_METHOD = 'email'
|
||||
ACCOUNT_EMAIL_VERIFICATION = 'optional'
|
||||
LOGIN_REDIRECT_URL = '/profile/page/dashboard/'
|
||||
LOGIN_URL = '/profile/login/'
|
||||
|
||||
LOGOUT_REDIRECT_URL = '/profile/login/'
|
||||
|
||||
ACCOUNT_SIGNUP_REDIRECT_URL = '/profile/page/dashboard/'
|
||||
ACCOUNT_LOGOUT_ON_GET = True
|
||||
# SOCIALACCOUNT_ADAPTER = 'GeneralApp.allauth_funcs.MyAccountAdapter'
|
||||
ACCOUNT_ADAPTER = 'GeneralApp.allauth_funcs.MyAccountAdapter'
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'allauth.account.auth_backends.AuthenticationBackend',
|
||||
]
|
||||
|
||||
|
||||
SOCIALACCOUNT_PROVIDERS = {
|
||||
'google': {
|
||||
'SCOPE': [
|
||||
'profile',
|
||||
'email',
|
||||
],
|
||||
'AUTH_PARAMS': {
|
||||
'access_type': 'online',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# NOTIFICATION_KEY = 'BJLyGzmo8sLI3Qkc6pN2cz11frCXiJdewvgve7Yps-_fM1lY1LSnTQfQxYtAgQ_26nAji_rgeYC1DkLiTwxw0Mo'
|
||||
|
||||
# SESSION_COOKIE_HTTPONLY = False
|
||||
|
||||
# Application definition
|
||||
@@ -46,17 +93,27 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
|
||||
'django.contrib.sites',
|
||||
|
||||
'colorfield',
|
||||
|
||||
'ckeditor',
|
||||
'ckeditor_uploader',
|
||||
|
||||
'webpush',
|
||||
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'allauth.socialaccount.providers.google',
|
||||
|
||||
'GeneralApp',
|
||||
'AuthApp',
|
||||
'RoutesApp',
|
||||
'ReferenceDataApp',
|
||||
'ArticlesApp',
|
||||
'SubscribesApp',
|
||||
'PushMessages',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@@ -69,8 +126,14 @@ MIDDLEWARE = [
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'AuthApp.middleware.ResponseInterceptionMiddleware',
|
||||
'TWB.tz_middelware.TimezoneMiddleware',
|
||||
# 'tz_detect.middleware.TimezoneMiddleware',
|
||||
|
||||
"allauth.account.middleware.AccountMiddleware",
|
||||
]
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
ROOT_URLCONF = 'TWB.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
@@ -109,11 +172,8 @@ CHANNEL_LAYERS = {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
POSTGRES_DB = config('POSTGRES_DB')
|
||||
POSTGRES_USER = config('POSTGRES_USER')
|
||||
|
||||
|
||||
# Database
|
||||
@@ -122,8 +182,8 @@ CHANNEL_LAYERS = {
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': 'twbDB',
|
||||
'USER': 'test_user',
|
||||
'NAME': POSTGRES_DB,
|
||||
'USER': POSTGRES_USER,
|
||||
'PASSWORD': 'test_db_pass',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '5432',
|
||||
@@ -209,6 +269,7 @@ django.conf.locale.LANG_INFO = LANG_INFO
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
DATA_UPLOAD_MAX_MEMORY_SIZE = 4145728
|
||||
|
||||
CKEDITOR_BASEPATH = "/static/ckeditor/ckeditor/"
|
||||
CKEDITOR_UPLOAD_PATH = "uploads/"
|
||||
@@ -216,7 +277,9 @@ CKEDITOR_RESTRICT_BY_DATE = False
|
||||
CKEDITOR_RESTRICT_BY_USER = True
|
||||
CKEDITOR_BROWSE_SHOW_DIRS = True
|
||||
|
||||
CKEDITOR_IMAGE_BACKEND = "pillow"
|
||||
# CKEDITOR_IMAGE_BACKEND = 'ckeditor_uploader.backends.PillowBackend'
|
||||
# CKEDITOR_IMAGE_BACKEND = 'pillow'
|
||||
CKEDITOR_IMAGE_BACKEND = "BaseModels.PIL.pillow_backend.PillowBackend"
|
||||
|
||||
# CKEDITOR_BROWSE_SHOW_DIRS = True
|
||||
|
||||
@@ -231,6 +294,10 @@ CKEDITOR_CONFIGS = {
|
||||
'toolbar': 'Custom',
|
||||
'forcePasteAsPlainText': True,
|
||||
'allowedContent': True,
|
||||
|
||||
'filebrowserImageThumbWidth': 300,
|
||||
'filebrowserImageThumbHeight': 300,
|
||||
'filebrowserUploadUrl': '/ckeditor/upload/',
|
||||
# 'disallowedContent': 'img{width,height};img[width,height]',
|
||||
# 'extraPlugins': 'image2',
|
||||
|
||||
@@ -266,6 +333,16 @@ CKEDITOR_CONFIGS = {
|
||||
}
|
||||
}
|
||||
|
||||
CELERY_BROKER_URL = config('CELERY_BROKER_URL')
|
||||
CELERY_RESULT_BACKEND = config('CELERY_RESULT_BACKEND')
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = 'smtp.gmail.com'
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_PORT = 587
|
||||
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
|
||||
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
|
||||
|
||||
|
||||
# CKEDITOR_OPTIONS = {
|
||||
# 'height': 291,
|
||||
|
||||
40
TWB/tasks.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from SubscribesApp.models import SubscribeForUser
|
||||
from TWB import settings
|
||||
from TWB.celery import app
|
||||
from django.core.mail import send_mail
|
||||
|
||||
|
||||
@app.task
|
||||
def check_auto_subscribe():
|
||||
current_time = datetime.now()
|
||||
subscribes = SubscribeForUser.objects.filter(auto_continue=True)
|
||||
if subscribes:
|
||||
for subscribe in subscribes:
|
||||
if subscribe.paid_period_to_DT and subscribe.paid_period_to_DT <= current_time + timedelta(hours=1):
|
||||
user_email = subscribe.user.email
|
||||
subject = 'Подписка продлена!'
|
||||
message = 'Ваша подписка успешно продлена!'
|
||||
send_mail(subject, message, settings.EMAIL_HOST_USER, [user_email], fail_silently=False)
|
||||
else:
|
||||
print('Нету подписок')
|
||||
|
||||
|
||||
@app.task
|
||||
def subscription_expiration_check():
|
||||
current_time = datetime.now()
|
||||
subscribes = SubscribeForUser.objects.filter(paid_period_to_DT__gte=current_time)
|
||||
if subscribes:
|
||||
for subscribe in subscribes:
|
||||
expiration_date = subscribe.paid_period_to_DT
|
||||
remaining_days = (expiration_date - current_time).days
|
||||
|
||||
if remaining_days <= 7:
|
||||
message = f'Ваша подписка заканчивается через {remaining_days} дня. Пожалуйста, продлите её.'
|
||||
|
||||
user_email = subscribe.user.email
|
||||
subject = 'Подписка истекает!'
|
||||
send_mail(subject, message, settings.EMAIL_HOST_USER, [user_email], fail_silently=False)
|
||||
else:
|
||||
print('Нету подписок')
|
||||
22
TWB/tz_middelware.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import zoneinfo
|
||||
from django.utils import timezone
|
||||
from django.shortcuts import render
|
||||
|
||||
class TimezoneMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
tz = request.COOKIES.get("user_tz")
|
||||
if tz:
|
||||
if request.user.is_authenticated:
|
||||
if not 'user_timezone' in request.user.user_profile.json_data or request.user.user_profile.json_data['user_timezone'] != tz:
|
||||
request.user.user_profile.json_data['user_timezone'] = tz
|
||||
request.user.user_profile.save(update_fields=['json_data'])
|
||||
|
||||
msg = f'user={str(request.user.id)} tz={str(tz)}'
|
||||
print(msg)
|
||||
timezone.activate(zoneinfo.ZoneInfo(tz))
|
||||
else:
|
||||
timezone.activate(zoneinfo.ZoneInfo("UTC"))
|
||||
return self.get_response(request)
|
||||
44
TWB/urls.py
@@ -1,15 +1,45 @@
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf.urls.static import static
|
||||
from django.conf import settings
|
||||
from GeneralApp.views import Page404
|
||||
from AuthApp.views import login_View
|
||||
|
||||
handler404 = Page404
|
||||
|
||||
urlpatterns = [
|
||||
# path('admin/', admin.site.urls),
|
||||
path('ckeditor/', include('ckeditor_uploader.urls')),
|
||||
path('i18n/', include('django.conf.urls.i18n')),
|
||||
|
||||
# path('tz_detect/', include('tz_detect.urls')),
|
||||
|
||||
path('accounts/signup/', login_View, name='signup'),
|
||||
path('accounts/login/cancelled/', login_View),
|
||||
|
||||
path('accounts/', include('allauth.urls')),
|
||||
path('accounts/', include('allauth.socialaccount.urls')),
|
||||
|
||||
path('messages/', include('ChatServiceApp.urls')),
|
||||
|
||||
path('user_account/', include('AuthApp.js_urls')),
|
||||
|
||||
|
||||
|
||||
path('routes/', include('RoutesApp.js_urls')),
|
||||
|
||||
path('subscribes/', include('SubscribesApp.js_urls')),
|
||||
|
||||
path('messages/', include('ChatServiceApp.js_urls')),
|
||||
|
||||
path('reference_data/', include('ReferenceDataApp.js_urls')),
|
||||
|
||||
path('', include('ArticlesApp.js_urls')),
|
||||
|
||||
path('test_404', Page404, name='page_404'),
|
||||
|
||||
path('', include('PushMessages.urls')),
|
||||
path('', include('SubscribesApp.urls')),
|
||||
|
||||
]
|
||||
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
@@ -19,19 +49,11 @@ urlpatterns += i18n_patterns(
|
||||
path('', include('GeneralApp.urls')),
|
||||
path('profile/', include('AuthApp.urls')),
|
||||
|
||||
path('user_account/', include('AuthApp.js_urls')),
|
||||
|
||||
path('routes/', include('RoutesApp.js_urls')),
|
||||
path('routes/', include('RoutesApp.urls')),
|
||||
|
||||
path('subscribes/', include('SubscribesApp.js_urls')),
|
||||
|
||||
path('messages/', include('ChatServiceApp.js_urls')),
|
||||
|
||||
path('reference_data/', include('ReferenceDataApp.js_urls')),
|
||||
|
||||
path('', include('ArticlesApp.urls_translate')),
|
||||
path('', include('ArticlesApp.js_urls')),
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
8
env.sample
Normal file
@@ -0,0 +1,8 @@
|
||||
EMAIL_HOST_USER=
|
||||
EMAIL_HOST_PASSWORD=
|
||||
|
||||
POSTGRES_DB=
|
||||
POSTGRES_USER=
|
||||
|
||||
CELERY_BROKER_URL=
|
||||
CELERY_RESULT_BACKEND=
|
||||
@@ -580,9 +580,9 @@ msgstr ""
|
||||
#: .\templates\blocks\static_pages_blocks\b_partners.html:18
|
||||
msgid ""
|
||||
"Вы можете разместить объявление о перевозке посылки и перевозчики со всего "
|
||||
"мира откликнуться на ваше объявление или воспользовавшись поиском на сайте "
|
||||
"мира откликнутся на ваше объявление или воспользовавшись поиском на сайте "
|
||||
"найти перевозчика, который будет готов взять Вашу посылку и доставить в "
|
||||
"указанное место авто- или авива транспортом."
|
||||
"указанное место наземным или авиатранспортом."
|
||||
msgstr ""
|
||||
|
||||
#: .\templates\blocks\static_pages_blocks\b_about_service.html:22
|
||||
|
||||
@@ -2,7 +2,6 @@ Django==4.2.2
|
||||
django-ckeditor==6.5.1
|
||||
psycopg2-binary==2.9.6
|
||||
requests
|
||||
Pillow
|
||||
django-modeltranslation==0.18.10
|
||||
overpass
|
||||
geopy
|
||||
@@ -10,4 +9,9 @@ channels==4.0.0
|
||||
daphne==4.0.0
|
||||
channels-redis==4.1.0
|
||||
django-colorfield
|
||||
|
||||
django-webpush==0.3.5
|
||||
django-allauth==0.60.0
|
||||
pytz==2024.1
|
||||
celery==5.3.6
|
||||
djangorestframework==3.14.0
|
||||
python-decouple==3.8
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
from BaseModels.admin_utils import Admin_GenericBaseIconStackedInline, Admin_BaseIconModel
|
||||
from BaseModels.admin_utils import Admin_GenericBaseIconStackedInline, Admin_BaseIconModel, GenericStackedInline
|
||||
from copy import deepcopy
|
||||
|
||||
class AdminStacked_FAQitem(Admin_GenericBaseIconStackedInline):
|
||||
from GeneralApp.models import FAQitem
|
||||
model = FAQitem
|
||||
extra = 0
|
||||
fields = ['order', 'question', 'answer']
|
||||
|
||||
|
||||
class Admin_BaseModel(Admin_BaseIconModel):
|
||||
pass
|
||||
@@ -68,6 +64,47 @@ class Admin_BaseBlock(Admin_BaseIconModel):
|
||||
fieldsets = super(Admin_BaseBlock, self).get_fieldsets(request, obj)
|
||||
return fieldsets
|
||||
|
||||
|
||||
from modeltranslation.admin import TranslationAdmin
|
||||
|
||||
class AdminTranslationBase(TranslationAdmin):
|
||||
|
||||
# def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
# field = super(AdminTranslation_BaseIconModel, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
# self.patch_translation_field(db_field, field, **kwargs)
|
||||
# return field
|
||||
|
||||
class Media:
|
||||
|
||||
js = (
|
||||
'modeltranslation/js/force_jquery.js',
|
||||
'http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
|
||||
'http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js',
|
||||
'modeltranslation/js/tabbed_translation_fields.js',
|
||||
)
|
||||
css = {
|
||||
'screen': ('modeltranslation/css/tabbed_translation_fields.css',),
|
||||
}
|
||||
|
||||
from modeltranslation.admin import TranslationGenericStackedInline
|
||||
class AdminStacked_FAQitem(TranslationGenericStackedInline):
|
||||
from GeneralApp.models import FAQitem
|
||||
model = FAQitem
|
||||
extra = 0
|
||||
fields = ['order', 'question', 'answer']
|
||||
|
||||
class Media:
|
||||
|
||||
js = (
|
||||
'modeltranslation/js/force_jquery.js',
|
||||
'http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
|
||||
'http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js',
|
||||
'modeltranslation/js/tabbed_translation_fields.js',
|
||||
)
|
||||
css = {
|
||||
'screen': ('modeltranslation/css/tabbed_translation_fields.css',),
|
||||
}
|
||||
|
||||
class Admin_BaseModelViewPage(Admin_BaseIconModel):
|
||||
pass
|
||||
# def get_fieldsets(self, request, obj=None):
|
||||
@@ -92,31 +129,14 @@ class Admin_BaseModelViewPage(Admin_BaseIconModel):
|
||||
# else:
|
||||
# return {}
|
||||
#
|
||||
# inlines = [AdminStacked_FAQitem]
|
||||
inlines = [AdminStacked_FAQitem]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
from modeltranslation.admin import TranslationAdmin
|
||||
class AdminTranslationBase(TranslationAdmin):
|
||||
|
||||
# def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
# field = super(AdminTranslation_BaseIconModel, self).formfield_for_dbfield(db_field, **kwargs)
|
||||
# self.patch_translation_field(db_field, field, **kwargs)
|
||||
# return field
|
||||
|
||||
class Media:
|
||||
|
||||
js = (
|
||||
'modeltranslation/js/force_jquery.js',
|
||||
'http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
|
||||
'http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js',
|
||||
'modeltranslation/js/tabbed_translation_fields.js',
|
||||
)
|
||||
css = {
|
||||
'screen': ('modeltranslation/css/tabbed_translation_fields.css',),
|
||||
}
|
||||
|
||||
|
||||
class Admin_Trans_BaseModel(Admin_BaseModel, AdminTranslationBase):
|
||||
@@ -126,4 +146,5 @@ class Admin_Trans_BaseModel(Admin_BaseModel, AdminTranslationBase):
|
||||
# pass
|
||||
|
||||
class Admin_Trans_BaseModelViewPage(Admin_BaseModelViewPage, AdminTranslationBase):
|
||||
pass
|
||||
pass
|
||||
|
||||
|
||||
410
static/css/datarangepicker.css
Normal file
@@ -0,0 +1,410 @@
|
||||
.daterangepicker {
|
||||
position: absolute;
|
||||
color: inherit;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
width: 278px;
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
margin-top: 7px;
|
||||
top: 100px;
|
||||
left: 20px;
|
||||
z-index: 3001;
|
||||
display: none;
|
||||
font-family: arial;
|
||||
font-size: 15px;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.daterangepicker:before, .daterangepicker:after {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
content: '';
|
||||
}
|
||||
|
||||
.daterangepicker:before {
|
||||
top: -7px;
|
||||
border-right: 7px solid transparent;
|
||||
border-left: 7px solid transparent;
|
||||
border-bottom: 7px solid #ccc;
|
||||
}
|
||||
|
||||
.daterangepicker:after {
|
||||
top: -6px;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #fff;
|
||||
border-left: 6px solid transparent;
|
||||
}
|
||||
|
||||
.daterangepicker.opensleft:before {
|
||||
right: 9px;
|
||||
}
|
||||
|
||||
.daterangepicker.opensleft:after {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.daterangepicker.openscenter:before {
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.daterangepicker.openscenter:after {
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.daterangepicker.opensright:before {
|
||||
left: 9px;
|
||||
}
|
||||
|
||||
.daterangepicker.opensright:after {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.daterangepicker.drop-up {
|
||||
margin-top: -7px;
|
||||
}
|
||||
|
||||
.daterangepicker.drop-up:before {
|
||||
top: initial;
|
||||
bottom: -7px;
|
||||
border-bottom: initial;
|
||||
border-top: 7px solid #ccc;
|
||||
}
|
||||
|
||||
.daterangepicker.drop-up:after {
|
||||
top: initial;
|
||||
bottom: -6px;
|
||||
border-bottom: initial;
|
||||
border-top: 6px solid #fff;
|
||||
}
|
||||
|
||||
.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.daterangepicker.single .drp-selected {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.daterangepicker.show-calendar .drp-calendar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.daterangepicker.show-calendar .drp-buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.daterangepicker.auto-apply .drp-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar {
|
||||
display: none;
|
||||
max-width: 270px;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar.left {
|
||||
padding: 8px 0 8px 8px;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar.right {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar.single .calendar-table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span {
|
||||
color: #fff;
|
||||
border: solid black;
|
||||
border-width: 0 2px 2px 0;
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.daterangepicker .calendar-table .next span {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.daterangepicker .calendar-table .prev span {
|
||||
transform: rotate(135deg);
|
||||
-webkit-transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.daterangepicker .calendar-table th, .daterangepicker .calendar-table td {
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
min-width: 32px;
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.daterangepicker .calendar-table {
|
||||
border: 1px solid #fff;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.daterangepicker .calendar-table table {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.daterangepicker td.available:hover, .daterangepicker th.available:hover {
|
||||
background-color: #eee;
|
||||
border-color: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.daterangepicker td.week, .daterangepicker th.week {
|
||||
font-size: 80%;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date {
|
||||
background-color: #fff;
|
||||
border-color: transparent;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.daterangepicker td.in-range {
|
||||
background-color: #ebf4f8;
|
||||
border-color: transparent;
|
||||
color: #000;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.daterangepicker td.start-date {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.daterangepicker td.end-date {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.daterangepicker td.start-date.end-date {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.daterangepicker td.active, .daterangepicker td.active:hover {
|
||||
background-color: #357ebd;
|
||||
border-color: transparent;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.daterangepicker th.month {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.daterangepicker td.disabled, .daterangepicker option.disabled {
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.daterangepicker select.monthselect, .daterangepicker select.yearselect {
|
||||
font-size: 12px;
|
||||
padding: 1px;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.daterangepicker select.monthselect {
|
||||
margin-right: 2%;
|
||||
width: 56%;
|
||||
}
|
||||
|
||||
.daterangepicker select.yearselect {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect {
|
||||
width: 50px;
|
||||
margin: 0 auto;
|
||||
background: #eee;
|
||||
border: 1px solid #eee;
|
||||
padding: 2px;
|
||||
outline: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.daterangepicker .calendar-time {
|
||||
text-align: center;
|
||||
margin: 4px auto 0 auto;
|
||||
line-height: 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.daterangepicker .calendar-time select.disabled {
|
||||
color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-buttons {
|
||||
clear: both;
|
||||
text-align: right;
|
||||
padding: 8px;
|
||||
border-top: 1px solid #ddd;
|
||||
display: none;
|
||||
line-height: 12px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-selected {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-buttons .btn {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.daterangepicker.show-ranges.single.rtl .drp-calendar.left {
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.daterangepicker.show-ranges.single.ltr .drp-calendar.left {
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.daterangepicker.show-ranges.rtl .drp-calendar.right {
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.daterangepicker.show-ranges.ltr .drp-calendar.left {
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.daterangepicker .ranges {
|
||||
float: none;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.daterangepicker.show-calendar .ranges {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.daterangepicker .ranges ul {
|
||||
list-style: none;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.daterangepicker .ranges li {
|
||||
font-size: 12px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.daterangepicker .ranges li:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.daterangepicker .ranges li.active {
|
||||
background-color: #08c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Larger Screen Styling */
|
||||
@media (min-width: 564px) {
|
||||
.daterangepicker {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.daterangepicker .ranges ul {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.daterangepicker.single .ranges ul {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.daterangepicker.single .drp-calendar.left {
|
||||
clear: none;
|
||||
}
|
||||
|
||||
.daterangepicker.single .ranges, .daterangepicker.single .drp-calendar {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.daterangepicker {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar.left {
|
||||
clear: left;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar.left .calendar-table {
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar.right {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar.right .calendar-table {
|
||||
border-left: none;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar.left .calendar-table {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.daterangepicker .ranges, .daterangepicker .drp-calendar {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 730px) {
|
||||
.daterangepicker .ranges {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.daterangepicker .ranges {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.daterangepicker.rtl .ranges {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.daterangepicker .drp-calendar.left {
|
||||
clear: none !important;
|
||||
}
|
||||
}
|
||||
2232
static/css/mobile_styles.css
Normal file
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 32 KiB |
BIN
static/img/png/banner_test.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
static/img/png/banner_test_3.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
static/img/png/en.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 929 B |
5
static/img/svg/LogoMobile.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="70" height="20" viewBox="0 0 70 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.08592 4.00795V18.001C7.08592 18.5531 7.29259 19.0195 7.70594 19.4003C8.0996 19.8001 8.58184 20 9.15265 20C9.72346 20 10.2155 19.8001 10.6289 19.4003C11.0225 19.0195 11.2194 18.5531 11.2194 18.001V4.00795H16.2386C16.8094 4.00795 17.3015 3.81756 17.7148 3.4368C18.1085 3.037 18.3053 2.56105 18.3053 2.00894C18.3053 1.45683 18.1085 0.980879 17.7148 0.581078C17.3015 0.200314 16.8094 0.00993247 16.2386 0.00993247H2.06673C1.49592 0.00993247 1.00384 0.200314 0.590493 0.581078C0.196831 0.980879 0 1.45683 0 2.00894C0 2.56105 0.196831 3.037 0.590493 3.4368C1.00384 3.81756 1.49592 4.00795 2.06673 4.00795H7.08592Z" fill="#272424"/>
|
||||
<path d="M47.4358 0.209834C46.924 -0.0376623 46.3926 -0.0662197 45.8414 0.124162C45.31 0.295505 44.9163 0.628673 44.6604 1.12367L38.2536 13.5175L36.1278 9.43382L39.4936 2.89421C39.7495 2.39922 39.7889 1.89471 39.6117 1.38068C39.4346 0.847613 39.0901 0.45733 38.5784 0.209834C38.0666 -0.0376623 37.5352 -0.0662197 36.984 0.124162C36.4526 0.295505 36.0589 0.628673 35.803 1.12367L33.8249 4.95034L31.8172 1.12367C31.5613 0.628673 31.1677 0.295505 30.6362 0.124162C30.0851 -0.0662197 29.5537 -0.0376623 29.0419 0.209834C28.5301 0.45733 28.1857 0.847613 28.0085 1.38068C27.8314 1.89471 27.8708 2.39922 28.1266 2.89421L31.4924 9.43382L29.3962 13.5175L22.9598 1.12367C22.7039 0.628673 22.3103 0.295505 21.7788 0.124162C21.2277 -0.0662197 20.6963 -0.0376623 20.1845 0.209834C19.6727 0.45733 19.3283 0.847613 19.1511 1.38068C18.974 1.89471 19.0134 2.39922 19.2692 2.89421L27.5361 18.8863C27.9101 19.6288 28.5301 20 29.3962 20C30.2426 20 30.8527 19.6288 31.2267 18.8863L33.8249 13.8888L36.3935 18.8863C36.7675 19.6288 37.3875 20 38.2536 20C39.1 20 39.7101 19.6288 40.0841 18.8863L48.351 2.89421C48.6069 2.39922 48.6463 1.89471 48.4691 1.38068C48.292 0.847613 47.9475 0.45733 47.4358 0.209834Z" fill="#272424"/>
|
||||
<path d="M55.2377 16.002V12.004H63.7998C64.3706 12.004 64.8529 12.2039 65.2465 12.6037C65.6599 12.9844 65.8665 13.4509 65.8665 14.003C65.8665 14.5551 65.6599 15.0215 65.2465 15.4023C64.8529 15.8021 64.3706 16.002 63.7998 16.002H55.2377ZM55.2377 4.00795H62.6188C63.1896 4.00795 63.6719 4.20785 64.0655 4.60765C64.4789 4.98841 64.6856 5.45485 64.6856 6.00695C64.6856 6.55906 64.4789 7.02549 64.0655 7.40626C63.6719 7.80606 63.1896 8.00596 62.6188 8.00596H55.2377V4.00795ZM53.1709 0.00993247C52.6001 0.00993247 52.1179 0.200314 51.7242 0.581078C51.3109 0.980879 51.1042 1.45683 51.1042 2.00894V18.001C51.1042 18.5531 51.3109 19.0195 51.7242 19.4003C52.1179 19.8001 52.6001 20 53.1709 20H63.7998C65.5122 20 66.9688 19.4098 68.1695 18.2295C69.3898 17.0681 70 15.6593 70 14.003C70 12.1372 69.2422 10.6047 67.7266 9.40526C68.4549 8.3772 68.819 7.24443 68.819 6.00695C68.819 4.35063 68.2088 2.94181 66.9885 1.78048C65.7878 0.600116 64.3313 0.00993247 62.6188 0.00993247H53.1709Z" fill="#272424"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
3
static/img/svg/arrow_f_curtain.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="7" height="13" viewBox="0 0 7 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 1L6 6.5L1 12" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 191 B |
1
static/img/svg/burger.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg>
|
||||
|
After Width: | Height: | Size: 527 B |
1
static/img/svg/filter.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 416c0 17.7 14.3 32 32 32l54.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48L480 448c17.7 0 32-14.3 32-32s-14.3-32-32-32l-246.7 0c-12.3-28.3-40.5-48-73.3-48s-61 19.7-73.3 48L32 384c-17.7 0-32 14.3-32 32zm128 0a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zM320 256a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm32-80c-32.8 0-61 19.7-73.3 48L32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l246.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48l54.7 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-54.7 0c-12.3-28.3-40.5-48-73.3-48zM192 128a32 32 0 1 1 0-64 32 32 0 1 1 0 64zm73.3-64C253 35.7 224.8 16 192 16s-61 19.7-73.3 48L32 64C14.3 64 0 78.3 0 96s14.3 32 32 32l86.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48L480 128c17.7 0 32-14.3 32-32s-14.3-32-32-32L265.3 64z" stroke="#ffffff"/></svg>
|
||||
|
After Width: | Height: | Size: 992 B |
7
static/img/svg/gb.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icon-css-gb" viewBox="0 0 512 512">
|
||||
<path fill="#012169" d="M0 0h512v512H0z"/>
|
||||
<path fill="#FFF" d="M512 0v64L322 256l190 187v69h-67L254 324 68 512H0v-68l186-187L0 74V0h62l192 188L440 0z"/>
|
||||
<path fill="#C8102E" d="M184 324l11 34L42 512H0v-3l184-185zm124-12l54 8 150 147v45L308 312zM512 0L320 196l-4-44L466 0h46zM0 1l193 189-59-8L0 49V1z"/>
|
||||
<path fill="#FFF" d="M176 0v512h160V0H176zM0 176v160h512V176H0z"/>
|
||||
<path fill="#C8102E" d="M0 208v96h512v-96H0zM208 0v512h96V0h-96z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 541 B |