280 Commits

Author SHA1 Message Date
b905fab6c5 1.1.50 add pagination for b_my_routes.html && upd conditional rendering for carrier card based on departure date 2024-07-29 19:32:13 +03:00
d37b694d5a 1.1.47 add new gtag 2024-07-29 12:41:07 +03:00
345969fd61 1.1.46 upd poup url 2024-07-25 17:41:02 +03:00
632ff3c812 1.1.44 setTimeout for popup display 2024-07-25 17:19:45 +03:00
bb60f9d914 1.1.43 add popup display functionality with cookie-based visibility control 2024-07-25 16:55:35 +03:00
111a7eaff7 1.1.43 add popup display functionality with cookie-based visibility control 2024-07-25 16:55:13 +03:00
10814e9e5f Merge remote-tracking branch 'origin/dev' into dev 2024-07-25 01:03:20 +03:00
617f90e1ac 1.0.26 translate 2024-07-25 01:03:05 +03:00
81af5990ae 1.1.42 Fix date formatting issue in template 2024-07-23 14:28:41 +03:00
92e9b1ce63 1.1.41 show/hide unanswered_msgs_count 2024-07-23 13:41:41 +03:00
87b21c5f55 1.1.40 show/hide unanswered_msgs_count 2024-07-18 12:53:04 +03:00
f61482ded6 1.1.39 show/hide unanswered_msgs_count 2024-07-18 12:05:54 +03:00
c999d994d6 1.1.38 show/hide unanswered_msgs_count 2024-07-18 11:42:29 +03:00
f609361ee9 1.1.37 show/hide unanswered_msgs_count 2024-07-17 15:24:42 +03:00
79059796c8 Merge remote-tracking branch 'origin/dev' into dev 2024-07-17 13:10:27 +03:00
61facdb5fb 1.1.35 refactor date comparison logic in Django template 2024-07-17 13:10:15 +03:00
SDE
b007c3ad23 1.6.6 parsing timezones 2024-07-16 15:53:12 +03:00
SDE
0b5e37281b 1.6.5 parsing timezones 2024-07-16 15:34:35 +03:00
SDE
6e5e82401b 1.6.4 parsing timezones 2024-07-16 15:19:40 +03:00
d419131eab 1.1.34 upd chat_counter 2024-07-16 15:10:43 +03:00
f23ad1a33a 1.0.25 password text in registration mail 2024-07-15 23:40:42 +03:00
SDE
1486879a58 1.6.3 parsing timezones 2024-07-13 01:29:36 +03:00
SDE
3d6fb38937 1.6.2 parsing timezones 2024-07-12 21:07:07 +03:00
SDE
d981a83652 1.6.1 parsing timezones 2024-07-12 19:01:58 +03:00
SDE
3bfe64d57c Merge remote-tracking branch 'origin/dev' into dev 2024-07-12 18:54:24 +03:00
SDE
1ad58b05ef 1.6.0 parsing timezones 2024-07-12 18:54:14 +03:00
0ac8e906a2 1.0.24 robots 2024-07-10 17:56:29 +03:00
807edecbf4 1.0.23 add tenge currency 2024-07-10 16:34:37 +03:00
6254b1dcda 1.0.23 add tenge currency 2024-07-10 16:21:35 +03:00
SDE
9df1bf2743 1.5.2 change currency pay 2024-07-10 16:20:48 +03:00
a678fb467d 1.0.22 robots.txt modify 2024-07-09 21:25:20 +03:00
SDE
c24b7107a7 Merge remote-tracking branch 'origin/main' 2024-07-09 16:59:23 +03:00
SDE
c0596871da 1.5.1 rosetta translate 2024-07-09 16:59:13 +03:00
f0ad2d654d 0.0.22 translate 2024-07-09 16:54:35 +03:00
SDE
c2ef76b69b 1.5.0 rosetta translate 2024-07-09 16:51:05 +03:00
SDE
96f58732ba 1.4.8 bank res log w full info 2024-07-09 16:19:55 +03:00
SDE
15340d254c 1.4.7 bank res log w full info 2024-07-09 16:17:00 +03:00
SDE
890986c5de 1.4.6 bank res log w full info 2024-07-09 16:12:33 +03:00
SDE
3d137aefad 1.4.5 move pay_system sets to settings 2024-07-09 16:09:01 +03:00
SDE
6c911dde43 1.4.4 move pay_system sets to settings 2024-07-09 16:07:16 +03:00
SDE
37981602dd 1.4.3 move pay_system sets to settings 2024-07-09 16:03:16 +03:00
SDE
d64d858f15 Merge remote-tracking branch 'origin/main' 2024-07-09 13:40:42 +03:00
SDE
f9906ea8fc 1.4.2 move pay_system url to settings 2024-07-09 13:40:32 +03:00
c660831146 1.1.31 upd chats markers 2024-07-04 14:42:27 +03:00
a8ba3f9a3a 1.1.30 upd chats markers 2024-07-04 13:49:42 +03:00
8439fc8736 1.1.29 upd tik tok url 2024-07-04 09:44:25 +03:00
04f7fbdbb2 1.1.29 upd msg counter 2024-07-03 21:54:40 +03:00
e2b205240e 1.1.28 upd msg counter and news 2024-07-03 18:24:35 +03:00
2695738ae3 1.1.27 hide .advertisement_block_news 2024-07-03 17:22:17 +03:00
5a3336a657 Merge remote-tracking branch 'origin/main' 2024-07-03 17:18:56 +03:00
b67b290279 1.1.26 upd msg counter and styles for news 2024-07-03 17:18:40 +03:00
f34664cbb3 0.0.19 add articles indexing 2024-07-02 20:48:03 +03:00
4919560500 0.0.19 add articles indexing 2024-07-02 19:07:47 +03:00
c2b6890311 1.1.24 upd msg counter 2024-07-02 15:30:34 +03:00
38c4f3426d 1.1.23 add h1 for all pages 2024-07-01 17:53:54 +03:00
2400c334a9 1.1.23 upd title and description 2024-07-01 16:52:08 +03:00
SDE
114675703b 1.4.1 sitemap 2024-06-29 16:46:37 +03:00
SDE
0be38ac466 1.4.0 sitemap 2024-06-29 16:34:36 +03:00
SDE
0e184f0b87 1.3.6 add unanswered_msgs_count to all responses 2024-06-29 14:44:19 +03:00
SDE
75258f1706 1.3.5 send_mail_for_user_subscribes_that_is_going_to_finish 2024-06-28 18:24:23 +03:00
SDE
ce591345d3 1.3.4 send_mail_for_user_subscribes_that_is_going_to_finish 2024-06-28 18:12:01 +03:00
SDE
0770009443 1.3.3 send_mail_for_user_subscribes_that_is_going_to_finish 2024-06-28 18:10:35 +03:00
SDE
bf39639acc 1.3.2 send_mail_for_user_subscribes_that_is_going_to_finish 2024-06-28 18:02:57 +03:00
SDE
947f46c5ff 1.3.1 receive_finish_subscribe_msg 2024-06-28 15:27:29 +03:00
651423ce90 1.1.22 fix function for checking the status of the switch finish_subscribe_msg 2024-06-28 15:24:58 +03:00
afb0ea0bed 1.1.21 add function for checking the status of the switch finish_subscribe_msg 2024-06-28 15:17:39 +03:00
SDE
e2b7b0d356 Merge remote-tracking branch 'origin/main' 2024-06-27 19:15:02 +03:00
SDE
c55d5c6676 1.3.0 receive_finish_subscribe_msg, find errors by tickets 2024-06-27 19:14:52 +03:00
eb60bcec56 1.1.20 upd css for .menu_profile right 2024-06-26 13:02:52 +03:00
62325eb9fa 1.1.19 upd css for block-chat 2024-06-26 12:32:53 +03:00
18aae945ae 1.1.18 fix height for container_inf_about_moving 2024-06-25 18:33:54 +03:00
4afe61546a 1.1.17 add YandexMetrika tag for create_routes, registration button in header/footer v2 2024-06-25 13:10:32 +03:00
b6a0fcdf07 1.1.17 add YandexMetrika tag for create_routes, registration button in header/footer v2 2024-06-21 15:41:19 +03:00
2fc153843f 1.1.16 add YandexMetrika tag for create_routes, registration button in header/footer 2024-06-21 14:54:30 +03:00
729f03ca47 1.1.15 add YandexMetrika tag for feedback form v4 2024-06-21 13:48:37 +03:00
96c0111428 1.1.14 add YandexMetrika tag for feedback form v3 2024-06-21 13:17:35 +03:00
d0ccbb494f 1.1.13 add YandexMetrika tag for feedback form v2 2024-06-20 17:10:20 +03:00
f8aa0162a5 1.1.12 add YandexMetrika tag for feedback form 2024-06-20 16:56:21 +03:00
2b94d3f851 1.1.12 add YandexMetrika tag for registration page 2024-06-20 16:00:23 +03:00
fddda1ef0a 1.1.11 fix height for cards in search 2024-06-20 14:28:42 +03:00
035634df67 0.0.18 add footer social networks 2024-06-18 11:57:13 +03:00
8267d99b4a 0.0.18 add footer social networks 2024-06-18 11:56:14 +03:00
854faf56f5 0.0.18 add footer social networks 2024-06-18 11:52:15 +03:00
4bbb3428c0 0.0.18 add footer social networks 2024-06-18 11:46:31 +03:00
7a40ca0986 0.0.18 add footer social networks 2024-06-18 11:44:49 +03:00
c93a15025b 0.0.18 add footer social networks 2024-06-18 11:43:38 +03:00
57a1c82db2 0.0.18 add social network icons 2024-06-18 11:39:40 +03:00
b6af2c7997 0.0.18 add social network icons 2024-06-18 11:37:35 +03:00
df7b3e1a8b 0.0.17 social network link added 2024-06-15 18:07:41 +03:00
b7a6163251 0.0.17 social network link added 2024-06-15 18:06:15 +03:00
9c39f9e07c 0.0.17 social network link added 2024-06-15 17:57:04 +03:00
d33eff4935 0.0.17 social network link added 2024-06-15 17:55:34 +03:00
11ea66f576 0.0.17 social network link added 2024-06-15 17:53:45 +03:00
d31f019afb 0.0.17 social network instagram link added 2024-06-15 17:42:55 +03:00
52361a6a16 0.0.17 social network instagram link added 2024-06-15 17:37:58 +03:00
e04ec83877 0.0.17 social network instagram link added 2024-06-15 17:35:46 +03:00
SDE
301f301868 1.2.7 reset rising_DT in past routes 2024-06-15 17:18:07 +03:00
SDE
b2cb5463ef Merge remote-tracking branch 'origin/main' 2024-06-15 17:15:05 +03:00
SDE
d2754db97a 1.2.6 reset rirsing_DT in past routes 2024-06-15 17:14:57 +03:00
568906b2df 0.0.16 twb-tripwb replace 2024-06-14 09:32:27 +03:00
fbae553657 1.1.10 fix webpush button footer 2024-06-12 23:33:24 +03:00
03ecd45316 1.1.9 fix webpush button footer 2024-06-12 22:45:36 +03:00
b8aba1d951 1.1.9 fix width for container-carrier 2024-06-12 15:38:28 +03:00
SDE
4d76569131 Merge remote-tracking branch 'origin/main' 2024-06-11 16:48:50 +03:00
SDE
f90d87a301 1.2.5 fix get_routes_Dict rising 2024-06-11 16:48:39 +03:00
fe6375b6f9 1.1.8 add registration message 2024-06-11 14:54:15 +03:00
SDE
a30156cc1c 1.2.5 add get_permission_for_highlight 2024-06-11 01:09:40 +03:00
SDE
f8a8aa7577 Merge remote-tracking branch 'origin/main' 2024-06-11 01:03:34 +03:00
SDE
b527d18a3c 1.2.4 add get_permission_for_highlight 2024-06-11 01:03:25 +03:00
fc6a864f7c 0.0.15 robots.txt add allow/disallow options 2024-06-10 23:37:54 +03:00
8eaff8ebbc 0.0.15 robots.txt add allow/disallow options 2024-06-10 23:35:37 +03:00
SDE
3a18688143 Merge remote-tracking branch 'origin/main' 2024-06-10 18:47:07 +03:00
SDE
0e40bae701 1.2.3 get_my_routes_ajax add remains_routes_count operations 2024-06-10 18:46:50 +03:00
7aa7e32fda 1.1.8 upd raise and highlight function in my_routes 2024-06-10 17:48:10 +03:00
SDE
1aced3d20b 1.2.2 get_my_routes_ajax add remains_routes_count operations 2024-06-10 16:58:59 +03:00
SDE
9566082283 Merge remote-tracking branch 'origin/main' 2024-06-10 16:56:43 +03:00
SDE
152c92bf65 1.2.1 get_my_routes_ajax add remains_routes_count operations 2024-06-10 16:56:19 +03:00
4622424b9b Merge remote-tracking branch 'origin/main' 2024-06-10 12:40:31 +03:00
ef8337582f 1.1.6 upd raise and highlight function in my_routes 2024-06-10 12:40:20 +03:00
SDE
133fa7ea5f Merge remote-tracking branch 'origin/main' 2024-06-09 16:37:20 +03:00
SDE
74e76fe6e7 1.2.0 confirm email after registration 2024-06-09 16:37:08 +03:00
2996e84433 1.1.5 fix show_contact if user is not authenticated 2024-06-06 14:25:25 +03:00
f8d1de5d30 1.1.4 update rising and highlight my_routes 2024-06-05 17:47:01 +03:00
d33278faff 1.1.3 update meta_names.html 2024-06-05 16:55:09 +03:00
22e40409d7 1.1.3 rising and highlight my_route counter 2024-06-05 16:48:12 +03:00
632cf0b9b4 1.1.3 change meta_names.html 2024-06-05 16:47:10 +03:00
a9ed3e0bef 0.0.14 add yandex and google verification meta 2024-06-05 15:06:46 +03:00
28b505310d 0.0.14 add yandex and google verification meta 2024-06-05 15:00:58 +03:00
c9aea0b778 1.1.3 rising and highlight my_route 2024-06-05 14:26:36 +03:00
e1694cdc9c 1.1.3 update dropdown-content lang 2024-06-05 14:22:19 +03:00
SDE
3a235b4f60 1.1.13 not send to pay system if subscribe price is null 2024-06-05 12:03:00 +03:00
SDE
90405f64de 1.1.12 funcs for raise and highlight routes 2024-06-04 22:28:43 +03:00
35073f77c8 1.1.3 rising and highlight my_route 2024-06-04 20:02:54 +03:00
5cd0146c8c 1.1.2 upd description for user_profile 2024-06-04 11:09:28 +03:00
SDE
ffabffb2ef 1.1.11 funcs for raise and highlight routes 2024-06-04 00:19:10 +03:00
bb1e3e9691 Merge remote-tracking branch 'origin/main' 2024-06-03 21:35:30 +03:00
d89730ffbf 1.1.1 highlighting and raising routes 2024-06-03 21:35:15 +03:00
431ab2f9b0 0.0.14 add yandex and google verification meta 2024-06-03 15:55:12 +03:00
c444055bd7 0.0.14 add yandex and google verification meta 2024-06-03 15:38:40 +03:00
a0dd229c12 0.0.14 add yandex and google verification meta 2024-06-03 15:25:11 +03:00
074e141a22 0.0.13 fix metrika 2024-06-03 15:04:39 +03:00
SDE
40951fdfcb 1.1.10 funcs for raise and highlight routes 2024-06-03 02:57:36 +03:00
SDE
e319a8af46 1.1.9 funcs for raise and highlight routes 2024-06-03 02:46:58 +03:00
SDE
a83ff0e080 1.1.8 change settings for ws 2024-06-03 02:01:26 +03:00
SDE
22ae4b1563 1.1.7 rising and select for routes by subscribe 2024-06-03 01:47:44 +03:00
SDE
23fe1c3647 1.1.6 rising and select for routes by subscribe 2024-06-03 01:45:04 +03:00
SDE
079a7d3925 1.1.5 rising and select for routes by subscribe 2024-06-02 17:10:56 +03:00
SDE
69373925cd 1.1.4 rising and select for routes by subscribe 2024-06-02 17:07:53 +03:00
SDE
dbfc39639b 1.1.3 autoextension and autofinish subscribes 2024-05-31 15:03:15 +03:00
11bbfd0e73 Merge remote-tracking branch 'origin/main' 2024-05-31 13:08:46 +03:00
0075b474d7 1.0.11 upd subscribe_current 2024-05-31 13:08:36 +03:00
SDE
99090a8a95 Merge remote-tracking branch 'origin/main' 2024-05-31 13:07:13 +03:00
SDE
0e5ed13794 1.1.2 fix dublicates subscribe_for_user 2024-05-31 13:07:00 +03:00
ffa6ba2c47 1.0.10 add description for news create_route_for_customer, create_route_for_mover 2024-05-31 12:43:46 +03:00
4ae8f09430 1.0.9 add title for news create_route_for_customer, create_route_for_mover 2024-05-31 11:40:16 +03:00
SDE
17024d7350 1.1.1 autosubscribe to null price subscribe 2024-05-29 12:25:27 +03:00
SDE
efec0754cd 1.1.0 autosubscribe to null price subscribe 2024-05-29 07:53:32 +03:00
5a893faa43 1.0.8 fix view bugs in my_routes, my_subscribe, main 2024-05-20 13:27:51 +03:00
5981f982f3 0.0.12 replace link to login page on carrier widget 2024-05-17 12:23:55 +03:00
fd81675ee9 1.0.7 upd view for error_message for subscribe 2024-05-10 18:48:51 +03:00
SDE
3575391d3f 1.0.12 subscribe buy routines 2024-05-10 15:34:56 +03:00
SDE
563a4fde3c 1.0.11 subscribe buy routines 2024-05-10 13:55:49 +03:00
SDE
148d182e5e 1.0.10 subscribe buy routines 2024-05-10 13:52:47 +03:00
SDE
aab8cf69fb 1.0.9 subscribe buy routines 2024-05-10 13:44:30 +03:00
3900cb1382 1.0.7 upd view for error_message for subscribe 2024-05-08 18:08:56 +03:00
d77fe01795 1.0.6 view for error_message for subscribe 2024-05-08 17:04:37 +03:00
47eb056f87 Merge remote-tracking branch 'origin/main' 2024-05-08 14:24:19 +03:00
14691d0ee4 1.0.5 add error_message for subscribe 2024-05-08 14:24:06 +03:00
SDE
d6487d81c9 1.0.7 push routines 2024-05-08 11:33:24 +03:00
SDE
2f421bb0ac 1.0.6 push routines 2024-05-08 11:23:01 +03:00
SDE
4b588eb6e4 1.0.5 subscribe page w dynamically load 2024-05-08 00:44:36 +03:00
SDE
3a911d928f 1.0.4 subscribe page w dynamically load 2024-05-07 18:51:17 +03:00
69c037b30d 1.0.4 upd select_tab_profile 2024-05-07 18:18:27 +03:00
SDE
c84e884a7c 1.0.3 subscribe page w dynamically load 2024-05-07 18:07:43 +03:00
0a38314003 1.0.3 upd select_tab_profile 2024-05-07 17:59:11 +03:00
2d86f36a91 1.0.2 upd select_tab_profile 2024-05-07 17:52:16 +03:00
a0eeb7b642 1.0.1 upd select_tab_profile 2024-05-07 17:40:07 +03:00
SDE
9eb5936690 1.0.2 subscribe page w dynamically load 2024-05-07 16:23:40 +03:00
SDE
cbe5271426 1.0.1 subscribe page w dynamically load 2024-05-07 15:28:47 +03:00
SDE
34827af5be 1.0.0 subscribe page w dynamically load 2024-05-07 15:23:45 +03:00
6ed5652f16 1.0.0 upd view for my_routes 2024-05-07 15:21:40 +03:00
SDE
8ed567496b 0.12.52 subscribe page w dynamically load 2024-05-07 15:01:13 +03:00
SDE
b374e7aeca 0.12.51 subscribe page w dynamically load 2024-05-07 14:21:52 +03:00
SDE
a0ee72bc81 0.12.50 subscribe page w dynamically load 2024-05-07 13:51:20 +03:00
SDE
e8e4bf466b 0.12.49 fix push messages 2024-05-07 11:43:58 +03:00
SDE
ec01d11426 0.12.48 fix push messages 2024-05-07 11:39:36 +03:00
SBD
c088d25033 14 2024-05-05 14:57:28 +03:00
SBD
e9f3cdbc59 14 2024-05-05 14:05:09 +03:00
SBD
72f754e89c 14 2024-05-05 13:45:45 +03:00
SDE
c365f3274a Merge remote-tracking branch 'origin/main' 2024-05-05 13:37:56 +03:00
SDE
f8caafb2a9 0.12.47 fix push messages 2024-05-05 13:37:39 +03:00
2d147d5f09 0.0.11 insert yandex and google counter 2024-04-27 11:26:50 +03:00
7c4b554aea 0.0.11 insert yandex and google counter 2024-04-27 11:23:09 +03:00
0091919b93 0.0.11 insert yandex and google counter 2024-04-27 11:21:17 +03:00
d46e5cc238 0.0.10 style bold for text in tarif plans 2024-04-25 17:22:14 +03:00
97116a2070 Merge remote-tracking branch 'origin/main' 2024-04-25 11:36:46 +03:00
27aaff4a4b 0.1.379 update carrier_card info in search 2024-04-25 11:36:36 +03:00
32c2fd4e6c 0.0.10 style bold for text in tarif plans 2024-04-25 11:09:50 +03:00
51b86323d8 0.0.10 style bold for text in tarif plans 2024-04-25 11:04:53 +03:00
f0bd3ce088 0.0.10 style bold for text in tarif plans 2024-04-25 10:57:24 +03:00
SDE
474bb9f02e Merge remote-tracking branch 'origin/main' 2024-04-24 14:11:40 +03:00
SDE
a2175f9110 0.12.46 fix validation dates for route in localization 2024-04-24 14:11:25 +03:00
aec0218787 0.1.378 update carrier_card info in search 2024-04-24 12:25:35 +03:00
567ee01619 0.1.377 hide img in carrier_card info in search 2024-04-23 20:57:35 +03:00
SDE
e4a929cfd6 0.12.45 favicon 2024-04-23 13:29:21 +03:00
SDE
27b0840704 0.12.44 favicon 2024-04-23 13:17:12 +03:00
SDE
525da42ae9 0.12.43 favicon 2024-04-23 12:55:14 +03:00
SDE
8d1199bc4f 0.12.42 favicon 2024-04-23 12:49:49 +03:00
SDE
dac8f602d8 0.12.41 favicon 2024-04-23 12:42:00 +03:00
SDE
26d04dfaa0 0.12.40 favicon 2024-04-23 12:36:49 +03:00
SDE
22306371a0 0.12.39 favicon 2024-04-23 12:33:15 +03:00
SDE
881f0cdbfa 0.12.39 favicon 2024-04-23 12:30:28 +03:00
SDE
314d342f5c Merge remote-tracking branch 'origin/main' 2024-04-23 10:18:00 +03:00
SDE
d627b6a780 0.12.38 fix static pages w subscribes 2024-04-23 10:17:49 +03:00
9528572f37 0.1.376 update carrier_card info in search 2024-04-22 13:59:55 +03:00
273caa5c5d 0.1.375 update carrier_card info in search 2024-04-22 12:40:00 +03:00
c9f04057a7 0.1.374 update carrier_card info in search 2024-04-22 11:10:10 +03:00
SBD
8599030ccb 14 2024-04-20 12:30:55 +03:00
SDE
3fe5970811 0.12.37 pays and subscribes 2024-04-20 12:26:12 +03:00
SDE
723229d595 0.12.36 pays and subscribes 2024-04-20 12:24:37 +03:00
SDE
bb319780b6 0.12.36 pays and subscribes 2024-04-20 12:15:55 +03:00
5c06aceb27 0.1.373 fix carrier_card in my_routes 2024-04-19 12:44:07 +03:00
27b4dd072e 0.1.372 fix maegin for curtain and for inf_carrier 2024-04-19 12:27:22 +03:00
SDE
2c1f70018e 0.12.36 fix webpush 2024-04-16 17:07:32 +03:00
SDE
698f4578fc 0.12.35 fix webpush 2024-04-15 17:19:12 +03:00
SDE
fd70e388a7 0.12.34 fix redirect to subscribe after login 2024-04-13 11:23:44 +03:00
SDE
07b06a4177 0.12.33 fix redirect to subscribe after login 2024-04-12 18:40:06 +03:00
SBD
10e8d477e5 14 2024-04-12 17:29:35 +03:00
7daadc816c 0.1.371 hide name_carrier if user not registered 2024-04-12 16:21:11 +03:00
89db74c782 0.1.370 upd compare with current_date 2024-04-12 15:52:10 +03:00
01f6136798 0.1.369 compare with current_date 2024-04-12 15:43:09 +03:00
SDE
1bb4aa04a6 0.12.33 m_found_matched_routes change 2024-04-12 14:39:45 +03:00
6b63f8b87e 0.0.9 style search field fix 2024-04-11 09:49:56 +03:00
cd6fde962b 0.0.8 promotion block bottom_block_static commented 2024-04-09 09:49:27 +03:00
SDE
607a0f8245 0.12.32 fix switch langs 2024-04-08 18:08:05 +03:00
SBD
ad1db581a7 14 2024-04-08 17:32:14 +03:00
SBD
ca5b0039a1 14 2024-04-08 17:24:19 +03:00
214ee3bd58 0.0.7 text fix 2024-04-08 10:53:27 +03:00
b1f2cc2376 0.0.7 text fix 2024-04-08 10:48:45 +03:00
f1f523209b 0.0.7 text fix 2024-04-08 10:41:43 +03:00
9c23fd02fe 0.0.7 text fix 2024-04-08 10:39:31 +03:00
eee30b50db 0.0.7 text fix 2024-04-08 10:35:41 +03:00
6ba35ccd96 0.0.7 text fix 2024-04-08 10:22:59 +03:00
5916d5d038 0.0.7 text fix 2024-04-08 10:04:40 +03:00
3eb0b499c6 0.0.6 header menu item translate 2024-04-04 23:56:52 +03:00
SDE
c2d021a516 0.12.31 fix switch langs 2024-04-04 23:16:26 +03:00
SDE
46fdda269f 0.12.30 fix login_required 2024-04-04 22:45:20 +03:00
SDE
536a1e967e 0.12.29 fix trans 2024-04-04 15:21:36 +03:00
SDE
ac4fcb634d Merge remote-tracking branch 'origin/main' 2024-04-04 11:26:57 +03:00
SDE
d92fe4c22c 0.12.28 fix trans 2024-04-04 11:26:50 +03:00
3d49100656 0.0.5 fix footer logo 2024-04-04 09:50:51 +03:00
9841524ec0 0.0.5 fix footer logo 2024-04-04 09:44:24 +03:00
0fe8cb1bfb 0.0.5 fix unsubscribe text decoration 2024-04-03 13:17:55 +03:00
910b211840 0.1.368 centered the logo in the footer 2024-04-03 11:33:47 +03:00
4f7d6d05bd Merge remote-tracking branch 'origin/main' 2024-04-03 11:29:15 +03:00
b8feec88a7 0.1.367 add text in subscribe block 2024-04-03 11:29:04 +03:00
6043e1f5a7 0.0.4 fix text for contact page 2024-04-03 10:38:14 +03:00
SDE
4981bdcaa6 Merge remote-tracking branch 'origin/main' 2024-04-02 16:06:56 +03:00
SDE
c29cc25581 0.12.28 localization routines 2024-04-02 16:06:45 +03:00
0ce03ca73f Merge remote-tracking branch 'origin/main' 2024-04-02 14:14:45 +03:00
80a226a302 0.1.366 add text in subscribe block 2024-04-02 14:14:29 +03:00
0d41bd40e5 0.0.4 fix text for about page 2024-04-02 13:48:11 +03:00
d2fe96a599 0.0.4 center footer logo 2024-04-02 13:06:42 +03:00
d0d41a4d2b 0.0.3 replace footer logo max-width desktop 2024-04-02 10:44:46 +03:00
5e7a153d6d 0.0.3 replace footer logo max-width desktop 2024-04-02 10:40:04 +03:00
55d414c1d0 0.0.3 replace footer logo max-width desktop 2024-04-02 10:37:45 +03:00
ac6a440151 0.0.3 replace footer logo max-width desktop 2024-04-02 10:32:58 +03:00
ff48ca83e2 0.0.2 replace footer logo max-width 2024-04-02 10:27:09 +03:00
358577745f 0.0.2 replace Logo in footer 2024-04-02 10:14:45 +03:00
087a913a48 0.0.2 replace word in contact form about service 2024-04-02 09:42:13 +03:00
a9dedb7869 0.0.1 replace twb to tripwithbonus on main page 2024-04-02 09:31:58 +03:00
SDE
92e3d76b7e 0.12.27 fix auth 2024-03-27 12:59:38 +03:00
2da7195dd0 0.1.365 add img 2024-03-15 15:38:42 +03:00
18f7fedbc2 0.1.365 add img 2024-03-15 15:37:10 +03:00
aa93813ba5 0.1.364 add new logo fix old css 2024-03-15 15:35:58 +03:00
def3d770ed 0.1.363 add new logo 2024-03-08 16:07:51 +03:00
6b8bcb8ebb 0.1.363 fix misspell 2024-03-07 19:47:50 +03:00
SBD
ff5afe518a 14 2024-03-06 18:22:48 +03:00
SBD
6f42251f5f Merge remote-tracking branch 'origin/main' 2024-03-06 18:11:58 +03:00
SBD
06917078ae 14 2024-03-06 18:11:46 +03:00
137 changed files with 9180 additions and 1285 deletions

