diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8940219 --- /dev/null +++ b/.gitignore @@ -0,0 +1,480 @@ +# Created by https://www.toptal.com/developers/gitignore/api/django,python,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=django,python,visualstudiocode + +### Django ### +*.log +*.pot +*.pyc +__pycache__/ +local_settings.py +db.sqlite3 +db.sqlite3-journal +media + +# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ +# in your Git repository. Update and uncomment the following line accordingly. +# /staticfiles/ + +### Django.Python Stack ### +# Byte-compiled / optimized / DLL files +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo + +# Django stuff: + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python ### +# Byte-compiled / optimized / DLL files + +# C extensions + +# Distribution / packaging + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. + +# Installer logs + +# Unit test / coverage reports + +# Translations + +# Django stuff: + +# Flask stuff: + +# Scrapy stuff: + +# Sphinx documentation + +# PyBuilder + +# Jupyter Notebook + +# IPython + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm + +# Celery stuff + +# SageMath parsed files + +# Environments + +# Spyder project settings + +# Rope project settings + +# mkdocs documentation + +# mypy + +# Pyre type checker + +# pytype static type analyzer + +# Cython debug symbols + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/django,python,visualstudiocode + + +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,django +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,django + +### Django ### +*.log +*.pot +*.pyc +__pycache__/ +local_settings.py +db.sqlite3 +db.sqlite3-journal +media + +# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ +# in your Git repository. Update and uncomment the following line accordingly. +# /staticfiles/ + +### Django.Python Stack ### +# Byte-compiled / optimized / DLL files +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo + +# Django stuff: + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,django diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cee4243 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "iis.configDir": "" +} \ No newline at end of file diff --git a/BackEnd/.iis/applicationhost.config b/BackEnd/.iis/applicationhost.config new file mode 100644 index 0000000..18beaf0 --- /dev/null +++ b/BackEnd/.iis/applicationhost.config @@ -0,0 +1,906 @@ + + + + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + + +
+
+ + +
+
+
+ + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BackEnd/.vscode/settings.json b/BackEnd/.vscode/settings.json new file mode 100644 index 0000000..cee4243 --- /dev/null +++ b/BackEnd/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "iis.configDir": "" +} \ No newline at end of file diff --git a/BackEnd/BackEnd/asgi.py b/BackEnd/BackEnd/asgi.py new file mode 100644 index 0000000..4a251ee --- /dev/null +++ b/BackEnd/BackEnd/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for BackEnd 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/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BackEnd.settings") + +application = get_asgi_application() diff --git a/BackEnd/BackEnd/settings.py b/BackEnd/BackEnd/settings.py new file mode 100644 index 0000000..707967b --- /dev/null +++ b/BackEnd/BackEnd/settings.py @@ -0,0 +1,228 @@ +""" +Django settings for BackEnd project. + +Generated by 'django-admin startproject' using Django 5.0.3. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +from pathlib import Path +from environs import Env +import os +import datetime +from datetime import timedelta + +# Environment Variables +# Build paths inside the project like this: BASE_DIR / 'subdir'. + +BASE_DIR = Path(__file__).resolve().parent.parent + + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-#zdhi36fsv(lx#%swqp(l9)0dctgcmwqc__*6h5$9gk@sqxn-e" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True +env = Env() +env.read_env() + +# Setting Website URL +WEBSITE_URL = 'http://localhost:8000/' #env.str('WEBSITE_URL') +# BASE_URL = 'http://localhost:8000/' +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_HOST_USER = 'eniakgroupiust@gmail.com'#env.str('EMAIL_HOST_USER') +EMAIL_HOST_PASSWORD = 'otawrhfscdedswzd'# '%_giw.9?5=3aNQr'#env.str('EMAIL_HOST_PASSWORD') +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +REDIS_HOST = os.environ.get('REDIS_HOST', '127.0.0.1') +# CSRF_TRUSTED_ORIGINS = ["http://127.0.0.1:8000/"] + +SESSION_COOKIE_SECURE = False +SESSION_EXPIRE_AT_BROWSER_CLOSE = False +MEDIA_URL = "media/" +MEDIA_ROOT = os.path.join(BASE_DIR, "media/") + + +# SESSION_COOKIE_DOMAIN +ALLOWED_HOSTS = ['*'] + +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES' : [ + 'rest_framework.permissions.AllowAny' + ], + 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' , + 'DEFAULT_AUTHENTICATION_CLASSES' : [ + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework_simplejwt.authentication.JWTAuthentication', + 'rest_framework.authentication.TokenAuthentication', + ] +} + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=10), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=10), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': False, + 'UPDATE_LAST_LOGIN': False, + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'VERIFYING_KEY': None, + 'AUDIENCE': None, + 'ISSUER': None, + 'JWK_URL': None, + 'LEEWAY': 0, + 'AUTH_HEADER_TYPES': ('Bearer',), + 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + 'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule', + + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', + 'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser', + + 'JTI_CLAIM': 'jti', + + 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', + 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=15), + 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), +} +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "corsheaders", + "telegrambot", + "accounts", + "reservation", + "rest_framework", + "rest_framework_swagger", + "rest_framework_simplejwt.token_blacklist", + "Profile", + "drf_yasg", + "counseling", + # "django_tgbot", + "background_task", + 'django.contrib.sites' , + "reservation", + "TherapyTests", + "telegrambot", +] +SITE_ID = 1 + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.CommonMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + + +CORS_ALLOWED_ORIGINS = [ + # 'CORS_ALLOW_ALL_ORIGINS', + "http://localhost:5173" , + +] + + +ROOT_URLCONF = "BackEnd.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 = "BackEnd.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +# DATABASES = { +# "default": { +# "ENGINE": "django.db.backends.sqlite3", +# "NAME": BASE_DIR / "db.sqlite3", +# } +# } + +DATABASES = { + + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'TherapyDB', + 'USER': 'postgres', + 'PASSWORD': 'Hgbr5391', + 'HOST': 'localhost', + 'PORT': '5432', + + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/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/5.0/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = 'Asia/Tehran' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" +AUTH_USER_MODEL = 'accounts.User' +AUTH_PROFILE_MODULE = 'accounts.User' + +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', + 'django.contrib.auth.backends.RemoteUserBackend', +) \ No newline at end of file diff --git a/BackEnd/BackEnd/urls.py b/BackEnd/BackEnd/urls.py new file mode 100644 index 0000000..018acdc --- /dev/null +++ b/BackEnd/BackEnd/urls.py @@ -0,0 +1,55 @@ +""" +URL configuration for BackEnd project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.0/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 drf_yasg.views import get_schema_view +from rest_framework import permissions +from drf_yasg import openapi +from django.urls import re_path as url +from django.views.static import serve +from django.conf import settings + +schema_view = get_schema_view( + openapi.Info( + title="API", + default_version='v1', + description="API", + terms_of_service="https://www.google.com/policies/terms/", + contact=openapi.Contact(email="contact#snippets.local"), + license=openapi.License(name="BSD License"), + ), + public=True, + permission_classes=[permissions.AllowAny, ], +) + +urlpatterns = [ + path("admin/", admin.site.urls), + path("accounts/" , include("accounts.urls")), + path("HomePage/" , include("HomePage.urls")), + path("reserve/" ,include("reservation.urls") ) , + path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + path('profile/' , include("Profile.urls")) , + path('telegrambot/' , include("telegrambot.urls")), + path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), + path('TherapyTests/' , include("TherapyTests.urls")), +] + + +if settings.DEBUG: + urlpatterns += [ + url(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT}), +] \ No newline at end of file diff --git a/BackEnd/BackEnd/wsgi.py b/BackEnd/BackEnd/wsgi.py new file mode 100644 index 0000000..feb5fb1 --- /dev/null +++ b/BackEnd/BackEnd/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for BackEnd 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/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BackEnd.settings") + +application = get_wsgi_application() diff --git a/BackEnd/HomePage/__init__.py b/BackEnd/HomePage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/HomePage/admin.py b/BackEnd/HomePage/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/BackEnd/HomePage/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/BackEnd/HomePage/apps.py b/BackEnd/HomePage/apps.py new file mode 100644 index 0000000..c87dea3 --- /dev/null +++ b/BackEnd/HomePage/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HomepageConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'HomePage' diff --git a/BackEnd/HomePage/migrations/__init__.py b/BackEnd/HomePage/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/HomePage/models.py b/BackEnd/HomePage/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/BackEnd/HomePage/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/BackEnd/HomePage/serializers.py b/BackEnd/HomePage/serializers.py new file mode 100644 index 0000000..6347d11 --- /dev/null +++ b/BackEnd/HomePage/serializers.py @@ -0,0 +1,10 @@ + +from rest_framework import serializers + +class DoctorCountSerializer(serializers.Serializer): + doctor_count = serializers.IntegerField() + +class PationtCountSerializer(serializers.Serializer): + Pationt_count = serializers.IntegerField() + + diff --git a/BackEnd/HomePage/tests.py b/BackEnd/HomePage/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/BackEnd/HomePage/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/BackEnd/HomePage/urls.py b/BackEnd/HomePage/urls.py new file mode 100644 index 0000000..330db91 --- /dev/null +++ b/BackEnd/HomePage/urls.py @@ -0,0 +1,11 @@ +from .views import * +from django.urls import path + + +urlpatterns = [ + # path('PationtCount/' , PationtCountView.as_view() , name='PationtCount' ) , + # path('DoctorCount/' , DoctorCountView.as_view() , name='DoctorCount' ) , + path('count/',CountView.as_view(),name='count'), + + +] \ No newline at end of file diff --git a/BackEnd/HomePage/views.py b/BackEnd/HomePage/views.py new file mode 100644 index 0000000..d2dddff --- /dev/null +++ b/BackEnd/HomePage/views.py @@ -0,0 +1,33 @@ + +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from counseling.models import Psychiatrist,Pationt +from .serializers import DoctorCountSerializer, PationtCountSerializer + +# class DoctorCountView(APIView): +# def get(self, request): +# doctor_count = Psychiatrist.objects.count() +# serializer = DoctorCountSerializer({'doctor_count': doctor_count}) +# return Response(serializer.data, status=status.HTTP_200_OK) + +# class PationtCountView(APIView): +# def get(self, request): +# Pationt_count = Pationt.objects.count() +# serializer = PationtCountSerializer({'Pationt_count': Pationt_count}) +# return Response(serializer.data, status=status.HTTP_200_OK) + + +class CountView(APIView): + def get(self, request): + doctor_count = Psychiatrist.objects.count() + Pationt_count = Pationt.objects.count() + + doctor_serializer = DoctorCountSerializer({'doctor_count': doctor_count}) + Pationt_serializer = PationtCountSerializer({'Pationt_count': Pationt_count}) + + data = { + 'doctor_data': doctor_serializer.data, + 'Pationt_data': Pationt_serializer.data + } + return Response(data, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/BackEnd/Profile/__init__.py b/BackEnd/Profile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/Profile/admin.py b/BackEnd/Profile/admin.py new file mode 100644 index 0000000..bdc2f0d --- /dev/null +++ b/BackEnd/Profile/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from .models import Profile + +admin.site.register( Profile ) diff --git a/BackEnd/Profile/apps.py b/BackEnd/Profile/apps.py new file mode 100644 index 0000000..1c48018 --- /dev/null +++ b/BackEnd/Profile/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class ProfileConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "Profile" + + def ready(self) : + import Profile.signals + # from signals.signals import save_profile \ No newline at end of file diff --git a/BackEnd/Profile/migrations/0001_initial.py b/BackEnd/Profile/migrations/0001_initial.py new file mode 100644 index 0000000..621f713 --- /dev/null +++ b/BackEnd/Profile/migrations/0001_initial.py @@ -0,0 +1,43 @@ +# Generated by Django 5.0.3 on 2024-04-08 07:27 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("counseling", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Profile", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("description", models.TextField(blank=True, null=True)), + ("name", models.CharField(blank=True, max_length=50, null=True)), + ("image", models.ImageField(upload_to="images\\doctors\\profile_pics")), + ("profile_type", models.CharField(max_length=10)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("is_private", models.BooleanField(default=False)), + ( + "psychiatrist", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to="counseling.psychiatrist", + ), + ), + ], + ), + ] diff --git a/BackEnd/Profile/migrations/__init__.py b/BackEnd/Profile/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/Profile/models.py b/BackEnd/Profile/models.py new file mode 100644 index 0000000..a1e13fc --- /dev/null +++ b/BackEnd/Profile/models.py @@ -0,0 +1,44 @@ +from django.db import models +from counseling.models import Psychiatrist +from django.contrib.auth import get_user_model +from django.core.exceptions import ValidationError + +class Profile(models.Model): + psychiatrist = models.OneToOneField(Psychiatrist, on_delete=models.CASCADE) + description = models.TextField(null=True, blank=True) + name = models.CharField(max_length=50, blank=True, null=True) + image = models.ImageField(upload_to='images\doctors\profile_pics') + profile_type = models.CharField(max_length=10) + created_at = models.DateTimeField(auto_now_add=True) + is_private = models.BooleanField(default=False) + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + if not isinstance(self.psychiatrist , Psychiatrist): + raise ValidationError('Invalid value for content_object') + self.name = self.determine_name(self.name) + self.profile_type = self.determine_profile_type() + self.image = self.determine_image() + super().save(*args, **kwargs) + + + def determine_image(self): + if not self.pk: + var = self.psychiatrist.get_profile_image() + print(var) + return var + return self.image + + + def determine_name(self, name): + if not self.pk: + return self.psychiatrist.get_fullname() + return name + + def determine_profile_type(self): + if not self.pk : + return self.psychiatrist.field + return self.profile_type + diff --git a/BackEnd/Profile/signals.py b/BackEnd/Profile/signals.py new file mode 100644 index 0000000..81daea2 --- /dev/null +++ b/BackEnd/Profile/signals.py @@ -0,0 +1,27 @@ +from django.db.models.signals import post_save, pre_delete ,pre_save +from django.dispatch import receiver +from .models import Profile +from counseling.models import Psychiatrist + + +@receiver(post_save, sender=Psychiatrist ) +def create_profile(sender, instance, created, **kwargs): + if created: + print( "instance ----------->" , instance) + print( " sender------------->" , sender ) + Profile.objects.create(psychiatrist=instance) + + +@receiver(post_save, sender=Psychiatrist) +def save_profile(sender, instance, **kwargs): + if not instance.profile : + Profile.objects.create(psychiatrist=instance) + instance.profile.save() + + +# @receiver(pre_save, sender=Psychiatrist) +# def checker(sender, instance, **kwargs): +# if instance.id is None: +# pass +# else: +# save_profile(sender = sender , instance = instance , kwargs = kwargs ) \ No newline at end of file diff --git a/BackEnd/Profile/tests.py b/BackEnd/Profile/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/BackEnd/Profile/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/BackEnd/Profile/urls.py b/BackEnd/Profile/urls.py new file mode 100644 index 0000000..b2af6d5 --- /dev/null +++ b/BackEnd/Profile/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from .views import DoctorProfileViewSet + +urlpatterns = [ + path('doctors/', DoctorProfileViewSet.as_view({'get': 'list', 'post': 'create'}), name='doctor-profiles-list'), + path('doctors//', DoctorProfileViewSet.as_view({'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}), name='doctor-profiles-detail'), + path('doctors/typed/', DoctorProfileViewSet.as_view({'get': 'filter_by_profile_type'}), name='doctor-profiles-filter-by-profile-type'), +] diff --git a/BackEnd/Profile/views.py b/BackEnd/Profile/views.py new file mode 100644 index 0000000..c9ef431 --- /dev/null +++ b/BackEnd/Profile/views.py @@ -0,0 +1,25 @@ +from rest_framework import viewsets, serializers +from rest_framework.decorators import action +from rest_framework.response import Response +from .models import Profile + +class DoctorProfileSerializer(serializers.ModelSerializer): + class Meta: + model = Profile + fields = '__all__' + +class DoctorProfileViewSet(viewsets.ModelViewSet): + queryset = Profile.objects.all() + serializer_class = DoctorProfileSerializer + + @action(detail=False, methods=['get']) + def filter_by_profile_type(self, request): + profile_type = request.query_params.get('profile_type') + if profile_type: + queryset = self.queryset.filter(profile_type=profile_type) + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + else: + return Response({"error": "profile_type parameter is required"}, status=400) + + diff --git a/BackEnd/TherapyTests/__init__.py b/BackEnd/TherapyTests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/TherapyTests/admin.py b/BackEnd/TherapyTests/admin.py new file mode 100644 index 0000000..14ca498 --- /dev/null +++ b/BackEnd/TherapyTests/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from .models import TherapyTests , GlasserTest +admin.site.register(TherapyTests) +admin.site.register(GlasserTest) +# Register your models here. diff --git a/BackEnd/TherapyTests/apps.py b/BackEnd/TherapyTests/apps.py new file mode 100644 index 0000000..a981d7b --- /dev/null +++ b/BackEnd/TherapyTests/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TherapytestsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "TherapyTests" diff --git a/BackEnd/TherapyTests/migrations/0001_initial.py b/BackEnd/TherapyTests/migrations/0001_initial.py new file mode 100644 index 0000000..f11985f --- /dev/null +++ b/BackEnd/TherapyTests/migrations/0001_initial.py @@ -0,0 +1,38 @@ +# Generated by Django 5.0.3 on 2024-04-11 16:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("counseling", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="TherapyTests", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("MBTItest", models.CharField(blank=True, max_length=6)), + ( + "pationt", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="counseling.pationt", + ), + ), + ], + ), + ] diff --git a/BackEnd/TherapyTests/migrations/0002_glassertest_therapytests_glassertest.py b/BackEnd/TherapyTests/migrations/0002_glassertest_therapytests_glassertest.py new file mode 100644 index 0000000..64ef32a --- /dev/null +++ b/BackEnd/TherapyTests/migrations/0002_glassertest_therapytests_glassertest.py @@ -0,0 +1,42 @@ +# Generated by Django 5.0.3 on 2024-04-17 20:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("TherapyTests", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="GlasserTest", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("love", models.FloatField(default=0.0)), + ("survive", models.FloatField(default=0.0)), + ("freedom", models.FloatField(default=0.0)), + ("power", models.FloatField(default=0.0)), + ("fun", models.FloatField(default=0.0)), + ], + ), + migrations.AddField( + model_name="therapytests", + name="glasserTest", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + to="TherapyTests.glassertest", + ), + ), + ] diff --git a/BackEnd/TherapyTests/migrations/__init__.py b/BackEnd/TherapyTests/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/TherapyTests/models.py b/BackEnd/TherapyTests/models.py new file mode 100644 index 0000000..d261630 --- /dev/null +++ b/BackEnd/TherapyTests/models.py @@ -0,0 +1,22 @@ +from django.db import models +from counseling.models import Pationt + +# نیاز به بقا +# نیاز به عشق و تعلق خاطر +# نیاز به آزادی +# نیاز به قدرت +# نیاز به تفریح + +class GlasserTest(models.Model) : + love = models.FloatField( default=0.0 ) + survive = models.FloatField( default=0.0 ) + freedom = models.FloatField( default=0.0 ) + power = models.FloatField( default=0.0 ) + fun = models.FloatField( default=0.0 ) + +class TherapyTests(models.Model) : + pationt = models.ForeignKey(Pationt , on_delete=models.CASCADE ) + MBTItest = models.CharField( max_length=6 , blank=True ) + glasserTest = models.ForeignKey( GlasserTest , on_delete=models.DO_NOTHING , null=True) + + diff --git a/BackEnd/TherapyTests/tests.py b/BackEnd/TherapyTests/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/BackEnd/TherapyTests/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/BackEnd/TherapyTests/urls.py b/BackEnd/TherapyTests/urls.py new file mode 100644 index 0000000..1bdab6a --- /dev/null +++ b/BackEnd/TherapyTests/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from .views import * + +urlpatterns = [ + path( 'MBTI/' , GetMBTItest.as_view({'post' : 'create' , 'get' : 'retrieve'}) , name='MBTI') , + path('glasser/' , GlasserTestView.as_view({'post' : 'create' , 'get' : 'retrieve'}) , name='glasser') , + path('tests/' , ThrepayTestsView.as_view({"get" : "get"}) , name="patient_tests") +] \ No newline at end of file diff --git a/BackEnd/TherapyTests/views.py b/BackEnd/TherapyTests/views.py new file mode 100644 index 0000000..dc0303a --- /dev/null +++ b/BackEnd/TherapyTests/views.py @@ -0,0 +1,105 @@ +from django.shortcuts import render +from rest_framework import viewsets, serializers +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from utils.therapy_tests import GetMBTIresults ,GlasserResults +from counseling.models import Pationt +from .models import TherapyTests , GlasserTest +from rest_framework import status +import json +from django.http.request import QueryDict + +class ThrepayTestsView(viewsets.ModelViewSet ) : + permission_classes = [IsAuthenticated] + def get( self , request ) : + user = request.user + pationt = Pationt.objects.filter(user = user ).first() + if not pationt : + Response({"message" : "there is not patient"} , status=status.HTTP_400_BAD_REQUEST ) + test = TherapyTests.objects.filter( pationt = pationt ).first() + if not test : + Response({"message" : "this user hasn't take any tests!"} , status=status.HTTP_400_BAD_REQUEST) + + return Response( {"TherapTests" : test} , status=status.HTTP_200_OK ) + + +class GlasserTestView(viewsets.ModelViewSet ) : + permission_classes = [IsAuthenticated] + def create( self, request , *args , **kwargs ) : + req_data = {} + d = request.data["data"] + data = json.loads(d) + for key in data.keys() : + print(data[key]) + req_data[key] = data[key] + data[key] + print( req_data ) + if not req_data : + return Response({"message" : "test's results could not be empty!!!"} , status=status.HTTP_400_BAD_REQUEST) + categories = GlasserResults( data=req_data ) + glasser = GlasserTest.objects.create( + love = categories["love"] , + survive = categories["survive"] , + freedom = categories["freedom"] , + power = categories["power"] , + fun = categories["fun"] + ) + user = request.user + pationt = Pationt.objects.filter(user = user).first() + old_test = TherapyTests.objects.filter( pationt = pationt ).first() + if old_test : + old_test.glasserTest = glasser + old_test.save() + return Response( {'message' : 'test`s results was successfullly updated'} , status=status.HTTP_200_OK ) + else : + test = TherapyTests.objects.create( + pationt = pationt , + glasserTest = glasser + ) + return Response( {'message' : 'test`s results was successfullly registered'} , status=status.HTTP_200_OK ) + + + def retrieve(self, request, *args, **kwargs): + user = request.user + pationt = Pationt.objects.filter(user = user ).first() + print(pationt) + mbti = TherapyTests.objects.filter( pationt = pationt ).first() + return Response( {"glasser" : mbti.glasserTest} , status=status.HTTP_200_OK ) + + +class GetMBTItest(viewsets.ModelViewSet) : + permission_classes = [IsAuthenticated ] + + def create(self, request, *args, **kwargs): + udata = request.data + + data = {} + for key in udata.keys() : + data[int(key)] = udata[key] + + user = request.user + pationt = Pationt.objects.filter(user = user).first() + mbti = GetMBTIresults( data , user.gender ) + + old_test = TherapyTests.objects.filter( pationt = pationt ).first() + if old_test : + old_test.MBTItest = mbti['final'] + old_test.save() + return Response( {'message' : 'test`s results was successfullly updated'} , status=status.HTTP_200_OK ) + else : + test = TherapyTests.objects.create( + pationt = pationt , + MBTItest = mbti['final'] + ) + return Response( {'message' : 'test`s results was successfullly registered'} , status=status.HTTP_200_OK ) + + + def retrieve(self, request, *args, **kwargs): + user = request.user + pationt = Pationt.objects.filter(user = user ).first() + print(pationt) + # mbti = pationt.therapytests + mbti = TherapyTests.objects.filter( pationt = pationt ).first() + return Response( {"type" : mbti.MBTItest} , status=status.HTTP_200_OK ) + diff --git a/BackEnd/accounts/admin.py b/BackEnd/accounts/admin.py new file mode 100644 index 0000000..8f3e17c --- /dev/null +++ b/BackEnd/accounts/admin.py @@ -0,0 +1,69 @@ +from django.contrib import admin +from django.contrib.auth.models import Group +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.forms import ReadOnlyPasswordHashField +from django.core.exceptions import ValidationError +from django import forms +from .models import User + + + +class UserCreationForm(forms.ModelForm): + password1 = forms.CharField(label='Password', widget=forms.PasswordInput) + password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) + + class Meta: + model = User + fields = ('email', 'date_of_birth', 'gender' , 'firstname' , 'lastname' , 'phone_number' , 'role') + + def clean_password2(self): + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise ValidationError("Passwords don't match") + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + user = super().save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.save() + return user + + +class UserChangeForm(forms.ModelForm): + password = ReadOnlyPasswordHashField() + + class Meta: + model = User + fields = ('firstname' , 'lastname' , 'gender','email', 'password', 'date_of_birth', 'is_active', 'is_admin' , 'phone_number' , 'role') + + def clean_password(self): + return self.initial["password"] + + +class UserAdmin(BaseUserAdmin): + form = UserChangeForm + add_form= UserCreationForm + list_display = ('email' ,'firstname', 'lastname', 'date_of_birth' , 'is_admin' , 'is_active' , 'phone_number' , 'role') #'is_email_verified' 'phone_number' + list_filter = ('is_admin' , 'email' ,'firstname', 'lastname' , 'phone_number' , 'role') + fieldsets=( + (None, {'fields': ('email', 'password')}), + ('Personal info', {'fields': ('date_of_birth','firstname', 'lastname' ,'gender' ,'phone_number' , 'role')}), ## 'phone_number' + ('Permissions', {'fields': ( 'is_admin', 'is_active','is_email_verified' )}), #'is_email_verified' + ) + + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'password1', 'password2', 'is_active' ), # , 'date_of_birth' + }), + ) + + search_fields = ('email',) + ordering = ('email',) + filter_horizontal = () + +admin.site.register(User, UserAdmin) diff --git a/BackEnd/accounts/apps.py b/BackEnd/accounts/apps.py new file mode 100644 index 0000000..0cb51e6 --- /dev/null +++ b/BackEnd/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "accounts" diff --git a/BackEnd/accounts/migrations/0001_initial.py b/BackEnd/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..a735a5c --- /dev/null +++ b/BackEnd/accounts/migrations/0001_initial.py @@ -0,0 +1,102 @@ +<<<<<<< HEAD +# Generated by Django 5.0.3 on 2024-04-14 06:56 +======= + +>>>>>>> dev + +import datetime +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="User", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ("firstname", models.CharField(blank=True, max_length=20, null=True)), + ("lastname", models.CharField(blank=True, max_length=30, null=True)), + ("email", models.EmailField(max_length=255, unique=True)), + ("date_of_birth", models.DateField(blank=True, null=True)), + ( + "gender", + models.CharField( + choices=[("F", "Female"), ("M", "Male"), ("B", "Both")], + max_length=1, + null=True, + ), + ), + ("is_active", models.BooleanField(default=True)), + ("is_admin", models.BooleanField(default=False)), + ( + "phone_number", + models.CharField( + blank=True, + max_length=15, + null=True, + validators=[ + django.core.validators.RegexValidator( + message="Phone number must be in a valid Iranian format.", + regex="^(?:\\+98|0)(?:\\s?)9[0-9]{9}$", + ) + ], + ), + ), + ( + "role", + models.CharField( + choices=[ + ("user", "User"), + ("doctor", "Doctor"), + ("admin", "Admin"), + ], + default="user", + max_length=255, + ), + ), + ("is_email_verified", models.BooleanField(default=False)), + ( + "verification_code", + models.CharField(blank=True, max_length=4, null=True), + ), + ("verification_tries_count", models.IntegerField(default=0)), + ( + "last_verification_sent", + models.DateTimeField( + blank=True, +<<<<<<< HEAD + default=datetime.datetime(2024, 4, 14, 10, 26, 48, 131829), +======= + + default=datetime.datetime(2024, 4, 18, 0, 28, 53, 286324), + +>>>>>>> dev + null=True, + ), + ), + ("has_verification_tries_reset", models.BooleanField(default=False)), + ], + options={"abstract": False,}, + ), + ] diff --git a/BackEnd/accounts/migrations/0002_alter_user_last_verification_sent.py b/BackEnd/accounts/migrations/0002_alter_user_last_verification_sent.py new file mode 100644 index 0000000..f49c3c9 --- /dev/null +++ b/BackEnd/accounts/migrations/0002_alter_user_last_verification_sent.py @@ -0,0 +1,31 @@ +<<<<<<< HEAD +# Generated by Django 5.0.3 on 2024-04-14 06:58 +======= +# Generated by Django 5.0.3 on 2024-04-17 20:58 +>>>>>>> dev + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="last_verification_sent", + field=models.DateTimeField( + blank=True, +<<<<<<< HEAD + default=datetime.datetime(2024, 4, 14, 10, 28, 2, 616472), +======= + default=datetime.datetime(2024, 4, 18, 0, 28, 58, 537727), +>>>>>>> dev + null=True, + ), + ), + ] diff --git a/BackEnd/accounts/migrations/0003_alter_user_last_verification_sent.py b/BackEnd/accounts/migrations/0003_alter_user_last_verification_sent.py new file mode 100644 index 0000000..1cfb7ae --- /dev/null +++ b/BackEnd/accounts/migrations/0003_alter_user_last_verification_sent.py @@ -0,0 +1,39 @@ +<<<<<<< HEAD +# Generated by Django 5.0.3 on 2024-04-16 02:05 +======= +<<<<<<<< HEAD:BackEnd/accounts/migrations/0005_alter_user_last_verification_sent.py +# Generated by Django 5.0.3 on 2024-04-16 02:22 +======== +# Generated by Django 5.0.3 on 2024-04-19 08:49 +>>>>>>>> dev:BackEnd/accounts/migrations/0003_alter_user_last_verification_sent.py +>>>>>>> dev + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0002_alter_user_last_verification_sent"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="last_verification_sent", + field=models.DateTimeField( + blank=True, +<<<<<<< HEAD + default=datetime.datetime(2024, 4, 16, 5, 35, 38, 697110), +======= +<<<<<<<< HEAD:BackEnd/accounts/migrations/0005_alter_user_last_verification_sent.py + default=datetime.datetime(2024, 4, 16, 5, 52, 27, 638776), +======== + default=datetime.datetime(2024, 4, 19, 12, 19, 51, 747477), +>>>>>>>> dev:BackEnd/accounts/migrations/0003_alter_user_last_verification_sent.py +>>>>>>> dev + null=True, + ), + ), + ] diff --git a/BackEnd/accounts/migrations/0004_alter_user_last_verification_sent.py b/BackEnd/accounts/migrations/0004_alter_user_last_verification_sent.py new file mode 100644 index 0000000..d722c6a --- /dev/null +++ b/BackEnd/accounts/migrations/0004_alter_user_last_verification_sent.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.3 on 2024-04-16 02:06 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0003_alter_user_last_verification_sent"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="last_verification_sent", + field=models.DateTimeField( + blank=True, + default=datetime.datetime(2024, 4, 16, 5, 36, 5, 433083), + null=True, + ), + ), + ] diff --git a/BackEnd/accounts/migrations/0006_alter_user_last_verification_sent.py b/BackEnd/accounts/migrations/0006_alter_user_last_verification_sent.py new file mode 100644 index 0000000..83e890c --- /dev/null +++ b/BackEnd/accounts/migrations/0006_alter_user_last_verification_sent.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.3 on 2024-04-16 02:22 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0005_alter_user_last_verification_sent"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="last_verification_sent", + field=models.DateTimeField( + blank=True, + default=datetime.datetime(2024, 4, 16, 5, 52, 54, 649665), + null=True, + ), + ), + ] diff --git a/BackEnd/accounts/migrations/__init__.py b/BackEnd/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/accounts/models.py b/BackEnd/accounts/models.py new file mode 100644 index 0000000..0aff6b0 --- /dev/null +++ b/BackEnd/accounts/models.py @@ -0,0 +1,147 @@ +from django.db import models +from django.contrib.contenttypes.fields import GenericRelation +from django.core.validators import MinLengthValidator, MaxLengthValidator +from django.db import models +from django.contrib.auth.models import AbstractBaseUser , BaseUserManager +from datetime import datetime +from django.core.validators import RegexValidator +from django.contrib.auth.hashers import make_password + + +class UserManager(BaseUserManager): + def create_user(self , email , firstname , lastname , gender , date_of_birth, phone_number ,password=None) : #phone = None + """ + Creates and saves a User with the given email, + data of birth and password + """ + if not email: + raise ValueError('User must have an email address') + + user = self.model( + email = self.normalize_email(email), + date_of_birth = date_of_birth, + firstname = firstname , + lastname = lastname, + gender= gender, + phone_number = phone_number + ) + + user.set_password(password) + user.save(using=self._db) + return user + + def get_queryset(self) -> models.QuerySet: + return super().get_queryset() + + + + def create_superuser(self , email ,password=None): #phone firstname ,lastname , gender , phone_number, date_of_birth , + """ + Creates and saves a superuser with the given email, birthdat + and password. + """ + + user = self.create_user( + email=email, + password=password, + date_of_birth="2000-3-3", + firstname = "admin" , + lastname = "adminzadeh", + gender= 'B', + phone_number="+989999999999" + ) + + user.is_admin = True + user.is_superuser = True + user.is_email_verified = True + user.is_active = True + user.role = User.TYPE_ADMIN + user.save(using=self._db) + return user + + + def save(self, *args, **kwargs): + self.password = make_password(self.password) + + def get_by_natural_key(self, email): + return self.get(email=email) + + + +class User(AbstractBaseUser): + GENDER_Male = 'M' + GENDER_Female = 'F' + GENDER_BOTH = 'B' + GENDER_CHOICES = [ + (GENDER_Female, 'Female'), + (GENDER_Male, 'Male'), + (GENDER_BOTH , 'Both') + ] + + TYPE_USER = "user" + TYPE_DOCTOR = "doctor" + TYPE_ADMIN = "admin" + + CHOICES = ( + (TYPE_USER , "User") , + (TYPE_DOCTOR , "Doctor") , + (TYPE_ADMIN , "Admin") + ) + + firstname = models.CharField(max_length=20 , blank=True, null = True ) + lastname = models.CharField(max_length=30 , blank=True, null = True ) + email = models.EmailField( + max_length= 255 , + unique = True, + ) + USERNAME_FIELD = 'email' + + objects = UserManager() + date_of_birth= models.DateField(blank=True , null = True) + gender = models.CharField(max_length=1, choices=GENDER_CHOICES ,null=True ) # default = GENDER_BOTH, + is_active = models.BooleanField(default=True) + is_admin = models.BooleanField(default=False) + + phone_number_regex = r'^(?:\+98|0)(?:\s?)9[0-9]{9}$' + phone_number_validator = RegexValidator( + regex=phone_number_regex, + message="Phone number must be in a valid Iranian format." + ) + + phone_number = models.CharField( + max_length=15, # Adjust the length as per your requirement + validators=[phone_number_validator], + blank=True, + null=True + ) + + role = models.CharField( max_length=255, choices=CHOICES , default=TYPE_USER ) + # email varification + is_email_verified = models.BooleanField(default=False) + verification_code = models.CharField(max_length=4, null=True, blank=True) + verification_tries_count = models.IntegerField(default=0) + last_verification_sent = models.DateTimeField(null=True, blank=True, default=datetime.now()) + has_verification_tries_reset = models.BooleanField(default=False) + + def get_role(self ) : + return self.role + + def __str__(self): + return self.email + + def has_perm(self , perm , obj=None ): + "Does the user have a specific permisision?" + return True + + def has_module_perms(self, app_label): + "Does the user have permissions to view the app `app_label`?" + # Simplest possible answer: Yes, always + return True + + @property + def is_staff(self): + "Is the user a member of staff?" + # Simplest possible answer: All admins are staff + return self.is_admin + + \ No newline at end of file diff --git a/BackEnd/accounts/serializers.py b/BackEnd/accounts/serializers.py new file mode 100644 index 0000000..24b700b --- /dev/null +++ b/BackEnd/accounts/serializers.py @@ -0,0 +1,171 @@ +from rest_framework import serializers +from accounts.models import User +from django.contrib.auth.password_validation import validate_password +from django.contrib.auth import password_validation +from django.core import exceptions as exception + + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ["date_of_birth", "email" ,"gender","firstname" ,"lastname" , "phone_number" , "id" ] #, "phone_number"] , "role" + + def validate(self, attrs): + return super().validate(attrs) + + +class CompleteInfoSerializer(serializers.ModelSerializer ) : + + class Meta: + model = User + fields = ['firstname' , 'lastname' , 'phone_number' , 'date_of_birth','gender' ] + + def validate(self, attrs): + return super().validate(attrs) + + + +class SignUpSerializer(serializers.ModelSerializer): + password2 = serializers.CharField(style={"input_type": "password"}, write_only=True) + password1 = serializers.CharField( + style={'input_type': 'password'}, + validators=[password_validation.validate_password], + write_only=True + ) + + class Meta: + model = User + fields = ('email','password1', 'password2' ) + + extra_kwargs = { + 'password1': {'write_only': True}, + 'password2': {'write_only': True}, + } + + def validate_email(self, value): + user = User.objects.filter(email__iexact=value) + if user.exists(): + user = user.first() + if user.is_email_verified: + raise serializers.ValidationError("Email already exists.") + if user.phone_number != self.initial_data.get('phone_number'): + raise serializers.ValidationError("Email already exists.") + + return str.lower(value) + + + + def validate_password2(self, value): + + if value != self.initial_data.get('password1'): + raise serializers.ValidationError('Passwords must match.') + return value + + def validate_password1(self, value): + if value != self.initial_data.get('password2'): + raise serializers.ValidationError('Passwords must match.') + password_validation.validate_password(value) + return value + + +class ActivationConfirmSerializer(serializers.Serializer): + verification_code = serializers.CharField(max_length=4, min_length=4) + + +class ActivationResendSerializer(serializers.Serializer): + email = serializers.EmailField(required=True) + + def validate(self, attrs): + email = attrs.get('email', None) + try: + user = User.objects.get(email__iexact=email) + except User.DoesNotExist: + raise serializers.ValidationError({"message": "user does not exist."}) + + if user.is_email_verified: + raise serializers.ValidationError({"message": "user with this email is already verified."}) + + attrs['user'] = user + return attrs + + + +class ForgotPasswordSerializer(serializers.Serializer): + email = serializers.EmailField() + +class ChangePasswordSerializer(serializers.Serializer): + old_password = serializers.CharField(required=True, write_only=True) + new_password = serializers.CharField(required=True, write_only=True) + new_password1 = serializers.CharField(required=True, write_only=True) + + def validate(self, attrs): + if attrs['new_password'] != attrs['new_password1']: + raise serializers.ValidationError({ + 'new_password1': ['Passwords must match.'], + }) + try: + validate_password(attrs['new_password']) + except exception.ValidationError as e: + raise serializers.ValidationError({ + 'new_password': list(e.messages) + }) + return attrs + +class ResetPasswordSerializer(serializers.Serializer): + new_password = serializers.CharField(write_only=True, required=True) + confirm_password = serializers.CharField(write_only=True, required=True) + verification_code = serializers.CharField(max_length=4, min_length=4) + + def validate(self, attrs): + new_password = attrs.get('new_password') + confirm_password = attrs.get('confirm_password') + if new_password != confirm_password: + raise serializers.ValidationError("New password and confirm password do not match.") + try: + validate_password(new_password) + except serializers.ValidationError as validation_error: + raise serializers.ValidationError({"new_password": validation_error}) + return attrs + + +class LoginSerializer(serializers.Serializer): + email=serializers.EmailField( + label=("Email"), + ) + password = serializers.CharField( + label=("password"), + style={"input_type": "password"}, + write_only=True + ) + + token = serializers.CharField( + label =("Token"), + read_only=True + ) + + def validate(self, attrs): + email = attrs.get('email', None) + password = attrs.get('password', None) + if email and password: + email = self.validate_email(email) + user = User.objects.get(email__iexact=email) + if not user.check_password(password): + msg = 'Incorrect password.' + raise serializers.ValidationError( { "message" : msg} , code='authorization') + if not user.is_email_verified: + raise serializers.ValidationError({"message": "User is not verified."}) + attrs['user'] = user + else: + msg = ('Must include "email" and "password".') + raise serializers.ValidationError(msg, code='authorization') + return attrs + + def validate_email(self, value): + msg = 'Email does not exist.' + user_exists = User.objects.filter(email__iexact=value).exists() + + if not user_exists: + raise serializers.ValidationError( { "message" : msg} ) + return str.lower(value) + diff --git a/BackEnd/accounts/tests.py b/BackEnd/accounts/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/BackEnd/accounts/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/BackEnd/accounts/urls.py b/BackEnd/accounts/urls.py new file mode 100644 index 0000000..e96971f --- /dev/null +++ b/BackEnd/accounts/urls.py @@ -0,0 +1,16 @@ +from .views import * +from django.urls import path + + +urlpatterns = [ + path('signup/' , SignUpView.as_view() , name='signup' ) , + path('activation_confirm//', ActivationConfirmView.as_view(), name='activation_confirm'), + path('activation_resend/', ActivationResend.as_view(), name='activation_resend'), + path('forgot_password/' , ForgotPassword.as_view() , name='forgot_password'), + path('reset_password//', ResetPassword.as_view(), name='reset_password'), + path('complete_info/' , CompleteInfoView.as_view() , name= 'complete_info') , + path('Login/',LoginView.as_view(),name='Login'), + path('Logout/',LogoutView.as_view(),name='Logout'), + path('change_password/' , ChangePasswordView.as_view() , name='change_password'), + path('get_user/' , RetrieveUserData.as_view() , name='get_user') +] \ No newline at end of file diff --git a/BackEnd/accounts/utils.py b/BackEnd/accounts/utils.py new file mode 100644 index 0000000..8404b30 --- /dev/null +++ b/BackEnd/accounts/utils.py @@ -0,0 +1,33 @@ +from rest_framework_simplejwt.tokens import RefreshToken +from django.core.mail import EmailMessage +import threading +from .models import User + +def generate_tokens(user_id): + refresh = RefreshToken.for_user(User.objects.get(id=user_id)) + return { + 'refresh': str(refresh), + 'access': str(refresh.access_token), + } + +class EmailThread(threading.Thread): + def __init__(self, email_handler ,subject,recipient_list, verification_token, registration_tries, show_text , token ): + super().__init__() + self.email_handler = email_handler + self.subject = subject + self.recipient_list = recipient_list + self.verification_token = verification_token + self.registration_tries = registration_tries + self.show_text = show_text + self.access_token = token + + def run(self): + self.email_handler.send_verification_message( + subject=self.subject, + recipient_list=self.recipient_list, + verification_token=self.verification_token, + registration_tries=self.registration_tries, + show_text=self.show_text , + token = self.access_token + ) + print(f"Email sent to {self.recipient_list}") \ No newline at end of file diff --git a/BackEnd/accounts/views.py b/BackEnd/accounts/views.py new file mode 100644 index 0000000..6268d60 --- /dev/null +++ b/BackEnd/accounts/views.py @@ -0,0 +1,324 @@ +from django.http import HttpResponse +from django.contrib.auth.hashers import make_password +from rest_framework import status +from rest_framework.permissions import IsAuthenticated +from rest_framework_simplejwt.authentication import JWTAuthentication +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.generics import CreateAPIView , GenericAPIView +from .serializers import SignUpSerializer , UserSerializer , ActivationConfirmSerializer ,ActivationResendSerializer \ + ,ForgotPasswordSerializer , ResetPasswordSerializer , LoginSerializer , CompleteInfoSerializer ,ChangePasswordSerializer +from .models import User +from datetime import datetime +from django.contrib.sites.shortcuts import get_current_site +from .utils import generate_tokens , EmailThread +import random +from django.conf import settings +import utils.email as email_handler +import jwt +from jwt.exceptions import ExpiredSignatureError, InvalidSignatureError +from django.shortcuts import render +from django.views.decorators.csrf import csrf_protect +from django.utils.decorators import method_decorator +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.decorators import login_required +from rest_framework_simplejwt.views import TokenObtainPairView +from rest_framework_simplejwt.tokens import RefreshToken +from utils.project_variables import MAX_VERIFICATION_TRIES +from counseling.models import Pationt + +class SignUpView(CreateAPIView): + serializer_class = SignUpSerializer + def post(self,request ) : + serializer = SignUpSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + validated_data = serializer.validated_data + email = str.lower(validated_data['email']) + # verification code + verification_code = str(random.randint(1000, 9999)) + user = User.objects.filter(email__iexact = email ) + user = User.objects.create( + email = email, + password = make_password( validated_data['password1']) , + verification_code=verification_code, + verification_tries_count=1, + last_verification_sent=datetime.now(), + ) + + # TODO make sure it is locate in right place + Pationt.objects.create( user= user ) + # varify email + token = generate_tokens(user.id)["access"] + subject = 'تایید ایمیل ثبت نام' + show_text = user.has_verification_tries_reset or user.verification_tries_count > 1 + # sending email verification with thread + email_thread = EmailThread(email_handler, subject=subject, + recipient_list=[user.email], + verification_token=verification_code, + registration_tries=user.verification_tries_count, + show_text=show_text , + token = token ) + + email_thread.start() + user_data = { + "user": UserSerializer(user).data , + "message": "User created successfully. Please check your email to activate your account.", + "code": verification_code, + "url": f'{settings.WEBSITE_URL}accounts/activation_confirm/{token}/' + } + return Response(user_data, status=status.HTTP_201_CREATED) + + + +class ActivationConfirmView(GenericAPIView): + serializer_class = ActivationConfirmSerializer + permission_classes = [] + + def get(self, request, *args, **kwargs): + return render(request, 'varify_email.html') + + def post(self, request, token): + token = self.validate_token(token) + if not token: + return Response({'message': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + user = self.get_user_from_token(token) + if not user: + return Response({'message': 'Invalid user'}, status=status.HTTP_400_BAD_REQUEST) + + if request.data.get('verification_code') != user.verification_code: + return Response({'message': 'Invalid code'}, status=status.HTTP_400_BAD_REQUEST) + + user.is_email_verified = True + user.verification_code = None + user.save() + return Response({'message' : 'successfully verified'}, status=status.HTTP_200_OK) + + def get_user_from_token(self, token): + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) + user_id = payload.get('user_id') + return User.objects.filter(id=user_id).first() + except ExpiredSignatureError: + return None + + + def validate_token(self, token): + try: + jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) + return token + except ExpiredSignatureError: + return None + except InvalidSignatureError: + return None + + + +class ActivationResend(GenericAPIView): + serializer_class = ActivationResendSerializer + permission_classes = [] + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + if serializer.is_valid(): + user = serializer.validated_data['user'] + subject = 'تایید ایمیل ثبت نام' + verification_code = str(random.randint(1000, 9999)) + user.verification_tries_count += 1 + user.verification_code = verification_code + user.last_verification_sent = datetime.now() + user.save() + show_text = user.has_verification_tries_reset or user.verification_tries_count > 1 + token = generate_tokens(user.id)["access"] + email_handler.send_verification_message(subject=subject, + recipient_list=[user.email], + verification_token=verification_code, + registration_tries=user.verification_tries_count, + show_text=show_text , + token=token) + return Response({ + "message": "email sent", + "url": f'{settings.WEBSITE_URL}accounts/activation_confirm/{token}/', + "code" : verification_code + }, status=status.HTTP_200_OK) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +class ChangePasswordView(GenericAPIView): + serializer_class = ChangePasswordSerializer + permission_classes = [IsAuthenticated] + + def post(self, request): + serializer = self.serializer_class(data=request.data, context={'request': request}) + if serializer.is_valid(): + user = request.user + print(user.email ) + old_password = serializer.validated_data['old_password'] + new_password = serializer.validated_data['new_password'] + + # Check if the current password matches the user's actual password + if not user.check_password(old_password): + return Response({'error': 'Invalid current password.'}, status=status.HTTP_400_BAD_REQUEST) + + # Change the user's password + user.set_password(new_password) + user.save() + + return Response({'message': 'Password changed successfully.'}, status=status.HTTP_200_OK) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +class ForgotPassword(GenericAPIView) : + serializer_class = ForgotPasswordSerializer + def post(self, request, *args, **kwargs) : + serializer = self.get_serializer(data=request.data) + if serializer.is_valid(): + email = str.lower(serializer.validated_data['email']) + users = User.objects.filter(email__iexact =email) + if (users.exists() ) : + user = users.first() + subject = 'فراموشی رمز عبور' + verification_code = str(random.randint(1000, 9999)) + user.verification_tries_count += 1 + user.verification_code = verification_code + user.last_verification_sent = datetime.now() + user.save() + token = generate_tokens(user.id)["access"] + email_handler.send_forget_password_verification_message(subject=subject, + recipient_list=[user.email], + verification_token=verification_code, + verification_tries=user.verification_tries_count) + return Response({ + "message": "email sent", + "url": f'{settings.WEBSITE_URL}accounts/reset_password/{token}/', + "code": verification_code + }, status=status.HTTP_200_OK) + else : + return Response( {'message': 'Invalid email'}, status=status.HTTP_400_BAD_REQUEST) + else: + return Response(serializer.errors , status=status.HTTP_400_BAD_REQUEST) + + +class ResetPassword(GenericAPIView) : + serializer_class = ResetPasswordSerializer + def post(self, request, token ) : + token = self.validate_token(token) + if not token: + return Response({'message': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST) + serializer = self.get_serializer(data= request.data ) + serializer.is_valid(raise_exception=True) + user = self.get_user_from_token(token) + if not user: + return Response({'message': 'Invalid user'}, status=status.HTTP_400_BAD_REQUEST) + + if serializer.validated_data['verification_code'] != user.verification_code: + return Response({'message': 'Invalid code'}, status=status.HTTP_400_BAD_REQUEST) + user.verification_code = None + new_password =serializer.validated_data['new_password'] + user.set_password(new_password) + user.save() + return Response( {"message" : "password successfully update"} , status=status.HTTP_204_NO_CONTENT) + + + def get_user_from_token(self, token): + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) + user_id = payload.get('user_id') + return User.objects.filter(id=user_id).first() + except ExpiredSignatureError: + return None + + def validate_token(self, token): + try: + jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) + return token + except ExpiredSignatureError: + return None + except InvalidSignatureError: + return None + + +class LoginView(TokenObtainPairView): + serializer_class = LoginSerializer + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.validated_data['user'] + if user is not None: + tokens = generate_tokens(user.id) + # login(request, user) + login(request, user, backend = 'django.contrib.auth.backends.ModelBackend') + return Response({ + 'refresh': tokens['refresh'], + 'access': tokens['access'], + 'user': UserSerializer(user).data + }) + return Response( {"message" : "there is no user with this email"} , status=status.HTTP_400_BAD_REQUEST) + + + +class RetrieveUserData(GenericAPIView) : + permission_classes = [IsAuthenticated] + serializer_class = UserSerializer + def get(self , request ) : + print( request.headers["Authorization"] ) + if not hasattr(request, 'user'): + return Response({'message': 'request does not have proper authentication tokens'}, status=status.HTTP_400_BAD_REQUEST) + email = str.lower(request.user.email) + user = User.objects.filter( email__iexact = email ) + if not user.exists() : + return Response({'message': 'Invalid user'}, status=status.HTTP_400_BAD_REQUEST) + user = user.first() + data = { + "user" : UserSerializer(user).data + } + return Response( data= data , status=status.HTTP_200_OK) + + +class CompleteInfoView(GenericAPIView) : + permission_classes = [IsAuthenticated] + serializer_class = CompleteInfoSerializer + def post( self , request ) : + serializer = CompleteInfoSerializer(data=request.data ) + serializer.is_valid(raise_exception=True) + validated_data = serializer.validated_data + email = str.lower( request.user.email) + user = User.objects.filter( email__iexact = email ) + if not user.exists(): + return Response({'message': 'Invalid user'}, status=status.HTTP_400_BAD_REQUEST) + user = user.first() + user.firstname = validated_data["firstname"] + user.gender = validated_data['gender'] + user.lastname = validated_data['lastname'] + user.date_of_birth = validated_data['date_of_birth'] + user.phone_number = validated_data['phone_number'] + user.save() + return Response(data={'message' : 'successfully updated'}, status=status.HTTP_200_OK) + + + +class LogoutView(APIView): + permission_classes = [IsAuthenticated] + def get(self, request): + print(request.COOKIES) + refresh_token = request.COOKIES.get('token') + if refresh_token: + try: + token = RefreshToken(refresh_token) + token.blacklist() + except Exception as e: + + return Response(data={'detail': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST) + response = Response(data={'detail': 'Logged out successfully'}, status=status.HTTP_200_OK) + response.delete_cookie('refresh_token') + response.delete_cookie('access_token') + return response + if request.user.is_authenticated: + logout(request) + return Response(data={'detail': 'Logged out successfully'}, status=status.HTTP_200_OK) + return Response(data={'detail': 'Not logged in'}, status=status.HTTP_400_BAD_REQUEST) + + diff --git a/BackEnd/counseling/__init__.py b/BackEnd/counseling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/counseling/admin.py b/BackEnd/counseling/admin.py new file mode 100644 index 0000000..2568aa0 --- /dev/null +++ b/BackEnd/counseling/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import Pationt , Psychiatrist + +admin.site.register(Pationt ) +admin.site.register(Psychiatrist) +# Register your models here. diff --git a/BackEnd/counseling/apps.py b/BackEnd/counseling/apps.py new file mode 100644 index 0000000..6c26bd6 --- /dev/null +++ b/BackEnd/counseling/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + + +class CounselingConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "counseling" + + def ready(self) : + import counseling.signals \ No newline at end of file diff --git a/BackEnd/counseling/migrations/0001_initial.py b/BackEnd/counseling/migrations/0001_initial.py new file mode 100644 index 0000000..f8a323a --- /dev/null +++ b/BackEnd/counseling/migrations/0001_initial.py @@ -0,0 +1,78 @@ +# Generated by Django 5.0.3 on 2024-04-08 07:27 + +import django.db.models.deletion +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="Pationt", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="Psychiatrist", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "image", + models.ImageField( + blank=True, null=True, upload_to="images/doctors/profile_pics" + ), + ), + ( + "field", + models.CharField( + choices=[ + ("individual", "Individual"), + ("couples", "Couples"), + ("kids", "Kids"), + ("teen", "Teen"), + ], + default="defualt", + max_length=255, + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/BackEnd/counseling/migrations/0002_pationt_telegramaccount_psychiatrist_telegramaccount.py b/BackEnd/counseling/migrations/0002_pationt_telegramaccount_psychiatrist_telegramaccount.py new file mode 100644 index 0000000..f45365d --- /dev/null +++ b/BackEnd/counseling/migrations/0002_pationt_telegramaccount_psychiatrist_telegramaccount.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.3 on 2024-04-14 06:56 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("counseling", "0001_initial"), + ("telegrambot", "0002_remove_telegramaccount_user"), + ] + + operations = [ + migrations.AddField( + model_name="pationt", + name="telegramAccount", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="telegrambot.telegramaccount", + ), + ), + migrations.AddField( + model_name="psychiatrist", + name="telegramAccount", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="telegrambot.telegramaccount", + ), + ), + ] diff --git a/BackEnd/counseling/migrations/0003_alter_pationt_telegramaccount_alter_pationt_user_and_more.py b/BackEnd/counseling/migrations/0003_alter_pationt_telegramaccount_alter_pationt_user_and_more.py new file mode 100644 index 0000000..c7ff48c --- /dev/null +++ b/BackEnd/counseling/migrations/0003_alter_pationt_telegramaccount_alter_pationt_user_and_more.py @@ -0,0 +1,46 @@ +# Generated by Django 5.0.3 on 2024-04-18 07:47 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("counseling", "0002_pationt_telegramaccount_psychiatrist_telegramaccount"), + ("telegrambot", "0002_remove_telegramaccount_user"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name="pationt", + name="telegramAccount", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="telegrambot.telegramaccount", + ), + ), + migrations.AlterField( + model_name="pationt", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + unique=True, + ), + ), + migrations.AlterField( + model_name="psychiatrist", + name="telegramAccount", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="telegrambot.telegramaccount", + ), + ), + ] diff --git a/BackEnd/counseling/migrations/0004_alter_psychiatrist_field.py b/BackEnd/counseling/migrations/0004_alter_psychiatrist_field.py new file mode 100644 index 0000000..c47cea3 --- /dev/null +++ b/BackEnd/counseling/migrations/0004_alter_psychiatrist_field.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.3 on 2024-04-19 08:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("counseling", "0003_alter_psychiatrist_image"), + ] + + operations = [ + migrations.AlterField( + model_name="psychiatrist", + name="field", + field=models.CharField( + choices=[ + ("individual", "فردی"), + ("couples", "زوج"), + ("kids", "کودک"), + ("teen", "نوجوان"), + ], + default="defualt", + max_length=255, + ), + ), + ] diff --git a/BackEnd/counseling/migrations/0004_alter_psychiatrist_user.py b/BackEnd/counseling/migrations/0004_alter_psychiatrist_user.py new file mode 100644 index 0000000..b1ce41d --- /dev/null +++ b/BackEnd/counseling/migrations/0004_alter_psychiatrist_user.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.3 on 2024-04-18 07:49 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "counseling", + "0003_alter_pationt_telegramaccount_alter_pationt_user_and_more", + ), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name="psychiatrist", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + unique=True, + ), + ), + ] diff --git a/BackEnd/counseling/migrations/0005_alter_psychiatrist_field.py b/BackEnd/counseling/migrations/0005_alter_psychiatrist_field.py new file mode 100644 index 0000000..3e7e1ce --- /dev/null +++ b/BackEnd/counseling/migrations/0005_alter_psychiatrist_field.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.3 on 2024-04-19 08:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("counseling", "0004_alter_psychiatrist_field"), + ] + + operations = [ + migrations.AlterField( + model_name="psychiatrist", + name="field", + field=models.CharField( + choices=[ + ("فردی", "فردی"), + ("زوج", "زوج"), + ("کودک", "کودک"), + ("نوجوان", "نوجوان"), + ], + default="defualt", + max_length=255, + ), + ), + ] diff --git a/BackEnd/counseling/migrations/__init__.py b/BackEnd/counseling/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/counseling/models.py b/BackEnd/counseling/models.py new file mode 100644 index 0000000..36325cb --- /dev/null +++ b/BackEnd/counseling/models.py @@ -0,0 +1,78 @@ +from typing import Any +from django.db import models +from accounts.models import User +from django.conf import settings +from django.contrib.contenttypes.fields import GenericRelation +from django.core.exceptions import ValidationError +from telegrambot.models import TelegramAccount + +class Psychiatrist(models.Model ) : + TYPE_INDIVIDUAL = "فردی" + TYPE_KIDS = "کودک" + TYPE_COUPLES = "زوج" + TYPE_TEEN = "نوجوان" + TYPE_USER = "defualt" + CHOICES = ( + (TYPE_INDIVIDUAL , "فردی") , + (TYPE_COUPLES , "زوج") , + (TYPE_KIDS , "کودک") , + (TYPE_TEEN , "نوجوان") + ) + telegramAccount = models.OneToOneField(TelegramAccount , on_delete=models.CASCADE,null=True ,blank=True ) + + # telegram account = models.one to one ( telegram account ) + user = models.ForeignKey(User, on_delete=models.CASCADE , unique=True ) + image = models.ImageField(upload_to='images/doctors/profile_pics', null=True,blank=True ) #, default='images/doctors/profile_pics/default.png') + field = models.CharField( max_length=255, choices=CHOICES , default=TYPE_USER) + + def get_default_profile_image(self): + + if self.user.gender == 'M': + res = 'images/doctors/profile_pics/male_default.png' ## settings.MEDIA_URL + + return res + else: + res = 'images/doctors/profile_pics/female_default.png' + return res + + def get_profile_image(self ) : + if not self.image : + var = self.get_default_profile_image() + return var + else : + VAR = settings.MEDIA_URL + str(self.image ) + print(VAR) + return VAR + + def get_fullname(self) : + return str(self.user.firstname) + " " + str(self.user.lastname) + + def save(self, *args, **kwargs): + """ + Check if there's already a Psychiatrist object associated with this User + """ + if Pationt.objects.filter(user=self.user).exists() : + raise ValidationError("a patient could not be register as a doctor") + if not self.user.role == 'doctor': + self.user.role = User.TYPE_DOCTOR + self.user.save() + super().save(*args, **kwargs) + + + +class Pationt( models.Model ) : + user = models.ForeignKey(User, on_delete=models.CASCADE , unique=True ) + telegramAccount = models.OneToOneField(TelegramAccount , on_delete=models.CASCADE,null=True , blank=True ) + def save(self, *args, **kwargs): + """ + Check if there's already a Pationt object associated with this User + """ + # if Pationt.objects.filter(user=self.user).exists() and self.user.role == User.TYPE_USER : + # return super().save(*args, **kwargs) + # if Pationt.objects.filter(user=self.user).exists() : + # raise ValidationError("A Pationt object already exists for this User.") + if Psychiatrist.objects.filter(user=self.user).exists() : + raise ValidationError("a doctor could not be register as a patient") + return super().save(*args, **kwargs) + + + diff --git a/BackEnd/counseling/signals.py b/BackEnd/counseling/signals.py new file mode 100644 index 0000000..5f6a0ef --- /dev/null +++ b/BackEnd/counseling/signals.py @@ -0,0 +1,9 @@ +from django.db.models.signals import post_save, pre_delete ,pre_save , post_delete +from django.dispatch import receiver +from counseling.models import Psychiatrist , Pationt + + +@receiver(post_delete , sender= Pationt ) +@receiver(post_delete , sender= Psychiatrist ) +def delete_associated_user( sender , instance , **kwargs ) : + instance.user.delete() \ No newline at end of file diff --git a/BackEnd/counseling/tests.py b/BackEnd/counseling/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/BackEnd/counseling/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/BackEnd/counseling/urls.py b/BackEnd/counseling/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/counseling/views.py b/BackEnd/counseling/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/BackEnd/counseling/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/BackEnd/datadump.json b/BackEnd/datadump.json new file mode 100644 index 0000000..8e2f0be --- /dev/null +++ b/BackEnd/datadump.json @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/BackEnd/manage.py b/BackEnd/manage.py new file mode 100644 index 0000000..1733f6f --- /dev/null +++ b/BackEnd/manage.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys +import multiprocessing +from django.core.management import call_command +from django.core.management.commands.runserver import Command as RunServerCommand +# from django.core.management import execute_from_command_line + +# def run_send_daily_message(): +# """Function to run the send_daily_message management command.""" +# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BackEnd.settings") +# from django.core.management import call_command +# call_command("start_bot") + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BackEnd.settings") + try: + from django.core.management import execute_from_command_line + # send_daily_message_process = multiprocessing.Process(target=run_send_daily_message) + # send_daily_message_process.start() + # execute_from_command_line(sys.argv) + # send_daily_message_process.join() + + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() + + + +# class Command(RunServerCommand): +# def handle(self, *args, **options): +# # Define a function to run your management command +# def run_tasks(): + +# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'BackEnd.settings') +# call_command('command') + +# # Create a separate process for running the management command +# process = multiprocessing.Process(target=run_tasks) +# process.start() + +# # Run the Django development server as usual +# super().handle(*args, **options) + +# if __name__ == "__main__": +# from django.core.management import execute_from_command_line +# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BackEnd.settings") +# print("lsjdfljflf") +# execute_from_command_line(sys.argv) + diff --git a/BackEnd/reservation/__init__.py b/BackEnd/reservation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/reservation/admin.py b/BackEnd/reservation/admin.py new file mode 100644 index 0000000..22db211 --- /dev/null +++ b/BackEnd/reservation/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from .models import Reservation +admin.site.register( Reservation) +# Register your models here. diff --git a/BackEnd/reservation/apps.py b/BackEnd/reservation/apps.py new file mode 100644 index 0000000..058a964 --- /dev/null +++ b/BackEnd/reservation/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ReservationConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "reservation" diff --git a/BackEnd/reservation/migrations/0001_initial.py b/BackEnd/reservation/migrations/0001_initial.py new file mode 100644 index 0000000..04c4117 --- /dev/null +++ b/BackEnd/reservation/migrations/0001_initial.py @@ -0,0 +1,68 @@ +# Generated by Django 5.0.3 on 2024-04-08 07:27 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("counseling", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Reservation", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("date", models.DateField()), + ("time", models.TimeField()), + ( + "type", + models.CharField(choices=[("حضوری", "حضوری"), ("مجازی", "مجازی")]), + ), + ( + "day", + models.CharField( + choices=[ + ("شنبه", "شنبه"), + ("یک\u200cشنبه", "یک\u200cشنبه"), + ("دو\u200cشنبه", "دو\u200cشنبه"), + ("سه\u200cشنبه", "سه\u200cشنبه"), + ("چهار\u200cشنبه", "چهار\u200cشنبه"), + ("پنج\u200cشنبه", "پنج\u200cشنبه"), + ("جمعه", "جمعه"), + ], + max_length=10, + ), + ), + ( + "pationt", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="pationt_reservations", + to="counseling.pationt", + ), + ), + ( + "psychiatrist", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="psychiatrist_reservations", + to="counseling.psychiatrist", + ), + ), + ], + options={"unique_together": {("date", "time")},}, + ), + ] diff --git a/BackEnd/reservation/migrations/0002_alter_reservation_day.py b/BackEnd/reservation/migrations/0002_alter_reservation_day.py new file mode 100644 index 0000000..77f5b64 --- /dev/null +++ b/BackEnd/reservation/migrations/0002_alter_reservation_day.py @@ -0,0 +1,30 @@ +# Generated by Django 5.0.3 on 2024-04-08 11:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("reservation", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="reservation", + name="day", + field=models.CharField( + blank=True, + choices=[ + ("شنبه", "شنبه"), + ("یک\u200cشنبه", "یک\u200cشنبه"), + ("دو\u200cشنبه", "دو\u200cشنبه"), + ("سه\u200cشنبه", "سه\u200cشنبه"), + ("چهار\u200cشنبه", "چهار\u200cشنبه"), + ("پنج\u200cشنبه", "پنج\u200cشنبه"), + ("جمعه", "جمعه"), + ], + max_length=10, + ), + ), + ] diff --git a/BackEnd/reservation/migrations/0003_alter_reservation_type.py b/BackEnd/reservation/migrations/0003_alter_reservation_type.py new file mode 100644 index 0000000..463f212 --- /dev/null +++ b/BackEnd/reservation/migrations/0003_alter_reservation_type.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.3 on 2024-04-10 18:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("reservation", "0002_alter_reservation_day"), + ] + + operations = [ + migrations.AlterField( + model_name="reservation", + name="type", + field=models.CharField( + choices=[("حضوری", "حضوری"), ("مجازی", "مجازی")], max_length=15 + ), + ), + ] diff --git a/BackEnd/reservation/migrations/0004_alter_reservation_unique_together_and_more.py b/BackEnd/reservation/migrations/0004_alter_reservation_unique_together_and_more.py new file mode 100644 index 0000000..99faccf --- /dev/null +++ b/BackEnd/reservation/migrations/0004_alter_reservation_unique_together_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 5.0.3 on 2024-04-18 10:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("counseling", "0004_alter_psychiatrist_user"), + ("reservation", "0003_alter_reservation_type"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="reservation", + unique_together={("date", "time", "pationt", "psychiatrist")}, + ), + migrations.AlterField( + model_name="reservation", + name="day", + field=models.CharField( + blank=True, + choices=[ + ("شنبه", "شنبه"), + ("یکشنبه", "یک\u200cشنبه"), + ("دوشنبه", "دو\u200cشنبه"), + ("سه\u200cشنبه", "سه\u200cشنبه"), + ("چهارشنبه", "چهار\u200cشنبه"), + ("پنج\u200cشنبه", "پنج\u200cشنبه"), + ("جمعه", "جمعه"), + ], + max_length=10, + ), + ), + ] diff --git a/BackEnd/reservation/migrations/__init__.py b/BackEnd/reservation/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/reservation/models.py b/BackEnd/reservation/models.py new file mode 100644 index 0000000..557d85a --- /dev/null +++ b/BackEnd/reservation/models.py @@ -0,0 +1,69 @@ +from typing import Iterable +from django.db import models +from counseling.models import Psychiatrist , Pationt +from datetime import date + +class Reservation(models.Model) : + DAY0 = 'شنبه' + DAY1 = 'یکشنبه' + DAY2 = 'دوشنبه' + DAY3 = 'سه‌شنبه' + DAY4 = 'چهارشنبه' + DAY5 = 'پنج‌شنبه' + DAY6 = 'جمعه' + + DAY_CHOICES = [ + (DAY0 , 'شنبه' ), + ( DAY1, 'یک‌شنبه'), + ( DAY2 ,'دو‌شنبه'), + (DAY3 ,'سه‌شنبه'), + (DAY4 ,'چهار‌شنبه'), + (DAY5 , 'پنج‌شنبه'), + (DAY6 ,'جمعه') + ] + + REMOTE = 'مجازی' + INPERSON = 'حضوری' + RESERVE_CHOICES = [ + (INPERSON , 'حضوری') , + ( REMOTE , 'مجازی') + ] + + psychiatrist = models.ForeignKey(Psychiatrist, on_delete=models.CASCADE, related_name='psychiatrist_reservations') + pationt = models.ForeignKey(Pationt, on_delete=models.CASCADE, related_name='pationt_reservations') + date = models.DateField() + time = models.TimeField() + type = models.CharField(max_length=15 ,choices=RESERVE_CHOICES) + day = models.CharField(max_length=10, choices=DAY_CHOICES , blank=True ) + + class Meta: + unique_together = ['date', 'time' , 'pationt' , 'psychiatrist'] + + def save(self, *args, **kwargs) : + day_dict = { + 0 : 'دوشنبه' , + 1 : 'سه شنبه' , + 2 : 'چهارشنبه' , + 3 : 'پنج‌شنبه' , + 4 : 'جمعه' , + 5 : 'شنبه' , + 6 : 'یکشنبه' + } + + if not self.day: + day_num = date.today().weekday() + if day_dict[day_num] == self.DAY0 : + self.day = self.DAY0 + elif day_dict[day_num] == self.DAY1 : + self.day = self.DAY1 + elif day_dict[day_num] == self.DAY2 : + self.day = self.DAY2 + elif day_dict[day_num] == self.DAY3 : + self.day = self.DAY3 + elif day_dict[day_num] == self.DAY4 : + self.day = self.DAY4 + elif day_dict[day_num] == self.DAY5 : + self.day = self.DAY5 + else : + self.day = self.DAY6 + return super().save() \ No newline at end of file diff --git a/BackEnd/reservation/serializer.py b/BackEnd/reservation/serializer.py new file mode 100644 index 0000000..eaac01d --- /dev/null +++ b/BackEnd/reservation/serializer.py @@ -0,0 +1,28 @@ +from rest_framework import serializers +from accounts.models import User +from django.contrib.auth.password_validation import validate_password +from django.contrib.auth import password_validation +from django.core import exceptions as exception +from .models import Reservation +from datetime import date + + +class ReserveSerializer(serializers.Serializer ) : + class Meta : + model = Reservation + fields = ["day" , "type" , "date" , "time" , "id"] + + + def validate_date(self, attrs): + if date.today > attrs : + return serializers.ValidationError("date is not accessable") + return attrs + +class DaySerializer(serializers.Serializer) : + date = serializers.DateField() + doctor_id = serializers.IntegerField() + +class BetweenDatesSerializer(serializers.Serializer): + start_date = serializers.DateField() + end_date = serializers.DateField() + doctor_id = serializers.IntegerField() \ No newline at end of file diff --git a/BackEnd/reservation/tests.py b/BackEnd/reservation/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/BackEnd/reservation/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/BackEnd/reservation/urls.py b/BackEnd/reservation/urls.py new file mode 100644 index 0000000..18d8407 --- /dev/null +++ b/BackEnd/reservation/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from .views import * + +urlpatterns = [ + path("create/" , ReservationView.as_view({'post':'create'}) , name="create") , + path("delete//" , ReservationView.as_view({'delete': 'destroy'}) , name="delete") , + path("between_dates/", ReservationView.as_view({'get': 'between_dates'}), name="between_dates"), + path("last_month/" ,ReservationView.as_view({'get' : 'list_month'}),name="last_month" ), + path("last_week/" , ReservationView.as_view({'get' : 'last_week'}) , name="last_week") +] diff --git a/BackEnd/reservation/views.py b/BackEnd/reservation/views.py new file mode 100644 index 0000000..2e60515 --- /dev/null +++ b/BackEnd/reservation/views.py @@ -0,0 +1,112 @@ +from django.shortcuts import render +from rest_framework import viewsets, serializers +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework import status +from counseling.models import Pationt , Psychiatrist +from .serializer import ReserveSerializer +from .models import Reservation +from django.utils import timezone +from django.core.exceptions import ObjectDoesNotExist +from datetime import date + + + +class ReservationView(viewsets.ModelViewSet ) : + """ + A viewset for reservation that provides `create()`, `retrieve()`, `update()`, + `partial_update()`, `destroy()` and `list()` actions. + """ + permission_classes = [IsAuthenticated] + serializer_class = ReserveSerializer + + def create(self, request, *args, **kwargs): + serializer = self.serializer_class( data= request.data ) + serializer.is_valid(raise_exception=True) + + if not hasattr(request, 'user'): + return Response({'message': 'user is not loged in'}, status=status.HTTP_400_BAD_REQUEST) + + doctor = request.data.get('doctor_id') + pationt = Pationt.objects.filter( user = request.user ) + reserve = Reservation.objects.create( + date = serializer.validated_data["date"] , + type = serializer.validated_data["type"] , + time = serializer.validated_data["time"] , + day = serializer.validated_data["day"] , + psychiatrist = doctor , + Pationt = pationt + ) + + response = { + "reserve" : ReserveSerializer(reserve).data , + "message" : "reservation successfully created" + } + return Response( data=response , status=status.HTTP_201_CREATED) + + + def destroy(self, request, *args, **kwargs): + try: + reservation_id = kwargs.get('pk') + reservation = Reservation.objects.get(id=reservation_id) + reservation.delete() + return Response({"message": "Reservation successfully deleted"}, status=status.HTTP_204_NO_CONTENT) + except Reservation.DoesNotExist: + return Response({"message": "Reservation not found"}, status=status.HTTP_404_NOT_FOUND) + + + def list_month(self, request): + queryset = Reservation.objects.all() + month = request.data.get('month') + year = request.data.get('year') + queryset = queryset.filter(date__year=year, date__month=month) + serializer = ReserveSerializer(queryset, many=True) + return Response(serializer.data , status=status.HTTP_200_OK) + + def last_week( self , request ) : + # get the date of saturday + # day_dict = { + # 0 : 'شنبه' , + # 1 : 'یکشنبه', + # 2 : 'دوشنبه' , + # 3 : 'سه شنبه' , + # 4 : 'چهارشنبه' , + # 5 : 'پنج‌شنبه' , + # 6 : 'جمعه' + # } + serializer = self.get_serializer(data=request.query_params) + serializer.is_valid(raise_exception=True) + date1 = serializer.validated_data['date'] + doctor = serializer.validated_data['doctor_id'] + day = (date1.weekday() + 2)%7 + saturday = date( day= date1.day- day , month=date1.month , year=date1.year) + thirsday = date( day= saturday.day+5 , month=saturday.month , year=saturday.year) + reservations = Reservation.objects.filter(date__range=[saturday, thirsday], psychiatrist=doctor) + serializer = ReserveSerializer(reservations, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + + def between_dates(self, request): + serializer = self.get_serializer(data=request.query_params) + serializer.is_valid(raise_exception=True) + + start_date = serializer.validated_data['start_date'] + end_date = serializer.validated_data['end_date'] + doctor_id = serializer.validated_data['doctor_id'] + + try: + doctor = Psychiatrist.objects.get(id=doctor_id) + except ObjectDoesNotExist: + return Response({"message": "Doctor not found"}, status=status.HTTP_404_NOT_FOUND) + + if not start_date or not end_date: + return Response({"message": "Both start_date and end_date are required"}, status=status.HTTP_400_BAD_REQUEST) + try: + start_date = timezone.datetime.strptime(start_date, "%Y-%m-%d").date() + end_date = timezone.datetime.strptime(end_date, "%Y-%m-%d").date() + except ValueError: + return Response({"message": "Invalid date format. Use YYYY-MM-DD."}, status=status.HTTP_400_BAD_REQUEST) + + reservations = Reservation.objects.filter(date__range=[start_date, end_date], psychiatrist=doctor) + serializer = ReserveSerializer(reservations, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/BackEnd/telegrambot/__init__.py b/BackEnd/telegrambot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/telegrambot/admin.py b/BackEnd/telegrambot/admin.py new file mode 100644 index 0000000..bcecbcc --- /dev/null +++ b/BackEnd/telegrambot/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import TelegramAccount + +admin.site.register( TelegramAccount ) +# Register your models here. +# mnmnmnmn \ No newline at end of file diff --git a/BackEnd/telegrambot/apps.py b/BackEnd/telegrambot/apps.py new file mode 100644 index 0000000..36be1df --- /dev/null +++ b/BackEnd/telegrambot/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TelegrambotConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "telegrambot" diff --git a/BackEnd/telegrambot/bot.py b/BackEnd/telegrambot/bot.py new file mode 100644 index 0000000..d3e8ec8 --- /dev/null +++ b/BackEnd/telegrambot/bot.py @@ -0,0 +1,52 @@ +import requests +from .credentials import TELEGRAM_API_URL, URL , TOKEN +from telegram.ext import Updater +import datetime +from reservation.models import Reservation +from datetime import time + +def send_message(method, data): + return requests.post(TELEGRAM_API_URL + method, data) + + +def send_daily_message(context): + """Function to send daily message to all users. + """ + print("here") + reserves = Reservation.objects.all() + # accounts = TelegramAccount.objects.all() + for reserve in reserves : + p = reserve.pationt + d = reserve.psychiatrist + accountP = p.telegramAccount + accountD = d.telegramAccount + doctor_msg = 'شما امروز در ساعت فلان یک ملاقات با بیمار بنام فلان دارید.' + patient_msg = 'شما امروز با دکتر فلانی در زمان فلان یک ملاقات دارید.' + send_message("sendMessage", { + 'chat_id': accountD.chat_id, + 'text': doctor_msg + }) + + send_message("sendMessage", { + 'chat_id': accountP.chat_id, + 'text': patient_msg + }) + +def main(): + # Initialize the updater and job queue + updater = Updater(token=TOKEN , use_context=True) + job_queue = updater.job_queue + + # Add the daily job to the job queue + job_queue.run_daily(send_daily_message, time(hour=8 , minute=0 , second=0), context='your_chat_id') + # Start the bot + updater.start_polling() + updater.idle() + +if __name__ == '__main__': + main() + + + + + diff --git a/BackEnd/telegrambot/credentials.py b/BackEnd/telegrambot/credentials.py new file mode 100644 index 0000000..591045e --- /dev/null +++ b/BackEnd/telegrambot/credentials.py @@ -0,0 +1,9 @@ +TOKEN = '6800635126:AAGV8Ev4Wf4qsq4lPa-Vm-dcYNsoQ19h1So' +NGROK = 'https://a882-37-44-62-110.ngrok-free.app' +URL = f'{NGROK}/getpost/' +TELEGRAM_API_URL = f'https://api.telegram.org/bot{TOKEN}/' +WEB_HOOK_URL = f'{TELEGRAM_API_URL}/setWebhook?url={NGROK}/telegrambot/getpost/' + +# https://api.telegram.org/bot6800635126:AAGV8Ev4Wf4qsq4lPa-Vm-dcYNsoQ19h1So/setWebhook?url=https://33e3-37-156-157-110.ngrok-free.app +# https://api.telegram.org/bot6800635126:AAGV8Ev4Wf4qsq4lPa-Vm-dcYNsoQ19h1So/setWebhook?url=https://a882-37-44-62-110.ngrok-free.app/telegrambot/getpost/ +# /////////// \ No newline at end of file diff --git a/BackEnd/telegrambot/management/__init__.py b/BackEnd/telegrambot/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/telegrambot/management/commands/__init__.py b/BackEnd/telegrambot/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/telegrambot/management/commands/start_bot.py b/BackEnd/telegrambot/management/commands/start_bot.py new file mode 100644 index 0000000..80f17b9 --- /dev/null +++ b/BackEnd/telegrambot/management/commands/start_bot.py @@ -0,0 +1,57 @@ +import subprocess +from django.core.management.base import BaseCommand +import requests +from telegrambot.credentials import TELEGRAM_API_URL, URL , TOKEN +from telegram.ext import Updater +import datetime +from reservation.models import Reservation +from datetime import time + +class Command(BaseCommand): + __name__ = "start_bot" + help = 'Starts the Telegram bot process' + def handle(self, *args, **options): + q = Reservation.objects.all() + # updater = Updater(TOKEN) #token=TOKEN , use_context=True + # job_queue = updater.job_queue + # job_queue.run_daily(self.send_daily_message, time(hour=13 , minute=0 , second=0)) + self.send_daily_message() + # updater.start_polling() + # updater.idle() + + def send_message(self, method, data): + return requests.post(TELEGRAM_API_URL + method, data) + + def send_daily_message(self ): + """Function to send daily message to all users. + """ + today = datetime.date.today() + reserves = Reservation.objects.filter( date = today ) + if reserves.exists() : + for reserve in reserves : + p = reserve.pationt + d = reserve.psychiatrist + accountP = p.telegramAccount + accountD = d.telegramAccount + + if accountD : + doctor_msg = f'شما امروز یک رزرو با اطلاعات زیر دارید.\n زمان :{reserve.time} ' + + self.send_message("sendMessage", { + 'chat_id': accountD.chat_id, + 'text': doctor_msg + }) + if accountP : + print("doctor account : " , accountP.chat_id) + patient_msg = f'پیام یادآوری: \n نوبت رزرو شده شامل اطلاعات زیر است : \n زمان : {reserve.time} \n دکتر : {d.get_fullname()}' + self.send_message("sendMessage", { + 'chat_id': accountP.chat_id, + 'text': patient_msg + }) + else : + print("there is no reservation") + + + + + diff --git a/BackEnd/telegrambot/migrations/0001_initial.py b/BackEnd/telegrambot/migrations/0001_initial.py new file mode 100644 index 0000000..3ba022f --- /dev/null +++ b/BackEnd/telegrambot/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 5.0.3 on 2024-04-12 18:15 + +import django.db.models.deletion +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="TelegramAccount", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("is_varify", models.BooleanField(default=False)), + ("chat_id", models.CharField(max_length=15, unique=True)), + ( + "varification_code", + models.CharField(blank=True, max_length=4, null=True), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/BackEnd/telegrambot/migrations/0002_remove_telegramaccount_user.py b/BackEnd/telegrambot/migrations/0002_remove_telegramaccount_user.py new file mode 100644 index 0000000..6602853 --- /dev/null +++ b/BackEnd/telegrambot/migrations/0002_remove_telegramaccount_user.py @@ -0,0 +1,14 @@ +# Generated by Django 5.0.3 on 2024-04-14 06:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("telegrambot", "0001_initial"), + ] + + operations = [ + migrations.RemoveField(model_name="telegramaccount", name="user",), + ] diff --git a/BackEnd/telegrambot/migrations/__init__.py b/BackEnd/telegrambot/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BackEnd/telegrambot/models.py b/BackEnd/telegrambot/models.py new file mode 100644 index 0000000..8bd8402 --- /dev/null +++ b/BackEnd/telegrambot/models.py @@ -0,0 +1,12 @@ +from typing import Iterable +from django.db import models +from accounts.models import User +# Create your models here. +class TelegramAccount(models.Model) : + # user = models.ForeignKey(User , on_delete=models.CASCADE ) + is_varify = models.BooleanField(default=False ) + chat_id = models.CharField( unique= True , max_length=15 ) + varification_code = models.CharField( max_length=4 , blank=True , null=True ) + + + \ No newline at end of file diff --git a/BackEnd/telegrambot/tests.py b/BackEnd/telegrambot/tests.py new file mode 100644 index 0000000..c36d4ba --- /dev/null +++ b/BackEnd/telegrambot/tests.py @@ -0,0 +1,15 @@ +from django.test import TestCase +import re + +email_pattern = r"email\s*/\s*([^<>@\s]+@[^<>@\s]+\.[^<>@\s]+)" + +# text = "email / hslfj@gmail.com" +text = "email / another@example.com email / ano33ther@example.com" + + +match = re.search( email_pattern , text ) + +if match : + print( match.groups(2)) + + diff --git a/BackEnd/telegrambot/urls.py b/BackEnd/telegrambot/urls.py new file mode 100644 index 0000000..9205ab7 --- /dev/null +++ b/BackEnd/telegrambot/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from . import views + + +urlpatterns = [ + path('getpost/', views.telegram_bot, name='telegram_bot'), + path('setwebhook/', views.setwebhook, name='setwebhook'), +] + +# telegrambot/setwebhook/ \ No newline at end of file diff --git a/BackEnd/telegrambot/views.py b/BackEnd/telegrambot/views.py new file mode 100644 index 0000000..e84d80a --- /dev/null +++ b/BackEnd/telegrambot/views.py @@ -0,0 +1,177 @@ +import json +import re +import requests +from django.http import HttpResponse, HttpResponseBadRequest +from django.views.decorators.csrf import csrf_exempt +from .models import TelegramAccount +from .credentials import TELEGRAM_API_URL, URL , TOKEN , WEB_HOOK_URL +from accounts.models import User +from counseling.models import Pationt, Psychiatrist +import utils.email as email_handler +from rest_framework.response import Response +import random +from rest_framework import status +from telegram.ext import Updater , CallbackContext +import datetime +from reservation.models import Reservation +from datetime import time + +email_pattern = r"email\s*/\s*([^<>@\s]+@[^<>@\s]+\.[^<>@\s]+)" +code_pattern = r"code\s*/\s*(\d{4})" + +def set_webhook(request): + response = requests.post(TELEGRAM_API_URL + "setWebhook?url=" + URL).json() + return HttpResponse(f"{response}") + +@csrf_exempt +def telegram_bot(request): + if request.method == 'POST': + update = json.loads(request.body.decode('utf-8')) + handle_update(update) + return HttpResponse('ok') + else: + return HttpResponseBadRequest('Bad Request') + +def handle_update(update): + chat_id = update['message']['chat']['id'] + text = update['message']['text'] + print( "chat id ----------------> " , chat_id ) + if text == "/start": + handle_start_command(chat_id) + elif text == "/verify": + tel_chat = TelegramAccount.objects.filter( chat_id = chat_id ).first() + if tel_chat : + if tel_chat.is_varify : + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'این اکانت قبلا تایید شده.' + }) + return Response({"message" : "this email does not exist."} , status=status.HTTP_400_BAD_REQUEST) + handle_verify_command(chat_id) + else: + + handle_other_commands(chat_id, text) + +def handle_start_command(chat_id): + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'به بات تلگرام اینیاک خوش آمدید. برای تایید حساب خود گزینه verify را از منو انتخاب کنید.' + }) + +def handle_verify_command(chat_id): + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'لطفا ایمیلتان را به صورت روبه رو وارد کنید \n email/<ایمیل >' + }) + +def handle_other_commands(chat_id ,text ): + match_email = re.search(email_pattern, text) + match_code = re.search(code_pattern, text) + if match_email or match_code : + tel_chat = TelegramAccount.objects.filter( chat_id = chat_id ) + if tel_chat : + if tel_chat.first().is_varify : + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'این اکانت قبلا تایید شده.' + }) + return Response({"message" : "this email does not exist."} , status=status.HTTP_400_BAD_REQUEST) + + if match_email: + email = match_email.group(1) + print("email ------> " , email ) + user = User.objects.filter(email__iexact = email.strip() ).first() + + if not user : + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'ایمیل داده شده در میان کاربران موجود نمی باشد. لطفا ایمیل صحیح را وارد نمایید.' + }) + return Response({"message" : "this email does not exist."} , status=status.HTTP_400_BAD_REQUEST) + + elif ( user.is_email_verified == False) : + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'ایمیل شما توسط سایت تایید نشده است. لطفا ابتدا ایمیل خود را در سایت کلنیک تایید کنید.' + }) + return Response({"message" : "this email does not varifyed in app."} , status=status.HTTP_400_BAD_REQUEST) + + else : + + + acc = TelegramAccount.objects.filter( chat_id = chat_id ) + if not acc.exists() : + verification_code = str(random.randint(1000, 9999)) + tel_account = TelegramAccount.objects.create( + varification_code = verification_code , + chat_id = chat_id + ) + + if user.role == "doctor" : + doctor = Psychiatrist.objects.filter( user = user ) + if not doctor : + return Response({"message" : "there is no doctor with this email ."} , status=status.HTTP_400_BAD_REQUEST) + doctor = doctor.first() + doctor.telegramAccount = tel_account + elif user.role == "user" : + patient = Pationt.objects.filter( user = user ) + print("117") + if not patient : + return Response({"message" : "there is no patient with this email ."} , status=status.HTTP_400_BAD_REQUEST) + patient = patient.first() + print( patient.pk) + patient.telegramAccount = tel_account + patient.save() + + print("118") + email_handler.send_telegram_account_verification_message( + subject='تایید اکانت تلگرام' , + recipient_list=[user.email ] , + verification_token= verification_code , + ) + print("128") + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'کد تایید ارسال شده به ایمیلتان را به صورت روبه رو وارد کنید\n code/ <کد>.' + }) + + elif match_code: + tel_account = TelegramAccount.objects.filter(chat_id = chat_id ).first() + if not tel_account : + return Response({"message" : "this chat_id is not varified by the Enic bot!"} , status=status.HTTP_400_BAD_REQUEST) + else : + code = match_code.group(1) + if tel_account.varification_code != code : + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'کد وارد شده درست نمی باشد یا با فرمت خواسته شده وارد نشده.' + }) + return Response({"message" : "varification code did not match"} , status=status.HTTP_400_BAD_REQUEST) + else : + tel_account.is_varify = True + tel_account.varification_code = '' + tel_account.save() + + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'حساب شما با موفقیت تایید شد. رزروهای شما از طریق بات تلگرام به شما اطلاع رسانی خواهد شد.' + }) + + else: + send_message("sendMessage", { + 'chat_id': chat_id, + 'text': 'این پیام پشتیبانی نمیشود' + }) + + +def setwebhook(request) : + if request.method == 'GET' : + return requests.get(url=WEB_HOOK_URL ) + + +def send_message(method, data): + return requests.post(TELEGRAM_API_URL + method, data) + + + + diff --git a/BackEnd/templates/active_email.html b/BackEnd/templates/active_email.html new file mode 100644 index 0000000..b31a139 --- /dev/null +++ b/BackEnd/templates/active_email.html @@ -0,0 +1,55 @@ +{% autoescape off %} + + + + + + تایید ایمیل + + + +
+

