From 0c275efea140460b12e136d276637fc765f77fd8 Mon Sep 17 00:00:00 2001 From: "anurag.r" Date: Wed, 7 Jan 2026 12:09:20 +0530 Subject: [PATCH] base setup --- B42/__init__.py | 0 B42/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 151 bytes B42/__pycache__/settings.cpython-312.pyc | Bin 0 -> 3054 bytes B42/__pycache__/urls.cpython-312.pyc | Bin 0 -> 1847 bytes B42/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 630 bytes B42/asgi.py | 16 + B42/settings.py | 141 +++++ B42/urls.py | 33 + B42/wsgi.py | 16 + at_django_boilerplate/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 184 bytes at_django_boilerplate/accounts/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 193 bytes .../__pycache__/admin.cpython-312.pyc | Bin 0 -> 1551 bytes .../accounts/__pycache__/apps.cpython-312.pyc | Bin 0 -> 525 bytes .../__pycache__/auth_backends.cpython-312.pyc | Bin 0 -> 2354 bytes .../__pycache__/forms.cpython-312.pyc | Bin 0 -> 9639 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 9713 bytes .../__pycache__/tokens.cpython-312.pyc | Bin 0 -> 887 bytes .../accounts/__pycache__/urls.cpython-312.pyc | Bin 0 -> 4223 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 6921 bytes .../__pycache__/views.cpython-312.pyc | Bin 0 -> 19748 bytes at_django_boilerplate/accounts/admin.py | 29 + .../accounts/api/__init__.py | 0 .../api/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 197 bytes .../api/__pycache__/v1.cpython-312.pyc | Bin 0 -> 11120 bytes at_django_boilerplate/accounts/api/v1.py | 273 ++++++++ at_django_boilerplate/accounts/apps.py | 6 + .../accounts/auth_backends.py | 51 ++ at_django_boilerplate/accounts/forms.py | 165 +++++ .../accounts/management/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 204 bytes .../accounts/management/commands/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 213 bytes .../seed_admin_account.cpython-312.pyc | Bin 0 -> 1421 bytes .../management/commands/seed_admin_account.py | 19 + .../accounts/migrations/0001_initial.py | 50 ++ .../migrations/0002_alter_customuser_email.py | 19 + .../accounts/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 4227 bytes ...002_alter_customuser_email.cpython-312.pyc | Bin 0 -> 938 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 204 bytes at_django_boilerplate/accounts/models.py | 178 ++++++ .../accounts/templates/change_password.html | 75 +++ .../accounts/templates/login.html | 596 ++++++++++++++++++ .../accounts/templates/otp/otp_email.html | 31 + .../accounts/templates/profile/profile.html | 66 ++ .../templates/profile/update_profile.html | 21 + .../profile/update_profile_picture.html | 14 + .../templates/registration/create_user.html | 21 + .../templates/registration/signup.html | 316 ++++++++++ .../templates/registration/verify_email.html | 15 + .../registration/verify_email_complete.html | 14 + .../registration/verify_email_confirm.html | 18 + .../registration/verify_email_done.html | 12 + .../registration/verify_email_message.html | 20 + .../templates/registration/verify_otp.html | 49 ++ .../templates/reset/password_reset.html | 62 ++ .../reset/password_reset_complete.html | 9 + .../reset/password_reset_confirm.html | 42 ++ .../templates/reset/password_reset_done.html | 26 + .../templates/reset/password_reset_email.html | 13 + .../reset/password_reset_email_html.html | 24 + .../reset/password_reset_subject.txt | 1 + .../accounts/templates/user_list.html | 25 + .../accounts/templatetags/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 206 bytes .../__pycache__/custom_tags.cpython-312.pyc | Bin 0 -> 602 bytes .../__pycache__/forms_tags.cpython-312.pyc | Bin 0 -> 656 bytes .../accounts/templatetags/custom_tags.py | 7 + .../accounts/templatetags/forms_tags.py | 8 + at_django_boilerplate/accounts/tests.py | 3 + at_django_boilerplate/accounts/tokens.py | 10 + at_django_boilerplate/accounts/urls.py | 57 ++ at_django_boilerplate/accounts/utils.py | 189 ++++++ at_django_boilerplate/accounts/views.py | 415 ++++++++++++ .../backend_admin/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 198 bytes .../__pycache__/admin.cpython-312.pyc | Bin 0 -> 386 bytes .../__pycache__/apps.cpython-312.pyc | Bin 0 -> 539 bytes .../__pycache__/forms.cpython-312.pyc | Bin 0 -> 722 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 2289 bytes .../__pycache__/urls.cpython-312.pyc | Bin 0 -> 1157 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 286 bytes .../__pycache__/views.cpython-312.pyc | Bin 0 -> 8980 bytes at_django_boilerplate/backend_admin/admin.py | 5 + at_django_boilerplate/backend_admin/apps.py | 6 + at_django_boilerplate/backend_admin/forms.py | 38 ++ .../backend_admin/migrations/0001_initial.py | 38 ++ .../backend_admin/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 2874 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 209 bytes at_django_boilerplate/backend_admin/models.py | 30 + .../templates/admin_dashboard.html | 25 + .../templates/appointment_list.html | 188 ++++++ .../backend_admin/templates/contact_list.html | 239 +++++++ .../backend_admin/templates/seo_config.html | 22 + .../backend_admin/templates/subscriber.html | 60 ++ at_django_boilerplate/backend_admin/tests.py | 3 + at_django_boilerplate/backend_admin/urls.py | 21 + at_django_boilerplate/backend_admin/utils.py | 49 ++ at_django_boilerplate/backend_admin/views.py | 156 +++++ at_django_boilerplate/blogs/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 190 bytes .../blogs/__pycache__/admin.cpython-312.pyc | Bin 0 -> 639 bytes .../blogs/__pycache__/apps.cpython-312.pyc | Bin 0 -> 516 bytes .../blogs/__pycache__/models.cpython-312.pyc | Bin 0 -> 1576 bytes .../blogs/__pycache__/urls.cpython-312.pyc | Bin 0 -> 578 bytes .../blogs/__pycache__/views.cpython-312.pyc | Bin 0 -> 2063 bytes at_django_boilerplate/blogs/admin.py | 6 + at_django_boilerplate/blogs/apps.py | 7 + .../blogs/generate_dummy_blogs.py | 45 ++ .../blogs/migrations/0001_initial.py | 34 + .../blogs/migrations/0002_alter_blog_id.py | 19 + .../0003_alter_blog_options_and_more.py | 23 + .../0004_alter_blog_options_and_more.py | 21 + .../blogs/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 2346 bytes .../0002_alter_blog_id.cpython-312.pyc | Bin 0 -> 854 bytes ...lter_blog_options_and_more.cpython-312.pyc | Bin 0 -> 1162 bytes ...lter_blog_options_and_more.cpython-312.pyc | Bin 0 -> 792 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 201 bytes at_django_boilerplate/blogs/models.py | 17 + .../blogs/templates/blog_detail.html | 45 ++ .../blogs/templates/blogs_list.html | 233 +++++++ at_django_boilerplate/blogs/tests.py | 3 + at_django_boilerplate/blogs/urls.py | 8 + at_django_boilerplate/blogs/views.py | 29 + .../communications/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 199 bytes .../__pycache__/admin.cpython-312.pyc | Bin 0 -> 383 bytes .../__pycache__/apps.cpython-312.pyc | Bin 0 -> 543 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 3245 bytes .../__pycache__/urls.cpython-312.pyc | Bin 0 -> 604 bytes .../__pycache__/views.cpython-312.pyc | Bin 0 -> 8331 bytes at_django_boilerplate/communications/admin.py | 6 + at_django_boilerplate/communications/apps.py | 6 + .../communications/migrations/0001_initial.py | 50 ++ .../migrations/0002_feedbackmodel_is_junk.py | 18 + ...ointmentmodel_id_alter_feedbackmodel_id.py | 24 + .../communications/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 3738 bytes ...0002_feedbackmodel_is_junk.cpython-312.pyc | Bin 0 -> 793 bytes ..._id_alter_feedbackmodel_id.cpython-312.pyc | Bin 0 -> 1100 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 210 bytes .../communications/models.py | 63 ++ .../templates/book_appointment.html | 437 +++++++++++++ .../templates/contact_us_form.html | 518 +++++++++++++++ at_django_boilerplate/communications/tests.py | 3 + at_django_boilerplate/communications/urls.py | 7 + at_django_boilerplate/communications/views.py | 190 ++++++ at_django_boilerplate/core/__init__.py | 0 .../core/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 189 bytes .../core/__pycache__/admin.cpython-312.pyc | Bin 0 -> 653 bytes .../core/__pycache__/apps.cpython-312.pyc | Bin 0 -> 498 bytes .../core/__pycache__/models.cpython-312.pyc | Bin 0 -> 2057 bytes .../__pycache__/scheduler.cpython-312.pyc | Bin 0 -> 175 bytes .../core/__pycache__/urls.cpython-312.pyc | Bin 0 -> 2279 bytes .../core/__pycache__/views.cpython-312.pyc | Bin 0 -> 10065 bytes at_django_boilerplate/core/admin.py | 7 + at_django_boilerplate/core/apps.py | 7 + .../core/context_processors.py | 14 + .../core/logging/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 197 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 1169 bytes .../logging/__pycache__/views.cpython-312.pyc | Bin 0 -> 3850 bytes at_django_boilerplate/core/logging/config.py | 46 ++ at_django_boilerplate/core/logging/views.py | 128 ++++ .../core/middleware/__init__.py | 0 .../core/middleware/block_ips.py | 67 ++ .../core/middleware/country_detection.py | 12 + .../core/migrations/0001_initial.py | 41 ++ .../core/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 1790 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 200 bytes at_django_boilerplate/core/models.py | 30 + at_django_boilerplate/core/scheduler.py | 96 +++ .../core/templates/company/about_us.html | 321 ++++++++++ .../core/templates/company/blog.html | 0 .../core/templates/company/careers.html | 22 + .../core/templates/company/faq.html | 301 +++++++++ .../core/templates/company/ourcoreteam.html | 393 ++++++++++++ .../core/templates/company/subscriber.html | 65 ++ .../core/templates/defaults/400.html | 5 + .../core/templates/defaults/403.html | 97 +++ .../core/templates/defaults/404.html | 24 + .../core/templates/defaults/500.html | 24 + .../core/templates/legal/cookiepolicy.html | 1 + .../core/templates/legal/privacy.html | 344 ++++++++++ .../core/templates/legal/toc.html | 221 +++++++ .../core/templates/logging/log_detail.html | 67 ++ .../core/templates/logging/logs_list.html | 43 ++ at_django_boilerplate/core/tests.py | 3 + at_django_boilerplate/core/urls.py | 32 + at_django_boilerplate/core/views.py | 200 ++++++ .../history_tracker/__init__.py | 0 .../history_tracker/admin.py | 3 + at_django_boilerplate/history_tracker/apps.py | 6 + .../history_tracker/migrations/__init__.py | 0 .../history_tracker/models.py | 30 + .../history_tracker/tests.py | 3 + .../history_tracker/utils.py | 10 + .../history_tracker/views.py | 3 + .../notification/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 197 bytes .../__pycache__/admin.cpython-312.pyc | Bin 0 -> 241 bytes .../__pycache__/apps.cpython-312.pyc | Bin 0 -> 537 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 1672 bytes .../__pycache__/urls.cpython-312.pyc | Bin 0 -> 738 bytes .../__pycache__/views.cpython-312.pyc | Bin 0 -> 1824 bytes at_django_boilerplate/notification/admin.py | 3 + at_django_boilerplate/notification/apps.py | 6 + .../notification/context_processors.py | 10 + .../notification/migrations/0001_initial.py | 31 + .../notification/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 1750 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 208 bytes at_django_boilerplate/notification/models.py | 22 + .../templates/notifications_list.html | 58 ++ at_django_boilerplate/notification/tests.py | 3 + at_django_boilerplate/notification/urls.py | 10 + at_django_boilerplate/notification/utils.py | 8 + at_django_boilerplate/notification/views.py | 27 + .../user_activity/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 198 bytes .../__pycache__/admin.cpython-312.pyc | Bin 0 -> 372 bytes .../__pycache__/apps.cpython-312.pyc | Bin 0 -> 539 bytes .../context_processors.cpython-312.pyc | Bin 0 -> 769 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 7604 bytes .../__pycache__/urls.cpython-312.pyc | Bin 0 -> 520 bytes .../__pycache__/views.cpython-312.pyc | Bin 0 -> 22894 bytes at_django_boilerplate/user_activity/admin.py | 5 + at_django_boilerplate/user_activity/apps.py | 6 + .../user_activity/context_processors.py | 10 + .../user_activity/middleware/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 209 bytes .../__pycache__/get_web_user.cpython-312.pyc | Bin 0 -> 4225 bytes .../user_activity/middleware/get_web_user.py | 85 +++ .../user_activity/migrations/0001_initial.py | 66 ++ ...nt_dailyuserstats_visitormodel_and_more.py | 125 ++++ .../user_activity/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 3773 bytes ...tats_visitormodel_and_more.cpython-312.pyc | Bin 0 -> 5357 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 209 bytes at_django_boilerplate/user_activity/models.py | 93 +++ .../templates/active_sessions.html | 535 ++++++++++++++++ .../user_activity/templatetags/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 211 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 809 bytes .../user_activity/templatetags/utils.py | 10 + at_django_boilerplate/user_activity/tests.py | 3 + at_django_boilerplate/user_activity/urls.py | 11 + at_django_boilerplate/user_activity/views.py | 517 +++++++++++++++ at_django_boilerplate/utils/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 190 bytes .../company_matching.cpython-312.pyc | Bin 0 -> 6730 bytes .../__pycache__/custom_fields.cpython-312.pyc | Bin 0 -> 8527 bytes .../encryption_utils.cpython-312.pyc | Bin 0 -> 1804 bytes .../__pycache__/geolocation.cpython-312.pyc | Bin 0 -> 2034 bytes .../__pycache__/hash_utils.cpython-312.pyc | Bin 0 -> 1098 bytes .../utils/__pycache__/mixins.cpython-312.pyc | Bin 0 -> 2241 bytes at_django_boilerplate/utils/calendar_utils.py | 2 + .../utils/company_matching.py | 175 +++++ at_django_boilerplate/utils/custom_fields.py | 163 +++++ .../utils/encryption_utils.py | 24 + at_django_boilerplate/utils/geolocation.py | 57 ++ at_django_boilerplate/utils/hash_utils.py | 18 + at_django_boilerplate/utils/mixins.py | 32 + db.sqlite3 | Bin 0 -> 307200 bytes manage.py | 22 + requirements.txt | 36 ++ static/images/AppStore.png | Bin 0 -> 55073 bytes static/images/googleplay.png | Bin 0 -> 67670 bytes static/images/logo.svg | 7 + templates/includes/footer.html | 149 +++++ templates/includes/public_navbar.html | 30 + templates/index.html | 2 + templates/public_base.html | 354 +++++++++++ 278 files changed, 11228 insertions(+) create mode 100644 B42/__init__.py create mode 100644 B42/__pycache__/__init__.cpython-312.pyc create mode 100644 B42/__pycache__/settings.cpython-312.pyc create mode 100644 B42/__pycache__/urls.cpython-312.pyc create mode 100644 B42/__pycache__/wsgi.cpython-312.pyc create mode 100644 B42/asgi.py create mode 100644 B42/settings.py create mode 100644 B42/urls.py create mode 100644 B42/wsgi.py create mode 100755 at_django_boilerplate/__init__.py create mode 100644 at_django_boilerplate/__pycache__/__init__.cpython-312.pyc create mode 100755 at_django_boilerplate/accounts/__init__.py create mode 100644 at_django_boilerplate/accounts/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/__pycache__/admin.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/__pycache__/apps.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/__pycache__/auth_backends.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/__pycache__/forms.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/__pycache__/models.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/__pycache__/tokens.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/__pycache__/urls.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/__pycache__/utils.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/__pycache__/views.cpython-312.pyc create mode 100755 at_django_boilerplate/accounts/admin.py create mode 100755 at_django_boilerplate/accounts/api/__init__.py create mode 100644 at_django_boilerplate/accounts/api/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/api/__pycache__/v1.cpython-312.pyc create mode 100755 at_django_boilerplate/accounts/api/v1.py create mode 100755 at_django_boilerplate/accounts/apps.py create mode 100755 at_django_boilerplate/accounts/auth_backends.py create mode 100755 at_django_boilerplate/accounts/forms.py create mode 100755 at_django_boilerplate/accounts/management/__init__.py create mode 100644 at_django_boilerplate/accounts/management/__pycache__/__init__.cpython-312.pyc create mode 100755 at_django_boilerplate/accounts/management/commands/__init__.py create mode 100644 at_django_boilerplate/accounts/management/commands/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/management/commands/__pycache__/seed_admin_account.cpython-312.pyc create mode 100755 at_django_boilerplate/accounts/management/commands/seed_admin_account.py create mode 100644 at_django_boilerplate/accounts/migrations/0001_initial.py create mode 100644 at_django_boilerplate/accounts/migrations/0002_alter_customuser_email.py create mode 100755 at_django_boilerplate/accounts/migrations/__init__.py create mode 100644 at_django_boilerplate/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/migrations/__pycache__/0002_alter_customuser_email.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/migrations/__pycache__/__init__.cpython-312.pyc create mode 100755 at_django_boilerplate/accounts/models.py create mode 100755 at_django_boilerplate/accounts/templates/change_password.html create mode 100755 at_django_boilerplate/accounts/templates/login.html create mode 100755 at_django_boilerplate/accounts/templates/otp/otp_email.html create mode 100755 at_django_boilerplate/accounts/templates/profile/profile.html create mode 100755 at_django_boilerplate/accounts/templates/profile/update_profile.html create mode 100755 at_django_boilerplate/accounts/templates/profile/update_profile_picture.html create mode 100755 at_django_boilerplate/accounts/templates/registration/create_user.html create mode 100755 at_django_boilerplate/accounts/templates/registration/signup.html create mode 100755 at_django_boilerplate/accounts/templates/registration/verify_email.html create mode 100755 at_django_boilerplate/accounts/templates/registration/verify_email_complete.html create mode 100755 at_django_boilerplate/accounts/templates/registration/verify_email_confirm.html create mode 100755 at_django_boilerplate/accounts/templates/registration/verify_email_done.html create mode 100755 at_django_boilerplate/accounts/templates/registration/verify_email_message.html create mode 100755 at_django_boilerplate/accounts/templates/registration/verify_otp.html create mode 100755 at_django_boilerplate/accounts/templates/reset/password_reset.html create mode 100755 at_django_boilerplate/accounts/templates/reset/password_reset_complete.html create mode 100755 at_django_boilerplate/accounts/templates/reset/password_reset_confirm.html create mode 100755 at_django_boilerplate/accounts/templates/reset/password_reset_done.html create mode 100755 at_django_boilerplate/accounts/templates/reset/password_reset_email.html create mode 100755 at_django_boilerplate/accounts/templates/reset/password_reset_email_html.html create mode 100755 at_django_boilerplate/accounts/templates/reset/password_reset_subject.txt create mode 100755 at_django_boilerplate/accounts/templates/user_list.html create mode 100755 at_django_boilerplate/accounts/templatetags/__init__.py create mode 100644 at_django_boilerplate/accounts/templatetags/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/templatetags/__pycache__/custom_tags.cpython-312.pyc create mode 100644 at_django_boilerplate/accounts/templatetags/__pycache__/forms_tags.cpython-312.pyc create mode 100755 at_django_boilerplate/accounts/templatetags/custom_tags.py create mode 100755 at_django_boilerplate/accounts/templatetags/forms_tags.py create mode 100755 at_django_boilerplate/accounts/tests.py create mode 100755 at_django_boilerplate/accounts/tokens.py create mode 100755 at_django_boilerplate/accounts/urls.py create mode 100755 at_django_boilerplate/accounts/utils.py create mode 100755 at_django_boilerplate/accounts/views.py create mode 100755 at_django_boilerplate/backend_admin/__init__.py create mode 100644 at_django_boilerplate/backend_admin/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/backend_admin/__pycache__/admin.cpython-312.pyc create mode 100644 at_django_boilerplate/backend_admin/__pycache__/apps.cpython-312.pyc create mode 100644 at_django_boilerplate/backend_admin/__pycache__/forms.cpython-312.pyc create mode 100644 at_django_boilerplate/backend_admin/__pycache__/models.cpython-312.pyc create mode 100644 at_django_boilerplate/backend_admin/__pycache__/urls.cpython-312.pyc create mode 100644 at_django_boilerplate/backend_admin/__pycache__/utils.cpython-312.pyc create mode 100644 at_django_boilerplate/backend_admin/__pycache__/views.cpython-312.pyc create mode 100755 at_django_boilerplate/backend_admin/admin.py create mode 100755 at_django_boilerplate/backend_admin/apps.py create mode 100755 at_django_boilerplate/backend_admin/forms.py create mode 100644 at_django_boilerplate/backend_admin/migrations/0001_initial.py create mode 100755 at_django_boilerplate/backend_admin/migrations/__init__.py create mode 100644 at_django_boilerplate/backend_admin/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 at_django_boilerplate/backend_admin/migrations/__pycache__/__init__.cpython-312.pyc create mode 100755 at_django_boilerplate/backend_admin/models.py create mode 100755 at_django_boilerplate/backend_admin/templates/admin_dashboard.html create mode 100755 at_django_boilerplate/backend_admin/templates/appointment_list.html create mode 100755 at_django_boilerplate/backend_admin/templates/contact_list.html create mode 100755 at_django_boilerplate/backend_admin/templates/seo_config.html create mode 100755 at_django_boilerplate/backend_admin/templates/subscriber.html create mode 100755 at_django_boilerplate/backend_admin/tests.py create mode 100755 at_django_boilerplate/backend_admin/urls.py create mode 100755 at_django_boilerplate/backend_admin/utils.py create mode 100755 at_django_boilerplate/backend_admin/views.py create mode 100755 at_django_boilerplate/blogs/__init__.py create mode 100644 at_django_boilerplate/blogs/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/blogs/__pycache__/admin.cpython-312.pyc create mode 100644 at_django_boilerplate/blogs/__pycache__/apps.cpython-312.pyc create mode 100644 at_django_boilerplate/blogs/__pycache__/models.cpython-312.pyc create mode 100644 at_django_boilerplate/blogs/__pycache__/urls.cpython-312.pyc create mode 100644 at_django_boilerplate/blogs/__pycache__/views.cpython-312.pyc create mode 100755 at_django_boilerplate/blogs/admin.py create mode 100755 at_django_boilerplate/blogs/apps.py create mode 100755 at_django_boilerplate/blogs/generate_dummy_blogs.py create mode 100644 at_django_boilerplate/blogs/migrations/0001_initial.py create mode 100644 at_django_boilerplate/blogs/migrations/0002_alter_blog_id.py create mode 100644 at_django_boilerplate/blogs/migrations/0003_alter_blog_options_and_more.py create mode 100644 at_django_boilerplate/blogs/migrations/0004_alter_blog_options_and_more.py create mode 100755 at_django_boilerplate/blogs/migrations/__init__.py create mode 100644 at_django_boilerplate/blogs/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 at_django_boilerplate/blogs/migrations/__pycache__/0002_alter_blog_id.cpython-312.pyc create mode 100644 at_django_boilerplate/blogs/migrations/__pycache__/0003_alter_blog_options_and_more.cpython-312.pyc create mode 100644 at_django_boilerplate/blogs/migrations/__pycache__/0004_alter_blog_options_and_more.cpython-312.pyc create mode 100644 at_django_boilerplate/blogs/migrations/__pycache__/__init__.cpython-312.pyc create mode 100755 at_django_boilerplate/blogs/models.py create mode 100755 at_django_boilerplate/blogs/templates/blog_detail.html create mode 100755 at_django_boilerplate/blogs/templates/blogs_list.html create mode 100755 at_django_boilerplate/blogs/tests.py create mode 100755 at_django_boilerplate/blogs/urls.py create mode 100755 at_django_boilerplate/blogs/views.py create mode 100755 at_django_boilerplate/communications/__init__.py create mode 100644 at_django_boilerplate/communications/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/communications/__pycache__/admin.cpython-312.pyc create mode 100644 at_django_boilerplate/communications/__pycache__/apps.cpython-312.pyc create mode 100644 at_django_boilerplate/communications/__pycache__/models.cpython-312.pyc create mode 100644 at_django_boilerplate/communications/__pycache__/urls.cpython-312.pyc create mode 100644 at_django_boilerplate/communications/__pycache__/views.cpython-312.pyc create mode 100755 at_django_boilerplate/communications/admin.py create mode 100755 at_django_boilerplate/communications/apps.py create mode 100644 at_django_boilerplate/communications/migrations/0001_initial.py create mode 100644 at_django_boilerplate/communications/migrations/0002_feedbackmodel_is_junk.py create mode 100644 at_django_boilerplate/communications/migrations/0003_alter_appointmentmodel_id_alter_feedbackmodel_id.py create mode 100755 at_django_boilerplate/communications/migrations/__init__.py create mode 100644 at_django_boilerplate/communications/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 at_django_boilerplate/communications/migrations/__pycache__/0002_feedbackmodel_is_junk.cpython-312.pyc create mode 100644 at_django_boilerplate/communications/migrations/__pycache__/0003_alter_appointmentmodel_id_alter_feedbackmodel_id.cpython-312.pyc create mode 100644 at_django_boilerplate/communications/migrations/__pycache__/__init__.cpython-312.pyc create mode 100755 at_django_boilerplate/communications/models.py create mode 100755 at_django_boilerplate/communications/templates/book_appointment.html create mode 100755 at_django_boilerplate/communications/templates/contact_us_form.html create mode 100755 at_django_boilerplate/communications/tests.py create mode 100755 at_django_boilerplate/communications/urls.py create mode 100755 at_django_boilerplate/communications/views.py create mode 100755 at_django_boilerplate/core/__init__.py create mode 100644 at_django_boilerplate/core/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/core/__pycache__/admin.cpython-312.pyc create mode 100644 at_django_boilerplate/core/__pycache__/apps.cpython-312.pyc create mode 100644 at_django_boilerplate/core/__pycache__/models.cpython-312.pyc create mode 100644 at_django_boilerplate/core/__pycache__/scheduler.cpython-312.pyc create mode 100644 at_django_boilerplate/core/__pycache__/urls.cpython-312.pyc create mode 100644 at_django_boilerplate/core/__pycache__/views.cpython-312.pyc create mode 100755 at_django_boilerplate/core/admin.py create mode 100755 at_django_boilerplate/core/apps.py create mode 100755 at_django_boilerplate/core/context_processors.py create mode 100755 at_django_boilerplate/core/logging/__init__.py create mode 100644 at_django_boilerplate/core/logging/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/core/logging/__pycache__/config.cpython-312.pyc create mode 100644 at_django_boilerplate/core/logging/__pycache__/views.cpython-312.pyc create mode 100755 at_django_boilerplate/core/logging/config.py create mode 100755 at_django_boilerplate/core/logging/views.py create mode 100755 at_django_boilerplate/core/middleware/__init__.py create mode 100755 at_django_boilerplate/core/middleware/block_ips.py create mode 100755 at_django_boilerplate/core/middleware/country_detection.py create mode 100644 at_django_boilerplate/core/migrations/0001_initial.py create mode 100755 at_django_boilerplate/core/migrations/__init__.py create mode 100644 at_django_boilerplate/core/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 at_django_boilerplate/core/migrations/__pycache__/__init__.cpython-312.pyc create mode 100755 at_django_boilerplate/core/models.py create mode 100755 at_django_boilerplate/core/scheduler.py create mode 100755 at_django_boilerplate/core/templates/company/about_us.html create mode 100755 at_django_boilerplate/core/templates/company/blog.html create mode 100755 at_django_boilerplate/core/templates/company/careers.html create mode 100755 at_django_boilerplate/core/templates/company/faq.html create mode 100755 at_django_boilerplate/core/templates/company/ourcoreteam.html create mode 100755 at_django_boilerplate/core/templates/company/subscriber.html create mode 100755 at_django_boilerplate/core/templates/defaults/400.html create mode 100755 at_django_boilerplate/core/templates/defaults/403.html create mode 100755 at_django_boilerplate/core/templates/defaults/404.html create mode 100755 at_django_boilerplate/core/templates/defaults/500.html create mode 100755 at_django_boilerplate/core/templates/legal/cookiepolicy.html create mode 100755 at_django_boilerplate/core/templates/legal/privacy.html create mode 100755 at_django_boilerplate/core/templates/legal/toc.html create mode 100755 at_django_boilerplate/core/templates/logging/log_detail.html create mode 100755 at_django_boilerplate/core/templates/logging/logs_list.html create mode 100755 at_django_boilerplate/core/tests.py create mode 100755 at_django_boilerplate/core/urls.py create mode 100755 at_django_boilerplate/core/views.py create mode 100755 at_django_boilerplate/history_tracker/__init__.py create mode 100755 at_django_boilerplate/history_tracker/admin.py create mode 100755 at_django_boilerplate/history_tracker/apps.py create mode 100755 at_django_boilerplate/history_tracker/migrations/__init__.py create mode 100755 at_django_boilerplate/history_tracker/models.py create mode 100755 at_django_boilerplate/history_tracker/tests.py create mode 100755 at_django_boilerplate/history_tracker/utils.py create mode 100755 at_django_boilerplate/history_tracker/views.py create mode 100755 at_django_boilerplate/notification/__init__.py create mode 100644 at_django_boilerplate/notification/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/notification/__pycache__/admin.cpython-312.pyc create mode 100644 at_django_boilerplate/notification/__pycache__/apps.cpython-312.pyc create mode 100644 at_django_boilerplate/notification/__pycache__/models.cpython-312.pyc create mode 100644 at_django_boilerplate/notification/__pycache__/urls.cpython-312.pyc create mode 100644 at_django_boilerplate/notification/__pycache__/views.cpython-312.pyc create mode 100755 at_django_boilerplate/notification/admin.py create mode 100755 at_django_boilerplate/notification/apps.py create mode 100755 at_django_boilerplate/notification/context_processors.py create mode 100644 at_django_boilerplate/notification/migrations/0001_initial.py create mode 100755 at_django_boilerplate/notification/migrations/__init__.py create mode 100644 at_django_boilerplate/notification/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 at_django_boilerplate/notification/migrations/__pycache__/__init__.cpython-312.pyc create mode 100755 at_django_boilerplate/notification/models.py create mode 100755 at_django_boilerplate/notification/templates/notifications_list.html create mode 100755 at_django_boilerplate/notification/tests.py create mode 100755 at_django_boilerplate/notification/urls.py create mode 100755 at_django_boilerplate/notification/utils.py create mode 100755 at_django_boilerplate/notification/views.py create mode 100755 at_django_boilerplate/user_activity/__init__.py create mode 100644 at_django_boilerplate/user_activity/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/__pycache__/admin.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/__pycache__/apps.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/__pycache__/context_processors.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/__pycache__/models.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/__pycache__/urls.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/__pycache__/views.cpython-312.pyc create mode 100755 at_django_boilerplate/user_activity/admin.py create mode 100755 at_django_boilerplate/user_activity/apps.py create mode 100644 at_django_boilerplate/user_activity/context_processors.py create mode 100755 at_django_boilerplate/user_activity/middleware/__init__.py create mode 100644 at_django_boilerplate/user_activity/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/middleware/__pycache__/get_web_user.cpython-312.pyc create mode 100755 at_django_boilerplate/user_activity/middleware/get_web_user.py create mode 100644 at_django_boilerplate/user_activity/migrations/0001_initial.py create mode 100644 at_django_boilerplate/user_activity/migrations/0002_clickevent_dailyuserstats_visitormodel_and_more.py create mode 100755 at_django_boilerplate/user_activity/migrations/__init__.py create mode 100644 at_django_boilerplate/user_activity/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/migrations/__pycache__/0002_clickevent_dailyuserstats_visitormodel_and_more.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/migrations/__pycache__/__init__.cpython-312.pyc create mode 100755 at_django_boilerplate/user_activity/models.py create mode 100755 at_django_boilerplate/user_activity/templates/active_sessions.html create mode 100755 at_django_boilerplate/user_activity/templatetags/__init__.py create mode 100644 at_django_boilerplate/user_activity/templatetags/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/user_activity/templatetags/__pycache__/utils.cpython-312.pyc create mode 100755 at_django_boilerplate/user_activity/templatetags/utils.py create mode 100755 at_django_boilerplate/user_activity/tests.py create mode 100755 at_django_boilerplate/user_activity/urls.py create mode 100755 at_django_boilerplate/user_activity/views.py create mode 100755 at_django_boilerplate/utils/__init__.py create mode 100644 at_django_boilerplate/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 at_django_boilerplate/utils/__pycache__/company_matching.cpython-312.pyc create mode 100644 at_django_boilerplate/utils/__pycache__/custom_fields.cpython-312.pyc create mode 100644 at_django_boilerplate/utils/__pycache__/encryption_utils.cpython-312.pyc create mode 100644 at_django_boilerplate/utils/__pycache__/geolocation.cpython-312.pyc create mode 100644 at_django_boilerplate/utils/__pycache__/hash_utils.cpython-312.pyc create mode 100644 at_django_boilerplate/utils/__pycache__/mixins.cpython-312.pyc create mode 100755 at_django_boilerplate/utils/calendar_utils.py create mode 100755 at_django_boilerplate/utils/company_matching.py create mode 100755 at_django_boilerplate/utils/custom_fields.py create mode 100755 at_django_boilerplate/utils/encryption_utils.py create mode 100755 at_django_boilerplate/utils/geolocation.py create mode 100755 at_django_boilerplate/utils/hash_utils.py create mode 100755 at_django_boilerplate/utils/mixins.py create mode 100644 db.sqlite3 create mode 100755 manage.py create mode 100755 requirements.txt create mode 100644 static/images/AppStore.png create mode 100644 static/images/googleplay.png create mode 100644 static/images/logo.svg create mode 100755 templates/includes/footer.html create mode 100755 templates/includes/public_navbar.html create mode 100644 templates/index.html create mode 100644 templates/public_base.html diff --git a/B42/__init__.py b/B42/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/B42/__pycache__/__init__.cpython-312.pyc b/B42/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d392f0a9dd9255e750f869c2494cfb3cd5a462ac GIT binary patch literal 151 zcmX@j%ge<81n(ZiW`gL)AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdWu~8zpPQyU!Wflvf!Q$0bnxg(jAxfkWjY2A|g~Sj|g(w`t;U9tKJ;zCZo*sV; zHpgQf(-h#Jb0)@6Sd62HnCMetvQMGt_q3RTIRZT zQRcfypBA$yE9OuRRtsoB%%eP71pXzDA1!|uQfP4z70@N%FQCigu{a)A6uKV~m(Ue) z8Lf&1u;3E>FN3#hYEdkrtKtgaT=VscS77~)w-#5?uf(;}wGtzjAPlMMv8?wEhG5H5 z^gdyFhRN(~Z!rVYIL2LzO{Mm*j!oIZi0Pa%*U^}NLq?jSGsKcjE8x7&*aQ%m0C1b# zVmCKId=~&U!^DiD1GXkxilH;`vyK$drl|NMP>xeamNg*1E0>YcC9JPxVnWx@%2+Q0 zV%ahVN|%%;)|P#E>j*PFTU8lVAr@owUV9_!2ozz(WFE_^jmiI`%f!7YMgR6vp+vip z1KB!qc7iY{I>BAr#2X*3f3fxP$G_gb^NrduPQNi8t4KZRexaGz`fbmCA8k9mjV-yO z-Fl?2_dn6^n1gmH>lAz#wrl8?sdU(JRGsD7QyVm{%^Z3nShuXx0VYmyhDb0W5au@t z8W7|@NUY9ESa302?>dO{j%-OjS?U;YVh&Weepc>w4O_QBc0Roem+Y+1r*-7+L#!jo zcW~_&m{g-rocHGwAqg~FSGwc;A-|-@CfJnZuBALytW)Q``B1uHDZK!x6n8d)P-qH5 zDn5}-%#JS=5Lm&xU0Cd2(46hr$OW-xr{o3yY)Y#1rV`I1LuV`F>3^^iv&U9oxUb-o zDb~+sY>^>TrtRQh4@naEIwd&k#3V1Kh4=kjnUeZ+juWcVeRvGF3#HF~xogTAZVtQ> zCsT3>Xeg|0s>CS=2o~1-qzV#aQu1td++NxNnYhUbCvlg?DgUh{L0{;CZH7ro5$DD% z;+tG7!X3Ns+t?fF?fDNok3NxFz@#U_@ad5N&& z*Pyd}!RgZup=R7T!adnmt&7HhI_w1od88^9zU8FgFj$vDZj5j2)f&7TX>j|znSu7` z+?|PpX9_!z2{|!LBtZ*S(AWpo)5%$0&$BHoD4L?mUYXtZISkXe{kBN^imqt3R>ykZ zI(jYthqsmY%kLnt@ioAmw*_d}8a9>gn+Q>?Z`i~QAGRwECsBrXB@|-Wjrj{V?azM8 zy%uPL0BZ)qDq(k&K3tlyt6&wCuDWqaf;Xcix$yzKe^jMoh9Tf?LXtcH@7%=1P#PMTW(^96L>G7+?2pqT6|l&$A9HUtNhO4o}19AF=G#k5avd!-w)VVe@WCXQ1G&jA;j)AUtTcs?vR~UIStiDk59w8heM_ z9xqiu2vpbhc}Z+S$|V3ws%`$J;l{jGTXaJP0YBSdN5!+%&HL-!TAdgCs!9h`&mDKE z%I|WA^|l1co6>HLuUG$0z4Bi1KYv<2GBjM4b=#EtA`#T#Fv2MlmXx9Y)XdOT+BT)WT9|n0xtNA{PQm zfy%EAGou7m;OXa!SDxHETfF`>f8%*!my+~%Fu~9ge4GohchAP~A zO6AWZ)loRK7J5n*L4NthaAB08qPgcw?DPD}lh4oc*Iz`#d60^ZrBKDl<5`(T(JrKroFoKZ{WM-asS0z8lb;s5{u literal 0 HcmV?d00001 diff --git a/B42/__pycache__/urls.cpython-312.pyc b/B42/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d23b6f9202afc6d780053d969e515627118c398d GIT binary patch literal 1847 zcmb7E&2Jk;6rXk0Uu(N5Y1LMyDgy;MMu}HMX_2gaG)>YpE#M?gC1ixQ>)naH>F$oT zGX~;Q;X=8QQ{c)Cl|P{W0S7q5a;3mZRS!LJL8&S(J@IDNZcLIQSjp=*@BQBIeeV2{ z%V`Lnpa1;S`6Q0eQ*r1&nV#_c2L+)WWTPsw6+7lCUaT5ZggoR9d1_Vl;?=l3t8T(e zR+CbWyD2YSO)Dsdh-xQDZ1Z%V_~3u@%z@mO9>gL^`X%8Vw^I)w1M&RBmi`x;CT*Ex z{UTfTSX)l|oj7c(C#qSJ+{{N=48DVTk_DeMSkDjYBN?<8j%t4z?$^=IAq32Q27D9? z3~wFP9%k4&xplJmn%RcdX}8xtz!vp4oO&xTxkG)tK?8hsasoF4x=Acv(zNvk!FO7L z+cY^RfzR$>*I^t7w8aU-z`!u4_%nxm&Pwi_T zWnH(a#Y*<3>DOsLoJBpI`1)jNLg%#USWFjS9l|qRTl~WGfQyy2Yc1cB>B_K@^t}?E zvu$kp*zuY);AM1;tf4S?a|}WHPgCFeA|%pk;@y$BQ4nN2rvw*>a~2w zk~dtBS}+8i3Nr(92~^Mo za04Hx#gv1D(40N+-jKmcQSXAJu&T`ABvi$8!_2_Ohw+8QtGAaz?biC-`trO1ixXyg z@>->`9;QEBTv(oydMy%#`bYE}Ska3KU3?U}>BB8j*B1!83m>n(vR1jVIKO^NuZ>Sm z=y0jXpNL&*ZoNoNdDL#XWF`!@q@ZoV{*b@F2mukTG&I#ZQLr&c=Z%9l6y<47F|&Ot$c E1Ft~|g8%>k literal 0 HcmV?d00001 diff --git a/B42/__pycache__/wsgi.cpython-312.pyc b/B42/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3cb724c04653e1f2cf04917bcf812d1e6390a6dd GIT binary patch literal 630 zcmYjPL2DC16rS0QP1&?T#P&Rhmw?^DkdqWeO$;fTh=~XR*-Um{HXCPWmYqpzdXb*| z3H}D@#sA?&sUYmBA|AYml3P#CCJpU_nfKnjdEfWFVLoqfJIL6dv)9o(jL^?KSzF;E zSPV_@0R^a!0vuQz_p$Ldw_>|*V+44zxo)}ilhS3pj)FpP?ZjGT7+DDHlZ#S+V{TP8 zj!vCd-DZpUQjDUIjATk4)v6>(Mx%*j<(F?t^%zi5voctD|()x_q@!KD!aZC;rd3qn?t6y zA<2p$W>G3d=9tC+Mr_J;R=UFW5iN2x=egJzC==6^g|rUpjg|?0{-Sfxc--sK;a;su z4gYfG@NP0c-`rol?ftZDAk1Guikj`OjWPab6>(v417YVEx_gGK_s+5N9qoKYJ70F) VPvvju!H@E-)AH@(Qo3b&{sVUxvx)!! literal 0 HcmV?d00001 diff --git a/B42/asgi.py b/B42/asgi.py new file mode 100644 index 0000000..a78da75 --- /dev/null +++ b/B42/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for B42 project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'B42.settings') + +application = get_asgi_application() diff --git a/B42/settings.py b/B42/settings.py new file mode 100644 index 0000000..a0fdb96 --- /dev/null +++ b/B42/settings.py @@ -0,0 +1,141 @@ +""" +Django settings for B42 project. + +Generated by 'django-admin startproject' using Django 4.2.11. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-+#=200%4@^lboy^ovldlwc=mret9fu$d4zf-2abm3qn#g5n@rp' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'at_django_boilerplate.accounts', + 'at_django_boilerplate.core', + 'at_django_boilerplate.backend_admin', + 'at_django_boilerplate.blogs', + 'at_django_boilerplate.communications', + 'at_django_boilerplate.user_activity', + 'at_django_boilerplate.notification', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'B42.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'B42.wsgi.application' +AUTHENTICATION_BACKENDS = [ + 'at_django_boilerplate.accounts.auth_backends.CustomAuthBackend', +] + +AUTH_USER_MODEL = 'accounts.CustomUser' + +# Database +# https://docs.djangoproject.com/en/4.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.2/howto/static-files/ +# Static files configuration (add these) +import os + +STATIC_URL = '/static/' +STATICFILES_DIRS = [ + BASE_DIR / "static", +] +STATIC_ROOT = BASE_DIR / "staticfiles" # for collectstatic in production + +# Default primary key field type +# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/B42/urls.py b/B42/urls.py new file mode 100644 index 0000000..91320dd --- /dev/null +++ b/B42/urls.py @@ -0,0 +1,33 @@ +""" +URL configuration for B42 project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns = [ + path('admin/', admin.site.urls), + path("accounts/", include("at_django_boilerplate.accounts.urls")), + path("auth/", include("at_django_boilerplate.accounts.urls")), + path("",include("at_django_boilerplate.backend_admin.urls")), + path("",include("at_django_boilerplate.core.urls")), + path("communications/", include("at_django_boilerplate.communications.urls")), + +] +if settings.DEBUG: + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/B42/wsgi.py b/B42/wsgi.py new file mode 100644 index 0000000..85aea1e --- /dev/null +++ b/B42/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for B42 project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'B42.settings') + +application = get_wsgi_application() diff --git a/at_django_boilerplate/__init__.py b/at_django_boilerplate/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a25255788967907b73e1ccf7ce5472e8b836299b GIT binary patch literal 184 zcmX@j%ge<81UI>ZGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!3fIrb&rQ`&%quNQ zOxJfwEzT~JmtkIamWj77{q F769`gFt`8! literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/__init__.py b/at_django_boilerplate/accounts/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/accounts/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/accounts/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19aae8272c6d4b0167a31ca0b7ec701c60e367ce GIT binary patch literal 193 zcmX@j%ge<81UI>ZGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!O3=^9&rQ`&%quNQ zOxJfwEzT~KEC2xbG*AEl literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/__pycache__/admin.cpython-312.pyc b/at_django_boilerplate/accounts/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5de9811b49b09d13447e91a7b553d3a99123e5dd GIT binary patch literal 1551 zcma)6&1>976ra)8N?NVgsqrUqYba?El$DcHpdnDwP}gnT;H1zBija3^ZEf_inUNCg z`mhDl(nG0l_MwL!Tl}Z=vW6CK1`L6oa@*pZa_SrHZr07I1A6m&^WMDq&Aj*YN3CWf z7|*Jk(Vm9T-%=S5eFQF^060Pf5gelg`xr~C#7d(2Dqxjpv6kq*j*)^sK}0`5#GvYl zI^6FY6t}0wK__$j$^?%Q)v0l!3^j~)E3WxMDlQg!NRlW8V13Cc`$#G;@X%!6DR`bG za=kg+I+U;Ze+BvxqR7Vt`3hBijbL9Vif>5nM2~PwAqJpIiA+G2L@m{?fQL=0#Q8-5 z9s4G!`4)UOnL0pzbys!kqCPplD5~Z!o)rH-P7|(+;t`&!4sFG|;Of|DK zhwwpKBwLh;T8Cw=D5gPYC(AQYg;+_%d6cEx)hEXbNAfE=lojxT!q{e6(cxlRY5}ai z-h+kqG$Pcklq^|Y@}HJ`tuf#06L@2CfUAf~6S#89Sq=PMTn1>4^8-r=Y7fbJx%eK$ z5!%0zk55|`i zPBW%qN=ZP%JQNxaA)PwRG;WCsqmK*9bD?HiZK1U?mI$M{6Q? zX~Dv6?_MrT*pl2VXlgvNVIZxm-^Vw(p(d=*3&J*&_5o%)$`_0+j~{P1kK zak||2?c3Ak5C3#-f#K)1AJ=|be?A5mum`!0FoUuXgFx7UJg$IR3xpE{kBcytB^m_G z09T=xT!a(Dl>!oRAigWAr3M8Gw-nZ}18B}gEhHqkq;eXv=FVtEn9vrKMd>zs47W?{x3= z>xA{BCd?SjQJRJC5Kd18!i($O1zyn~wg%r33g|0~r z*Gf&>$`%^^K2Jg!=lanay&|E<9oln-oU$Nx_I4?#B!R{?jBgSW6wDH31Cj(16XlEt+B&l|_Xx)N=*b1TUh(uBBM5)kWPn)Gc zTvzQ5yshrMS}SkWt=pEU&G-}B%&J8qS}b(@xxA{;bW!Uk<=y7%Q6rSC+*Soe8x801xY%-pou!qE=!IJ#Afa$!X8jYFy5hh}`{vEO zH{ZPXX5M>0^z=9Z{&rk53CIHQ3z@V&dz0BqVrCtvK&4V3O+_e*@N|k!GZDt(nUpPU zkJv5VmSQ6;4IrEBZ2=vOklUmhvj|Uvw}EP31u6@vO{Tf(%-j5qU&+KzneO*A6{gNB z(FK@M@w_(%4LPSnB&Uf0ukdlWq$XleH^R{dLW9!H$=(oV)&T?&N(B*GwW;(f6=76n z6+~=#%5OIhU&!f(mOhs=;?0A?QCkNT4hnGLe=mzM*MZSAAK+jV>CNUj6e!Xwhl{pk z(~XY(I8ZH@m+2SpMs$(UsXWF>w!+>4vf1P@f}^#JPjA|?Y*(3O*5F%`wzh)@MMu)Q z;Fhd0CVQTplUb4e&elfciX5U<`!b6WSY3JnN`F_B_7yL;QHm_`e#RxmF8WOCOe7RR za7-+ zhrzg(hCwBhLrN?dg8G7?WrH8gynp%Pg{!l{nQOB_1Y_7Ui1Jzv>4t)gTsEi}vYJ#f zF-@M+a9~k3r5G3`8pT&-=t1I!Jl806dLWxO-JOmVMxQHEflfKGP(P2W!3uc9^^_Ji zeB0c3-6fP=!*%i4dg05$?Gsh;Wa-j@^k70#7HJ077ve0vr%1 zJHoNLDAhz?MfBCgsfsxDFkTj?s^Uj$wjDvNA0J=4{F@WFkJddx%`;ZkJAgTgf0YB5+JkU~sK zf3w{bDbg|j{mtT*7A8F_q02ZY6Q4pqjYvsE$1%|fo+KfOEF8t8aN_w(@A~Xt!^*lF zlNEr&bL+;<8)fd;9-ad~9S-9b$H_9u0a-SkGU=ST6ye>nd_AY68WQvpR=1A*79bLW zh8?8YAVN6&>!fL{f<5|#y>D-ZqU@s$O(OTZ%#p_X4v^GEi8C%}UR_zz6% BKl}gy literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/__pycache__/forms.cpython-312.pyc b/at_django_boilerplate/accounts/__pycache__/forms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abb0d6a56e447429e1ea266c4599d50a9421d137 GIT binary patch literal 9639 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 literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/__pycache__/models.cpython-312.pyc b/at_django_boilerplate/accounts/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa54976e0be5f00f1e4504296af818972cb9681b GIT binary patch literal 9713 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 literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/__pycache__/tokens.cpython-312.pyc b/at_django_boilerplate/accounts/__pycache__/tokens.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a60051d90ae5b40f5326ad042a085b5568200a7 GIT binary patch literal 887 zcmZ8fJ8#rL5T5n=@;ICjp#&uaK@gQeIwPU*kPt#V6bYmdUDDu!)#i5bIp-I<>yV2m zDDne9LOMzc3Q!vU2My6xSfql6F43h*#oE489#hQB?tJrk=3D<58>@o0UuWw61_Jn# z9|l(GC%p$c=>P+U0tk_dP@Wrs5n>l3Fkk@~-U4QFw2jLi*EE2e+kFDJ(yrJ;|MHWK zve_jn<(pWrI+t9n#4R2z@Q4elVxh~Uif<^Q+6iy;+l!!wE39`;hYoOX5d+s?XbW82 zHf)@YpQLK^M=82Sw9n5yQFN;T8@xqpMu9ltw)^#CIAd_PsgXzbY9NwkVu%?70gj`W z!N!Z(s4od^D1V)=*jSjFB{Py%Htcall@fXZQ}W}4poG$(a4L*YU~GG zBmq^LrO}AfNJ&TK`pa6<&L&7mTbwkhY?5^vqqt%{ngGJlSMhJ;$$O*}+ zgoH6mgFLqgd7aXrtcbH3`whf|4q`laLj#xVD2KJ%f}UYkswE~bFWN)dg>v&+BaW2t zS8Fs?&04v!$Yss2^f!vPC>XQpzAWWKQRT-GDd1vKqmRCSIC8I?^uCzZh5T=kSGC*2 h2%%kgxC=7}W(8G$PTe>F9rrD`c&$5oeFvrslK=FN+w1@U literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/__pycache__/urls.cpython-312.pyc b/at_django_boilerplate/accounts/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d25d064ea49060bf7d0d5c0eb7cc1e1c93b0fff7 GIT binary patch literal 4223 zcmb`JO>Emn7RM=DlugMWi6q;ytvZq|xp88VPAoZT;#HjZ18JH~6emtoBO8GhX_>Yp zDir0^1QzJte9WHKr-gglB0c!n;{tmr&h}hvRopp{}#0z`Ob$s3V zqTMnugMy5q2BAHCC&TYz1Rx`NLPpo0!02`{y8eVb{s_aJM-QxQJIrO|WHou=vGiQ8 zYhR~qUhEI@PP=$Pn>Tu#cbG%SH75-BlQ^z_XP@-B82ykjenQ58ixJ^{n`hsT!3};r zbhw{k7bgli=iZa^K?7&R#W@4_H2HoU=MKJTv-J4>#o&DU_zSj7vmgcTt3d=&bxGzP+~!qe!m$`4Qf) zbQ8qt$j;@7kA6h336JTmvhoJ}>?w&wp^}&Qr*%Q%Ip`s`1^!v>NAzkzt8M#&(oqwN zm3oyiebxX?udroa3A3B*=S*RPFKp%OHC{qa#_VZ!C_cOdW0LsOf(R9*vnq+}LJ3aj zLUZ%BsYL2kj+J@GW@hVkfxA-O%9=gR5oMs+&R2zkT$doQzjZ+At+KV+Gg0DF5?|wG zn5yG|1%M8v-?420TdkG^o-;#^-_qqdvBE279j7Hu6~xNAAeCXl0=G7k&N8#IxW!kp zj1sq&bM!XE*3%nuxdcnvE%W7S3GEz}(L7A0S*=7I%HgVT7BBORGWwQPtTM&5)U@Jw4>)z%iVSOjXmsz32C|!-F5^S^>R1QWt-RMi<5{);x-d><^E1lBc z=w%9`v(dgrKPg1H8~~9uz3Tvm4}uRun{=ow+tNcvsHny z8djn3X9{}Ye%X?M+A`(TiBbBF`LHyE2>2)(w83Tv7RH)=E&8UvS>be;(uJ0Au8$J1 zM+kanB4Gx&O?ZMLU6)EV3H>NEM{Ex~09g{&XtpkI&>UY7B~})tn&d^IPz#YnqKF^q z^Y~dX33HT{^OiKfCc^tG;ije8LP4xo^VXfkkwI6)YAYNH&f`nr2(Zt-5P5t*OuP9l3%?Jh-PE?rZhq5l z-b1gois^@&hCPePry4rds#fn&hgyyRJY4vK%ka&unO(SG*Q)}aJ@C+ORVUl)xsP!m z+*A<9JKW^8>)`}@(E&9S`zzvSSZ(6B$IONZpL(`Zm)Ih+z}L29vC8C@@7`LRe{i2! z{_H-y0Y#w(^V<>YQcY$h8GeDZ>>S4F90oNkUETSubG9sU^%9?z(9Z_+MO6D0{Kmk!S4{#;`60sFXov0F_Y}HdHF3QNqif50P%d z)(xXf(jb~PqO%}6t3|V#RRCr$V+^OjaO&WWPR`>EjqH;~>=KAwQnUATavA$BqAlJ5 z={xGmmQI$iZ*+grNL&MnYwFV9bn*#0y0;uTOv2~irT3D?&=eS&I{3RzE<(g^YOl{2 zq`@G4@K`5ryjk=ldfUDT{FL(``Q)C@h@1zJ^G0MEM5eV!=9^D+@}`}l&xmC~ENjG; zKx|2i!P>3sWDy6??Dre-SrDH!;tL?Ypv9Nezp^^HhJ%y;SvTk$pmPTO2+)r-dR1k2 xbW%a-(0k)ng2vEgFmzcPy7J!-bn@<-fUmRd3*Va{;py642SoTE0SW!W{vCpNMl%2a literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/__pycache__/utils.cpython-312.pyc b/at_django_boilerplate/accounts/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ca7b08ffb1702baf1e9049b64bed84bde697d78 GIT binary patch literal 6921 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 Q!$&u7yg|3n`w89q|LY+dl>h($ literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/admin.py b/at_django_boilerplate/accounts/admin.py new file mode 100755 index 0000000..2f585dc --- /dev/null +++ b/at_django_boilerplate/accounts/admin.py @@ -0,0 +1,29 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import CustomUser + +class CustomUserAdmin(UserAdmin): + model = CustomUser + list_display = ('email', 'name', 'is_staff', 'is_active',) + list_filter = ('is_staff', 'is_active',) + fieldsets = ( + (None, {'fields': ('email', 'password')}), + ('Personal info', {'fields': ('first_name', 'last_name', 'dob', 'contact_number', 'profile_photo')}), + ('Permissions', {'fields': ('is_staff', 'is_active', 'is_superuser', 'groups', 'user_permissions')}), + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'first_name', 'last_name', 'password1', 'password2', 'is_staff', 'is_active')} + ), + ) + search_fields = ('email',) + ordering = ('email',) + + def save_model(self, request, obj, form, change): + if not change: # If the user is being created + obj.set_password(form.cleaned_data['password1']) + obj.save() # Call save with custom_save=True + else: + obj.save() +admin.site.register(CustomUser, CustomUserAdmin) diff --git a/at_django_boilerplate/accounts/api/__init__.py b/at_django_boilerplate/accounts/api/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/accounts/api/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/accounts/api/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3ad9fac1485a014921395712a50f6988b934b40 GIT binary patch literal 197 zcmX@j%ge<81UI>ZGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!O3}~A&rQ`&%quNQ zOxJfwEzT~KECB5aHN5}; literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/api/__pycache__/v1.cpython-312.pyc b/at_django_boilerplate/accounts/api/__pycache__/v1.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..615d7e2f9f77e8d59f0115dbc6a8ffa94753fc47 GIT binary patch literal 11120 zcmcgyYit`=cAg<;_!dctlqgZ6Oj)umdTq*n;jx{uD^uO|fYEgJWq+?!ZP<)IqU-3dPbE zS?`~obB9AxH1lwP^a8wd=f3Zqd%p9XbNR0g4K@bC@9n1&m$os?f8mQB_)=o+Us#5@ z!U&AOrkFGvVOdNYQiilKVx(zfic9kmo~F5!DQ%9JX_`-2($VoX&WMwyEh$&p9dV}{BaQUknrcdWA|9Ilw#Kxjyy@mhGabX8YDxPdKALuCKVNG~JZyj&vIsu{-&! zzM++;xa9`yDTc^a1M>_cc-~|LugKmomU6ez+-Asasmkr8xjx8kt;!A2+&0MdSLJS} zx$ThKQI-3h*``q74>(AORn3DZkG+@>XCP@gB}!A-j3mC#swPQ}$FF#`!l zY+9ZWGjbvxlf{rhwG5_G*_px2tm--@4OW#2P}xIURplnK8EDfyO;S>9OpIQLN#ej0 zQ85$G3gRQ#f{5ABjZYBAV$&%(D&r)g<6=f6F*!@1$R3x-SX8_yrl(|hwMt?}h^AwS z6uh_zyodx^MJ1UeGUJeE!_-t%j*a80ERrb8P=IOAR7{d)vP6i!7E2|Bn2d8}Ro9E< zw_!r)n6@L+lAKMSkwh{Q*S?Y#mp&x3)(Y=e7?Fvv0uwQa#&I^n3GACpgr7BpjH=`L z?06zGIF-OHj~lAGutM4ezqMaM?iD7-CaVhdWU_QL^mlv;tbUBX%9(DO_4%ELQRdC_mYgZsq?cEo#3)$it(jgZZLZ|%sT-vP)UraYW~k-U%PP;R zS~jR<5bX2T%3kEmN&KiaA!miY{56^I3b`GR=Dx!S4k+oYSMr__N`jA;BsV{rM_Rsq z-3**Sp+RuIYpVK=J&*Re;X78J0M~{Su=UGZhTJ2nWg6rn6HAM#MVBbmoE9Z1HZIQf z9?r@WLA^#WmJxz_rC^MRK_b37ogktRCOC(Xf#Bwnc1*J>hwW58CZrP?=rWdp9*Js` z#rQ-f5l_T2swGY(QzQ@B;GCOESx}me$6@rb=~T$1@)SW-j$~7!YQp}G33Lp#VTxqO z5-Bk{HIW5q!Ou4rIF_MUP?>O+l;;}`c~uTzBBmP0MVW9oQ4|%``|RlG$>N*4s)mH1+Qt$@lB0CHt*KZkN$@kNMnGBBPAAh3lR5EywYfC2-O)26gJ(vcJ#p$- zWOyiKQ#naYjj3kbeNmECE=I;B)igPS&&nqt0bD~jU4u4ff#6Y+kT?X(;o&QN6WO%b z7t2hO*m&QNC{4=QslJn^PCPe!Wb}03sk5j1h&Y~*WRc8fr(ymvB2Q2C#pI}vjAh2N z(F-~-EwYjA-p+Ff~j`~AS?t1mCTTnOw_0{cD?@_~KJfg^8?EDT@T`-#g_a0TzU zf<<5FWv{)5tTJ&{a=05RtUE=P0+KX+SSKF4_ime?* zf9KV0OWTT@y089d=|}m_zU!~v^8UK>?>qCmp3Zj+77sjCIB;A!aJ+Efg*yjcxaKW% z?NPe+6uS1_>Drrr>0GgW=VuM(fOFl+_yUENUZtgX-DUP}UuDb<+ti zHo-YfCnO1!WHb&3PZA|^5DMOB$RT);0~jG<(r61LBsWBFGDZL9MSq@aS>w9+Nmi?j z_3A59#L8MlD)OLCO;nwlsXFzQRJ3@qq`^yMW`kx$JVCv=_V2XI$Iy`#&1%RQBzHw8 z2nN)U?;3P^B<$%o&rob2TAX*1)8?XR~0TJzFbU6!dEamkomL(UC*; z5ek(@A2#bd5ll!GOnu*=noDR%@$CBLi4rFGh&s6hb7#N`wpTl;E+M>-d6iNCo=@ z)R0mTy}{HI;@`V`Ai~%A{Qeh|j#K$_udH~Yc}KM9YI!^NR*uST!QQFZI~Omm*n6=m zmw)oO(lL^MDYD`@mv@}I>+)S2&JTV_6$x|3TjS+$! z8N>+bj@nYihiip_9L8!Dc^JZ1jGl*|gsKhYA(U_14gyi^+?@~f=Q;nH!OXv0k_@a| zUotjQOezgV!2lGKn>B_w)e9_Ml=W{{PayD+Qh|68PQf>%nqNyGtV|X4Ks8ts>e1Pp zp+fyYbeLpAO(S^Cu{k4%53T{i2Jx{8b}Al@FBG2!TJEea@2;LFwS~2w^|*&2V25rH z!bgNOZaB-FW@ea+#m4o6%(4gVO-@w-!o$EW962X=VQt=C-RxMSRvmpO) z60|&BF2m$G9AW2Cn35<2p%U6nT9a_F)R# z#}LeIBr6Z&%v3iu#pvKsI`s`;fGhk$FleiHcqtM@pqcb*h(N`=eODZp9R+t#aR;yY zmffKR{xi_m%_S^&QR#Ro|HJr-N60&bRgSY}+54VA!Lvp2Y$_bX^xBGNChwT}eBH&k{RP)n z#kKVT!@@A73H(s$h~<;%6;I}lBeQD6l7|wSIll}ZHZZph-G_UPx3{qn$2rK=19C(v zmok-0jJ1#~8Cv=~J}c^Do~xWp8T+6iXQ*ei=|)u&5v(Ru3`xD#JfAZNU>bt?V$5+E zzsIBW%$wx0R0@2qV(ys*)4WASGnR&2*@y#!3P}JB^~|_=TMkU+c{?CR*^J9sz{s-Y z>^UuadFZJJ+McFNHasDxQ&sAep7G!*jdFu^WV^M?>LtW_g8HdMpZq-xTvI z?0JG8L$wZHjEhs~?NIqC@RGJzwbAtMu&sz`We^^s;ks!MJK+n%jyUgCBN( z*!*GaxBQ1Qzm4VnCqLtj9@~O>-NCf>=6er)FthA?X2G#)Vm#l0TzCW)&o4J^r|Fi? zLh~-AdDnui=-;&HTX3#&CTrV$NAsoe#hL52TO;`wUjCiqm7>S@`CV7*1IA!&yX$Do zdqX$7ug7j|z22H{>R)l}x$9~D^C}O84<%INA02cbZeVUVSP$0zTwjx$!5UpOzWhg z8obJ+;ujRWK#oMuNESkGn7VUYy;)hC>eNM3QQpoW+qwbj^ zofViuE-&!!8onH&stH3?Tjc8-+j{l!D$nY+)$k?bA)rAtP?#c7>=BZp|z=J+*M;?nW|k{WK*LyBCJgX|S!}>Q`IISf+|bHGPkp(Z966)jX6>UnQ-+)b-K8c1Gu zxo(f1syuV1DvWswytQa6*MuBb=?!Y&*{=yVO79ztj*@9pwVTMjYeMyIZuk-oByLr0 z>zHHJZ2_f_LC_$V5v@YJs`k8$VS0UrDWg=0w!tlhLGx>T-5G2|DVR;!cMkZsd){2V zHn~e5xk5M}XSFyMP(kzS=F_ET6(wvac~;0#7^DJ*6g0^M@Yql|#8QvP z=-d|F3J*@iq~HZn%#=*Bif1}C8zu?pCDc-DT_Ju^T%^cKlK5JIRrHCFG)5VW5EKc1 zCbf~mHkfT?R}4W$O^_W>0!b#Znge)jxdwQFV}P-U#NhR)P(Wl{8mv+eP8nN?00k1B zAPrzQ_;Y~RR{0+`G6rhN{Q?`G1$I#1rvr{z9ovQ#_tA~m2B-WMCtwLW0*WKBII-f`R&==EKK9nJpM0Or zCYawjs<_Y4*@P5dh$1bW4Is3);|rjeR``6q?<+9;X~jLbah(+m|MR+=ac`=E{G0Jx zT{p%Q_jfn8_#^JjpG&a63%2+l_YbkmNBdfaEzED&L3Wroe$3I>Vm)dwe%!cq_>l4A zy)32=@etSHoTxIXZ?~3nj$r{tm*FQN)k4nM!aLV?ZR4F9<;3Fpl=Ex3c3tQLIjP;u zJaSoTgT6scF>BkoFIHCb;G?Q%Ig{}!@F=@@0cug=XsBm7*Rz;)m_`@x5c4({H;glJ z!^^wj!V*+rT?)$8y8?x}brcummLXW583O6M+-1X0_HfKRKhIraGn25&GC)`Akyj!A zOD;R{YPR0R=3}fAJpHgvL)~>6>aNpJcb$fM>#V3`@Gq213BmX-4{JZ9tv&NUFm@e^ zOy2s|SAM$mh3o9QGz{OZj=x5F^4mHQU$RqYN;`EHcBHI8tIo*xzjCMkpT92nfOxoo zhqGO!)Oopl6`rg^xo&d0QFj`okA>enH&Zti;z#Bj$E4tC{jL=plL(cXn*+p_A^i}6 z@m(iZHBZZlR6?HBT(ur*Wsn`%=oO60x>+^GQmMK<*x;U5zu2X>yrmvhkABw~O=P0_ zFsQNB253@s{cDF;{ieIdh64X=MRQY^6pw^NQrT&ZTFgLy68Z;AO1sJ|^3;GWnnFda zu4rmnzfbwZ?kdwzec8KXVR+Te_&5Ep%i?ve zI+^B{RXfw(akXcurx@tG`qI)%#m(J6-|^EO#jediZ~tlgnw4*CS~WuDg`o!ww(&B~ zrnjSi87;Q>3oTofmaWSzy$jD2z3l~WpW^K+c=ssYJ!FvrE}xpzG=@ORwB*?_8XD=ir*%)a+cvffj~7E00%b*mS31(_@c# zt?TDI{&vT$SAYHbuU^N|TdxS0g<_lks&C0x?A&xUwUoj|pIbWj6{834Gz2z|{z;4f ztCsSawiJ`s_0|w*l`l&Wk(`Ej5$A=7MV)3ou%NA2mDcUT|?w@=t2?0PBW! zs9pkpJBW@ExL}5VU`>W!ga1WI1a-UWt0{t4`tLM)?$O0q-B9}j#-@##VZ5Q!929V) zEv6*xZx|l!&jw+1W|G7OT8sWmL-p6RuUvIY^zxft0jhzTS{v^HwadZA(ikYr5;%b6 zDeA1%yd?fQ6A06JN}7+iqf~+JMi_l6^j~jM82|G|(DkFb%Y6_rj2BFrPq(F%UG_bv zvN8B49Q6sSwUbJxsGC@|m;1_$QJ=A9O6YyT=wevZeZ>;BC)6!<|FjX-fh=6wa70Db zoXU>F-`>VmE8q(Jc?15uL*1LI2@Bu_O-On@N*$S$S<=H)!#f{Z4r>2Q_#XLdXo0Li zdLJUVVqw`&ncjQMj(bekJ!Ur~{)2h?9&_lwndd%bzE`*C9`nSf%+`C%o_p9ZNE7{^ zG6&a84C^Xd{R_T=)vs9ni~E+XJ#U!SO+D-+dui`FgU|I5*2ccfE{?A=_+0l7ux*P_ h29NbA_Gy-NUfZ_L;B)<`;Sl@erSU&9@SsQk{{XvrZO{M! literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/api/v1.py b/at_django_boilerplate/accounts/api/v1.py new file mode 100755 index 0000000..22fc58d --- /dev/null +++ b/at_django_boilerplate/accounts/api/v1.py @@ -0,0 +1,273 @@ + +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from rest_framework.authtoken.models import Token +from django.contrib.auth import authenticate +from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.authentication import TokenAuthentication + +from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode + +from django.contrib.auth.tokens import default_token_generator + +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from rest_framework.authtoken.models import Token +from django.contrib.auth import authenticate +from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.authentication import TokenAuthentication +from django.views.decorators.csrf import csrf_exempt + +from django.core.mail import send_mail +from django.template.loader import render_to_string +from django.utils.html import strip_tags +from django.conf import settings +from django.contrib.auth import password_validation +from django.core.exceptions import ValidationError + +from at_django_boilerplate.accounts.models import CustomUser + + +import logging +logger = logging.getLogger(__name__) + + +class LoginApiView(APIView): + permission_classes = [AllowAny] # This makes the endpoint open to all users + + def post(self, request, *args, **kwargs): + # Get the username and password from the request data + username = request.data.get('username') + password = request.data.get('password') + + if not username or not password: + return Response({"message": "Both username and password are required."}, status=status.HTTP_400_BAD_REQUEST) + + # Authenticate the user + user = authenticate(username=username, password=password) + if user is not None: + # User is authenticated, generate token + token, created = Token.objects.get_or_create(user=user) + # You can retrieve the user's role or permission-based data here + if user.is_superuser: + role = "admin" + elif user.get_manager(): + role = "manager" + elif user.get_technician(): + role = "technician" + else: + role = "customer" + + return Response({ + "token": token.key, + "role": role, + "userId": user.id, + "username": f"{user.first_name} {user.last_name}", + "profile_photo": user.profile_photo.url if user.profile_photo.url else None, + "message": "Login successful" + }, status=status.HTTP_200_OK) + + # Invalid credentials + return Response({"message": "Invalid username or password."}, status=status.HTTP_401_UNAUTHORIZED) + + +class ValidatePasswordAPIView(APIView): + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + def post(self, request): + password = request.data.get('password') + if not password: + return Response({"success": False, "message": "Password is required."}, status=400) + user = request.user + if user.check_password(password): + return Response({"success": True, "message": "Password is valid."}, status=200) + return Response({"success": False, "message": "Password is incorrect."}, status=400) + + +class ResetPasswordAPIView(APIView): + def post(self, request): + # Get the email from the request data + email = request.data.get('email') + + # Ensure the email is provided + if not email: + return Response({"success": False, "message": "Email address is required."}, status=400) + + try: + # Check if the user exists in the database + user = CustomUser.objects.get(email=email) + except CustomUser.DoesNotExist: + return Response({"success": False, "message": "User with this email does not exist."}, status=404) + + # Send the password reset email + if self.send_reset_email(request, user, email): + return Response({"success": True, "message": "Please check your email to reset your password."}, status=200) + else: + return Response({"success": False, "message": "Failed to send email. Please try again later."}, status=500) + + + def send_reset_email(self, request, user, email): + # Generate a password reset token + token = default_token_generator.make_token(user) + + # Convert user.pk (integer) to string and then encode it to base64 + uid = urlsafe_base64_encode(str(user.pk).encode('utf-8')) # Encode user ID to base64 string + + # Prepare the context for the email template + context = { + 'user': user, + 'reset_link': f"http://localhost:3000/reset-password/{uid}/{token}/" # Update the URL if needed (to your front-end) + } + + # Render the HTML email template with the context + html_message = render_to_string('email_template.html', context) + + # Subject of the email + subject = "Password Reset Request" + + # Strip HTML tags for the plain-text version of the email + message = strip_tags(html_message) + + # From email and recipient list + from_email = settings.DEFAULT_FROM_EMAIL + recipient_list = [email] + + try: + # Send the email + send_mail(subject, message, from_email, recipient_list, html_message=html_message) + return True + except Exception as e: + print(f"Error sending email: {str(e)}") + return False + +class ResetPasswordConfirmAPIView(APIView): + def get(self, request, uidb64, token): + try: + # Decode the user ID from base64 + uid = urlsafe_base64_decode(uidb64).decode('utf-8') + user = CustomUser.objects.get(pk=uid) + + # Check if the token is valid + if default_token_generator.check_token(user, token): + return Response( + {"success": True, "message": "Token is valid."}, + status=status.HTTP_200_OK + ) + else: + return Response( + {"success": False, "message": "Invalid or expired token."}, + status=status.HTTP_400_BAD_REQUEST + ) + + except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist): + return Response( + {"success": False, "message": "Invalid or expired token."}, + status=status.HTTP_400_BAD_REQUEST + ) + + def post(self, request, uidb64, token): + password = request.data.get('password') + + if not password: + return Response( + {"success": False, "message": "Password is required."}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + # Decode the user ID from base64 + uid = urlsafe_base64_decode(uidb64).decode('utf-8') + user = CustomUser.objects.get(pk=uid) + except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist): + return Response( + {"success": False, "message": "Invalid user ID."}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + password_validation.validate_password(password=password) + except ValidationError as e: + return Response( + {"success": False, "message": str(e)}, + status=status.HTTP_400_BAD_REQUEST + ) + # Validate the token + if default_token_generator.check_token(user, token): + # Set and save the new password + user.set_password(password) + user.save() + return Response( + {"success": True, "message": "Password has been reset successfully."}, + status=status.HTTP_200_OK + ) + else: + return Response( + {"success": False, "message": "Invalid or expired token."}, + status=status.HTTP_400_BAD_REQUEST + ) + + +class UserProfileView(APIView): + permission_classes = [IsAuthenticated] + authentication_classes = [TokenAuthentication] + + def get(self, request): + user = request.user + + if user.is_superuser: + print('Is Superuser') + users = CustomUser.objects.all() + data = [ + { + 'id': u.id, + 'email': u.email, + 'first_name': u.first_name, + 'last_name': u.last_name, + } + for u in users + ] + + elif user.is_manager: # Manager user only sees their own utility's data + utility = user.get_utility() # Get Utility + users = utility.accounts_in_utility.all() + + data = [{ + 'id': user.id, + 'email': user.email, + 'first_name': user.first_name, + 'last_name': user.last_name, + 'utility': str(utility) + } + for u in users + ] + + elif user.is_technician: + utility = user.get_utility() # Get Utility + users = utility.accounts_in_utility.all() + + data = [{ + 'id': u.id, + 'email': u.email, + 'first_name': u.first_name, + 'last_name': u.last_name, + 'utility': str(utility) + } + for u in users if not u.is_manager and not u.is_superuser + ] + + elif user.is_customer: + utility = user.get_utility() # Get Utility + data = [{ + 'id': user.id, + 'email': user.email, + 'first_name': user.first_name, + 'last_name': user.last_name, + 'utility': str(utility) + }] + + else: + data = None + + return Response(data, status=status.HTTP_200_OK) diff --git a/at_django_boilerplate/accounts/apps.py b/at_django_boilerplate/accounts/apps.py new file mode 100755 index 0000000..f7c12be --- /dev/null +++ b/at_django_boilerplate/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'at_django_boilerplate.accounts' diff --git a/at_django_boilerplate/accounts/auth_backends.py b/at_django_boilerplate/accounts/auth_backends.py new file mode 100755 index 0000000..703562b --- /dev/null +++ b/at_django_boilerplate/accounts/auth_backends.py @@ -0,0 +1,51 @@ +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth import get_user_model +from at_django_boilerplate.utils.hash_utils import hexdigest +import logging + +logger = logging.getLogger(__name__) + +class CustomAuthBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + print('Username:',username) + if username is None: + username = kwargs.get('email') # fallback if email is passed explicitly + print('Username:',username) + + if not username or not password: + return None + + username = username.lower() + email_hash = hexdigest(username) + user_found=False + + UserModel = get_user_model() + + + try: + user = UserModel.objects.get_by_email(email=username) + if user: + user_found=True + + except UserModel.DoesNotExist: + logger.info(f'User with email {username} not found.') + + + try: + if not user_found: + user = UserModel.objects.get_by_contact_number(contact_number=username) + if user: + user_found=True + except UserModel.DoesNotExist: + logger.info(f'User with contact_number {username} not found.') + return None + + if user_found: + if user.check_password(password) and self.user_can_authenticate(user): + return user + + logger.info(f'Authentication failed for user with email hash {email_hash}.') + return None + + def user_can_authenticate(self, user): + return user.is_active diff --git a/at_django_boilerplate/accounts/forms.py b/at_django_boilerplate/accounts/forms.py new file mode 100755 index 0000000..a32962a --- /dev/null +++ b/at_django_boilerplate/accounts/forms.py @@ -0,0 +1,165 @@ +from django import forms +from django.contrib.auth.forms import PasswordResetForm +from django.utils.http import urlsafe_base64_encode +from django.utils.encoding import force_bytes +from django.contrib.auth.tokens import default_token_generator +from django.contrib.auth.password_validation import (validate_password, + get_password_validators, + MinimumLengthValidator, + UserAttributeSimilarityValidator, + CommonPasswordValidator, + NumericPasswordValidator,) +from django.conf import settings +from .models import CustomUser +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ + +from at_django_boilerplate.utils.hash_utils import hexdigest +import logging + +logger = logging.getLogger(__name__) + + +class UserSignUpForm(forms.ModelForm): + password = forms.CharField(widget=forms.PasswordInput) + confirm_password = forms.CharField(widget=forms.PasswordInput) + terms_accepted = forms.BooleanField(required=True, label='I accept the terms and conditions') + + class Meta: + model = CustomUser + fields = ['first_name', 'last_name', 'email','contact_number','dob','password','confirm_password','terms_accepted'] + widgets = { + 'dob': forms.DateInput(attrs={'type': 'date'}), + 'first_name': forms.TextInput(attrs={'type': 'text'}), + 'last_name': forms.TextInput(attrs={'type': 'text'}), + 'email': forms.EmailInput(attrs={'type': 'email'}), + 'contact_number': forms.TextInput(attrs={'type': 'text'}), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['password'].help_text = self.get_password_requirements() + + def get_password_requirements(self): + password_validators = get_password_validators(settings.AUTH_PASSWORD_VALIDATORS) + requirements = [] + for validator in password_validators: + if isinstance(validator, MinimumLengthValidator): + requirements.append(f"*Password must be at least {validator.min_length} characters long.
") + elif isinstance(validator, UserAttributeSimilarityValidator): + requirements.append("*Password cannot be too similar to your other personal information.
") + elif isinstance(validator, CommonPasswordValidator): + requirements.append("*Password cannot be a commonly used password.
") + elif isinstance(validator, NumericPasswordValidator): + requirements.append("*Password cannot be entirely numeric.
") + return " ".join(requirements) + + + + def clean(self): + cleaned_data = super().clean() + password = cleaned_data.get("password") + confirm_password = cleaned_data.get("confirm_password") + + if password and confirm_password: + try: + validate_password(password, self.instance) + except forms.ValidationError as error: + self.add_error('password', error) + return cleaned_data + + if password != confirm_password: + self.add_error('confirm_password', "Passwords do not match") + + return cleaned_data + + + +class SigninForm(forms.Form): + username = forms.CharField(max_length=65) + password = forms.CharField(max_length=65, widget=forms.PasswordInput) + + +class CustomPasswordResetForm(PasswordResetForm): + + def get_users(self, email): + """Given an email, return matching user(s) who should receive a reset.""" + email_hash = hexdigest(email) + active_custom_users = CustomUser.objects.filter(email_hash=email_hash) + active_users = [user for user in active_custom_users ] + valid_users = [u for u in active_users if u.has_usable_password()] + return valid_users + + + def save(self, domain_override=None, + subject_template_name='reset/password_reset_subject.txt', + email_template_name='reset/password_reset_email.html', + use_https=False, token_generator=default_token_generator, + from_email=None, request=None, html_email_template_name=None, + extra_email_context=None): + """ + Generates a one-use only link for resetting password and sends to the user. + """ + email = self.cleaned_data["email"] + for user in self.get_users(email): + context = { + 'email': email, + 'domain': domain_override or request.get_host(), + 'request':request, + 'site_name': 'we-kwick', + 'uid': urlsafe_base64_encode(force_bytes(user.pk)), + 'user': user, + 'token': token_generator.make_token(user), + 'protocol': 'https' if use_https else 'http', + **(extra_email_context or {}), + } + from_email = settings.EMAIL_HOST_USER + # print('From:',from_email) + # print('Email Template:',email_template_name) + # print('HTML Email Template:',html_email_template_name) + self.send_mail( + subject_template_name, email_template_name, context, from_email, + email, html_email_template_name=html_email_template_name, + ) + + + +class ProfileUpdateForm(forms.ModelForm): + class Meta: + model = CustomUser + fields = [ 'contact_number'] + widgets = { + 'dob': forms.DateInput(attrs={'type': 'date'}), + } + +class UpdateProfileForm(forms.ModelForm): + class Meta: + model = CustomUser + fields = [ 'contact_number'] + widgets = { + 'dob': forms.DateInput(attrs={'type': 'date'}), + } + + + +def validate_image(file): + valid_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'] + valid_file_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.svg','.JPG'] + + file_mime_type = file.content_type + if file_mime_type not in valid_mime_types: + raise ValidationError(_('Unsupported file type. Please upload a JPEG, PNG, GIF, or SVG image.')) + + extension = file.name.split('.')[-1].lower() + if not any(file.name.endswith(ext) for ext in valid_file_extensions): + raise ValidationError(_('Unsupported file extension.')) + +class ProfilePictureUpdateForm(forms.ModelForm): + profile_photo = forms.ImageField( + widget=forms.ClearableFileInput(attrs={'accept': 'image/jpeg,image/png,image/gif,image/svg+xml'}), + validators=[validate_image] + ) + + class Meta: + model = CustomUser + fields = ['profile_photo'] diff --git a/at_django_boilerplate/accounts/management/__init__.py b/at_django_boilerplate/accounts/management/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/accounts/management/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/accounts/management/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..707928cf74040dc431bf5fd2a832df389b431f47 GIT binary patch literal 204 zcmZ8au?@m75R3^(2$Vr+_yrIR&`6=*Hplh}vku4$lZj!F9t=U&77S5`^(66l|p{$u@9GkNe%` zoI!fY0HxB7B(~P0BZ1@_5X6hSFv{|))s@7i5)@MM+qZ`J{J!z8}+dUE-MA!A1FTuqb3Bm>x3f9u!$Te`t_uI|t zSU`R%0HxBNB(^S~CxH|i5X7siu*&hX)3wB=78F~`(gjN%jIcsUBP~JcOmvk8&b8IV c`MhbryliW!j$oO-Kdxb(!EwKuK+iIM!?_wBql^WK~H{Bx<4 zN3eb^Z23V3p+7W9mo)>E8vu?FMHB}p#7&Gf&IFmzXc`GN0yDIl7DgHLA)@91q87u) z#?-%=9cHV!XPQ*8D%>Q3)uS*ZeF`*RkA;lFdxCLb<&XN3b-6Ex8=ObHJw0G5@r+C^ z0yshpH8Dla48=`@W)4u(92!+q>It@O9nG|JkuD#{=&ca zsdYSy3u>F)H@x$^5|&Lp5_W$6|K5dqucV%H-FtP8Q?!qIucdgddy8|NBAQDhO*KYC zRJAwvN`!`f{{uc0wRRLLYh6=iCeLgri-p7wRKDYLA-z5cnOX=)3e9)lyZ-*&*nwMq z|MqLA!Qc(4v)*n;abF7OE$3z|;7NsGqFUr8n5kUU>an&ID%<9aNTy6dzF?{-nDhoj zh~0=&RrG}?;sN6^G@DugM%vN`Gh1&+(&?zIkTi;=vUZ^dnX-f&22AA|_v-bHMg!W* z6fEc{lkhIwwX>UOLxuyP!*F1|_{!amLgtcw%t_b1#l((`2JW4$+aGV#?>5}6Pa7_0 zU0+DXhfxgq6E5R{OQc78q~DFaR^$ha4+2QTC9|Ymm=@AyA?r&Q>ZxZ(xB?2}C3S{G zlH$|`LzUfz&4BHdXM<3ia@}WQ55#@+Te)&tc1C6Aw7fDZuY4cL>0i4>EkzILw@u}^Mm#-WePwmRMa{17BSQ;-bgDRdIKhL6t(xbuyaJe+H zFFk(qyVY-2PnWKZmad(=ef!DMonP!vCU862tXj(Uk~jA}mG`_bqH(|g7d`Lum;|~T zfSKDY81O6L@dM%xDE_(@y4@3@r$CfI9H4RGo%^>YX5QMuQ%h~MS->%V|JYHFC7jLB6oprb*r4RiJv7 g#bprB4UF*_x_*Y%&d}O#lD@ literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/management/commands/seed_admin_account.py b/at_django_boilerplate/accounts/management/commands/seed_admin_account.py new file mode 100755 index 0000000..ae649fd --- /dev/null +++ b/at_django_boilerplate/accounts/management/commands/seed_admin_account.py @@ -0,0 +1,19 @@ +from django.core.management.base import BaseCommand +from at_django_boilerplate.accounts.models import CustomUser +from django.db.utils import IntegrityError + + +class Command(BaseCommand): + help = 'Add predefined tags to the database' + + def handle(self, *args, **kwargs): + tmp = CustomUser.objects.create(email='admin@rys.com',first_name='Admin',last_name='User') + tmp.save() + tmp.set_password('1234') + tmp.is_superuser = True + tmp.is_active = True + tmp.is_staff = True + tmp.save() + + + self.stdout.write(self.style.SUCCESS("✅ Seeded Accounts & Businesses")) diff --git a/at_django_boilerplate/accounts/migrations/0001_initial.py b/at_django_boilerplate/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..aed6b55 --- /dev/null +++ b/at_django_boilerplate/accounts/migrations/0001_initial.py @@ -0,0 +1,50 @@ +# Generated by Django 5.2.6 on 2025-12-29 05:20 + +import at_django_boilerplate.accounts.models +import at_django_boilerplate.utils.custom_fields +import django.utils.timezone +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='CustomUser', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('first_name', at_django_boilerplate.utils.custom_fields.EncryptedTextField()), + ('last_name', at_django_boilerplate.utils.custom_fields.EncryptedTextField()), + ('email', at_django_boilerplate.utils.custom_fields.EncryptedSearchableTextField()), + ('dob', models.DateField(blank=True, null=True, verbose_name='Date Of Birth')), + ('contact_number', at_django_boilerplate.utils.custom_fields.EncryptedSearchableTextField(blank=True, null=True)), + ('profile_photo', models.ImageField(blank=True, default='assets/default_profile.png', null=True, upload_to=at_django_boilerplate.accounts.models.user_directory_path)), + ('is_staff', models.BooleanField(default=False)), + ('is_active', models.BooleanField(default=True)), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now)), + ('terms_accepted', models.BooleanField(default=False, verbose_name='Terms & Conditions')), + ('terms_accepted_time', models.DateTimeField(default=django.utils.timezone.now)), + ('source', models.CharField(default='manual', max_length=10)), + ('last_active', models.DateTimeField(blank=True, null=True)), + ('is_technician', models.BooleanField(default=False)), + ('is_manager', models.BooleanField(default=False)), + ('email_hash', models.CharField(blank=True, editable=False, max_length=64, null=True)), + ('contact_number_hash', models.CharField(blank=True, editable=False, max_length=64, null=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/at_django_boilerplate/accounts/migrations/0002_alter_customuser_email.py b/at_django_boilerplate/accounts/migrations/0002_alter_customuser_email.py new file mode 100644 index 0000000..4a562f1 --- /dev/null +++ b/at_django_boilerplate/accounts/migrations/0002_alter_customuser_email.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.6 on 2025-12-30 09:23 + +import at_django_boilerplate.utils.custom_fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='email', + field=at_django_boilerplate.utils.custom_fields.EncryptedSearchableTextField(blank=True, null=True), + ), + ] diff --git a/at_django_boilerplate/accounts/migrations/__init__.py b/at_django_boilerplate/accounts/migrations/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc b/at_django_boilerplate/accounts/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59c0a9e9b912494e72815a4e28317567857a05ec GIT binary patch literal 4227 zcmcgvOKcm*8D2g_k<`PaWLb(H~aV*KQ%_v^2I74#fDC{m-5KYg?P2f#2_r%nO^X4D)wF?EISQleZ6G@-2fHYSCY0PQ)^FmhPH9?kC z%afN87S;6bA%+>O6K^%Z_br1dqLdRkQZD4oI#O;YgPYdCmLLC+3z5C%wZM;e7zQ;V zciFS$w0AO2)J!=KK#d&m_m+J-=z-Tyd5<0-wFR}JwsQL(-XoOvSR*fPUF&UJMJ=GM zr5xCDzeg6t?Q5Y%&NcRcwMgUIR(%O@cl^&Jb^Z)V!#|1XDD}@sBM~vE}1?{5bf3Kog-l4yxoKG+Z9pgXY9xXd2=lJ>0^5 zo{0U2d>`czI&W^FEi7KXGD`o|r-( z?>&+2MyFAW;3%Kj>0i)Y`!>$T&RM>jJ3V?4I<}iHYB_6obaZT-<3$ZCTv3HbMo?6Z zllVO5^86D{#8MX6{4+&Wlk<0g!}1n*Rb7`A^yVqRq`iNavGp-lR%I1a36@{vL6|6K z1*t^mES<+(R@17;OAjr#R1`&}4dhs@g377I0#*o&PfDN7VpYgWyoOa)%kdgK1eGOw zY>ro1UKCm2%?qkZ-Z|{LpylME#^NUhQAi7#xWNKS5FkidIEwS}QsBLGR@Hc|r~-^; zgfo0m)GS{Piv>=@Pc$2iQ$R7dLTc0UVkBt%s)((Yf+FO3WrKT&H!L4O5qME3Vavbk zG|NX#CjPUUaGn=L%Z23CQrj$8!rscT(}Dv2Y){LQ#;3v2#r!H(Vov1d~65M>^5nrwNAKQ%s+i8*XLe6Rp2 zg~wnB0yNy3EJzqx?GXDs*-GPrhEb_wkaK6s9);(+W?A&(>V!3#Y>hSNI1u5A-nw4X&l5j_vR82Qn#@S z<_eKy8``&&QY-iHC0BMxPT0=U)T}A?5 z056OAbdHrXB#8v2E;Vj>X|X_HEH{~z{G^)VRA$kj|UG|*i=Qy~+6(I#VtCiy(K|pCk;H+jrf{frrs}{)K5hN%$4ME!A3Otmq$3|p%N#2)a30p2n zUbl|iZ(X08SzJiW z-(N^5I4h`7U>i_QP`!#)EF=!}LShGHV)y2q7=>=m)k737Y$)VCLLa0Y)g=hi*Ub0r z9Z#=pdl=u@ZI|i1AJjP*dK77Xjhlh2Ns$CJkKWaZ8_!`yQ4P8bT#Y1ccr z{?E982+q1{!7jb~#5a%4?h{7$iRbP=_0ka@j|dRIq07Xc;Iy+AWc9&`XCvm|gfR$y(61u>p$vP!a@=I>Ow zB&P?2T5v>|b1r4frOdep#@vHyl+yz%wcrPO-^tA>v+tzQ zck=nwmmSr<8+zboEx1p4@&&50ztjUas81$|PbQ7Q$qKFx&gy|V8p?5!G2=#fyfRr0 zU)BTDbTvU%6Gk}k96|W+-q%;&rxM^hP4JyIqNgj`ORTS~>O#I6wUYyIjS*a9M&DS) z4PL+I5Q!JRbk30!OSx9afL)KsaLIku4Sw#wMi_(F-L+suA3pbN$s9gs44->3q|dLz z#uevnA|d0Xf*wJ}0(IM!CW$+O2oc1t?L^Pf_U$AS?Ax4!!s%*%_i@PQ-*&Mbp0BPe z3p<4t^IO9Q)@yvHUgP%Hu`&xstk}W4w86#q_qQ&)@5JrBo#vz5B$}s*EMJ`gt zTd>^$<_QmlxCpZ!yyxxieo?s(B>8Sp$>-Cy%i(am^)QZ8ubI);O#f@9 SeB(C*Lq=fep9~OG(!T*OxKGCb literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/migrations/__pycache__/0002_alter_customuser_email.cpython-312.pyc b/at_django_boilerplate/accounts/migrations/__pycache__/0002_alter_customuser_email.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d37a3798b55342db7de64f86c5648e35778ff759 GIT binary patch literal 938 zcmZuwJ#W)M7(Rd2F)a#i5uYtc6d@0dQ`RCRJ_=F=0W}>;mea*|q)sokIiDr6AdotA zWUItKK(1BBd;yINP;CMQ?cTeILIc&pmhkHZx-bxn{pKgP#fjzp|iQ z#;G%YES)`o0ahqzDRNh-+S6K^0#xuEU~LCrooHRFES&87TH&|!zTz6cv!LNB!tMnf zhIkN#i9bmOpzJW-mFONI`Lvdbm5$QVR6z7@&i3CDAlYDznB9wK(Jbm#&%tK@$FPp| z17lw;z8sh(YdO{KPq~(;ye#>;s<4pnM`_3tF~ewy&8mnMV>gY-~9 zH)OWarYPJNdYDq`YVza}CKQIKM-AS_t4}tX z-uj!S$4DngB%OYgvV6=*Hplh}vku4$lZj!F9t=U&77S5`^(66l|p{$u@9GkNe%` zoI!fY0HxB7B(~P0BZ1@_5X6hSFv{|))s@7i5)@MM+a*dPF&BC}rd}GEK#zFl6v;;WE literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/models.py b/at_django_boilerplate/accounts/models.py new file mode 100755 index 0000000..c877741 --- /dev/null +++ b/at_django_boilerplate/accounts/models.py @@ -0,0 +1,178 @@ +from django.db import models +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin +from django.utils.translation import gettext_lazy as _ +from django.utils import timezone +from django.urls import reverse +import uuid +from at_django_boilerplate.utils.encryption_utils import EncryptionUtils +# ✅ imports fixed according to your project structure +from at_django_boilerplate.utils.hash_utils import hexdigest +from at_django_boilerplate.utils.custom_fields import ( + EncryptedTextField, + EncryptedSearchableTextField, +) +from at_django_boilerplate.utils.mixins import UUIDMixin + +def user_directory_path(instance, filename): + user_id = str(instance.id).zfill(10) + return f'uploads/users/{user_id}/{filename}' + +class CustomUserManager(BaseUserManager): + def create_user(self, email=None, contact_number=None, password=None, **extra_fields): + """ + Create and save a User with the given email and/or contact_number and password. + At least one of email or contact_number must be provided. + """ + if not email and not contact_number: + raise ValueError("Either email or contact number must be provided.") + + if email: + email = self.normalize_email(email) + + user = self.model(email=email, contact_number=contact_number, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email=None, contact_number=None, password=None, **extra_fields): + extra_fields.setdefault('is_staff', True) + extra_fields.setdefault('is_superuser', True) + + if extra_fields.get('is_staff') is not True: + raise ValueError('Superuser must have is_staff=True.') + if extra_fields.get('is_superuser') is not True: + raise ValueError('Superuser must have is_superuser=True.') + + # Superuser usually needs at least one identifier + if not email and not contact_number: + raise ValueError('Superuser must have email or contact number.') + + return self.create_user(email, contact_number, password, **extra_fields) + + # Keep your existing search helpers unchanged + def get_by_email(self, email): + try: + return self.get(email_hash=hexdigest(email)) + except CustomUser.DoesNotExist: + return None + + def filter_by_email(self, email): + return self.filter(email_hash=hexdigest(email)) + + def get_by_contact_number(self, contact_number): + try: + return self.get(contact_number_hash=hexdigest(contact_number)) + except CustomUser.DoesNotExist: + return None + + def filter_by_contact_number(self, contact_number): + return self.filter(contact_number_hash=hexdigest(contact_number)) + +class CustomUser(AbstractBaseUser, PermissionsMixin,UUIDMixin): + + first_name = EncryptedTextField() + last_name = EncryptedTextField() + email = EncryptedSearchableTextField(hash_field='email_hash',null=True, # ← Must be True + blank=True) + + dob = models.DateField(_('Date Of Birth'), blank=True, null=True) + + contact_number = EncryptedSearchableTextField( hash_field='contact_number_hash',null=True, blank=True) + profile_photo = models.ImageField( + upload_to=user_directory_path, + default='assets/default_profile.png', + null=True, + blank=True + ) + + is_staff = models.BooleanField(default=False) + is_active = models.BooleanField(default=True) + date_joined = models.DateTimeField(default=timezone.now) + + terms_accepted = models.BooleanField("Terms & Conditions", default=False) + terms_accepted_time = models.DateTimeField(default=timezone.now) + + source = models.CharField(max_length=10, default='manual') + + last_active = models.DateTimeField(null=True,blank=True) + + + is_technician = models.BooleanField(default=False) + is_manager = models.BooleanField(default=False) + + + + + objects = CustomUserManager() + + USERNAME_FIELD = 'id' + REQUIRED_FIELDS = ['first_name', 'last_name'] + + def __str__(self): + return self.get_decrypted_name() + + @property + def name(self): + return self.get_decrypted_name() + + + @property + def is_admin(self): + return self.is_superuser + + def get_absolute_url(self): + return reverse("user_profile") + + def get_decrypted_first_name(self): + return self.first_name or '' + + def get_decrypted_last_name(self): + return self.last_name or '' + + def get_decrypted_name(self): + return f"{self.get_decrypted_first_name()} {self.get_decrypted_last_name()}".strip() + + def get_decrypted_email(self): + return self.email or '' + + def get_decrypted_contact_number(self): + return self.contact_number or '' + + def set_contact_number(self, value): + self.contact_number = value + self.save() + + def set_first_name(self, value): + self.first_name = value + self.save() + + def set_last_name(self, value): + self.last_name = value + self.save() + + def set_name(self, value): + names = value.strip().split(' ', 1) + self.set_first_name(names[0]) + self.set_last_name(names[1] if len(names) > 1 else '') + + def has_contact_number(self): + return bool(self.contact_number) + + + def is_customer(self): + try: + return self.connection_account.all().count() >=1 + except Exception as e: + print(e) + return False + + def get_connection(self): + return self.connection_account.all().first() + + def get_technician(self): + return self.is_technician + + + def get_manager(self): + return self.is_manager + # def ge \ No newline at end of file diff --git a/at_django_boilerplate/accounts/templates/change_password.html b/at_django_boilerplate/accounts/templates/change_password.html new file mode 100755 index 0000000..5a86ba5 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/change_password.html @@ -0,0 +1,75 @@ +{% extends "public_base.html" %} +{% block content %} +
+
+
+
+
+
+

Change Your Password

+
+ {% if form.errors %} + + {% endif %} + +
+
+ {% csrf_token %} +
+
+
+ + + + +
+
+
+
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+{% endblock content %} \ No newline at end of file diff --git a/at_django_boilerplate/accounts/templates/login.html b/at_django_boilerplate/accounts/templates/login.html new file mode 100755 index 0000000..e7ee588 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/login.html @@ -0,0 +1,596 @@ +{% extends 'public_base.html' %} +{% block title %} + Login +{% endblock %} + +{% block content %} +{% load static %} + + + + + + + + +
+
+
+
Login with OTP
+ +
+
+
+

Enter your registered email or mobile number

+ + +
+ + +
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/at_django_boilerplate/accounts/templates/otp/otp_email.html b/at_django_boilerplate/accounts/templates/otp/otp_email.html new file mode 100755 index 0000000..fb3c615 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/otp/otp_email.html @@ -0,0 +1,31 @@ +{% load static %} + + + + + Your OTP for {{ purpose|capfirst }} + + + +
+

Hello,

+

You requested an OTP for {{ purpose|capfirst }}.

+

Your One-Time Password (OTP) is:

+
{{ otp }}
+

This OTP is valid for {{ validity_minutes }} minutes.

+ +

⚠️ Do not share this OTP with anyone.

+ +

If you did not request this OTP, please ignore this email or contact support immediately.

+ + +
+ + diff --git a/at_django_boilerplate/accounts/templates/profile/profile.html b/at_django_boilerplate/accounts/templates/profile/profile.html new file mode 100755 index 0000000..6f5faf2 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/profile/profile.html @@ -0,0 +1,66 @@ +{% extends 'base.html' %} +{% block title %}User Profile{% endblock %} +{% load crispy_forms_tags %} +{% load static %} + +{% block content %} + + + + + +
+
+
+ + +
+ Profile Image +

{{ user }}

+ Update Profile + +
+ + +
+

Account Information

+
+
+

Email

+

{{ user.email }}

+
+
+

Phone

+

{{ user.get_decrypted_contact_number }}

+
+
+ +

Social Media

+
+ + + +
+
+ +
+
+
+ +{% endblock %} diff --git a/at_django_boilerplate/accounts/templates/profile/update_profile.html b/at_django_boilerplate/accounts/templates/profile/update_profile.html new file mode 100755 index 0000000..444b3fd --- /dev/null +++ b/at_django_boilerplate/accounts/templates/profile/update_profile.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% load crispy_forms_tags %} + +{% block title %} + Update Profile +{% endblock %} + +{% block content %} +
+

Update Profile

+ +
+ {% csrf_token %} + {{ form|crispy }} + + +
+
+{% endblock %} diff --git a/at_django_boilerplate/accounts/templates/profile/update_profile_picture.html b/at_django_boilerplate/accounts/templates/profile/update_profile_picture.html new file mode 100755 index 0000000..18bb230 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/profile/update_profile_picture.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} +{% block title %} + Update Profile Picture +{% endblock %} +{% block content %} +
+

Update Profile Picture

+
+ {% csrf_token %} + {{ form.as_p }} + +
+
+{% endblock %} diff --git a/at_django_boilerplate/accounts/templates/registration/create_user.html b/at_django_boilerplate/accounts/templates/registration/create_user.html new file mode 100755 index 0000000..f52a981 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/registration/create_user.html @@ -0,0 +1,21 @@ + + + + Create User + + +

Create User

+
+ {% csrf_token %} + {{ form.as_p }} + +
+ + + diff --git a/at_django_boilerplate/accounts/templates/registration/signup.html b/at_django_boilerplate/accounts/templates/registration/signup.html new file mode 100755 index 0000000..1983bd2 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/registration/signup.html @@ -0,0 +1,316 @@ +{% extends 'public_base.html' %} +{% load static %} +{% load custom_tags %} + +{% block title %}Sign Up | Register your startup{% endblock %} + +{% block content %} + +
+
+
+
+ + + + +
+ +
+
+
+ +
+
+

Create Your Account

+

Join thousands of successful startups

+
+ + + + + + {% if form.errors %} +
+
+ + Please correct the following errors: +
+
    + {% for field, errors in form.errors.items %} + {% for error in errors %} +
  • {{ error }}
  • + {% endfor %} + {% endfor %} +
+
+ {% endif %} + + + + +
+ {% csrf_token %} + + +
+
+
+ +
+

Personal Information

+
+ + + +
+ +
+ +
+ + + + {{ form.first_name|add_class:"appearance-none block w-full pl-10 px-3 py-3 border border-gray-300 rounded-lg shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition duration-200" }} +
+
+ + +
+ +
+ + + + {{ form.last_name|add_class:"appearance-none block w-full pl-10 px-3 py-3 border border-gray-300 rounded-lg shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition duration-200" }} +
+
+
+ + +
+ +
+ +
+ + + + {{ form.contact_number|add_class:"appearance-none block w-full pl-10 px-3 py-3 border border-gray-300 rounded-lg shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition duration-200" }} +
+
+ + +
+ +
+ + + + {{ form.email|add_class:"appearance-none block w-full pl-10 px-3 py-3 border border-gray-300 rounded-lg shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition duration-200" }} +
+
+
+
+ + +
+
+
+ +
+

Security

+
+ + +
+ +
+ + + + {{ form.password|add_class:"appearance-none block w-full pl-10 px-3 py-3 border border-gray-300 rounded-lg shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition duration-200" }} +
+ + +
+ + + +
+
+ + +
+ +
+ + + + {{ form.confirm_password|add_class:"appearance-none block w-full pl-10 px-3 py-3 border border-gray-300 rounded-lg shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition duration-200" }} +
+
+
+ + +
+
+ {{ form.terms_accepted|add_class:"h-5 w-5 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded mt-0.5" }} + +
+ {% if form.terms_accepted.errors %} +
+ {{ form.terms_accepted.errors }} +
+ {% endif %} +
+ + +
+ +
+ + +
+

+ Already have an account? + + Sign in here + +

+
+
+
+
+
+
+
+ + + + +{% endblock %} \ No newline at end of file diff --git a/at_django_boilerplate/accounts/templates/registration/verify_email.html b/at_django_boilerplate/accounts/templates/registration/verify_email.html new file mode 100755 index 0000000..a0435ae --- /dev/null +++ b/at_django_boilerplate/accounts/templates/registration/verify_email.html @@ -0,0 +1,15 @@ +{% extends 'public_base.html' %} + +{% block title %} +Verify +{% endblock %} + +{% block content %} + +

You need to verify your email

+
+ {% csrf_token %} + +
+ +{% endblock %} diff --git a/at_django_boilerplate/accounts/templates/registration/verify_email_complete.html b/at_django_boilerplate/accounts/templates/registration/verify_email_complete.html new file mode 100755 index 0000000..2a9a066 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/registration/verify_email_complete.html @@ -0,0 +1,14 @@ + +{% extends 'public_base.html' %} + +{% block title %} +Verify +{% endblock %} + +{% block content %} + + +
+ You have successfully verified your e-mail +
+{% endblock %} diff --git a/at_django_boilerplate/accounts/templates/registration/verify_email_confirm.html b/at_django_boilerplate/accounts/templates/registration/verify_email_confirm.html new file mode 100755 index 0000000..4d8fde4 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/registration/verify_email_confirm.html @@ -0,0 +1,18 @@ +{% extends 'public_base.html' %} + +{% block title %} +Verify +{% endblock %} + +{% block content %} +
+ {% if messages %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} +
+ +{% endblock %} diff --git a/at_django_boilerplate/accounts/templates/registration/verify_email_done.html b/at_django_boilerplate/accounts/templates/registration/verify_email_done.html new file mode 100755 index 0000000..d6dbbc8 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/registration/verify_email_done.html @@ -0,0 +1,12 @@ +{% extends 'public_base.html' %} + +{% block title %} +Verify +{% endblock %} + +{% block content %} + +
An email has been sent with instructions to verify your email
+
If you have not received the email. Please check the spam folder
+{% endblock %} + diff --git a/at_django_boilerplate/accounts/templates/registration/verify_email_message.html b/at_django_boilerplate/accounts/templates/registration/verify_email_message.html new file mode 100755 index 0000000..445d2a9 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/registration/verify_email_message.html @@ -0,0 +1,20 @@ +
+

Verify Email

+
+

Hi {{ user.name }},

+

You created an account on we-kwick, you need to verify your email. Please click on the button below to verify your email.

+ + + Verify Email + + +

Or you can copy the link below to your browser

+

{{ request.scheme }}://{{ domain }}{% url 'verify-email-confirm' uidb64=uid token=token %}

+

The We-Kwick Team

+
+
+

© {% now 'Y' %} Blog

+

Follow us on Twitter

+
+
diff --git a/at_django_boilerplate/accounts/templates/registration/verify_otp.html b/at_django_boilerplate/accounts/templates/registration/verify_otp.html new file mode 100755 index 0000000..d97faf3 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/registration/verify_otp.html @@ -0,0 +1,49 @@ +{% extends 'public_base.html' %} +{% load static %} +{% block title %}Verify OTP{% endblock %} + +{% block content %} +
+
+

Verify OTP

+ + {% if messages %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} + +
+ {% csrf_token %} +
+ + +
+ +
+ +
+
+ +
+ Didn’t receive the OTP? + Resend +
+
+
+{% endblock %} diff --git a/at_django_boilerplate/accounts/templates/reset/password_reset.html b/at_django_boilerplate/accounts/templates/reset/password_reset.html new file mode 100755 index 0000000..e6b7d63 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/reset/password_reset.html @@ -0,0 +1,62 @@ +{% extends "public_base.html" %} + +{% block content %} +
+
+
+ + +
+

Forgot Password?

+
+ + + {% if form.errors %} +
+
    + {% for key, value in form.errors.items %} +
  • {{ value }}
  • + {% endfor %} +
+
+ {% endif %} + + +
+
+ {% csrf_token %} + + +
+ + +
+ + +
+ +
+
+
+ + + + +
+
+
+{% endblock content %} diff --git a/at_django_boilerplate/accounts/templates/reset/password_reset_complete.html b/at_django_boilerplate/accounts/templates/reset/password_reset_complete.html new file mode 100755 index 0000000..c8e11b8 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/reset/password_reset_complete.html @@ -0,0 +1,9 @@ + +{% extends 'base.html' %} + +{% block title %}Password reset complete{% endblock %} + +{% block content %} +

Password reset complete

+

Your new password has been set. You can log in now on the log in page.

+{% endblock %} \ No newline at end of file diff --git a/at_django_boilerplate/accounts/templates/reset/password_reset_confirm.html b/at_django_boilerplate/accounts/templates/reset/password_reset_confirm.html new file mode 100755 index 0000000..509132d --- /dev/null +++ b/at_django_boilerplate/accounts/templates/reset/password_reset_confirm.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} + +{% block title %}Password Reset{% endblock %} + +{% block content %} +{% load crispy_forms_tags %} + + + + +{% if validlink %} +
+
+
+
+

Set a New Password

+
+ {% csrf_token %} + {{form|crispy}} + + +
+
+
+
+
+ +{% else %} +

The password reset link was invalid, possibly because it has already been used. Please request a new password reset.

+{% endif %} +{% endblock %} diff --git a/at_django_boilerplate/accounts/templates/reset/password_reset_done.html b/at_django_boilerplate/accounts/templates/reset/password_reset_done.html new file mode 100755 index 0000000..45c6ca8 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/reset/password_reset_done.html @@ -0,0 +1,26 @@ + +{% extends "base.html" %} + +{% block title %}Email Sent{% endblock %} + +{% block content %} + +
+ {% if messages %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} +
+ +

Check your inbox.

+

We've emailed you instructions for setting your password. You should receive the email shortly!

+ +{% endblock %} \ No newline at end of file diff --git a/at_django_boilerplate/accounts/templates/reset/password_reset_email.html b/at_django_boilerplate/accounts/templates/reset/password_reset_email.html new file mode 100755 index 0000000..5e4e76e --- /dev/null +++ b/at_django_boilerplate/accounts/templates/reset/password_reset_email.html @@ -0,0 +1,13 @@ +{% autoescape off %} + To initiate the password reset process for your we-kwick account, + click the link below: + + {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} + + If clicking the link above doesn't work, please copy and paste the URL below in a new browser + window instead. + + Sincerely, + we-kwick Team +{% endautoescape %} + diff --git a/at_django_boilerplate/accounts/templates/reset/password_reset_email_html.html b/at_django_boilerplate/accounts/templates/reset/password_reset_email_html.html new file mode 100755 index 0000000..05ae679 --- /dev/null +++ b/at_django_boilerplate/accounts/templates/reset/password_reset_email_html.html @@ -0,0 +1,24 @@ +
+

+ Password Reset +

+
+

Hi {{ user.name }},

+

+ To initiate the password reset process for your we-kwick account, + click the link below: + + + Reset Password + + +

Or you can copy the link below to your browser

+

{{ request.scheme }}://{{ domain }}{% url 'verify-email-confirm' uidb64=uid token=token %}

+

The We-Kwick Team

+
+
+

© {% now 'Y' %} Blog

+

Follow us on Twitter

+
+
diff --git a/at_django_boilerplate/accounts/templates/reset/password_reset_subject.txt b/at_django_boilerplate/accounts/templates/reset/password_reset_subject.txt new file mode 100755 index 0000000..d26e69a --- /dev/null +++ b/at_django_boilerplate/accounts/templates/reset/password_reset_subject.txt @@ -0,0 +1 @@ +We-Kwick Password Reset \ No newline at end of file diff --git a/at_django_boilerplate/accounts/templates/user_list.html b/at_django_boilerplate/accounts/templates/user_list.html new file mode 100755 index 0000000..de2317f --- /dev/null +++ b/at_django_boilerplate/accounts/templates/user_list.html @@ -0,0 +1,25 @@ + + + + User List + + +

User List

+
    + {% for user in users %} +
  • + Name: {{ user.name }}, Birthday: {{ user.birthday }}, + Address: {{ user.address }}, Email: {{ user.email_address }}, + Contact: {{ user.contact_number }} +
  • + {% endfor %} +
+ + + diff --git a/at_django_boilerplate/accounts/templatetags/__init__.py b/at_django_boilerplate/accounts/templatetags/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/accounts/templatetags/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/accounts/templatetags/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21733db873a3c51e6878fcd26dc869c8d8304e52 GIT binary patch literal 206 zcmX@j%ge<81UI>ZGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!%GJ-v&rQ`&%quNQ zOxJfwEzT~gjEsy$%s>_ZH+4Ce literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/templatetags/__pycache__/custom_tags.cpython-312.pyc b/at_django_boilerplate/accounts/templatetags/__pycache__/custom_tags.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89e8c906d95568e40f87ae0a3aca142a087e95c3 GIT binary patch literal 602 zcmYjNziZn-7`>A$*N$o^bm(9bhjb|F&~pie3?UwpEFmPp?$RvctUIf6Bum_#2!;$U z6yi+D<~3tN@}Fc$@KAbCFm&qH;w@9}rT5Jd10LSO(2yM=u4|Eo209GcYZmS=n(iE(S*z*&V9zJh=AR^M`pzjVWa zvLtAfgji5n#)P26IfbNs_+>CO40t0w!WT@F+rc>h$7re8TQc@Wn5q;(gb1Qk}mG~vD=P=kh1JBggq}#dxE3{14|M9O2Gh3>=);av&Q9{tJTY;VPkC9?+f**YJokcD2uaIOn-`ewi~n=W_?-8 zeaT`c2$jD~^ct{=8e`8DJeJm@n4}@yU@ujwf02Js@Y{qC{s7g9T^+7{v#Sqwb!;yV eN)uQC-75VzmU$7@Xo5L7}DDr4Rf3zeY*Vks;YDk$kfmdMF-9)5Wrk?kiMiJ?eH zFv66-0hRxPk%g*5g{4ZBU||Dg>%{pbZY56g-S2#Nzw@2HZEUO~kRPNgzJmQ}9Vgxj zEQSE)C_)iVQHJ{%TkNG?=J$P!Jj8JlxT0%&yn_8YuO~OI?;VQ#cjzjoIje>B-!>Ft z6KI}IQ>MAa2mDYo$=L|ILa;SBKAgoKs5#PCu{S7j&xVMT#OD7ey5TO4R~0?zy&u-h zx=SMvv$TVJyDCSyPo)F6>7-f6jzbxphBIA_hrDQRE!qr3bT~J5p7rFD1&nKTIXPfPl&Gwh}DcSyB+g?Br?T3MBEPG13I&*vJ zbSnDJazIs0XqwN&P%=3)fqT1Yj717FR2@GfNq|AFfw~MHWkpn`{IR?Ru3d_H0P2sA rF+NAFa}-{X)`y)l(mElnQ*!SpxWH@gx8833^)P<8@R4`3LKWpddFhxO literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/accounts/templatetags/custom_tags.py b/at_django_boilerplate/accounts/templatetags/custom_tags.py new file mode 100755 index 0000000..1d19690 --- /dev/null +++ b/at_django_boilerplate/accounts/templatetags/custom_tags.py @@ -0,0 +1,7 @@ +from django import template + +register = template.Library() + +@register.filter(name='add_class') +def add_class(field, css): + return field.as_widget(attrs={"class": css}) diff --git a/at_django_boilerplate/accounts/templatetags/forms_tags.py b/at_django_boilerplate/accounts/templatetags/forms_tags.py new file mode 100755 index 0000000..9c16ac9 --- /dev/null +++ b/at_django_boilerplate/accounts/templatetags/forms_tags.py @@ -0,0 +1,8 @@ +from django import template +from django.forms.widgets import Textarea + +register = template.Library() + +@register.filter +def is_textarea(field): + return isinstance(field.field.widget, Textarea) diff --git a/at_django_boilerplate/accounts/tests.py b/at_django_boilerplate/accounts/tests.py new file mode 100755 index 0000000..7ce503c --- /dev/null +++ b/at_django_boilerplate/accounts/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/at_django_boilerplate/accounts/tokens.py b/at_django_boilerplate/accounts/tokens.py new file mode 100755 index 0000000..a9c4d5f --- /dev/null +++ b/at_django_boilerplate/accounts/tokens.py @@ -0,0 +1,10 @@ +from django.contrib.auth.tokens import PasswordResetTokenGenerator +from six import text_type + +class TokenGenerator(PasswordResetTokenGenerator): + def _make_hash_value(self, user, timestamp): + return ( + text_type(user.pk) + text_type(timestamp) + + text_type(getattr(user, 'is_active', True)) + ) +account_activation_token = TokenGenerator() \ No newline at end of file diff --git a/at_django_boilerplate/accounts/urls.py b/at_django_boilerplate/accounts/urls.py new file mode 100755 index 0000000..6b3a245 --- /dev/null +++ b/at_django_boilerplate/accounts/urls.py @@ -0,0 +1,57 @@ +# urls.py +from django.urls import path +from django.contrib.auth.decorators import login_required +from django.contrib.auth import views as auth_views + +from . import views +from at_django_boilerplate.accounts.api import v1 as api_v1 + +urlpatterns = [ + path('signup/', views.SignupView.as_view(), name='signup'), + path('ajax/check_username/', views.CheckUsernameAvailability.as_view(), name='check_username'), + + path('login/', views.LoginPageView.as_view(), name='login'), + path('login/', views.LoginPageView.as_view(), name='signin'), + path('login', views.LoginPageView.as_view(), name='login'), + path('login', views.LoginPageView.as_view(), name='signin'), + + path('profile/', login_required(views.ProfileView.as_view()), name='user_profile'), + path('update_profile/', login_required(views.UpdateProfileView.as_view()), name='update_profile'), + path('update_profile_picture/', login_required(views.update_profile_picture), name='update_profile_picture'), + + + path('password-reset/', views.ResetPasswordView.as_view(), name='password_reset'), + path('password-reset/applied', views.ResetPasswordDoneView.as_view(), name='password_reset_done'), + + path('password-reset-confirm///', + auth_views.PasswordResetConfirmView.as_view(template_name='reset/password_reset_confirm.html'), + name='password_reset_confirm'), + path('password-reset-complete/', + auth_views.PasswordResetCompleteView.as_view(template_name='reset/password_reset_complete.html'), + name='password_reset_complete'), + path('password_change/',views.ChangePasswordView.as_view()), + + path('verify-email/', views.verify_email, name='verify-email'), + path('verify-email/done/', views.verify_email_done, name='verify-email-done'), + path('verify-email-confirm///', views.verify_email_confirm, name='verify-email-confirm'), + path('verify-email/complete/', views.verify_email_complete, name='verify-email-complete'), + + path('logout/', views.LogoutView.as_view(), name='logout'), + + # Otp via signin + path('request-otp/', views.request_otp_view, name='request_otp'), + path('verify-otp/', views.verify_otp_view, name='verify_otp'), + +] + + +api_v1=[ + path('api/login/', api_v1.LoginApiView.as_view(), name='login-api'), + path('validate-password/', api_v1.ValidatePasswordAPIView.as_view(), name='validate-password-api'), + path('api/reset-password/', api_v1.ResetPasswordAPIView.as_view(), name='reset-password-api'), + path('reset-password///', api_v1.ResetPasswordConfirmAPIView.as_view(), name='reset-password-confirm'), + path('api/profile/', api_v1.UserProfileView.as_view(), name='profile-api'), +] + + +urlpatterns +=api_v1 \ No newline at end of file diff --git a/at_django_boilerplate/accounts/utils.py b/at_django_boilerplate/accounts/utils.py new file mode 100755 index 0000000..f3cf3bc --- /dev/null +++ b/at_django_boilerplate/accounts/utils.py @@ -0,0 +1,189 @@ +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode +from django.template.loader import render_to_string +from .tokens import account_activation_token +from django.core.mail import EmailMessage +from django.shortcuts import redirect +from django.core.mail import EmailMultiAlternatives +from django.conf import settings + +from django.contrib.auth import login +from .models import CustomUser +from at_django_boilerplate.utils.hash_utils import hexdigest +from at_django_boilerplate.utils.encryption_utils import EncryptionUtils + +def send_activation_email(request, user, to_email): + try: + domain = request.get_host() + message = render_to_string('registration/verify_email_message.html', { + 'request': request, + 'user': user, + 'domain': domain, + 'uid': urlsafe_base64_encode(force_bytes(user.pk)), + 'token': account_activation_token.make_token(user), + }) + + subject = 'Welcome to we-kwick. Verify your Email' + from_email = settings.EMAIL_HOST_USER + _to_email = [to_email] + text_content = '' + html_content = message + + email = EmailMultiAlternatives(subject, text_content, from_email, _to_email) + email.attach_alternative(html_content, "text/html") + email.send() + print(f'Sent Joining Email to {to_email} ') + return True + except Exception as e: + print('Failed to Send Email') + return False + + +def redirect_to_next_or_home(request): + try: + if 'next' in request.POST: + _next = request.POST.get('next') + if _next is not None: + if _next == '' or _next == '/': + return redirect('home') + else: + return redirect(_next) + else: + return redirect('home') + else: + return redirect('home') + except Exception as e: + print('Exception Post:', e) + return redirect('home') + + +def save_user(request,user_form): + email = user_form.cleaned_data['email'].lower() + contact_number = user_form.cleaned_data['contact_number'] + email_hash = hexdigest(email) + existing_email = CustomUser.objects.filter(email_hash=email_hash) + existing_email_exists = existing_email.exists() + + existing_number_exists = False + if all([contact_number != '',contact_number != None]): + contact_number_hash = hexdigest(contact_number) + existing_number = CustomUser.objects.filter(contact_number_hash=contact_number_hash) + existing_number_exists = existing_number.exists() + + if existing_email_exists: + user_form.add_error('email', 'Email is already in use.') + elif existing_number_exists: + user_form.add_error('contact_number', 'Contact number is already in use.') + else: + try: + user = user_form.save(commit=False) + user.set_password(user.password) + user.save(custom_save=True) + login(request, user) + user.is_active = False + user.save() + send_activation_email(request=request,user=user,to_email=user.get_decrypted_email()) + return True,user + except Exception as e: + return False,user_form + + return False,user_form + + + +# Otp +import random +import re +from django.core.mail import send_mail +from django.core.cache import cache +from django.conf import settings + +PHONE_REGEX = re.compile(r'^\+?\d{10,15}$') + +def generate_otp(length=6): + return ''.join([str(random.randint(0, 9)) for _ in range(length)]) + +# def send_otp(identifier, purpose='login'): +# """ +# Sends an OTP via email or SMS based on the identifier. +# Returns a tuple: (success: bool, method: 'email'|'sms') +# """ +# otp = generate_otp() +# cache_key = f'otp_{purpose}_{identifier}' + +# if PHONE_REGEX.match(identifier): +# method = 'sms' +# cache.set(cache_key, otp, timeout=300) +# # Replace with your SMS API logic (e.g., Twilio) +# print(f"📲 SMS OTP sent to {identifier}: {otp}") +# else: +# method = 'email' +# cache.set(cache_key, otp, timeout=300) +# try: +# send_mail( +# subject=f"Your {purpose.capitalize()} OTP", +# message=f"Your {purpose} OTP is: {otp}\n\nThis OTP is valid for 5 minutes.", +# from_email=settings.EMAIL_HOST_USER, +# recipient_list=[identifier], +# fail_silently=False, +# ) +# except Exception as e: +# print(f"❌ Failed to send OTP email to {identifier}: {e}") +# return False, 'email' + +# return True, method + +from django.template.loader import render_to_string +from django.core.mail import EmailMessage + +def send_otp(identifier, purpose='login'): + """ + Sends an OTP via email or SMS based on the identifier. + Returns a tuple: (success: bool, method: 'email'|'sms') + """ + otp = generate_otp() + cache_key = f'otp_{purpose}_{identifier}' + + if PHONE_REGEX.match(identifier): + method = 'sms' + cache.set(cache_key, otp, timeout=300) + # Replace with your SMS API logic + print(f"📲 SMS OTP sent to {identifier}: {otp}") + else: + method = 'email' + cache.set(cache_key, otp, timeout=300) + + try: + email_subject = f"Your {purpose.capitalize()} OTP" + email_body = render_to_string('otp/otp_email.html', { + 'otp': otp, + 'purpose': purpose, + 'identifier': identifier, + 'validity_minutes': 5, + }) + email = EmailMessage( + subject=email_subject, + body=email_body, + from_email=settings.EMAIL_HOST_USER, + to=[identifier], + ) + email.content_subtype = 'html' # Important for HTML emails + email.send(fail_silently=False) + except Exception as e: + print(f"❌ Failed to send OTP email to {identifier}: {e}") + return False, 'email' + + return True, method + + +def verify_otp(identifier, user_input_otp, purpose='login'): + """ + Verifies the OTP entered by the user against the cache. + """ + cache_key = f'otp_{purpose}_{identifier}' + cached_otp = cache.get(cache_key) + + if cached_otp and cached_otp == user_input_otp: + cache.delete(cache_key) + return True + return False diff --git a/at_django_boilerplate/accounts/views.py b/at_django_boilerplate/accounts/views.py new file mode 100755 index 0000000..46bf034 --- /dev/null +++ b/at_django_boilerplate/accounts/views.py @@ -0,0 +1,415 @@ +from django.urls import reverse_lazy +from django.views import View +from django.views.generic import FormView +from django.views.generic.edit import UpdateView +from django.views.generic.detail import DetailView +from django.views.generic.list import ListView +from django.contrib.auth import login,authenticate,logout +from django.contrib import messages +from django.shortcuts import redirect, render +from django.db import IntegrityError +import hashlib +from django.contrib.auth.tokens import default_token_generator +from django.contrib.auth.decorators import login_required + +from .forms import (UserSignUpForm, + SigninForm, + CustomPasswordResetForm, + ProfilePictureUpdateForm, + UpdateProfileForm) +from django.contrib.auth.views import (PasswordResetView, + PasswordChangeView) + +from django.http import JsonResponse +from django.contrib.messages.views import SuccessMessageMixin +from django.contrib.auth.mixins import UserPassesTestMixin + +from .models import CustomUser + +from django.utils.encoding import force_str +from django.utils.http import urlsafe_base64_decode +from .tokens import account_activation_token +from django.contrib import messages +from django.contrib.auth import authenticate, login +from django.utils.http import url_has_allowed_host_and_scheme + + +from .utils import (send_activation_email, + redirect_to_next_or_home, + ) +from at_django_boilerplate.utils.encryption_utils import EncryptionUtils + +from django.shortcuts import render, redirect +from .forms import UserSignUpForm + + + +import logging +logger = logging.getLogger(__name__) + +class CheckUsernameAvailability(View): + def get(self, request): + username = request.GET.get('username', None) + is_taken = CustomUser.objects.filter_by_email(username).exists() + data = { + 'is_taken': is_taken + } + return JsonResponse(data) + + +class SignupView(FormView): + template_name = 'registration/signup.html' + form_class = UserSignUpForm + success_url = reverse_lazy('home') + + def post(self, request, *args, **kwargs): + next_url = request.GET.get('next') + user_form = self.form_class(request.POST) + + if user_form.is_valid(): + email = user_form.cleaned_data['email'].lower() + contact_number = user_form.cleaned_data.get('contact_number') + + email_exists = CustomUser.objects.filter_by_email(email).exists() + number_exists = False + + if contact_number: + number_exists = CustomUser.objects.filter_by_contact_number(contact_number).exists() + + if email_exists: + user_form.add_error('email', 'Email is already in use.') + elif number_exists: + user_form.add_error('contact_number', 'Contact number is already in use.') + else: + try: + user = user_form.save(commit=False) + user.set_password(user.password) + user.save() + login(request, user) + + user.is_active = True + user.save() + + send_activation_email( + request=request, + user=user, + to_email=user.get_decrypted_email() + ) + + # return redirect(next_url or 'verify-email-done') + return redirect('dashboard') + + except IntegrityError: + # Rollback if needed + user.delete() + + return self.render_to_response(self.get_context_data(form=user_form)) + + +def verify_email(request): + return render(request, 'registration/verify_email.html') + + +def verify_email_done(request): + return render(request, 'registration/verify_email_done.html') + + +def verify_email_confirm(request, uidb64, token): + try: + uid = force_str(urlsafe_base64_decode(uidb64)) + user = CustomUser.objects.get(pk=uid) + except(TypeError, ValueError, OverflowError, CustomUser.DoesNotExist): + user = None + if user is not None and account_activation_token.check_token(user, token): + user.is_active = True + user.save() + messages.success(request, 'Your email has been verified.') + return redirect('verify-email-complete') + else: + messages.warning(request, 'The link is invalid.') + return render(request, 'registration/verify_email_confirm.html') + + +def verify_email_complete(request): + return render(request, 'registration/verify_email_complete.html') + + + +class LoginPageView(View): + template_name = 'login.html' + form_class = SigninForm + + def get(self, request): + if request.user.is_authenticated: + next_url = request.GET.get('next') + if next_url and url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}): + return redirect(next_url) + return redirect('home') + + form = self.form_class() + return render(request, self.template_name, {'form': form, 'message': ''}) + + def post(self, request): + print('Login') + form = self.form_class(request.POST) + message = 'Login failed!' + + if form.is_valid(): + print('Form Valid') + username = form.cleaned_data['username'].lower() + password = form.cleaned_data['password'] + + user = authenticate(request, username=username, password=password) + # user.backend = 'accounts.auth_backends.CustomAuthBackend' # <--- key line added + print(f"Authenticated User: {user}") + if user: + if user.is_active: + login(request, user) + return redirect_to_next_or_home(request) + else: + messages.error(request, 'Please verify your account before logging in.') + else: + messages.error(request, 'Invalid username or password.') + else: + messages.error(request, 'Invalid input.') + + return render(request, self.template_name, { + 'form': form, + 'message': message, + }) + + + +class LogoutView(View): + def get(self, request): + logout(request) + return redirect('home') + + +class ResetPasswordView(SuccessMessageMixin, PasswordResetView): + template_name = 'reset/password_reset.html' + email_template_name = 'reset/password_reset_email.html' + html_email_template_name = 'reset/password_reset_email_html.html' + subject_template_name = 'reset/password_reset_subject.txt' + + success_message = "We've emailed you instructions for setting your password, " \ + "if an account exists with the email you entered. You should receive them shortly." \ + " If you don't receive an email, " \ + "please make sure you've entered the address you registered with, and check your spam folder." + success_url = reverse_lazy('password_reset_done') + token_generator = default_token_generator + form_class = CustomPasswordResetForm + +class ResetPasswordDoneView(View): + template_name = 'reset/password_reset_done.html' + success_message = "We've emailed you instructions for setting your password, " \ + "if an account exists with the email you entered. You should receive them shortly." \ + " If you don't receive an email, " \ + "please make sure you've entered the address you registered with, and check your spam folder." + + def get(self, request): + return render(request=request,template_name=self.template_name) + + + +class ChangePasswordView(SuccessMessageMixin, PasswordChangeView): + template_name = 'change_password.html' + success_message = "Successfully Changed Your Password" + success_url = reverse_lazy('home') + + + + +class CustomUserListView(ListView): + model = CustomUser + template_name = 'user_list.html' + users = 'CustomUsers' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + users = CustomUser.objects.all() + context['users'] = [{ + 'username': custom_user.username, + 'name': custom_user.get_decrypted_name(), + 'email': custom_user.get_decrypted_email(), + 'birthday': custom_user.birthday, + 'address': custom_user.get_decrypted_address(), + 'contact_number': custom_user.get_decrypted_contact_number() + } for custom_user in users] + return context + +class ProfileView(DetailView): + model = CustomUser + template_name = 'profile/profile.html' + context_object_name = 'custom_user' + + def get_object(self): + return self.request.user + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Initialize the form with the current user's data + form = ProfilePictureUpdateForm(instance=self.get_object()) + context['update_profile_photo_form'] = form + return context + +class UpdateProfileView(UserPassesTestMixin, UpdateView): + model = CustomUser + form_class = UpdateProfileForm + template_name = 'profile/update_profile.html' + + def test_func(self): + obj = self.get_object() + requested_by_user = self.request.user + return (obj == requested_by_user) + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + # Decrypt the data before passing it to the form + encryption_tool = EncryptionUtils() + contact_number = None + if self.object.contact_number: + contact_number = encryption_tool.decrypt(self.object.contact_number) + + initial_data = { + 'contact_number': contact_number + } + form = self.form_class(instance=self.object, initial=initial_data) + return self.render_to_response(self.get_context_data(form=form)) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + form = self.form_class(request.POST, instance=self.object) + if form.is_valid(): + obj = form.save(commit=False) + encryption_tool = EncryptionUtils() + # address = form.cleaned_data['address'] + # obj.address = encryption_tool.encrypt(address) + contact_number = form.cleaned_data['contact_number'] + if all([contact_number!=None,contact_number!='']): + obj.contact_number = encryption_tool.encrypt(contact_number) + obj.save() + + return self.form_valid(form) + else: + # Decrypt the data before re-rendering the form with errors + encryption_tool = EncryptionUtils() + contact_number = None + if self.object.contact_number: + contact_number = encryption_tool.decrypt(self.object.contact_number) + + initial_data = { + 'contact_number': contact_number + } + form = self.form_class(instance=self.object, initial=initial_data) + + return self.form_invalid(form) + +@login_required +def update_profile_picture(request): + if request.user.pk !=None: + user_profile = request.user + if request.method == 'POST': + form = ProfilePictureUpdateForm(request.POST, request.FILES, instance=user_profile) + if form.is_valid(): + form.save() + messages.success(request, 'Your profile picture has been updated successfully!') + return redirect('user_profile') + else: + form = ProfilePictureUpdateForm(instance=user_profile) + return render(request, 'profile/update_profile_picture.html', {'form': form}) + + +from django.core.validators import EmailValidator, ValidationError +from django.core.cache import cache +from .utils import send_otp, verify_otp +from .models import CustomUser +import re + +PHONE_REGEX = re.compile(r'^\+?\d{10,15}$') + + + +def is_email(identifier): + validator = EmailValidator() + try: + validator(identifier) + return True + except ValidationError: + return False + +def request_otp_view(request): + if request.method == 'POST': + identifier = request.POST.get('identifier', '').strip().lower() + + if not identifier: + messages.error(request, "Please enter your email or mobile number.") + return redirect('login') # Assuming OTP modal is on the login page + print(f"Identifier: {identifier}") + user = None + if is_email(identifier): + print(f"Email: {identifier}") + user = CustomUser.objects.filter_by_email(email=identifier).first() + else: + if not PHONE_REGEX.match(identifier): + messages.error(request, "Invalid mobile number format.") + return redirect('login') + print(f"Mobile hash: {identifier}") + user = CustomUser.objects.filter_by_contact_number(contact_number=identifier).first() + print(f"User: {user}") + if not user: + messages.error(request, "No user found with this email or mobile number.") + return redirect('login') + + # Rate limiting + cache_key = f"otp_rate_limit_{identifier}" + if cache.get(cache_key): + messages.error(request, "Please wait before requesting another OTP.") + return redirect('login') + + # Send OTP + success, _ = send_otp(identifier, purpose='login') + if success: + cache.set(cache_key, True, timeout=60) # 60 seconds + request.session['otp_identifier'] = identifier + messages.success(request, "OTP sent successfully.") + + return redirect('verify_otp') + else: + messages.error(request, "Failed to send OTP. Please try again.") + return redirect('login') + + return redirect('login') + + +def verify_otp_view(request): + identifier = request.session.get('otp_identifier') + if not identifier: + messages.error(request, "Session expired. Please request a new OTP.") + return redirect('login') + + if request.method == 'POST': + user_otp = request.POST.get('otp', '').strip() + + if not user_otp: + messages.error(request, "Please enter the OTP.") + return render(request, 'registration/verify_otp.html') + + if verify_otp(identifier, user_otp): + # Match user + if is_email(identifier): + print(f"Email: {identifier}") + user = CustomUser.objects.get_by_email(email=identifier) + else: + + user = CustomUser.objects.get_by_contact_number(contact_number=identifier) + + # user.backend = 'django.contrib.auth.backends.ModelBackend' # <--- key line added + login(request, user) + messages.success(request, "Logged in successfully.") + request.session.pop('otp_identifier', None) + return redirect('home') + else: + messages.error(request, "Invalid or expired OTP.") + + return render(request, 'registration/verify_otp.html') diff --git a/at_django_boilerplate/backend_admin/__init__.py b/at_django_boilerplate/backend_admin/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/backend_admin/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/backend_admin/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..79c601572766516d7f76c28b0cc642597c3ab70b GIT binary patch literal 198 zcmX@j%ge<81UI>ZGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!O4ZNE&rQ`&%quNQ zOxJfwEzT~gjEsy$%s>_ZD$F(| literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/backend_admin/__pycache__/admin.cpython-312.pyc b/at_django_boilerplate/backend_admin/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a3e38321a70893f47dfd9133af5e76ffc55a6b2 GIT binary patch literal 386 zcmYL_u}%U(5Qb+wxDW`&1RsC`3mWzXj1AIQh&gPeA$#n4F5K;2_Vx-&E8`RR48}*X z(ZU!np|YYQp`&sayfDSg_wQzA{@wRxvko?X8i9DW_1B3qynomP7G8h>!x$1ABLX{O zUVM~zV-JDjtX8%h>a20#lDPMVdq!APy495vT`a1UB|@&;omD5J!LYC7y@+y6jZpHs z>leptMrEXWQzeZSp?2O1KT(XwnXWof$%NrWP4h@(hU-_@*i+bsKrF?ipFzrnQHNOK2~lv-Sb~r}|_48(N>x`Z(=;!I^en^atNCVebF{ literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/backend_admin/__pycache__/apps.cpython-312.pyc b/at_django_boilerplate/backend_admin/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5506a9ae24c576a6e864d149759388f6f25627f GIT binary patch literal 539 zcmZuuJxc>Y5Z%4INX#V&L4*_*A_&*qYG)xrK2)%XlGu37vdM1FGxz1*U2*Az{D=4# z#L}N(V;K*utn7qnlgiy(QV32l`-V5~F+1~8sg!_-H@)LLi}ynot0*kNY)^m*pnyUb zc+i3nDBuiGWeljw;FFs7SxTxIg-=-+Fk7#OVKeXtj@{4cJi}!%+Z12|7_cA(mO`PW zQpM6z)zGqPgY<{Yr*-N%e(o{arnkhm1C#blFQCkg%!XsvlQ=kam`g{yB*u9M?gfs^ zc<7RtnZ4Wy6Y1183Rw|jpLh)8tc0;JN?eJ{7~dttT{2b51|)G~OoTliNdHWe+#{ID z>E{I91Rg`gPdKqri$%jY2+?Kd;{2p})kU4_F5=8~qL}eCNO%+z9w#CCcSQ7OL`1?c zGQ*V1)i07JKvWTF45qciS8Z#m@87q0dBwL4HPe^7kzy{7k;`?77L8IrFCOLpJ?0yt bly`{a4Zc(e;XBy*UQ^(~L+uBMXQBE9g&mW* literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/backend_admin/__pycache__/forms.cpython-312.pyc b/at_django_boilerplate/backend_admin/__pycache__/forms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c16f439f227a862f514ce945ec408b938ffa092 GIT binary patch literal 722 zcmZuvzi-n(6uuv}lm3X5LM47=>A>Qpp4bo_N{fWl79}irIbC{};Pz~Xvpr<*02^Cj zVn+Q__y>xmPE6ep0U>4LJzt`#h?D&Jdw1{N_wKv<+Ucwk7~j_V$p>KHF<1_NL1z0v z&WR$58j{kOQlgmRhNV2_lrZv~DDR9YU(=KJa`Vmqf;%4j(;#eJV-V8XALTYJuBnT< zgT15OJR2qB%E~gyvtzf%p|=QTTR_fM$D%?^y-fOzG5BV zj9hlN-uags58nrN119WUfWy$UYZzdP?JTEl7g>{a2U%+gc&-~UZ#(Q_OOc#enuN4dZ%FMfJ?(vyQS4Thra6-R`tj%cef&P} zV>`bO4h{gc{c&|$ycGrDFMM*bh5jA9{SpnH0Raev1Zu=CL4W|b1BAd+AOso@4jdHp`~<-C4F zzMR)@*q8JAUH9d@em8pDM87NCdm)T|S6)RvG~|e{qc`)YFXzo1`_Gxg_O!wAT?Fv9#V_9DZa{S9B)8Pr;BvJ z89-rHf^x;EI?*bWRMvnG4RMA*R$pvFgQEnf^O~sQ3_V93avEQykVKTB-uIpR$oRn{ zy2QzfEb^R0eZPH|G8BqyD55|KbyY%915&&q8&EcibkGTvB~CtYNVzUa8h%#q(FrHQ zvNBhLEb9!gEPCGS5*}Y;*~2;~^{;67<={k1Xi6(0lmus_svrfPFIz+R&i3lyjC~Y! z&Bz(%%1YgE(=k`XLmG-A=^oM*ag#Uj)KHDxY{CkqG@D@L5?|B9<6vN4=p)1T1)DH|rW$tfptuL1z>@eFu?l2mxh&sBpBc-nC2B#Ty zmEjCl*yrSm!j=_Lf|@F!_F~E$e*jSdIH4xWO#gdPRF5?Lndt3s@94jwK7R_%!yq-& zT(rlht?}uWZm#H^al<4N-Q3)1%+AeOxw-bvIRKl)YTz9JH`f9tnK_G3o;2+Eq!piR z&70pUow#a}Bi;0D^9MUUYo%w~pCic{v4ka=Wh5En)1F6JHi#9Rg}Q^Xq|Opk&9I%AvJz9Rl37wai9?ed?PiM2Jv&phGR1Zt zN!E#FEXiWY_*rb?WY3OGSh0y#-uy=H#FU@6x~L{Ac522-&9s9^u}YM%BE5tZ$uqZ| zxz^}penLLo!oul_U0ARR3$L-{KCyu%x9%cIuA94k8hep_o^2Vg3+DFYPVNaVqq|_{ zYQ2+gph|V~^QVh;e%{K@w{>K+MXY0^!Ui&;x)gI7elhfXs3p7}F~2+RP>(G#t>rIP z_8v_-(SA!5g>wHT5!%H682YP!6N5dvfJW~lU-jQEUu%=-6g33U$I$LPND#!IAoY$M TC!P=|jc3Qd9RCgQxTpOGCAU(? literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/backend_admin/__pycache__/urls.cpython-312.pyc b/at_django_boilerplate/backend_admin/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77cf8ba691d1ab42c47d32aba6c194504b9c7d13 GIT binary patch literal 1157 zcmaJ=y-(Xf6h9{p3Hcy=sYF240i^|jaHXqA`t~_jpSac z--7`Bbe0zEaQKYZOAnP&Y^E`y2eY_as4SQMNqIe%K!FZ#7-a}}pkC$Y; zaWCszG~CAD3T4h=xY8jI|kz`+_`RLzlQ&*Ha?h$lHx*^$b&`dzX&{fTB zV9lhGJKW=TP!(#5zG#s;CHywJfP|PKVUCmQ-Pvri z(d_eqh9T`tmhg*8_$wfBzVSQiCU!TrSOJsiaAT||`#&Zo8vO`v7z5lw=}>QA32GLB zij>E+X6lBty|Y!^Sbx4N?Y!8P2v(RHCP%tOs0oQ_84@(*?sv*%U9Dqc)S-!`GOX0F zhUCudBz}TLiu`WS&uqI23WD$pO#dDL!uWL*ga(hN-&)SZf<3X& z%I4eR#)TMZ4bHsZbJELpdf7>@+3B_ObpC6uEtWdD#j}u;DcG4pYa6%4gHG(>hmbS7 zYR|5=@_TJ@{~{P}MIN6mIoYC}Ew-Mab`bv%BgYR;(#~YYp3JnK6xw3(dVuTSKM6Uh ec{?@lq_TD@d!AbQve_03S5wJk;7yG1ck%}f0y9zo literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/backend_admin/__pycache__/utils.cpython-312.pyc b/at_django_boilerplate/backend_admin/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac71f1bf18ac9718f496c2ed936cbd07826f4c7f GIT binary patch literal 286 zcmXv}J5Izf6nu^&6j=5K2#GSy`T~dsXea<>Y1k%m)=$B#KauSe;Ub)Yvv2|;K>^iv zNZW0N4eXQN)68fzqxU+U9zdM8NhLq`0KO90P4*8qM-e=O06`RJvLGbkOl6ubD1m{N z`8rukGVTYdQJ@Muk|L2OYw)_v$3pOcNpLh;$3yYRO>1X;kjlF}N-wZUA=`#MSdEOE z-tm@QpkD>svD@nA>hk<<&Z_%4bJ$8B&<(bCK5!TMj`2|cMb?d#3SFmSA8Yt>g+|m| lXldAQ*{mD3#i_QUS9s>q_av^L#PdxFA@6Yf0Vl4A{0A7CQ0f2x literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/backend_admin/__pycache__/views.cpython-312.pyc b/at_django_boilerplate/backend_admin/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9c982c1e6c644a17561c7fc2b9c5e5b040bb52f GIT binary patch literal 8980 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 GJoUfDDZj=5 literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/backend_admin/admin.py b/at_django_boilerplate/backend_admin/admin.py new file mode 100755 index 0000000..b1148cd --- /dev/null +++ b/at_django_boilerplate/backend_admin/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from .models import SEOConfiguration +# Register your models here. + +admin.site.register(SEOConfiguration) \ No newline at end of file diff --git a/at_django_boilerplate/backend_admin/apps.py b/at_django_boilerplate/backend_admin/apps.py new file mode 100755 index 0000000..366e507 --- /dev/null +++ b/at_django_boilerplate/backend_admin/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BackendAdminConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'at_django_boilerplate.backend_admin' diff --git a/at_django_boilerplate/backend_admin/forms.py b/at_django_boilerplate/backend_admin/forms.py new file mode 100755 index 0000000..35fa87f --- /dev/null +++ b/at_django_boilerplate/backend_admin/forms.py @@ -0,0 +1,38 @@ +from django import forms +from .models import SEOConfiguration + + +class SEOConfigurationForm(forms.ModelForm): + class Meta: + model = SEOConfiguration + fields = "__all__" + + + # widgets = { + # "home_page_meta_title": forms.Text(attrs={"class": "form-control", "placeholder": "Home Page Meta Title"}), + # "home_page_meta_description": forms.Textarea(attrs={"class": "form-control", "rows": 3, "placeholder": "Home Page Meta Description"}), + + # "about_meta_title": forms.TextInput(attrs={"class": "form-control", "placeholder": "About Page Meta Title"}), + # "about_meta_description": forms.Textarea(attrs={"class": "form-control", "rows": 3, "placeholder": "About Page Meta Description"}), + + + # "tools_meta_title": forms.TextInput(attrs={"class": "form-control", "placeholder": "Tools Page Meta Title"}), + # "tools_meta_description": forms.Textarea(attrs={"class": "form-control", "rows": 3, "placeholder": "Tools Page Meta Description"}), + + # "contact_meta_title": forms.TextInput(attrs={"class": "form-control", "placeholder": "Contact Page Meta Title"}), + # "contact_meta_description": forms.Textarea(attrs={"class": "form-control", "rows": 3, "placeholder": "Contact Page Meta Description"}), + + # "career_meta_title": forms.TextInput(attrs={"class": "form-control", "placeholder": "Career Page Meta Title"}), + # "career_meta_description": forms.Textarea(attrs={"class": "form-control", "rows": 3, "placeholder": "Career Page Meta Description"}), + + # "blog_meta_title": forms.TextInput(attrs={"class": "form-control", "placeholder": "Blog Page Meta Title"}), + # "blog_meta_description": forms.Textarea(attrs={"class": "form-control", "rows": 3, "placeholder": "Blog Page Meta Description"}), + + # "term_and_con_meta_title": forms.TextInput(attrs={"class": "form-control", "placeholder": "Terms & Conditions Meta Title"}), + # "term_and_con_meta_description": forms.Textarea(attrs={"class": "form-control", "rows": 3, "placeholder": "Terms & Conditions Meta Description"}), + # "term_and_con_canonical_url": forms.URLInput(attrs={"class": "form-control", "placeholder": "Canonical URL"}), + + # "privacy_pol_meta_title": forms.TextInput(attrs={"class": "form-control", "placeholder": "Privacy Policy Meta Title"}), + # "privacy_pol_meta_description": forms.Textarea(attrs={"class": "form-control", "rows": 3, "placeholder": "Privacy Policy Meta Description"}), + # "privacy_pol_canonical_url": forms.URLInput(attrs={"class": "form-control", "placeholder": "Canonical URL"}), + # } diff --git a/at_django_boilerplate/backend_admin/migrations/0001_initial.py b/at_django_boilerplate/backend_admin/migrations/0001_initial.py new file mode 100644 index 0000000..626469b --- /dev/null +++ b/at_django_boilerplate/backend_admin/migrations/0001_initial.py @@ -0,0 +1,38 @@ +# Generated by Django 5.2.6 on 2025-12-29 05:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='SEOConfiguration', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('home_page_meta_title', models.CharField(help_text='SEO title for the page (max 60 characters).', max_length=100)), + ('home_page_meta_description', models.CharField(help_text='Meta description for search engines (max 160 characters).', max_length=300)), + ('about_meta_title', models.CharField(help_text='SEO title for the page (max 60 characters).', max_length=100)), + ('about_meta_description', models.CharField(help_text='Meta description for search engines (max 160 characters).', max_length=300)), + ('tools_meta_title', models.CharField(help_text='SEO title for the page (max 60 characters).', max_length=100)), + ('tools_meta_description', models.CharField(help_text='Meta description for search engines (max 160 characters).', max_length=300)), + ('contact_meta_title', models.CharField(help_text='SEO title for the page (max 60 characters).', max_length=100)), + ('contact_meta_description', models.CharField(help_text='Meta description for search engines (max 160 characters).', max_length=300)), + ('career_meta_title', models.CharField(help_text='SEO title for the page (max 60 characters).', max_length=100)), + ('career_meta_description', models.CharField(help_text='Meta description for search engines (max 160 characters).', max_length=300)), + ('blog_meta_title', models.CharField(help_text='SEO title for the page (max 60 characters).', max_length=100)), + ('blog_meta_description', models.CharField(help_text='Meta description for search engines (max 160 characters).', max_length=300)), + ('term_and_con_meta_title', models.CharField(help_text='SEO title for the page (max 60 characters).', max_length=100)), + ('term_and_con_meta_description', models.CharField(help_text='Meta description for search engines (max 160 characters).', max_length=300)), + ('term_and_con_canonical_url', models.URLField(blank=True, help_text='Canonical URL to avoid duplicate content.', null=True)), + ('privacy_pol_meta_title', models.CharField(help_text='SEO title for the page (max 60 characters).', max_length=100)), + ('privacy_pol_meta_description', models.CharField(help_text='Meta description for search engines (max 160 characters).', max_length=300)), + ('privacy_pol_canonical_url', models.URLField(blank=True, help_text='Canonical URL to avoid duplicate content.', null=True)), + ], + ), + ] diff --git a/at_django_boilerplate/backend_admin/migrations/__init__.py b/at_django_boilerplate/backend_admin/migrations/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/backend_admin/migrations/__pycache__/0001_initial.cpython-312.pyc b/at_django_boilerplate/backend_admin/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d87c36263fa36acecbcf257056686ebc5c58c56a GIT binary patch literal 2874 zcmb`IO>EOh6vu5R4NV##DW%~XFQhF=Kin2tR48d zKv{{y9=UPtF+e3a?vdk4E3Is)k}IE(_Qb6ctXAT*Z=81P8Y1mseTd)8`@R1gPu`pP z{ouh61<&ilQ{w9eiu#ibcAvT(YW%Gx&rc{3V5{ERCovgXpX; zPi6fKh3l5W?trR|7~})rfu+bdd^DnwAJuIH-uSED1qK~(=*Dl*^(eRzdc*unHq<1; z=-@u`P)*XSdAKI&)r{06y_(UQq*t@CCh65YQj_#*Hq|7(nn!DrUd`s3q*pUmlk{pH zt4Vq_kJluq6f-_7P01y#uDSk)3;9}(;<938<$EERbjKhoh85gzdpmh<|eeZrFfVJ)YMMRLTf z4opBw)-~f~)3BiC7Fh@vWvo?m_IQ$t9W+JR5n)~_>8^#_&8``jt{u@8Mbh?>t{FGk zm{VjOzRk@Z+c)Q?qd7svSariO_mrDGJTEDEH+gh#!p+5CUkkh-BOcPa`O{xJ+;J!N zxpRW7$YM^A_>wBECWd#1?CjJyt1GOqqKJq^rJ@9oj#=`FSk}7|KD%yS669r-oYl5p zE=iKz47Ic(+bE@ByBIVWOGC0Ci~>ixshTdRda1|>I**nFIj`{ZiYQ^VD8a6B^FnSJ*2xQ~Aj;g{<;rz;clYo+ zU(;1AtK^1ueu$y{0r%T;>cgSNXJgxbx+(B%e7l|sp8X|c)t@oz&um4{e~2D6V#yaP zRxD}8lE3O@Xq%B3s5D!N0W&dRj6B??s5Lsxe5R=6Fav|=2(uOK zG`OM4ip33?+z`y&rZdDf&CC(5`v-@+58Qd9rEh)CYUwju`i$=j#_E&JmZwH!Z7Z5E z+OJmnt@f*C`&9@XgB0+(!%Pu{j%i0>=6_LWG1@M#uUKuD&9=)%S}`84L52${Cqo)C zv>Tn*DsxunHM8>?Ea4_SLR|Nl8M2bDS%;egE^f5;uP<1w{bp;wk&=y{o^H0T0U1q! zGih`TRx(z{pxH49b2n&bukJF_M5J@Z5t#+g)JfVuxGBclPDVnztl zG3qcGU^t^E^{(0KNtrz<04C@g#5K#@CtU9Xhsy%jQBf)jZteysw(odj;OARv?{0xl!c7&dwn`V8cY4=p*J=y&5X@mOg7u|-uzC_) oSNmWHXFjE!f}d?4P1An|C_3>uz|w&iAuAF$Bk{i|7&y`Y0N~B|ApigX literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/backend_admin/migrations/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/backend_admin/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7e94a1aaa3795c29e1023c0a18d09f23a3fb43a GIT binary patch literal 209 zcmZ9Gu?@m75Jh7G5&~rq8g2nZ12hzX5*jM&LM&sF*w)!8G6^#<3lmT?0ZNw&r{zoc z|GU!_|CJ=ODCj*;)sUaT{?(1c9 z=A1!#$$*4Z&I@WyL(U`l#sYd?+)+!zbE7Ijwn8r;$7?Bc!Lg{7hFVENuZ(uUxzfsW bzO-#WUDc*&DzTAdmin Dashboard + + + +{% endblock %} diff --git a/at_django_boilerplate/backend_admin/templates/appointment_list.html b/at_django_boilerplate/backend_admin/templates/appointment_list.html new file mode 100755 index 0000000..57b192e --- /dev/null +++ b/at_django_boilerplate/backend_admin/templates/appointment_list.html @@ -0,0 +1,188 @@ +{% extends 'base.html' %} +{% load static %} +{% block title %}Appointment Leads - Admin{% endblock %} + +{% block content %} +
+
+ + +
+

Appointment Requests

+

Manage appointments: update status, reschedule, and track progress

+
+ + {% if appointments %} +
+ + + + + + + + + + + + + + + + + + {% for apt in appointments %} + + + + + + + + + + + + + + + + + + + + + {% endfor %} + +
#Ticket IDNameContactExpertScheduled ForStatusActions
{{ forloop.counter }} + APP-{{ apt.pk|stringformat:"06d" }} + + {{ apt.full_name }} + + {{ apt.email }}
+ {{ apt.phone }} +
+ {{ apt.get_meeting_with_display }} + + {{ apt.appointment_datetime|date:"d M Y" }} at {{ apt.appointment_datetime|time:"h:i A" }} + + + {{ apt.get_status_display }} + + + + + + +
+
+ + {% else %} +
+

No appointment requests yet.

+
+ {% endif %} +
+
+ + + + + + +{% endblock %} \ No newline at end of file diff --git a/at_django_boilerplate/backend_admin/templates/contact_list.html b/at_django_boilerplate/backend_admin/templates/contact_list.html new file mode 100755 index 0000000..3601dee --- /dev/null +++ b/at_django_boilerplate/backend_admin/templates/contact_list.html @@ -0,0 +1,239 @@ +{% extends 'base.html' %} +{% load crispy_forms_tags %} + +{% block content %} + + + + +
+ +

All Contacts

+ + + +
+ + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + {% for c in contacts %} + + + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + + +
NameEmailMessageDateActions
+ {{ c.name }} + + {{ c.email }} + + {{ c.message }} + + {{ c.created_at|date:"M d, Y H:i" }} + + + + + + +{% if not c.is_junk %} +
+ {% csrf_token %} + +
+{% else %} +Junk +{% endif %} + +
+ No contacts found. +
+
+ + + {% if is_paginated %} +
+ + {% if page_obj.has_previous %} + + Prev + + {% endif %} + + + {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} + + + {% if page_obj.has_next %} + + Next + + {% endif %} +
+ {% endif %} + +
+ + + + + + + + + + + + +{% endblock %} diff --git a/at_django_boilerplate/backend_admin/templates/seo_config.html b/at_django_boilerplate/backend_admin/templates/seo_config.html new file mode 100755 index 0000000..a4789c3 --- /dev/null +++ b/at_django_boilerplate/backend_admin/templates/seo_config.html @@ -0,0 +1,22 @@ +{% extends 'base.html' %} +{% block content %} +{% load crispy_forms_tags %} + +{% load static %} + +

SEO Configuration

+ + {% if messages %} + {% for message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + +
+ {% csrf_token %} + {{ form.as_p }} + +
+ + +{% endblock %} \ No newline at end of file diff --git a/at_django_boilerplate/backend_admin/templates/subscriber.html b/at_django_boilerplate/backend_admin/templates/subscriber.html new file mode 100755 index 0000000..e78184e --- /dev/null +++ b/at_django_boilerplate/backend_admin/templates/subscriber.html @@ -0,0 +1,60 @@ +{% extends 'base.html' %} +{% block content %} +{% load crispy_forms_tags %} + + + + +
+ + +

Subscriber List

+ +
+ + + + + + + + + + + + {% for subscriber in subscribers %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
IDEmailSubscribed AtStatusAction
{{ subscriber.id }}{{ subscriber.email }}{{ subscriber.subscribed_at|date:"M d, Y H:i" }} + {% if subscriber.is_active %} + ✅ Active + {% else %} + ❌ Inactive + {% endif %} + + + {% if subscriber.is_active %}Deactivate{% else %}Activate{% endif %} + +
No subscribers yet.
+
+ +
+ +{% endblock %} diff --git a/at_django_boilerplate/backend_admin/tests.py b/at_django_boilerplate/backend_admin/tests.py new file mode 100755 index 0000000..7ce503c --- /dev/null +++ b/at_django_boilerplate/backend_admin/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/at_django_boilerplate/backend_admin/urls.py b/at_django_boilerplate/backend_admin/urls.py new file mode 100755 index 0000000..6411dc4 --- /dev/null +++ b/at_django_boilerplate/backend_admin/urls.py @@ -0,0 +1,21 @@ +# urls.py +from django.urls import path +# from .views import (AdminBusinessListView, +# admin_add_business_ajax) +from . import views +urlpatterns = [ + + # path('admin/businesses/', AdminBusinessListView.as_view(), name='admin-business-list'), + # path('admin/business/add/', admin_add_business_ajax, name='admin_add_business_ajax'), + path("admin_view", views.admin_dashboard, name="admin_dashboard"), # default dashboard + path('admin_view/contact_list/', views.ContactListView.as_view(), name='contact_list'), + path("admin_view/contacts//junk/", views.set_message_to_junk, name="set_message_to_junk"), + path('admin_view/subscriber_list/', views.subscriber_list, name='subscriber_list'), + path('admin_view/subscriber_toggle//', views.toggle_subscriber, name='toggle_subscriber'), + + path('appointments/', views.appointment_list, name='appointment_list'), + + + path("seo/config/", views.SEOConfigView.as_view(), name="seo_config"), + +] diff --git a/at_django_boilerplate/backend_admin/utils.py b/at_django_boilerplate/backend_admin/utils.py new file mode 100755 index 0000000..ddd4da2 --- /dev/null +++ b/at_django_boilerplate/backend_admin/utils.py @@ -0,0 +1,49 @@ +from datetime import timedelta +from django.utils import timezone +# from business.models import Subscription, SubscriptionPackage + + +# def assign_free_trial_subscription(business): +# """ +# Assigns a free trial subscription to a business, if allowed. +# Handles expiration of old trials and avoids duplicates. +# """ +# try: +# # Find the first free trial plan +# plan = SubscriptionPackage.objects.filter( +# provider='trial', +# is_free_plan=True, +# free_trial_days__gt=0 +# ).first() + +# if not plan: +# return {"success": False, "message": "No free trial plan available."} + +# # Check for existing active trial +# existing_trial = Subscription.objects.filter( +# business=business, +# plan__provider='trial', +# status='trial' +# ).order_by('-subscribed_on').first() + +# if existing_trial and existing_trial.current_period_end > timezone.now(): +# return {"success": False, "message": "Active trial already exists."} + +# # Mark existing trial as expired (if any) +# if existing_trial: +# existing_trial.status = 'expired' +# existing_trial.save() + +# # Create new free trial +# Subscription.objects.create( +# business=business, +# plan=plan, +# status='trial', +# subscribed_on=timezone.now(), +# current_period_end=timezone.now() + timedelta(days=plan.free_trial_days) +# ) + +# return {"success": True, "message": f"{plan.free_trial_days}-day free trial assigned."} + +# except Exception as e: +# return {"success": False, "message": f"Error assigning trial: {str(e)}"} \ No newline at end of file diff --git a/at_django_boilerplate/backend_admin/views.py b/at_django_boilerplate/backend_admin/views.py new file mode 100755 index 0000000..3ba5817 --- /dev/null +++ b/at_django_boilerplate/backend_admin/views.py @@ -0,0 +1,156 @@ +from at_django_boilerplate.communications.models import FeedbackModel,AppointmentModel +from at_django_boilerplate.core.models import Subscriber +from django.shortcuts import render, redirect,get_object_or_404 +from django.contrib import messages +from django.views import View +from .models import SEOConfiguration +from .forms import SEOConfigurationForm +from django.contrib.auth.decorators import login_required, user_passes_test +from django.contrib.auth import get_user_model +from django.utils.decorators import method_decorator +from django.views.generic import ListView +from django.db.models import Q +from .utils import * +from django.utils import timezone +from django.contrib.auth.mixins import UserPassesTestMixin +from django.http import HttpResponseForbidden + +# Keep your custom decorator +def superuser_required(view_func): + def wrapper(request, *args, **kwargs): + if not request.user.is_authenticated: + return redirect('login') + if not request.user.is_superuser: + return HttpResponseForbidden() + return view_func(request, *args, **kwargs) + return wrapper + + + +class SuperuserRequiredMixin(UserPassesTestMixin): + def test_func(self): + return self.request.user.is_superuser +User = get_user_model() + +@superuser_required +def admin_dashboard(request): + return render(request, "admin_dashboard.html") + + + +class ContactListView(SuperuserRequiredMixin ,ListView): + model = FeedbackModel + template_name = 'contact_list.html' + context_object_name = 'contacts' + paginate_by = 50 + + def get_queryset(self): + queryset = FeedbackModel.objects.all().order_by("-created_at") + + search = self.request.GET.get("search") + status = self.request.GET.get("status") # NEW + + if search: + queryset = queryset.filter( + Q(name__icontains=search) | + Q(email__icontains=search) | + Q(message__icontains=search) + ) + + # ================= JUNK FILTER ================= + if status == "junk": + queryset = queryset.filter(is_junk=True) + elif status == "inbox": + queryset = queryset.filter(is_junk=False) + + return queryset + +@superuser_required +def set_message_to_junk(request, pk): # Change parameter from 'uuid' to 'pk' + contact = get_object_or_404(FeedbackModel, pk=pk) # Use 'id=pk' instead of 'uuid=uuid' + contact.is_junk = True + contact.save(update_fields=["is_junk"]) + + messages.success(request, "Message marked as junk.") + return redirect(request.META.get("HTTP_REFERER", "contact_list")) # Better fallback + +@superuser_required +# Show all subscribers +def subscriber_list(request): + subscribers = Subscriber.objects.all().order_by("-subscribed_at") + return render(request, "subscriber.html", {"subscribers": subscribers}) + + + +@superuser_required +def toggle_subscriber(request, pk): # Changed from pk to uuid + subscriber = get_object_or_404(Subscriber, pk=pk) # Assuming Subscriber also uses UUID as PK + subscriber.is_active = not subscriber.is_active + subscriber.save() + return redirect("subscriber_list") + +class SEOConfigView(View): + template_name = "seo_config.html" + + def get(self, request): + seo_config = SEOConfiguration.objects.first() + if not seo_config: + seo_config = SEOConfiguration.objects.create() # ensure one record exists + + form = SEOConfigurationForm(instance=seo_config) + return render(request, self.template_name, {"form": form}) + + def post(self, request): + seo_config = SEOConfiguration.objects.first() + form = SEOConfigurationForm(request.POST, instance=seo_config) + if form.is_valid(): + form.save() + messages.success(request, "SEO configuration updated successfully.") + return redirect("seo_config") + return render(request, self.template_name, {"form": form}) + + +@superuser_required +def appointment_list(request): + if request.method == "POST": + appointment_id = request.POST.get("appointment_id") + action = request.POST.get("action") + appointment = get_object_or_404(AppointmentModel, id=appointment_id) + + if action == "update_status": + new_status = request.POST.get("status") + if new_status in dict(AppointmentModel.STATUS_CHOICES): + appointment.status = new_status + appointment.save() + messages.success(request, f"Status updated to '{appointment.get_status_display()}' for {appointment.full_name}") + + elif action == "reschedule": + date_str = request.POST.get("new_date") + time_str = request.POST.get("new_time") + if date_str and time_str: + try: + new_datetime_str = f"{date_str} {time_str}" + new_datetime = timezone.datetime.strptime(new_datetime_str, "%Y-%m-%d %H:%M") + new_datetime = timezone.make_aware(new_datetime) + + if new_datetime > timezone.now(): + appointment.appointment_datetime = new_datetime + appointment.status = 'rescheduled' + appointment.save() + messages.success(request, f"Appointment rescheduled to {new_datetime.strftime('%d %B %Y, %I:%M %p')}") + else: + messages.error(request, "New time must be in the future.") + except ValueError: + messages.error(request, "Invalid date/time format.") + else: + messages.error(request, "Please select both date and time.") + + return redirect('appointment_list') + + appointments = AppointmentModel.objects.all().order_by('-appointment_datetime') + status_choices = AppointmentModel.STATUS_CHOICES + + return render(request, "appointment_list.html", { + "appointments": appointments, + "status_choices": status_choices, + }) \ No newline at end of file diff --git a/at_django_boilerplate/blogs/__init__.py b/at_django_boilerplate/blogs/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/blogs/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/blogs/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..632d1359fbcdfc9db246a321819c30fb7c9d5c8a GIT binary patch literal 190 zcmX@j%ge<81UI>ZGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!iq+4^&rQ`&%quNQ zOxJfwEzT~g LjEsy$%s>_ZNo+F& literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/blogs/__pycache__/admin.cpython-312.pyc b/at_django_boilerplate/blogs/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8b4cdd51da95174d86dd4be6eceefdcd8aca2f7 GIT binary patch literal 639 zcmYLFzi-qq6n;)}cj35}UpkN&DwYmenw0?|(H0>gDCi0USuEe#TvMDl*iNNvRI0?< zt$zc$|ABvi2{9lnRjS0)4HTwM*hx<3!SDP0-us^4_ib%$2=@HCK1sjY_}h`?>35(m zEI0!S6miH#7E!(LxA`{dYCVb?V$cu#>w+#iTny6<1;&5@4^rS66nZ8# zJS#IDE4Poi@Z&-}+@ZH*;0u@b+)z-)WA`}l>uDlR1IFoanM~=JvEh z%PHk$j0YrS80Qs?l|jliu3~(j624&8DeIGzCzvQ(+}FOjrMYJ?)#uK-=thJLkwGen zkD4sLOGJb&+ZX32jjIl7cRNTjKZp}1Gm*+TAu>rL^zUhCei|ZC6uVI-b;k2%DG=9G z8-dBz!JD-?vG*UEvbyA3j+xtwEVNka)C;+-(R@+sXXV4GL zW96hm4nFGEYmX}S!G#`tbWbf5iWdYD13rYFd@H!Uz{nAc=M5#=juvB=9xU=SY9as)o1r+-P52U7km1DE)=KrMexO@*j~9;UHMshN z_jUj|g$JA>2pkmm;vs3Y0v-rJI2La*M|`F{VuZ-&Nk>8;D>0v!4y~gVM0{rSCSu}$ z=T6?rjJ>nVv0It(cbKxze1EjR5CH+E_GUwAv>qpJ&Y1Rn=SQ{>fuxs(RWu)JM(gp| zkrYTu#`nKFvW19MTs~s=;|=A#TUq08vF@}}#R)1sG*IJmgSpj;B2AjwfoedjL3XKR zIfg+a+lFQ}ivspq6pyjVQ=x{<*a@d)APuMljjWeHGs-1kQ&FI@<`AUTG1LeIGzQ>~ z<{0E){4_KuBw)1LRF$8nKE$f- zX;={;(bT{(?PCD#I@Vi6M;0CTHBH!lfiSS?c$J;4#vYL$2zwVrMGL*4a?9B^bbA*9 zm3bwX5QcigT7_+ztgfqT>$OKKYs+u5w%b&Gh>!s_E7(+Aw^e|(9YPZfX?3FS!ymaT zmVtZoyu6D{Sk^2DYmIUh+D(F5<&Dkt$BPTKXXVYWpOrCe=r)14jT~$f4HKtT)`$xB zG^>HsZKN9zw+y!XvVWiD;7=&E+Bn8C_>=ef*st02iW|x^hf4!_#+7G!!ijWR>C0br z7Kf?X!<|8D)=ka+Q0Je)3c_XpDjce-$L|Fn8W`qNL&rR*@9?=B6pc{iIs5l^#c)qZxR^K_Ug zbUz(r3T~#r0^I%e-Y@t1nf1<-VJ7eC^KK@8{=pY#(r=Y><(q-hbd_d5V=()~{Nes! zV&0vY?^XH}A9o%N)46VRkj}a3TyL?T{^YwATnx{=R*ca^P^1#r4jM|nmK~xSc5uh@ z|5?hC&nTJRCu(D_d_gh27aOGbh@m&}FN5}dfJ;pDo~`ZuCx3}N&tGse7u?(h_u1c4 PlD~iW`9B<6eBXZofF6#> literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/blogs/__pycache__/urls.cpython-312.pyc b/at_django_boilerplate/blogs/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24864fee9b1f5b292cd53a67a50b411047e489b0 GIT binary patch literal 578 zcmaKoF;C+#6vyo(rD;R%P*;e=*q#IyBM0hmz``M@D4awYA|qQN#c3kj0hNu7ZvYqawbaqSCjW_uH-?QKEJ->U`Edph?wf*o}`Bxo`Y~oYAUny|` z2q2Jw2sR)D#2_X!BC}yaU;qkWgJR6s9kCyWT>_Tj+X=9dGf`Te_QjvTAR6>$`F0 I@F~Un4_wxfl>h($ literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/blogs/__pycache__/views.cpython-312.pyc b/at_django_boilerplate/blogs/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..723b5e95bb3be7d6102d839f3f49d6dcda295fe5 GIT binary patch literal 2063 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 literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/blogs/migrations/__pycache__/0004_alter_blog_options_and_more.cpython-312.pyc b/at_django_boilerplate/blogs/migrations/__pycache__/0004_alter_blog_options_and_more.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86a7854905f3320a30f45c82c261c2afbd2d4158 GIT binary patch literal 792 zcmaJ+fmLI@%8qe4`)MTN-{I@!Gs*TKJPC#cy# z>cGg>zX7#>2MbfCMz_Mk7OCyhi95R?uyB&k-}~;pyXSZQJ~Ly17z;n&hTjOl4++Mj zc3GT05XB)dU?hT=xP*WL&w(iiz*Hn1Z7i6)kJPGmmA+=zeo0^xZpC4cd)+Wiik%4; z;Lkz6=pF)sOBlEcBLQ($1yJjhCjXXzI6j19^+>6@C2RU$hD1zZ+Hq~n3KX~A*X=qt zH^xqEh1dKj4GMm<-EQBdUev{$$_`Dl(kk^5M&mR`uF19#Xo(jiuV1e7#Zq5svYsD> z#V&ov5bq-m*}mXgamp~FQk9#+)TV84gO-?5Zc(v2>qRK)X-fBcUL=_kG0ZSwnCygD zj=#Km$9WcV(fMf)gU5K#Q!P+S+&NF{`v>p*Uu U=~syU#X6a74Q5;CAV8`94dO-2n*aa+ literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/blogs/migrations/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/blogs/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9308038b58deaf06f264fced20be3a7fa5a4aa97 GIT binary patch literal 201 zcmZ9Fu?@m75Jh7G5&~rq8g2nZ12hzX5*jMY5td_cZ0l?nnS>dbg$byc0HsTX-SVaT z|J~`N|0;@E7WJN&GUX@Pzxr{!JhCoZki#>>QQZ3UGc?ecmIKoPnSd>KcW*45%Hw{w zIag3#DnKrz^Bg-9(0PVFSYV!BZ%hkAYa + + + + +{% endblock %} + +{% block content %} +
+ + + + +
+
+

+ {{ blog.title }} +

+ +
+ + {% if blog.cover_image %} +
+ {{ blog.title }} +
+ {% endif %} + +
+ {{ blog.content|linebreaks }} +
+
+
+{% endblock %} diff --git a/at_django_boilerplate/blogs/templates/blogs_list.html b/at_django_boilerplate/blogs/templates/blogs_list.html new file mode 100755 index 0000000..f0911e4 --- /dev/null +++ b/at_django_boilerplate/blogs/templates/blogs_list.html @@ -0,0 +1,233 @@ +{% extends 'public_base.html' %} + +{% block title %}{{ meta_title|default:"Our Blog - RegisterYourStartup" }}{% endblock %} +{% block meta_description %}{{ meta_description|default:"Read the latest articles, guides, and updates on company registration, compliance, startups, and business growth from RegisterYourStartup." }}{% endblock %} + +{% block content %} + + + +
+
+
+

+ Our Blog +

+

+ Insights, guides, and updates on company registration, startup compliance, taxation, funding, and business growth in India. +

+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+ + + +
+
+ +
+
+
+ {% for blog in blogs %} + + {% empty %} +
+ +

No blog posts available yet. Stay tuned!

+
+ {% endfor %} +
+ + + +
+
+ + + + +{% endblock %} \ No newline at end of file diff --git a/at_django_boilerplate/blogs/tests.py b/at_django_boilerplate/blogs/tests.py new file mode 100755 index 0000000..7ce503c --- /dev/null +++ b/at_django_boilerplate/blogs/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/at_django_boilerplate/blogs/urls.py b/at_django_boilerplate/blogs/urls.py new file mode 100755 index 0000000..34beac9 --- /dev/null +++ b/at_django_boilerplate/blogs/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from .import views + +urlpatterns = [ + + path('', views.BlogsListView.as_view(), name='blogs_list'), + path('blogs//', views.BlogDetailView.as_view(), name='blog_detail'), +] \ No newline at end of file diff --git a/at_django_boilerplate/blogs/views.py b/at_django_boilerplate/blogs/views.py new file mode 100755 index 0000000..a4dca40 --- /dev/null +++ b/at_django_boilerplate/blogs/views.py @@ -0,0 +1,29 @@ +from django.views.generic import ListView,DetailView +from at_django_boilerplate.backend_admin.models import SEOConfiguration +# Create your views here. + +from at_django_boilerplate.blogs.models import Blog +class BlogsListView(ListView): + model = Blog + template_name = 'blogs_list.html' + context_object_name = 'blogs' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + seo = SEOConfiguration.objects.first() + context["meta_title"] = seo.blog_meta_title if seo else "Blog - RegisterYourStartup" + context["meta_description"] = seo.blog_meta_description if seo else "Read articles and updates from Zestato." + return context + + +class BlogDetailView(DetailView): + model = Blog + template_name = 'blog_detail.html' + context_object_name = 'blog' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + blog = self.get_object() + context["meta_title"] = blog.title + context["meta_description"] = blog.meta_description if hasattr(blog, 'meta_description') else blog.short_description[:160] + return context \ No newline at end of file diff --git a/at_django_boilerplate/communications/__init__.py b/at_django_boilerplate/communications/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/communications/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/communications/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4928a9d2fcaf5f73e8254e43e11ac0bd0e0df50a GIT binary patch literal 199 zcmX@j%ge<81UI>ZGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!O4HBC&rQ`&%quNQ zOxJfwEzT~crHoQnyanl&ClOe*Zh2?(h4E<0iP+dA}3FxN!bMOKjI7va00U^L^ zJVdesig zJw9RCSalMWSuIA|8!JQU+eSCMs2gFij5Lmgv8Z2R<*Kkw=!23-SypH^re|oTR^{}j zcYS$&dfTVH`#!BkW~$J}GHr{TGCSOC9FCMoQRgYMNE0Pxk%@#^p)y1NJa_WhIy!bv riWE~{K3n0@yw1476g4(4#UjDUns&Bx|9v_7Hr-2H+h?eFsoJlf-Q`}p2__kO%qDkY%oz1;9$)cdK)%nDO5+E!oyD4;L^ zA#@=G1~>!M=mTmp_-y8JuFf?_7AhWdg1V(y* zaSPq>kRcMKf_SLLk~_&`blJE#KdD|dQRBLa1oQkvGSTI!NF)(5jnTh7qWK;XiQ~kH zyF#yj;w%M{igJB0T;F@M)`sPshniTL>2=%8%G2%WWT6j|rOV8GqS{a>J-N!A~e{g#aY literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/communications/__pycache__/models.cpython-312.pyc b/at_django_boilerplate/communications/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4ef8fd0a7bffd1a1621bce3c01a0d341619752b GIT binary patch literal 3245 zcma)8O>7(25#B%kFDa3fWY-j3DvFwJ6Nyghs6k>RkSNKK6aB!V+L}PKU2&hNrI)+x z+g;dXdtd<{)SG%rfS`a16sQads1H5(&_j;_dLa=4Bo+v2pzXmoHBu44mv-JRNmDdk zqY$u zcihR7#0fT$i+VC6t4}<~jH}7{9lO{NJFjEu>t!B%siohs4D>8R|0nw3w~C&3JX{$P zRp^92hLa*A7jdkvy0d;dkmKmLw2L}+ul9R#c@!^x|3ztEB;%E^5V6B62+2EA{_YyN z0MFu5KCfGvY0PCT!_-UL2EulDw__vjV!f1o$k{wbs$?Qr)T9!!In7d4+q0ue+Fd)K zYa)0;CK4FTLj*VsAt8Utzho;DgWyz@o3lDQQ3j+w#EwFMW2jzB}+Be zg$dg)Ivb0k9TY{lxt5B6=SA@smZXx9L=ltAZwFU&jFf`*5vth1r44a?Bm3d%`c0d& zNM6C^lBB3B3Q}d;g9vX2GJ6v4NXmOq1&|y`kui0fU(|IKNt&znDTY|INWxik-|WZ? zYp00vrtK>s!;lKd4lltmWuc-R1I&@2SWUlY2lE&?Cn%W@sWT8$&*b!;UP5U}v#?Z1 zFCpWush886w>NGrXR=%A+jq9o7!?%5M7W|`*f1q*TIIB4igHoX3c9$XD=NZe@@3L_ zy;QO^B`=wZt{G{!yywalOp4bbq*9H40(n5SLzDaSt$y+rir!MH4W-%){Ia&#j!f&B&Xzn@=KFiR5Y{a`kZO$ZSTg*KW3l$eu)J4klXBnMQQxXzx$zL$w)w z@7K4m&`o`ADHp9F(E#UU8f+mE1+r})Z?5s|C`L%K*D zd+Ck=#L3~4NDp7*FWVFRjr2}pOdfqe<;I+LjAi3?-)=t8-yE1FqCn&|+R zzzR$?zGE4R1{F7(hr13KA!%kcvWAeEGWFEQ(!C18U^WglQC1a6%cEJNjPi=4LNQf? zUBk=*7>z-%({WkHPT|a=`(=d9YP4&!g`jSs@G62=U~FF*X|kdfY~QBKfGhwpm{K0V z5x}I&f;^~bVj1gs2(ubo)l!=-51J50Mt%Uit0j+~^Sk`;kW zm&K%{ZC-kQMn)EsmW!i61lk4I5#XP`MFfl^uzmSGU4gh@PN3Sw>r4K-1I?<*zYdc4^uc^9 zKHZ2`W#Y{`ce$^}_4?kEaB~00t#GmtP9DV`ee%Vf zX80$ymA)qQ=z25yPVLs$UfT1cZ-C5xO#yK~8_9TG@Y?~mfzQc1?z4sO%q>$<4foOT z%Fs4H=UB{@$P?Ybf1gwh2i9 literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/communications/__pycache__/urls.cpython-312.pyc b/at_django_boilerplate/communications/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..524b5e306e8a00d442faf45d2f4f6aa239ca979d GIT binary patch literal 604 zcmaJ-zfT)66t;7B$%QKlP~C#ijYt_9S7PWui-e$3TZtbEEvQ*ePR$pORpLK(Rgi=X_P1A z?w2M%Km;O~018_Wf~XW(Nr_r53xX0LFtUl&bEm=rdtSk(>|{bB=d^rcjgv=@iuQOi z?JDrU9&X{$Eww8BYJKL$Nb4}SQ>+ee;5czU$K;DVb~~IY+)*g6O<0)8@Dmr*2pFd1 zc)rc~(KDQ;JZ6d#rhDg1Dg39jU3YUY>S5O9jZ7p`m}lfbI0n3p@r->vF|S}5n$dY} zy>NWI@w8BHsEAn+yBxrR@%?OFzjdSQ!Q@qhNIy Yteyw22AlgA_Q9=V)yiKzF{d3L0QrlbT>t<8 literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/communications/__pycache__/views.cpython-312.pyc b/at_django_boilerplate/communications/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a271b782f2b6187173794bc34df16305fb4a86e GIT binary patch literal 8331 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 DGq9B= literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/communications/admin.py b/at_django_boilerplate/communications/admin.py new file mode 100755 index 0000000..cc1008a --- /dev/null +++ b/at_django_boilerplate/communications/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import FeedbackModel + + + +admin.site.register(FeedbackModel) diff --git a/at_django_boilerplate/communications/apps.py b/at_django_boilerplate/communications/apps.py new file mode 100755 index 0000000..8310888 --- /dev/null +++ b/at_django_boilerplate/communications/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CommunicationsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'at_django_boilerplate.communications' diff --git a/at_django_boilerplate/communications/migrations/0001_initial.py b/at_django_boilerplate/communications/migrations/0001_initial.py new file mode 100644 index 0000000..673740c --- /dev/null +++ b/at_django_boilerplate/communications/migrations/0001_initial.py @@ -0,0 +1,50 @@ +# Generated by Django 5.2.6 on 2025-12-29 05:20 + +import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='AppointmentModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('full_name', models.CharField(max_length=255)), + ('email', models.EmailField(max_length=254)), + ('phone', models.CharField(max_length=20)), + ('meeting_with', models.CharField(choices=[('business-consultant', 'Meet to Lawyer'), ('compliance-specialist', 'Meet to CA'), ('incorporation-expert', 'Meet to Secretary')], max_length=50)), + ('appointment_datetime', models.DateTimeField()), + ('notes', models.TextField(blank=True, null=True)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('contacted', 'Contacted'), ('in_process', 'In Process'), ('rescheduled', 'Rescheduled'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='pending', max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='appointments', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-appointment_datetime'], + }, + ), + migrations.CreateModel( + name='FeedbackModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.EmailField(blank=True, max_length=254, null=True)), + ('phone', models.CharField(blank=True, max_length=20, null=True)), + ('name', models.CharField(blank=True, max_length=255, null=True)), + ('subject', models.TextField(blank=True, null=True)), + ('message', models.TextField(blank=True, null=True)), + ('created_at', models.DateTimeField(default=django.utils.timezone.now)), + ('to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='feedbacks', to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='feedback_from', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/at_django_boilerplate/communications/migrations/0002_feedbackmodel_is_junk.py b/at_django_boilerplate/communications/migrations/0002_feedbackmodel_is_junk.py new file mode 100644 index 0000000..44b72af --- /dev/null +++ b/at_django_boilerplate/communications/migrations/0002_feedbackmodel_is_junk.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.6 on 2026-01-03 06:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('communications', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='feedbackmodel', + name='is_junk', + field=models.BooleanField(default=False), + ), + ] diff --git a/at_django_boilerplate/communications/migrations/0003_alter_appointmentmodel_id_alter_feedbackmodel_id.py b/at_django_boilerplate/communications/migrations/0003_alter_appointmentmodel_id_alter_feedbackmodel_id.py new file mode 100644 index 0000000..f995bba --- /dev/null +++ b/at_django_boilerplate/communications/migrations/0003_alter_appointmentmodel_id_alter_feedbackmodel_id.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.6 on 2026-01-03 06:18 + +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('communications', '0002_feedbackmodel_is_junk'), + ] + + operations = [ + migrations.AlterField( + model_name='appointmentmodel', + name='id', + field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='feedbackmodel', + name='id', + field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), + ), + ] diff --git a/at_django_boilerplate/communications/migrations/__init__.py b/at_django_boilerplate/communications/migrations/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/communications/migrations/__pycache__/0001_initial.cpython-312.pyc b/at_django_boilerplate/communications/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1042c4dcb11277c06611272ba4b499adb97edce0 GIT binary patch literal 3738 zcmcgvOKcm*8QxvKsfS6MdQr3S0MvP}aa=$sJNlEqB@3 zrETeura%ro<`x}`oZLzW9o18PRFFe<5duq0P}D$Ax~a8`0y(t(XP4B&lIrBp>Y({& z=G*`A{r^8R{JNzj%)s9tE-c9}LHaA5H2!>b;Z+8NFBplDScS>3&d50rJkXZ)o=CcrWrbDxpCpD~gT`HPW;!lQA^13W~Nl-_IA*%(Q=Sa9D z7qDo^nyM3jNt2MG=Z+LGOsqb6br<~nfI$(8GEe6N?ysg?cegcyLnCuIOxtZ(829c!y@3q4laHMuIU7> z`CNT1CvdIj;R=j&;Y(JEY)7}aLoY8n=)`I3TlFRI@REb+IgNp++3nadZEgR>c01Va zkXp7o8<_{qUFTT`z42olbvwAevpqo3;=t*hVxu~aZ56K_o7BbP(CIZPadw?-U*4*B zM4l^zr5>rbz{>1)Un3&$(px;McKUy0?VMi|tkeYDTxnPY4$rR(R%&?zd-c5hKqiB4 zU8zFh4>kyhdiWAfmZk|eGsSyJvsf`SA%~G@ zAc-`Uv0M`Iy0C`UNk~Un78Q8|k>)1|XEhxOs#roKl&>g?BkunjvIB>GxFkLk6r>i6 zRpLV>QC9Z<1v|u7Ue#2zPdk`2mk^>Cg78!}R(TK4ZCuJ$bXi5Zemw^_Lq#z})!1mA z16ZG-^`*t9>j(p^EvJ>riY%%*bX_l_9Q8>zHo^_y*!e-VPhlO zz$_yO(SXSIe?mRxNFcYW$vLFsDD^ES9#JGnK!qVgoIX`E5WT;ASw&RWh*yQUiC;HF zqoVVEs-=un31T9FMOT2e5Cmh0IZ9_}vLV8HSXPBH*1)E|5uQ=|7F`*3nlRFHt4OLS zGzCjX6IUAqkLud$qO(rSd1%ZSG0bU?LHOS3^@T-cbQ1@UKkX6}$ z;==UmCo%mggd=7FC?T3=q~_K!ed~}56OUFN2#=QUEeUfAQ}@!O`4Ax^oit54-Q+}t z=`}-|@5+S<=(B0c0OV^`#I6{=M|;JUTc&{66{x#9Y6U$r>ZoZABe|e{2uk=*s} z4VW3hRB^RKHzP(ZnlO7tH}iJSsMRyNd$rp0z8U$T7VR|S!<*Oa_^=fp-qEY^_sqz+ zvznxIn?dR<_f7{}3f6IShM((_!Ft$5YjZc`7yR~SS**(1ZQ@eZE>K+D_?+R3l?7?*S zwP(T$niF@uRP&^_7QJltj%_}%d&jKau@|vw@3a}YUyF8_UCGUMyDMpRC3isg-t;#! zU(ML*$5#5Wokms~+3B*CE`ydk+&uMpnfoR8Pdcn|e{%=06IbX~w8R~>HN{#F;!L#L zynM?{ybFA{wZ6U_4TTQ|PEg+WHp)+sJMdy`Z>1VvG9$}x^6}!z-mPkU(TqHFqTy*Y zyw%0;cwbzCoi2ALZYD-Hr|raul^EH%wJTK{9-nDP|uXuYlW6fsl*_<_-vu9OnRt0W8VP_~s z^V~<2y?HKA*`oun*I97bTjhRaJ{IU_E8JtMafMT;Mmo@ZKqep`DtB-x9BX~;YXSZT z`!D)`Gm9TKT80miF1PtcrR*r2WAICAv~ddH2LK~&^?m0Q7(F}$n(E7NIpy6`EOe;I zIq;m5%5Ba?YR<8_1k(4xareM{AAbx}`U^{c2mT#+SeAX|XW0Jln8cr%zP~U-ul+ph T|2%9*2CT@yKj2~Dchvj`TKJP2 literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/communications/migrations/__pycache__/0002_feedbackmodel_is_junk.cpython-312.pyc b/at_django_boilerplate/communications/migrations/__pycache__/0002_feedbackmodel_is_junk.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ce6d4d3da17c257b4776ac30da09be588204f7c GIT binary patch literal 793 zcmY*Xzi-n(6uz?^$F`EHM2MocV^hgOs#C9lIvVo`s zW4HbbpmyTlV2Xs4=vF4SNGU?<#GR8Uh;{;qPGQ%aH!Ys$`V{+m-*8L66>&A;gsj7H#^NXy zW*8G1q^(5{0Oc8(yp`Y#P~bxXe2u^k^mPqTqgx36zXeDcM7=Y+E6aJsh;dOmm#e9B zVa`R#X;#1NTEc!l*CWNPwBj(#Bi5P;tTY;pUBn`mF&v0$n^MxmtzIEplv#?pdDPo? zp(qpD#(9vrx?E5#g(4hMVHB~{W;7s&t}QH73G- z^y1*jqgTh?(VJtB(+*2B%KLH7(+u-0PduC<(#26HM$MQ7lqUhssJCp1xA=XYH0L&2 z@&wH+*-83b-H0M6klvT!1dL5!-x}-Mb@Risu>}!k6h6Z>X}O_NGKv*1f6Cdwg9| hbq&%@8OAz<@UID=dudjndFl*Tw+5?Qe}E)K_dkLY&OHDC literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/communications/migrations/__pycache__/0003_alter_appointmentmodel_id_alter_feedbackmodel_id.cpython-312.pyc b/at_django_boilerplate/communications/migrations/__pycache__/0003_alter_appointmentmodel_id_alter_feedbackmodel_id.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a568aee1ea96aae39b8241d56b76e199fa6e0ae9 GIT binary patch literal 1100 zcmb_aJ!=#}7@qyOz1_WBNDf656D0(3n{3cD1`)&hq7fzG@DTL1&B_`s8^>MMcjse$Hc3Q)m4fVB;Pb)q)RzW8L_(Yn~vTZ&Wo zA<3enayy`PhEf`Z2{(cW6F;e)hyW<{Naw2nTYvyh!N60oQdc}p1w?P;mH#dQqB1Pc zjK=WbzGTE~RQ}w>Ixb*+*Vxg!XS>CIy?@PN{=Xb`)w5fcV{_|a-wk2Sa4iagETpxb zJr|~@r?0^^LU0w;nz>hyCa{r(O+JF+IHF-15HY2TT%~w^#nE{YlQoq2DK`nGDXRK} zmtsZ(#9FXPTHHzqqsXT(h@**aNG%LeK)9aA!Wtz$=H-F3i%yA~Fuw*Nw;>d=%qId5 zLHImFzLZLaNlZda!WtzBlOKTFxBZl`dHE=lUUO@8_5NJWxt?Vd^MZ^s+>T<>`xx7x zBfE0^)?JSR;-WBPsP4{@q?tys`*`Wm!reP7%kI*%WtWjUO+ZGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!D%8)&&rQ`&%quNQ zOxJfwEzT~XdGxIV_;^XxSDt~d<xJd2*kx8#z$sGM#ds$APWFWVLMg; literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/communications/models.py b/at_django_boilerplate/communications/models.py new file mode 100755 index 0000000..5ef4bc6 --- /dev/null +++ b/at_django_boilerplate/communications/models.py @@ -0,0 +1,63 @@ +from django.db import models +from django.utils import timezone + +from at_django_boilerplate.utils.mixins import UUIDMixin + + + +class FeedbackModel(UUIDMixin): + user = models.ForeignKey('accounts.CustomUser',related_name='feedback_from',null=True,blank=True,on_delete=models.DO_NOTHING) + email = models.EmailField(null=True,blank=True) + phone = models.CharField(max_length=20, null=True, blank=True) # Corrected + name = models.CharField(max_length=255, blank=True, null=True) + to = models.ForeignKey('accounts.CustomUser',related_name='feedbacks',null=True,blank=True,on_delete=models.DO_NOTHING) + is_junk = models.BooleanField(default= False) + + subject = models.TextField(null=True,blank=True) + message = models.TextField(null=True,blank=True) + + created_at = models.DateTimeField(default=timezone.now) + + +# communications/models.py + +class AppointmentModel(UUIDMixin): + MEETING_CHOICES = ( + ('business-consultant', 'Meet to Lawyer'), + ('compliance-specialist', 'Meet to CA'), + ('incorporation-expert', 'Meet to Secretary'), + ) + + STATUS_CHOICES = ( + ('pending', 'Pending'), + ('contacted', 'Contacted'), + ('in_process', 'In Process'), + ('rescheduled', 'Rescheduled'), + ('completed', 'Completed'), + ('cancelled', 'Cancelled'), + ) + + user = models.ForeignKey( + 'accounts.CustomUser', + related_name='appointments', + null=True, + blank=True, + on_delete=models.DO_NOTHING + ) + full_name = models.CharField(max_length=255) + email = models.EmailField() + phone = models.CharField(max_length=20) + meeting_with = models.CharField(max_length=50, choices=MEETING_CHOICES) + appointment_datetime = models.DateTimeField() + notes = models.TextField(blank=True, null=True) + + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') + + created_at = models.DateTimeField(auto_now_add=True) + + + def __str__(self): + return f"APP-{self.pk:06d} - {self.full_name}" + + class Meta: + ordering = ['-appointment_datetime'] \ No newline at end of file diff --git a/at_django_boilerplate/communications/templates/book_appointment.html b/at_django_boilerplate/communications/templates/book_appointment.html new file mode 100755 index 0000000..979aa8d --- /dev/null +++ b/at_django_boilerplate/communications/templates/book_appointment.html @@ -0,0 +1,437 @@ +{% extends 'public_base.html' %} +{% load static %} + +{% block title %}Book an Appointment - RegisterYourStartup.com{% endblock %} +{% block meta_description %}Schedule a consultation with our business experts. Book an appointment for personalized guidance on incorporation, compliance, and business growth strategies.{% endblock %} + +{% block content %} + + + +
+
+
+
+

+ Book an Appointment +

+

+ Schedule a consultation with our business experts. Get personalized guidance on incorporation, compliance, and growth strategies tailored to your needs. +

+
+ Book Now +
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ + + {% if messages %} +
+ {% for message in messages %} +
+ {{ message|safe }} +
+ {% endfor %} +
+ {% endif %} + + +
+

Schedule Your Consultation

+
+ {% csrf_token %} + + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + +
+ + +
+
+ + +
+

+ Book an appointment with us +

+

+ Fill out the form to connect with us. During this session, our experts will: +

+ +
+
+
+
+
+
Walk you through our services
+

We provide solutions tailored to your business needs

+
+
+ +
+
+
+
+
Answer your specific questions
+

Get actionable insights for your business

+
+
+ +
+
+
+
+
Expert Guidance
+

Receive personalized advice from specialists in incorporation, compliance, and business growth

+
+
+ +
+
+
+
+
Time-Saving Efficiency
+

Streamlined consultations help you get clear answers quickly

+
+
+ +
+
+
+
+
Comprehensive Support
+

From starting a business to managing operations globally, we've got you covered

+
+
+
+
+
+
+ + +
+
+

Ready to Grow Your Business?

+

Book a consultation today and let our expert team guide you through seamless business setup and expansion

+ +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/at_django_boilerplate/communications/templates/contact_us_form.html b/at_django_boilerplate/communications/templates/contact_us_form.html new file mode 100755 index 0000000..87e39d1 --- /dev/null +++ b/at_django_boilerplate/communications/templates/contact_us_form.html @@ -0,0 +1,518 @@ +{% extends 'public_base.html' %} +{% load crispy_forms_tags %} + +{% block title %}Contact Us - RegisterYourStartup.com{% endblock %} +{% block meta_description %}Get in touch with RegisterYourStartup.com. Contact our global offices in India, UAE, USA, UK, Saudi Arabia, Malaysia, Singapore, Indonesia, Kenya, and Bangalore.{% endblock %} + +{% block content %} + + + +
+
+
+
+

+ Contact Us +

+

+ Get in touch with our global team of experts. We're here to help you with business incorporation, compliance, and expansion worldwide. +

+ +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ + +
+

Get In Touch

+ {% if messages %} + {% for message in messages %} +
+ {{ message|safe }} +
+ {% endfor %} + {% endif %} +
+ {% csrf_token %} + + + + + + + + + + + + {% if not user.is_authenticated %} + + + + + + + + {% endif %} + + +
+ + + {% if feedback_submitted %} +
+ Thank you for your feedback! We will get back to you shortly. Please use Ticket ID: {{ ticket_id }} for future reference. +
+ {% endif %} +
+ + +
+

Contact us

+

+ It is very important for us to keep in touch with you, so we are always ready to answer any questions that interest you. Shoot! +

+ + +
+
+
+ +
+
+

HEAD OFFICE

+

ADDRESS:

+

D - 878, LGF, New Friends Colony,

+

New Delhi - 110025, India

+
+
+ +
+
+ +
+
+

PHONE NUMBERS

+

Customer Care: +91 92204 33466

+
+
+ +
+
+ +
+
+

EMAIL

+

info@registeryourstartup.com

+
+
+
+
+
+
+ + + +
+
+

Our Global Offices

+
+ +
+

U.A.E Office

+

Visalite Global FZCO

+

Scality Office No 63, Building No 9WC 523

+

PO Box 491, Dubai Airport Freezone

+

Dubai, UAE

+
+ + +
+

U.S. Office

+

3240 E-State Street, Hamilton

+

New Jersey 08619

+

United States

+
+ + +
+

U.K. Office

+

8 Alexandra Road, Worthing

+

West Sussex BN 11 2DX

+

United Kingdom

+
+ + +
+

Saudi Arabia Office

+

Building No. 4219, Al Izdihar Street

+

Unit No. 4301, Riyadh 12486

+

Saudi Arabia

+
+ + +
+

Malaysia Office

+

Landmark, Suite 1705, Level 17

+

12 Jalan Ngee Heng

+

80000 Johor Bahru, Johor

+

Malaysia

+
+ + +
+

Singapore Office

+

3 Shenton Way, #09-07

+

Shenton House

+

Singapore 068805

+
+ + +
+

Indonesia Office

+

Cyber 2 Tower, Rasuna Said

+

Kuningan, Jakarta

+

Phone: +62 812-863-18349

+
+ + +
+

Kenya Office

+

#7 Vakaria Investment

+

Mombasa Road, Nairobi

+

Kenya

+
+ + +
+

Bangalore Office

+

Bizcon Services Toyama Bizhub

+

Second Floor, Near Manyata Tech Park

+

Thannisandra Main Road

+

Bangalore 560077, India

+
+
+
+
+ + +
+
+

Ready to Start Your Business Journey?

+

Contact us today and let our expert team guide you through seamless business setup and expansion

+ +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/at_django_boilerplate/communications/tests.py b/at_django_boilerplate/communications/tests.py new file mode 100755 index 0000000..7ce503c --- /dev/null +++ b/at_django_boilerplate/communications/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/at_django_boilerplate/communications/urls.py b/at_django_boilerplate/communications/urls.py new file mode 100755 index 0000000..44f80aa --- /dev/null +++ b/at_django_boilerplate/communications/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('contact/', views.ContactView.as_view(), name='contact_us_form'), + path('book-appointment/', views.AppointmentView.as_view(), name='book_appointment'), + ] \ No newline at end of file diff --git a/at_django_boilerplate/communications/views.py b/at_django_boilerplate/communications/views.py new file mode 100755 index 0000000..7a52bdc --- /dev/null +++ b/at_django_boilerplate/communications/views.py @@ -0,0 +1,190 @@ +from django.shortcuts import render, redirect +from django.core.mail import send_mail +from django.conf import settings +from django.views.generic import View +from django.contrib import messages +from django.utils import timezone # Ensure this is imported + +from at_django_boilerplate.accounts.models import CustomUser +from at_django_boilerplate.communications.models import FeedbackModel, AppointmentModel +from at_django_boilerplate.backend_admin.models import SEOConfiguration + + +class ContactView(View): + template_name = 'contact_us_form.html' + + def get(self, request, *args, **kwargs): + seo = SEOConfiguration.objects.first() + context = { + 'user': request.user, + 'meta_title': seo.contact_meta_title if seo else "Contact Us - RegisterYourStartup", + 'meta_description': seo.contact_meta_description if seo else "Get in touch with our team." + } + return render(request, self.template_name, context) + + def post(self, request, *args, **kwargs): + custom_user = None + if request.user.is_authenticated: + try: + custom_user = request.user + except CustomUser.DoesNotExist: + pass + + subject = request.POST.get('subject') + message = request.POST.get('message') + name = request.POST.get('name') + + email = '' + phone = '' + full_name = name or "Anonymous User" + + if custom_user: + email = custom_user.get_decrypted_email() + phone = custom_user.get_decrypted_contact_number() + full_name = custom_user.get_full_name() or name or "Authenticated User" + else: + email = request.POST.get('email') + phone = request.POST.get('phone') + + # Save feedback + feedback = FeedbackModel( + user=custom_user, + name=full_name, + email=email, + phone=phone, + subject=subject, + message=message + ) + feedback.save() + ticket_id = feedback.id + + # Prepare and send email + email_message = f''' + Ticket ID:\t{ticket_id} + Name:\t{full_name} + Email:\t{email} + Contact Number:\t{phone or 'Not provided'} + Subject:\t{subject} + Message:\t{message} + ''' + + try: + send_mail( + subject='You have a new message on RegisterYourStartup', + message=email_message, + from_email=settings.EMAIL_HOST_USER, + recipient_list=[settings.EMAIL_HOST_USER], + fail_silently=False, + ) + except Exception as e: + print("Email sending failed:", e) + + messages.success( + request, + f"Thank you, {full_name.split()[0] if full_name.split() else 'there'}! " + f"Your message has been sent successfully. " + f"Your Ticket ID is {ticket_id}. We will get back to you shortly." + ) + + return redirect('contact_us_form') + + +class AppointmentView(View): + template_name = 'book_appointment.html' + + def get(self, request, *args, **kwargs): + seo = SEOConfiguration.objects.first() + context = { + 'meta_title': seo.appointment_meta_title if seo and seo.appointment_meta_title else "Book an Appointment - RegisterYourStartup.com", + 'meta_description': seo.appointment_meta_description if seo and seo.appointment_meta_description else "Schedule a consultation with our business experts.", + } + return render(request, self.template_name, context) + + def post(self, request, *args, **kwargs): + custom_user = None + if request.user.is_authenticated: + try: + custom_user = request.user + except CustomUser.DoesNotExist: + pass + + full_name = request.POST.get('fullName') + email = request.POST.get('email') + phone = request.POST.get('phone') + meeting_with = request.POST.get('meetingWith') + appointment_datetime_str = request.POST.get('appointmentDateTime') + notes = request.POST.get('notes', '') + + # Basic server-side validation + if not all([full_name, email, phone, meeting_with, appointment_datetime_str]): + messages.error(request, "All required fields must be filled.") + return redirect('book_appointment') + + try: + # Parse the datetime-local input (format: YYYY-MM-DDTHH:MM) + # Replace 'T' with space to make it compatible with fromisoformat + naive_datetime = timezone.datetime.fromisoformat( + appointment_datetime_str.replace('T', ' ') + ) + + # Convert to timezone-aware datetime + appointment_datetime = timezone.make_aware(naive_datetime) + + if appointment_datetime <= timezone.now(): + messages.error(request, "Appointment time must be in the future.") + return redirect('book_appointment') + except ValueError: + messages.error(request, "Invalid date/time format.") + return redirect('book_appointment') + + # Save appointment + appointment = AppointmentModel( + user=custom_user, + full_name=full_name, + email=email, + phone=phone, + meeting_with=meeting_with, + appointment_datetime=appointment_datetime, + notes=notes + ) + appointment.save() + + ticket_id = f"APP-{appointment.pk:06d}" + + # Send email notification to admin + email_subject = f"New Appointment Booking - {ticket_id}" + email_message = f""" +New Appointment Request + +Ticket ID: {ticket_id} +Name: {full_name} +Email: {email} +Phone: {phone} +Meet With: {appointment.get_meeting_with_display()} +Date & Time: {appointment_datetime.strftime('%d %B %Y, %I:%M %p')} +Notes: {notes or 'No additional notes'} + +Please confirm or follow up with the user. + """ + + try: + send_mail( + subject=email_subject, + message=email_message, + from_email=settings.EMAIL_HOST_USER, + recipient_list=[settings.EMAIL_HOST_USER], + fail_silently=False, + ) + except Exception as e: + print("Appointment email failed:", e) + + # Success message via Django messages (shown on redirect) + messages.success( + request, + f"Thank you, {full_name.split()[0] if full_name.split() else 'there'}! " + f"Your appointment has been booked successfully. " + f"Your Ticket ID is {ticket_id}. We will contact you shortly to confirm." + ) + + # Redirect to same page to show success message and prevent resubmission + return redirect('book_appointment') \ No newline at end of file diff --git a/at_django_boilerplate/core/__init__.py b/at_django_boilerplate/core/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/at_django_boilerplate/core/__pycache__/__init__.cpython-312.pyc b/at_django_boilerplate/core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6ab27f73dc4b7224ebef638bfa99f3d5d2878af GIT binary patch literal 189 zcmX@j%ge<81UI>ZGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!iqX%=&rQ`&%quNQ zOxJfwEzT~G5r~UHjE~HW KjEqIhKo$TE3Nn=d literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/core/__pycache__/admin.cpython-312.pyc b/at_django_boilerplate/core/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa95b4e18d1c1d56f05132beea644b4d1295989d GIT binary patch literal 653 zcmYL_J#Q2-5Qc5Q)QyH)#!Db_WMQO z9b6mb;?ib33gc_T0_807r4CfM6e?)TYuC0y)fUKeB#@6e@(IVj#fjf5EVoxqHgBIB zrb|j&L@bo$Sa(!TS>|G8=Gk}ca!6^+B0*{CP#PsXmnI%j`Z;H^y&1_+XOxFJm8<}i zU@ADIA44Iz{)#H|y_*B?Fo}f6;#{$n_fqIjS(1A1=kMOUdhuc5%?}oy5-Z>cRV2C6 z8B`bBeC+Jvn~+b8B|8r|xvu z`|hB;-OzS|B+g)m$}GO@!(2jYWg9Wo8i-DAJ5bjq}jjT y1@p(k7+<2tmuPl{C&%}{%uZ%!&INw(j}ZLmO#GN`f1hqw2t;Ke^6;-o*VzAN=c4@p literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/core/__pycache__/apps.cpython-312.pyc b/at_django_boilerplate/core/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07fd23a387a001b9547647ffb33cfaf8f81e6ddc GIT binary patch literal 498 zcmZutJxc>Y5Z%4INX!R2sCg6sMZHmXYg6i=4`ENn7L0yOqkABgP<<_j_b6uc9!9kjFu!A00wMGfvr(! z>r}Iiu5KD>p)Nv}X?v^mhWL)KXv^{hWjwNKu2W57ap*Emdn+Wy*#vG0m$NY7BxY7y zE@+y$G>@@QJce;vz*y=Nu5bzCyM*v*XOXfFNqCHj+=@GDZEC3A6Bwyu$H(YecnlFg z35kOmEV_+FfX>cOPY&vr7pS$hvyE!<{d)>hryvppkri}9<>(|C5=3R0`e0bueKnSc z#f|$$Seo^lrk)n3UsvK#d7R0M3QY!;ew^RS{u=C||a zz4^`fr>?Frg7r&eQT>5O=y&$gTK=ZexChEE!U%I3(zz_hG2N&6bUw@LLRR3A58XzX z-$Ga*!ZV1&R@U!Y{a_7rSOcALiovA#ih+}y6VOdeG%M+Ix@Q$@O~p%EAwREfs)iF< zUY?tA)f@x2me5ohk3rc*1Z6plvOXUo{JPizwV#-K?t8|0?<_e$No{XS0~CLP3c9FR z%h!GaN;pA<+6R5+^Dx;k4sM}rq$nmsPI#%XV&$m1LTH8^v|y+Y3*>-vB&A@RvSEHL zD;R?(h{>u_(CkyEY-WraZ$aqxDKVGlvOM->1A>Bm<5p%Z8#8S$4v*3^gcdOqXQ&VL{QF9?_(jP}Rsg(j!7wOpCbRIOjXx zuc)?@EttOf_6IBTbAK&XFnRAKjxB2-tiM%Q8lOQRGA#nvgxX= z>Wm_SMYL6mC7+-dK{`QM2DdDidfIhNHEmGf63m zllrPs-(KcW8Y|zd$47T=)#9Vo_~=V~82`9DUylv$1ZuItYHVDUG(3x0{dVNw7S`w7H7>A0sC!n*YT5R_f?f9d}OJ>`MkzJ-#I z7{8u?3?de>EY&pR2c+l(A1PE-44d{t^bY2_JSfFG&)VYcC5B{hm$$W^fojh{<=o)j z)qUZGbRg}0Tj{=55wA6g8a|V#q3Jb+dYnu_B${So0E81_tpwvm-#C+sR~V3qIQ&{v z>>1d(;^ncQ`*G;7=hO0?dbEFgwHEEKM*H{Qu3Vd|+>t9Ee03OA%D3yWA;!v~YHaA` z&|z$%Jl7C7|Aa?H7J4BWbVA-(OW_r-vj)zbbg6=^YF0{T|9ci?-9Z_3y@qj9J>5yi z;wRqBzD}y@}1ma>4<0CU8BV!RWkOcrCg)CkG literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/core/__pycache__/urls.cpython-312.pyc b/at_django_boilerplate/core/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2b1eb1a5234daadfea50889d125937217fec4a4 GIT binary patch literal 2279 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 S5r~UHjE~HWjEqIhKo$V%a5b|4 literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/core/logging/__pycache__/config.cpython-312.pyc b/at_django_boilerplate/core/logging/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0004d3bdd068c9b9023d31af7a7ccb089522b3b4 GIT binary patch literal 1169 zcmYjQ&1)M+6rcT)WXaklj$})+R4Z;0X9;L&DHKXcDXE>dU^~c$(u5*Ln$c?G{ZM9R z(@L_zg+i~rB?lcsFKuc6lU^F=VY^TW^yHgV_vBOFNOI}Ge7yI5@BQ9;GxJ+MpG7eK z%r)K54TS!d!sSpGhw~~No*{yW;vrvos3?f2ROzZj`&NIb4s^>nlTb@Z^aohsE;rjIC%le~{N1swJz%ywy z+uyH`e9Ad?=*fjLveYEi4VjNcVsJ6==&%SBjvF|Q1HmZvKfwX!QX($LZI8BQR|WlO$##J;$^O?u&emn<^uhucYxqa-Dj{?I z(jZ?cmk+&Ozi8XN(5E&IB8DCNW6F<2*tZ`v_dnadcX(hozc{cNb>L-CHV7le1!f}Z z+gP;bdA8ai6wdk{RAF~QMs3Lqxq%RLU8m6>Br6UTvs7iub2YmO^zviyA7ME~vAOzu z^`*ISdOy~4-|c?0`~2P0-9PmDB!vpa_=fdrxfoaO$K~qO&`Rb+GuE<`E2#2zT)Q>b zWKm^XiK}nD@LpEmiA$UDMjdQ%xi-zGi{@knRqn>+I$*2jbp2{EJGqVC-1;f?d)1E1 z8*%OWG_9|h|16{0hX9Lfn;>tP(|oRwoxZoVoSL95BXtg`qCI)*efaU7+h($YSt&mw sc1xabz;coKzz<2}(Ywq7QGO?0fMud7it-mKP4qiT>P6vS1l!E(HI)5blmGw# literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/core/logging/__pycache__/views.cpython-312.pyc b/at_django_boilerplate/core/logging/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f0c09837ea4b0dd278cf2cc6ec6868487c1a6d68 GIT binary patch literal 3850 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 literal 0 HcmV?d00001 diff --git a/at_django_boilerplate/core/logging/config.py b/at_django_boilerplate/core/logging/config.py new file mode 100755 index 0000000..adaef59 --- /dev/null +++ b/at_django_boilerplate/core/logging/config.py @@ -0,0 +1,46 @@ +from pathlib import Path + +def get_logging_config(base_dir: Path): + log_dir = base_dir / "logs" + log_dir.mkdir(exist_ok=True) + + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "default": { + "format": "[{asctime}] [{levelname}] {name}: {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "default", + }, + "file": { + "class": "logging.handlers.TimedRotatingFileHandler", + "filename": log_dir / "django.log", + "when": "midnight", + "backupCount": 7, + "formatter": "default", + "delay": True, + }, + "mail_admins": { + "class": "django.utils.log.AdminEmailHandler", + "level": "ERROR", + }, + }, + "loggers": { + "django": { + "handlers": ["console", "file"], + "level": "INFO", + "propagate": True, + }, + "django.request": { + "handlers": ["console", "file", "mail_admins"], + "level": "ERROR", + "propagate": False, + }, + }, + } diff --git a/at_django_boilerplate/core/logging/views.py b/at_django_boilerplate/core/logging/views.py new file mode 100755 index 0000000..dfead62 --- /dev/null +++ b/at_django_boilerplate/core/logging/views.py @@ -0,0 +1,128 @@ +# utils/log_viewer.py +from pathlib import Path +from django.conf import settings +from django.contrib.admin.views.decorators import staff_member_required +from django.http import Http404 +from django.shortcuts import render +from datetime import datetime +from django.utils import timezone + + +def tail(filepath, lines=500, chunk_size=1024): + with open(filepath, "rb") as f: + f.seek(0, 2) + file_size = f.tell() + + buffer = b"" + pointer = file_size + + while pointer > 0 and buffer.count(b"\n") < lines: + read_size = min(chunk_size, pointer) + pointer -= read_size + f.seek(pointer) + buffer = f.read(read_size) + buffer + + return buffer.decode(errors="ignore").splitlines()[-lines:] + + + +@staff_member_required +def log_list(request): + log_dir = Path(settings.BASE_DIR) / "logs" + + files = [] + for f in log_dir.glob("*.log"): + stat = f.stat() + files.append({ + "name": f.name, + "size": stat.st_size, + "mtime": timezone.make_aware( + datetime.fromtimestamp(stat.st_mtime)) + }) + + return render(request, "logging/logs_list.html", {"files": files}) + + + + + +# @staff_member_required +# def log_detail(request, filename): +# log_dir = Path(settings.LOG_VIEWER_DIR) +# file_path = log_dir / filename + +# if not file_path.exists(): +# raise Http404 + +# lines = tail(file_path, settings.LOG_VIEWER_MAX_LINES) + +# return render( +# request, +# "logging/log_detail.html", +# {"filename": filename, "lines": lines}, +# ) + + +import re +from pathlib import Path +from django.conf import settings +from django.shortcuts import render +from django.http import Http404 + +LOG_PATTERN = re.compile( + r""" + ^\[ + (?P