View File

@@ -21,6 +21,9 @@ def get_articles_block_ajax(request):
if request.method != 'POST':
raise Http404
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
try:
data = request.POST.dict()
@@ -42,6 +45,9 @@ def get_articles_block_ajax(request):
# 'form': RouteForm(initial=data)
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:

View File

@@ -6,7 +6,7 @@ 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 GeneralApp.funcs import get_inter_http_response
from django.utils.translation import gettext_lazy as _
# from django.contrib.auth.decorators import login_required
@@ -85,14 +85,14 @@ def ArticlesPageView(request, year=None):
Dict = get_articles(art_kwargs=kwargs)
Dict.update({
'page': {
'title': _('Страница списка новостей'),
'description': _('Все новости сайта tripwb.com'),
'title': _('Новости сервиса доставки посылок | TripWB'),
'description': _('Обновления, полезные статьи и свежие новости от TripWB ✓ Актуальные новости в мире доставок по всему СНГ ➡️ Следите за нашими новостями'),
'keywords': _('Все новости сайта tripwb.com'),
}
})
t = loader.get_template('pages/p_articles.html')
return get_inter_http_respose(t, Dict, request)
return get_inter_http_response(t, Dict, request)
# return HttpResponse(t.render(Dict, request))
@@ -125,7 +125,7 @@ def UserPageView(request, page_url):
t = loader.get_template('pages/p_user_page.html')
return get_inter_http_respose(t, Dict, request)
return get_inter_http_response(t, Dict, request)
# return HttpResponse(t.render(Dict, request))
@@ -155,5 +155,5 @@ def ArticlesOnePageView(request, art_url):
t = loader.get_template('pages/p_article.html')
return get_inter_http_respose(t, Dict, request)
return get_inter_http_response(t, Dict, request)
# return HttpResponse(t.render(Dict, request))

View File

@@ -1,4 +1,5 @@
from django.template.loader import render_to_string
from SubscribesApp.funcs import check_option_in_cur_user_subscribe
def get_user_timezone_Dict(user, request=None):
@@ -30,10 +31,12 @@ def get_profile_page_content_html(request, page_name, data):
if page_name == 'chat':
from ChatServiceApp.funcs import get_chat_page_content_html
return get_chat_page_content_html(request, data)
elif page_name == 'create_route_for_customer':
elif (page_name == 'create_route_for_customer' and
check_option_in_cur_user_subscribe(request.user, 'размещение заявок')):
from RoutesApp.funcs import get_profile_new_route_page_html
return get_profile_new_route_page_html(request, {'owner_type': 'customer'})
elif page_name == 'create_route_for_mover':
elif (page_name == 'create_route_for_mover' and
check_option_in_cur_user_subscribe(request.user, 'размещение заявок')):
from RoutesApp.funcs import get_profile_new_route_page_html
return get_profile_new_route_page_html(request, {'owner_type': 'mover'})
elif page_name == 'my_routes':
@@ -42,8 +45,9 @@ def get_profile_page_content_html(request, page_name, data):
elif page_name == 'support':
return get_profile_support_page_content_html(request, data)
elif page_name == 'my_subscribe':
from SubscribesApp.funcs import get_profile_subscribe_page_content_html
return get_profile_subscribe_page_content_html(request)
from SubscribesApp.funcs import get_profile_subscribe_page_content_Dict
res = get_profile_subscribe_page_content_Dict(request, check_orders_required=True)
return res['html']
elif page_name == 'change_profile':
return get_profile_change_page_content_html(request)
elif page_name == 'dashboard':

View File

@@ -18,9 +18,9 @@ from django.core.files import File
import base64
from django.core.validators import validate_email
from django.urls import reverse
from GeneralApp.funcs import get_and_set_lang
# @login_required(login_url='/profile/login/')
# @login_required()#login_url='/profile/login/')
# def subscribe_ajax(request):
# if request.method != 'POST':
# raise Http404
@@ -36,6 +36,8 @@ def mailing_subscribe_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
try:
email = request.POST['email']
@@ -45,11 +47,13 @@ def mailing_subscribe_ajax(request):
user.user_profile.mailing_on = True
user.user_profile.save(update_fields=['mailing_on'])
return JsonResponse({
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict = {
'status': 'sended',
'del_form': True,
'html': _('Подписка на рассылку для адреса ') + user.email + _(' одобрена')
})
}
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
if not user:
try:
@@ -62,11 +66,14 @@ def mailing_subscribe_ajax(request):
else:
redirect_url = f"{reverse('registration_page')}?mailingSubscribeRequired=true"
return JsonResponse({
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict = {
'status': 'sended',
'redirect_url': redirect_url,
'email': email
})
}
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
@@ -80,6 +87,8 @@ def send_message_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
try:
data = request.POST
@@ -196,10 +205,15 @@ def send_message_ajax(request):
html = render_to_string('widgets/w_msg_send_success.html', Dict, request)
return JsonResponse({
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict = {
'status': 'sended',
'html': html
})
}
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
return JsonResponse({
'status': 'error',
@@ -207,11 +221,13 @@ def send_message_ajax(request):
}, status=400)
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def chats_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
from ChatServiceApp.funcs import get_chat_receivers_for_user, get_msgs_for_chat_w_users
receivers, unread_msgs_count = get_chat_receivers_for_user(request.user)
@@ -235,19 +251,28 @@ def chats_ajax(request):
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)
@login_required(login_url='/profile/login/')
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict = {'html': html}
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
@login_required()#login_url='/profile/login/')
def support_tickets_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
html = get_profile_support_page_content_html(request)
return JsonResponse({'html': html}, status=200)
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict = {'html': html}
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
@login_required(login_url='/profile/login/')
@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
@@ -255,6 +280,8 @@ def change_avatar_confirm_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
try:
file_data = json.loads(request.body)
@@ -273,14 +300,19 @@ def change_avatar_confirm_ajax(request):
print(msg)
return JsonResponse({'error': msg}, status=400)
return JsonResponse({'url': request.user.user_profile.avatar.url})
res_Dict = {'url': request.user.user_profile.avatar.url}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def change_profile_confirm_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
data = request.POST
if not data:
data = json.loads(request.body)
@@ -346,14 +378,19 @@ def change_profile_confirm_ajax(request):
user_profiles.update(**data_for_save)
html = get_profile_change_page_content_html(request)
return JsonResponse({'html': html}, status=200)
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict = {'html': html}
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def dashboard_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
try:
from .funcs import get_dashboard_page_content_html
@@ -367,26 +404,32 @@ def dashboard_ajax(request):
return JsonResponse({'html': html}, status=200)
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def change_profile_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
html = get_profile_change_page_content_html(request)
return JsonResponse({'html': html}, status=200)
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def my_routes_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
Dict = {
}
html = render_to_string('blocks/profile/b_my_routes.html', Dict, request=request)
return JsonResponse({'html': html}, status=200)
res_Dict = {'html': html}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
@@ -396,6 +439,8 @@ def login_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
try:
data = request.POST
@@ -424,11 +469,16 @@ def login_ajax(request):
html = render_to_string('forms/f_login.html', Dict, request=request)
return JsonResponse({'html': html}, status=400)
if not 'HTTP_REFERER' in request.META or not '/?next=/' in request.META['HTTP_REFERER']:
redirect_url = reverse('profile_page', args=['dashboard'])
else:
redirect_url = request.META['HTTP_REFERER'].split('/?next=')[1]
res_Dict = {
'redirect_url': reverse('profile_page', args=['dashboard'])
'redirect_url': redirect_url
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
@@ -444,24 +494,24 @@ def login_ajax(request):
def send_registration_mail(data_Dict, user):
def send_check_email_after_registration(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!')
subject = _('Trip With Bonus - Подтверждение регистрации ')
Dict = {
'logo': f'{sets["domain"]}/static/img/svg/LogoMobile.svg',
'project_name': sets['project_name'],
'domain': sets['domain'],
'message_title': subject,
}
Dict.update(data_Dict)
html = render_to_string('mail/m_registration.html', Dict)
html = render_to_string('mail/m_confirm_email.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']
@@ -479,10 +529,16 @@ def send_registration_mail(data_Dict, user):
def registration_ajax(request):
if request.method != 'POST':
raise Http404
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
try:
data = request.POST
@@ -503,28 +559,33 @@ def registration_ajax(request):
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, backend='django.contrib.auth.backends.ModelBackend')
# if 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']
user.is_active = False
user.save()
user.user_profile.phone = form.data['tel']
user.user_profile.authMailCode = uuid1().hex
user.user_profile.save()
mail_Dict = {
'user': user,
'pass': form.data['password']
}
res = send_registration_mail(mail_Dict, user)
res = send_check_email_after_registration(mail_Dict, user)
# res = send_registration_mail(mail_Dict, user)
res_Dict = {
'redirect_url': reverse('profile_page', args=['dashboard'])
# 'redirect_url': reverse('profile_page', args=['dashboard'])
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:

View File

@@ -16,7 +16,6 @@ from datetime import datetime
def user_name_str(self):
return f'{self.last_name} {self.first_name}'
User.add_to_class("__str__", user_name_str)

View File

@@ -46,9 +46,9 @@ urlpatterns = [
#
# # -----------------------
#
# url(r'^check_user_registration_and_activate/(?P<user_id>[\d+]*)/(?P<authCode>[0-9a-z\+\-\_]+)$',
# check_user_registration_and_activate,
# name='check_user_registration_and_activate'),
path('check_user_registration_and_activate/<int:user_id>/<str:authMailCode>/',
check_user_registration_and_activate,
name='check_user_registration_and_activate'),
#
# # url(r'^user/password/reset/$',
# # 'django.contrib.auth.views.password_reset',

View File

@@ -5,7 +5,8 @@ from django.shortcuts import render
from uuid import uuid1
from AuthApp.models import *
from django.contrib import auth
from django.http import HttpResponse, Http404
from django.urls import reverse
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.template import loader, RequestContext
from django.contrib.auth.decorators import login_required
from BaseModels.mailSender import techSendMail
@@ -13,7 +14,60 @@ 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
from GeneralApp.funcs import get_inter_http_response
from GeneralApp.funcs import get_and_set_lang
def send_registration_mail(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,
'user': user
}
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 check_user_registration_and_activate(request, user_id, authMailCode):
try:
user = User.objects.get(
id=user_id,
is_active=False,
user_profile__authMailCode=authMailCode
)
user.is_active = True
user.save(update_fields=['is_active'])
res = send_registration_mail(user)
return HttpResponseRedirect(reverse('login_profile'))
except User.DoesNotExist:
user = None
raise Http404
def registration_View(request):
@@ -25,7 +79,7 @@ def registration_View(request):
# if request.p
t = loader.get_template('pages/profile/p_registration.html')
return get_inter_http_respose(t, Dict, request)
return get_inter_http_response(t, Dict, request)
# return HttpResponse(t.render(Dict, request))
@@ -41,10 +95,17 @@ def registration_View(request):
# return HttpResponse(t.render(Dict, request))
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def profile_page_View(request, page_name, id=None):
lang = get_and_set_lang(request)
page_html = get_profile_page_content_html(request, page_name, id)
if not page_html:
raise Http404
Dict = {
'page_html': get_profile_page_content_html(request, page_name, id),
'page_html': page_html,
'page_name': page_name,
'page_type': 'profile'
}
@@ -54,15 +115,15 @@ def profile_page_View(request, page_name, id=None):
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,
}
})
# title = _('Личный кабинет пользователя') + ' ' + 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({
@@ -70,11 +131,11 @@ def profile_page_View(request, page_name, id=None):
# })
t = loader.get_template('pages/profile/p_user_profile.html')
return get_inter_http_respose(t, Dict, request)
return get_inter_http_response(t, Dict, request)
# return HttpResponse(t.render(Dict, request))
# @login_required(login_url='/profile/login/')
# @login_required()#login_url='/profile/login/')
# def chat_w_user_View(request, user_id=None):
# from ChatServiceApp.funcs import get_chat_page_content_Dict
#
@@ -103,7 +164,7 @@ def profile_page_View(request, page_name, id=None):
# t = loader.get_template('pages/profile/p_user_profile.html')
# return HttpResponse(t.render(Dict, request))
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def user_profile_View(request):
Dict = {}
@@ -112,7 +173,7 @@ def user_profile_View(request):
# request.COOKIES['user_id'] = request.user.id
t = loader.get_template('pages/profile/p_user_profile.html')
response = get_inter_http_respose(t, Dict, request)
response = get_inter_http_response(t, Dict, request)
# response = HttpResponse(t.render(Dict, request))
response.set_cookie('user_id', request.user.id)
return response
@@ -137,7 +198,7 @@ def login_View(request):
request.session['mailingSubscribeRequired'] = 'true'
t = loader.get_template('pages/profile/p_login.html')
return get_inter_http_respose(t, Dict, request)
return get_inter_http_response(t, Dict, request)
# return HttpResponse(t.render(Dict, request))

View File

