first push

This commit is contained in:
2026-01-18 12:05:56 +03:30
commit cdbb2e11ed
109 changed files with 3083 additions and 0 deletions

25
.env.local Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
# Created by .ignore support plugin (hsz.mobi)

8
.idea/.gitignore generated vendored Normal file
View 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
View 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="&lt;map/&gt;" />
<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
View 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>

View 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>

View 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
View 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
View 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
View 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
View 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
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
ArtaSystem/asgi.py Normal file
View 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
View 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
View 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
View 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()

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
Authentication/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
Authentication/apps.py Normal file
View File

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

View 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,
},
),
]

View File

41
Authentication/models.py Normal file
View 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)

View 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
View 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
View File

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

60
Authentication/urls.py Normal file
View 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
View 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
)

View File

Binary file not shown.

View 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
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
Core/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
Core/apps.py Normal file
View File

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

View File

Binary file not shown.

33
Core/models.py Normal file
View 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
View File

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

1
Core/views.py Normal file
View File

@@ -0,0 +1 @@
from django.shortcuts import render

59
Dockerfile Normal file
View 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
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
Notification/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
Notification/apps.py Normal file
View File

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

View 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,
},
),
]

View File

89
Notification/models.py Normal file
View 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

View File

Binary file not shown.

View 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)

View 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

View 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
View File

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

12
Notification/urls.py Normal file
View 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
View 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
View File

@@ -0,0 +1,2 @@
# ArtaSystemMain
Arta System Project For Main Server

0
Wallet/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
Wallet/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
Wallet/apps.py Normal file
View 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
View 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.
"""

View 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')},
},
),
]

View File

Binary file not shown.

404
Wallet/models.py Normal file
View 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
View 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