کد تایید ایمیل:

+

{{ email_verification_token }}

+ +
+ + +{% endautoescape %} + + diff --git a/BackEnd/templates/forget_password.html b/BackEnd/templates/forget_password.html new file mode 100644 index 0000000..051ea46 --- /dev/null +++ b/BackEnd/templates/forget_password.html @@ -0,0 +1,53 @@ +{% autoescape off %} + + + + + + تایید ایمیل + + + +
+

کد تایید ایمیل:

+

{{ email_verification_token }}

+
+ + +{% endautoescape %} + + diff --git a/BackEnd/templates/successfull_activation.html b/BackEnd/templates/successfull_activation.html new file mode 100644 index 0000000..eda5775 --- /dev/null +++ b/BackEnd/templates/successfull_activation.html @@ -0,0 +1,31 @@ + + + + + + فعال‌سازی موفقیت‌آمیز + + + +
+

ایمیل شما با موفقیت تایید شد

+
+ + diff --git a/BackEnd/templates/varify_email.html b/BackEnd/templates/varify_email.html new file mode 100644 index 0000000..c6f365d --- /dev/null +++ b/BackEnd/templates/varify_email.html @@ -0,0 +1,57 @@ +{% autoescape off %} + + + + + + کد تایید + + + +
+ + +