@@ -18,7 +18,7 @@ from django.contrib.contenttypes.fields import GenericRelation
# add_introspection_rules([], ["^tinymce\.models\.HTMLField"])
class BaseModel(models.Model):
name = models.TextField(verbose_name=_('Название'),
name = models.TextField(verbose_name=_("Название"),
help_text=_('Название'), null=True, blank=True)
name_plural = models.TextField(verbose_name=_('Название (множественное число)'),
null=True, blank=True)

View File

@@ -0,0 +1,113 @@
import json
from django.conf import settings
import requests
from requests_pkcs12 import get,post
# для песочницы
# pkcs12_filename = 'dvldigitalprojects.p12'
# pkcs12_password = 'QNlhRStcY7mB'
# api_pass = 'aPqSRVZhxFjjSqbB'
# для прода
# pkcs12_filename = 'dvldigitalprojects.p12'
# pkcs12_password = 'fzSBm6WISje7'
# api_pass = 't9g2+bZSvxNxCu+t'
def get_domain_url():
return settings.PAY_SYSTEM_URL #'https://sandboxapi.paymtech.kz/'
def get_kwargs_for_request():
return {
'headers': {
'content-type': 'application/json',
},
'auth': ('dvldigitalprojects', settings.API_PASS),
'pkcs12_filename': settings.PKCS12_FILENAME,
'pkcs12_password': settings.PKCS12_PASS
}
def ping():
url = f'{get_domain_url()}ping'
data = {}
try:
msg = f'GET {url}'
print(msg)
res = get(
url,
**get_kwargs_for_request()
)
msg = f'answer received = {str(res)}'
print(msg)
except Exception as e:
msg = f'Exception GET {url} = {str(e)} ({str(res)})'
print(msg)
res = None
return False
return True
def get_order_status(bank_order_id):
url = f'{get_domain_url()}orders/{str(bank_order_id)}'
res = None
data = {
'expand': [
'card', 'client', 'location', 'custom_fields',
'issuer', 'secure3d', 'operations', 'cashflow'
]
}
try:
msg = f'GET {url}'
print(msg)
res = get(
url,
data=json.dumps(data),
**get_kwargs_for_request()
)
msg = f'get_order_status answer received = {str(res)}'
print(msg)
except Exception as e:
msg = f'Exception get_order_status GET {url} = {str(e)} ({str(res)})'
print(msg)
res = None
return res
def create_order(data):
url = f'{get_domain_url()}orders/create'
res = None
try:
msg = f'POST {url}'
print(msg)
res = post(
url,
data=json.dumps(data),
**get_kwargs_for_request()
)
msg = f'create_order answer received = {str(res.text)}'
# if res:# and res.status_code > 300:
# msg += f' > ({str(res.text)})'
print(msg)
except Exception as e:
msg = f'Exception create_order POST {url} = {str(e)} ({str(res)})'
if res:
msg += f' > ({str(res.text)})'
print(msg)
res = None
return res

0
BillingApp/__init__.py Normal file
View File

39
BillingApp/admin.py Normal file
View File

@@ -0,0 +1,39 @@
from sets.admin import *
from .models import *
from django.contrib import admin
class Admin_SubscribeOrder(Admin_BaseModel):
fieldsets = (
(None, {
'classes': ['wide'],
'fields': (
('user', 'subscribe', 'subscribe_for_user'),
('enable', 'order'),
('sum', 'currency'),
('status', 'last_operation_status'),
('bank_order_id', 'pay_page'),
'json_data',
)
}),
)
list_display = [
'id', 'enable',
'user', 'subscribe', 'subscribe_for_user',
'bank_order_id',
'sum', 'currency',
'status', 'last_operation_status',
'order', 'modifiedDT', 'createDT'
]
list_display_links = ['id', 'user', 'subscribe']
list_editable = ['enable']
readonly_fields = ['subscribe_for_user', 'sum', 'currency', 'modifiedDT', 'createDT']
list_filter = ['enable', 'status', 'modifiedDT', 'createDT']
search_fields = ['id', 'last_operation_status', 'status']
# filter_horizontal = ['options']
admin.site.register(SubscribeOrder, Admin_SubscribeOrder)

6
BillingApp/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class BillingappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'BillingApp'

101
BillingApp/funcs.py Normal file
View File

@@ -0,0 +1,101 @@
from datetime import datetime, timedelta
from .models import *
import json
def get_order_status(order):
from BaseModels.pay_systems.DVL_Group_kaz.api.funcs import get_order_status
res_status = None
if not order or not order.bank_order_id:
return order
try:
res_data = get_order_status(order.bank_order_id)
res = json.loads(res_data.text)
res = res['orders'][0]
order.json_data['status'] = res
order.status = res['status']
# if res['amount'] == res['amount_charged'] and res['status'] == 'charged':
order.save()
# return order.status
except Exception as e:
msg = f'Exception get_order_status = {str(e)}'
if order:
msg = f'Exception get_order_status (data = {str(order.id)}) = {str(e)}'
print(msg)
return order
def get_orders_for_user(user):
orders = SubscribeOrder.objects.filter(
enable=True,
user=user,
subscribe_for_user=None,
createDT__gt=datetime.now() - timedelta(hours=1)
).order_by('subscribe', '-createDT').distinct('subscribe')
SubscribeOrder.objects.filter(
user=user
).exclude(
id__in=orders.values_list('id', flat=True)
).update(enable=False)
return orders
def create_subscribe_order(data):
order = None
try:
order = SubscribeOrder.objects.create(**data)
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)
from BaseModels.pay_systems.DVL_Group_kaz.api.funcs import create_order
data = {
'currency': data['currency'],
'amount': data['sum'],
'description': f'Заказ {order.id} на подписку '
f'{data["subscribe"].name} '
f'для пользователя {data["user"].username}',
'options': {
'force3d': 1,
'auto_charge': 1,
'return_url': f'{sets["domain"]}/profile/page/my_subscribe/'
}
}
res_data = create_order(data)
order.pay_page = res_data.headers.get('location')
res = json.loads(res_data.text)
res = res['orders'][0]
order.json_data['create_order'] = res
order.modifiedDT = datetime.strptime(res['updated'], '%Y-%m-%d %H:%M:%S')
order.status = res['status']
order.bank_order_id = res['id']
if 'segment' in res:
order.segment = res['segment']
if 'merchant_order_id' in res:
order.merchant_order_id = res['merchant_order_id']
order.save()
except Exception as e:
msg = f'Exception create_subscribe_order (data = {str(data)}) = {str(e)}'
print(msg)
return order

View File

@@ -0,0 +1,42 @@
# Generated by Django 4.2.2 on 2024-04-19 16:24
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('SubscribesApp', '0003_alter_subscribe_bg_color_alter_subscribe_text_color'),
]
operations = [
migrations.CreateModel(
name='SubscribeOrder',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField(blank=True, help_text='Название', null=True, verbose_name='Название')),
('name_plural', models.TextField(blank=True, null=True, verbose_name='Название (множественное число)')),
('order', models.IntegerField(blank=True, null=True, verbose_name='Очередность отображения')),
('createDT', models.DateTimeField(auto_now_add=True, verbose_name='Дата и время создания')),
('modifiedDT', models.DateTimeField(blank=True, null=True, verbose_name='Дата и время последнего изменения')),
('enable', models.BooleanField(db_index=True, default=True, verbose_name='Включено')),
('json_data', models.JSONField(blank=True, default=dict, verbose_name='Дополнительные данные')),
('sum', models.PositiveSmallIntegerField(verbose_name='Сумма')),
('currency', models.CharField(max_length=3, verbose_name='Валюта')),
('segment', models.CharField(verbose_name='ID Сегмента')),
('merchant_order_id', models.CharField(verbose_name='merchant_order_id')),
('bank_order_id', models.CharField(verbose_name='ID заказа в банке')),
('status', models.CharField(verbose_name='Статус заказа в банке')),
('subscribe', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscribe_orders_for_subscribe', to='SubscribesApp.subscribe', verbose_name='Подписка')),
('subscribe_for_user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscribe_orders_for_user_subscribe', to='SubscribesApp.subscribeforuser', verbose_name='Подписка пользователя')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscribe_orders_for_user', to='SubscribesApp.subscribe', verbose_name='Пользователь')),
],
options={
'verbose_name': 'Заказ на подписку',
'verbose_name_plural': 'Заказы на подписки',
},
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 4.2.2 on 2024-04-19 16:29
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('BillingApp', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='subscribeorder',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscribe_orders_for_user', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь'),
),
]

View File

@@ -0,0 +1,43 @@
# Generated by Django 4.2.2 on 2024-04-19 16:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('BillingApp', '0002_alter_subscribeorder_user'),
]
operations = [
migrations.AddField(
model_name='subscribeorder',
name='pay_page',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Ссылка на страницу оплаты'),
),
migrations.AlterField(
model_name='subscribeorder',
name='bank_order_id',
field=models.CharField(default='', verbose_name='ID заказа в банке'),
),
migrations.AlterField(
model_name='subscribeorder',
name='merchant_order_id',
field=models.CharField(default='', verbose_name='merchant_order_id'),
),
migrations.AlterField(
model_name='subscribeorder',
name='segment',
field=models.CharField(default='', verbose_name='ID Сегмента'),
),
migrations.AlterField(
model_name='subscribeorder',
name='status',
field=models.CharField(default='', verbose_name='Статус заказа в банке'),
),
migrations.AlterField(
model_name='subscribeorder',
name='sum',
field=models.PositiveSmallIntegerField(default=0, verbose_name='Сумма'),
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 4.2.2 on 2024-04-19 16:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('BillingApp', '0003_subscribeorder_pay_page_and_more'),
]
operations = [
migrations.AlterField(
model_name='subscribeorder',
name='bank_order_id',
field=models.CharField(default=None, null=True, verbose_name='ID заказа в банке'),
),
migrations.AlterField(
model_name='subscribeorder',
name='currency',
field=models.CharField(default='USD', max_length=3, verbose_name='Валюта'),
),
migrations.AlterField(
model_name='subscribeorder',
name='merchant_order_id',
field=models.CharField(default=None, null=True, verbose_name='merchant_order_id'),
),
migrations.AlterField(
model_name='subscribeorder',
name='segment',
field=models.CharField(default=None, null=True, verbose_name='ID Сегмента'),
),
migrations.AlterField(
model_name='subscribeorder',
name='status',
field=models.CharField(default=None, null=True, verbose_name='Статус заказа в банке'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2024-04-19 17:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('BillingApp', '0004_alter_subscribeorder_bank_order_id_and_more'),
]
operations = [
migrations.AddField(
model_name='subscribeorder',
name='last_operation_status',
field=models.CharField(default=None, null=True, verbose_name='Статус последней операции'),
),
]

View File

75
BillingApp/models.py Normal file
View File

@@ -0,0 +1,75 @@
from django.db import models
from BaseModels.base_models import BaseModel
from SubscribesApp.models import Subscribe, SubscribeForUser
from AuthApp.models import User
from django.utils.translation import gettext as _
class SubscribeOrder(BaseModel):
subscribe = models.ForeignKey(
Subscribe, verbose_name=_('Подписка'),
on_delete=models.SET_NULL, blank=True, null=True,
related_name='subscribe_orders_for_subscribe'
)
user = models.ForeignKey(
User, verbose_name=_('Пользователь'),
on_delete=models.SET_NULL, blank=True, null=True,
related_name='subscribe_orders_for_user'
)
subscribe_for_user = models.OneToOneField(
SubscribeForUser, verbose_name=_('Подписка пользователя'),
on_delete=models.SET_NULL, blank=True, null=True,
related_name='subscribe_orders_for_user_subscribe'
)
sum = models.PositiveSmallIntegerField(verbose_name=_('Сумма'), default=0)
currency = models.CharField(verbose_name=_('Валюта'), max_length=3, default='KZT')
segment = models.CharField(verbose_name=_('ID Сегмента'), null=True, default=None)
merchant_order_id = models.CharField(verbose_name=_('merchant_order_id'), null=True, default=None)
bank_order_id = models.CharField(verbose_name=_('ID заказа в банке'), null=True, default=None)
status = models.CharField(verbose_name=_('Статус заказа в банке'), null=True, default=None)
last_operation_status = models.CharField(verbose_name=_('Статус последней операции'), null=True, default=None)
pay_page = models.URLField(verbose_name=_('Ссылка на страницу оплаты'), null=True, blank=True, default=None)
class Meta:
verbose_name = _('Заказ на подписку')
verbose_name_plural = _('Заказы на подписки')
def activate_subscribe_for_user(self):
from datetime import datetime, timedelta
kwargs = {
'user': self.user,
'subscribe': self.subscribe,
'last_paid_DT': datetime.now(),
'receive_finish_subscribe_msg': True,
# 'enable': True,
}
from SubscribesApp.funcs import create_subscribe_by_data
subscribe_for_user = create_subscribe_by_data(kwargs)
self.subscribe_for_user = subscribe_for_user
self.enable = False
self.save()
subscribe_for_user.activate(
paid_period_from_DT=datetime.now(),
paid_period_to_DT=datetime.now() + timedelta(hours=self.subscribe.period)
)
return self
def __str__(self):
res = 'Заказ'
if self.subscribe:
res += f' на подписку {self.subscribe.name}'
if self.user:
res += f' для {self.user.username}'
if not res:
res += f' {str(self.id)}'
return res

3
BillingApp/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
BillingApp/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -17,6 +17,9 @@ from AuthApp.funcs import get_user_timezone_Dict
def get_unanswered_msgs_count_for_user(user):
if not user or not user.is_authenticated:
return 0
msgs = Message.objects.filter(receiver=user, status='sended', group=None)
return msgs.count()

View File

@@ -18,12 +18,15 @@ from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def get_file_from_msg_ajax(request):
if request.method != 'POST':
raise Http404
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
try:
data = json.loads(request.body)
@@ -35,7 +38,9 @@ def get_file_from_msg_ajax(request):
res_Dict = file
break
return JsonResponse(res_Dict, status=200)
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
msg = f'get_file_from_msg_ajax Error = {str(e)}'
@@ -43,12 +48,15 @@ def get_file_from_msg_ajax(request):
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def show_chat_w_user_ajax(request):
if request.method != 'POST':
raise Http404
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
try:
data = json.loads(request.body)
@@ -57,10 +65,12 @@ def show_chat_w_user_ajax(request):
tpl_name = 'blocks/profile/b_chats.html'
html = render_to_string(tpl_name, Dict, request=request)
return JsonResponse({'html': html}, status=200)
res_Dict = {'html': html}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
msg = f'show_chat_w_user_ajax Error = {str(e)}'
@@ -74,6 +84,9 @@ def update_chat_ajax2(request):
if request.method != 'POST':
raise Http404
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
res_Dict = {}
msgs = []
Dict = {}
@@ -148,7 +161,9 @@ def update_chat_ajax2(request):
res_Dict.update({
'required_beep': required_beep,
})
return JsonResponse(res_Dict, status=200)
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
msg = f'update_chat_ajax2 Error = {str(e)}'
@@ -163,6 +178,9 @@ def update_chat_ajax(request):
if request.method != 'POST':
raise Http404
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
res_Dict = {}
msgs = []
Dict = {}
@@ -256,14 +274,17 @@ def update_chat_ajax(request):
res_Dict.update({
'required_beep': required_beep,
})
return JsonResponse(res_Dict, status=200)
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
msg = f'update_chat_ajax Error = {str(e)}'
return JsonResponse({'error': msg}, status=400)
# @login_required(login_url='/profile/login/')
# @login_required()#login_url='/profile/login/')
# def send_msg_ajax(request):
# from AuthApp.models import User
#
@@ -375,12 +396,15 @@ def update_chat_ajax(request):
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def support_show_chat_by_ticket_ajax(request):
if request.method != 'POST':
raise Http404
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
try:
data = json.loads(request.body)
@@ -420,20 +444,26 @@ def support_show_chat_by_ticket_ajax(request):
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)
res_Dict = {'html': html}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
msg = f'support_show_chat_by_ticket_ajax Error = {str(e)}'
return JsonResponse({'error': msg}, status=400)
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def support_create_ticket_form_ajax(request):
from ChatServiceApp.forms import TicketForm
if request.method != 'POST':
raise Http404
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
Dict = {
'form': TicketForm()
@@ -444,16 +474,23 @@ def support_create_ticket_form_ajax(request):
tpl_name = 'blocks/profile/b_create_ticket.html'
html = render_to_string(tpl_name, Dict, request=request)
return JsonResponse({'html': html}, status=200)
res_Dict = {'html': html}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def create_ticket_ajax(request):
from ChatServiceApp.forms import TicketForm
if request.method != 'POST':
raise Http404
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
try:
data = request.POST
@@ -512,6 +549,8 @@ def create_ticket_ajax(request):
'html': html
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:

View File

@@ -47,7 +47,7 @@ class Admin_StaticPage(Admin_Trans_BaseModelViewPage):
if request.user.is_superuser:
return True
if obj.url in ('main', 'spec_technics', 'works'):
if not obj or obj.url in ('main', 'works'):
return False
admin.site.register(StaticPage,Admin_StaticPage)

View File

@@ -1,6 +1,47 @@
from django.http import HttpResponse, Http404, FileResponse
from django.conf import settings
def get_and_set_lang(request):
from django.utils.translation import activate, get_language
lang = None
cur_url_list = request.path.split('/')
if len(cur_url_list) > 1 and cur_url_list[1] in settings.MODELTRANSLATION_LANGUAGES:
lang = cur_url_list[1]
if not lang:
referer_url = request.META.get('HTTP_REFERER')
if referer_url:
url_list = referer_url.split('//')
if len(url_list) > 1:
url_list = url_list[1].split('/')
if len(url_list) > 1 and url_list[1] in settings.MODELTRANSLATION_LANGUAGES:
lang = url_list[1]
if not lang:
lang = get_language()
if not lang:
lang = 'en'
activate(lang)
return lang
def get_add_to_ajax_response_Dict(user):
context_Dict = {}
from ChatServiceApp.funcs import get_unanswered_msgs_count_for_user
context_Dict.update({
'unanswered_msgs_count': get_unanswered_msgs_count_for_user(user)
})
return context_Dict
def get_inter_Dict(user):
from SubscribesApp.funcs import get_cur_user_subscribe
@@ -21,7 +62,7 @@ def get_inter_Dict(user):
return Dict
def get_inter_http_respose(template_obj, context_Dict, request):
def get_inter_http_response(template_obj, context_Dict, request):
context_Dict.update(get_inter_Dict(request.user))
@@ -42,4 +83,9 @@ def get_inter_http_respose(template_obj, context_Dict, request):
# if text and title and not request.user.is_anonymous:
# send_push(user=request.user, title=title, text=text)
from ChatServiceApp.funcs import get_unanswered_msgs_count_for_user
context_Dict.update({
'unanswered_msgs_count': get_unanswered_msgs_count_for_user(request.user)
})
return HttpResponse(template_obj.render(context_Dict, request))

View File

@@ -13,15 +13,15 @@ def get_options_by_opt_types(opt_types, only_vals=False):
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['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']}"})

View File

View File

@@ -0,0 +1,48 @@
from django.core.management.base import BaseCommand
from datetime import datetime
from BaseModels.mailSender import techSendMail
from ...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 RoutesApp.search_matches import search_matches
msg = search_matches()
if msg:
print(msg)
log += f'\n{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 datetime.now().hour == 19:
try:
from SubscribesApp.reports import send_mail_for_user_subscribes_that_is_going_to_finish
msg = send_mail_for_user_subscribes_that_is_going_to_finish()
if msg:
print(msg)
log += f'\n{msg}'
except Exception as e:
msg = f'send_mail_for_user_subscribes_that_is_going_to_finish search_matches fail = {str(e)}'
print(msg)
techSendMail(mail_sets, msg, title='every_1hour_start send_mail_for_user_subscribes_that_is_going_to_finish')
if log:
techSendMail(mail_sets, str(msg), title='every_1hour_start get_competitors_prices')
print(f'- processing time = {str(datetime.now() - log_begin_DT)} -')

View File

@@ -15,21 +15,16 @@ class Command(BaseCommand):
print(msg)
try:
from ...search_matches import search_matches
msg = search_matches()
if msg:
print(msg)
from SubscribesApp.funcs import finish_user_subscribes, extension_free_subscribes
extension_free_subscribes()
finish_user_subscribes()
except Exception as e:
msg = f'every_1hour_start search_matches fail = {str(e)}'
msg = f'every_day_start 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')
techSendMail(mail_sets, msg, title='every_day_start fail')
# if msg:
# techSendMail(mail_sets, str(msg), title='every_1hour_start get_competitors_prices')
print(f'- processing time = {str(datetime.now() - log_begin_DT)} -')

View File

@@ -78,7 +78,7 @@ def del_amp_symbols(value):
@stringfilter
def del_lang_from_path(value):
path_list = value.split('/')
path = '/' + '/'.join(path_list[2:])
path = '/' + '/'.join(path_list[4:])
# for i in path_list[1:]:
# path.join(i + '/')

View File

@@ -5,7 +5,7 @@ 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 .funcs import get_inter_http_response
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
@@ -13,49 +13,26 @@ from django.contrib.auth.models import User
from django.views.decorators.csrf import csrf_exempt
from webpush import send_user_notification
import json
from datetime import datetime, timedelta
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
# import allauth
# from allauth.socialaccount.models import SocialApp
# apps = SocialApp.objects.all()
# apps.delete()
res = None
from RoutesApp.search_matches import search_matches
search_matches()
from ReferenceDataApp.funcs import parse_data
parse_data()
# 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"})
# from SubscribesApp.reports import send_mail_for_user_subscribes_that_is_going_to_finish
# send_mail_for_user_subscribes_that_is_going_to_finish()
# 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()
if res:
if type(res) == str:
return HttpResponse(res)
else:
return res
return HttpResponse('finished')
@@ -67,7 +44,7 @@ def Page404(request, exeption=None):
t = loader.get_template('404.html')
try:
res = get_inter_http_respose(t, Dict, request)
res = get_inter_http_response(t, Dict, request)
return HttpResponse(res, status=404)
except Exception as e:
return HttpResponse(str(e))
@@ -104,14 +81,14 @@ def MainPage(request):
Dict.update({'breadcrumbs': breadcrumbs_Dict})
t = loader.get_template('pages/p_main.html')
return get_inter_http_respose(t, Dict, request)
return get_inter_http_response(t, Dict, request)
# return HttpResponse(t.render(Dict, request))
def StaticPageView(request, url):
from RoutesApp.forms import RouteForm
from SubscribesApp.funcs import get_subsribes_w_options
from SubscribesApp.funcs import get_subscribes_w_options
Dict = {}
@@ -133,7 +110,7 @@ def StaticPageView(request, url):
raise Http404
if url in ['for_movers', 'for_customers']:
subscribes, all_options = get_subsribes_w_options()
subscribes, all_options = get_subscribes_w_options()
Dict.update({
'subscribes': subscribes,
})
@@ -148,8 +125,11 @@ def StaticPageView(request, url):
'page': page,
})
# from PushMessages.views import send_push
# send_push(user=request.user, title='title', text='text')
t = loader.get_template('pages/p_static_page.html')
return get_inter_http_respose(t, Dict, request)
return get_inter_http_response(t, Dict, request)
# return HttpResponse(t.render(Dict, request))

