first push
This commit is contained in:
25
.env.local
Normal file
25
.env.local
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
SECRET_KEY=django-insecure-&-inwstj0rt25wjb=#t8jq&96vb=h4y@udy&$^gj)ih-da3r4j
|
||||||
|
DEBUG=True
|
||||||
|
ALLOWED_HOSTS=testbackend.rasadyaar.ir,test.rasadyaar.ir,127.0.0.1,rasadyaar.ir,rasadyar.net,userbackend.rasadyar.com,rasadyar.com
|
||||||
|
DB_NAME=users
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=ShVaaU0yhBNET7lpUFT2aMDGdhOQLSzYxsOLnJRePGL1rQLWVv2iyIwRex3o7uoz
|
||||||
|
DB_HOST=31.7.78.133
|
||||||
|
DB_PORT=14362
|
||||||
|
CELERY_BROKER_URL=redis://redis://localhost:6379
|
||||||
|
CELERY_RESULT_BACKEND=redis://redis://localhost:6379
|
||||||
|
CELERY_ACCEPT_CONTENT=application/json
|
||||||
|
CELERY_TASK_SERIALIZER=json
|
||||||
|
CELERY_RESULT_SERIALIZER=json
|
||||||
|
CELERY_TIMEZONE=Asia/Tehran
|
||||||
|
CORS_ORIGIN_ALLOW_ALL=True
|
||||||
|
CORS_ORIGIN_WHITELIST=http://localhost:8080,http://127.0.0.1:8080,http://127.0.0.1:3000,http://localhost:3000,https://userbackend.rasadyaar.ir,https://rasadyaar.ir,https://rasadyar.net,https://userbackend.rasadyar.com,https://rasadyar.com
|
||||||
|
CORS_ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080,http://127.0.0.1:3000,http://localhost:3000,https://userbackend.rasadyaar.ir,https://rasadyaar.ir,https://rasadyar.net,https://userbackend.rasadyar.com,https://rasadyar.com
|
||||||
|
SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,https
|
||||||
|
SECURE_SSL_REDIRECT=False
|
||||||
|
SESSION_COOKIE_SECURE=True
|
||||||
|
CSRF_COOKIE_SECURE=True
|
||||||
|
REDIS_URL=redis://:ydnW4hwzuDRYcTX3FWCHgQ1f@apo.liara.cloud:33740/0
|
||||||
|
|
||||||
|
|
||||||
|
ENV RUNNING_IN_DOCKER=0
|
||||||
24
.env.prod
Normal file
24
.env.prod
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
SECRET_KEY=django-insecure-&-inwstj0rt25wjb=#t8jq&96vb=h4y@udy&$^gj)ih-da3r4j
|
||||||
|
DEBUG=True
|
||||||
|
ALLOWED_HOSTS=testbackend.rasadyaar.ir,test.rasadyaar.ir,127.0.0.1,rasadyaar.ir,rasadyar.net,userbackend.rasadyar.com,rasadyar.com
|
||||||
|
DB_NAME=users
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=ShVaaU0yhBNET7lpUFT2aMDGdhOQLSzYxsOLnJRePGL1rQLWVv2iyIwRex3o7uoz
|
||||||
|
DB_HOST=31.7.78.133
|
||||||
|
DB_PORT=14362
|
||||||
|
CELERY_BROKER_URL=redis://redis://localhost:6379
|
||||||
|
CELERY_RESULT_BACKEND=redis://redis://localhost:6379
|
||||||
|
CELERY_ACCEPT_CONTENT=application/json
|
||||||
|
CELERY_TASK_SERIALIZER=json
|
||||||
|
CELERY_RESULT_SERIALIZER=json
|
||||||
|
CELERY_TIMEZONE=Asia/Tehran
|
||||||
|
CORS_ORIGIN_ALLOW_ALL=True
|
||||||
|
CORS_ORIGIN_WHITELIST=http://localhost:8080,http://127.0.0.1:8080,http://127.0.0.1:3000,http://localhost:3000,https://userbackend.rasadyaar.ir,https://rasadyaar.ir,https://rasadyar.net,https://userbackend.rasadyar.com,https://rasadyar.com
|
||||||
|
CORS_ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080,http://127.0.0.1:3000,http://localhost:3000,https://userbackend.rasadyaar.ir,https://rasadyaar.ir,https://rasadyar.net,https://userbackend.rasadyar.com,https://rasadyar.com
|
||||||
|
SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,https,http
|
||||||
|
SECURE_SSL_REDIRECT=False
|
||||||
|
SESSION_COOKIE_SECURE=True
|
||||||
|
CSRF_COOKIE_SECURE=True
|
||||||
|
REDIS_URL=redis://:ydnW4hwzuDRYcTX3FWCHgQ1f@apo.liara.cloud:33740/0
|
||||||
|
|
||||||
|
ENV RUNNING_IN_DOCKER=0
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
28
.idea/ArtaSystem.iml
generated
Normal file
28
.idea/ArtaSystem.iml
generated
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="django" name="Django">
|
||||||
|
<configuration>
|
||||||
|
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||||
|
<option name="settingsModule" value="ArtaSystem/settings.py" />
|
||||||
|
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
|
||||||
|
<option name="environment" value="<map/>" />
|
||||||
|
<option name="doNotUseTestRunner" value="false" />
|
||||||
|
<option name="trackFilePattern" value="migrations" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.9 (chicken) (6)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||||
|
<option name="TEMPLATE_FOLDERS">
|
||||||
|
<list>
|
||||||
|
<option value="$MODULE_DIR$/../ArtaSystem\templates" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
12
.idea/dataSources.xml
generated
Normal file
12
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="users@31.7.78.133" uuid="2f4c51a3-e8e6-4ba4-b92a-91ba45e3c44f">
|
||||||
|
<driver-ref>postgresql</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:postgresql://31.7.78.133:14362/users</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
79
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
79
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<value>
|
||||||
|
<list size="66">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="pkg-resources" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="qrcode" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="django-subscriptions" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="asgiref" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="requests" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="importlib-metadata" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="sqlparse" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="django-filter" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="Django" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="zipp" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="certifi" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="charset-normalizer" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="pytz" />
|
||||||
|
<item index="13" class="java.lang.String" itemvalue="urllib3" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="djangorestframework" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="Markdown" />
|
||||||
|
<item index="16" class="java.lang.String" itemvalue="idna" />
|
||||||
|
<item index="17" class="java.lang.String" itemvalue="Pillow" />
|
||||||
|
<item index="18" class="java.lang.String" itemvalue="django-oauth-toolkit" />
|
||||||
|
<item index="19" class="java.lang.String" itemvalue="setuptools" />
|
||||||
|
<item index="20" class="java.lang.String" itemvalue="appbase" />
|
||||||
|
<item index="21" class="java.lang.String" itemvalue="fancywidgets" />
|
||||||
|
<item index="22" class="java.lang.String" itemvalue="arrow" />
|
||||||
|
<item index="23" class="java.lang.String" itemvalue="python-dateutil" />
|
||||||
|
<item index="24" class="java.lang.String" itemvalue="cffi" />
|
||||||
|
<item index="25" class="java.lang.String" itemvalue="numpy" />
|
||||||
|
<item index="26" class="java.lang.String" itemvalue="appdirs" />
|
||||||
|
<item index="27" class="java.lang.String" itemvalue="Deprecated" />
|
||||||
|
<item index="28" class="java.lang.String" itemvalue="Pygments" />
|
||||||
|
<item index="29" class="java.lang.String" itemvalue="pkginfo" />
|
||||||
|
<item index="30" class="java.lang.String" itemvalue="botocore" />
|
||||||
|
<item index="31" class="java.lang.String" itemvalue="wrapt" />
|
||||||
|
<item index="32" class="java.lang.String" itemvalue="djangorestframework-recursive" />
|
||||||
|
<item index="33" class="java.lang.String" itemvalue="Khayyam" />
|
||||||
|
<item index="34" class="java.lang.String" itemvalue="APScheduler" />
|
||||||
|
<item index="35" class="java.lang.String" itemvalue="pandas" />
|
||||||
|
<item index="36" class="java.lang.String" itemvalue="tqdm" />
|
||||||
|
<item index="37" class="java.lang.String" itemvalue="boto3" />
|
||||||
|
<item index="38" class="java.lang.String" itemvalue="s3transfer" />
|
||||||
|
<item index="39" class="java.lang.String" itemvalue="PySocks" />
|
||||||
|
<item index="40" class="java.lang.String" itemvalue="httpie" />
|
||||||
|
<item index="41" class="java.lang.String" itemvalue="virtualenv" />
|
||||||
|
<item index="42" class="java.lang.String" itemvalue="multidict" />
|
||||||
|
<item index="43" class="java.lang.String" itemvalue="fancytools" />
|
||||||
|
<item index="44" class="java.lang.String" itemvalue="openpyxl" />
|
||||||
|
<item index="45" class="java.lang.String" itemvalue="requests-toolbelt" />
|
||||||
|
<item index="46" class="java.lang.String" itemvalue="django-oauth2-provider" />
|
||||||
|
<item index="47" class="java.lang.String" itemvalue="rsa" />
|
||||||
|
<item index="48" class="java.lang.String" itemvalue="setuptools-rust" />
|
||||||
|
<item index="49" class="java.lang.String" itemvalue="async-timeout" />
|
||||||
|
<item index="50" class="java.lang.String" itemvalue="PyYAML" />
|
||||||
|
<item index="51" class="java.lang.String" itemvalue="psycopg2-binary" />
|
||||||
|
<item index="52" class="java.lang.String" itemvalue="cryptography" />
|
||||||
|
<item index="53" class="java.lang.String" itemvalue="packaging" />
|
||||||
|
<item index="54" class="java.lang.String" itemvalue="pycparser" />
|
||||||
|
<item index="55" class="java.lang.String" itemvalue="django-extensions" />
|
||||||
|
<item index="56" class="java.lang.String" itemvalue="redis" />
|
||||||
|
<item index="57" class="java.lang.String" itemvalue="crypto" />
|
||||||
|
<item index="58" class="java.lang.String" itemvalue="semantic-version" />
|
||||||
|
<item index="59" class="java.lang.String" itemvalue="oauthlib" />
|
||||||
|
<item index="60" class="java.lang.String" itemvalue="typing_extensions" />
|
||||||
|
<item index="61" class="java.lang.String" itemvalue="django-jalali" />
|
||||||
|
<item index="62" class="java.lang.String" itemvalue="jdatetime" />
|
||||||
|
<item index="63" class="java.lang.String" itemvalue="shortuuid" />
|
||||||
|
<item index="64" class="java.lang.String" itemvalue="pyparsing" />
|
||||||
|
<item index="65" class="java.lang.String" itemvalue="django-cors-headers" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (chicken) (6)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/ArtaSystem.iml" filepath="$PROJECT_DIR$/.idea/ArtaSystem.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/sqldialects.xml
generated
Normal file
6
.idea/sqldialects.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="PROJECT" dialect="PostgreSQL" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
0
ArtaSystem/__init__.py
Normal file
0
ArtaSystem/__init__.py
Normal file
BIN
ArtaSystem/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
ArtaSystem/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
ArtaSystem/__pycache__/settings.cpython-39.pyc
Normal file
BIN
ArtaSystem/__pycache__/settings.cpython-39.pyc
Normal file
Binary file not shown.
BIN
ArtaSystem/__pycache__/urls.cpython-39.pyc
Normal file
BIN
ArtaSystem/__pycache__/urls.cpython-39.pyc
Normal file
Binary file not shown.
BIN
ArtaSystem/__pycache__/wsgi.cpython-39.pyc
Normal file
BIN
ArtaSystem/__pycache__/wsgi.cpython-39.pyc
Normal file
Binary file not shown.
16
ArtaSystem/asgi.py
Normal file
16
ArtaSystem/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for ArtaSystem 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/3.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ArtaSystem.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
264
ArtaSystem/settings.py
Normal file
264
ArtaSystem/settings.py
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
"""
|
||||||
|
Django settings for ArtaSystem project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.2.14.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
|
"""
|
||||||
|
import socket
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
loc_ip = socket.gethostbyname(socket.gethostname())
|
||||||
|
|
||||||
|
|
||||||
|
if not os.getenv("RUNNING_IN_DOCKER"):
|
||||||
|
dotenv_path = BASE_DIR / ".env.local"
|
||||||
|
load_dotenv(dotenv_path)
|
||||||
|
|
||||||
|
SECRET_KEY = os.environ.get("SECRET_KEY")
|
||||||
|
|
||||||
|
DEBUG = os.environ.get("DEBUG")
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS").split(',')
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'oauth2_provider',
|
||||||
|
'rest_framework',
|
||||||
|
'django_filters',
|
||||||
|
'corsheaders',
|
||||||
|
'Authentication.apps.AuthenticationConfig',
|
||||||
|
'Notification.apps.NotificationConfig',
|
||||||
|
'Core.apps.CoreConfig',
|
||||||
|
'Wallet.apps.WalletConfig',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'oauth2_provider.middleware.OAuth2TokenMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'ArtaSystem.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 = 'ArtaSystem.wsgi.application'
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
|
'NAME': os.environ.get("DB_NAME"),
|
||||||
|
'USER': os.environ.get("DB_USER"),
|
||||||
|
'PASSWORD': os.environ.get("DB_PASSWORD"),
|
||||||
|
'HOST': os.environ.get("DB_HOST"),
|
||||||
|
'PORT': os.environ.get("DB_PORT"),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
# DATABASES['default'] = DATABASES['Auth_db']
|
||||||
|
# DATABASE_ROUTERS = ['ArtaSystem.dbrouter.MyDatabaseRouter']
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
|
# 'rest_framework.authentication.SessionAuthentication',
|
||||||
|
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
),
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
|
),
|
||||||
|
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
|
||||||
|
}
|
||||||
|
|
||||||
|
OAUTH2_PROVIDER = {
|
||||||
|
# other OAUTH2 settings
|
||||||
|
'REFRESH_TOKEN_EXPIRE_SECONDS': 360000,
|
||||||
|
'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.JSONOAuthLibCore',
|
||||||
|
'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'groups': 'Access to your groups'},
|
||||||
|
'ACCESS_TOKEN_EXPIRE_SECONDS': 360000
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
'oauth2_provider.backends.OAuth2Backend',
|
||||||
|
# Uncomment following if you want to access the admin
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
]
|
||||||
|
|
||||||
|
# CACHES = {
|
||||||
|
# "default": {
|
||||||
|
# "BACKEND": "django_redis.cache.RedisCache",
|
||||||
|
# "LOCATION": "redis://:ydnW4hwzuDRYcTX3FWCHgQ1f@apo.liara.cloud:33740/0",
|
||||||
|
# "OPTIONS": {
|
||||||
|
# "CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||||
|
#
|
||||||
|
# },
|
||||||
|
# "KEY_PREFIX": "You have successfully set up a key-value pair!"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CACHES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django_redis.cache.RedisCache",
|
||||||
|
"LOCATION":"redis://default:wHM2fSW8EXtsoTjHxLZyyaRsD8IJm4tOU108252rizfmUYrp709PuCLUhr9mmYDK@31.7.78.133:14353/0",
|
||||||
|
"OPTIONS": {
|
||||||
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||||
|
},
|
||||||
|
"KEY_PREFIX": "You have successfully set up a key-value pair!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
# TIME_ZONE = 'UTC'
|
||||||
|
TIME_ZONE = 'Asia/Tehran'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = False
|
||||||
|
|
||||||
|
USE_TZ = False
|
||||||
|
|
||||||
|
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||||
|
|
||||||
|
# Cache time to live is 2 minutes.
|
||||||
|
CACHE_TTL = 60 * 2
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
STATIC_ROOT = BASE_DIR / 'static'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
DATA_UPLOAD_MAX_MEMORY_SIZE = 50242880
|
||||||
|
|
||||||
|
# CELERY STUFF
|
||||||
|
BROKER_URL = 'redis://localhost:6379'
|
||||||
|
CELERY_RESULT_BACKEND = 'redis://localhost:6379'
|
||||||
|
CELERY_ACCEPT_CONTENT = ['application/json']
|
||||||
|
CELERY_TASK_SERIALIZER = 'json'
|
||||||
|
CELERY_RESULT_SERIALIZER = 'json'
|
||||||
|
CELERY_TIMEZONE = 'Asia/Tehran'
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
MIDDLEWARE += [
|
||||||
|
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||||
|
]
|
||||||
|
INSTALLED_APPS += [
|
||||||
|
'debug_toolbar',
|
||||||
|
# 'oauth2_provider',
|
||||||
|
# 'rest_framework',
|
||||||
|
# 'corsheaders'
|
||||||
|
]
|
||||||
|
INTERNAL_IPS = [
|
||||||
|
# ...
|
||||||
|
"51.89.107.194",
|
||||||
|
# "127.0.0.1",
|
||||||
|
|
||||||
|
# ...
|
||||||
|
]
|
||||||
|
|
||||||
|
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
|
||||||
|
INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips]
|
||||||
|
|
||||||
|
DEBUG_TOOLBAR_PANELS = [
|
||||||
|
'debug_toolbar.panels.history.HistoryPanel',
|
||||||
|
'debug_toolbar.panels.versions.VersionsPanel',
|
||||||
|
'debug_toolbar.panels.timer.TimerPanel',
|
||||||
|
'debug_toolbar.panels.settings.SettingsPanel',
|
||||||
|
'debug_toolbar.panels.headers.HeadersPanel',
|
||||||
|
'debug_toolbar.panels.request.RequestPanel',
|
||||||
|
'debug_toolbar.panels.sql.SQLPanel',
|
||||||
|
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
|
||||||
|
'debug_toolbar.panels.templates.TemplatesPanel',
|
||||||
|
'debug_toolbar.panels.cache.CachePanel',
|
||||||
|
'debug_toolbar.panels.signals.SignalsPanel',
|
||||||
|
'debug_toolbar.panels.logging.LoggingPanel',
|
||||||
|
'debug_toolbar.panels.redirects.RedirectsPanel',
|
||||||
|
'debug_toolbar.panels.profiling.ProfilingPanel',
|
||||||
|
]
|
||||||
|
|
||||||
|
# this is the main reason for not showing up the toolbar
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
mimetypes.add_type("application/javascript", ".js", True)
|
||||||
|
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
|
'INTERCEPT_REDIRECTS': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CORS_ORIGIN_ALLOW_ALL = os.environ.get("CORS_ORIGIN_ALLOW_ALL", "False").lower() == "true"
|
||||||
|
CORS_ORIGIN_WHITELIST = os.environ.get("CORS_ORIGIN_WHITELIST").split(',')
|
||||||
|
|
||||||
|
CORS_ALLOWED_ORIGINS = os.environ.get("CORS_ORIGIN_WHITELIST").split(',')
|
||||||
|
|
||||||
|
SECURE_PROXY_SSL_HEADER = os.environ.get("SECURE_PROXY_SSL_HEADER").split(',')
|
||||||
|
SECURE_SSL_REDIRECT = os.environ.get("SECURE_SSL_REDIRECT")
|
||||||
|
SESSION_COOKIE_SECURE = os.environ.get("SESSION_COOKIE_SECURE")
|
||||||
|
CSRF_COOKIE_SECURE = os.environ.get("CSRF_COOKIE_SECURE")
|
||||||
23
ArtaSystem/urls.py
Normal file
23
ArtaSystem/urls.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"""ArtaSystem URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('', include('Authentication.urls')),
|
||||||
|
path('', include('Notification.urls')),
|
||||||
|
]
|
||||||
16
ArtaSystem/wsgi.py
Normal file
16
ArtaSystem/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for ArtaSystem 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/3.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ArtaSystem.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
0
Authentication/__init__.py
Normal file
0
Authentication/__init__.py
Normal file
BIN
Authentication/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Authentication/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Authentication/__pycache__/admin.cpython-39.pyc
Normal file
BIN
Authentication/__pycache__/admin.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Authentication/__pycache__/apps.cpython-39.pyc
Normal file
BIN
Authentication/__pycache__/apps.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Authentication/__pycache__/models.cpython-39.pyc
Normal file
BIN
Authentication/__pycache__/models.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Authentication/__pycache__/serializers.cpython-39.pyc
Normal file
BIN
Authentication/__pycache__/serializers.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Authentication/__pycache__/sms.cpython-39.pyc
Normal file
BIN
Authentication/__pycache__/sms.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Authentication/__pycache__/urls.cpython-39.pyc
Normal file
BIN
Authentication/__pycache__/urls.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Authentication/__pycache__/views.cpython-39.pyc
Normal file
BIN
Authentication/__pycache__/views.cpython-39.pyc
Normal file
Binary file not shown.
3
Authentication/admin.py
Normal file
3
Authentication/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
Authentication/apps.py
Normal file
6
Authentication/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'Authentication'
|
||||||
65
Authentication/migrations/0001_initial.py
Normal file
65
Authentication/migrations/0001_initial.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2023-09-17 15:05
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ClientToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('client_name', models.CharField(max_length=50)),
|
||||||
|
('client_id', models.CharField(max_length=50)),
|
||||||
|
('client_secret', models.CharField(max_length=150)),
|
||||||
|
('client_token', models.CharField(max_length=50)),
|
||||||
|
('client_web_address', models.CharField(max_length=200, null=True)),
|
||||||
|
('client_web_address_backend', models.CharField(max_length=200, null=True)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='clienttoken_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='clienttoken_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserIdentity',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('first_name', models.CharField(max_length=100, null=True)),
|
||||||
|
('last_name', models.CharField(max_length=100, null=True)),
|
||||||
|
('mobile', models.CharField(max_length=20, null=True)),
|
||||||
|
('national_id', models.CharField(max_length=20, null=True)),
|
||||||
|
('national_code', models.CharField(max_length=20, null=True)),
|
||||||
|
('city', models.CharField(max_length=100, null=True)),
|
||||||
|
('province', models.CharField(max_length=100, null=True)),
|
||||||
|
('client', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='client_identity', to='Authentication.clienttoken')),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='useridentity_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='useridentity_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('role', models.ManyToManyField(related_name='identity_group', to='auth.Group')),
|
||||||
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_identity', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Authentication/migrations/__init__.py
Normal file
0
Authentication/migrations/__init__.py
Normal file
Binary file not shown.
BIN
Authentication/migrations/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Authentication/migrations/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
41
Authentication/models.py
Normal file
41
Authentication/models.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from django.db import models
|
||||||
|
from Core.models import BaseModel
|
||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
class ClientToken(BaseModel):
|
||||||
|
client_name = models.CharField(max_length=50)
|
||||||
|
client_id = models.CharField(max_length=50)
|
||||||
|
client_secret = models.CharField(max_length=150)
|
||||||
|
client_token = models.CharField(max_length=50)
|
||||||
|
client_web_address = models.CharField(max_length=200, null=True)
|
||||||
|
client_web_address_backend = models.CharField(max_length=200, null=True)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(ClientToken, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class UserIdentity(BaseModel):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='user_identity')
|
||||||
|
role = models.ManyToManyField(
|
||||||
|
Group,
|
||||||
|
related_name='identity_group'
|
||||||
|
)
|
||||||
|
client = models.ForeignKey(
|
||||||
|
ClientToken,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
related_name="client_identity"
|
||||||
|
)
|
||||||
|
first_name = models.CharField(max_length=100, null=True)
|
||||||
|
last_name = models.CharField(max_length=100, null=True)
|
||||||
|
mobile = models.CharField(max_length=20, null=True)
|
||||||
|
national_id = models.CharField(max_length=20, null=True)
|
||||||
|
national_code = models.CharField(max_length=20, null=True)
|
||||||
|
city = models.CharField(max_length=100, null=True)
|
||||||
|
province = models.CharField(max_length=100, null=True)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(UserIdentity, self).save(*args, **kwargs)
|
||||||
31
Authentication/serializers.py
Normal file
31
Authentication/serializers.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
from Authentication.models import ClientToken, UserIdentity
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Group
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class ClientTokenSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ClientToken
|
||||||
|
fields = (
|
||||||
|
'client_name',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserIdentitySerializer(serializers.ModelSerializer):
|
||||||
|
client = ClientTokenSerializer(required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UserIdentity
|
||||||
|
exclude = (
|
||||||
|
'id',
|
||||||
|
'created_by',
|
||||||
|
'modified_by',
|
||||||
|
'trash'
|
||||||
|
)
|
||||||
|
extra_kwargs = {'role': {'required': False}, }
|
||||||
17
Authentication/sms.py
Normal file
17
Authentication/sms.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def send_otp_code(receptor, rand):
|
||||||
|
receptor = str(receptor)
|
||||||
|
message = 'سلام همراه عزیز کد پیامکی ارسالی برای شما :{}'.format(rand)
|
||||||
|
u = "http://webservice.sahandsms.com/newsmswebservice.asmx/SendPostUrl?username=pmstores&password=Aht00100&from=30002501&to={}&message={}".format(
|
||||||
|
receptor, message)
|
||||||
|
|
||||||
|
url = u.format()
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||||
|
|
||||||
|
response = requests.request("GET", url, headers=headers, data=payload, verify=False)
|
||||||
|
|
||||||
|
print(response.text)
|
||||||
3
Authentication/tests.py
Normal file
3
Authentication/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
60
Authentication/urls.py
Normal file
60
Authentication/urls.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from django.urls import path, include
|
||||||
|
from rest_framework import routers
|
||||||
|
from django.conf import settings
|
||||||
|
import oauth2_provider.views as oauth2_views
|
||||||
|
|
||||||
|
from Authentication.views import (
|
||||||
|
register,
|
||||||
|
login,
|
||||||
|
change_password,
|
||||||
|
check_otp,
|
||||||
|
send_otp,
|
||||||
|
UserIdentityViewSet, store_send_otp, Find_User, Identity, register_all, change_user_mobile, NumberOfActiveUsers,
|
||||||
|
remove_access_token,check_user_exists, remove_user_role
|
||||||
|
)
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register('user_identity', UserIdentityViewSet, basename='user_identity')
|
||||||
|
|
||||||
|
oauth2_endpoint_views = [
|
||||||
|
path('authorize/', oauth2_views.AuthorizationView.as_view(), name="authorize"),
|
||||||
|
path('token/', oauth2_views.TokenView.as_view(), name="token"),
|
||||||
|
path('register/', register, name="register"),
|
||||||
|
path('register_all/', register_all, name="register_all"),
|
||||||
|
path('login/', login, name="login"),
|
||||||
|
path('change_password/', change_password, name="change_password"),
|
||||||
|
path('send_otp/', send_otp, name="send_otp"),
|
||||||
|
path('send/', store_send_otp, name="send"),
|
||||||
|
path('check_otp/', check_otp, name="check_otp"),
|
||||||
|
path('find/', Find_User, name="find"),
|
||||||
|
path('identity/', Identity, name="identity"),
|
||||||
|
path('active-users/', NumberOfActiveUsers, name="active-users"),
|
||||||
|
path('remove_access_token/', remove_access_token, name="remove_access_token"),
|
||||||
|
path('check_user_exists/', check_user_exists, name="check_user_exists"),
|
||||||
|
path('remove_user_role/', remove_user_role, name="remove_user_role"),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
# OAuth2 Application Management endpoints
|
||||||
|
oauth2_endpoint_views += [
|
||||||
|
path('applications/', oauth2_views.ApplicationList.as_view(), name="list"),
|
||||||
|
path('applications/register/', oauth2_views.ApplicationRegistration.as_view(), name="register"),
|
||||||
|
path('applications/<pk>/', oauth2_views.ApplicationDetail.as_view(), name="detail"),
|
||||||
|
path('applications/<pk>/delete/', oauth2_views.ApplicationDelete.as_view(), name="delete"),
|
||||||
|
path('applications/<pk>/update/', oauth2_views.ApplicationUpdate.as_view(), name="update"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# OAuth2 Token Management endpoints
|
||||||
|
oauth2_endpoint_views += [
|
||||||
|
path('authorized-tokens/', oauth2_views.AuthorizedTokensListView.as_view(), name="authorized-token-list"),
|
||||||
|
path('authorized-tokens/<pk>/delete/', oauth2_views.AuthorizedTokenDeleteView.as_view(),
|
||||||
|
name="authorized-token-delete"),
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
path('api/', include((oauth2_endpoint_views, 'oauth2_provider.urls'), namespace="oauth2_provider")),
|
||||||
|
path('change_mobile_number/', change_user_mobile),
|
||||||
|
|
||||||
|
]
|
||||||
635
Authentication/views.py
Normal file
635
Authentication/views.py
Normal file
@@ -0,0 +1,635 @@
|
|||||||
|
import cryptocode
|
||||||
|
from django.core.cache import cache
|
||||||
|
from rest_framework.decorators import permission_classes, api_view
|
||||||
|
from .models import UserIdentity
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render
|
||||||
|
from rest_framework import status, viewsets
|
||||||
|
from oauth2_provider.models import AccessToken
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import random
|
||||||
|
import uuid
|
||||||
|
import cryptocode
|
||||||
|
from oauth2_provider.contrib.rest_framework import (
|
||||||
|
TokenHasReadWriteScope,
|
||||||
|
OAuth2Authentication, )
|
||||||
|
from rest_framework.decorators import authentication_classes
|
||||||
|
from Authentication.models import ClientToken
|
||||||
|
from Authentication.sms import send_otp_code
|
||||||
|
from .serializers import UserIdentitySerializer
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
BASE_URL = "https://userbackend.rasadyar.com/api/"
|
||||||
|
|
||||||
|
ARTA_CLIENT_ID = 'cpxlBf9GPPnk0nfOMLEa6fZyUrew6Z17wujOUMJr'
|
||||||
|
ARTA_CLIENT_SECRET = 'ONFoHxBCPOtIUw72QnLL4oa0wOKQNQ6h3Hc8pZrk3qHcR759hmgFn7fJZJMh1nQRWMeRGUHbRoTBFCIQn7OsiKrY7y4JM975T7mjM7WXJs3Ezl30gMAUgfpuEpzJgChz'
|
||||||
|
|
||||||
|
CHICKEN_CLIENT_SECRET = '4EK8EAPBOGsUHTeTHpgXrjQwbOQKAnNnQIOHmZa3IlOYVafwV1rmoKHhJE91OmLJ201yp7UkGu5TikiesoZxhNj0FYOyTtC7YtcqvopdBO36e2PSnjuqkLt0yCmaK2ph'
|
||||||
|
CHICKEN_CLIENT_ID = 'DhL3VMce6p3CBPSTwBg1AJjcaREvddWoOP8G8pHc'
|
||||||
|
|
||||||
|
LO_CHICKEN_CLIENT_SECRET = "xqZM6iTDe0XDS1mC8iVhahXqb2TWIZ07mx7yYOZrzTYHyHoFYIpvBm6IcM169fsGZ8uQs3gBHmicgbUMVXwbHyJIaCOeFp9SNK72E4v2OR51om3eH43VMQSK4pEKmMX6"
|
||||||
|
LO_CHICKEN_CLIENT_ID = "kSHxeTGASY8JsczTinnt5t820clWOKC3X1NHnMOi"
|
||||||
|
|
||||||
|
HA_CHICKEN_CLIENT_SECRET = 'l2Gt9AgwOfIneoQU2hamnGYCOiIUdAY2nmLI9eCkNo7wXU6TvNEU93oHtk8IzSHzJc5vVkm9scJaAlWGbzumNenGsQbIESbA1mAsLXWoWSllZKCuGyCBTJtKQ7BhnHZ6'
|
||||||
|
HA_CHICKEN_CLIENT_ID = 'WwpP780hSemYh8K93MqeuZ3HAir3ahQxDTGG43nG'
|
||||||
|
|
||||||
|
DM_CLIENT_ID = '2fDx0CopuiLnRz7YyCQD8nBXKjpxzqZg38Fcl02l'
|
||||||
|
DM_CLIENT_SECRET = 'PKStjauydu4k157bSaoPVenKHvLVtLI9Upn4JxU7tnHhuHPfAUp1abkfWp55orh7dFCXdE09E5CeWu7vBJsv1VpXz13EBl7OSW2LAceo3ztvq4FNAEVmEEt56cEmQzpF'
|
||||||
|
|
||||||
|
INSPECTION_CLIENT_ID = 'R2Ox6eqrXPeh1KbeWLDO5MCapuOFpHDvstOOD1XC'
|
||||||
|
INSPECTION_CLIENT_SECRET = 'imFgEGkcs248XZkLE7JNMo6mwVkiUMGYUBenBAlgZFwW0lyCYILrmh5Akh8dpHbgpCYaSvuYepFu3WdUXY3ZXPDZq11KbqlrmjHwf8wuW2DUsa0oSDozDv4p9Lx3lJPO'
|
||||||
|
|
||||||
|
|
||||||
|
# # Create your views here.
|
||||||
|
# @api_view(["POST"])
|
||||||
|
# @permission_classes([AllowAny])
|
||||||
|
# def GernalSendOtp(request):
|
||||||
|
# mobile = request.data["mobile"]
|
||||||
|
# state = request.data["state"]
|
||||||
|
# try:
|
||||||
|
# user = User.objects.get(username__exact=mobile)
|
||||||
|
# user_identity = UserIdentity.objects.get(user)
|
||||||
|
# client = ClientToken.objects.get(key=user_identity.client.key)
|
||||||
|
# except User.DoesNotExist:
|
||||||
|
# return Response({'is_user': False}, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
# if len(mobile) < 11 or len(mobile) > 11:
|
||||||
|
# return Response(
|
||||||
|
# {
|
||||||
|
# "pattern": "wrong",
|
||||||
|
# },
|
||||||
|
# status=status.HTTP_403_FORBIDDEN,
|
||||||
|
# )
|
||||||
|
# key = str(uuid.uuid4())
|
||||||
|
# rand = random.randint(10000, 99000)
|
||||||
|
# cache.set(key, str(rand), timeout=120)
|
||||||
|
# if not User.objects.filter(username=mobile).exists():
|
||||||
|
# receptor = mobile
|
||||||
|
# send_otp_code(receptor, rand)
|
||||||
|
# return Response(
|
||||||
|
# {
|
||||||
|
# "is_user": False,
|
||||||
|
# "key": key,
|
||||||
|
# },
|
||||||
|
# status=status.HTTP_404_NOT_FOUND,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# if state == "forget_password":
|
||||||
|
# receptor = mobile
|
||||||
|
# send_otp_code(receptor, rand)
|
||||||
|
# return Response(
|
||||||
|
# {
|
||||||
|
# "is_user": True,
|
||||||
|
# "key": key,
|
||||||
|
# },
|
||||||
|
# status=status.HTTP_200_OK,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# elif state == "change_password":
|
||||||
|
# receptor = mobile
|
||||||
|
# send_otp_code(receptor, rand)
|
||||||
|
# return Response(
|
||||||
|
# {
|
||||||
|
# "is_user": True,
|
||||||
|
# "key": key,
|
||||||
|
# },
|
||||||
|
# status=status.HTTP_200_OK,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# elif state == "":
|
||||||
|
# return Response(
|
||||||
|
# {
|
||||||
|
# "is_user": True,
|
||||||
|
# },
|
||||||
|
# status=status.HTTP_200_OK,
|
||||||
|
# )
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def send_otp(request):
|
||||||
|
# frontend_url = request.headers.get("Origin")
|
||||||
|
# frontend_url = request.data.get("frontend_url", frontend_url)
|
||||||
|
# if "https://rasadyaar.ir" in frontend_url:
|
||||||
|
# return Response({'result': 'https://rasadyar.net'}, status.HTTP_401_UNAUTHORIZED)
|
||||||
|
mobile = request.data["mobile"]
|
||||||
|
state = request.data["state"]
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username__exact=mobile)
|
||||||
|
user_identity = UserIdentity.objects.get(user=user)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return Response({'is_user': False}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
if len(mobile) < 11 or len(mobile) > 11:
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"pattern": "wrong",
|
||||||
|
},
|
||||||
|
status=status.HTTP_403_FORBIDDEN,
|
||||||
|
)
|
||||||
|
key = str(uuid.uuid4())
|
||||||
|
rand = random.randint(10000, 99000)
|
||||||
|
cache.set(key, str(rand), timeout=120)
|
||||||
|
if not User.objects.filter(username=mobile).exists():
|
||||||
|
receptor = mobile
|
||||||
|
# send_otp_code(receptor, rand)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"is_user": False,
|
||||||
|
"key": key,
|
||||||
|
},
|
||||||
|
status=status.HTTP_404_NOT_FOUND,
|
||||||
|
)
|
||||||
|
|
||||||
|
if state == "forget_password":
|
||||||
|
receptor = mobile
|
||||||
|
send_otp_code(receptor, rand)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"is_user": True,
|
||||||
|
"key": key,
|
||||||
|
"address": user_identity.client.client_web_address,
|
||||||
|
"backend": user_identity.client.client_web_address_backend,
|
||||||
|
"api_key": user_identity.client.client_token,
|
||||||
|
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif state == "change_password":
|
||||||
|
receptor = mobile
|
||||||
|
send_otp_code(receptor, rand)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"is_user": True,
|
||||||
|
"key": key,
|
||||||
|
"address": user_identity.client.client_web_address,
|
||||||
|
"backend": user_identity.client.client_web_address_backend,
|
||||||
|
"api_key": user_identity.client.client_token,
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif state == "":
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"is_user": True,
|
||||||
|
"address": user_identity.client.client_web_address,
|
||||||
|
"backend": user_identity.client.client_web_address_backend,
|
||||||
|
"api_key": user_identity.client.client_token,
|
||||||
|
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def store_send_otp(request):
|
||||||
|
mobile = request.data["mobile"]
|
||||||
|
key = str(uuid.uuid4())
|
||||||
|
rand = random.randint(10000, 99000)
|
||||||
|
cache.set(key, str(rand), timeout=120)
|
||||||
|
receptor = mobile
|
||||||
|
send_otp_code(receptor, rand)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"key": key,
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def change_user_mobile(request):
|
||||||
|
first_mobile = request.data["first_mobile_number"]
|
||||||
|
second_mobile = request.data["second_mobile_number"]
|
||||||
|
user = User.objects.get(username=first_mobile)
|
||||||
|
user.username = second_mobile
|
||||||
|
user.save()
|
||||||
|
# user_identity=UserIdentity.objects.get(mobile=first_mobile)
|
||||||
|
# user_identity.mobile=second_mobile
|
||||||
|
# user_identity.save()
|
||||||
|
|
||||||
|
return Response({"result": "number changed"}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def check_otp(request):
|
||||||
|
key = request.data["key"]
|
||||||
|
code = cache.get(key)
|
||||||
|
if request.data["code"] == code:
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"code": True,
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"code": False,
|
||||||
|
},
|
||||||
|
status=status.HTTP_403_FORBIDDEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
# @permission_classes([TokenHasReadWriteScope])
|
||||||
|
@authentication_classes([OAuth2Authentication])
|
||||||
|
def change_password(request):
|
||||||
|
username = request.data["username"]
|
||||||
|
password = request.data["password"]
|
||||||
|
user = User.objects.get(username=username)
|
||||||
|
user.password = cryptocode.encrypt(password, password)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
return Response({"password": "changed"}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def register(request):
|
||||||
|
# if 'role' in request.data.keys() and 'tenant' in request.data.keys():
|
||||||
|
# request.data.pop('role')
|
||||||
|
# request.data.pop('tenant')
|
||||||
|
|
||||||
|
username = request.data["username"]
|
||||||
|
password = request.data["password"]
|
||||||
|
api_key = request.data["api_key"]
|
||||||
|
client = ClientToken.objects.get(client_token=api_key)
|
||||||
|
if User.objects.filter(username__exact=username).exists():
|
||||||
|
return Response({"result": "user exist"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if 'first_name' in request.data.keys() and 'last_name' in request.data.keys():
|
||||||
|
user = User(
|
||||||
|
username=username, password=cryptocode.encrypt(password, password), first_name=request.data['first_name'],
|
||||||
|
last_name=request.data['last_name']
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
|
||||||
|
user = User(
|
||||||
|
username=username, password=cryptocode.encrypt(password, password)
|
||||||
|
)
|
||||||
|
user.save()
|
||||||
|
# if 'role' in request.data.keys():
|
||||||
|
# group = Group.objects.get(name__exact=request.data['role'])
|
||||||
|
if not UserIdentity.objects.filter(user=user):
|
||||||
|
user_identity = UserIdentity(
|
||||||
|
user=user,
|
||||||
|
client=client
|
||||||
|
)
|
||||||
|
user_identity.save()
|
||||||
|
if 'national_code' in request.data.keys():
|
||||||
|
user_identity.national_id = request.data['national_code']
|
||||||
|
if 'first_name' in request.data.keys() and 'last_name' in request.data.keys():
|
||||||
|
user_identity.first_name = request.data['first_name']
|
||||||
|
user_identity.last_name = request.data['last_name']
|
||||||
|
user_identity.mobile = request.data['username']
|
||||||
|
user_identity.save()
|
||||||
|
|
||||||
|
# user_identity.role.add(group)
|
||||||
|
data = {
|
||||||
|
"username": str(user.username),
|
||||||
|
"password": user.password,
|
||||||
|
"client_id": client.client_id,
|
||||||
|
"client_secret": client.client_secret,
|
||||||
|
"grant_type": "client_credentials",
|
||||||
|
# "scope": "read"
|
||||||
|
"scope": "read write",
|
||||||
|
}
|
||||||
|
r = requests.post(url=BASE_URL + "token/", data=json.dumps(data), verify=False)
|
||||||
|
access = AccessToken.objects.get(token=r.json()["access_token"])
|
||||||
|
access.user = user
|
||||||
|
access.save()
|
||||||
|
dict_info = {
|
||||||
|
"access_token": r.json()["access_token"],
|
||||||
|
"expires_in": r.json()["expires_in"],
|
||||||
|
"token_type": r.json()["token_type"],
|
||||||
|
"scope": r.json()["scope"],
|
||||||
|
"expire_time": access.expires,
|
||||||
|
}
|
||||||
|
# r.json()["expire_time"]=access.expires
|
||||||
|
return Response(dict_info, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def register_all(request):
|
||||||
|
username = request.data["username"]
|
||||||
|
password = request.data["password"]
|
||||||
|
api_key = request.data["api_key"]
|
||||||
|
client = ClientToken.objects.get(client_token=api_key)
|
||||||
|
if User.objects.filter(username__exact=username).exists():
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
if 'first_name' in request.data.keys() and 'last_name' in request.data.keys():
|
||||||
|
user = User(
|
||||||
|
username=username, password=password, first_name=request.data['first_name'],
|
||||||
|
last_name=request.data['last_name']
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
user = User(
|
||||||
|
username=username, password=password
|
||||||
|
)
|
||||||
|
user.save()
|
||||||
|
if not UserIdentity.objects.filter(user=user):
|
||||||
|
user_identity = UserIdentity(
|
||||||
|
user=user,
|
||||||
|
client=client
|
||||||
|
)
|
||||||
|
user_identity.save()
|
||||||
|
if 'national_code' in request.data.keys():
|
||||||
|
user_identity.national_id = request.data['national_code']
|
||||||
|
if 'first_name' in request.data.keys() and 'last_name' in request.data.keys():
|
||||||
|
user_identity.first_name = request.data['first_name']
|
||||||
|
user_identity.last_name = request.data['last_name']
|
||||||
|
user_identity.mobile = request.data['username']
|
||||||
|
user_identity.save()
|
||||||
|
|
||||||
|
return Response("ok", status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def login(request):
|
||||||
|
username = request.data['username']
|
||||||
|
password = (request.data['password'],)
|
||||||
|
api_key = request.data["api_key"]
|
||||||
|
roles = []
|
||||||
|
roles_from_request = []
|
||||||
|
client = ClientToken.objects.get(client_token=api_key)
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username__exact=username)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return Response({'is_user': False}, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
if 'role' in request.data.keys():
|
||||||
|
if type(request.data['role']) is list:
|
||||||
|
roles_from_request = request.data['role']
|
||||||
|
else:
|
||||||
|
roles_from_request.append(request.data['role'])
|
||||||
|
|
||||||
|
if 'user_key' in request.data.keys():
|
||||||
|
for item in roles_from_request:
|
||||||
|
group = Group.objects.get(name__exact=item)
|
||||||
|
if not UserIdentity.objects.filter(user=user, role=group):
|
||||||
|
if not UserIdentity.objects.filter(user=user).exists():
|
||||||
|
user_identity = UserIdentity()
|
||||||
|
else:
|
||||||
|
user_identity = UserIdentity.objects.get(user=user)
|
||||||
|
user_identity.user = user
|
||||||
|
user_identity.key = request.data['user_key']
|
||||||
|
user_identity.client = client
|
||||||
|
user_identity.save()
|
||||||
|
user_identity.role.add(group)
|
||||||
|
else:
|
||||||
|
user_identity = UserIdentity.objects.get(user=user)
|
||||||
|
user_identity.key = request.data['user_key']
|
||||||
|
user_identity.client = client
|
||||||
|
user_identity.save()
|
||||||
|
for item in user_identity.role.all():
|
||||||
|
roles.append(item.name)
|
||||||
|
decrypted_password = cryptocode.decrypt(user.password, password[0])
|
||||||
|
if decrypted_password != password[0]:
|
||||||
|
return Response({'password': 'wrong'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
data = {
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
"client_id": client.client_id,
|
||||||
|
"client_secret": client.client_secret,
|
||||||
|
"grant_type": "client_credentials",
|
||||||
|
"scope": "read write",
|
||||||
|
}
|
||||||
|
r = requests.post(url=BASE_URL + "token/", data=json.dumps(data), verify=False)
|
||||||
|
access = AccessToken.objects.get(token=r.json()["access_token"])
|
||||||
|
access.user = user
|
||||||
|
access.save()
|
||||||
|
dict_info = {
|
||||||
|
"access_token": r.json()["access_token"],
|
||||||
|
"expires_in": r.json()["expires_in"],
|
||||||
|
"token_type": r.json()["token_type"],
|
||||||
|
"scope": r.json()["scope"],
|
||||||
|
"expire_time": access.expires,
|
||||||
|
"role": roles
|
||||||
|
}
|
||||||
|
return Response(dict_info, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class UserIdentityViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = UserIdentity.objects.all()
|
||||||
|
serializer_class = UserIdentitySerializer
|
||||||
|
permission_classes = [TokenHasReadWriteScope]
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
edit_type = request.data['type']
|
||||||
|
request.data.pop('type')
|
||||||
|
|
||||||
|
if edit_type == 'check_user':
|
||||||
|
# return Response({'sss': 'exist'}, status=status.HTTP_201_CREATED)
|
||||||
|
# if user exists in system
|
||||||
|
if self.queryset.filter(
|
||||||
|
mobile=request.data['value']
|
||||||
|
).exists() or self.queryset.filter(
|
||||||
|
national_id=request.data['value']
|
||||||
|
).exists():
|
||||||
|
|
||||||
|
if self.queryset.filter(
|
||||||
|
mobile=request.data['value']
|
||||||
|
).exists():
|
||||||
|
# contains user object
|
||||||
|
user = self.queryset.get(
|
||||||
|
mobile=request.data['value'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.queryset.filter(
|
||||||
|
national_id=request.data['value']
|
||||||
|
).exists():
|
||||||
|
# contains user object
|
||||||
|
user = self.queryset.get(
|
||||||
|
national_id=request.data['value'],
|
||||||
|
)
|
||||||
|
serializer = self.serializer_class(user)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
# contains user identity object
|
||||||
|
user_identity = UserIdentity.objects.get(key=request.data['userprofile_key'])
|
||||||
|
request.data.pop('userprofile_key') # remove user key from data
|
||||||
|
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
identity_obj = serializer.update(validated_data=request.data, instance=user_identity)
|
||||||
|
serializer = self.serializer_class(identity_obj)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["GET"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def Find_User(request):
|
||||||
|
data = request.GET["data"]
|
||||||
|
if UserIdentity.objects.filter(mobile=data).exists():
|
||||||
|
user = UserIdentity.objects.get(mobile=data)
|
||||||
|
elif UserIdentity.objects.filter(national_id=data).exists():
|
||||||
|
user = UserIdentity.objects.get(national_id=data)
|
||||||
|
else:
|
||||||
|
return Response({"result": "user not found"}, status=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
"firstname": user.first_name,
|
||||||
|
"lastname": user.last_name,
|
||||||
|
"national_id": user.national_id,
|
||||||
|
"mobile": user.mobile,
|
||||||
|
"city": user.city,
|
||||||
|
"province": user.province,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def Identity(request):
|
||||||
|
user = UserIdentity.objects.get(user__username=request.data["mobile"])
|
||||||
|
user.mobile = request.data["mobile"]
|
||||||
|
user.first_name = request.data["first_name"]
|
||||||
|
user.last_name = request.data["last_name"]
|
||||||
|
user.national_id = request.data["national_id"]
|
||||||
|
user.city = request.data["city"]
|
||||||
|
user.province = request.data["province"]
|
||||||
|
user.save()
|
||||||
|
return Response({"mobile": user.mobile, "first_name": user.first_name, "last_name": user.last_name})
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["GET"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def NumberOfActiveUsers(request):
|
||||||
|
from datetime import datetime
|
||||||
|
now=datetime.now().date()
|
||||||
|
access = AccessToken.objects.filter(expires__date__gte=now)
|
||||||
|
return Response({"number_of_active_users":len(access)})
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["GET"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def remove_access_token(request):
|
||||||
|
import datetime
|
||||||
|
token=request.GET.get('token')
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
accesses = AccessToken.objects.filter(created__date__gte=now.date() - timedelta(days=3))
|
||||||
|
if token is not None:
|
||||||
|
accesses=accesses.filter(token=token)
|
||||||
|
for access in accesses:
|
||||||
|
access.expires = now - timedelta(days=2)
|
||||||
|
access.save()
|
||||||
|
return Response("ok",status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["GET"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def check_user_exists(request):
|
||||||
|
mobile = request.GET.get('mobile')
|
||||||
|
|
||||||
|
if not mobile:
|
||||||
|
return Response(
|
||||||
|
{"error": "mobile parameter is required"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username__exact=mobile)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"exists": True,
|
||||||
|
"mobile": mobile,
|
||||||
|
"user_id": user.id
|
||||||
|
},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"exists": False,
|
||||||
|
"mobile": mobile
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def remove_user_role(request):
|
||||||
|
mobile = request.data.get('mobile')
|
||||||
|
role = request.data.get('role')
|
||||||
|
|
||||||
|
if not mobile:
|
||||||
|
return Response(
|
||||||
|
{"error": "mobile parameter is required"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
if not role:
|
||||||
|
return Response(
|
||||||
|
{"error": "role parameter is required"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username__exact=mobile)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{"error": "user not found"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_identity = UserIdentity.objects.get(user=user)
|
||||||
|
except UserIdentity.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{"error": "user identity not found"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
group = Group.objects.get(name__exact=role)
|
||||||
|
except Group.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{"error": "role not found"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_identity.role.filter(id=group.id).exists():
|
||||||
|
user_identity.role.remove(group)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"result": "role removed successfully",
|
||||||
|
"mobile": mobile,
|
||||||
|
"role": role
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"error": "user does not have this role",
|
||||||
|
"mobile": mobile,
|
||||||
|
"role": role
|
||||||
|
},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
0
Core/ArvanStorage/__init__.py
Normal file
0
Core/ArvanStorage/__init__.py
Normal file
BIN
Core/ArvanStorage/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Core/ArvanStorage/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Core/ArvanStorage/__pycache__/arvan_storage.cpython-39.pyc
Normal file
BIN
Core/ArvanStorage/__pycache__/arvan_storage.cpython-39.pyc
Normal file
Binary file not shown.
60
Core/ArvanStorage/arvan_storage.py
Normal file
60
Core/ArvanStorage/arvan_storage.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import io
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
from django.http import HttpResponse
|
||||||
|
import base64
|
||||||
|
|
||||||
|
|
||||||
|
# ARVAN_STORAGE_URL = "https://dmstore.s3.ir-thr-at1.arvanstorage.com/36bba98f-a813-4667-bd60-33aef708bcba.jpg?AWSAccessKeyId=d5739a44-e663-4f43-99f3-13121a62a9e6&Signature=KpBpHBtAS77Y3hHx53g6bmjlGpc%3D&Expires=1651552380"
|
||||||
|
|
||||||
|
|
||||||
|
def connect():
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
try:
|
||||||
|
s3_resource = boto3.resource(
|
||||||
|
's3',
|
||||||
|
endpoint_url='https://s3.ir-thr-at1.arvanstorage.com',
|
||||||
|
aws_access_key_id='d5739a44-e663-4f43-99f3-13121a62a9e6',
|
||||||
|
aws_secret_access_key='bcc8bc64cac2de7711f8c5d3a4b0123f28319f97fb9e0e9b8fbcfd7465678cdb'
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logging.info(exc)
|
||||||
|
return s3_resource
|
||||||
|
|
||||||
|
|
||||||
|
def get_bucket_list():
|
||||||
|
s3_resource = connect()
|
||||||
|
li = []
|
||||||
|
try:
|
||||||
|
for bucket in s3_resource.buckets.all():
|
||||||
|
logging.info(f'bucket_name: {bucket.name}')
|
||||||
|
li.append(bucket.name)
|
||||||
|
except ClientError as exc:
|
||||||
|
logging.error(exc)
|
||||||
|
return li[0]
|
||||||
|
|
||||||
|
|
||||||
|
def upload_object(image_data, bucket_name, object_name):
|
||||||
|
resource_connect = connect()
|
||||||
|
s3_resource = resource_connect
|
||||||
|
bucket = s3_resource.Bucket(bucket_name)
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
imgdata = base64.b64decode(image_data)
|
||||||
|
img = Image.open(io.BytesIO(imgdata))
|
||||||
|
new_img = img.resize((500, 500)) # x, y
|
||||||
|
new_img.save(buffer, format="PNG")
|
||||||
|
img_b64 = base64.b64encode(buffer.getvalue())
|
||||||
|
with open(object_name, "wb") as fh:
|
||||||
|
fh.write(base64.standard_b64decode(img_b64))
|
||||||
|
# base64.standard_b64decode(image_data)
|
||||||
|
with open(object_name, "rb") as fh:
|
||||||
|
bucket.put_object(
|
||||||
|
ACL='public-read',
|
||||||
|
Body=fh,
|
||||||
|
Key=object_name
|
||||||
|
)
|
||||||
0
Core/__init__.py
Normal file
0
Core/__init__.py
Normal file
BIN
Core/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Core/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Core/__pycache__/admin.cpython-39.pyc
Normal file
BIN
Core/__pycache__/admin.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Core/__pycache__/apps.cpython-39.pyc
Normal file
BIN
Core/__pycache__/apps.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Core/__pycache__/models.cpython-39.pyc
Normal file
BIN
Core/__pycache__/models.cpython-39.pyc
Normal file
Binary file not shown.
3
Core/admin.py
Normal file
3
Core/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
Core/apps.py
Normal file
6
Core/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CoreConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'Core'
|
||||||
0
Core/migrations/__init__.py
Normal file
0
Core/migrations/__init__.py
Normal file
BIN
Core/migrations/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Core/migrations/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
33
Core/models.py
Normal file
33
Core/models.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
from django.conf import settings
|
||||||
|
from datetime import datetime, timezone, time
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
class BaseModel(models.Model):
|
||||||
|
key = models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)
|
||||||
|
create_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
modify_date = models.DateTimeField(auto_now=True)
|
||||||
|
created_by = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
related_name="%(class)s_createdby",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
modified_by = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="%(class)s_modifiedby",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
trash = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
3
Core/tests.py
Normal file
3
Core/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
1
Core/views.py
Normal file
1
Core/views.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
59
Dockerfile
Normal file
59
Dockerfile
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Dockerfile
|
||||||
|
FROM python:3.9-slim-bookworm
|
||||||
|
ENV TZ="Asia/Tehran"
|
||||||
|
RUN ls /usr/share/zoneinfo && \
|
||||||
|
cp /usr/share/zoneinfo/Asia/Tehran /etc/localtime && \
|
||||||
|
echo "Asia/Tehran" > /etc/timezone && \
|
||||||
|
dpkg-reconfigure -f noninteractive tzdata
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# ساخت sources.list جدید با mirror ArvanCloud (برای سرعت در ایران)
|
||||||
|
RUN echo "deb http://mirror.arvancloud.ir/debian bookworm main" > /etc/apt/sources.list \
|
||||||
|
&& echo "deb-src http://mirror.arvancloud.ir/debian bookworm main" >> /etc/apt/sources.list \
|
||||||
|
&& echo "deb https://mirror.arvancloud.ir/debian-security bookworm-security main" >> /etc/apt/sources.list \
|
||||||
|
&& echo "deb-src https://mirror.arvancloud.ir/debian-security bookworm-security main" >> /etc/apt/sources.list \
|
||||||
|
&& echo "deb http://mirror.arvancloud.ir/debian bookworm-updates main" >> /etc/apt/sources.list \
|
||||||
|
&& echo "deb-src http://mirror.arvancloud.ir/debian bookworm-updates main" >> /etc/apt/sources.list
|
||||||
|
|
||||||
|
# Update + Install system deps (with apt cache)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
libpq-dev \
|
||||||
|
python3-dev \
|
||||||
|
libcairo2 \
|
||||||
|
libcairo2-dev \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libpangoft2-1.0-0 \
|
||||||
|
libpangocairo-1.0-0 \
|
||||||
|
libpango1.0-dev \
|
||||||
|
libgdk-pixbuf-2.0-0 \
|
||||||
|
libffi-dev \
|
||||||
|
libjpeg-dev \
|
||||||
|
libpng-dev \
|
||||||
|
libfreetype6 \
|
||||||
|
libharfbuzz0b \
|
||||||
|
shared-mime-info \
|
||||||
|
fonts-dejavu \
|
||||||
|
curl \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Upgrade pip
|
||||||
|
RUN pip install --upgrade pip
|
||||||
|
|
||||||
|
# Copy requirements
|
||||||
|
COPY ./requirements.txt .
|
||||||
|
|
||||||
|
# Install Python dependencies با cache mount (سرعت pip رو بیشتر میکنه)
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
|
pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy project files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose Django port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Run Django development server
|
||||||
|
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||||
0
Notification/__init__.py
Normal file
0
Notification/__init__.py
Normal file
BIN
Notification/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Notification/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Notification/__pycache__/admin.cpython-39.pyc
Normal file
BIN
Notification/__pycache__/admin.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Notification/__pycache__/apps.cpython-39.pyc
Normal file
BIN
Notification/__pycache__/apps.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Notification/__pycache__/models.cpython-39.pyc
Normal file
BIN
Notification/__pycache__/models.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Notification/__pycache__/serializers.cpython-39.pyc
Normal file
BIN
Notification/__pycache__/serializers.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Notification/__pycache__/urls.cpython-39.pyc
Normal file
BIN
Notification/__pycache__/urls.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Notification/__pycache__/views.cpython-39.pyc
Normal file
BIN
Notification/__pycache__/views.cpython-39.pyc
Normal file
Binary file not shown.
3
Notification/admin.py
Normal file
3
Notification/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
Notification/apps.py
Normal file
6
Notification/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'Notification'
|
||||||
80
Notification/migrations/0001_initial.py
Normal file
80
Notification/migrations/0001_initial.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2023-09-17 15:05
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NotificationType',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('name', models.CharField(choices=[('user', 'USER'), ('alluser', 'AllUSER'), ('group', 'GROUP'), ('allgroup', 'AllGROUP'), ('usergroup', 'UserGroup'), ('poultry', 'Poultry'), ('province_accept', 'ProvinceAccept'), ('province_rejected', 'ProvinceRejected'), ('city_operator_accept', 'CityOperatorAccept'), ('city_operator_rejected', 'CityOperatorRejected'), ('assignment_accepted', 'AssignmentAccepted'), ('assignment_rejected', 'AssignmentRejected')], default='', max_length=50, null=True)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notificationtype_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notificationtype_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NotificationToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('token', models.CharField(max_length=100)),
|
||||||
|
('app_name', models.CharField(max_length=100, null=True)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notificationtoken_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notificationtoken_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notification_user', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Notification',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('title', models.CharField(default='', max_length=200, null=True)),
|
||||||
|
('content', models.CharField(default='', max_length=500, null=True)),
|
||||||
|
('image', models.CharField(max_length=100, null=True)),
|
||||||
|
('icon', models.CharField(max_length=100, null=True)),
|
||||||
|
('app_ids', models.CharField(default='', max_length=200, null=True)),
|
||||||
|
('device_ids', models.CharField(default='', max_length=200, null=True)),
|
||||||
|
('hash_id', models.CharField(default='', max_length=20, null=True)),
|
||||||
|
('status', models.CharField(choices=[('read', 'Read'), ('pending', 'Pending'), ('sent', 'Sent'), ('unread', 'Unread'), ('silent', 'Silent')], default='', max_length=10, null=True)),
|
||||||
|
('app_name', models.CharField(max_length=100, null=True)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notification_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notification_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('notif_type', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='types', to='Notification.notificationtype')),
|
||||||
|
('notification_group', models.ManyToManyField(null=True, related_name='group', to='auth.Group')),
|
||||||
|
('notification_user', models.ManyToManyField(null=True, related_name='notification_token', to='Notification.NotificationToken')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Notification/migrations/__init__.py
Normal file
0
Notification/migrations/__init__.py
Normal file
BIN
Notification/migrations/__pycache__/0001_initial.cpython-39.pyc
Normal file
BIN
Notification/migrations/__pycache__/0001_initial.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Notification/migrations/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Notification/migrations/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
89
Notification/models.py
Normal file
89
Notification/models.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
from Core.models import BaseModel
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
class NotificationType(BaseModel):
|
||||||
|
notif_types = (
|
||||||
|
("user", "USER"),
|
||||||
|
("alluser", "AllUSER"),
|
||||||
|
("group", "GROUP"),
|
||||||
|
("allgroup", "AllGROUP"),
|
||||||
|
("usergroup", "UserGroup"),
|
||||||
|
("poultry", "Poultry"),
|
||||||
|
("province_accept", "ProvinceAccept"),
|
||||||
|
("province_rejected", "ProvinceRejected"),
|
||||||
|
("city_operator_accept", "CityOperatorAccept"),
|
||||||
|
("city_operator_rejected", "CityOperatorRejected"),
|
||||||
|
("assignment_accepted", "AssignmentAccepted"),
|
||||||
|
("assignment_rejected", "AssignmentRejected"),
|
||||||
|
)
|
||||||
|
name = models.CharField(choices=notif_types, max_length=50, default="", null=True)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(NotificationType, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationToken(BaseModel):
|
||||||
|
token = models.CharField(max_length=100)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notification_user", null=True)
|
||||||
|
app_name = models.CharField(max_length=100, null=True)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.token
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(NotificationToken, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Notification(BaseModel):
|
||||||
|
s = (
|
||||||
|
("read", "Read"),
|
||||||
|
("pending", "Pending"),
|
||||||
|
("sent", "Sent"),
|
||||||
|
("unread", "Unread"),
|
||||||
|
("silent", "Silent"),
|
||||||
|
)
|
||||||
|
notif_type = models.ForeignKey(
|
||||||
|
NotificationType,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
related_name="types",
|
||||||
|
)
|
||||||
|
notification_user = models.ManyToManyField(
|
||||||
|
NotificationToken,
|
||||||
|
null=True,
|
||||||
|
related_name="notification_token"
|
||||||
|
)
|
||||||
|
notification_group = models.ManyToManyField(
|
||||||
|
Group,
|
||||||
|
null=True,
|
||||||
|
related_name="group"
|
||||||
|
)
|
||||||
|
title = models.CharField(max_length=200, default="", null=True)
|
||||||
|
content = models.CharField(max_length=500, default="", null=True)
|
||||||
|
image = models.CharField(max_length=100, null=True)
|
||||||
|
icon = models.CharField(max_length=100, null=True)
|
||||||
|
app_ids = models.CharField(max_length=200, default="", null=True)
|
||||||
|
device_ids = models.CharField(max_length=200, default="", null=True)
|
||||||
|
hash_id = models.CharField(max_length=20, default="", null=True)
|
||||||
|
status = models.CharField(choices=s, max_length=10, default="", null=True)
|
||||||
|
app_name = models.CharField(max_length=100, null=True)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(Notification, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
pass
|
||||||
0
Notification/najva/__init__.py
Normal file
0
Notification/najva/__init__.py
Normal file
BIN
Notification/najva/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Notification/najva/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
17
Notification/najva/get_segments_detail.py
Normal file
17
Notification/najva/get_segments_detail.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from django.http.response import JsonResponse
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def get_segments(request):
|
||||||
|
url = "https://app.najva.com/api/v1/websites/65b3a75a-d634-48c5-824f-c80c703534af/segments/"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'content-type': "application/json",
|
||||||
|
'authorization': "Token 982c17c1d460fec1eef6270c7d6550e3b9b33d2d",
|
||||||
|
'cache-control': "no-cache",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.request('GET', url=url, headers=headers)
|
||||||
|
resp = json.loads(response.text.encode('utf8'))
|
||||||
|
return JsonResponse(resp, safe=False)
|
||||||
75
Notification/najva/send_notif_to_segments.py
Normal file
75
Notification/najva/send_notif_to_segments.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from django.http.response import JsonResponse
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def send_notification_to_all_segments(
|
||||||
|
title=None,
|
||||||
|
body=None,
|
||||||
|
content=None,
|
||||||
|
icon=None,
|
||||||
|
image=None,
|
||||||
|
segments_include=None,
|
||||||
|
segments_exclude=None,
|
||||||
|
):
|
||||||
|
url = "https://app.najva.com/api/v1/notifications/"
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"api_key": "65b3a75a-d634-48c5-824f-c80c703534af",
|
||||||
|
"title": "title",
|
||||||
|
"body": "body",
|
||||||
|
"priority": "high",
|
||||||
|
"onclick_action": "open-link",
|
||||||
|
"url": "https://imedstores.ir/",
|
||||||
|
"content": "content",
|
||||||
|
"icon": "",
|
||||||
|
"image": "",
|
||||||
|
# "json": "{"key":"value"}",
|
||||||
|
"sent_time": datetime.now() + timedelta(minutes=1),
|
||||||
|
"segments_include": [],
|
||||||
|
"segments_exclude": [],
|
||||||
|
"one_signal_enabled": False,
|
||||||
|
"one_signal_accounts": []
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
'authorization': "Token 982c17c1d460fec1eef6270c7d6550e3b9b33d2d",
|
||||||
|
'content-type': "application/json",
|
||||||
|
'cache-control': "no-cache",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.request("POST", url, data=json.dumps(payload, default=str), headers=headers)
|
||||||
|
resp = json.loads(response.text.encode('utf-8'))
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
def send_notification_to_specific_segment(
|
||||||
|
title="سامانه سبحان طیور",
|
||||||
|
body="خوش آمدید",
|
||||||
|
content="سامانه مدیریت درخواست های مرغداران",
|
||||||
|
icon="https://user-image-gallery.s3.ir-thr-at1.arvanstorage.com/1WGPTMFND3TREWD.jpg",
|
||||||
|
image="https://user-image-gallery.s3.ir-thr-at1.arvanstorage.com/1WGPTMFND3TREWD.jpg",
|
||||||
|
subscriber_tokens=None,
|
||||||
|
):
|
||||||
|
url = "https://app.najva.com/notification/api/v1/notifications/"
|
||||||
|
payload = {
|
||||||
|
"api_key": "65b3a75a-d634-48c5-824f-c80c703534af",
|
||||||
|
"subscriber_tokens": subscriber_tokens,
|
||||||
|
"title": title,
|
||||||
|
"body": body,
|
||||||
|
"onclick_action": "open-link",
|
||||||
|
"url": "https://imedstores.ir/",
|
||||||
|
"content": content,
|
||||||
|
"icon": icon,
|
||||||
|
"image": image,
|
||||||
|
"sent_time": datetime.now() + timedelta(minutes=3),
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
'authorization': "Token 982c17c1d460fec1eef6270c7d6550e3b9b33d2d",
|
||||||
|
'content-type': "application/json",
|
||||||
|
'cache-control': "no-cache",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.request("POST", url, data=json.dumps(payload, default=str), headers=headers)
|
||||||
|
resp = json.loads(response.text.encode('utf-8'))
|
||||||
|
return resp
|
||||||
18
Notification/serializers.py
Normal file
18
Notification/serializers.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from .models import Notification, NotificationToken
|
||||||
|
from Authentication.serializers import GroupSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationTokenSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
Model = NotificationToken
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationSerializer(serializers.ModelSerializer):
|
||||||
|
notif_user = NotificationTokenSerializer()
|
||||||
|
notif_group = GroupSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
Model = Notification
|
||||||
|
fields = "__all__"
|
||||||
3
Notification/tests.py
Normal file
3
Notification/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
12
Notification/urls.py
Normal file
12
Notification/urls.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.urls import path, include
|
||||||
|
from rest_framework import routers
|
||||||
|
from django.conf import settings
|
||||||
|
import oauth2_provider.views as oauth2_views
|
||||||
|
from .views import NajvaNotificationViewSet
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register('notification_base', NajvaNotificationViewSet, basename='notification_base')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
]
|
||||||
157
Notification/views.py
Normal file
157
Notification/views.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from Authentication.models import UserIdentity
|
||||||
|
from rest_framework import viewsets, status
|
||||||
|
|
||||||
|
from Core.ArvanStorage.arvan_storage import upload_object
|
||||||
|
from .models import (
|
||||||
|
Notification,
|
||||||
|
NotificationType,
|
||||||
|
NotificationToken
|
||||||
|
)
|
||||||
|
from .najva.send_notif_to_segments import (
|
||||||
|
send_notification_to_all_segments,
|
||||||
|
send_notification_to_specific_segment
|
||||||
|
)
|
||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
from .serializers import (
|
||||||
|
NotificationTokenSerializer,
|
||||||
|
NotificationSerializer
|
||||||
|
)
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
ARVAN_NOTIFICATION_GALLERY_URL = 'https://notification-gallery.s3.ir-thr-at1.arvanstorage.com/'
|
||||||
|
|
||||||
|
|
||||||
|
class NajvaNotificationViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = NotificationToken.objects.all()
|
||||||
|
serializer_class = NotificationSerializer
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
if "key" in request.GET:
|
||||||
|
add_obj = Notification.objects.get(key__exact=request.GET["key"])
|
||||||
|
serializer = self.serializer_class(add_obj)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
if "read_notif" in request.GET:
|
||||||
|
add_obj = Notification.objects.filter(
|
||||||
|
user_id=request.user.id,
|
||||||
|
status="read",
|
||||||
|
app_name=request.GET['app_name']
|
||||||
|
)
|
||||||
|
query = [x for x in add_obj]
|
||||||
|
serializer = self.serializer_class(query, many=True)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
if "unread_notif" in request.GET:
|
||||||
|
add_obj = Notification.objects.filter(
|
||||||
|
user_id=request.user.id,
|
||||||
|
status="unread",
|
||||||
|
app_name=request.GET['app_name']
|
||||||
|
)
|
||||||
|
query = [x for x in add_obj]
|
||||||
|
serializer = self.serializer_class(query, many=True)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
if "pending_notif" in request.GET:
|
||||||
|
add_obj = Notification.objects.filter(
|
||||||
|
user_id=request.user.id,
|
||||||
|
status="pending",
|
||||||
|
app_name=request.GET['app_name']
|
||||||
|
)
|
||||||
|
query = [x for x in add_obj]
|
||||||
|
serializer = self.serializer_class(query, many=True)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
queryset = Notification.objects.all()
|
||||||
|
serializer = self.serializer_class(queryset, many=True)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
segments = []
|
||||||
|
userprofile = User.objects.get(user=request.user)
|
||||||
|
ran = ''.join(random.choices(string.ascii_uppercase + string.digits, k=15))
|
||||||
|
notification = Notification()
|
||||||
|
if 'image' in request.data.keys():
|
||||||
|
image = request.data['image']
|
||||||
|
upload_object(
|
||||||
|
image_data=image,
|
||||||
|
bucket_name="notification-gallery",
|
||||||
|
object_name="{0}.jpg".format(str(ran))
|
||||||
|
)
|
||||||
|
notification_image = ARVAN_NOTIFICATION_GALLERY_URL + "{0}.jpg".format(str(ran))
|
||||||
|
os.remove("{0}.jpg".format(str(ran)))
|
||||||
|
else:
|
||||||
|
notification_image = ""
|
||||||
|
if 'icon' in request.data.keys():
|
||||||
|
icon = request.data['icon']
|
||||||
|
upload_object(
|
||||||
|
image_data=icon,
|
||||||
|
bucket_name="notification-gallery",
|
||||||
|
object_name="{0}.jpg".format(str(ran))
|
||||||
|
)
|
||||||
|
notification_icon = ARVAN_NOTIFICATION_GALLERY_URL + "{0}.jpg".format(str(ran))
|
||||||
|
os.remove("{0}.jpg".format(str(ran)))
|
||||||
|
else:
|
||||||
|
notification_icon = ""
|
||||||
|
if 'request_type' in request.data.keys():
|
||||||
|
if request.data['request_type'] == "token":
|
||||||
|
if not NotificationToken.objects.filter(user=userprofile):
|
||||||
|
notification = NotificationToken()
|
||||||
|
notification.token = request.data['token']
|
||||||
|
notification.user = userprofile
|
||||||
|
notification.app_name = request.data['app_name']
|
||||||
|
notification.save()
|
||||||
|
return Response({"msg": "Done"}, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response({"msg": "user already has token"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
if 'value' in request.data.keys():
|
||||||
|
if not request.data['value']:
|
||||||
|
send_notification = send_notification_to_all_segments(
|
||||||
|
title=request.data['title'],
|
||||||
|
body=request.data['body'],
|
||||||
|
content=request.data['content'],
|
||||||
|
icon=notification_icon,
|
||||||
|
image=notification_image,
|
||||||
|
segments_include=request.data['segments_include'],
|
||||||
|
segments_exclude=request.data['segments_exclude'],
|
||||||
|
# subscriber_tokens=['c22206d3-248a-4c81-b7c2-de2cfe5e5766']
|
||||||
|
# subscriber_tokens=['2cc244fc-1340-4942-bf19-2ba9f66f44e6']
|
||||||
|
)
|
||||||
|
notification.notif_type = NotificationType.objects.get(name="alluser")
|
||||||
|
else:
|
||||||
|
for key in request.data['value']:
|
||||||
|
if User.objects.filter(key__exact=key):
|
||||||
|
notif_user = NotificationToken.objects.get(user__key__exact=key)
|
||||||
|
segments.append(notif_user.token)
|
||||||
|
if Group.objects.filter(name__exact=key):
|
||||||
|
for item in NotificationToken.objects.filter(user__role__name=key):
|
||||||
|
segments.append(item.token)
|
||||||
|
send_notification = send_notification_to_specific_segment(
|
||||||
|
title=request.data['title'],
|
||||||
|
body=request.data['body'],
|
||||||
|
content=request.data['content'],
|
||||||
|
icon=notification_icon,
|
||||||
|
image=notification_image,
|
||||||
|
subscriber_tokens=segments
|
||||||
|
)
|
||||||
|
notification.notif_type = NotificationType.objects.get(name=request.data['request_type'])
|
||||||
|
notification.title = request.data['title']
|
||||||
|
notification.content = request.data['content']
|
||||||
|
notification.icon = notification_icon
|
||||||
|
notification.image = notification_image
|
||||||
|
notification.app_name = request.data['app_name']
|
||||||
|
notification.save()
|
||||||
|
if 'value' in request.data.keys():
|
||||||
|
for key in request.data['value']:
|
||||||
|
if UserIdentity.objects.filter(key__exact=key):
|
||||||
|
user = UserIdentity.objects.get(key__exact=key).user
|
||||||
|
notification.notification_user.add(user)
|
||||||
|
# elif Group.objects.filter(name__exact=key):
|
||||||
|
# notification.notification_group.add(Group.objects.get(name__exact=key))
|
||||||
|
# for item in User.objects.filter(role=Group.objects.get(name__exact=key)):
|
||||||
|
# notification.notification_user.add(item)
|
||||||
|
return Response(send_notification)
|
||||||
2
README.md
Normal file
2
README.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# ArtaSystemMain
|
||||||
|
Arta System Project For Main Server
|
||||||
0
Wallet/__init__.py
Normal file
0
Wallet/__init__.py
Normal file
BIN
Wallet/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Wallet/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Wallet/__pycache__/admin.cpython-39.pyc
Normal file
BIN
Wallet/__pycache__/admin.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Wallet/__pycache__/apps.cpython-39.pyc
Normal file
BIN
Wallet/__pycache__/apps.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Wallet/__pycache__/errors.cpython-39.pyc
Normal file
BIN
Wallet/__pycache__/errors.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Wallet/__pycache__/models.cpython-39.pyc
Normal file
BIN
Wallet/__pycache__/models.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Wallet/__pycache__/processor.cpython-39.pyc
Normal file
BIN
Wallet/__pycache__/processor.cpython-39.pyc
Normal file
Binary file not shown.
3
Wallet/admin.py
Normal file
3
Wallet/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
Wallet/apps.py
Normal file
6
Wallet/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class WalletConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'Wallet'
|
||||||
10
Wallet/errors.py
Normal file
10
Wallet/errors.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.db import IntegrityError
|
||||||
|
|
||||||
|
|
||||||
|
class InsufficientBalance(IntegrityError):
|
||||||
|
"""Raised when a wallet has insufficient balance to
|
||||||
|
run an operation.
|
||||||
|
We're subclassing from :mod:`django.db.IntegrityError`
|
||||||
|
so that it is automatically rolled-back during django's
|
||||||
|
transaction lifecycle.
|
||||||
|
"""
|
||||||
204
Wallet/migrations/0001_initial.py
Normal file
204
Wallet/migrations/0001_initial.py
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
# Generated by Django 3.2.13 on 2023-09-17 15:05
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Address',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('title', models.CharField(default='', max_length=200, null=True)),
|
||||||
|
('country', models.CharField(default='', max_length=100, null=True)),
|
||||||
|
('province', models.CharField(default='', max_length=50, null=True)),
|
||||||
|
('city', models.CharField(default='', max_length=50, null=True)),
|
||||||
|
('street', models.CharField(default='', max_length=200, null=True)),
|
||||||
|
('postal_code', models.CharField(default='', max_length=20, null=True)),
|
||||||
|
('phone', models.CharField(default='', max_length=20, null=True)),
|
||||||
|
('phone_type', models.CharField(default='', max_length=20, null=True)),
|
||||||
|
('no', models.CharField(default='', max_length=5, null=True)),
|
||||||
|
('floor', models.IntegerField(default=0, null=True)),
|
||||||
|
('unit', models.IntegerField(default=0, null=True)),
|
||||||
|
('is_default', models.BooleanField(default=False, null=True)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='address_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='address_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_address', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BankCard',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('card', models.CharField(default='', max_length=16, null=True)),
|
||||||
|
('iban', models.CharField(default='', max_length=100, null=True)),
|
||||||
|
('state', models.CharField(default='pending', max_length=20)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bankcard_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bankcard_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='banks', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PaymentMethod',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('method_type', models.CharField(default='', max_length=255)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='paymentmethod_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='paymentmethod_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='payment_user', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Wallet',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('credit', models.CharField(default='0', max_length=20)),
|
||||||
|
('card_expiry', models.DateTimeField(default=django.utils.timezone.now, null=True)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wallet_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('credit_cards', models.ManyToManyField(to='Wallet.BankCard')),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wallet_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wallets', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'wallet',
|
||||||
|
'verbose_name_plural': 'wallets',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Transaction',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('date', models.DateTimeField(default=django.utils.timezone.now, help_text='When the account was created')),
|
||||||
|
('amount', models.DecimalField(decimal_places=5, max_digits=12)),
|
||||||
|
('status', models.CharField(choices=[('completed', 'Complete!'), ('requested', 'Requested!'), ('pending', 'Pending!'), ('confirmed', 'Confirmed!')], max_length=45)),
|
||||||
|
('transaction_type', models.CharField(choices=[('send', 'Send'), ('request', 'Request'), ('transfer', 'Transfer')], default='', max_length=45)),
|
||||||
|
('category', models.CharField(choices=[('Bank', 'Bank Transfer'), ('Utilities', 'Bills & Utilities'), ('Transportation', 'Auto & Transport'), ('Groceries', 'Groceries'), ('Food', 'Food'), ('Shopping', 'Shopping'), ('Health', 'Healthcare'), ('Education', 'Education'), ('Travel', 'Travel'), ('Housing', 'Housing'), ('Entertainment', 'Entertainment'), ('Others', 'Others')], max_length=45)),
|
||||||
|
('description', models.CharField(default=False, max_length=200)),
|
||||||
|
('create_date', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||||
|
('is_complete', models.BooleanField(default=False)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transaction_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('creator', models.ForeignKey(default='', on_delete=django.db.models.deletion.PROTECT, related_name='creator', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transaction_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('payment_method', models.ForeignKey(default='', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='payment_method', to='Wallet.paymentmethod')),
|
||||||
|
('receiver', models.ForeignKey(default='', on_delete=django.db.models.deletion.PROTECT, related_name='receiver', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('wallet', models.ForeignKey(blank=True, help_text='Wallet holding payment information', null=True, on_delete=django.db.models.deletion.SET_NULL, to='Wallet.wallet')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'transaction',
|
||||||
|
'verbose_name_plural': 'transactions',
|
||||||
|
'ordering': ('-date',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Shipping',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('client', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shipping_client', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('client_address', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='client_address', to='Wallet.address')),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shipping_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shipping_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('supplier', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shipping_supplier', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Factor',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('key', models.UUIDField(default=uuid.uuid4, editable=False, null=True, unique=True)),
|
||||||
|
('create_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('modify_date', models.DateTimeField(auto_now=True)),
|
||||||
|
('trash', models.BooleanField(default=False)),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='factor_createdby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='factor_modifiedby', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('transaction', models.ForeignKey(blank=True, help_text='Transactions', null=True, on_delete=django.db.models.deletion.SET_NULL, to='Wallet.transaction')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Account',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('balance', models.FloatField(default=0.0)),
|
||||||
|
('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='Wallet.paymentmethod')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Card',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('card_type', models.CharField(choices=[('Credit', 'Credit Card'), ('Debit', 'Debit Card')], max_length=45)),
|
||||||
|
('card_number', models.CharField(default=None, max_length=16)),
|
||||||
|
('owner_first_name', models.CharField(max_length=45)),
|
||||||
|
('owner_last_name', models.CharField(max_length=45)),
|
||||||
|
('security_code', models.CharField(default=None, max_length=3)),
|
||||||
|
('expiration_date', models.DateField(default=None)),
|
||||||
|
('payment', models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, to='Wallet.paymentmethod')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['card_type', 'card_number'],
|
||||||
|
'unique_together': {('card_type', 'owner_first_name', 'owner_last_name', 'card_number', 'security_code', 'expiration_date')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Bank',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('owner_first_name', models.CharField(default=None, max_length=255)),
|
||||||
|
('owner_last_name', models.CharField(default=None, max_length=255)),
|
||||||
|
('routing_number', models.CharField(default=None, max_length=9)),
|
||||||
|
('account_number', models.CharField(default=None, max_length=10)),
|
||||||
|
('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='Wallet.paymentmethod')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('routing_number', 'account_number')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
Wallet/migrations/__init__.py
Normal file
0
Wallet/migrations/__init__.py
Normal file
BIN
Wallet/migrations/__pycache__/0001_initial.cpython-39.pyc
Normal file
BIN
Wallet/migrations/__pycache__/0001_initial.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Wallet/migrations/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Wallet/migrations/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
404
Wallet/models.py
Normal file
404
Wallet/models.py
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from Authentication.models import BaseModel
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from ArtaSystem import settings
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from django.urls import reverse
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from Wallet.errors import InsufficientBalance
|
||||||
|
|
||||||
|
from Wallet.processor import DPSPayProcessor
|
||||||
|
|
||||||
|
try: # available from Django1.4
|
||||||
|
from django.utils.timezone import now
|
||||||
|
except ImportError:
|
||||||
|
now = datetime.now
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
|
||||||
|
class Address(BaseModel):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_address", null=True)
|
||||||
|
title = models.CharField(max_length=200, default="", null=True)
|
||||||
|
country = models.CharField(max_length=100, default="", null=True)
|
||||||
|
province = models.CharField(max_length=50, default="", null=True)
|
||||||
|
# province = models.CharField(choices=provinces, max_length=50, default="", null=True)
|
||||||
|
city = models.CharField(max_length=50, default="", null=True)
|
||||||
|
# city = models.CharField(choices=cities, max_length=50, default="", null=True)
|
||||||
|
street = models.CharField(default="", max_length=200, null=True)
|
||||||
|
postal_code = models.CharField(max_length=20, default="", null=True)
|
||||||
|
phone = models.CharField(max_length=20, default="", null=True)
|
||||||
|
phone_type = models.CharField(max_length=20, default="", null=True)
|
||||||
|
# phone_type = models.CharField(choices=phone_types, max_length=20, default="home", null=True)
|
||||||
|
no = models.CharField(max_length=5, default="", null=True)
|
||||||
|
floor = models.IntegerField(default=0, null=True)
|
||||||
|
unit = models.IntegerField(default=0, null=True)
|
||||||
|
# geo_points = models.OneToOneField(
|
||||||
|
# GEOPoints, default=None, on_delete=models.CASCADE, null=True
|
||||||
|
# )
|
||||||
|
|
||||||
|
is_default = models.BooleanField(default=False, null=True)
|
||||||
|
|
||||||
|
# def get_geo_points(self):
|
||||||
|
# return {"lang": self.geo_points.lang, "lat": self.geo_points.lat}
|
||||||
|
#
|
||||||
|
# def get_persian_address(self):
|
||||||
|
# return (
|
||||||
|
# "کشور %c - استان %c - شهر %c - خیابان %c - طبقه %c - واحد %c - پلاک %c"
|
||||||
|
# % (
|
||||||
|
# self.country,
|
||||||
|
# self.province,
|
||||||
|
# self.city,
|
||||||
|
# self.street,
|
||||||
|
# self.floor,
|
||||||
|
# self.unit,
|
||||||
|
# self.no,
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# def get_english_address(self):
|
||||||
|
# return (
|
||||||
|
# "%c Country - %c Province - %c City - %c Street - %c Floor - %c Unit - %c No."
|
||||||
|
# % (
|
||||||
|
# self.country,
|
||||||
|
# self.province,
|
||||||
|
# self.city,
|
||||||
|
# self.street,
|
||||||
|
# self.floor,
|
||||||
|
# self.unit,
|
||||||
|
# self.no,
|
||||||
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(Address, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class BankCard(BaseModel):
|
||||||
|
# CARD_TYPE_CHOICES = (
|
||||||
|
# ('CB', "Carte Bleu / VISA / Mastercard"),
|
||||||
|
# ('AMEX', "American Express"))
|
||||||
|
|
||||||
|
user = models.ForeignKey(
|
||||||
|
User, on_delete=models.CASCADE, related_name="banks", null=True
|
||||||
|
)
|
||||||
|
card = models.CharField(max_length=16, null=True, default="")
|
||||||
|
iban = models.CharField(max_length=100, null=True, default="")
|
||||||
|
state = models.CharField(max_length=20, default="pending")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(BankCard, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentMethod(BaseModel):
|
||||||
|
method_type = models.CharField(max_length=255, default="")
|
||||||
|
user = models.ForeignKey(
|
||||||
|
User, related_name="payment_user", on_delete=models.PROTECT
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.key)
|
||||||
|
|
||||||
|
|
||||||
|
Card_Type = (
|
||||||
|
("Credit", "Credit Card"),
|
||||||
|
("Debit", "Debit Card"),
|
||||||
|
)
|
||||||
|
|
||||||
|
Transaction_Type = (
|
||||||
|
("send", "Send"),
|
||||||
|
("request", "Request"),
|
||||||
|
("transfer", "Transfer"),
|
||||||
|
)
|
||||||
|
|
||||||
|
states = (
|
||||||
|
("completed", "Complete!"),
|
||||||
|
("requested", "Requested!"),
|
||||||
|
("pending", "Pending!"),
|
||||||
|
("confirmed", "Confirmed!"),
|
||||||
|
)
|
||||||
|
|
||||||
|
Categories = (
|
||||||
|
("Bank", "Bank Transfer"),
|
||||||
|
("Utilities", "Bills & Utilities"),
|
||||||
|
("Transportation", "Auto & Transport"),
|
||||||
|
("Groceries", "Groceries"),
|
||||||
|
("Food", "Food"),
|
||||||
|
("Shopping", "Shopping"),
|
||||||
|
("Health", "Healthcare"),
|
||||||
|
("Education", "Education"),
|
||||||
|
("Travel", "Travel"),
|
||||||
|
("Housing", "Housing"),
|
||||||
|
("Entertainment", "Entertainment"),
|
||||||
|
("Others", "Others"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_uuid4():
|
||||||
|
return str(uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
def expiry_date_to_datetime(expiry_date):
|
||||||
|
"""Convert a credit card expiry date to a datetime object.
|
||||||
|
The datetime is the last day of the month.
|
||||||
|
"""
|
||||||
|
exp = datetime.strptime(expiry_date, "%m%y") # format: MMYY
|
||||||
|
# to find the next month
|
||||||
|
# - add 31 days (more than a month) to the first day of the current month
|
||||||
|
# - replace the day to be "1"
|
||||||
|
# - substract one day
|
||||||
|
exp += timedelta(days=31)
|
||||||
|
exp = exp.replace(day=1)
|
||||||
|
exp -= timedelta(days=1)
|
||||||
|
return exp
|
||||||
|
|
||||||
|
|
||||||
|
class Account(models.Model):
|
||||||
|
payment = models.OneToOneField(PaymentMethod, on_delete=models.CASCADE)
|
||||||
|
balance = models.FloatField(default=0.00)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Account: %s" % self.payment.user.username
|
||||||
|
|
||||||
|
def get_update_url(self):
|
||||||
|
return reverse("account_transfer", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
# ensure that the database only stores 2 decimal places
|
||||||
|
self.balance = round(self.balance, 2)
|
||||||
|
super(Account, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Bank(models.Model):
|
||||||
|
payment = models.OneToOneField(PaymentMethod, on_delete=models.CASCADE)
|
||||||
|
owner_first_name = models.CharField(max_length=255, default=None)
|
||||||
|
owner_last_name = models.CharField(max_length=255, default=None)
|
||||||
|
routing_number = models.CharField(max_length=9, default=None)
|
||||||
|
account_number = models.CharField(max_length=10, default=None)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Bank: ****%s" % self.account_number[5:]
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse("bank_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def get_update_url(self):
|
||||||
|
return reverse("bank_update", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def get_delete_url(self):
|
||||||
|
return reverse("bank_delete", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ("routing_number", "account_number")
|
||||||
|
|
||||||
|
|
||||||
|
class Card(models.Model):
|
||||||
|
payment = models.OneToOneField(PaymentMethod, on_delete=models.DO_NOTHING)
|
||||||
|
card_type = models.CharField(max_length=45, choices=Card_Type)
|
||||||
|
card_number = models.CharField(max_length=16, default=None)
|
||||||
|
owner_first_name = models.CharField(max_length=45)
|
||||||
|
owner_last_name = models.CharField(max_length=45)
|
||||||
|
security_code = models.CharField(max_length=3, default=None)
|
||||||
|
expiration_date = models.DateField(default=None)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse("card_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def get_update_url(self):
|
||||||
|
return reverse("card_update", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def get_delete_url(self):
|
||||||
|
return reverse("card_delete", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s Card: ************%s" % (self.card_type, self.card_number[12:])
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = (
|
||||||
|
"card_type",
|
||||||
|
"owner_first_name",
|
||||||
|
"owner_last_name",
|
||||||
|
"card_number",
|
||||||
|
"security_code",
|
||||||
|
"expiration_date",
|
||||||
|
)
|
||||||
|
ordering = ["card_type", "card_number"]
|
||||||
|
|
||||||
|
|
||||||
|
class Wallet(BaseModel):
|
||||||
|
owner = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
related_name="wallets",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
credit_cards = models.ManyToManyField(
|
||||||
|
BankCard,
|
||||||
|
)
|
||||||
|
credit = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
default="0",
|
||||||
|
)
|
||||||
|
card_expiry = models.DateTimeField(default=timezone.now, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("wallet")
|
||||||
|
verbose_name_plural = _("wallets")
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
"""Return True if the card expiry date is in the future."""
|
||||||
|
exp = expiry_date_to_datetime(self.card_expiry)
|
||||||
|
today = datetime.today()
|
||||||
|
return exp >= expiry_date_to_datetime(today.strftime("%m%y"))
|
||||||
|
|
||||||
|
def expires_this_month(self):
|
||||||
|
"""Return True if the card expiry date is in this current month."""
|
||||||
|
today = datetime.today().strftime("%m%y")
|
||||||
|
return today == self.card_expiry
|
||||||
|
|
||||||
|
def make_payment(self, amount):
|
||||||
|
"""Make a payment from this wallet."""
|
||||||
|
pp = DPSPayProcessor()
|
||||||
|
result, transaction, message = pp.make_wallet_payment(self.wallet_id, amount)
|
||||||
|
if result:
|
||||||
|
self.transaction_set.create(amount=amount, transaction_id=transaction)
|
||||||
|
return result, message
|
||||||
|
|
||||||
|
def deposit(self, value):
|
||||||
|
"""Deposits a value to the wallet.
|
||||||
|
Also creates a new transaction with the deposit
|
||||||
|
value.
|
||||||
|
"""
|
||||||
|
self.transaction_set.create(
|
||||||
|
value=value, running_balance=self.current_balance + value
|
||||||
|
)
|
||||||
|
self.current_balance += value
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def withdraw(self, value):
|
||||||
|
"""Withdraw's a value from the wallet.
|
||||||
|
Also creates a new transaction with the withdraw
|
||||||
|
value.
|
||||||
|
Should the withdrawn amount is greater than the
|
||||||
|
balance this wallet currently has, it raises an
|
||||||
|
:mod:`InsufficientBalance` error. This exception
|
||||||
|
inherits from :mod:`django.db.IntegrityError`. So
|
||||||
|
that it automatically rolls-back during a
|
||||||
|
transaction lifecycle.
|
||||||
|
"""
|
||||||
|
if value > self.current_balance:
|
||||||
|
raise InsufficientBalance("This wallet has insufficient balance.")
|
||||||
|
|
||||||
|
self.transaction_set.create(
|
||||||
|
value=-value, running_balance=self.current_balance - value
|
||||||
|
)
|
||||||
|
self.current_balance -= value
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def transfer(self, wallet, value):
|
||||||
|
"""Transfers an value to another wallet.
|
||||||
|
Uses `deposit` and `withdraw` internally.
|
||||||
|
"""
|
||||||
|
self.withdraw(value)
|
||||||
|
wallet.deposit(value)
|
||||||
|
|
||||||
|
|
||||||
|
class Shipping(BaseModel):
|
||||||
|
client_address = models.OneToOneField(Address, on_delete=models.CASCADE, related_name="client_address", null=True)
|
||||||
|
# supplier_address = models.OneToOneField(Address, related_name="supplier_address")
|
||||||
|
client = models.OneToOneField(User, on_delete=models.CASCADE, related_name="shipping_client", null=True)
|
||||||
|
supplier = models.OneToOneField(User, on_delete=models.CASCADE, related_name="shipping_supplier", null=True)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.supplier.username
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(Shipping, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction(BaseModel):
|
||||||
|
"""Payment."""
|
||||||
|
|
||||||
|
wallet = models.ForeignKey(
|
||||||
|
Wallet,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL, # do never ever delete
|
||||||
|
help_text=_("Wallet holding payment information"),
|
||||||
|
)
|
||||||
|
date = models.DateTimeField(
|
||||||
|
default=now, help_text=_("When the account was created")
|
||||||
|
)
|
||||||
|
amount = models.DecimalField(max_digits=12, decimal_places=5)
|
||||||
|
|
||||||
|
status = models.CharField(max_length=45, choices=states)
|
||||||
|
transaction_type = models.CharField(
|
||||||
|
max_length=45, choices=Transaction_Type, default=""
|
||||||
|
)
|
||||||
|
category = models.CharField(max_length=45, choices=Categories)
|
||||||
|
# amount = models.FloatField(default=0.00)
|
||||||
|
description = models.CharField(max_length=200, default=False)
|
||||||
|
create_date = models.DateTimeField(default=now, editable=False)
|
||||||
|
is_complete = models.BooleanField(default=False)
|
||||||
|
receiver = models.ForeignKey(
|
||||||
|
User, related_name="receiver", on_delete=models.PROTECT, default=""
|
||||||
|
)
|
||||||
|
creator = models.ForeignKey(
|
||||||
|
User, related_name="creator", on_delete=models.PROTECT, default=""
|
||||||
|
)
|
||||||
|
payment_method = models.ForeignKey(
|
||||||
|
PaymentMethod,
|
||||||
|
related_name="payment_method",
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
default="",
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_status(self):
|
||||||
|
return "status is: %c" % self.status
|
||||||
|
|
||||||
|
def set_status(self, state):
|
||||||
|
self.status = state
|
||||||
|
if state == "completed":
|
||||||
|
self.is_complete = True
|
||||||
|
return "Status %c has been set!"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse("staff_tran_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def get_delete_url(self):
|
||||||
|
return reverse("staff_tran_delete", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
# ensure that the database only stores 2 decimal places
|
||||||
|
self.amount = round(self.amount, 2)
|
||||||
|
super(Transaction, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.transaction_id)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ("-date",)
|
||||||
|
verbose_name = _("transaction")
|
||||||
|
verbose_name_plural = _("transactions")
|
||||||
|
|
||||||
|
|
||||||
|
class Factor(BaseModel):
|
||||||
|
transaction = models.ForeignKey(
|
||||||
|
Transaction,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL, # do never ever delete
|
||||||
|
help_text=_("Transactions"),
|
||||||
|
)
|
||||||
|
|
||||||
|
pass
|
||||||
147
Wallet/processor.py
Normal file
147
Wallet/processor.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from datetime import datetime
|
||||||
|
from logging import getLogger
|
||||||
|
from os import path
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from suds import WebFault
|
||||||
|
from suds.client import Client
|
||||||
|
|
||||||
|
|
||||||
|
logger = getLogger("payline")
|
||||||
|
|
||||||
|
|
||||||
|
class DPSPayProcessor(object):
|
||||||
|
"""Payline Payment Backend."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Instantiate suds client."""
|
||||||
|
here = path.abspath(path.dirname(__file__))
|
||||||
|
self.wsdl = getattr(
|
||||||
|
settings,
|
||||||
|
"PAYLINE_WSDL",
|
||||||
|
"file://%s" % path.join(here, "DirectPaymentAPI.wsdl"),
|
||||||
|
)
|
||||||
|
self.merchant_id = getattr(settings, "PAYLINE_MERCHANT_ID", "")
|
||||||
|
self.api_key = getattr(settings, "PAYLINE_KEY", "")
|
||||||
|
self.vad_number = getattr(settings, "PAYLINE_VADNBR", "")
|
||||||
|
self.client = Client(
|
||||||
|
url=self.wsdl, username=self.merchant_id, password=self.api_key
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_card(self, card_number, card_type, card_expiry, card_cvx):
|
||||||
|
"""Do an Authorization request to make sure the card is valid."""
|
||||||
|
minimum_amount = 100 # 1€ is the smallest amount authorized
|
||||||
|
payment = self.client.factory.create("ns1:payment")
|
||||||
|
payment.amount = minimum_amount
|
||||||
|
payment.currency = 978 # euros
|
||||||
|
payment.action = 100 # authorization only
|
||||||
|
payment.mode = "CPT" # CPT = comptant
|
||||||
|
payment.contractNumber = self.vad_number
|
||||||
|
order = self.client.factory.create("ns1:order")
|
||||||
|
order.ref = str(uuid4())
|
||||||
|
order.amount = minimum_amount
|
||||||
|
order.currency = 978
|
||||||
|
order.date = datetime.now().strftime("%d/%m/%Y %H:%M")
|
||||||
|
card = self.client.factory.create("ns1:card")
|
||||||
|
card.number = card_number
|
||||||
|
card.type = card_type
|
||||||
|
card.expirationDate = card_expiry
|
||||||
|
card.cvx = card_cvx
|
||||||
|
try:
|
||||||
|
res = self.client.service.doAuthorization(
|
||||||
|
payment=payment, order=order, card=card
|
||||||
|
)
|
||||||
|
except WebFault:
|
||||||
|
logger.error("Payment backend failure", exc_info=True)
|
||||||
|
return (False, None, _("Payment backend failure, please try again later."))
|
||||||
|
result = (
|
||||||
|
res.result.code == "00000", # success ?
|
||||||
|
res.result.shortMessage + ": " + res.result.longMessage,
|
||||||
|
)
|
||||||
|
if result[0]: # authorization was successful, now cancel it (clean up)
|
||||||
|
self.client.service.doReset(
|
||||||
|
transactionID=res.transaction.id, comment="Card validation cleanup"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def create_update_wallet(
|
||||||
|
self,
|
||||||
|
wallet_id,
|
||||||
|
last_name,
|
||||||
|
first_name,
|
||||||
|
card_number,
|
||||||
|
card_type,
|
||||||
|
card_expiry,
|
||||||
|
card_cvx,
|
||||||
|
create=True,
|
||||||
|
):
|
||||||
|
"""Create or update a customer wallet to hold payment information.
|
||||||
|
Return True if the creation or update was successful.
|
||||||
|
"""
|
||||||
|
wallet = self.client.factory.create("ns1:wallet")
|
||||||
|
wallet.walletId = wallet_id
|
||||||
|
wallet.lastName = last_name
|
||||||
|
wallet.firstName = first_name
|
||||||
|
wallet.card = self.client.factory.create("ns1:card")
|
||||||
|
wallet.card.number = card_number
|
||||||
|
wallet.card.type = card_type
|
||||||
|
wallet.card.expirationDate = card_expiry
|
||||||
|
wallet.card.cvx = card_cvx
|
||||||
|
service = self.client.service.createWallet
|
||||||
|
if not create:
|
||||||
|
service = self.client.service.updateWallet
|
||||||
|
try:
|
||||||
|
res = service(contractNumber=self.vad_number, wallet=wallet)
|
||||||
|
except:
|
||||||
|
logger.error("Payment backend failure", exc_info=True)
|
||||||
|
return (False, _("Payment backend failure, please try again later."))
|
||||||
|
return (
|
||||||
|
res.result.code == "02500", # success ?
|
||||||
|
res.result.shortMessage + ": " + res.result.longMessage,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_wallet(self, wallet_id):
|
||||||
|
"""Get wallet information from Payline."""
|
||||||
|
try:
|
||||||
|
res = self.client.service.getWallet(
|
||||||
|
contractNumber=self.vad_number, walletId=wallet_id
|
||||||
|
)
|
||||||
|
except WebFault:
|
||||||
|
logger.error("Payment backend failure", exc_info=True)
|
||||||
|
return (False, _("Payment backend failure, please try again later."))
|
||||||
|
return (
|
||||||
|
res.result.code == "02500", # success ?
|
||||||
|
getattr(res, "wallet", None), # None is needed because of suds
|
||||||
|
res.result.shortMessage + ": " + res.result.longMessage,
|
||||||
|
)
|
||||||
|
|
||||||
|
def make_wallet_payment(self, wallet_id, amount):
|
||||||
|
"""Make a payment from the given wallet."""
|
||||||
|
amount_cents = amount * 100 # use the smallest unit possible (cents)
|
||||||
|
payment = self.client.factory.create("ns1:payment")
|
||||||
|
payment.amount = amount_cents
|
||||||
|
payment.currency = 978 # euros
|
||||||
|
payment.action = 101 # authorization + validation = payment
|
||||||
|
payment.mode = "CPT" # CPT = comptant
|
||||||
|
payment.contractNumber = self.vad_number
|
||||||
|
order = self.client.factory.create("ns1:order")
|
||||||
|
order.ref = str(uuid4())
|
||||||
|
order.amount = amount_cents
|
||||||
|
order.currency = 978
|
||||||
|
order.date = datetime.now().strftime("%d/%m/%Y %H:%M")
|
||||||
|
try:
|
||||||
|
res = self.client.service.doImmediateWalletPayment(
|
||||||
|
payment=payment, order=order, walletId=wallet_id
|
||||||
|
)
|
||||||
|
except WebFault:
|
||||||
|
logger.error("Payment backend failure", exc_info=True)
|
||||||
|
return (False, None, _("Payment backend failure, please try again later."))
|
||||||
|
return (
|
||||||
|
res.result.code == "00000", # success ?
|
||||||
|
res.transaction.id,
|
||||||
|
res.result.shortMessage + ": " + res.result.longMessage,
|
||||||
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user