commit cdbb2e11edca04078a6a4e69376b91bcc653ac41 Author: mostafa7171 Date: Sun Jan 18 12:05:56 2026 +0330 first push diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..3ce9a73 --- /dev/null +++ b/.env.local @@ -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 \ No newline at end of file diff --git a/.env.prod b/.env.prod new file mode 100644 index 0000000..d37988a --- /dev/null +++ b/.env.prod @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfa6a22 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +# Created by .ignore support plugin (hsz.mobi) diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -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/ diff --git a/.idea/ArtaSystem.iml b/.idea/ArtaSystem.iml new file mode 100644 index 0000000..936bdcb --- /dev/null +++ b/.idea/ArtaSystem.iml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..8059b2c --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://31.7.78.133:14362/users + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..ff59c29 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,79 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..62a2f26 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4a68777 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..6df4889 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ArtaSystem/__init__.py b/ArtaSystem/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ArtaSystem/__pycache__/__init__.cpython-39.pyc b/ArtaSystem/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..5d5ec53 Binary files /dev/null and b/ArtaSystem/__pycache__/__init__.cpython-39.pyc differ diff --git a/ArtaSystem/__pycache__/settings.cpython-39.pyc b/ArtaSystem/__pycache__/settings.cpython-39.pyc new file mode 100644 index 0000000..60ead69 Binary files /dev/null and b/ArtaSystem/__pycache__/settings.cpython-39.pyc differ diff --git a/ArtaSystem/__pycache__/urls.cpython-39.pyc b/ArtaSystem/__pycache__/urls.cpython-39.pyc new file mode 100644 index 0000000..f176022 Binary files /dev/null and b/ArtaSystem/__pycache__/urls.cpython-39.pyc differ diff --git a/ArtaSystem/__pycache__/wsgi.cpython-39.pyc b/ArtaSystem/__pycache__/wsgi.cpython-39.pyc new file mode 100644 index 0000000..32c00ee Binary files /dev/null and b/ArtaSystem/__pycache__/wsgi.cpython-39.pyc differ diff --git a/ArtaSystem/asgi.py b/ArtaSystem/asgi.py new file mode 100644 index 0000000..6e34487 --- /dev/null +++ b/ArtaSystem/asgi.py @@ -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() diff --git a/ArtaSystem/settings.py b/ArtaSystem/settings.py new file mode 100644 index 0000000..296d0fe --- /dev/null +++ b/ArtaSystem/settings.py @@ -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") diff --git a/ArtaSystem/urls.py b/ArtaSystem/urls.py new file mode 100644 index 0000000..cd668ef --- /dev/null +++ b/ArtaSystem/urls.py @@ -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')), +] diff --git a/ArtaSystem/wsgi.py b/ArtaSystem/wsgi.py new file mode 100644 index 0000000..82cfc4a --- /dev/null +++ b/ArtaSystem/wsgi.py @@ -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() diff --git a/Authentication/__init__.py b/Authentication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication/__pycache__/__init__.cpython-39.pyc b/Authentication/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..24ac63a Binary files /dev/null and b/Authentication/__pycache__/__init__.cpython-39.pyc differ diff --git a/Authentication/__pycache__/admin.cpython-39.pyc b/Authentication/__pycache__/admin.cpython-39.pyc new file mode 100644 index 0000000..d7a255c Binary files /dev/null and b/Authentication/__pycache__/admin.cpython-39.pyc differ diff --git a/Authentication/__pycache__/apps.cpython-39.pyc b/Authentication/__pycache__/apps.cpython-39.pyc new file mode 100644 index 0000000..40e82ec Binary files /dev/null and b/Authentication/__pycache__/apps.cpython-39.pyc differ diff --git a/Authentication/__pycache__/models.cpython-39.pyc b/Authentication/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000..bfc43dd Binary files /dev/null and b/Authentication/__pycache__/models.cpython-39.pyc differ diff --git a/Authentication/__pycache__/serializers.cpython-39.pyc b/Authentication/__pycache__/serializers.cpython-39.pyc new file mode 100644 index 0000000..f25dc3c Binary files /dev/null and b/Authentication/__pycache__/serializers.cpython-39.pyc differ diff --git a/Authentication/__pycache__/sms.cpython-39.pyc b/Authentication/__pycache__/sms.cpython-39.pyc new file mode 100644 index 0000000..1b3607b Binary files /dev/null and b/Authentication/__pycache__/sms.cpython-39.pyc differ diff --git a/Authentication/__pycache__/urls.cpython-39.pyc b/Authentication/__pycache__/urls.cpython-39.pyc new file mode 100644 index 0000000..309daf1 Binary files /dev/null and b/Authentication/__pycache__/urls.cpython-39.pyc differ diff --git a/Authentication/__pycache__/views.cpython-39.pyc b/Authentication/__pycache__/views.cpython-39.pyc new file mode 100644 index 0000000..683ae95 Binary files /dev/null and b/Authentication/__pycache__/views.cpython-39.pyc differ diff --git a/Authentication/admin.py b/Authentication/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Authentication/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Authentication/apps.py b/Authentication/apps.py new file mode 100644 index 0000000..dcc48d9 --- /dev/null +++ b/Authentication/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthenticationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Authentication' diff --git a/Authentication/migrations/0001_initial.py b/Authentication/migrations/0001_initial.py new file mode 100644 index 0000000..5512586 --- /dev/null +++ b/Authentication/migrations/0001_initial.py @@ -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, + }, + ), + ] diff --git a/Authentication/migrations/__init__.py b/Authentication/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Authentication/migrations/__pycache__/0001_initial.cpython-39.pyc b/Authentication/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000..0391b8e Binary files /dev/null and b/Authentication/migrations/__pycache__/0001_initial.cpython-39.pyc differ diff --git a/Authentication/migrations/__pycache__/__init__.cpython-39.pyc b/Authentication/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..027790a Binary files /dev/null and b/Authentication/migrations/__pycache__/__init__.cpython-39.pyc differ diff --git a/Authentication/models.py b/Authentication/models.py new file mode 100644 index 0000000..47f4348 --- /dev/null +++ b/Authentication/models.py @@ -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) diff --git a/Authentication/serializers.py b/Authentication/serializers.py new file mode 100644 index 0000000..afc89c9 --- /dev/null +++ b/Authentication/serializers.py @@ -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}, } diff --git a/Authentication/sms.py b/Authentication/sms.py new file mode 100644 index 0000000..6b03801 --- /dev/null +++ b/Authentication/sms.py @@ -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) diff --git a/Authentication/tests.py b/Authentication/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Authentication/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Authentication/urls.py b/Authentication/urls.py new file mode 100644 index 0000000..629ec83 --- /dev/null +++ b/Authentication/urls.py @@ -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//', oauth2_views.ApplicationDetail.as_view(), name="detail"), + path('applications//delete/', oauth2_views.ApplicationDelete.as_view(), name="delete"), + path('applications//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//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), + +] diff --git a/Authentication/views.py b/Authentication/views.py new file mode 100644 index 0000000..b716cb3 --- /dev/null +++ b/Authentication/views.py @@ -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 + ) diff --git a/Core/ArvanStorage/__init__.py b/Core/ArvanStorage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Core/ArvanStorage/__pycache__/__init__.cpython-39.pyc b/Core/ArvanStorage/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..59ebda9 Binary files /dev/null and b/Core/ArvanStorage/__pycache__/__init__.cpython-39.pyc differ diff --git a/Core/ArvanStorage/__pycache__/arvan_storage.cpython-39.pyc b/Core/ArvanStorage/__pycache__/arvan_storage.cpython-39.pyc new file mode 100644 index 0000000..27e4ca5 Binary files /dev/null and b/Core/ArvanStorage/__pycache__/arvan_storage.cpython-39.pyc differ diff --git a/Core/ArvanStorage/arvan_storage.py b/Core/ArvanStorage/arvan_storage.py new file mode 100644 index 0000000..661b5ec --- /dev/null +++ b/Core/ArvanStorage/arvan_storage.py @@ -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 + ) diff --git a/Core/__init__.py b/Core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Core/__pycache__/__init__.cpython-39.pyc b/Core/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..7530536 Binary files /dev/null and b/Core/__pycache__/__init__.cpython-39.pyc differ diff --git a/Core/__pycache__/admin.cpython-39.pyc b/Core/__pycache__/admin.cpython-39.pyc new file mode 100644 index 0000000..f05d08a Binary files /dev/null and b/Core/__pycache__/admin.cpython-39.pyc differ diff --git a/Core/__pycache__/apps.cpython-39.pyc b/Core/__pycache__/apps.cpython-39.pyc new file mode 100644 index 0000000..82e2416 Binary files /dev/null and b/Core/__pycache__/apps.cpython-39.pyc differ diff --git a/Core/__pycache__/models.cpython-39.pyc b/Core/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000..571f99c Binary files /dev/null and b/Core/__pycache__/models.cpython-39.pyc differ diff --git a/Core/admin.py b/Core/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Core/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Core/apps.py b/Core/apps.py new file mode 100644 index 0000000..2e490ab --- /dev/null +++ b/Core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Core' diff --git a/Core/migrations/__init__.py b/Core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Core/migrations/__pycache__/__init__.cpython-39.pyc b/Core/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..88e5c90 Binary files /dev/null and b/Core/migrations/__pycache__/__init__.cpython-39.pyc differ diff --git a/Core/models.py b/Core/models.py new file mode 100644 index 0000000..b702e01 --- /dev/null +++ b/Core/models.py @@ -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 + diff --git a/Core/tests.py b/Core/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Core/views.py b/Core/views.py new file mode 100644 index 0000000..2536b37 --- /dev/null +++ b/Core/views.py @@ -0,0 +1 @@ +from django.shortcuts import render diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7b71086 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/Notification/__init__.py b/Notification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Notification/__pycache__/__init__.cpython-39.pyc b/Notification/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..d3e2b48 Binary files /dev/null and b/Notification/__pycache__/__init__.cpython-39.pyc differ diff --git a/Notification/__pycache__/admin.cpython-39.pyc b/Notification/__pycache__/admin.cpython-39.pyc new file mode 100644 index 0000000..8c7a378 Binary files /dev/null and b/Notification/__pycache__/admin.cpython-39.pyc differ diff --git a/Notification/__pycache__/apps.cpython-39.pyc b/Notification/__pycache__/apps.cpython-39.pyc new file mode 100644 index 0000000..d835718 Binary files /dev/null and b/Notification/__pycache__/apps.cpython-39.pyc differ diff --git a/Notification/__pycache__/models.cpython-39.pyc b/Notification/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000..52f4a5c Binary files /dev/null and b/Notification/__pycache__/models.cpython-39.pyc differ diff --git a/Notification/__pycache__/serializers.cpython-39.pyc b/Notification/__pycache__/serializers.cpython-39.pyc new file mode 100644 index 0000000..fa915ae Binary files /dev/null and b/Notification/__pycache__/serializers.cpython-39.pyc differ diff --git a/Notification/__pycache__/urls.cpython-39.pyc b/Notification/__pycache__/urls.cpython-39.pyc new file mode 100644 index 0000000..abf6e9e Binary files /dev/null and b/Notification/__pycache__/urls.cpython-39.pyc differ diff --git a/Notification/__pycache__/views.cpython-39.pyc b/Notification/__pycache__/views.cpython-39.pyc new file mode 100644 index 0000000..4498a1a Binary files /dev/null and b/Notification/__pycache__/views.cpython-39.pyc differ diff --git a/Notification/admin.py b/Notification/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Notification/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Notification/apps.py b/Notification/apps.py new file mode 100644 index 0000000..0afd4da --- /dev/null +++ b/Notification/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class NotificationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Notification' diff --git a/Notification/migrations/0001_initial.py b/Notification/migrations/0001_initial.py new file mode 100644 index 0000000..37e9eda --- /dev/null +++ b/Notification/migrations/0001_initial.py @@ -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, + }, + ), + ] diff --git a/Notification/migrations/__init__.py b/Notification/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Notification/migrations/__pycache__/0001_initial.cpython-39.pyc b/Notification/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000..98fa53e Binary files /dev/null and b/Notification/migrations/__pycache__/0001_initial.cpython-39.pyc differ diff --git a/Notification/migrations/__pycache__/__init__.cpython-39.pyc b/Notification/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..42d15d4 Binary files /dev/null and b/Notification/migrations/__pycache__/__init__.cpython-39.pyc differ diff --git a/Notification/models.py b/Notification/models.py new file mode 100644 index 0000000..06f07b3 --- /dev/null +++ b/Notification/models.py @@ -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 diff --git a/Notification/najva/__init__.py b/Notification/najva/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Notification/najva/__pycache__/__init__.cpython-39.pyc b/Notification/najva/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..496fce8 Binary files /dev/null and b/Notification/najva/__pycache__/__init__.cpython-39.pyc differ diff --git a/Notification/najva/__pycache__/send_notif_to_segments.cpython-39.pyc b/Notification/najva/__pycache__/send_notif_to_segments.cpython-39.pyc new file mode 100644 index 0000000..af6f5c0 Binary files /dev/null and b/Notification/najva/__pycache__/send_notif_to_segments.cpython-39.pyc differ diff --git a/Notification/najva/get_segments_detail.py b/Notification/najva/get_segments_detail.py new file mode 100644 index 0000000..56bcf9f --- /dev/null +++ b/Notification/najva/get_segments_detail.py @@ -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) diff --git a/Notification/najva/send_notif_to_segments.py b/Notification/najva/send_notif_to_segments.py new file mode 100644 index 0000000..671b742 --- /dev/null +++ b/Notification/najva/send_notif_to_segments.py @@ -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 diff --git a/Notification/serializers.py b/Notification/serializers.py new file mode 100644 index 0000000..4a6e3b1 --- /dev/null +++ b/Notification/serializers.py @@ -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__" diff --git a/Notification/tests.py b/Notification/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Notification/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Notification/urls.py b/Notification/urls.py new file mode 100644 index 0000000..499022a --- /dev/null +++ b/Notification/urls.py @@ -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)), +] diff --git a/Notification/views.py b/Notification/views.py new file mode 100644 index 0000000..6c4f60c --- /dev/null +++ b/Notification/views.py @@ -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) diff --git a/README.md b/README.md new file mode 100644 index 0000000..18f3c40 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ArtaSystemMain +Arta System Project For Main Server diff --git a/Wallet/__init__.py b/Wallet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wallet/__pycache__/__init__.cpython-39.pyc b/Wallet/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..2b8e222 Binary files /dev/null and b/Wallet/__pycache__/__init__.cpython-39.pyc differ diff --git a/Wallet/__pycache__/admin.cpython-39.pyc b/Wallet/__pycache__/admin.cpython-39.pyc new file mode 100644 index 0000000..eab5eb7 Binary files /dev/null and b/Wallet/__pycache__/admin.cpython-39.pyc differ diff --git a/Wallet/__pycache__/apps.cpython-39.pyc b/Wallet/__pycache__/apps.cpython-39.pyc new file mode 100644 index 0000000..fd4f5c4 Binary files /dev/null and b/Wallet/__pycache__/apps.cpython-39.pyc differ diff --git a/Wallet/__pycache__/errors.cpython-39.pyc b/Wallet/__pycache__/errors.cpython-39.pyc new file mode 100644 index 0000000..56e6da8 Binary files /dev/null and b/Wallet/__pycache__/errors.cpython-39.pyc differ diff --git a/Wallet/__pycache__/models.cpython-39.pyc b/Wallet/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000..6631eb4 Binary files /dev/null and b/Wallet/__pycache__/models.cpython-39.pyc differ diff --git a/Wallet/__pycache__/processor.cpython-39.pyc b/Wallet/__pycache__/processor.cpython-39.pyc new file mode 100644 index 0000000..d41484d Binary files /dev/null and b/Wallet/__pycache__/processor.cpython-39.pyc differ diff --git a/Wallet/admin.py b/Wallet/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/Wallet/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Wallet/apps.py b/Wallet/apps.py new file mode 100644 index 0000000..2845f18 --- /dev/null +++ b/Wallet/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WalletConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'Wallet' diff --git a/Wallet/errors.py b/Wallet/errors.py new file mode 100644 index 0000000..881c2c2 --- /dev/null +++ b/Wallet/errors.py @@ -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. + """ \ No newline at end of file diff --git a/Wallet/migrations/0001_initial.py b/Wallet/migrations/0001_initial.py new file mode 100644 index 0000000..b11cf3c --- /dev/null +++ b/Wallet/migrations/0001_initial.py @@ -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')}, + }, + ), + ] diff --git a/Wallet/migrations/__init__.py b/Wallet/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wallet/migrations/__pycache__/0001_initial.cpython-39.pyc b/Wallet/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000..7c964ab Binary files /dev/null and b/Wallet/migrations/__pycache__/0001_initial.cpython-39.pyc differ diff --git a/Wallet/migrations/__pycache__/__init__.cpython-39.pyc b/Wallet/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..60a5c38 Binary files /dev/null and b/Wallet/migrations/__pycache__/__init__.cpython-39.pyc differ diff --git a/Wallet/models.py b/Wallet/models.py new file mode 100644 index 0000000..a272ed7 --- /dev/null +++ b/Wallet/models.py @@ -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 diff --git a/Wallet/processor.py b/Wallet/processor.py new file mode 100644 index 0000000..d9ac7b3 --- /dev/null +++ b/Wallet/processor.py @@ -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, + ) diff --git a/Wallet/serializers/__init__.py b/Wallet/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wallet/serializers/customer.py b/Wallet/serializers/customer.py new file mode 100644 index 0000000..1f69204 --- /dev/null +++ b/Wallet/serializers/customer.py @@ -0,0 +1,29 @@ +from rest_framework import serializers +from rest_framework.response import Response + +from rest_framework_recursive.fields import RecursiveField +import os + +from Wallet.models import Transaction, Wallet + + +class WalletSerializer(serializers.ModelSerializer): + class Meta: + model = Wallet + fields = "__all__" + + + def create(self, validated_data): + w = Wallet.objects.create(**validated_data) + return w + + +class TransactionSerializer(serializers.ModelSerializer): + class Meta: + model = Transaction + fields = ["__all__"] + + + def create(self, validated_data): + t = Transaction.objects.create(**validated_data) + return t diff --git a/Wallet/tests.py b/Wallet/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Wallet/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Wallet/views.py b/Wallet/views.py new file mode 100644 index 0000000..218b4fd --- /dev/null +++ b/Wallet/views.py @@ -0,0 +1,133 @@ +from django.shortcuts import render + +from Wallet.models import Transaction, Wallet +from rest_framework import viewsets, generics, status, permissions + +# Create your views here. +from rest_framework.response import Response +from django.forms.models import model_to_dict + +from Wallet.serializers.customer import TransactionSerializer, WalletSerializer +from oauth2_provider.contrib.rest_framework import ( + TokenHasReadWriteScope, + OAuth2Authentication, +) +from oauth2_provider.models import AccessToken + + +class WalletViewSet(viewsets.ModelViewSet): + queryset = Wallet.objects.all() + serializer_class = WalletSerializer + permission_classes = [TokenHasReadWriteScope] + lookup_field = 'key' + lookup_url_kwarg = 'key' + + def list(self, request, *args, **kwargs): + if "key" in request.GET: + wallet_obj = Wallet.objects.get(key__exact=request.GET["key"]) + queryset = wallet_obj.category.all() + products = [x for x in queryset] + serializer = WalletSerializer(products, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + if "type" in request.GET: + wallet_obj = Wallet.objects.filter(user_id=request.user.id) + query = [x for x in wallet_obj] + serializer = WalletSerializer(query, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + queryset = Wallet.objects.all() + serializer = WalletSerializer(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request, *args, **kwargs): + wallet = Wallet.objects.get(key__exact=request.data["key"]) + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + w = serializer.create(validated_data=request.data) + + w.user = request.user + w.save() + + w_s = self.serializer_class(w) + return Response(w_s.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors) + + def retrieve(self, request, key=None, *args, **kwargs): + queryset = self.get_object() + serializer = self.serializer_class(queryset) + return Response(serializer.data, status=status.HTTP_200_OK) + + def update(self, request, pk=None, *args, **kwargs): + queryset = self.get_object() + + queryset.save() + serializer = self.serializer_class(queryset) + serializer.update(instance=queryset, validated_data=request.data) + return Response(serializer.data, status=status.HTTP_200_OK) + + def partial_update(self, request, pk=None, *args, **kwargs): + pass + + def destroy(self, request, pk=None, *args, **kwargs): + queryset = self.get_object() + queryset.trash = True + queryset.save() + return Response(status=status.HTTP_200_OK) + + +class TransactionViewSet(viewsets.ModelViewSet): + queryset = Transaction.objects.all() + serializer_class = TransactionSerializer + permission_classes = [TokenHasReadWriteScope] + + def list(self, request, *args, **kwargs): + if "key" in request.GET: + transaction_obj = Transaction.objects.get(key__exact=request.GET["key"]) + queryset = transaction_obj.category.all() + transactions = [x for x in queryset] + serializer = self.serializer_class(transactions, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + if "type" in request.GET: + transactions_obj = Transaction.objects.filter(user_id=request.user.id) + query = [x for x in transactions_obj] + serializer = self.serializer_class(query, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + queryset = Wallet.objects.all() + serializer = self.serializer_class(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request, *args, **kwargs): + transaction_obj = Transaction.objects.get(key__exact=request.data["key"]) + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + t = serializer.create(validated_data=request.data) + + t.user = request.user + t.save() + + t_s = self.serializer_class(t) + return Response(t_s.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors) + + def retrieve(self, request, pk=None, *args, **kwargs): + queryset = Transaction.objects.get(key__exact=request.GET["key"]) + serializer = self.serializer_class(queryset) + return Response(serializer.data, status=status.HTTP_200_OK) + + def update(self, request, pk=None, *args, **kwargs): + queryset = Transaction.objects.get(key__exact=request.GET["key"]) + + queryset.save() + serializer = self.serializer_class(queryset) + serializer.update(instance=queryset, validated_data=request.data) + return Response(serializer.data, status=status.HTTP_200_OK) + + def partial_update(self, request, pk=None, *args, **kwargs): + pass + + def destroy(self, request, pk=None, *args, **kwargs): + queryset = Transaction.objects.get(key__exact=request.GET["wallet_id"]) + queryset.trash = True + queryset.save() + return Response(status=status.HTTP_200_OK) diff --git a/__pycache__/manage.cpython-39.pyc b/__pycache__/manage.cpython-39.pyc new file mode 100644 index 0000000..ea1fb0a Binary files /dev/null and b/__pycache__/manage.cpython-39.pyc differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..57fabf6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +services: + web: + build: ./ + volumes: + - .:/app + ports: + - "8000:8000" \ No newline at end of file diff --git a/liara.json b/liara.json new file mode 100644 index 0000000..12cba34 --- /dev/null +++ b/liara.json @@ -0,0 +1,7 @@ +{ + + "django": { + "pythonVersion": "3.9" + }, + "app":"rasadyaarbackend" +} \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..19ca81d --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ArtaSystem.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..922371e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,91 @@ +aiohttp==3.8.4 +aiosignal==1.3.1 +amqp==5.1.1 +asgiref==3.4.1 +async-timeout==4.0.2 +attrs==23.1.0 +billiard==3.6.4.0 +boto3==1.22.2 +botocore==1.25.2 +cached-property==1.5.2 +celery==5.2.7 +certifi==2021.10.8 +cffi==1.15.0 +charset-normalizer==2.0.12 +click==8.1.3 +click-didyoumean==0.3.0 +click-plugins==1.1.1 +click-repl==0.2.0 +crypto==1.4.1 +cryptocode==0.1 +cryptography==36.0.2 +Deprecated==1.2.13 +Django==3.2.13 +django-cors-headers==3.10.1 +django-debug-toolbar==3.2.4 +django-environ==0.9.0 +django-extensions==3.1.5 +django-filter==21.1 +django-jalali==5.1.0 +django-oauth-toolkit==1.7.1 +django-oauth2-provider==0.2.6.1 +django-redis==5.2.0 +django-split-settings==1.2.0 +django-url-filter==0.3.15 +djangorestframework==3.13.1 +djangorestframework-recursive==0.1.2 +docopt==0.6.2 +enum-compat==0.0.3 +et-xmlfile==1.1.0 +frozenlist==1.3.3 +gunicorn==20.1.0 +idna==3.3 +importlib-metadata==4.8.3 +jdatetime==3.8.2 +jmespath==0.10.0 +jwcrypto==1.0 +kombu==5.2.4 +multidict==6.0.4 +Naked==0.1.31 +num2fawords==1.1 +numpy==1.19.5 +oauth2-provider==0.0 +oauthlib==3.2.0 +openpyxl==3.1.2 +packaging==21.3 +pandas==1.1.5 +Pillow==9.2.0 +prompt-toolkit==3.0.38 +psycopg2==2.9.3 +psycopg2-binary==2.9.3 +pyasn1==0.4.8 +pycparser==2.21 +pycryptodomex==3.14.1 +pyOpenSSL==22.0.0 +pyparsing==3.0.8 +python-dateutil==2.8.2 +pytz==2022.1 +PyYAML==6.0 +python-dotenv +redis==4.2.2 +requests==2.27.1 +rsa==4.8 +s3transfer==0.5.2 +schedule==1.1.0 +semantic-version==2.9.0 +setuptools-rust==1.1.2 +shellescape==3.8.1 +shortuuid==1.0.8 +six==1.16.0 +sqlparse==0.4.2 +suds==1.1.1 +tablib==3.2.1 +typing-extensions==4.1.1 +urllib3==1.26.9 +vine==5.0.0 +wcwidth==0.2.6 +wrapt==1.14.0 +xlrd==2.0.1 +XlsxWriter==3.0.3 +yarl==1.9.2 +zipp==3.6.0 \ No newline at end of file