View File

@@ -7,6 +7,7 @@ import json
from django.shortcuts import render, get_object_or_404
from django.conf import settings
from django.utils.translation import gettext as _
from SubscribesApp.funcs import check_option_in_cur_user_subscribe
def get_key_Dict():
@@ -18,6 +19,9 @@ def get_key_Dict():
return Dict
def send_push(user, title, text, url=None, button_name=None, img=None):
if not check_option_in_cur_user_subscribe(user, 'push уведомления'):
return False
try:
# body = request.body
# data = json.loads(body)

View File

@@ -2,6 +2,7 @@ from django.contrib import admin
from sets.admin import Admin_Trans_BaseModel
from .models import *
from modeltranslation.admin import TranslationAdmin
from django.utils.translation import gettext as _
class Admin_Country(Admin_Trans_BaseModel):
fieldsets = [
@@ -10,11 +11,20 @@ class Admin_Country(Admin_Trans_BaseModel):
'fields': [
'name', 'enable', 'short_code', 'code',
]
}]
}],
[_('Дополнительно'), {
'classes': ['wide', 'collapse'],
'fields': (
'timezone',
'geo_lat', 'geo_lon',
'json_data',
)
}],
]
list_display = [
'id', 'name', 'name_en', 'name_ru',
'timezone',
'short_code', 'code',
'enable', 'area_id', 'parsing_finished_DT',
'order', 'modifiedDT', 'createDT']
@@ -22,18 +32,35 @@ class Admin_Country(Admin_Trans_BaseModel):
admin.site.register(Country, Admin_Country)
class Admin_City(Admin_Trans_BaseModel):
def cur_dt(self, obj):
if obj.timezone:
return obj.get_current_datetime()
else:
return '-'
cur_dt.short_description = 'текущее время'
fieldsets = [
[None, {
'classes': ['wide'],
'fields': [
'name', 'enable', 'country',
]
}]
}],
[_('Дополнительно'), {
'classes': ['wide', 'collapse'],
'fields': (
'timezone',
'geo_lat', 'geo_lon',
'json_data',
)
}],
]
list_display = [
'id', 'name', 'name_en', 'name_ru',
'country',
'timezone', 'cur_dt',
'enable', 'area_id', 'parsing_finished_DT',
'order', 'modifiedDT', 'createDT']
search_fields = ['id', 'name_en', 'name_ru', 'country__name']
@@ -50,12 +77,21 @@ class Admin_Airport(Admin_Trans_BaseModel):
'international_name',
# 'area_id'
]
}]
}],
[_('Дополнительно'), {
'classes': ['wide', 'collapse'],
'fields': (
'timezone',
'geo_lat', 'geo_lon',
'json_data',
)
}],
]
list_display = [
'id', 'name', 'name_en', 'name_ru',
'city', 'iata_code', 'icao_code',
'timezone',
'international_name',
'enable', 'area_id', 'parsing_finished_DT',
'order', 'modifiedDT', 'createDT']

View File

@@ -3,7 +3,9 @@ from .models import *
import hashlib, json
from datetime import datetime, timedelta
from django.db.models import Q
from timezonefinder import TimezoneFinder
tzf = TimezoneFinder()
def search_cities_in_db(search_str):
@@ -57,25 +59,39 @@ def create_airports_by_airportsList(airportsList, city=None):
if airport_Dict['iata']:
kwargs.update({'iata_code': airport_Dict['iata']})
airport = Airport.objects.get(**kwargs)
if airport.geo_lat and airport.geo_lon and not airport.timezone:
airport.timezone = tzf.timezone_at(
lng=float(airport.geo_lon), lat=float(airport.geo_lat))
airport.modifiedDT = datetime.now()
airport.save()
print(f'airport {airport.international_name} - {airport.timezone}')
except Airport.DoesNotExist:
print(f' - - {airport_Dict["iata"]} не найден в БД > добавляем')
except Exception as e:
print(f'error = {str(e)}')
if not airport:
geo_lat = float(airport_Dict['@lat'])
geo_lon = float(airport_Dict['@lon'])
tz = tzf.timezone_at(lng=geo_lon, lat=geo_lat)
print(f'airport {airport_Dict["int_name"]} - {tz}')
airport_kwargs = {
'city': city,
# 'name_ru': airport_Dict['name:ru'],
# 'name_en': airport_Dict['name:en'],
'timezone': tz,
'geo_lat': str(airport_Dict['@lat']),
'geo_lon': str(airport_Dict['@lon']),
'geo_lat': str(geo_lat),
'geo_lon': str(geo_lon),
'international_name': airport_Dict['int_name'],
'iata_code': airport_Dict['iata'],
'icao_code': airport_Dict['icao'],
'modifiedDT': datetime.now(),
}
if airport_Dict['name:ru']:
@@ -119,7 +135,10 @@ def parse_data():
country = Country.objects.get(**kwargs)
if country.parsing_finished_DT and (datetime.now() - country.parsing_finished_DT).days < 30:
if (country.parsing_finished_DT
and (datetime.now() - country.parsing_finished_DT).days < 30
and country.timezone
):
print(f' + {country.name} - существует в БД, не требует парсинга')
continue
@@ -194,6 +213,12 @@ def parse_data():
else:
print(f'error = {str(e)}')
geo_lat = float(city_Dict['@lat'])
geo_lon = float(city_Dict['@lon'])
tz = tzf.timezone_at(lng=geo_lon, lat=geo_lat)
if not city or not city.timezone:
print(f'city {city_Dict["name:en"]} - {tz}')
# собираем данные
city_kwargs = {
'country': country,
@@ -201,8 +226,11 @@ def parse_data():
# 'name_ru': city_Dict['name:ru'],
# 'name_en': city_Dict['name:en'],
'geo_lat': str(city_Dict['@lat']),
'geo_lon': str(city_Dict['@lon']),
'timezone': tz,
'geo_lat': str(geo_lat),
'geo_lon': str(geo_lon),
}
if city_Dict['name:ru']:
@@ -232,8 +260,12 @@ def parse_data():
hash_data = hashlib.md5(json.dumps(country_Dict, sort_keys=True, ensure_ascii=True).encode('utf-8')).hexdigest()
country.add_node_to_json_data({'hash': hash_data})
if not country.timezone:
country.timezone = tzf.timezone_at(lng=float(country.geo_lon), lat=float(country.geo_lat))
print(f'country {country.name} - {country.timezone}')
if 'parsing_status' in country_Dict and country_Dict['parsing_status'] == 'finished':
country.parsing_finished_DT = datetime.now()
country.save(update_fields=['parsing_finished_DT'])
country.save()
return True

View File

@@ -13,7 +13,8 @@ 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
from GeneralApp.funcs import get_inter_http_response
from GeneralApp.funcs import get_and_set_lang
def get_address_point_ajax(request):
from .funcs import search_cities_in_db, search_airports_in_db
@@ -21,6 +22,9 @@ def get_address_point_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
try:
data = json.loads(request.body)
@@ -64,6 +68,8 @@ def get_address_point_ajax(request):
'res_search_list': html
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.2.2 on 2024-07-12 17:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ReferenceDataApp', '0005_remove_airport_parsing_finished_and_more'),
]
operations = [
migrations.AddField(
model_name='airport',
name='timezone',
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Часовая зона'),
),
migrations.AddField(
model_name='city',
name='timezone',
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Часовая зона'),
),
migrations.AddField(
model_name='country',
name='timezone',
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Часовая зона'),
),
]

View File

@@ -1,6 +1,8 @@
import pytz
from django.db import models
from BaseModels.base_models import BaseModel
from django.utils.translation import gettext_lazy as _
from datetime import datetime
class Country(BaseModel):
international_name = models.CharField(max_length=250, verbose_name=_('Международное название'), blank=True, null=True)
@@ -15,6 +17,8 @@ class Country(BaseModel):
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)
timezone = models.CharField(max_length=250, verbose_name=_('Часовая зона'), blank=True, null=True)
area_id = models.BigIntegerField(blank=True, null=True)
parsing_finished_DT = models.DateTimeField(verbose_name=_('Дата и время завершения парсинга'), blank=True, null=True)
@@ -46,6 +50,8 @@ class City(BaseModel):
area_id = models.BigIntegerField(blank=True, null=True)
timezone = models.CharField(max_length=250, verbose_name=_('Часовая зона'), blank=True, null=True)
parsing_finished_DT = models.DateTimeField(verbose_name=_('Дата и время завершения парсинга'), blank=True, null=True)
def __str__(self):
@@ -54,6 +60,17 @@ class City(BaseModel):
else:
return f'{self.id}'
def get_n_save_timezone(self):
from ReferenceDataApp.funcs import tzf
self.timezone = tzf.timezone_at(lng=float(self.geo_lon), lat=float(self.geo_lat))
self.save(update_fields=['timezone'])
return self.timezone
def get_current_datetime(self):
if not self.timezone:
self.timezone = self.get_n_save_timezone()
return datetime.now(tz=pytz.timezone(self.timezone))
def get_country_n_city_str(self):
country = _('Неизвестно')
city = self.name
@@ -83,6 +100,8 @@ class Airport(BaseModel):
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)
timezone = models.CharField(max_length=250, verbose_name=_('Часовая зона'), blank=True, null=True)
area_id = models.BigIntegerField(blank=True, null=True)
parsing_finished_DT = models.DateTimeField(verbose_name=_('Дата и время завершения парсинга'), blank=True, null=True)

View File