+ + + +
+ + +{% endautoescape %} + diff --git a/BackEnd/test.txt b/BackEnd/test.txt new file mode 100644 index 0000000..687976f --- /dev/null +++ b/BackEnd/test.txt @@ -0,0 +1,23 @@ +remaining_registration_tries = project_variables.MAX_VERIFICATION_TRIES - registration_tries + + if show_text: + if remaining_registration_tries < project_variables.MAX_VERIFICATION_TRIES: + remaining_text = f'تا کنون به تعداد {registration_tries}بار، ' \ + f'درخواست ثبت نام داشته اید. به تعداد {remaining_registration_tries} ' \ + f'دفعۀ دیگر میتوانید برای دریافت کد تایید، درخواست نمایید. ' + else: + remaining_text = 'به سقف مجاز برای ثبت درخواست کد تایید رسیده اید. ' \ + 'پس از 12 ساعت انتظار، می توانید برای باری دیگر، برای دریافت کد تایید، درخواست نمایید.' + else: + remaining_text = '' + + + +# remaining_verification_tries = project_variables.MAX_VERIFICATION_TRIES - verification_tries + # if remaining_verification_tries < project_variables.MAX_VERIFICATION_TRIES: + # remaining_text = f'تا کنون به تعداد {verification_tries}بار، ' \ + # f'درخواست تغییر رمز عبور داشته اید. به تعداد {remaining_verification_tries} ' \ + # f'دفعۀ دیگر می توانید برای دریافت کد تایید، درخواست نمایید. ' + # else: + # remaining_text = 'به سقف مجاز برای ثبت درخواست کد تایید رسیده اید. ' \ + # 'پس از 12 ساعت انتظار، می توانید برای باری دیگر، برای دریافت کد تایید، درخواست نمایید.' \ No newline at end of file diff --git a/BackEnd/utils/email.py b/BackEnd/utils/email.py new file mode 100644 index 0000000..4328df4 --- /dev/null +++ b/BackEnd/utils/email.py @@ -0,0 +1,36 @@ +from django.conf import settings +from django.core.mail import EmailMultiAlternatives +from django.template.loader import render_to_string +from BackEnd.settings import EMAIL_HOST +from . import project_variables +import json + + +def send_verification_message(subject, recipient_list, verification_token, registration_tries, show_text , token): + + context = { + 'email_verification_token': verification_token, + # 'remaining_text': remaining_text, + 'varification_link' : token , + } + + html_message = render_to_string( 'active_email.html', context= context ) #'activation_template.html' + email = EmailMultiAlternatives(subject, '', EMAIL_HOST, recipient_list) + email.attach_alternative(html_message, "text/html" ) #"text/html") + email.send() + + +def send_forget_password_verification_message(subject, recipient_list, verification_token, verification_tries=None): + context = { + 'email_verification_token': verification_token, + # 'remaining_text': remaining_text, + } + + html_message = render_to_string('forget_password.html', context) + email = EmailMultiAlternatives(subject, '', EMAIL_HOST, recipient_list) + email.attach_alternative(html_message, "text/html") + email.send() + +def send_telegram_account_verification_message(subject, recipient_list, verification_token): + send_forget_password_verification_message( subject= subject , recipient_list= recipient_list , verification_token=verification_token ) + diff --git a/BackEnd/utils/project_variables.py b/BackEnd/utils/project_variables.py new file mode 100644 index 0000000..cca5de3 --- /dev/null +++ b/BackEnd/utils/project_variables.py @@ -0,0 +1 @@ +MAX_VERIFICATION_TRIES = 5 \ No newline at end of file diff --git a/BackEnd/utils/therapy_tests.py b/BackEnd/utils/therapy_tests.py new file mode 100644 index 0000000..92ebabc --- /dev/null +++ b/BackEnd/utils/therapy_tests.py @@ -0,0 +1,75 @@ + +def GlasserResults(data ) : + # data = { + # 1 : { + # "category" : 2 , + # "res" : 4 + # }, + # 2 : { + # "category" : 2 , + # "res" : 4 + # } + # } + + categories_score = {} + for value in data.values() : + if value["category"] not in categories_score.keys() : + categories_score[ value["category"]] = value["res"] + else : + categories_score[ value["category"]] += value["res"] + return categories_score + + +def GetMBTIresults(data, gender ) : + colomn1 = [ data[7*i + 1] for i in range(10)] + colomn2 = [ data[7*i + 2] for i in range(10)] + colomn3 = [ data[7*i + 3] for i in range(10)] + colomn4 = [ data[7*i + 4] for i in range(10)] + colomn5 = [ data[7*i + 5] for i in range(10)] + colomn6 = [ data[7*i + 6] for i in range(10)] + colomn7 = [ data[7*i + 7] for i in range(10)] + # E , I + E = colomn1.count('a')*10 + I = colomn1.count('b')*10 + # S , N + S = (colomn2.count('a') + colomn3.count('a'))*5 + N = (colomn2.count('b') + colomn3.count('b'))*5 + # T , F + T = (colomn4.count('a') + colomn5.count('a'))*5 + F = (colomn4.count('b') + colomn5.count('b'))*5 + # J , P + J = (colomn6.count('a') + colomn7.count('a'))*5 + P = (colomn6.count('b') + colomn7.count('b'))*5 + + e_i = 'I' + print('E ' ,E , 'I ' , I ) + if E > I : + e_i = 'E' + + s_n = 'N' + if S > N : + s_n = 'S' + if T > F : + t_f = 'T' + elif T == F and gender == 'M' : + t_f = 'T' + else : + t_f = 'F' + + j_p = 'P' + if J > P : + j_p = 'J' + + result = { + 'E' : E , + 'I' : I , + 'S' : S , + 'N' : N , + 'T' : T , + 'F' : F , + 'J' : J , + 'P' : P , + 'final' : e_i+s_n + t_f+ j_p + } + + return result \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..36d85b8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,38 @@ +asgiref==3.7.2 +certifi==2024.2.2 +charset-normalizer==3.3.2 +coreapi==2.3.3 +coreschema==0.0.4 +Django==5.0.3 +django-cors-headers==4.3.1 +django-environ==0.11.2 +django-phonenumber-field==7.3.0 +django-rest-swagger==2.2.0 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.3.1 +drf-yasg==1.21.7 +environs==11.0.0 +idna==3.6 +inflection==0.5.1 +itypes==1.2.0 +Jinja2==3.1.3 +MarkupSafe==2.1.5 +marshmallow==3.21.1 +openapi-codec==1.3.2 +packaging==24.0 +phonenumbers==8.13.32 +pillow==10.2.0 +psycopg2==2.9.9 +psycopg2-binary==2.9.9 +PyJWT==2.8.0 +python-dotenv==1.0.1 +pytz==2024.1 +PyYAML==6.0.1 +requests==2.31.0 +setuptools==69.2.0 +simplejson==3.19.2 +sqlparse==0.4.4 +typing_extensions==4.10.0 +tzdata==2024.1 +uritemplate==4.1.1 +urllib3==2.2.1