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