@@ -50,6 +50,9 @@ def routeForm_assign_choices_by_type_transport(form, type_transport):
class RouteForm(forms.ModelForm):
from_address_point_txt = forms.CharField(required=True)
to_address_point_txt = forms.CharField(required=True)
departure_DT = forms.DateTimeField(required=True, input_formats=['%d.%m.%Y %H:%M'])
arrival_DT = forms.DateTimeField(required=True, input_formats=['%d.%m.%Y %H:%M'])
class Meta:
model = Route
exclude = [
@@ -61,8 +64,6 @@ class RouteForm(forms.ModelForm):
# print('check')
cleaned_data = super(RouteForm, self).clean()
try:
if 'phone' in cleaned_data and 'phone' in cleaned_data:

View File

@@ -3,6 +3,7 @@ from .forms import *
from django.utils.translation import gettext as _
from django.template.loader import render_to_string
from datetime import datetime
from django.db.models import F
elements_on_page = 25
@@ -134,6 +135,11 @@ def get_profile_my_routes_page_content_html(request):
print(msg)
return msg
from SubscribesApp.funcs import get_cur_user_subscribe
user_subscribe = get_cur_user_subscribe(request.user)
if user_subscribe:
routes_Dict.update(user_subscribe.remains_route_adding_options())
html = render_to_string('blocks/profile/b_my_routes.html', routes_Dict, request=request)
return html
@@ -223,9 +229,35 @@ 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')
# rising_routes = Route.objects.filter(
# **kwargs,
# ).exclude(
# rising_DT=None
# ).order_by(
# '-rising_DT', '-departure_DT', '-arrival_DT', '-modifiedDT'
# )
routes = Route.objects.exclude(
rising_DT=None
).filter(
departure_DT__lt=datetime.now()
)
routes.update(
rising_DT=None
)
routes = Route.objects.filter(
**kwargs
).order_by(
F('rising_DT').desc(nulls_last=True),
# '-rising_DT',
'-departure_DT', '-arrival_DT', '-modifiedDT'
)
routes_count = routes.count()
if from_el and to_el:
routes = routes[from_el:to_el]
elif from_el:

View File

@@ -4,6 +4,8 @@ from django.urls import path
# from AuthApp.import_funcs import *
from .js_views import *
# /routes/
urlpatterns = [
path('change_route/<int:route_id>/', create_or_change_route_ajax, name='change_route_ajax'),
path('create_or_change_route/', create_or_change_route_ajax, name='create_or_change_route_ajax'),
@@ -14,4 +16,7 @@ urlpatterns = [
path('get_routes/', get_my_routes_ajax, name='get_my_routes_ajax'),
path('find_routes/', find_routes_ajax, name='find_routes_ajax'),
path('raise_route/', raise_route_ajax, name='raise_route_ajax'),
path('highlight_route/', highlight_route_ajax, name='highlight_route_ajax'),
]

View File

@@ -15,12 +15,103 @@ from django.template.loader import render_to_string
from django.urls import reverse
from .forms import *
from .funcs import *
from GeneralApp.funcs import get_and_set_lang
from SubscribesApp.funcs import check_option_in_cur_user_subscribe
def highlight_route_ajax(request):
if request.method != 'POST':
raise Http404
data = request.POST
if not data and request.body:
data = json.loads(request.body)
if not data or not 'route_id' in data:
msg = _('Недостаточно данных')
return JsonResponse({'errors': msg})
try:
route = Route.objects.get(owner=request.user, id=data['route_id'])
except Route.DoesNotExist:
msg = _('Не найден маршрут')
return JsonResponse({'errors': msg})
if not route.get_permission_for_highlight():
msg = _('Нет доступа к выделению')
return JsonResponse({'errors': msg})
route.highlight_color = '#FF0000'
route.save(update_fields=['highlight_color'])
from SubscribesApp.funcs import get_cur_user_subscribe
user_subscribe = get_cur_user_subscribe(request.user)
user_subscribe.used_route_highlight_count += 1
user_subscribe.save(update_fields=['used_route_highlight_count'])
Dict = {
'route': route,
}
html = render_to_string('widgets/routes/w_my_route.html', Dict, request=request)
res_Dict = {
'html': html
}
res_Dict.update(user_subscribe.remains_route_adding_options())
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
def raise_route_ajax(request):
if request.method != 'POST':
raise Http404
data = request.POST
if not data and request.body:
data = json.loads(request.body)
if not data or not 'route_id' in data:
msg = _('Недостаточно данных')
return JsonResponse({'errors': msg})
try:
route = Route.objects.get(owner=request.user, id=data['route_id'])
except Route.DoesNotExist:
msg = _('Не найден маршрут')
return JsonResponse({'errors': msg})
if not route.get_permission_for_raise():
msg = _('Нет доступных поднятий')
return JsonResponse({'errors': msg})
route.rising_DT = datetime.now()
route.save(update_fields=['rising_DT'])
from SubscribesApp.funcs import get_cur_user_subscribe
user_subscribe = get_cur_user_subscribe(request.user)
user_subscribe.used_route_rising_count += 1
user_subscribe.save(update_fields=['used_route_rising_count'])
res_Dict = {'status': 'ok'}
res_Dict.update(user_subscribe.remains_route_adding_options())
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
def del_route_ajax(request):
if request.method != 'POST':
raise Http404
if not check_option_in_cur_user_subscribe(request.user, 'размещение заявок'):
return JsonResponse({'html': 'нет доступа'}, status=403)
lang = get_and_set_lang(request)
try:
data = json.loads(request.body)
@@ -40,6 +131,8 @@ def del_route_ajax(request):
'html': html
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
@@ -54,6 +147,11 @@ def edit_route_ajax(request):
if request.method != 'POST':
raise Http404
if not check_option_in_cur_user_subscribe(request.user, 'размещение заявок'):
return JsonResponse({'html': 'нет доступа'}, status=403)
lang = get_and_set_lang(request)
data = json.loads(request.body)
Dict = {}
@@ -88,7 +186,13 @@ def edit_route_ajax(request):
return JsonResponse({'errors': msg})
html = render_to_string('blocks/profile/b_new_route.html', Dict, request=request)
return JsonResponse({'html': html}, status=200)
res_Dict = {
'html': html,
'btn_title': _('Сохранить изменения')
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
@@ -98,6 +202,10 @@ def edit_route_ajax(request):
def new_route_view_ajax(request):
if request.method != 'POST':
raise Http404
if not check_option_in_cur_user_subscribe(request.user, 'размещение заявок'):
return JsonResponse({'html': 'нет доступа'}, status=403)
lang = get_and_set_lang(request)
# form = RouteForm()
# Dict = {
@@ -123,7 +231,10 @@ def new_route_view_ajax(request):
# html = render_to_string('blocks/profile/b_new_route.html', Dict, request=request)
return JsonResponse({'html': html}, status=200)
res_Dict = {'html': html}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
def find_routes_ajax(request):
@@ -132,6 +243,8 @@ def find_routes_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
try:
@@ -145,6 +258,7 @@ def find_routes_ajax(request):
if 'errors' in routes_Dict:
return JsonResponse(routes_Dict, status=400)
if routes_Dict['routes']:
html = render_to_string('blocks/b_search_routes.html', routes_Dict, request=request)
else:
@@ -157,6 +271,8 @@ def find_routes_ajax(request):
# 'form': RouteForm(initial=data)
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
@@ -175,11 +291,18 @@ def get_my_routes_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
try:
routes_Dict = get_routes_Dict(request.user)
if 'errors' in routes_Dict:
return JsonResponse(routes_Dict, status=400)
from SubscribesApp.funcs import get_cur_user_subscribe
user_subscribe = get_cur_user_subscribe(request.user)
if user_subscribe:
routes_Dict.update(user_subscribe.remains_route_adding_options())
html = render_to_string('blocks/profile/b_my_routes.html', routes_Dict, request=request)
@@ -187,6 +310,8 @@ def get_my_routes_ajax(request):
'html': html
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
@@ -206,6 +331,11 @@ def create_or_change_route_ajax(request, route_id=None):
if request.method != 'POST':
raise Http404
if not check_option_in_cur_user_subscribe(request.user, 'размещение заявок'):
return JsonResponse({'html': 'нет доступа'}, status=403)
lang = get_and_set_lang(request)
Dict = {}
try:
@@ -248,6 +378,11 @@ def create_or_change_route_ajax(request, route_id=None):
routes_Dict = get_routes_Dict(request.user)
from SubscribesApp.funcs import get_cur_user_subscribe
user_subscribe = get_cur_user_subscribe(request.user)
if user_subscribe:
routes_Dict.update(user_subscribe.remains_route_adding_options())
if 'errors' in routes_Dict:
form.errors.update(routes_Dict['errors'])
Dict.update({'form': form})
@@ -261,6 +396,8 @@ def create_or_change_route_ajax(request, route_id=None):
'route_id': route_id
}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:

View File

@@ -0,0 +1,34 @@
# Generated by Django 4.2.2 on 2024-06-03 01:31
import colorfield.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RoutesApp', '0005_route_from_city_route_to_city'),
]
operations = [
migrations.AddField(
model_name='route',
name='rising_DT',
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата и время последнего поднятия'),
),
migrations.AddField(
model_name='route',
name='select_color',
field=colorfield.fields.ColorField(blank=True, default=None, image_field=None, max_length=25, null=True, samples=None, verbose_name='Цвет выделения'),
),
migrations.AlterField(
model_name='route',
name='arrival_DT',
field=models.DateTimeField(verbose_name='Дата и время прибытия'),
),
migrations.AlterField(
model_name='route',
name='departure_DT',
field=models.DateTimeField(verbose_name='Дата и время выезда'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2024-06-03 02:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('RoutesApp', '0006_route_rising_dt_route_select_color_and_more'),
]
operations = [
migrations.RenameField(
model_name='route',
old_name='select_color',
new_name='highlight_color',
),
]

View File

@@ -1,6 +1,7 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from BaseModels.base_models import BaseModel
from colorfield.fields import ColorField
type_transport_choices = [
@@ -38,8 +39,8 @@ class Route(BaseModel):
type_transport = models.CharField(
choices=type_transport_choices, default='', verbose_name=_('Выберите способ перевозки'))
departure_DT = models.DateTimeField(default=True, verbose_name=_('Дата и время выезда'))
arrival_DT = models.DateTimeField(default=True, verbose_name=_('Дата и время прибытия'))
departure_DT = models.DateTimeField(verbose_name=_('Дата и время выезда'))
arrival_DT = models.DateTimeField(verbose_name=_('Дата и время прибытия'))
from_address_point = models.IntegerField(verbose_name=_('Пункт выезда'))
to_address_point = models.IntegerField(verbose_name=_('Пункт приезда'))
from_city = models.ForeignKey(
@@ -62,6 +63,15 @@ class Route(BaseModel):
receive_msg_by_sms = models.BooleanField(default=False, verbose_name=_('Получать уведомления по SMS'))
owner = models.ForeignKey(User, verbose_name=_('Владелец'), related_name='rel_routes_for_owner', on_delete=models.CASCADE)
rising_DT = models.DateTimeField(
verbose_name=_('Дата и время последнего поднятия'),
blank=True, null=True
)
highlight_color = ColorField(
verbose_name=_('Цвет выделения'),
blank=True, null=True
)
def __str__(self):
if self.name:
return f'{self.name}'
@@ -74,6 +84,29 @@ class Route(BaseModel):
verbose_name_plural = _(u'Маршруты')
ordering = ('name',)
def get_permission_for_raise(self):
from SubscribesApp.funcs import get_cur_user_subscribe
user_subscribe = get_cur_user_subscribe(self.owner)
if not user_subscribe:
return False
data_Dict = user_subscribe.remains_route_adding_options()
if data_Dict['remains_route_rising_count'] > 0:
return True
return False
def get_permission_for_highlight(self):
from SubscribesApp.funcs import get_cur_user_subscribe
user_subscribe = get_cur_user_subscribe(self.owner)
if not user_subscribe:
return False
data_Dict = user_subscribe.remains_route_adding_options()
if data_Dict['remains_route_highlight_count'] > 0:
return True
return False
def from_country_n_city_str(self):
res = _('Неизвестно')
if self.from_city:

View File

@@ -4,7 +4,7 @@ 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
from SubscribesApp.funcs import check_option_in_cur_user_subscribe
def get_Dict_for_send_msgs(kwargs, search_owner_type):
@@ -36,6 +36,9 @@ def send_push_message_for_found_matches_routes(route, data_Dict):
if not route.owner.is_authenticated:
return None
if not check_option_in_cur_user_subscribe(route.owner, 'push уведомления'):
return False
from PushMessages.views import send_push
title = 'Мы нашли исполнителя по Вашему объявлению!'
text = 'Для просмотра результата нажмите на кнопку ниже'
@@ -126,14 +129,39 @@ def search_matches(for_routes=None):
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:
data_Dict = None
try:
data_Dict = get_Dict_for_send_msgs(params, found_routes[0].owner_type)
except Exception as e:
msg = f'<br>\n! search_matches Error get_Dict_for_send_msgs = {str(e)}'
print(msg)
log += msg
if data_Dict and check_option_in_cur_user_subscribe(
route.owner, 'push уведомления'
):
try:
msg = send_push_message_for_found_matches_routes(route, data_Dict)
if msg:
log += msg
except Exception as e:
msg = f'<br>\n! search_matches Error send_push_message_for_found_matches_routes = {str(e)}'
print(msg)
log += msg
if data_Dict and check_option_in_cur_user_subscribe(
route.owner,
'уведомление на e-mail о появлении перевозчика по заданным критериям'
):
try:
msg = send_mail_found_matches_routes(route, data_Dict)
if msg:
log += msg
except Exception as e:
msg = f'<br>\n! search_matches Error send_mail_found_matches_routes = {str(e)}'
print(msg)
log += msg
except Exception as e:
msg = f'<br>\n! search_matches Error = {str(e)}'
print(msg)

View File

@@ -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
from GeneralApp.funcs import get_inter_http_response
@@ -19,42 +19,48 @@ from GeneralApp.funcs import get_inter_http_respose
def route_search_results_View(request):
Dict = {}
data = None
data = {}
if request.GET:
data = request.GET.dict()
try:
routes_Dict = get_routes_Dict(data=data)
if routes_Dict:
Dict = {
'routes': routes_Dict['routes'],
'last_block': routes_Dict['last_block'],
'show_filter_and_results': True,
'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']})
if 'to_address_point_txt' in routes_Dict:
data.update({'to_address_point_txt': routes_Dict['to_address_point_txt']})
Dict.update({'route_form': RouteForm(initial=data)})
if request.GET:
data = request.GET.dict()
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"]}'
routes_Dict = get_routes_Dict(data=data)
if routes_Dict:
Dict = {
'routes': routes_Dict['routes'],
'last_block': routes_Dict['last_block'],
'show_filter_and_results': True,
'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']})
if 'to_address_point_txt' in routes_Dict:
data.update({'to_address_point_txt': routes_Dict['to_address_point_txt']})
Dict.update({'route_form': RouteForm(initial=data)})
Dict.update({
'page': {
'title': title,
'description': title,
'keywords': title,
}
})
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"]}'
t = loader.get_template('pages/p_results_find_route.html')
return get_inter_http_respose(t, Dict, request)
# return HttpResponse(t.render(Dict, request))
Dict.update({
'page': {
'title': title,
'description': title,
'keywords': title,
}
})
t = loader.get_template('pages/p_results_find_route.html')
return get_inter_http_response(t, Dict, request)
except Exception as e:
msg = f'!!! --- route_search_results_View Exception {str(e)}'
print(msg)
raise Http404

0
SitemapApp/__init__.py Normal file
View File

3
SitemapApp/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

16
SitemapApp/tests.py Normal file
View File

@@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

11
SitemapApp/urls.py Normal file
View File

@@ -0,0 +1,11 @@
from django.urls import include, path
from django.contrib.sitemaps import views as sitemaps_views
from django.views.decorators.cache import cache_page
from django.contrib.sitemaps.views import sitemap
from SitemapApp.views import sitemaps
urlpatterns = [
path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap')
]

149
SitemapApp/views.py Normal file
View File

@@ -0,0 +1,149 @@
# coding=utf-8
from django.contrib.sitemaps import Sitemap
from django.urls import reverse
from BaseModels.mailSender import techSendMail
import json
from datetime import datetime, time, timezone
# from PageSetsApp.models import *
# from ArticlesApp.models import *
# from BaseModels.base_api_requests import base_api_request
# from tEsiteProj.settings import API_URL
from django.db.models import Q
limit_records = 1000
sitemaps = {
}
protocol = 'https'
class sm_StaticPage(Sitemap):
changefreq = 'monthly'
priority = 1
i18n = True
protocol = protocol
def items(self):
from GeneralApp.models import StaticPage
return StaticPage.objects.filter(enable=True)
def location(self, item):
if item.url == 'main':
return reverse('main')
else:
return reverse('static_page', args=[item.url])
def lastmod(self, obj):
return obj.modifiedDT
sitemaps.update({'static_pages': sm_StaticPage})
class sm_ArticlesPage(Sitemap):
changefreq = 'daily'
priority = 2
i18n = True
protocol = protocol
def items(self):
return ['']
def location(self, item):
return reverse('articles')
def lastmod(self, obj):
from ArticlesApp.models import ArticleModel
article = ArticleModel.objects.filter(enable=True).order_by('-modifiedDT').first()
if article:
return article.modifiedDT
else:
return datetime.now()
sitemaps.update({'articles_page': sm_ArticlesPage})
class sm_Article(Sitemap):
changefreq = 'yearly'
priority = 2
i18n = True
protocol = protocol
def items(self):
from ArticlesApp.views import ArticleModel
objs = ArticleModel.objects.filter(enable=True)
return objs
def location(self, item):
return reverse('article_one', args=[item.url])
def lastmod(self, obj):
return obj.modifiedDT
sitemaps.update({'article': sm_Article})
class sm_UserPage(Sitemap):
changefreq = 'yearly'
priority = 2
i18n = True
protocol = protocol
def items(self):
from ArticlesApp.views import UserPageModel
objs = UserPageModel.objects.filter(enable=True)
return objs
def location(self, item):
return reverse('user_page', args=[item.url])
def lastmod(self, obj):
return obj.modifiedDT
sitemaps.update({'user_page': sm_UserPage})
class sm_Registration(Sitemap):
changefreq = 'never'
priority = 1
i18n = True
protocol = protocol
def items(self):
return ['']
def location(self, item):
return reverse('registration_page')
def lastmod(self, obj):
return datetime(2024, 6, 1)
sitemaps.update({'registration': sm_Registration})
class sm_Login(Sitemap):
changefreq = 'never'
priority = 1
i18n = True
protocol = protocol
def items(self):
return ['']
def location(self, item):
return reverse('login_profile')
def lastmod(self, obj):
return datetime(2024, 6, 1)
sitemaps.update({'login': sm_Login})

View File

@@ -42,6 +42,12 @@ class Admin_SubscribeOption(Admin_Trans_BaseModel):
'enable'
)
}),
(None, {
'classes': ['wide'],
'fields': (
'allow_route_rising_count', 'allow_route_highlight_count'
)
}),
)
list_display = [
@@ -63,7 +69,7 @@ class Admin_SubscribeForUser(Admin_Trans_BaseModel):
fieldsets = (
(None, {
'classes': ['wide'],
'fields': ('name',
'fields': ('enable',
'user', 'subscribe',
'last_paid_DT',
'paid_period_from_DT', 'paid_period_to_DT',
@@ -74,8 +80,9 @@ class Admin_SubscribeForUser(Admin_Trans_BaseModel):
)
list_display = [
'id',
'id', 'enable',
'name', 'user', 'subscribe',
'used_route_rising_count', 'used_route_highlight_count',
'last_paid_DT', 'paid_period_from_DT', 'paid_period_to_DT',
'auto_continue', 'receive_finish_subscribe_msg',
'order', 'modifiedDT', 'createDT'
@@ -88,6 +95,7 @@ class Admin_SubscribeForUser(Admin_Trans_BaseModel):
'auto_continue', 'receive_finish_subscribe_msg',
'modifiedDT', 'createDT'
]
list_editable = ['enable']
search_fields = ['name']
admin.site.register(SubscribeForUser,Admin_SubscribeForUser)

View File

@@ -1,41 +1,197 @@
from .models import *
from django.template.loader import render_to_string
from django.utils.translation import get_language, activate
from datetime import datetime, timedelta
import json
def get_user_subscribes_that_is_going_to_finish():
user_subscribes = SubscribeForUser.objects.filter(
enable=True,
subscribe__price__gt=0,
paid_period_to_DT__lt=datetime.now() + timedelta(days=3),
paid_period_to_DT__gt=datetime.now()
)
return user_subscribes
def extension_free_subscribes():
subscribe = get_null_price_subscribe()
if not subscribe:
return None
user_subscribes = SubscribeForUser.objects.filter(
enable=True,
subscribe=subscribe
)
for user_subscribe in user_subscribes:
user_subscribe.extension()
msg = f'{str(user_subscribe)} free subscribe extended'
print(msg)
return True
def finish_user_subscribes():
finished_user_subscribes = SubscribeForUser.objects.filter(
enable=True, paid_period_to_DT__lt=datetime.now()
)
finished_user_subscribes.update(enable=False)
for finished_user_subscribe in finished_user_subscribes:
msg = f'{str(finished_user_subscribe)} subscribe finished and switch to free'
print(msg)
subscribe_user_to_null_price_subscribe(finished_user_subscribe.user)
return True
def create_subscribe_by_data(create_kwargs):
subscribe = create_kwargs['subscribe']
create_kwargs.update({
'paid_period_from_DT': datetime.now(),
'paid_period_to_DT': datetime.now() + timedelta(hours=subscribe.period),
'enable': False
})
subscribe_for_user = SubscribeForUser.objects.create(**create_kwargs)
return subscribe_for_user
def check_option_in_cur_user_subscribe(user, option_name):
if not user or not user.is_active or not user.is_authenticated:
return False
user_subscribe = get_cur_user_subscribe(user)
try:
option = SubscribeOption.objects.get(
rel_subscribes_for_option=user_subscribe.subscribe,
name_ru__iexact=option_name
)
return True
except SubscribeOption.DoesNotExist:
return False
def get_null_price_subscribe():
subscribes_null_price = Subscribe.objects.filter(
enable=True,
price=0
)
if subscribes_null_price:
return subscribes_null_price[0]
return None
def subscribe_user_to_null_price_subscribe(user):
subscribe = get_null_price_subscribe()
if not subscribe:
return None
kwargs = {
'user': user,
'subscribe': subscribe,
}
subscribe_for_user = create_subscribe_by_data(kwargs)
subscribe_for_user = subscribe_for_user.activate()
return subscribe_for_user
def get_cur_user_subscribe(user):
user_subscribe = None
if not user or not user.is_active or not user.is_authenticated:
return None
try:
user_subscribe = SubscribeForUser.objects.get(user=user)
except Exception as e:
pass
user_subscribe = SubscribeForUser.objects.get(enable=True, user=user)
except SubscribeForUser.DoesNotExist:
user_subscribe = subscribe_user_to_null_price_subscribe(user)
except SubscribeForUser.MultipleObjectsReturned:
user_subscribes = SubscribeForUser.objects.filter(enable=True, user=user).order_by('-paid_period_to_DT')
user_subscribe = user_subscribes[0]
user_subscribes.exclude(id=user_subscribe.id).delete()
return user_subscribe
def get_subsribes_w_options():
def get_subscribes_w_options(user=None, check_subscribe_orders=False):
all_options = SubscribeOption.objects.filter(enable=True)
subscribes = Subscribe.objects.filter(enable=True)
for subscribe in subscribes:
subscribe_options_ids = subscribe.options.values_list('id', flat=True)
subscribe.disabled_options = all_options.exclude(id__in=subscribe_options_ids)
if user and check_subscribe_orders:
order = subscribe.get_last_order(user)
if order and order.status not in ['charged']:
error = f'{order.status}'
if 'status' in order.json_data and 'failure_message' in order.json_data['status']:
error = f'{error} ({order.json_data["status"]["failure_message"]})'
subscribe.order_error = error
return subscribes, all_options
def get_profile_subscribe_page_content_html(request):
def check_n_enable_subscribe_by_order(order):
if order and order.enable:
if order.status == 'charged':
order = order.activate_subscribe_for_user()
return order
def get_profile_subscribe_page_content_Dict(request, check_orders_required=False):
try:
# data = json.loads(request.body)
# all_options = SubscribeOption.objects.filter(enable=True)
subscribes, all_options = get_subsribes_w_options()
from GeneralApp.funcs import get_and_set_lang
lang = get_and_set_lang(request)
subscribe_for_user = SubscribeForUser.objects.filter(user=request.user)
if not subscribe_for_user:
data = {}
if request.body:
data = json.loads(request.body)
# check_orders_required = False
if data and 'check_orders_required' in data: #Требуется проверка статусов заказов
check_orders_required = data['check_orders_required']
# all_options = SubscribeOption.objects.filter(enable=True)
subscribes = []
subscribe_for_user = None
all_options = []
orders = None
if request.user and request.user.is_authenticated:
from BillingApp.funcs import get_orders_for_user, get_order_status
orders = get_orders_for_user(request.user)
if orders:
if check_orders_required:
for order in orders:
order = get_order_status(order)
order = check_n_enable_subscribe_by_order(order)
subscribe_for_user = order.subscribe_for_user
subscribes, all_options = get_subscribes_w_options(
request.user, check_subscribe_orders=True)
check_orders_required = False
else:
check_orders_required = True
if not subscribes:
subscribes, all_options = get_subscribes_w_options()
# if not subscribes_for_user:
subscribes_for_user = SubscribeForUser.objects.filter(enable=True, user=request.user)
if not subscribes_for_user:
tpl_name = 'blocks/profile/b_subscribe_variants.html'
else:
tpl_name = 'blocks/profile/b_subscribe_current.html'
subscribe_for_user = subscribe_for_user[0]
subscribe_for_user = subscribes_for_user[0]
subscribe_options_ids = subscribe_for_user.subscribe.options.values_list('id', flat=True)
subscribe_for_user.subscribe.disabled_options = all_options.exclude(id__in=subscribe_options_ids)
@@ -46,11 +202,14 @@ def get_profile_subscribe_page_content_html(request):
Dict = {
'subscribe_for_user': subscribe_for_user,
'subscribes': subscribes
'subscribes': subscribes,
}
html = render_to_string(tpl_name, Dict, request=request)
return html
return {
'html': html,
'check_orders_required': check_orders_required,
}
except Exception as e:
msg = f'show_cur_subscribe_ajax Error = {str(e)}'

View File

@@ -6,9 +6,12 @@ from .js_views import *
from django.contrib.auth import views
from RoutesApp.js_views import new_route_view_ajax
# /subscribes/receive_finish_subscribe_msg/
urlpatterns = [
path('show_cur_subscribe/', show_cur_subscribe_ajax, name='show_cur_subscribe_ajax'),
path('subscribe_now/', subscribe_now_ajax, name='subscribe_now_ajax'),
path('receive_finish_subscribe_msg/', receive_finish_subscribe_msg_ajax, name='receive_finish_subscribe_msg_ajax'),
# path('create_ticket/', create_ticket_ajax, name='create_ticket_ajax'),
# path('support_show_chat_by_ticket/', support_show_chat_by_ticket_ajax, name='support_show_chat_by_ticket_ajax'),
# # path('send_msg/', send_msg_ajax, name='send_msg_ajax'),

View File

@@ -3,7 +3,7 @@ from django.shortcuts import render
from uuid import uuid1
from .models import *
from django.contrib import auth
from django.http import HttpResponse, Http404, JsonResponse
from django.http import HttpResponse, Http404, JsonResponse, HttpResponseRedirect
from django.template import loader, RequestContext
from django.contrib.auth.decorators import login_required
from BaseModels.mailSender import techSendMail
@@ -16,36 +16,80 @@ import json
from datetime import datetime, time, timedelta
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from GeneralApp.funcs import get_and_set_lang
from django.shortcuts import redirect
def receive_finish_subscribe_msg_ajax(request):
if request.method != 'POST':
raise Http404
@login_required(login_url='/profile/login/')
lang = get_and_set_lang(request)
try:
data = json.loads(request.body)
user_subscribe = get_cur_user_subscribe(request.user)
user_subscribe.receive_finish_subscribe_msg = data['receive_finish_subscribe_msg']
user_subscribe.save(update_fields=['receive_finish_subscribe_msg'])
except Exception as e:
msg = f'msg_send_after_subscribe_end_ajax Exception = {str(e)}'
return JsonResponse({'error': msg}, status=400)
res_Dict = {'status': user_subscribe.receive_finish_subscribe_msg}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
@login_required()#login_url='/profile/login/')
def subscribe_now_ajax(request):
if request.method != 'POST':
raise Http404
lang = get_and_set_lang(request)
try:
data = json.loads(request.body)
subscribe = Subscribe.objects.get(id=data['subscribe_id'])
kwargs = {
kwargs_for_order = {
'user': request.user,
'subscribe': subscribe,
'last_paid_DT': datetime.now(),
'paid_period_from_DT': datetime.now(),
'paid_period_to_DT': datetime.now() + timedelta(hours=subscribe.period),
'receive_finish_subscribe_msg': True,
'currency': 'KZT',
'sum': subscribe.price,
}
subscribe_for_user = SubscribeForUser.objects.filter(user=request.user)
if subscribe_for_user:
subscribe_for_user.update(**kwargs)
subscribe_for_user = subscribe_for_user[0]
subscribe_for_user = None
if subscribe.price > 0:
from BillingApp.funcs import create_subscribe_order
order = create_subscribe_order(kwargs_for_order)
if order:
res_Dict = {'redirect_url': order.pay_page}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
else:
subscribe_for_user = SubscribeForUser.objects.create(**kwargs)
subscribe_for_user = subscribe_user_to_null_price_subscribe(request.user)
# kwargs = {
# 'user': request.user,
# 'subscribe': subscribe,
# 'last_paid_DT': datetime.now(),
# 'paid_period_from_DT': datetime.now(),
# 'paid_period_to_DT': datetime.now() + timedelta(hours=subscribe.period),
# 'receive_finish_subscribe_msg': True,
# }
# subscribe_for_user = SubscribeForUser.objects.filter(enable=True, user=request.user)
# if subscribe_for_user:
# subscribe_for_user.update(**kwargs)
# subscribe_for_user = subscribe_for_user[0]
if not subscribe_for_user:
tpl_name = 'blocks/profile/b_subscribe_variants.html'
@@ -64,21 +108,30 @@ def subscribe_now_ajax(request):
}
html = render_to_string(tpl_name, Dict, request=request)
return JsonResponse({'html': html}, status=200)
res_Dict = {'html': html}
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
except Exception as e:
msg = f'show_cur_subscribe_ajax Error = {str(e)}'
return JsonResponse({'error': msg}, status=400)
@login_required(login_url='/profile/login/')
@login_required()#login_url='/profile/login/')
def show_cur_subscribe_ajax(request):
if request.method != 'POST':
raise Http404
html = get_profile_subscribe_page_content_html(request)
return JsonResponse({'html': html}, status=200)
lang = get_and_set_lang(request)
res_Dict = get_profile_subscribe_page_content_Dict(request)
from GeneralApp.funcs import get_add_to_ajax_response_Dict
res_Dict.update(get_add_to_ajax_response_Dict(request.user))
return JsonResponse(res_Dict)
# try:
#

View File

@@ -0,0 +1,33 @@
# Generated by Django 4.2.2 on 2024-06-02 12:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('SubscribesApp', '0003_alter_subscribe_bg_color_alter_subscribe_text_color'),
]
operations = [
migrations.AddField(
model_name='subscribeforuser',
name='used_route_rising_count',
field=models.IntegerField(default=0, verbose_name='Использовано поднятий объявлений'),
),
migrations.AddField(
model_name='subscribeforuser',
name='used_route_select_count',
field=models.IntegerField(default=0, verbose_name='Использовано выделений объявлений'),
),
migrations.AddField(
model_name='subscribeoption',
name='allow_route_rising_count',
field=models.IntegerField(default=0, verbose_name='Количество поднятий объявлений'),
),
migrations.AddField(
model_name='subscribeoption',
name='allow_route_select_count',
field=models.IntegerField(default=0, verbose_name='Количество выделений объявлений'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2024-06-03 02:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('SubscribesApp', '0004_subscribeforuser_used_route_rising_count_and_more'),
]
operations = [
migrations.RenameField(
model_name='subscribeforuser',
old_name='used_route_select_count',
new_name='used_route_highlight_count',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2024-06-03 02:43
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('SubscribesApp', '0005_rename_used_route_select_count_subscribeforuser_used_route_highlight_count'),
]
operations = [
migrations.RenameField(
model_name='subscribeoption',
old_name='allow_route_select_count',
new_name='allow_route_highlight_count',
),
]

View File

@@ -1,10 +1,25 @@
from django.db import models
from django.db.models import Count, Sum, F
from django.db.models.functions import Coalesce
from BaseModels.base_models import BaseModel
from django.utils.translation import gettext_lazy as _
from colorfield.fields import ColorField
from datetime import datetime, timedelta
# options_list 29.05.2024
# СМС уведомления
# push уведомления
# выделение заявки цветом (20 заявок) + 70 поднятий
# выделение заявок цветом (3 заявки) + 5 поднятий
# уведомление на e-mail о появлении перевозчика по заданным критериям
# размещение заявок
# просмотр контактов
class SubscribeOption(BaseModel):
allow_route_rising_count = models.IntegerField(verbose_name=_('Количество поднятий объявлений') ,default=0)
allow_route_highlight_count = models.IntegerField(verbose_name=_('Количество выделений объявлений'), default=0)
class Meta:
verbose_name = _('Опция подписки')
verbose_name_plural = _('Опции подписки')
@@ -16,7 +31,8 @@ class Subscribe(BaseModel):
price = models.FloatField(verbose_name='Стоимость', default=0)
options = models.ManyToManyField(
SubscribeOption, verbose_name=_('Подключенные опции'), blank=True, related_name='rel_subscribes_for_option'
SubscribeOption, verbose_name=_('Подключенные опции'), blank=True,
related_name='rel_subscribes_for_option'
)
period_name = models.CharField(max_length=250, verbose_name=_('Название периода'))
period = models.IntegerField(default=0, verbose_name=_('Длительность подписки в часах'))
@@ -24,6 +40,13 @@ class Subscribe(BaseModel):
bg_color = ColorField(default='#FFFFFF', verbose_name=_('Цвет фона'))
text_color = ColorField(default='#000000', verbose_name=_('Цвет текста'))
def get_last_order(self, user):
order = None
orders = self.subscribe_orders_for_subscribe.filter(user=user, enable=True).order_by('-createDT')
if orders:
order = orders[0]
return order
class Meta:
verbose_name = _('Подписка')
verbose_name_plural = _('Подписки')
@@ -47,6 +70,72 @@ class SubscribeForUser(BaseModel):
receive_finish_subscribe_msg = models.BooleanField(
default=False, verbose_name=_('Получать сообщения о окончании периода'))
used_route_rising_count = models.IntegerField(verbose_name=_('Использовано поднятий объявлений'), default=0)
used_route_highlight_count = models.IntegerField(verbose_name=_('Использовано выделений объявлений'), default=0)
class Meta:
verbose_name = _('Пользовательская подписка')
verbose_name_plural = _('Пользовательские подписки')
verbose_name_plural = _('Пользовательские подписки')
def __str__(self):
res = 'Подписка'
if self.subscribe:
res += f' {self.subscribe.name}'
if self.user:
res += f' для {self.user.username}'
if not res:
res += f' {str(self.id)}'
return res
def remains_route_adding_options(self):
total_data = SubscribeOption.objects.filter(
enable=True, rel_subscribes_for_option=self.subscribe
).aggregate(
total_route_rising_count = Coalesce(Sum('allow_route_rising_count'), 0),
total_route_highlight_count = Coalesce(Sum('allow_route_highlight_count'), 0),
)
total_data.update({
'used_route_rising_count': self.used_route_rising_count,
'used_route_highlight_count': self.used_route_highlight_count,
'remains_route_rising_count': total_data['total_route_rising_count'] - self.used_route_rising_count,
'remains_route_highlight_count': total_data['total_route_highlight_count'] - self.used_route_highlight_count,
})
return total_data
def get_days_to_finish(self):
days = (self.paid_period_to_DT - datetime.now()).days
return days
def extension(self, order=None):
if not order and self.subscribe.price > 0:
return {'error': 'not paid'}
if self.subscribe.price == 0:
self.activate(
paid_period_from_DT=datetime.now(),
paid_period_to_DT=datetime.now() + timedelta(hours=self.subscribe.period)
)
def activate(self, paid_period_from_DT=None, paid_period_to_DT=None):
self.enable = True
if paid_period_from_DT:
self.paid_period_from_DT = paid_period_from_DT
if paid_period_to_DT:
self.paid_period_to_DT = paid_period_to_DT
self.save()
subscribes_for_user = SubscribeForUser.objects.filter(
user=self.user
).exclude(
id=self.id
)
subscribes_for_user.update(
enable=False,
used_route_rising_count=0,
used_route_highlight_count=0,
)
return self

52
SubscribesApp/reports.py Normal file
View File

@@ -0,0 +1,52 @@
from .models import *
from datetime import datetime, timedelta
from .funcs import *
from django.utils.translation import gettext as _
def send_mail_for_user_subscribes_that_is_going_to_finish():
log = ''
user_subscribes = get_user_subscribes_that_is_going_to_finish()
for user_subscribe in user_subscribes:
if not user_subscribe.receive_finish_subscribe_msg:
continue
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 = _('tripwb.com - Уведомление о скором окончании срока подписки')
Dict = {
'logo': f'{sets["domain"]}/static/img/svg/LogoMobile.svg',
'message_title': subject,
'user_subscribe': user_subscribe
}
Dict.update(sets)
html = render_to_string('mail/m_user_subscribes_that_is_going_to_finish.html', Dict)
from BaseModels.mailSender import admin_send_mail_by_SMTPlib
mail_sets = get_mail_send_options()
to = [user_subscribe.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
)
if res and type(res) == str:
print(res)
# log += f'\n{res}'
except Exception as e:
msg = (f'send_mail_for_user_subscribes_that_is_going_to_finish '
f'for user {user_subscribe.user} '
f'Exception = {str(e)}')
print(msg)
log += f'\n{msg}'
return log

View File

@@ -0,0 +1 @@
__author__ = 'SDE'

View File

@@ -0,0 +1,157 @@
__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
@register.simple_tag()
def check_subscribe_option(s_user, option_name):
from ..funcs import check_option_in_cur_user_subscribe
res = check_option_in_cur_user_subscribe(s_user, option_name)
return res
#
# @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

View File

@@ -29,13 +29,22 @@ DEBUG = True
ALLOWED_HOSTS = ["*"]
# https://web-push-codelab.glitch.me/
WEBPUSH_SETTINGS = {
"VAPID_PUBLIC_KEY": "BKS8byh3MucwCF2h06JY9oey1s1RYII09j-j3ehI3qTYhs965UHv0qNPl-jFjQBbIJCvjVXm9RW6t_oJJK8yMOk",
"VAPID_PRIVATE_KEY": "f5NMgOntBtRqsyeKwEzloK-051ggMnZGF_GFimERY0w",
"VAPID_ADMIN_EMAIL": "admin@tripwb.com"
"VAPID_PUBLIC_KEY": "BNUNtlKRY-9h2rFemuT6cODbg1Lkl9zMJJl9vcoVxoSXOYTpT-y8iHIseTsrzsSH03462tjYNx38fTkWyumKyEM",
"VAPID_PRIVATE_KEY": "ECoHc1i_XcLEo6KmkF76sHvdk49qBA4aNFyns6N3fPs",
"VAPID_ADMIN_EMAIL": "tripwithbonus@gmail.com"
}
PAY_SYSTEM_URL = 'https://sandboxapi.paymtech.kz/'
PKCS12_PASS = 'QNlhRStcY7mB'
API_PASS = 'aPqSRVZhxFjjSqbB'
PKCS12_FILENAME = 'dvldigitalprojects.p12'
# PAY_SYSTEM_URL = 'https://api.paymtech.kz/'
# PKCS12_PASS = 'fzSBm6WISje7'
# API_PASS = 't9g2+bZSvxNxCu+t'
# PKCS12_FILENAME = 'dvldigitalprojects_prod.p12'
SOCIALACCOUNT_LOGIN_ON_GET=True
ACCOUNT_DEFAULT_HTTP_PROTOCOL='https'
@@ -45,7 +54,9 @@ ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_VERIFICATION = 'optional'
LOGIN_REDIRECT_URL = '/profile/page/dashboard/'
LOGIN_URL = '/profile/login/'
# LOGIN_URL = '/profile/login/'
from django.urls import reverse_lazy
LOGIN_URL = reverse_lazy('login_profile')
LOGOUT_REDIRECT_URL = '/profile/login/'
@@ -93,6 +104,8 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'django.contrib.humanize',
'django.contrib.sitemaps',
'django.contrib.sites',
'colorfield',
@@ -107,6 +120,8 @@ INSTALLED_APPS = [
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'rosetta',
'GeneralApp',
'AuthApp',
'RoutesApp',
@@ -114,6 +129,7 @@ INSTALLED_APPS = [
'ArticlesApp',
'SubscribesApp',
'PushMessages',
'BillingApp',
]
MIDDLEWARE = [
@@ -157,7 +173,8 @@ TEMPLATES = [
# WSGI_APPLICATION = 'TWB.wsgi.application'
ASGI_APPLICATION = 'TWB.asgi.application'
WS_ADDRESS = 'localhost:8000'
# WS_ADDRESS = 'localhost:8000'
WS_ADDRESS = 'tripwb.com'
CHANNEL_LAYERS = {
'default': {
@@ -249,6 +266,7 @@ LOCALE_PATHS = (
gettext = lambda s: s
LANGUAGES = (
(u'ru', gettext(u'Russian')),
@@ -337,6 +355,10 @@ CKEDITOR_CONFIGS = {
}
ROSETTA_MESSAGES_PER_PAGE = 100
ROSETTA_SHOW_AT_ADMIN_PANEL = True
# CKEDITOR_OPTIONS = {
# 'height': 291,
# 'width': '95%',

View File

@@ -1,10 +1,11 @@
from django.contrib import admin
from django.urls import path, include
from django.urls import path, include, re_path
from django.conf.urls.static import static
from django.conf import settings
from GeneralApp.views import Page404
from AuthApp.views import login_View
from django.views.generic.base import RedirectView
handler404 = Page404
@@ -39,7 +40,9 @@ urlpatterns = [
path('test_404', Page404, name='page_404'),
path('', include('PushMessages.urls'))
path('', include('PushMessages.urls')),
path('', include('SitemapApp.urls')),
]
from django.conf.urls.i18n import i18n_patterns
@@ -54,7 +57,13 @@ urlpatterns += i18n_patterns(
path('', include('ArticlesApp.urls_translate')),
path('favicon.svg',RedirectView.as_view(url='/static/favicon.ico')),
)
if 'rosetta' in settings.INSTALLED_APPS:
urlpatterns += [
re_path(r'^rosetta/', include('rosetta.urls'))
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

BIN
dvldigitalprojects.p12 Normal file

Binary file not shown.

BIN
dvldigitalprojects_prod.p12 Normal file

Binary file not shown.

1360
favicon.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 95 KiB

View File

@@ -0,0 +1 @@
google-site-verification: google180852ecd111cd7b.html

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,11 @@ channels==4.0.0
daphne==4.0.0
channels-redis==4.1.0
django-colorfield
django-webpush==0.3.5
django-webpush==0.3.6
django-allauth==0.60.0
pytz==2024.1
requests-pkcs12==1.24
#django-tz-detect==0.4.0
django-rosetta==0.10.0
timezonefinder==6.5.2

View File

@@ -1,4 +1,4 @@
User-agent: *
Disallow: /
Host: tripwb.com
Host: dev.tripwb.com

View File

@@ -3,7 +3,7 @@
@media (max-width: 1280px){
.remove_route{
width: 100%;
text-align: center;
}
@@ -116,11 +116,19 @@
}
@media (max-width: 1180px){
.container_inf_about_moving{
padding: 15px;
}
.input_list.find_route{
left: unset;
width: 70%;
}
.dropdown{
vertical-align: sub;
}
.handler_curtain_left{
display: block;
}
@@ -136,6 +144,10 @@
transition: 200ms;
}
.unsubscribe_info {
font-size: 12px;
}
.container_content_handler_curtain_left{
display: flex;
rotate: 90deg;
@@ -254,7 +266,7 @@
}
header{
padding: 5px 16px;
padding: 3px 16px;
margin-top: unset;
}
.header_logo, .header_btn_mover, .header_btn_sender{
@@ -359,7 +371,7 @@
.info_profile>div>div>.left-part-carrier-card>.container_inf_about_moving{
width: 100%;
width: 98%;
}
@@ -386,11 +398,14 @@
margin-left: 5px;
}
.header_logo_mobile {
margin-right: 37px;
margin-right: 20px;
}
.line_f_header{
top: 43px;
}
header .header-second{
margin-top: 3px;
}
.self_news_img{
width: 40%;
@@ -494,7 +509,9 @@
.footer_logo{
text-align: center;
margin: 40px 0;
margin: 40px auto;
max-width: 90px;
/*margin-left: 43%;*/
}
.footer_text_sub{
@@ -717,7 +734,7 @@
margin-bottom: 25px;
}
ч
.arrange_subscribe{
margin: 20px auto 20px auto;
}
@@ -1101,9 +1118,12 @@
.left-part-carrier-card, .inf_carrier_container{
width: unset;
float: none;
padding: 1px 15px;
padding: 1px 20px;
border-right: unset;
}
.control_frame{
top: 3px;
}
.inf_carrier_container{
padding-top: 70px;
padding-bottom: 10px;
@@ -1133,7 +1153,7 @@
.cargo_type_trans{
display: block;
margin-bottom: 15px;
/*float: unset;*/
float: unset;
}
.from-to-country-container-carrier{
@@ -1181,7 +1201,7 @@
.from-to-city-text{
font-size: 12px;
font-weight: 400;
padding-bottom: 10px;
/*padding-bottom: 10px;*/
/*padding-top: unset;*/
}
.arrow_inf_about_moving{
@@ -1194,6 +1214,7 @@
font-style: normal;
font-weight: 600;
text-align: center;
margin-top: 7px;
}
.inf_carrier_icon{
width: 20px;
@@ -1208,7 +1229,7 @@
padding-right: unset;
font-size: 14px;
top: unset;
background: linear-gradient(99deg, #000000 1%, #ffffff 16%, #ffffff);
/*background: linear-gradient(99deg, #000000 1%, #ffffff 16%, #ffffff);*/
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
@@ -1218,6 +1239,7 @@
.name_carrier{
font-size: 14px;
position: unset;
}
@@ -1323,6 +1345,10 @@
justify-content: space-between;
}
.subscribe_wrpapper{
justify-content: space-between;
}
.text_another_subscribe{
font-size: 12px;
font-style: normal;
@@ -1484,11 +1510,19 @@
width: 153px;
}
.popup_content{
width: 41%;
}
}
@media (max-width: 950px){
#title_static_small{
display: none;
}
.info_profile{
width: 65%;
float: none;
@@ -1524,6 +1558,9 @@
.pag_news_item_text{
width: unset;
}
.popup_content{
width: 52%;
}
}
@media (max-width: 850px){
@@ -1568,7 +1605,7 @@
.read_more_about_subscribe{
.read_more_about_subscribe, .read_more_about_subscribe.error {
padding: 0 14px;
height: 30px;
font-size: 14px;
@@ -1627,15 +1664,21 @@
.toggle_switch_cont{
margin-top: 6px;
width: 12%;
width: unset;
}
}
@media (max-width: 800px) {
.line_inf_about_moving{
margin-bottom: 40px;
}
.marker_messages_mobile{
position: absolute;
top: 0;
@@ -1651,6 +1694,9 @@
.marker_messages_mobile.show{
display: block;
}
.popup_content>.confirm_profile_btn{
width: 90%;
}
}
@media (max-width: 828px){
@@ -1684,8 +1730,9 @@
}
.another_subscribe {
flex-direction: column;
.subscribe_wrpapper{
flex-direction: column;
}
.info_profile>div>div>.left-part-carrier-card>.container_inf_about_moving>.line_inf_about_moving>.carrier_inf_moving.left{
@@ -1741,6 +1788,10 @@
background: #ffffff;
}
.dropdown-content-lang{
z-index: 1 ;
}
.menu_buttons.right.open .handler_menu{
background: #FFFFFF;
color: #000000;
@@ -1792,14 +1843,20 @@
width: 100%;
}
.another_subscribe{
padding: 10px;
.subscribe_wrpapper{
padding: 10px;
flex-direction: unset;
justify-content: space-between;
}
.inf_carrier_icon{
/*width: 3%;*/
}
.popup_content{
width: 70%;
}
}
@media (max-width: 687px){
/*.to_address_point_txt.find_route {*/
@@ -1808,6 +1865,10 @@
/*.from_address_point_txt.find_route.first {*/
/* width: 52.1%;*/
/*}*/
.info_profile>div>div>.left-part-carrier-card>.container_inf_about_moving{
width: 98%;
}
.container_inf_about_moving {
width: 97%;
@@ -1848,6 +1909,10 @@
.info_profile>div>div>.left-part-carrier-card>.container_inf_about_moving{
width: 89%;
}
.phones_carrier{
display: block;
}
@@ -1873,6 +1938,7 @@
.container_inf_about_moving{
width: 100%;
padding: 16px;
}
.arrow_inf_about_moving{
@@ -1893,6 +1959,11 @@
margin-bottom: unset;
}
.name_carrier{
position: relative;
}
.splitter-from-to-country{
@@ -1969,7 +2040,7 @@
}
.dropdown-content{
width: 190px;
width: 202px;
top: 27px;
left: -83px;
}
@@ -2024,9 +2095,13 @@
.another_subscribe{
padding: 15px 0;
flex-direction: column;
justify-content: unset;
}
.subscribe_wrpapper, .error_order{
flex-direction: column;
justify-content: unset;
width: unset;
}
}

View File

@@ -443,7 +443,7 @@
.block-chat{
width: 63%;
height: calc(100vh - 120px);
height: calc(100vh - 150px);
border-radius: 10px;
border: 1px solid #E6E6E6;
background: #ffffff;
@@ -708,6 +708,13 @@
display: block;
}
.unredmessages_value_text{
padding-top: 8px;
padding-left: 0;
position:relative;
top: -6px;
}
.cost-messages-in-user-tab-messenger{
width: 15px;
@@ -719,8 +726,8 @@
}
.cost-messages-in-user-tab-messenger > span{
padding-top: 1px;
display: block;
padding-top: 7px;
/*display: block;*/
font-size: 10px;
}
@@ -1127,7 +1134,7 @@
border: 1px solid #E6E6E6;
display: block;
height: 20px;
width: calc(100% - 26px);
width: calc(100% - 17px);
padding: 20px 10px;
background: url(/static/img/svg/Calendar.svg) white 50%;
background-repeat: no-repeat;
@@ -1280,6 +1287,9 @@
border-radius: 10px;
/*padding: 20px;*/
}
.carrier-card.hide{
display: none;
}
.left-part-carrier-card{
width: 58%;
@@ -1310,7 +1320,7 @@
}
.from-to-country-container-carrier{
width: calc(100% - 70px);
width: calc(100% - 60px);
margin: auto;
background: #F8F8F8;
box-shadow: -1px 4px 10px 0 rgba(198, 199, 203, 0.20), 0 -1px 10px 0 rgba(198, 199, 203, 0.20);
@@ -1319,6 +1329,7 @@
padding-left: 35px;
padding-right: 35px;
margin-bottom: 20px;
border-radius: 10px;
}
@@ -1381,6 +1392,9 @@
.container_inf_about_moving{
display: block;
width: 100%;
background: #F8F8F8;
padding: 5px;
border-radius: 10px;
}
@@ -1414,7 +1428,7 @@
text-decoration: none;
color: #000000;
font-size: 16px;
padding-bottom: 10px;
padding: 10px 0 10px;
display: block;
}
@@ -1422,7 +1436,7 @@
position: relative;
top: 6px;
/*background: linear-gradient(99deg, #040404 56%, #9f9f9f 25%, #ffffff);*/
background: linear-gradient(99deg, #000000 1%, #ffffff 16%, #ffffff);
/*background: linear-gradient(99deg, #000000 1%, #ffffff 16%, #ffffff);*/
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
transition: 200ms;
@@ -1446,6 +1460,8 @@
white-space: unset;
}
.phones_carrier input{
display: none;
}
@@ -1461,7 +1477,7 @@
position: relative;
top: 5px;
/*background: linear-gradient(99deg, #040404 2%, #f5f5f5 16%, #ffffff);*/
background: linear-gradient(99deg, #040404 2%, #ffffff 13%, #ffffff);
/* background: linear-gradient(99deg, #040404 2%, #ffffff 13%, #ffffff);*/
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
transition: 200ms;
@@ -1481,6 +1497,7 @@
white-space: unset;
}
.email_carrier input{
display: none;
}
@@ -1552,6 +1569,7 @@ a.open_inf_carrier{
width: 33%;
float: right;
padding: 2%;
position: relative;
}
.title_container_inf_carrier{
@@ -1574,6 +1592,17 @@ a.open_inf_carrier{
position: relative;
top: 11px;
left: 4px;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.name_carrier.active{
background: unset;
-webkit-background-clip: unset;
-webkit-text-fill-color: unset;
overflow: unset;
overflow-wrap: break-word;
white-space: unset;
}
.from-to-city-text{
@@ -1804,14 +1833,18 @@ a.open_inf_carrier{
.another_subscribe{
width: calc(100% - 50px);
padding: 0 20px;
padding: 10px 20px;
background: #FFFFFF;
box-shadow: -1px 4px 10px 0 rgba(198, 199, 203, 0.20), 0 -1px 10px 0 rgba(198, 199, 203, 0.20);
display: flex;
/*display: flex;*/
margin-bottom: 20px;
border-radius: 10px;
/*height: 76px;*/
}
.subscribe_wrpapper {
display: flex;
align-items: center;
height: 76px;
}
.name_subscribe_another{
@@ -1842,6 +1875,39 @@ a.open_inf_carrier{
height: 44px;
width: 160px;
}
.read_more_about_subscribe.error{
background: unset;
color: #FF613A;
width: unset;
height: unset;
font-size: 15px;
font-weight: 600;
cursor: pointer;
}
.error_order {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #FF613A;
padding: 5px;
border-radius: 10px;
margin-top: 10px;
width: 100%;
}
.error_icon{
vertical-align: middle;
}
.error_title {
font-weight: 600;
font-size: 14px;
}
.error_text {
font-weight: 400;
font-size: 12px;
}
.subscribe_was_paid{
color: #27242499;
@@ -2201,7 +2267,7 @@ a.open_inf_carrier{
height: 1px;
background: #dad7d7;
width: 100%;
top: 60px;
top: 100px;
z-index: 999;
}
@@ -2464,7 +2530,7 @@ a.open_inf_carrier{
background: #FFFFFF;
border-radius: 10px;
max-height: calc(100vh - 125px);
overflow: unset;
/*overflow: unset;*/
left: -19px;
top: 0;
}
@@ -2535,7 +2601,10 @@ a.open_inf_carrier{
}
.menu_buttons.right.open .menu_profile{
padding-top: 30px;
padding-top: 45px;
}
.menu_buttons.right.padding_remove.open .menu_profile{
padding-top: unset;
}
@@ -2729,7 +2798,7 @@ a.open_inf_carrier{
/*}*/
.menu_profile>div>a{
/*font-size: 12px;*/
line-height: 13px;
/*line-height: 13px;*/
}
.menu_profile>div>.logout{
height: 100%;
@@ -2939,6 +3008,8 @@ a.open_inf_carrier{
display: none;
}
.cookie_block.show{
display: block;
}
@@ -2965,4 +3036,64 @@ a.open_inf_carrier{
height: 25px;
width: 95px;
cursor: pointer;
}
/*popup*/
.popup_wrapper{
display: none;
position: fixed; /* Используйте fixed, чтобы попап оставался на месте при прокрутке */
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Полупрозрачный фон */
justify-content: center;
align-items: center;
z-index: 9999;
}
.popup_wrapper.show{
display: flex;
}
.popup_content{
position: relative;
width: 38%;
height: fit-content;
background: #FFFFFF;
border-radius: 15px;
text-align: center;
padding: 28px;
font-family: Inter;
}
.popup_cross{
position: absolute;
right: 5%;
cursor: pointer;
}
.popup_img>img{
background: #E1E1E1;
border-radius: 39px;
padding: 5px;
}
.popup_title{
font-weight: 700;
font-size: 34px;
margin-top: 20px;
}
.popup_text{
line-height: 30px;
font-size: 20px;
margin-top: 10px;
color: #6F6C90;
}
.popup_content>.confirm_profile_btn{
width: 62%;
}
#poup_text_bold{
font-weight: 700;
color: #1d1e20;
}
#authenticated_img{
display: none;
}

View File

@@ -299,7 +299,8 @@ section.register>h1 {
margin-bottom: 10px;
}
.footer_logo{
margin-bottom: 40px;
margin-bottom: 20px;
max-width: 45px;
}
footer>div {
@@ -502,10 +503,12 @@ footer>div {
header .header-first {
float: left;
margin-top: 12px;
/*margin-top: 12px;*/
}
.header_logo_mobile{
display: none;
}
@@ -663,6 +666,7 @@ header>div>a>span{
header .header-second {
float: right;
margin-top: 20px;
}
@@ -678,6 +682,71 @@ header .header-second {
width: 40px;
border-radius: 10px;
aspect-ratio: 4/3;
display: none;
}
.route_contact_avatar.active{
display: inline-block;
}
.carrier-card.highlight-color{
background: linear-gradient(90deg, #FBED96 0%, #ABECD6 100%);
}
.control_frame{
display: none;
position: absolute;
background-color: white;
border-radius: 10px;
box-shadow: -1px 4px 10px 0px rgba(198, 199, 203, 0.20), 0px -1px 10px 0px rgba(198, 199, 203, 0.20);
z-index: 1;
border: 1px solid #FF613A;
padding: 0 10px;
left: 0;
right: 0;
margin: 0 18px;
}
.control_frame.show{
display: block;
}
button#send_upgrade {
font-size: 16px;
font-weight: 500;
line-height: 20px;
text-align: center;
color: #FF613A;
width: 100%;
padding: 10px;
margin: 10px 0;
}
button#send_upgrade:hover {
background: #FF613A;
color: white;
border-radius: 15px;
}
.success_rising{
display: none;
}
.success_rising.show{
display: block;
padding: 5px;
}
.success_rising_text{
text-align: center;
padding-bottom: 10px;
}
.close_success_rising{
cursor: pointer;
width: 100%;
padding-bottom: 5px;
}
.upd_form.hide{
display: none;
}
.from_address_point_txt.red_text{
@@ -687,6 +756,24 @@ header .header-second {
color: #ff0000;
}
.info_text_wrapper{
padding-top: 20px;
text-align: center;
color: red;
}
.info_text{
display: none;
}
.info_text.show{
display: block;
}
button#webpush-subscribe-button {
color: white;
text-decoration: underline;
}
.header-second-item,
@@ -738,6 +825,7 @@ span.btn_profile_name {
display: none;
position: absolute;
background-color: #f9f9f9;
z-index: 1;
}
@@ -1036,6 +1124,16 @@ input.deactive {
background-color: white;
}
.custom-checkbox.round+label::before {
border-radius: 20px;
width: 25px;
height: 25px;
border: 1px solid #E0E0E0;
background-color: #FAFAFA;
}
.custom-checkbox:not(:disabled):not(:checked)+label:hover::before {
@@ -1072,6 +1170,12 @@ input.deactive {
left: 29%;
}
.out_of_date{
pointer-events: none;
filter: grayscale(100%);
opacity: 0.5;
}
.title_for_msg_by_email{
display: none;
z-index: 100;
@@ -1469,6 +1573,21 @@ span.errorlist{
position: absolute;
bottom: 27%;
}
.label_text{
display: inline-block;
position: absolute;
bottom: 20%;
font-size: 14px;
}
label.route_label {
margin-bottom: unset;
padding-top: 10px;
}
.sub_label_text {
font-size: 14px;
margin-left: 35px;
}
/*create new route*/
select#id_type_transport{
@@ -1966,7 +2085,7 @@ input#id_extra_phone
display: inline-block;
}
button#edit_route {
button#raise_route {
display: block;
height: 44px;
width: 100%;
@@ -1980,7 +2099,23 @@ button#edit_route {
line-height: 26px;
border-radius: 10px;
text-align: center;
margin-bottom: 5px;
margin-bottom: 10px;
margin-top: 10px;
}
.edit_route{
width: 68%;
height: 44px;
border-radius: 10px;
background: #E6E6E6;
}
.edit_route.highlight-color{
background: #F8F8F8;
}
.edit_route.hide{
display: none;
}
.button_remove_route {
@@ -1992,12 +2127,17 @@ button#edit_route {
.remove_route {
height: 44px;
width: 285px;
width: 30%;
color: rgba(39, 36, 36, 0.60);
}
.remove_route.hide{
display: none;
}
.filter-orange{
filter: invert(43%) sepia(93%) saturate(1113%) hue-rotate(336deg) brightness(100%) contrast(102%);
}
.msg_send{
font-size: 24px;
text-align: center;
@@ -2125,7 +2265,7 @@ button.cancel_remove.show, button.confirm_remove.show{
font-size: 26px;
}
#title_static{
#title_static, #title_static_small{
text-align: center;
font-size: 44px;
font-style: normal;
@@ -2135,6 +2275,10 @@ button.cancel_remove.show, button.confirm_remove.show{
}
#title_static_small{
font-size: 34px;
}
.ta_center{
margin: 120px auto 40px;
}
@@ -2197,12 +2341,12 @@ span#sub_title_static{
height: 60px;
}
.top_block_static{
.top_block_static {
position: relative;
width: 90%;
height: 194px;
height: 270px;
margin: 0 auto 0;
padding: 60px 40px 0 40px;
padding: 40px 40px 0 40px;
}
.top_block_static_wrapper{
@@ -2236,10 +2380,10 @@ span#sub_title_static{
url(/static/img/png/Box8.png),
url(/static/img/png/Box4.png);
background-position:
bottom -31px left -13px,
bottom 26px left -13px,
top 159px left 242px,
top 0px right -15px,
top 146px right 215px;
top 34px right -19px,
top 145px right 215px;
background-repeat:
no-repeat,
no-repeat,
@@ -2620,6 +2764,15 @@ button#send_feedback_form:active{
/* display: block;*/
/*}*/
.unsubscribe_info {
color: #a5a5a5;
font-size: 10px;
margin-top: 5px;
}
.tab-btn-1,
.tab-btn-2 {
display: inline-block;
@@ -3151,6 +3304,31 @@ details[open] summary ~ *{
text-decoration: underline;
}
.self_news_text>ul>li{
list-style: disc;
}
.self_news_text>ul{
margin-left: 20px;
}
.self_news_text>ol{
padding-left: 20px;
}
.self_news_text>ol>li{
margin: 10px 0;
}
.self_news_text>ol>li>ul{
list-style: disc;
margin-left: 20px;
}
.advertisement_block_news {
display: none;
}
.self_news_text>img{
margin: 15px 0;
}
.self_news_img{
float: right;
width: 40%;

1076
static/favicon.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 81 KiB

BIN
static/img/1234.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
static/img/png/finlogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.73145 9.34686H2.98145V5.59686" stroke="#272424" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.16895 17.8313C7.32246 18.9857 8.79246 19.7721 10.393 20.091C11.9935 20.4099 13.6527 20.2469 15.1605 19.6227C16.6684 18.9984 17.9573 17.941 18.8641 16.5842C19.7709 15.2273 20.255 13.632 20.255 12C20.255 10.368 19.7709 8.7727 18.8641 7.41585C17.9573 6.059 16.6684 5.00158 15.1605 4.37735C13.6527 3.75313 11.9935 3.59014 10.393 3.90902C8.79246 4.22789 7.32246 5.0143 6.16895 6.16875L2.98145 9.34688" stroke="#272424" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 725 B

View File

@@ -1,17 +1,17 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_581_1765)">
<g filter="url(#filter0_d_266_275)">
<path d="M13.9973 8.66525C12.1611 8.66525 10.6626 10.1638 10.6626 12C10.6626 13.8362 12.1611 15.3348 13.9973 15.3348C15.8335 15.3348 17.332 13.8362 17.332 12C17.332 10.1638 15.8335 8.66525 13.9973 8.66525ZM23.9989 12C23.9989 10.6191 24.0114 9.25064 23.9338 7.87221C23.8563 6.27113 23.4911 4.85017 22.3203 3.67938C21.147 2.50609 19.7286 2.14334 18.1275 2.06579C16.7466 1.98824 15.3782 2.00074 13.9998 2.00074C12.6189 2.00074 11.2505 1.98824 9.8721 2.06579C8.27105 2.14334 6.85012 2.50859 5.67935 3.67938C4.50608 4.85267 4.14334 6.27113 4.06579 7.87221C3.98824 9.25314 4.00074 10.6216 4.00074 12C4.00074 13.3784 3.98824 14.7494 4.06579 16.1278C4.14334 17.7289 4.50858 19.1498 5.67935 20.3206C6.85262 21.4939 8.27105 21.8567 9.8721 21.9342C11.253 22.0118 12.6214 21.9993 13.9998 21.9993C15.3807 21.9993 16.7491 22.0118 18.1275 21.9342C19.7286 21.8567 21.1495 21.4914 22.3203 20.3206C23.4936 19.1473 23.8563 17.7289 23.9338 16.1278C24.0139 14.7494 23.9989 13.3809 23.9989 12ZM13.9973 17.131C11.1579 17.131 8.86644 14.8394 8.86644 12C8.86644 9.16058 11.1579 6.86903 13.9973 6.86903C16.8367 6.86903 19.1282 9.16058 19.1282 12C19.1282 14.8394 16.8367 17.131 13.9973 17.131ZM19.3383 7.8572C18.6754 7.8572 18.14 7.32184 18.14 6.65889C18.14 5.99594 18.6754 5.46058 19.3383 5.46058C20.0013 5.46058 20.5366 5.99594 20.5366 6.65889C20.5368 6.81631 20.5059 6.97222 20.4458 7.1177C20.3856 7.26317 20.2974 7.39535 20.1861 7.50666C20.0748 7.61798 19.9426 7.70624 19.7971 7.76639C19.6516 7.82654 19.4957 7.8574 19.3383 7.8572Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_581_1765" x="0" y="0" width="28" height="28" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<filter id="filter0_d_266_275" x="0" y="0" width="28" height="28" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_581_1765"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_581_1765" result="shape"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_266_275"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_266_275" result="shape"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,5 +1,834 @@
<svg width="100" height="30" viewBox="0 0 100 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1227 6.01192V27.0015C10.1227 27.8296 10.418 28.5293 11.0085 29.1004C11.5709 29.7001 12.2598 30 13.0752 30C13.8907 30 14.5936 29.7001 15.1841 29.1004C15.7465 28.5293 16.0277 27.8296 16.0277 27.0015V6.01192H23.198C24.0134 6.01192 24.7164 5.72635 25.3069 5.1552C25.8692 4.5555 26.1504 3.84157 26.1504 3.01341C26.1504 2.18525 25.8692 1.47132 25.3069 0.871618C24.7164 0.300472 24.0134 0.0148987 23.198 0.0148987H2.95247C2.13702 0.0148987 1.43406 0.300472 0.843562 0.871618C0.281187 1.47132 0 2.18525 0 3.01341C0 3.84157 0.281187 4.5555 0.843562 5.1552C1.43406 5.72635 2.13702 6.01192 2.95247 6.01192H10.1227Z" fill="white"/>
<path d="M67.7654 0.314751C67.0343 -0.0564935 66.2751 -0.0993296 65.4878 0.186242C64.7286 0.443258 64.1662 0.94301 63.8006 1.6855L54.648 20.2763L51.6112 14.1507L56.4195 4.34132C56.785 3.59883 56.8413 2.84207 56.5882 2.07102C56.3351 1.27142 55.843 0.685995 55.112 0.314751C54.3809 -0.0564935 53.6217 -0.0993296 52.8343 0.186242C52.0751 0.443258 51.5128 0.94301 51.1472 1.6855L48.3213 7.4255L45.4532 1.6855C45.0876 0.94301 44.5252 0.443258 43.766 0.186242C42.9787 -0.0993296 42.2195 -0.0564935 41.4884 0.314751C40.7573 0.685995 40.2653 1.27142 40.0122 2.07102C39.7591 2.84207 39.8154 3.59883 40.1809 4.34132L44.9892 14.1507L41.9946 20.2763L32.7997 1.6855C32.4342 0.94301 31.8718 0.443258 31.1126 0.186242C30.3253 -0.0993296 29.5661 -0.0564935 28.835 0.314751C28.1039 0.685995 27.6118 1.27142 27.3588 2.07102C27.1057 2.84207 27.1619 3.59883 27.5275 4.34132L39.3373 28.3294C39.8716 29.4431 40.7573 30 41.9946 30C43.2037 30 44.0754 29.4431 44.6096 28.3294L48.3213 20.8331L51.9908 28.3294C52.525 29.4431 53.4108 30 54.648 30C55.8571 30 56.7288 29.4431 57.263 28.3294L69.0729 4.34132C69.4385 3.59883 69.4947 2.84207 69.2416 2.07102C68.9885 1.27142 68.4965 0.685995 67.7654 0.314751Z" fill="white"/>
<path d="M78.9109 24.003V18.006H91.1426C91.958 18.006 92.6469 18.3058 93.2093 18.9055C93.7998 19.4767 94.0951 20.1763 94.0951 21.0045C94.0951 21.8326 93.7998 22.5323 93.2093 23.1034C92.6469 23.7031 91.958 24.003 91.1426 24.003H78.9109ZM78.9109 6.01192H89.4555C90.2709 6.01192 90.9598 6.31177 91.5222 6.91147C92.1127 7.48262 92.4079 8.18227 92.4079 9.01043C92.4079 9.83859 92.1127 10.5382 91.5222 11.1094C90.9598 11.7091 90.2709 12.0089 89.4555 12.0089H78.9109V6.01192ZM75.9585 0.0148987C75.143 0.0148987 74.4541 0.300472 73.8918 0.871618C73.3013 1.47132 73.006 2.18525 73.006 3.01341V27.0015C73.006 27.8296 73.3013 28.5293 73.8918 29.1004C74.4541 29.7001 75.143 30 75.9585 30H91.1426C93.5889 30 95.6697 29.1147 97.385 27.3442C99.1283 25.6022 100 23.4889 100 21.0045C100 18.2059 98.9174 15.907 96.7523 14.1079C97.7927 12.5658 98.3129 10.8667 98.3129 9.01043C98.3129 6.52595 97.4412 4.41271 95.6978 2.67072C93.9826 0.900174 91.9018 0.0148987 89.4555 0.0148987H75.9585Z" fill="white"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="998px" height="1080px" viewBox="0 0 998 1080" enable-background="new 0 0 998 1080" xml:space="preserve"> <image id="image0" width="998" height="1080" x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+YAAAQ4CAYAAACE88b2AAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAACA
AElEQVR42uzdd5hrVdWA8XfdRodLE5AqTVCqiCKoWFCxC/beEHvvn9j7Z6+fvaKiYi9YQEREBBQE
RATpRaT3drn3ru+PfcYbhmQmmcnMTjLv73nyJHNycs46JzOTrLP3XhskSZIkSZIkSZIkSZIkSZIk
SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk
SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk
SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk
SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk
SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk
SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk
SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk
SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk
SZIkSZIkSZIkSZIkSZIkSZIkSZIkDYSoHYAkjaLMXBVYBVgDWA1YCKwKLGpWWQVYqXm8UvMzzXqr
tdnk2DbGW9RsdyKdtjlV85vj6pd5wJp93J6mbhlwfe0g+uhaIGsH0Sc3ArfVDqJPbgAOB46JiFE5
JkmaFhNzSepRZi4EdgX2AHYGtgA2BNYDVsYkU5K6cT0lQf8l8KuIuKh2QJJUi4m5JE0iMxcBewIP
Bh4A3JOSgEuS+udk4DBKov6niFhWOyBJmi0m5pLURmZuCjy6ue3Niq7mkqSZdznwM+DHwOERcXPt
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
zHxThc/J32XmgtrHLkmSZklmrpGZJ87il40fZ2bUPm5JkrqRs38R+1+ZuW7t45YkSbMsM9fPzFNn
4cvGEZm5au3jlSSpF5m5avMZNtP+kZmb1D5eSZJUSWaulZm/ncEvG1/KzEW1j1OSpKnIzEXNZ9lM
OTwz16t9nJIkqbLMnJ+ZB2Xmkj5+0bgqM59e+9gkSeqHzHx6Zl7Zx8/JW5rP3vm1j02SJA2QzNwu
pz+e7ubM/KRX/yVJoyYz12k+46ZTsX1ZZn4vM7esfTySJGmAZeZOmfmpzLy0hy8ap2XmmzNzw9rx
S5I0kzLzTpn5P5n59x4+J8/OzP81IZfmNishS+pZZs4DdgF2B+4GbAis1Dx9PXAB8HfgmIi4oHa8
kiTNtszcDNgL2A7YDFhM+e59DXApcArw14j4Z+1YJUmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEma
i6J2AHNZZq4KbA6sB9wJWLe5rQMsBBY3q64CrFQ7XkmSJEkj51bg5ubxNcBtwFXAlc3tMuAK4PyI
uKl2sKPKxHyGZeYC4K7ATsDdga2AuwBbABvUjk+SJEmSunQpcF5zOws4DTgV+GdELK0d3DAzMe+j
zAxge+C+wJ7ALs3Pi2rHJkmSJEkzZAlwOvA34E/AH4HTIyJrBzYsTMynKTN3AvYF9gbuA6xdOyZJ
kiRJquxq4FjgKOBXwKkm6p2ZmPcoM9cCHgI8gpKQb1Q7JkmSJEkacJdQEvRfAr+NiGtrBzRITMy7
kJmrA48CngI8HLumS5IkSdJULQEOAw4Bfh4RN9QOqDYT8w4ycz6lVfzZwCOBlWvHJEmSJEkj5hbg
F8DXgV9GxLLaAdVgYj5OZm4OPB94LrBJ7XgkSZIkaY64CPga8KWIOL92MLPJxLyRmfsAr6K0knte
JEmSJKmOpHR1/1hEHF47mNkwpxPQzFwIPBl4LWVqM0mSJEnS4Pgb8BHguxFxW+1gZsqcTMwzcxHw
AuBN2F1dkiRJkgbdRcAHgC9GxJLawfTbnErMM3MBZfz4W4BNa8cjSdKsSubYJ78kaQRdCLwX+HJE
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
4AxFrjhRYr5q7eB6cGXtACRJkiRJA2cocsWJEvM1agfXg8trByBJkiRJGjhDkSvOm+Jzg+ba2gFI
kiRJkgbOUOSKbZPvzFxYO7Ae3VI7AEmSJEnSwBmKXLFTq/hqtQPr0VCcbEmSJEnSrBqKXHGYuqtL
kiRJkjRyTMwlSZIkSarIxFySJEmSpIpMzCVJkiRJqsjEXJIkSZKkikzMJUmSJEmqyMRckiRJkqSK
TMwlSZIkSarIxFySJEmSpIpMzCVJkiRJqsjEXJIkSZKkikzMJUmSJEmqyMRckiRJkqSKTMwlSZIk
SarIxFySJEmSpIpMzCVJkiRJqsjEXJIkSZKkikzMJUmSJEmqyMRckiRJkqSKTMwlSZIkSarIxFyS
JEmSpIpMzCVJkiRJqsjEXJIkSZKkikzMJUmSJEmqyMRckiRJkqSKTMwlSZIkSarIxFySJEmSpIpM
zCVJkiRJqsjEXJIkSZKkikzMJUmSJEmqyMRckiRJkqSKTMwlSZIkSarIxFySJEmSpIpMzCVJkiRJ
qsjEXJIkSZKkikzMJUmSJEmqyMRckiRJkqSKTMwlSZIkSapoQe0AJEnSxDJzFWBjYD5wOXBtRCyr
HZckSeoPE3NJkqYpMxcCWwKbAasBKzX3GwJXAP8BrgcujogzJ9jGguZ1DwUeASQlEX9Ms+0AbgJu
yczzgOOBQ4DzgMsjYkntcyFJknoX7RZm5mLg6trB9WDtiLimdhCSpNGWmfMordY7A6sCTwIuAh4G
3I8yROwWYOWWly1v7m8Dfg58l5Jw3xtYDOwI/AJYA9gfOAtYBGwDbMLtP6uz+flaSqJ/U/O6I4Gj
gEMi4rrMXNQsvzYiltY+b5Ik1TIsua2JuSRJE8jMBcDuwPnAR4AnUhLwsc/QpcA1wFrAwvEvB/4E
HAYcB2wL7AfsQ/mcXbPZ1nJW1H2J9pH8Nykf/3gpcA5wl2Y/FwHbAQ8AlgDvjoj31T6PkiTVMCy5
rV3ZJUlqIzM3BJ4DvBRYh5JQXwl8g9Jqvg0lKV6P2yflCZwJ/BT4XvO6ZwLvHreLxaxIrue37rq5
H5+gR4fHCygJfwIPBlZpnl8GnEpJ1CVJ0gAzMZckqZGZW1K6oe8AvJDS6jyWBL+Y0n39gcCxwK7N
usuBWyljyX8OXAh8uOlSPp/Stf2pTJxoT7R8rHW8033r61Zr+flGYF3gummcj92BtYE/RsRNs/Mu
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
mc/NzG2m8N7cKTN/mCWhXzUzt87Mx2TmAzPzK5n59cy8f59/Hw7q8P4emplr9nNfkqS5KYcktx3q
4Fssrv2GS5ImlqUa90fH/f++OUtr82Mz8+5ZKql301rcqdp4O2PLr8jMJ2cZt/7u7JD4ZWnJfV1m
HpCZD88yXv2aCbZ9SpZENrKMhz8pM7ecwvm5Z5Yu9f/J7i9C3JiZv8lmHHlOo1U7M/fNzJ9l5ncy
8y3NcdzW7GPsWC/McgFl1T79Tty1zbkdey9/1a/9SJLmrhyS3Haog2+xuPYbLkmaWJapypaO+/99
RZYk+UtZkvRlLc/1o4V8zNIsBd9e2+xjSWaelZkPyJZkNkvr+J9yxZRiR2dpzb9pgniWZWmtfmaz
jSl1+87MzTLzjZm5YZYu8N0c/5LM/EH2adqzLF3239Ic77LsfAHk6X38vfjfce/3hVla0m/OzG9P
5VxKkjQmhyS3HergWyyu/YZLkjrLMld1u0rjl2ZJkMebypzdY69blrcfc31xZh6Ypfr5Zc16/84y
pvvSLK31Kzdxzs/M3TJzn8zcKMvc6Isy8x1N/K0t9q3F4zIzj8zMRdM4R9Hs8zd5+wsU7Y5xzJLM
/Eqf36v5mfniDuf/tiwXKzaZ/p7+u7/7tdnXkzLzbc3jN6bJuSRpinJIctt5tU+UJGm0Zea9gS9T
5q0e707AVmOrtiwPbj9v99h9jluvnXOAoyjzkt8F2Bz4IWWu9AuAPwD3A+4PfBt4IrAoM1dqHm8J
HBcRl0TEdRGxBPgw8BLg2pb9fB54H/CfJqa7AetO9Tw1c4pfRpljfMJVWx7fAHx6qvvsIClzxb+A
Mq/6dcBy4G+UOefvHxEX9XF/y9os+yrwW+Bi4P2Ucy9J0twyLFcVWiyufc4kSXeUpXv2+S3/r8e3
hHcaT96p8vhkrejLshRee1u2aWXNMqZ5zcx8dGY+Y9xz8zJz48xc0OFY1spSnO6vWVrLb83M72Xm
plnGlr+rT+dsft6xQF67c/HHzHzeLLyH62bmTjlD470z88F5x94H2byHH2yW35yZj5rpY5UkjZ4c
ktw2OgVPuVo+LNaOiGtqByFJWiEzVwF+TWmdhtIS2+5z5yZg5ea564BDgAcBWzfPt76m0zYAbgPe
Anw6Im6eIK4Dgb2At0TERZn5BOBfzXa/ACwF/gocD4xVN39vRNyamWsBNwOPBfZvYrwAeDKwKCJu
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
I+L26rCSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS
JEmSJEnD8P8BuRQKl4e1/1QAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjQtMDQtMDRUMDg6NDk6MzUr
MDI6MDANFd99AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDI0LTA0LTA0VDA4OjQ5OjM1KzAyOjAwfEhn
wQAAAABJRU5ErkJggg==" />
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,17 +1,17 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_581_1764)">
<g filter="url(#filter0_d_266_274)">
<path d="M22.65 2.1315L4.93388 10.2774C3.72482 10.8564 3.73182 11.6606 4.71205 12.0192L9.26049 13.7111L19.7843 5.79398C20.2819 5.43298 20.7365 5.62718 20.3628 6.02274L11.8365 15.198H11.8345L11.8365 15.1992L11.5227 20.7894C11.9824 20.7894 12.1852 20.538 12.443 20.2414L14.6523 17.6798L19.2477 21.7271C20.095 22.2835 20.7036 21.9975 20.9144 20.7918L23.931 3.84003C24.2398 2.36384 23.4584 1.69544 22.65 2.1315Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_581_1764" x="0" y="0" width="28" height="28" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<filter id="filter0_d_266_274" x="0" y="0" width="28" height="28" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_581_1764"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_581_1764" result="shape"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_266_274"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_266_274" result="shape"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.2748 5.00001L3.27479 24C3.09955 24.3035 3.00715 24.6478 3.00684 24.9983C3.00653 25.3487 3.09832 25.6931 3.27302 25.997C3.44773 26.3008 3.6992 26.5534 4.00226 26.7294C4.30532 26.9055 4.64932 26.9988 4.99979 27H26.9998C27.3503 26.9988 27.6943 26.9055 27.9973 26.7294C28.3004 26.5534 28.5519 26.3008 28.7266 25.997C28.9013 25.6931 28.9931 25.3487 28.9927 24.9983C28.9924 24.6478 28.9 24.3035 28.7248 24L17.7248 5.00001C17.5509 4.6961 17.2997 4.44353 16.9968 4.26787C16.6939 4.09221 16.35 3.99969 15.9998 3.99969C15.6496 3.99969 15.3057 4.09221 15.0028 4.26787C14.6998 4.44353 14.4487 4.6961 14.2748 5.00001Z" fill="#FF613A"/>
<path d="M16 13V18" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 24C16.8284 24 17.5 23.3284 17.5 22.5C17.5 21.6716 16.8284 21 16 21C15.1716 21 14.5 21.6716 14.5 22.5C14.5 23.3284 15.1716 24 16 24Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1001 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.8008 5.98567C19.8008 5.98567 19.6055 4.61327 19.0039 4.01066C18.2422 3.21755 17.3906 3.21366 17 3.16701C14.2031 2.96484 10.0039 2.96484 10.0039 2.96484H9.99609C9.99609 2.96484 5.79687 2.96484 3 3.16701C2.60938 3.21366 1.75781 3.21755 0.996094 4.01066C0.394531 4.61327 0.203125 5.98567 0.203125 5.98567C0.203125 5.98567 0 7.59911 0 9.20867V10.7171C0 12.3267 0.199219 13.9401 0.199219 13.9401C0.199219 13.9401 0.394531 15.3125 0.992187 15.9151C1.75391 16.7082 2.75391 16.681 3.19922 16.7666C4.80078 16.9182 10 16.9648 10 16.9648C10 16.9648 14.2031 16.9571 17 16.7588C17.3906 16.7121 18.2422 16.7082 19.0039 15.9151C19.6055 15.3125 19.8008 13.9401 19.8008 13.9401C19.8008 13.9401 20 12.3306 20 10.7171V9.20867C20 7.59911 19.8008 5.98567 19.8008 5.98567ZM7.93359 12.5483V6.95374L13.3359 9.76073L7.93359 12.5483Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 940 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 228 KiB

View File

@@ -0,0 +1,4 @@
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.29785 17L17.2979 1" stroke="#170F49" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.2979 17L1.29785 1" stroke="#170F49" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -1,17 +1,17 @@
<svg width="20" height="28" viewBox="0 0 20 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_581_1766)">
<path d="M15.2137 13.2494L15.8362 9.63H11.9452V7.28125C11.9452 6.29125 12.4885 5.32562 14.2313 5.32562H16V2.24438C16 2.24438 14.3951 2 12.8603 2C9.6562 2 7.56191 3.73375 7.56191 6.87187V9.63062H4V13.25H7.56191V22H11.9452V13.25L15.2137 13.2494Z" fill="white"/>
<g filter="url(#filter0_d_3422_27624)">
<path d="M15.2137 13.2494L15.8362 9.63H11.9452V7.28125C11.9452 6.29125 12.4885 5.32563 14.2313 5.32563H16V2.24438C16 2.24438 14.3951 2 12.8603 2C9.6562 2 7.56191 3.73375 7.56191 6.87188V9.63062H4V13.25H7.56191V22H11.9452V13.25L15.2137 13.2494Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_581_1766" x="0" y="0" width="20" height="28" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<filter id="filter0_d_3422_27624" x="0" y="0" width="20" height="28" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_581_1766"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_581_1766" result="shape"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3422_27624"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3422_27624" result="shape"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 984 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.2344 0H10.683V13.6232C10.683 15.2464 9.31705 16.5797 7.61721 16.5797C5.91737 16.5797 4.55143 15.2464 4.55143 13.6232C4.55143 12.029 5.88702 10.7246 7.52616 10.6667V7.24639C3.914 7.30433 1 10.1159 1 13.6232C1 17.1594 3.9747 20 7.64757 20C11.3204 20 14.2951 17.1304 14.2951 13.6232V6.63767C15.6307 7.56522 17.2698 8.11594 19 8.14494V4.72464C16.3289 4.63768 14.2344 2.55072 14.2344 0Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 514 B

3
static/img/svg/vk.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.40589 1.40589C0 2.81177 0 5.0745 0 9.6V10.4C0 14.9255 0 17.1882 1.40589 18.5941C2.81177 20 5.0745 20 9.6 20H10.4C14.9255 20 17.1882 20 18.5941 18.5941C20 17.1882 20 14.9255 20 10.4V9.6C20 5.0745 20 2.81177 18.5941 1.40589C17.1882 0 14.9255 0 10.4 0H9.6C5.0745 0 2.81177 0 1.40589 1.40589ZM3.37505 6.08337C3.48338 11.2834 6.08338 14.4084 10.6417 14.4084H10.9001V11.4334C12.5751 11.6 13.8417 12.825 14.35 14.4084H16.7167C16.0667 12.0417 14.3583 10.7334 13.2916 10.2334C14.3583 9.61671 15.8583 8.11671 16.2166 6.08337H14.0666C13.5999 7.73337 12.2167 9.23337 10.9001 9.37504V6.08337H8.75V11.85C7.41667 11.5167 5.73337 9.90004 5.65837 6.08337H3.37505Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 818 B

View File

@@ -20,7 +20,8 @@ function SendLoginForm(el){
data: formData,
success: function(data){
location.href = `/profile/page/dashboard/`
location.href = data.redirect_url//`/profile/page/dashboard/`
window.sessionStorage.removeItem('mailingSubscribeRequired')
window.sessionStorage.removeItem('email')
@@ -29,6 +30,8 @@ function SendLoginForm(el){
},
error: function (data, exception){
document.querySelector(".login").innerHTML = data.responseJSON.html

View File

@@ -1,17 +1,37 @@
function update_count_unread_messages (data) {
let selected_btn = document.querySelector(".selected")
if (selected_btn.dataset['ajaxUrl'] !== 'chats'){
let list_unrd = document.querySelectorAll(".unredmessages_value_text")
let list_unrd_parent = document.querySelectorAll(".icon_unread_messages")
let i = 0
for (i;i < list_unrd.length;i++){
if (!list_unrd_parent[i].classList.contains("showed")){
list_unrd_parent[i].classList.toggle("showed")
function update_count_unread_messages(data) {
let selected_btn = document.querySelector(".selected");
let list_unrd = document.querySelectorAll(".unredmessages_value_text");
let list_unrd_parent = document.querySelectorAll(".icon_unread_messages");
if (selected_btn.dataset['ajaxUrl'] !== 'chats') {
for (let i = 0; i < list_unrd.length; i++) {
let current_count = parseInt(list_unrd[i].innerHTML);
if (data.unread_msgs_count) {
list_unrd[i].innerHTML = (current_count + 1).toString();
}
if (current_count === 0) {
list_unrd_parent[i].classList.toggle("showed");
}
}
} else if(data.unanswered_msgs_count || data.unanswered_msgs_count === 0){
for (let i = 0; i < list_unrd.length; i++){
list_unrd[i].innerHTML = data.unanswered_msgs_count.toString()
if(data.unanswered_msgs_count === 0){
list_unrd_parent[i].classList.remove("showed");
}
list_unrd[i].innerHTML = data.unread_msgs_count.toString()
}
}
// else {
// for (let i = 0; i < list_unrd.length; i++) {
// let current_count = parseInt(list_unrd[i].innerHTML);
// if (current_count === 0 || selected_btn.dataset['ajaxUrl'] !== 'chats') {
// list_unrd_parent[i].classList.toggle("showed");
// }
// }
// }
}
function play_required_beep (data,beep) {

View File

@@ -4,7 +4,7 @@ separator_iterator = 1
iterator_f_check = 1
paging_iterator = 1
function load_routes (el,news=null,incrase) {
function load_routes (el,news=null,incrase,owner_type) {
let local_page_iterator = standart_page_iterator
if (!news){
local_page_iterator = page_iterator
@@ -37,7 +37,9 @@ function load_routes (el,news=null,incrase) {
// if (new_el_dataset){
// let number_last_route = new_el_dataset['numberOfRoute']
// let number_last_route = el.dataset['lastRoute']
if (owner_type){
data_d['owner_type'] = owner_type
}
$.ajax({
headers: { "X-CSRFToken": $('input[name=csrfmiddlewaretoken]').val() },

View File

@@ -134,7 +134,7 @@ function ajax_for_filter (data_d,get_url){
} else {
old_page_iterator = 1
let new_search = document.querySelector(`.page_paging_elements_${page_iterator}`)
if (new_search){
if (new_twsearch){
new_search.innerHTML = data.html
} else {
let new_page_routes1 = document.createElement("div")

View File

@@ -4,16 +4,21 @@ function show_inf_carrier (el) {
let form = el.closest("div[name='form_carrier']")
let finish_form = form.children
let form_open = finish_form[0].children
let ph_1 = form_open[0]
let em_1 = form_open[1]
let img_1 = form_open[0]
let nm_1 = form_open[1]
let ph_1 = form_open[2]
let em_1 = form_open[3]
let btn_open_chat = finish_form[1]
let els = [ph_1.querySelectorAll(".el_for_open_el")[0],em_1.querySelectorAll(".el_for_open_el")[0]]
let els = [img_1, nm_1, ph_1.querySelectorAll(".el_for_open_el")[0],em_1.querySelectorAll(".el_for_open_el")[0]]
// let iter_lists = 0
for (let i = 0;i < els.length;i++){
els[i].classList.toggle("active")
// iter_lists++
}
// let btn_open_chat = finish_form[3]
// ph_1.parentElement.children[2].classList.toggle("active");

View File

@@ -41,7 +41,7 @@ window.onload = function (){
// }
let chats = document.querySelector(`[data-ajax-url='chats']`)
if (chats){
deleteMarkerMessages()
// deleteMarkerMessages()
}
}
}
@@ -227,6 +227,14 @@ function checkStateCookie () {
if (!window.document.cookie.includes("allow_cookie=true")){
document.querySelector(".cookie_block").classList.add("show")
}
if (!window.document.cookie.includes("popup_show=false")){
setTimeout(() => {
document.querySelector('.popup_wrapper').classList.add('show');
}, 15000);
}
if(!window.document.cookie.includes("first_authorization=true") && window.document.getElementById('authenticated_img')){
document.querySelector(".popup_wrapper").classList.add("show")
}
if (window.document.cookie.includes("twb_new_messages=true")){
if (getInfoAboutUser('screen_width') < 800) {
let marker_new_messages = document.querySelector(".marker_messages_mobile");
@@ -258,12 +266,22 @@ function getCsrfCookie () {
return csrf
}
function setCokie (days,name,val) {
function setCokie (days,name,val, url = null) {
let date = new Date();
// let days = 182;
date.setTime(+ date + (days * 86400000));
window.document.cookie = `${name}=${val}` + "; expires=" + date.toGMTString() + "; path=/";
document.querySelector(".cookie_block").classList.remove("show")
if(name === 'allow_cookie' ){
document.querySelector(".cookie_block").classList.remove("show")
} else if(name === 'popup_show'){
document.querySelector(".popup_wrapper").classList.remove("show")
if(url){
window.location.href = url}
} else if (name === 'first_authorization'){
document.querySelector(".popup_wrapper").classList.remove("show")
}
// return value;
}

View File

@@ -30,20 +30,48 @@ const showNotAllowed = (message) => {
button.setAttribute('disabled', 'true');
};
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
return outputData;
// urlB64ToUint8Array is a magic function that will encode the base64 public key
// to Array buffer which is needed by the subscription option
// function urlB64ToUint8Array2(base64String) {
// const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
// const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
// const rawData = atob(base64)
// const outputArray = new Uint8Array(rawData.length)
// for (let i = 0; i < rawData.length; ++i) {
// outputArray[i] = rawData.charCodeAt(i)
// }
// return outputArray
// }
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/')
;
const rawData = window.atob(base64);
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}
const subscribe = async (reg) => {
// function urlB64ToUint8Array(base64String) {
// const padding = '='.repeat((4 - base64String.length % 4) % 4);
// const base64 = (base64String + padding)
// .replace(/\-/g, '+')
// .replace(/_/g, '/');
//
// const rawData = window.atob(base64);
// const outputArray = new Uint8Array(rawData.length);
// const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
//
// return outputData;
// }
const push_subscribe = async (reg) => {
const subscription = await reg.pushManager.getSubscription();
if (subscription) {
sendSubData(subscription);
@@ -55,7 +83,7 @@ const subscribe = async (reg) => {
const options = {
userVisibleOnly: true,
// if key exists, create applicationServerKey property
...(key && {applicationServerKey: urlB64ToUint8Array(key)})
...(key && {applicationServerKey: urlBase64ToUint8Array(key)})
};
const sub = await reg.pushManager.subscribe(options);
@@ -65,7 +93,7 @@ const subscribe = async (reg) => {
const sendSubData = async (subscription) => {
const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
const data = {
status_type: 'subscribe',
status_type: 'push_subscribe',
subscription: subscription.toJSON(),
browser: browser,
user_agent: browser,

View File

@@ -16,10 +16,20 @@ function SendRegistrationForm(el){
// enctype: 'json',
data: formData,
success: function(data){
location.href = `/profile/page/dashboard/`
document.querySelector('.info_text').classList.add('show')
// location.href = `/profile/page/dashboard/`
window.sessionStorage.removeItem('mailingSubscribeRequired')
window.sessionStorage.removeItem('email')
if(typeof ym === 'function'){
ym(97070898,'reachGoal','Registration')
return true;
}else {
console.log('Скрипт Яндекс.Метрики не найден. Статистика не будет отправлена.');
}
},
error: function (data, exception){
document.querySelector(".register").innerHTML = data.responseJSON.html

Some files were not shown because too many files have changed in this diff Show More