From 3e4574dcdd623273e60d63d148cd071f32e531c2 Mon Sep 17 00:00:00 2001 From: javad Date: Mon, 15 Jun 2026 10:57:28 +0330 Subject: [PATCH] first commit --- .gitignore | 8 + apps/__init__.py | 0 apps/appsetting/__init__.py | 0 apps/appsetting/admin.py | 10 + apps/appsetting/apps.py | 6 + apps/appsetting/migrations/0001_initial.py | 30 +++ apps/appsetting/migrations/__init__.py | 0 apps/appsetting/models.py | 13 ++ apps/appsetting/selector.py | 13 ++ apps/appsetting/tests.py | 3 + apps/appsetting/urls.py | 5 + apps/appsetting/views.py | 70 +++++++ apps/cuser/__init__.py | 0 apps/cuser/admin.py | 10 + apps/cuser/apps.py | 6 + apps/cuser/migrations/0001_initial.py | 40 ++++ .../migrations/0002_alter_cuser_mobile.py | 18 ++ .../migrations/0003_cuser_mojri_status.py | 18 ++ apps/cuser/migrations/__init__.py | 0 apps/cuser/models.py | 63 ++++++ apps/cuser/mojri_urls.py | 6 + apps/cuser/register_urls.py | 4 + apps/cuser/services.py | 0 apps/cuser/views.py | 78 +++++++ apps/dgcode/__init__.py | 0 apps/dgcode/admin.py | 14 ++ apps/dgcode/apps.py | 6 + apps/dgcode/migrations/0001_initial.py | 48 +++++ apps/dgcode/migrations/__init__.py | 0 apps/dgcode/models.py | 26 +++ apps/dgcode/tests.py | 3 + apps/dgcode/views.py | 3 + apps/emtiyazat/__init__.py | 0 apps/emtiyazat/admin.py | 13 ++ apps/emtiyazat/apps.py | 6 + apps/emtiyazat/migrations/0001_initial.py | 33 +++ .../migrations/0002_emtiyazat_project.py | 20 ++ .../0003_alter_emtiyazat_options.py | 17 ++ .../migrations/0004_arzeshemtiyaz.py | 25 +++ apps/emtiyazat/migrations/__init__.py | 0 apps/emtiyazat/models.py | 26 +++ apps/emtiyazat/selector.py | 20 ++ apps/emtiyazat/services.py | 31 +++ apps/emtiyazat/tests.py | 5 + apps/emtiyazat/urls.py | 8 + apps/emtiyazat/views.py | 38 ++++ apps/op_panel/__init__.py | 0 apps/op_panel/order_view.py | 106 ++++++++++ apps/op_panel/selector.py | 31 +++ apps/op_panel/services.py | 33 +++ apps/op_panel/urls.py | 15 ++ apps/op_panel/views.py | 111 ++++++++++ apps/order/__init__.py | 0 apps/order/admin.py | 9 + apps/order/apps.py | 6 + apps/order/migrations/0001_initial.py | 35 ++++ apps/order/migrations/0002_order_code.py | 18 ++ apps/order/migrations/0003_order_status.py | 18 ++ .../migrations/0004_alter_order_options.py | 17 ++ apps/order/migrations/__init__.py | 0 apps/order/models.py | 40 ++++ apps/order/selector.py | 12 ++ apps/order/services.py | 46 +++++ apps/order/tests.py | 3 + apps/order/urls.py | 8 + apps/order/views.py | 88 ++++++++ apps/product/__init__.py | 0 apps/product/admin.py | 14 ++ apps/product/apps.py | 6 + apps/product/migrations/0001_initial.py | 38 ++++ apps/product/migrations/__init__.py | 0 apps/product/models.py | 32 +++ apps/product/selector.py | 5 + apps/product/services.py | 0 apps/product/urls.py | 7 + apps/product/views.py | 31 +++ apps/project/__init__.py | 0 apps/project/admin.py | 13 ++ apps/project/apps.py | 6 + apps/project/migrations/0001_initial.py | 50 +++++ .../0002_alter_project_factor_price.py | 18 ++ ...03_alter_project_options_project_status.py | 22 ++ .../migrations/0004_project_op_cuser.py | 21 ++ ...oject_status_alter_projectimage_project.py | 24 +++ .../migrations/0006_alter_project_cuser.py | 21 ++ ..._project_vahed_project_zirbana_and_more.py | 33 +++ ...t_tabaghat_alter_project_vahed_and_more.py | 28 +++ .../0009_alter_projectimage_image.py | 18 ++ .../migrations/0010_project_emtiyaz.py | 18 ++ .../migrations/0011_project_projectcode.py | 20 ++ apps/project/migrations/__init__.py | 0 apps/project/models.py | 44 ++++ apps/project/selector.py | 20 ++ apps/project/services.py | 52 +++++ apps/project/tests.py | 3 + apps/project/urls.py | 12 ++ apps/project/views.py | 159 ++++++++++++++ apps/projectcode/__init__.py | 0 apps/projectcode/admin.py | 13 ++ apps/projectcode/apps.py | 6 + apps/projectcode/migrations/0001_initial.py | 46 +++++ .../0002_alter_projectcode_mojri_user.py | 21 ++ ...0003_alter_projectcode_options_and_more.py | 21 ++ apps/projectcode/migrations/__init__.py | 0 apps/projectcode/models.py | 30 +++ apps/projectcode/selector.py | 43 ++++ apps/projectcode/services.py | 23 +++ apps/projectcode/task.py | 28 +++ apps/projectcode/tests.py | 3 + apps/projectcode/urls.py | 11 + apps/projectcode/views.py | 89 ++++++++ apps/user_auth/__init__.py | 0 apps/user_auth/admin.py | 14 ++ apps/user_auth/apps.py | 6 + apps/user_auth/auth_service.py | 64 ++++++ apps/user_auth/migrations/0001_initial.py | 47 +++++ apps/user_auth/migrations/__init__.py | 0 apps/user_auth/models.py | 27 +++ apps/user_auth/services.py | 91 ++++++++ apps/user_auth/tests.py | 3 + apps/user_auth/token_service.py | 48 +++++ apps/user_auth/urls.py | 11 + apps/user_auth/views.py | 81 ++++++++ apps/userprofile/__init__.py | 0 apps/userprofile/admin.py | 10 + apps/userprofile/apps.py | 6 + apps/userprofile/migrations/0001_initial.py | 33 +++ apps/userprofile/migrations/__init__.py | 0 apps/userprofile/models.py | 21 ++ apps/userprofile/selector.py | 11 + apps/userprofile/services.py | 36 ++++ apps/userprofile/tests.py | 3 + apps/userprofile/urls.py | 6 + apps/userprofile/views.py | 114 ++++++++++ asgar | 1 + config/__init__.py | 5 + config/asgi.py | 16 ++ config/celery.py | 22 ++ config/settings/__init__.py | 7 + config/settings/base.py | 194 ++++++++++++++++++ config/settings/local.py | 19 ++ config/settings/production.py | 42 ++++ config/urls.py | 41 ++++ config/wsgi.py | 16 ++ javad | 1 + kaftar.txt | 1 + liara.json | 10 + liara_nginx.conf | 29 +++ manage.py | 22 ++ mohammad | 1 + product/logo-looleh2.png | Bin 0 -> 14866 bytes requirements.txt | 19 ++ salam | 1 + supervisor.conf | 12 ++ utils/__init__.py | 0 utils/base_model.py | 9 + utils/order_pagination.py | 6 + utils/services/__init__.py | 0 utils/services/sms.py | 32 +++ utils/services/sms_task.py | 8 + utils/standard_pagination.py | 7 + utils/tools/__init__.py | 0 utils/tools/inline_serializer.py | 18 ++ 163 files changed, 3559 insertions(+) create mode 100644 .gitignore create mode 100644 apps/__init__.py create mode 100644 apps/appsetting/__init__.py create mode 100644 apps/appsetting/admin.py create mode 100644 apps/appsetting/apps.py create mode 100644 apps/appsetting/migrations/0001_initial.py create mode 100644 apps/appsetting/migrations/__init__.py create mode 100644 apps/appsetting/models.py create mode 100644 apps/appsetting/selector.py create mode 100644 apps/appsetting/tests.py create mode 100644 apps/appsetting/urls.py create mode 100644 apps/appsetting/views.py create mode 100644 apps/cuser/__init__.py create mode 100644 apps/cuser/admin.py create mode 100644 apps/cuser/apps.py create mode 100644 apps/cuser/migrations/0001_initial.py create mode 100644 apps/cuser/migrations/0002_alter_cuser_mobile.py create mode 100644 apps/cuser/migrations/0003_cuser_mojri_status.py create mode 100644 apps/cuser/migrations/__init__.py create mode 100644 apps/cuser/models.py create mode 100644 apps/cuser/mojri_urls.py create mode 100644 apps/cuser/register_urls.py create mode 100644 apps/cuser/services.py create mode 100644 apps/cuser/views.py create mode 100644 apps/dgcode/__init__.py create mode 100644 apps/dgcode/admin.py create mode 100644 apps/dgcode/apps.py create mode 100644 apps/dgcode/migrations/0001_initial.py create mode 100644 apps/dgcode/migrations/__init__.py create mode 100644 apps/dgcode/models.py create mode 100644 apps/dgcode/tests.py create mode 100644 apps/dgcode/views.py create mode 100644 apps/emtiyazat/__init__.py create mode 100644 apps/emtiyazat/admin.py create mode 100644 apps/emtiyazat/apps.py create mode 100644 apps/emtiyazat/migrations/0001_initial.py create mode 100644 apps/emtiyazat/migrations/0002_emtiyazat_project.py create mode 100644 apps/emtiyazat/migrations/0003_alter_emtiyazat_options.py create mode 100644 apps/emtiyazat/migrations/0004_arzeshemtiyaz.py create mode 100644 apps/emtiyazat/migrations/__init__.py create mode 100644 apps/emtiyazat/models.py create mode 100644 apps/emtiyazat/selector.py create mode 100644 apps/emtiyazat/services.py create mode 100644 apps/emtiyazat/tests.py create mode 100644 apps/emtiyazat/urls.py create mode 100644 apps/emtiyazat/views.py create mode 100644 apps/op_panel/__init__.py create mode 100644 apps/op_panel/order_view.py create mode 100644 apps/op_panel/selector.py create mode 100644 apps/op_panel/services.py create mode 100644 apps/op_panel/urls.py create mode 100644 apps/op_panel/views.py create mode 100644 apps/order/__init__.py create mode 100644 apps/order/admin.py create mode 100644 apps/order/apps.py create mode 100644 apps/order/migrations/0001_initial.py create mode 100644 apps/order/migrations/0002_order_code.py create mode 100644 apps/order/migrations/0003_order_status.py create mode 100644 apps/order/migrations/0004_alter_order_options.py create mode 100644 apps/order/migrations/__init__.py create mode 100644 apps/order/models.py create mode 100644 apps/order/selector.py create mode 100644 apps/order/services.py create mode 100644 apps/order/tests.py create mode 100644 apps/order/urls.py create mode 100644 apps/order/views.py create mode 100644 apps/product/__init__.py create mode 100644 apps/product/admin.py create mode 100644 apps/product/apps.py create mode 100644 apps/product/migrations/0001_initial.py create mode 100644 apps/product/migrations/__init__.py create mode 100644 apps/product/models.py create mode 100644 apps/product/selector.py create mode 100644 apps/product/services.py create mode 100644 apps/product/urls.py create mode 100644 apps/product/views.py create mode 100644 apps/project/__init__.py create mode 100644 apps/project/admin.py create mode 100644 apps/project/apps.py create mode 100644 apps/project/migrations/0001_initial.py create mode 100644 apps/project/migrations/0002_alter_project_factor_price.py create mode 100644 apps/project/migrations/0003_alter_project_options_project_status.py create mode 100644 apps/project/migrations/0004_project_op_cuser.py create mode 100644 apps/project/migrations/0005_alter_project_status_alter_projectimage_project.py create mode 100644 apps/project/migrations/0006_alter_project_cuser.py create mode 100644 apps/project/migrations/0007_project_tabaghat_project_vahed_project_zirbana_and_more.py create mode 100644 apps/project/migrations/0008_alter_project_tabaghat_alter_project_vahed_and_more.py create mode 100644 apps/project/migrations/0009_alter_projectimage_image.py create mode 100644 apps/project/migrations/0010_project_emtiyaz.py create mode 100644 apps/project/migrations/0011_project_projectcode.py create mode 100644 apps/project/migrations/__init__.py create mode 100644 apps/project/models.py create mode 100644 apps/project/selector.py create mode 100644 apps/project/services.py create mode 100644 apps/project/tests.py create mode 100644 apps/project/urls.py create mode 100644 apps/project/views.py create mode 100644 apps/projectcode/__init__.py create mode 100644 apps/projectcode/admin.py create mode 100644 apps/projectcode/apps.py create mode 100644 apps/projectcode/migrations/0001_initial.py create mode 100644 apps/projectcode/migrations/0002_alter_projectcode_mojri_user.py create mode 100644 apps/projectcode/migrations/0003_alter_projectcode_options_and_more.py create mode 100644 apps/projectcode/migrations/__init__.py create mode 100644 apps/projectcode/models.py create mode 100644 apps/projectcode/selector.py create mode 100644 apps/projectcode/services.py create mode 100644 apps/projectcode/task.py create mode 100644 apps/projectcode/tests.py create mode 100644 apps/projectcode/urls.py create mode 100644 apps/projectcode/views.py create mode 100644 apps/user_auth/__init__.py create mode 100644 apps/user_auth/admin.py create mode 100644 apps/user_auth/apps.py create mode 100644 apps/user_auth/auth_service.py create mode 100644 apps/user_auth/migrations/0001_initial.py create mode 100644 apps/user_auth/migrations/__init__.py create mode 100644 apps/user_auth/models.py create mode 100644 apps/user_auth/services.py create mode 100644 apps/user_auth/tests.py create mode 100644 apps/user_auth/token_service.py create mode 100644 apps/user_auth/urls.py create mode 100644 apps/user_auth/views.py create mode 100644 apps/userprofile/__init__.py create mode 100644 apps/userprofile/admin.py create mode 100644 apps/userprofile/apps.py create mode 100644 apps/userprofile/migrations/0001_initial.py create mode 100644 apps/userprofile/migrations/__init__.py create mode 100644 apps/userprofile/models.py create mode 100644 apps/userprofile/selector.py create mode 100644 apps/userprofile/services.py create mode 100644 apps/userprofile/tests.py create mode 100644 apps/userprofile/urls.py create mode 100644 apps/userprofile/views.py create mode 100644 asgar create mode 100644 config/__init__.py create mode 100644 config/asgi.py create mode 100644 config/celery.py create mode 100644 config/settings/__init__.py create mode 100644 config/settings/base.py create mode 100644 config/settings/local.py create mode 100644 config/settings/production.py create mode 100644 config/urls.py create mode 100644 config/wsgi.py create mode 100644 javad create mode 100644 kaftar.txt create mode 100644 liara.json create mode 100644 liara_nginx.conf create mode 100644 manage.py create mode 100644 mohammad create mode 100644 product/logo-looleh2.png create mode 100644 requirements.txt create mode 100644 salam create mode 100644 supervisor.conf create mode 100644 utils/__init__.py create mode 100644 utils/base_model.py create mode 100644 utils/order_pagination.py create mode 100644 utils/services/__init__.py create mode 100644 utils/services/sms.py create mode 100644 utils/services/sms_task.py create mode 100644 utils/standard_pagination.py create mode 100644 utils/tools/__init__.py create mode 100644 utils/tools/inline_serializer.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6483271 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea +media/* +config/media/* +*.xlsx +__pycache__ +*.pyc +*.sqlite3 +*.zip \ No newline at end of file diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/appsetting/__init__.py b/apps/appsetting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/appsetting/admin.py b/apps/appsetting/admin.py new file mode 100644 index 0000000..bd3d601 --- /dev/null +++ b/apps/appsetting/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from .models import City as CityModel +# Register your models here. + + +@admin.register(CityModel) +class CityAdmin(admin.ModelAdmin): + list_display = ['name', 'is_ostan', 'ostan', 'pv_id'] + search_fields = ['name',] + list_filter = ['name', 'is_ostan', 'ostan',] diff --git a/apps/appsetting/apps.py b/apps/appsetting/apps.py new file mode 100644 index 0000000..53197b2 --- /dev/null +++ b/apps/appsetting/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AppsettingConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.appsetting' diff --git a/apps/appsetting/migrations/0001_initial.py b/apps/appsetting/migrations/0001_initial.py new file mode 100644 index 0000000..a675f91 --- /dev/null +++ b/apps/appsetting/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.1 on 2024-03-27 13:43 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='City', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(blank=True, max_length=30, null=True)), + ('is_ostan', models.BooleanField(default=False)), + ('pv_id', models.PositiveSmallIntegerField(blank=True, null=True)), + ('ostan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='appsetting.city')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/appsetting/migrations/__init__.py b/apps/appsetting/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/appsetting/models.py b/apps/appsetting/models.py new file mode 100644 index 0000000..fcee625 --- /dev/null +++ b/apps/appsetting/models.py @@ -0,0 +1,13 @@ +from django.db import models +from utils.base_model import BaseModel +# Create your models here. + + +class City(BaseModel): + name = models.CharField(max_length=30, null=True, blank=True) + is_ostan = models.BooleanField(default=False, null=False, blank=False) + ostan = models.ForeignKey('self', models.CASCADE, null=True, blank=True) + pv_id = models.PositiveSmallIntegerField(null=True, blank=True) + + def __str__(self): + return str(self.name) diff --git a/apps/appsetting/selector.py b/apps/appsetting/selector.py new file mode 100644 index 0000000..0154ca7 --- /dev/null +++ b/apps/appsetting/selector.py @@ -0,0 +1,13 @@ +from apps.appsetting.models import City +from django.core.cache import cache + + +def get_city_list(): + city = cache.get('city') + + if city is None: + city = City.objects.all() + # 75 hour cache + cache.set('city', city, 60 * 60 * 72) + + return city diff --git a/apps/appsetting/tests.py b/apps/appsetting/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/appsetting/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/appsetting/urls.py b/apps/appsetting/urls.py new file mode 100644 index 0000000..4a45cfd --- /dev/null +++ b/apps/appsetting/urls.py @@ -0,0 +1,5 @@ +from django.urls import path +from apps.appsetting.views import CityListView +urlpatterns = [ + path('citys/', CityListView.as_view(), name='citys'), +] \ No newline at end of file diff --git a/apps/appsetting/views.py b/apps/appsetting/views.py new file mode 100644 index 0000000..2534359 --- /dev/null +++ b/apps/appsetting/views.py @@ -0,0 +1,70 @@ +from django.shortcuts import render +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import serializers +import pandas as pd +from apps.appsetting.models import City as CityModel +from apps.appsetting.selector import get_city_list +# Create your views here. + + +class CityListView(APIView): + class OutputSerializer(serializers.ModelSerializer): + class Meta: + model = CityModel + fields = '__all__' + + def get(self, request): + city_list = get_city_list() + output = self.OutputSerializer(city_list, many=True) + return Response(data=output.data) + + +class ImportExcelOstanView(APIView): + + def get(self, request): + + df = pd.read_excel('ostan.xlsx') + for index, row in df.iterrows(): + ostan_name = row['name'] + pv_id = row['pv_id'] + + try: + ostan = CityModel.objects.filter(pv_id=pv_id, is_ostan=True).exists() + except Exception as exp: + print(ostan_name) + print(pv_id) + + if not ostan: + ostan_obj = CityModel() + ostan_obj.name = ostan_name + ostan_obj.pv_id = pv_id + ostan_obj.is_ostan = True + ostan_obj.save() + + return Response(data={"result": 'ok'}) + + +class ImportExcelCitysView(APIView): + + def get(self, request): + + + df = pd.read_excel('cities.xlsx') + for index, row in df.iterrows(): + city_name = row['name'] + pv_id = row['province_id'] + + try: + ostan = CityModel.objects.get(pv_id=pv_id, is_ostan=True) + except Exception as exp: + print(city_name) + print(pv_id) + + city_obj = CityModel() + city_obj.name = city_name + city_obj.is_ostan = False + city_obj.ostan = ostan + city_obj.save() + + return Response(data={"result": 'ok'}) \ No newline at end of file diff --git a/apps/cuser/__init__.py b/apps/cuser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cuser/admin.py b/apps/cuser/admin.py new file mode 100644 index 0000000..5a31da0 --- /dev/null +++ b/apps/cuser/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from import_export.admin import ImportExportModelAdmin +from .models import CUser +# Register your models here. + + +@admin.register(CUser) +class CUserAdmin(ImportExportModelAdmin): + list_filter = ['step', 'type'] + search_fields = ['username', 'mobile', 'name_family', 'name_family'] \ No newline at end of file diff --git a/apps/cuser/apps.py b/apps/cuser/apps.py new file mode 100644 index 0000000..445a3c0 --- /dev/null +++ b/apps/cuser/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CuserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.cuser' diff --git a/apps/cuser/migrations/0001_initial.py b/apps/cuser/migrations/0001_initial.py new file mode 100644 index 0000000..2225f5c --- /dev/null +++ b/apps/cuser/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# Generated by Django 4.2.1 on 2024-03-27 10:12 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='CUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('username', models.CharField(max_length=50, unique=True)), + ('mobile', models.PositiveBigIntegerField(unique=True)), + ('name_family', models.CharField(blank=True, max_length=50, null=True)), + ('is_active', models.BooleanField(default=False)), + ('is_staff', models.BooleanField(default=False)), + ('is_superuser', models.BooleanField(default=False)), + ('step', models.CharField(choices=[('1', '1-register'), ('2', '2-profileOk')], default='1')), + ('type', models.CharField(choices=[('MO', 'mojri'), ('OP', 'operator')], default='MO')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/cuser/migrations/0002_alter_cuser_mobile.py b/apps/cuser/migrations/0002_alter_cuser_mobile.py new file mode 100644 index 0000000..ca9cc1d --- /dev/null +++ b/apps/cuser/migrations/0002_alter_cuser_mobile.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.1 on 2024-03-27 12:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cuser', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='cuser', + name='mobile', + field=models.PositiveBigIntegerField(blank=True, null=True, unique=True), + ), + ] diff --git a/apps/cuser/migrations/0003_cuser_mojri_status.py b/apps/cuser/migrations/0003_cuser_mojri_status.py new file mode 100644 index 0000000..a9baf16 --- /dev/null +++ b/apps/cuser/migrations/0003_cuser_mojri_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.1 on 2024-04-20 11:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cuser', '0002_alter_cuser_mobile'), + ] + + operations = [ + migrations.AddField( + model_name='cuser', + name='mojri_status', + field=models.CharField(blank=True, choices=[('WT', 'waiting'), ('AP', 'approv'), ('RJ', 'reject')], default='WT', null=True), + ), + ] diff --git a/apps/cuser/migrations/__init__.py b/apps/cuser/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cuser/models.py b/apps/cuser/models.py new file mode 100644 index 0000000..32acbea --- /dev/null +++ b/apps/cuser/models.py @@ -0,0 +1,63 @@ +from django.db import models +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin +from utils.base_model import BaseModel +import uuid +# Create your models here. + + +class CUserManager(BaseUserManager): + def create_user(self, username, password, **extra_fields): + if not username: + raise ValueError("Please Enter username") + user = self.model(username=username, **extra_fields) + user.set_password(password) + user.save() + return user + + def create_superuser(self, username, password, **extra_fields): + extra_fields.setdefault('is_staff', True) + extra_fields.setdefault('is_superuser', True) + extra_fields.setdefault('is_active', True) + + return self.create_user(username, password, **extra_fields) + + +class CUser(BaseModel, AbstractBaseUser, PermissionsMixin): + + STEP_CHOICES = [ + ("1", "1-register"), + ("2", "2-profileOk") + ] + + TYPE_CHOICES = [ + ("MO", "mojri"), + ("OP", "operator") + ] + + STATUS_CHOICES = [ + ("WT", "waiting"), + ("AP", "approv"), + ("RJ", "reject"), + ] + + username = models.CharField(max_length=50, null=False, blank=False, unique=True) + mobile = models.PositiveBigIntegerField(null=True, blank=True, unique=True) + name_family = models.CharField(max_length=50, null=True, blank=True) + is_active = models.BooleanField(default=False) + is_staff = models.BooleanField(default=False) + is_superuser = models.BooleanField(default=False) + step = models.CharField(default='1', null=False, blank=False, choices=STEP_CHOICES) + type = models.CharField(default='MO', null=False, blank=False, choices=TYPE_CHOICES) + mojri_status = models.CharField(default='WT', null=True, blank=True, choices=STATUS_CHOICES) + uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) + + USERNAME_FIELD = 'username' + + objects = CUserManager() + + def __str__(self): + return f'{self.name_family} - {self.username} - {self.mobile} - {self.type}' + + class Meta: + ordering = ['-created_at'] + \ No newline at end of file diff --git a/apps/cuser/mojri_urls.py b/apps/cuser/mojri_urls.py new file mode 100644 index 0000000..620127a --- /dev/null +++ b/apps/cuser/mojri_urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from apps.cuser.views import MojriInfoApiview + +urlpatterns = [ + path('info/', MojriInfoApiview.as_view(), name='mojri-info'), +] diff --git a/apps/cuser/register_urls.py b/apps/cuser/register_urls.py new file mode 100644 index 0000000..65139db --- /dev/null +++ b/apps/cuser/register_urls.py @@ -0,0 +1,4 @@ +from django.urls import path + +urlpatterns = [ +] \ No newline at end of file diff --git a/apps/cuser/services.py b/apps/cuser/services.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cuser/views.py b/apps/cuser/views.py new file mode 100644 index 0000000..d7c55e7 --- /dev/null +++ b/apps/cuser/views.py @@ -0,0 +1,78 @@ +from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated +from django.contrib.auth import get_user_model +from apps.cuser.models import CUser as CUserModel +from apps.project.models import Project as ProjectModel +from rest_framework import serializers +from rest_framework.response import Response +from rest_framework import status +from rest_framework.generics import ListAPIView +from utils.standard_pagination import StandardPagination +from utils.tools.inline_serializer import inline_serializer +from apps.emtiyazat.selector import get_emtiyaz_mojri +#get user model + +CUserModel2 = get_user_model() + + +class MojriInfoApiview(APIView): + permission_classes = (IsAuthenticated, ) + + def get(self, request): + + user_project_count = ProjectModel.objects.filter(cuser=request.user).count() + name_family = request.user.name_family + step = request.user.step + + if request.user.user_profile.ax_mojri: + image = request.user.user_profile.ax_mojri.url + else: + image = '/media/media/app/usericon.jpg' + + emtiyaz = get_emtiyaz_mojri(cuser=request.user) + + output = { + 'project_count': user_project_count, + 'name_family': name_family, + 'emtiyaz': emtiyaz, + 'step': step, + 'image': image + } + + return Response(output, status=status.HTTP_200_OK) + + +class ListMojrian(ListAPIView): + + class OutputSerializer(serializers.ModelSerializer): + + user_profile = inline_serializer(many=False, fields={ + 'codemeli': serializers.IntegerField(), + 'city': inline_serializer(many=False, fields={ + 'id': serializers.IntegerField(), + 'name': serializers.CharField(), + 'ostan': inline_serializer(many=False, fields={ + 'id': serializers.IntegerField(), + 'name': serializers.CharField(), + }) + }), + 'address': serializers.CharField(), + 'codeposti': serializers.IntegerField(), + 'ax_mojri': serializers.CharField(), + 'ax_meli': serializers.CharField(), + 'ax_fani': serializers.CharField(), + 'ax_sayer': serializers.CharField(), + + }) + + class Meta: + model = CUserModel2 + fields = ('id', 'name_family', 'mobile', 'uuid', 'user_profile') + + permission_classes = (IsAuthenticated,) + serializer_class = OutputSerializer + pagination_class = StandardPagination + + def get_queryset(self): + mojri_status = self.request.query_params.get('status', 'WT') + return CUserModel2.objects.filter(type='MO', is_superuser=False, mojri_status=mojri_status) diff --git a/apps/dgcode/__init__.py b/apps/dgcode/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/dgcode/admin.py b/apps/dgcode/admin.py new file mode 100644 index 0000000..44f18ae --- /dev/null +++ b/apps/dgcode/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin +from .models import DgCode as DgCodeModel, DgCategory as DgCategoryModel +# Register your models here. + + +@admin.register(DgCategoryModel) +class DgCategoryModelAdmin(admin.ModelAdmin): + pass + + +@admin.register(DgCodeModel) +class DgCodeModelAdmin(admin.ModelAdmin): + pass + diff --git a/apps/dgcode/apps.py b/apps/dgcode/apps.py new file mode 100644 index 0000000..c6fab65 --- /dev/null +++ b/apps/dgcode/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DgcodeConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.dgcode' diff --git a/apps/dgcode/migrations/0001_initial.py b/apps/dgcode/migrations/0001_initial.py new file mode 100644 index 0000000..660f0a0 --- /dev/null +++ b/apps/dgcode/migrations/0001_initial.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2.1 on 2024-03-27 14:54 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='DgCategory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(blank=True, max_length=30, null=True)), + ('desc', models.CharField(blank=True, max_length=30, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DgCode', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('code', models.CharField(max_length=30)), + ('is_used', models.BooleanField(default=False)), + ('buy_price', models.PositiveIntegerField(blank=True, null=True)), + ('desc', models.CharField(blank=True, max_length=30, null=True)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dgcode.dgcategory')), + ('for_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/dgcode/migrations/__init__.py b/apps/dgcode/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/dgcode/models.py b/apps/dgcode/models.py new file mode 100644 index 0000000..830955a --- /dev/null +++ b/apps/dgcode/models.py @@ -0,0 +1,26 @@ +from django.contrib.auth import get_user_model +from django.db import models +from utils.base_model import BaseModel +# Create your models here. +CUserModel = get_user_model() + + +class DgCategory(BaseModel): + name = models.CharField(max_length=30, null=True, blank=True) + desc = models.CharField(max_length=30, null=True, blank=True) + is_active = models.BooleanField(default=True) + + def __str__(self): + return str(self.name) + + +class DgCode(BaseModel): + category = models.ForeignKey(DgCategory, on_delete=models.CASCADE, null=False, blank=False) + code = models.CharField(max_length=30, null=False, blank=False) + is_used = models.BooleanField(default=False, null=False, blank=False) + buy_price = models.PositiveIntegerField(null=True, blank=True) + desc = models.CharField(max_length=30, null=True, blank=True) + for_user = models.ForeignKey(CUserModel, on_delete=models.SET_NULL, null=True, blank=True) + + def __str__(self): + return f'{self.category.name} - {self.code}' diff --git a/apps/dgcode/tests.py b/apps/dgcode/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/dgcode/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/dgcode/views.py b/apps/dgcode/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/dgcode/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/emtiyazat/__init__.py b/apps/emtiyazat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/emtiyazat/admin.py b/apps/emtiyazat/admin.py new file mode 100644 index 0000000..57204ea --- /dev/null +++ b/apps/emtiyazat/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from apps.emtiyazat.models import Emtiyazat as EmtiyazatModel, ArzeshEmtiyaz as ArzeshEmtiyazModel +# Register your models here. + + +@admin.register(EmtiyazatModel) +class EmtiyazatAdmin(admin.ModelAdmin): + pass + + +@admin.register(ArzeshEmtiyazModel) +class ArzeshEmtiyazAdmin(admin.ModelAdmin): + pass diff --git a/apps/emtiyazat/apps.py b/apps/emtiyazat/apps.py new file mode 100644 index 0000000..368f0d4 --- /dev/null +++ b/apps/emtiyazat/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EmtiyazatConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.emtiyazat' diff --git a/apps/emtiyazat/migrations/0001_initial.py b/apps/emtiyazat/migrations/0001_initial.py new file mode 100644 index 0000000..e44b70b --- /dev/null +++ b/apps/emtiyazat/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.1 on 2024-04-17 11:30 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Emtiyazat', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('credit', models.IntegerField(blank=True, default=None, null=True)), + ('debit', models.IntegerField(blank=True, default=None, null=True)), + ('desc', models.TextField(blank=True, default=None, null=True)), + ('cuser', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='emtiyazat', to=settings.AUTH_USER_MODEL)), + ('operator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='emtiyazat_operator', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/emtiyazat/migrations/0002_emtiyazat_project.py b/apps/emtiyazat/migrations/0002_emtiyazat_project.py new file mode 100644 index 0000000..d0108d0 --- /dev/null +++ b/apps/emtiyazat/migrations/0002_emtiyazat_project.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.1 on 2024-04-18 07:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0010_project_emtiyaz'), + ('emtiyazat', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='emtiyazat', + name='project', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='emtiyaz_project', to='project.project'), + ), + ] diff --git a/apps/emtiyazat/migrations/0003_alter_emtiyazat_options.py b/apps/emtiyazat/migrations/0003_alter_emtiyazat_options.py new file mode 100644 index 0000000..1669d6b --- /dev/null +++ b/apps/emtiyazat/migrations/0003_alter_emtiyazat_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.1 on 2024-04-28 07:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('emtiyazat', '0002_emtiyazat_project'), + ] + + operations = [ + migrations.AlterModelOptions( + name='emtiyazat', + options={'ordering': ['-created_at']}, + ), + ] diff --git a/apps/emtiyazat/migrations/0004_arzeshemtiyaz.py b/apps/emtiyazat/migrations/0004_arzeshemtiyaz.py new file mode 100644 index 0000000..5dedc08 --- /dev/null +++ b/apps/emtiyazat/migrations/0004_arzeshemtiyaz.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.1 on 2024-05-02 09:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('emtiyazat', '0003_alter_emtiyazat_options'), + ] + + operations = [ + migrations.CreateModel( + name='ArzeshEmtiyaz', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('price', models.IntegerField()), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/emtiyazat/migrations/__init__.py b/apps/emtiyazat/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/emtiyazat/models.py b/apps/emtiyazat/models.py new file mode 100644 index 0000000..ce7d7b2 --- /dev/null +++ b/apps/emtiyazat/models.py @@ -0,0 +1,26 @@ +from django.contrib.auth import get_user_model +from django.db import models +from utils.base_model import BaseModel +from apps.project.models import Project as ProjectModel +# Create your models here. + + +CUser = get_user_model() + + +class ArzeshEmtiyaz(BaseModel): + price = models.IntegerField(null=False, blank=False) + + def __str__(self): + return f'{self.price}' + +class Emtiyazat(BaseModel): + cuser = models.ForeignKey(CUser, on_delete=models.CASCADE, related_name='emtiyazat') + credit = models.IntegerField(null=True, blank=True, default=None) + debit = models.IntegerField(null=True, blank=True, default=None) + desc = models.TextField(null=True, blank=True, default=None) + project = models.ForeignKey(ProjectModel, on_delete=models.CASCADE, related_name='emtiyaz_project', null=True, blank=True) + operator = models.ForeignKey(CUser, on_delete=models.CASCADE, related_name='emtiyazat_operator') + + class Meta: + ordering = ['-created_at'] \ No newline at end of file diff --git a/apps/emtiyazat/selector.py b/apps/emtiyazat/selector.py new file mode 100644 index 0000000..14ae42d --- /dev/null +++ b/apps/emtiyazat/selector.py @@ -0,0 +1,20 @@ +from django.db.models import Sum + +from apps.emtiyazat.models import Emtiyazat as EmtiyazatModel, ArzeshEmtiyaz as ArzeshEmtiyazModel + + +def get_arzesh_emtiyaz(): + arzesh_obj = ArzeshEmtiyazModel.objects.filter().last() + return arzesh_obj.price + + +def get_emtiyaz_mojri(cuser) -> int: + sum_credit = EmtiyazatModel.objects.filter(cuser=cuser).aggregate(Sum('credit')) + sum_debit = EmtiyazatModel.objects.filter(cuser=cuser).aggregate(Sum('debit')) + + if sum_credit['credit__sum'] is None: + sum_credit['credit__sum'] = 0 + if sum_debit['debit__sum'] is None: + sum_debit['debit__sum'] = 0 + + return sum_credit['credit__sum'] - sum_debit['debit__sum'] diff --git a/apps/emtiyazat/services.py b/apps/emtiyazat/services.py new file mode 100644 index 0000000..cb2a12d --- /dev/null +++ b/apps/emtiyazat/services.py @@ -0,0 +1,31 @@ +from apps.emtiyazat.models import Emtiyazat as EmtiyazatModel +from apps.project.models import Project as ProjectModel +from rest_framework.validators import ValidationError +from utils.services.sms_task import task_send_sms + +def add_project_emtiyaz(credit: int, project_id: int, op_user): + try: + project = ProjectModel.objects.get(id=project_id) + if not project.emtiyaz and project.status == 'waiting': + ProjectModel.objects.filter(id=project_id).update(emtiyaz=credit, status='approved') + desc = f'بابت پروژه: {project.address}' + emtiyaz = EmtiyazatModel.objects.create(credit=credit, desc=desc, + cuser=project.cuser, operator=op_user, project=project) + + message = f'پروژه شما تایید و {credit} امتیاز به پروفایل شما اضافه شد ' + task_send_sms.delay(message=message, mobile=str(project.cuser.mobile)) + + return emtiyaz + except Exception as exp: + raise ValidationError(f'Error on add emtiyaz {exp}') + + +def decrease_emtiyaz(debit: int, desc: str, cuser): + emtiyaz = EmtiyazatModel.objects.create( + debit=debit, + desc=desc, + cuser=cuser, + operator=cuser + ) + return emtiyaz + diff --git a/apps/emtiyazat/tests.py b/apps/emtiyazat/tests.py new file mode 100644 index 0000000..adc1483 --- /dev/null +++ b/apps/emtiyazat/tests.py @@ -0,0 +1,5 @@ +from django.test import TestCase +from rest_framework.test import APITestCase +# Create your tests here. + + diff --git a/apps/emtiyazat/urls.py b/apps/emtiyazat/urls.py new file mode 100644 index 0000000..3eeaed5 --- /dev/null +++ b/apps/emtiyazat/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from apps.emtiyazat.views import ListEmtiyazatApiview, RetrieveArzeshEmtiyaz + + +urlpatterns = [ + path('list/', ListEmtiyazatApiview.as_view(), name='list-emtiyazat'), + path('arzesh/', RetrieveArzeshEmtiyaz.as_view(), name='arzesh-emtiyaz'), +] \ No newline at end of file diff --git a/apps/emtiyazat/views.py b/apps/emtiyazat/views.py new file mode 100644 index 0000000..8e509b0 --- /dev/null +++ b/apps/emtiyazat/views.py @@ -0,0 +1,38 @@ +from django.shortcuts import render +from rest_framework import response +from rest_framework import serializers +from rest_framework.views import APIView +from rest_framework.generics import ListAPIView +from apps.emtiyazat.models import Emtiyazat as EmtiyazatModel +from utils.standard_pagination import StandardPagination +from utils.tools.inline_serializer import inline_serializer +from apps.emtiyazat.selector import get_arzesh_emtiyaz +from rest_framework.response import Response +# Create your views here. + + +class RetrieveArzeshEmtiyaz(APIView): + def get(self, request): + arzesh_emtiyaz = get_arzesh_emtiyaz() + return Response({'arzesh_emtiyaz': arzesh_emtiyaz}) + + +class ListEmtiyazatApiview(ListAPIView): + + class OuputSerializer(serializers.ModelSerializer): + + project = inline_serializer(many=False, fields={ + 'id': serializers.IntegerField(), + 'factor_price': serializers.IntegerField() + }) + + class Meta: + model = EmtiyazatModel + fields = "__all__" + + serializer_class = OuputSerializer + pagination_class = StandardPagination + + def get_queryset(self): + cuser = self.request.user + return EmtiyazatModel.objects.filter(cuser=cuser) \ No newline at end of file diff --git a/apps/op_panel/__init__.py b/apps/op_panel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/op_panel/order_view.py b/apps/op_panel/order_view.py new file mode 100644 index 0000000..f01ae8f --- /dev/null +++ b/apps/op_panel/order_view.py @@ -0,0 +1,106 @@ +from rest_framework import serializers +from apps.cuser.models import CUser as CUserModel +from rest_framework.views import APIView +from rest_framework.generics import ListAPIView +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from utils.tools.inline_serializer import inline_serializer +from utils.standard_pagination import StandardPagination +from utils.order_pagination import OrderViewPagination +from apps.order.models import Order as OrderModel +from apps.op_panel.selector import get_orders_list, get_order +from apps.op_panel.services import update_order +from rest_framework import status + + +class RetrieveOrderApiView(APIView): + + permission_classes = (IsAuthenticated,) + + class OutputSerializer(serializers.Serializer): + id = serializers.IntegerField() + name = serializers.CharField() + status = serializers.CharField() + # get status choices display + status_text = serializers.CharField(source='get_status_display') + code = serializers.CharField() + desc = serializers.CharField() + cuser_mojri = inline_serializer(many=False, fields={ + 'id': serializers.IntegerField(), + 'name_family': serializers.CharField(), + 'user_profile': inline_serializer(many=False, fields={ + 'address': serializers.CharField() + }) + }) + + def get(self, request, id: int): + order = get_order(id) + serializer = self.OutputSerializer(order) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class UpdateOrderApiView(APIView): + permission_classes = (IsAuthenticated,) + + class InputSerializer(serializers.Serializer): + code = serializers.CharField() + desc = serializers.CharField() + + def patch(self, request, id: int): + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + code = serializer.validated_data['code'] + desc = serializer.validated_data['desc'] + + update_order(id, 'approve', code, desc) + + return Response({'id': id}, status=status.HTTP_200_OK) + +class OrderListView(ListAPIView): + + class OutputSerializer(serializers.ModelSerializer): + city = serializers.SerializerMethodField() + ostan = serializers.SerializerMethodField() + name_family = serializers.SerializerMethodField() + mojri_id = serializers.SerializerMethodField() + mobile = serializers.SerializerMethodField() + + class Meta: + model = OrderModel + fields = ('id', 'mojri_id', 'mobile' , 'city', 'name', 'ostan', 'name_family', 'desc', 'status') + + def get_city(self, obj): + if obj.cuser_mojri.user_profile.city is None: + return '' + return obj.cuser_mojri.user_profile.city.name + + def get_ostan(self, obj): + if obj.cuser_mojri.user_profile.city is None: + return '' + return obj.cuser_mojri.user_profile.city.ostan.name + + def get_name_family(self, obj): + if obj.cuser_mojri is None: + return '' + return obj.cuser_mojri.name_family + + def get_mojri_id(self, obj): + if obj.cuser_mojri is None: + return '' + return obj.cuser_mojri.id + + def get_mobile(self, obj): + if obj.cuser_mojri is None: + return '' + return obj.cuser_mojri.mobile + + permission_classes = (IsAuthenticated,) + pagination_class = OrderViewPagination + serializer_class = OutputSerializer + + def get_queryset(self): + rq_status = self.request.query_params.get('status') + orders = get_orders_list(status=rq_status) + return orders + diff --git a/apps/op_panel/selector.py b/apps/op_panel/selector.py new file mode 100644 index 0000000..ebd08eb --- /dev/null +++ b/apps/op_panel/selector.py @@ -0,0 +1,31 @@ +from apps.project.models import Project as ProjectModel +from apps.order.models import Order as OrderModel +from rest_framework.exceptions import ValidationError + + +def get_order(order_id): + try: + order = OrderModel.objects.get(id=order_id) + return order + except Exception as exp: + raise ValidationError({'error': f'error get order: {str(exp)}'}) + + +def get_orders_list(status: str = 'waiting'): + try: + base_filter = {} + if status: + base_filter['status'] = status + + orders = OrderModel.objects.filter(**base_filter) + return orders + except Exception as exp: + raise ValidationError({'error': f'error get orders: {str(exp)}'}) + + +def get_project_by_id(project_id): + try: + project = ProjectModel.objects.get(id=project_id) + return project + except Exception as exp: + raise ValidationError({'error': f'error get project: {str(exp)}'}) diff --git a/apps/op_panel/services.py b/apps/op_panel/services.py new file mode 100644 index 0000000..1b25b7d --- /dev/null +++ b/apps/op_panel/services.py @@ -0,0 +1,33 @@ +from django.contrib.auth import get_user_model +from rest_framework.exceptions import ValidationError +from apps.order.models import Order as OrderModel +from utils.services.sms_task import task_send_sms +CUserModel = get_user_model() + + +def update_order(id: int, status: str, code: str, desc: str): + try: + OrderModel.objects.filter(id=id).update( + code=code, + desc=desc, + status=status + ) + + order = OrderModel.objects.get(id=id) + + message = f'لوله گستر گلپایگان: مجری محترم درخواست شما برای {order.name} تایید شد ' + # build new + + task_send_sms.delay(message=message, mobile=order.cuser_mojri.mobile) + + return True + except Exception as e: + raise ValidationError({"error": f'error update order status ${e}'}) + + +def update_mojri_status(id: int, mojri_status: str = 'WT'): + try: + CUserModel.objects.filter(id=id).update(mojri_status=mojri_status) + return True + except Exception as e: + raise ValidationError({"error": f'error update mojri status ${e}'}) diff --git a/apps/op_panel/urls.py b/apps/op_panel/urls.py new file mode 100644 index 0000000..c6f6080 --- /dev/null +++ b/apps/op_panel/urls.py @@ -0,0 +1,15 @@ +from apps.op_panel.views import RetrieveProjectApiview, AddEmtiyazProjectApiview, RetrieveMojri, UpdateMojriStatusApiview +from apps.cuser.views import ListMojrian +from apps.op_panel.order_view import OrderListView,UpdateOrderApiView, RetrieveOrderApiView +from django.urls import path + +urlpatterns = [ + path('project//', RetrieveProjectApiview.as_view(), name='retrieve-project'), + path('project//add-emtiyaz/', AddEmtiyazProjectApiview.as_view(), name='add-emtiyaz-project'), + path('list-mojrian/', ListMojrian.as_view(), name='list-mojrian'), + path('mojri//', RetrieveMojri.as_view(), name='retrieve-mojri'), + path('mojri//update-status/', UpdateMojriStatusApiview.as_view(), name='update-mojri-status'), + path('order/list/', OrderListView.as_view(), name='order-list'), + path('order//update/', UpdateOrderApiView.as_view(), name='update-order'), + path('order//', RetrieveOrderApiView.as_view(), name='retrieve-order'), +] diff --git a/apps/op_panel/views.py b/apps/op_panel/views.py new file mode 100644 index 0000000..c002b48 --- /dev/null +++ b/apps/op_panel/views.py @@ -0,0 +1,111 @@ +from rest_framework import serializers +from apps.project.models import Project as ProjectModel, ProjectImage as ProjectImageModel +from apps.emtiyazat.models import Emtiyazat as EmtiyazatModel +from apps.cuser.models import CUser as CUserModel +from apps.op_panel.selector import get_project_by_id +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from utils.tools.inline_serializer import inline_serializer +from apps.emtiyazat.services import add_project_emtiyaz +from apps.op_panel.services import update_mojri_status +from rest_framework import status + +class UpdateMojriStatusApiview(APIView): + permission_classes = (IsAuthenticated,) + + class InputSerializer(serializers.Serializer): + mojri_status = serializers.CharField() + + def patch(self, request, id: int): + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + mojri_status = serializer.validated_data['mojri_status'] + + update_mojri_status(id, mojri_status) + + return Response({'id': id}, status=status.HTTP_200_OK) + +class RetrieveProjectApiview(APIView): + permission_classes = (IsAuthenticated, ) + + def get(self, request, id: int): + + class OutputSerializer(serializers.ModelSerializer): + + cuser = inline_serializer(many=False, fields={ + 'id': serializers.IntegerField(), + 'name_family': serializers.CharField(), + 'mobile': serializers.IntegerField() + }) + + project_image = inline_serializer(many=True, fields={ + 'id': serializers.IntegerField(), + 'image': serializers.ImageField(), + }) + + class Meta: + model = ProjectModel + fields = '__all__' + + project = get_project_by_id(id) + + output = OutputSerializer(project) + + return Response(output.data, status=status.HTTP_200_OK) + + +class RetrieveMojri(APIView): + permission_classes = (IsAuthenticated,) + + def get(self, request, id: int): + + class OutputSerializer(serializers.ModelSerializer): + + user_profile = inline_serializer(many=False, fields={ + 'codemeli': serializers.IntegerField(), + 'city': inline_serializer(many=False, fields={ + 'id': serializers.IntegerField(), + 'name': serializers.CharField(), + 'ostan': inline_serializer(many=False, fields={ + 'id': serializers.IntegerField(), + 'name': serializers.CharField(), + }) + }), + 'address': serializers.CharField(), + 'codeposti': serializers.IntegerField(), + 'ax_mojri': serializers.CharField(), + 'ax_meli': serializers.CharField(), + 'ax_fani': serializers.CharField(), + 'ax_sayer': serializers.CharField(), + + }) + + class Meta: + model = CUserModel + fields = ('id', 'mojri_status', 'name_family', 'mobile', 'uuid', 'user_profile') + + mojri = CUserModel.objects.get(id=id) + + output = OutputSerializer(mojri) + + return Response(output.data, status=status.HTTP_200_OK) + + +class AddEmtiyazProjectApiview(APIView): + permission_classes = (IsAuthenticated,) + + class InputSerializer(serializers.Serializer): + credit = serializers.IntegerField(required=True) + + def post(self, request, id: int): + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + op_user = request.user + + emtiyaz = add_project_emtiyaz(**serializer.validated_data, project_id=id, op_user=op_user) + + return Response({'success': emtiyaz.credit}, status=status.HTTP_201_CREATED) + diff --git a/apps/order/__init__.py b/apps/order/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/order/admin.py b/apps/order/admin.py new file mode 100644 index 0000000..560ff01 --- /dev/null +++ b/apps/order/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from apps.order.models import Order as OrderModel +# Register your models here. + + +@admin.register(OrderModel) +class OrderAdmin(admin.ModelAdmin): + list_filter = ['status'] + search_fields = ['name', 'cuser_mojri__name_family'] diff --git a/apps/order/apps.py b/apps/order/apps.py new file mode 100644 index 0000000..a044326 --- /dev/null +++ b/apps/order/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class OrderConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.order' diff --git a/apps/order/migrations/0001_initial.py b/apps/order/migrations/0001_initial.py new file mode 100644 index 0000000..bfcc734 --- /dev/null +++ b/apps/order/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.1 on 2024-03-27 19:53 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('product', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=30)), + ('desc', models.CharField(blank=True, max_length=30, null=True)), + ('price', models.PositiveIntegerField(blank=True, null=True)), + ('emtiyaz', models.PositiveIntegerField()), + ('cuser_mojri', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ('product', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='product.product')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/order/migrations/0002_order_code.py b/apps/order/migrations/0002_order_code.py new file mode 100644 index 0000000..285ae20 --- /dev/null +++ b/apps/order/migrations/0002_order_code.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.1 on 2024-04-28 07:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='order', + name='code', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/apps/order/migrations/0003_order_status.py b/apps/order/migrations/0003_order_status.py new file mode 100644 index 0000000..8106915 --- /dev/null +++ b/apps/order/migrations/0003_order_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.1 on 2024-04-28 07:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0002_order_code'), + ] + + operations = [ + migrations.AddField( + model_name='order', + name='status', + field=models.CharField(choices=[('waiting', 'در انتظار برسی'), ('approve', 'تایید شد'), ('canceled', 'رد شده')], default='waiting', max_length=30), + ), + ] diff --git a/apps/order/migrations/0004_alter_order_options.py b/apps/order/migrations/0004_alter_order_options.py new file mode 100644 index 0000000..ca13a0b --- /dev/null +++ b/apps/order/migrations/0004_alter_order_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.1 on 2024-04-30 05:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0003_order_status'), + ] + + operations = [ + migrations.AlterModelOptions( + name='order', + options={'ordering': ['-created_at']}, + ), + ] diff --git a/apps/order/migrations/__init__.py b/apps/order/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/order/models.py b/apps/order/models.py new file mode 100644 index 0000000..8c3853c --- /dev/null +++ b/apps/order/models.py @@ -0,0 +1,40 @@ +from django.contrib.auth import get_user_model +from django.db import models +from utils.base_model import BaseModel +from apps.product.models import Product as ProductModel +# Create your models here. +CUserModel = get_user_model() + + +class Order(BaseModel): + + STATUS_CHOICES = ( + ('waiting', 'در انتظار برسی'), + ('approve', 'تایید شد'), + ('canceled', 'رد شده'), + ) + product = models.ForeignKey(ProductModel, on_delete=models.SET_NULL, null=True, blank=True) + name = models.CharField(max_length=30, null=False, blank=False) + desc = models.CharField(max_length=30, null=True, blank=True) + code = models.TextField(null=True, blank=True) + price = models.PositiveIntegerField(null=True, blank=True) + emtiyaz = models.PositiveIntegerField(null=False, blank=False) + status = models.CharField(max_length=30, choices=STATUS_CHOICES, default='waiting') + cuser_mojri = models.ForeignKey(CUserModel, on_delete=models.SET_NULL, null=True, blank=True) + + def __str__(self): + cuser_name = getattr(self.cuser_mojri, 'name_family', None) + cuser_username = getattr(self.cuser_mojri, 'username', None) + product_name = getattr(self.product, 'name', None) + + if cuser_name and product_name: + return f'{cuser_name} - {product_name} - {cuser_username}' + elif cuser_name: + return f'{ cuser_name } - {cuser_username}' + elif product_name: + return f'{product_name} - {cuser_username}' + + + class Meta: + ordering = ['-created_at'] + diff --git a/apps/order/selector.py b/apps/order/selector.py new file mode 100644 index 0000000..c28619d --- /dev/null +++ b/apps/order/selector.py @@ -0,0 +1,12 @@ +from apps.order.models import Order as OrderModel +from rest_framework.exceptions import ValidationError + + +def retrieve_order(order_id: int) -> OrderModel: + try: + order = OrderModel.objects.get(id=order_id) + return order + except OrderModel.DoesNotExist: + raise ValidationError(f"Order with id {order_id} does not exist") + except Exception as e: + raise ValidationError(f"Error retrieving order: {e}") diff --git a/apps/order/services.py b/apps/order/services.py new file mode 100644 index 0000000..07e07c8 --- /dev/null +++ b/apps/order/services.py @@ -0,0 +1,46 @@ +import time + +from apps.product.models import Product as ProductModel +from apps.order.models import Order as OrderModel +from apps.emtiyazat.selector import get_emtiyaz_mojri +from rest_framework.exceptions import ValidationError +from apps.order.models import Order as OrderModel +from apps.emtiyazat.services import decrease_emtiyaz +from config.celery import app +from celery import shared_task +from utils.services.sms_task import task_send_sms +#from apps.projectcode.task import generate_code + + +def create_order(product_id: int, cuser) -> OrderModel: + + try: + product = ProductModel.objects.get(id=product_id) + product_emtiyaz = product.get_emtiyaz + emtiyaz_mojri = get_emtiyaz_mojri(cuser) + + #generate_code.delay(count=20) + + if emtiyaz_mojri >= product_emtiyaz: + + decrease_emtiyaz(product_emtiyaz, desc=product.name, cuser=cuser) + + order = OrderModel.objects.create( + cuser_mojri=cuser, + product=product, + emtiyaz=product_emtiyaz, + name=product.name, + desc=product.desc, + price=product.price + ) + + message = f'لوله گستر گلپایگان: مجری محترم درخواست شما ثبت و درحال برسی است' + task_send_sms.delay(message=message, mobile=cuser.mobile) + + return order + else: + raise ValidationError({'error': 'emtiyaz mojri not enough'}) + + except Exception as exp: + raise ValidationError({'error': f'error in create order {exp}'}) + diff --git a/apps/order/tests.py b/apps/order/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/order/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/order/urls.py b/apps/order/urls.py new file mode 100644 index 0000000..748adf1 --- /dev/null +++ b/apps/order/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from apps.order.views import CreateOrderApiView, RetrieveOrderApiView, ListOrderApiView + +urlpatterns = [ + path('create/', CreateOrderApiView.as_view(), name='create_order'), + path('list/', ListOrderApiView.as_view(), name='list_order'), + path('/', RetrieveOrderApiView.as_view(), name='retrieve_order'), +] diff --git a/apps/order/views.py b/apps/order/views.py new file mode 100644 index 0000000..12d533d --- /dev/null +++ b/apps/order/views.py @@ -0,0 +1,88 @@ +from django.shortcuts import render +from rest_framework.views import APIView +from rest_framework.generics import ListAPIView +from rest_framework import serializers +from rest_framework import response +from rest_framework import status +from apps.order.models import Order as OrderModel +from apps.order.services import create_order +from apps.order.selector import retrieve_order +from utils.tools.inline_serializer import inline_serializer +from rest_framework.permissions import IsAuthenticated +from utils.standard_pagination import StandardPagination +# Create your views here. + + +class ListOrderApiView(ListAPIView): + class OutputSerializer(serializers.ModelSerializer): + + product = inline_serializer(many=False, fields={ + 'id': serializers.IntegerField(), + 'desc': serializers.CharField(), + 'name': serializers.CharField(), + 'emtiyaz': serializers.IntegerField(), + 'price': serializers.IntegerField(), + 'image': serializers.ImageField(), + }) + + status_text = serializers.CharField(source='get_status_display', read_only=True) + + class Meta: + model = OrderModel + fields = '__all__' + + permission_classes = (IsAuthenticated,) + serializer_class = OutputSerializer + pagination_class = StandardPagination + + def get_queryset(self): + user = self.request.user + return OrderModel.objects.filter(cuser_mojri=user) + + +class RetrieveOrderApiView(APIView): + permission_classes = (IsAuthenticated,) + + class OutputSerializer(serializers.ModelSerializer): + + product = inline_serializer(many=False, fields={ + 'id': serializers.IntegerField(), + 'desc': serializers.CharField(), + 'name': serializers.CharField(), + 'emtiyaz': serializers.IntegerField(), + 'price': serializers.IntegerField(), + 'image': serializers.ImageField(), + }) + + status_text = serializers.CharField(source='get_status_display', read_only=True) + + class Meta: + model = OrderModel + fields = '__all__' + + def get(self, request, order_id): + order = retrieve_order(order_id=order_id) + serializer = self.OutputSerializer(order) + return response.Response(data=serializer.data, status=status.HTTP_200_OK) + + +class CreateOrderApiView(APIView): + permission_classes = (IsAuthenticated, ) + + class InputSerializer(serializers.Serializer): + product_id = serializers.IntegerField() + + def post(self, request): + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = request.user + + order = create_order(**serializer.validated_data, cuser=user) + + return response.Response(data={'order': order.id}, status=status.HTTP_201_CREATED) + + + + + + diff --git a/apps/product/__init__.py b/apps/product/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/product/admin.py b/apps/product/admin.py new file mode 100644 index 0000000..eab833e --- /dev/null +++ b/apps/product/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin +from .models import Product as ProductModel, ProductCategory as ProductCategoryModel +# Register your models here. + + +@admin.register(ProductCategoryModel) +class ProductCategoryAdmin(admin.ModelAdmin): + list_filter = ['name', 'is_enable'] + + +@admin.register(ProductModel) +class ProductAdmin(admin.ModelAdmin): + list_filter = ['category__name', 'is_enable'] + search_fields = ['name',] diff --git a/apps/product/apps.py b/apps/product/apps.py new file mode 100644 index 0000000..cb72851 --- /dev/null +++ b/apps/product/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ProductConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.product' diff --git a/apps/product/migrations/0001_initial.py b/apps/product/migrations/0001_initial.py new file mode 100644 index 0000000..6bd1dbc --- /dev/null +++ b/apps/product/migrations/0001_initial.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.1 on 2024-03-27 13:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='ProductCategory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('image', models.ImageField(blank=True, null=True, upload_to='product_category/')), + ('is_enable', models.BooleanField(default=True)), + ], + ), + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('price', models.PositiveIntegerField()), + ('emtiyaz', models.PositiveIntegerField()), + ('desc', models.TextField(blank=True, null=True)), + ('image', models.ImageField(blank=True, null=True, upload_to='product/')), + ('is_enable', models.BooleanField(default=True)), + ('quantity', models.PositiveIntegerField(blank=True, null=True)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='product_category', to='product.productcategory')), + ], + ), + ] diff --git a/apps/product/migrations/__init__.py b/apps/product/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/product/models.py b/apps/product/models.py new file mode 100644 index 0000000..3d1810c --- /dev/null +++ b/apps/product/models.py @@ -0,0 +1,32 @@ +from django.db import models +from apps.emtiyazat.models import ArzeshEmtiyaz as ArzeshEmtiyazModel +# Create your models here. + + +class ProductCategory(models.Model): + name = models.CharField(max_length=50, null=False, blank=False) + image = models.ImageField(upload_to='product_category/', null=True, blank=True) + is_enable = models.BooleanField(default=True) + + def __str__(self): + return self.name + + +class Product(models.Model): + category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE, related_name='product_category') + name = models.CharField(max_length=50, null=False, blank=False) + price = models.PositiveIntegerField(null=False, blank=False) + emtiyaz = models.PositiveIntegerField(null=False, blank=False) + desc = models.TextField(null=True, blank=True) + image = models.ImageField(upload_to='product/', null=True, blank=True) + is_enable = models.BooleanField(default=True) + quantity = models.PositiveIntegerField(null=True, blank=True) + + @property + def get_emtiyaz(self): + arzesh_obj = ArzeshEmtiyazModel.objects.filter().last() + return self.price / arzesh_obj.price + + def __str__(self): + return self.name + diff --git a/apps/product/selector.py b/apps/product/selector.py new file mode 100644 index 0000000..4563296 --- /dev/null +++ b/apps/product/selector.py @@ -0,0 +1,5 @@ +from apps.product.models import Product as ProductModel + + +def get_all_products() -> ProductModel: + return ProductModel.objects.filter(is_enable=True, category__is_enable=True) diff --git a/apps/product/services.py b/apps/product/services.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/product/urls.py b/apps/product/urls.py new file mode 100644 index 0000000..d6f008f --- /dev/null +++ b/apps/product/urls.py @@ -0,0 +1,7 @@ +from django.urls import path, include +from apps.product.views import * + + +urlpatterns = [ + path('list/', ProductList.as_view(), name='product-list'), +] \ No newline at end of file diff --git a/apps/product/views.py b/apps/product/views.py new file mode 100644 index 0000000..b858530 --- /dev/null +++ b/apps/product/views.py @@ -0,0 +1,31 @@ +from django.shortcuts import render +from rest_framework.views import APIView +from apps.product.selector import get_all_products +from rest_framework import serializers +from rest_framework.response import Response +from rest_framework import status +from utils.tools.inline_serializer import inline_serializer +from rest_framework.permissions import IsAuthenticated +# Create your views here. + + +class ProductList(APIView): + + permission_classes = (IsAuthenticated, ) + + class OutputSerializer(serializers.Serializer): + id = serializers.IntegerField() + category = inline_serializer(many=False, fields={ + 'name': serializers.CharField(), + }) + name = serializers.CharField() + desc = serializers.CharField() + emtiyaz = serializers.IntegerField() + get_emtiyaz = serializers.IntegerField() + image = serializers.ImageField() + + def get(self, request): + products = get_all_products() + output = self.OutputSerializer(products, many=True) + + return Response(data=output.data, status=status.HTTP_200_OK) diff --git a/apps/project/__init__.py b/apps/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/project/admin.py b/apps/project/admin.py new file mode 100644 index 0000000..f3a96be --- /dev/null +++ b/apps/project/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from apps.project.models import Project as ProjectModel, ProjectImage as ProjectImageModel +# Register your models here. + + +@admin.register(ProjectModel) +class ProjectAdmin(admin.ModelAdmin): + search_fields = ['cuser__name_family', ] + + +@admin.register(ProjectImageModel) +class ProjectImageAdmin(admin.ModelAdmin): + pass \ No newline at end of file diff --git a/apps/project/apps.py b/apps/project/apps.py new file mode 100644 index 0000000..8c36ea8 --- /dev/null +++ b/apps/project/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ProjectConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.project' diff --git a/apps/project/migrations/0001_initial.py b/apps/project/migrations/0001_initial.py new file mode 100644 index 0000000..ef3abdb --- /dev/null +++ b/apps/project/migrations/0001_initial.py @@ -0,0 +1,50 @@ +# Generated by Django 4.2.1 on 2024-03-28 05:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('appsetting', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Project', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('address', models.CharField(max_length=200)), + ('factor_price', models.PositiveIntegerField()), + ('seller_name', models.CharField(blank=True, max_length=50, null=True)), + ('seller_mobile', models.PositiveBigIntegerField(blank=True, null=True)), + ('seller_address', models.CharField(blank=True, max_length=200, null=True)), + ('ax_factor', models.ImageField(blank=True, null=True, upload_to='project-factor/')), + ('city', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='appsetting.city')), + ('cuser', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ProjectImage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('image', models.ImageField(upload_to='project/')), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='project.project')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/project/migrations/0002_alter_project_factor_price.py b/apps/project/migrations/0002_alter_project_factor_price.py new file mode 100644 index 0000000..b50767c --- /dev/null +++ b/apps/project/migrations/0002_alter_project_factor_price.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.1 on 2024-04-07 09:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='factor_price', + field=models.PositiveBigIntegerField(), + ), + ] diff --git a/apps/project/migrations/0003_alter_project_options_project_status.py b/apps/project/migrations/0003_alter_project_options_project_status.py new file mode 100644 index 0000000..720a9ef --- /dev/null +++ b/apps/project/migrations/0003_alter_project_options_project_status.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.1 on 2024-04-07 11:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0002_alter_project_factor_price'), + ] + + operations = [ + migrations.AlterModelOptions( + name='project', + options={'ordering': ['-created_at']}, + ), + migrations.AddField( + model_name='project', + name='status', + field=models.CharField(choices=[('waiting', 'Waiting'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='waiting', max_length=10), + ), + ] diff --git a/apps/project/migrations/0004_project_op_cuser.py b/apps/project/migrations/0004_project_op_cuser.py new file mode 100644 index 0000000..2c82942 --- /dev/null +++ b/apps/project/migrations/0004_project_op_cuser.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.1 on 2024-04-07 11:55 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('project', '0003_alter_project_options_project_status'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='op_cuser', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projects', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/apps/project/migrations/0005_alter_project_status_alter_projectimage_project.py b/apps/project/migrations/0005_alter_project_status_alter_projectimage_project.py new file mode 100644 index 0000000..3613b0b --- /dev/null +++ b/apps/project/migrations/0005_alter_project_status_alter_projectimage_project.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.1 on 2024-04-08 06:49 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0004_project_op_cuser'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='status', + field=models.CharField(choices=[('AwaitComp', 'Awaiting completion'), ('waiting', 'Waiting'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='waiting', max_length=10), + ), + migrations.AlterField( + model_name='projectimage', + name='project', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_image', to='project.project'), + ), + ] diff --git a/apps/project/migrations/0006_alter_project_cuser.py b/apps/project/migrations/0006_alter_project_cuser.py new file mode 100644 index 0000000..4b25bd9 --- /dev/null +++ b/apps/project/migrations/0006_alter_project_cuser.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.1 on 2024-04-08 10:00 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('project', '0005_alter_project_status_alter_projectimage_project'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='cuser', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mojri_projects', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/apps/project/migrations/0007_project_tabaghat_project_vahed_project_zirbana_and_more.py b/apps/project/migrations/0007_project_tabaghat_project_vahed_project_zirbana_and_more.py new file mode 100644 index 0000000..636e3d2 --- /dev/null +++ b/apps/project/migrations/0007_project_tabaghat_project_vahed_project_zirbana_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.1 on 2024-04-08 11:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0006_alter_project_cuser'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='tabaghat', + field=models.PositiveIntegerField(default=0), + ), + migrations.AddField( + model_name='project', + name='vahed', + field=models.PositiveIntegerField(default=0), + ), + migrations.AddField( + model_name='project', + name='zirbana', + field=models.PositiveIntegerField(default=0), + ), + migrations.AlterField( + model_name='project', + name='status', + field=models.CharField(choices=[('AwaitComp', 'Awaiting completion'), ('waiting', 'Waiting'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='AwaitComp', max_length=10), + ), + ] diff --git a/apps/project/migrations/0008_alter_project_tabaghat_alter_project_vahed_and_more.py b/apps/project/migrations/0008_alter_project_tabaghat_alter_project_vahed_and_more.py new file mode 100644 index 0000000..73fd529 --- /dev/null +++ b/apps/project/migrations/0008_alter_project_tabaghat_alter_project_vahed_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.1 on 2024-04-09 05:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0007_project_tabaghat_project_vahed_project_zirbana_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='tabaghat', + field=models.PositiveIntegerField(blank=True, default=0, null=True), + ), + migrations.AlterField( + model_name='project', + name='vahed', + field=models.PositiveIntegerField(blank=True, default=0, null=True), + ), + migrations.AlterField( + model_name='project', + name='zirbana', + field=models.PositiveIntegerField(blank=True, default=0, null=True), + ), + ] diff --git a/apps/project/migrations/0009_alter_projectimage_image.py b/apps/project/migrations/0009_alter_projectimage_image.py new file mode 100644 index 0000000..8ecc0a7 --- /dev/null +++ b/apps/project/migrations/0009_alter_projectimage_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.1 on 2024-04-17 11:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0008_alter_project_tabaghat_alter_project_vahed_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='projectimage', + name='image', + field=models.ImageField(upload_to='media/project/'), + ), + ] diff --git a/apps/project/migrations/0010_project_emtiyaz.py b/apps/project/migrations/0010_project_emtiyaz.py new file mode 100644 index 0000000..10f04a4 --- /dev/null +++ b/apps/project/migrations/0010_project_emtiyaz.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.1 on 2024-04-17 11:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('project', '0009_alter_projectimage_image'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='emtiyaz', + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/apps/project/migrations/0011_project_projectcode.py b/apps/project/migrations/0011_project_projectcode.py new file mode 100644 index 0000000..863895e --- /dev/null +++ b/apps/project/migrations/0011_project_projectcode.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.1 on 2024-05-02 06:39 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('projectcode', '0003_alter_projectcode_options_and_more'), + ('project', '0010_project_emtiyaz'), + ] + + operations = [ + migrations.AddField( + model_name='project', + name='projectcode', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='projectcode.projectcode'), + ), + ] diff --git a/apps/project/migrations/__init__.py b/apps/project/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/project/models.py b/apps/project/models.py new file mode 100644 index 0000000..cf330f6 --- /dev/null +++ b/apps/project/models.py @@ -0,0 +1,44 @@ +from django.contrib.auth import get_user_model +from django.db import models +from utils.base_model import BaseModel +from apps.appsetting.models import City as CityModel +from apps.projectcode.models import ProjectCode as ProjectCodeModel +# Create your models here. +CUserModel = get_user_model() + + +class Project(BaseModel): + + status_choices = [ + ('AwaitComp', 'Awaiting completion'), + ('waiting', 'Waiting'), + ('approved', 'Approved'), + ('rejected', 'Rejected'), + ] + + cuser = models.ForeignKey(CUserModel, on_delete=models.CASCADE, null=False, blank=False, related_name='mojri_projects') + city = models.ForeignKey(CityModel, on_delete=models.SET_NULL, null=True, blank=True) + address = models.CharField(max_length=200, null=False, blank=False) + zirbana = models.PositiveIntegerField(null=True, blank=True, default=0) + tabaghat = models.PositiveIntegerField(null=True, blank=True, default=0) + vahed = models.PositiveIntegerField(null=True, blank=True, default=0) + factor_price = models.PositiveBigIntegerField(null=False, blank=False) + seller_name = models.CharField(max_length=50, null=True, blank=True) + seller_mobile = models.PositiveBigIntegerField(null=True, blank=True) + seller_address = models.CharField(max_length=200, null=True, blank=True) + status = models.CharField(max_length=10, choices=status_choices, default='AwaitComp') + op_cuser = models.ForeignKey(CUserModel, on_delete=models.SET_NULL, null=True, blank=True, related_name='projects') + ax_factor = models.ImageField(upload_to='project-factor/', null=True, blank=True) + emtiyaz = models.IntegerField(null=True, blank=True) + projectcode = models.ForeignKey(ProjectCodeModel, on_delete=models.SET_NULL, null=True, blank=True) + + def __str__(self): + return f'{self.cuser.name_family} - {self.city.name}' + + class Meta: + ordering = ['-created_at'] + + +class ProjectImage(BaseModel): + project = models.ForeignKey(Project, models.CASCADE, null=False, blank=False, related_name='project_image') + image = models.ImageField(upload_to='media/project/', null=False, blank=False) \ No newline at end of file diff --git a/apps/project/selector.py b/apps/project/selector.py new file mode 100644 index 0000000..8c7787e --- /dev/null +++ b/apps/project/selector.py @@ -0,0 +1,20 @@ +from apps.project.models import Project as ProjectModel +from rest_framework.exceptions import ValidationError + + +def op_list_project(status: str = 'waiting') -> ProjectModel: + projects = ProjectModel.objects.filter(status=status) + return projects + + +def list_project(user) -> ProjectModel: + projects = ProjectModel.objects.filter(cuser=user) + return projects + + +def get_project(project_id: int) -> ProjectModel: + try: + project = ProjectModel.objects.get(id=project_id) + return project + except Exception as e: + raise ValidationError({'error': f'error on get project {str(e)}'}) diff --git a/apps/project/services.py b/apps/project/services.py new file mode 100644 index 0000000..331178c --- /dev/null +++ b/apps/project/services.py @@ -0,0 +1,52 @@ +from apps.project.models import Project as ProjectModel, ProjectImage as ProjectImageModel +from apps.cuser.models import CUser as CUserModel +from rest_framework.validators import ValidationError +from utils.services.sms import DefaultSmsProvider +from utils.services.sms_task import task_send_sms + +def create_project(cuser: CUserModel, city: int, address: str, factor_price: int, project_images: list, + tabaghat: int, zirbana: int, vahed:int): + try: + project = ProjectModel.objects.create( + cuser=cuser, + city_id=city, + tabaghat=tabaghat, + zirbana=zirbana, + vahed=vahed, + address=address, + factor_price=factor_price + ) + + for image in project_images: + ProjectImageModel.objects.create(project=project, image=image) + + message = f'پروژه شما با موفقیت در باشگاه مجریان لوله گستر گلپایگان ثبت شد، لطفا اطلاعات مربوط به فروشنده را تکمیل کنید.' + task_send_sms.delay(message=message, mobile=str(cuser.mobile)) + + return project + except Exception as e: + raise ValidationError({'error': f'error on create project {str(e)}'}) + + +def update_seller_project(project_id: int, **kwargs): + try: + project = ProjectModel.objects.filter(id=project_id).update(status='waiting', **kwargs) + project_obj = ProjectModel.objects.get(id=project_id) + return project_obj + except Exception as e: + raise ValidationError({'error': f'error on update project {str(e)}'}) + + +def send_sms_to_seller(seller_mobile: int, message: str): + sms = DefaultSmsProvider() + sms.send_sms(str(seller_mobile), message) + + +def update_seller_va_send_message(project_id: int, **kwargs): + project = update_seller_project(project_id, **kwargs) + message = f'پس از برسی و تایید پروژه توسط کارشناسان شرکت امتیاز به پروفایل شما اضافه خواهد شد' + task_send_sms.delay(message=message, mobile=str(project.cuser.mobile)) + #send_sms_to_seller(9384999655, message) + return project + + diff --git a/apps/project/tests.py b/apps/project/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/project/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/project/urls.py b/apps/project/urls.py new file mode 100644 index 0000000..d0a8f1b --- /dev/null +++ b/apps/project/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from apps.project.views import (CityListApiView, CreateProjectApiview, ListProjectApiview, RetrieveProjectApiview, + UpdateSellerProjectApiview, OpListProjectApiview) + +urlpatterns = [ + path('city-list/', CityListApiView.as_view(), name='city-list'), + path('create/', CreateProjectApiview.as_view(), name='create-project'), + path('list/', ListProjectApiview.as_view(), name='project-list'), + path('op-list/', OpListProjectApiview.as_view(), name='op-project-list'), + path('/', RetrieveProjectApiview.as_view(), name='project-detail'), + path('/update-seller/', UpdateSellerProjectApiview.as_view(), name='update-seller-project'), +] diff --git a/apps/project/views.py b/apps/project/views.py new file mode 100644 index 0000000..fe15584 --- /dev/null +++ b/apps/project/views.py @@ -0,0 +1,159 @@ +from django.shortcuts import render +from rest_framework.views import APIView +from apps.appsetting.models import City as CityModel +from rest_framework import serializers +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from apps.project.models import Project as ProjectModel, ProjectImage as ProjectImageModel +from apps.appsetting.models import City as CityModel +from apps.cuser.models import CUser as CUserModel +from apps.project.services import create_project, update_seller_project, update_seller_va_send_message +from apps.project.selector import list_project, get_project, op_list_project +from apps.projectcode.selector import project_code_validation, update_project_code_used +from apps.appsetting.selector import get_city_list +from rest_framework import status +# Create your views here. + + +class CityListApiView(APIView): + permission_classes = (IsAuthenticated,) + + class OutputSerializer(serializers.ModelSerializer): + class Meta: + model = CityModel + fields = '__all__' + + def get(self, request): + cities = get_city_list() + output = self.OutputSerializer(cities, many=True) + + return Response(data=output.data) + + +# create project with project images +class CreateProjectApiview(APIView): + permission_classes = (IsAuthenticated,) + + class InputSerializer(serializers.Serializer): + city = serializers.IntegerField(required=True) + address = serializers.CharField(max_length=200, required=True) + factor_price = serializers.IntegerField(required=True) + zirbana = serializers.IntegerField(required=True) + tabaghat = serializers.IntegerField(required=True) + vahed = serializers.IntegerField(required=True) + project_images = serializers.ListField(child=serializers.ImageField(), required=True) + + def post(self, request): + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + project_obj = create_project(cuser=request.user, **serializer.validated_data) + + return Response({'project_id': f'{str(project_obj.id)}'}, status=status.HTTP_201_CREATED) + + +class ListProjectApiview(APIView): + permission_classes = (IsAuthenticated,) + + class OutputSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectModel + fields = '__all__' + + def get(self, request): + projects = list_project(user=request.user) + output = self.OutputSerializer(projects, many=True) + + return Response(data=output.data, status=status.HTTP_200_OK) + + +class RetrieveProjectApiview(APIView): + permission_classes = (IsAuthenticated,) + + class OutputSerializer(serializers.ModelSerializer): + + class ProjectImageSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectImageModel + fields = '__all__' + + project_image = ProjectImageSerializer(many=True) + + class Meta: + model = ProjectModel + fields = '__all__' + + def get(self, request, project_id): + project = get_project(project_id=project_id) + output = self.OutputSerializer(project) + + return Response(data=output.data, status=status.HTTP_200_OK) + + +class UpdateSellerProjectApiview(APIView): + permission_classes = (IsAuthenticated,) + + class InputSerializer(serializers.Serializer): + seller_name = serializers.CharField(max_length=50, required=True) + seller_mobile = serializers.IntegerField(required=True) + seller_address = serializers.CharField(max_length=500, required=True) + projectcode = serializers.CharField(max_length=20, required=True) + + def patch(self, request, project_id): + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + seller_name = serializer.validated_data.get('seller_name') + seller_mobile = serializer.validated_data.get('seller_mobile') + seller_address = serializer.validated_data.get('seller_address') + projectcode = serializer.validated_data.get('projectcode') + + user = request.user + + projectcode_obj = update_project_code_used(code=projectcode, user=user) + + update_seller_va_send_message( + project_id=project_id, seller_name=seller_name, + seller_mobile=seller_mobile, seller_address=seller_address, + projectcode=projectcode_obj + ) + + return Response({'message': 'project updated successfully'}, status=status.HTTP_200_OK) + + +class OpListProjectApiview(APIView): + permission_classes = (IsAuthenticated,) + + class OutputSerializer(serializers.ModelSerializer): + + class CitySerializer(serializers.ModelSerializer): + + class OstanSerializer(serializers.ModelSerializer): + class Meta: + model = CityModel + fields = '__all__' + + ostan = OstanSerializer() + + class Meta: + model = CityModel + fields = '__all__' + + class CUserSerializer(serializers.ModelSerializer): + class Meta: + model = CUserModel + fields = ('name_family', 'mobile',) + + city = CitySerializer() + cuser = CUserSerializer() + + class Meta: + model = ProjectModel + fields = '__all__' + + def get(self, request): + project_status = request.query_params.get('status') + projects = op_list_project(status=project_status) + output = self.OutputSerializer(projects, many=True) + + return Response(data=output.data, status=status.HTTP_200_OK) diff --git a/apps/projectcode/__init__.py b/apps/projectcode/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/projectcode/admin.py b/apps/projectcode/admin.py new file mode 100644 index 0000000..bb20a25 --- /dev/null +++ b/apps/projectcode/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from apps.projectcode.models import ProjectCode as ProjectCodeModel, ProjectCodeHead as ProjectCodeHeadModel + + +# Register your models here. +@admin.register(ProjectCodeHeadModel) +class ProjectCodeHeadAdmin(admin.ModelAdmin): + pass + + +@admin.register(ProjectCodeModel) +class ProjectCodeAdmin(admin.ModelAdmin): + pass diff --git a/apps/projectcode/apps.py b/apps/projectcode/apps.py new file mode 100644 index 0000000..25114f6 --- /dev/null +++ b/apps/projectcode/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ProjectcodeConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.projectcode' diff --git a/apps/projectcode/migrations/0001_initial.py b/apps/projectcode/migrations/0001_initial.py new file mode 100644 index 0000000..4b10f0b --- /dev/null +++ b/apps/projectcode/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2.1 on 2024-04-30 05:08 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='ProjectCodeHead', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('title', models.CharField(max_length=300)), + ('count', models.IntegerField()), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ProjectCode', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('code', models.CharField(max_length=20)), + ('is_active', models.BooleanField(default=True)), + ('is_used', models.BooleanField(default=False)), + ('mojri_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('project_code_head', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='projectcode.projectcodehead')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/projectcode/migrations/0002_alter_projectcode_mojri_user.py b/apps/projectcode/migrations/0002_alter_projectcode_mojri_user.py new file mode 100644 index 0000000..9b581f8 --- /dev/null +++ b/apps/projectcode/migrations/0002_alter_projectcode_mojri_user.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.1 on 2024-04-30 06:30 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('projectcode', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='projectcode', + name='mojri_user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/apps/projectcode/migrations/0003_alter_projectcode_options_and_more.py b/apps/projectcode/migrations/0003_alter_projectcode_options_and_more.py new file mode 100644 index 0000000..53b3881 --- /dev/null +++ b/apps/projectcode/migrations/0003_alter_projectcode_options_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.1 on 2024-05-02 06:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('projectcode', '0002_alter_projectcode_mojri_user'), + ] + + operations = [ + migrations.AlterModelOptions( + name='projectcode', + options={'ordering': ['-created_at']}, + ), + migrations.AlterModelOptions( + name='projectcodehead', + options={'ordering': ['-created_at']}, + ), + ] diff --git a/apps/projectcode/migrations/__init__.py b/apps/projectcode/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/projectcode/models.py b/apps/projectcode/models.py new file mode 100644 index 0000000..436f1bb --- /dev/null +++ b/apps/projectcode/models.py @@ -0,0 +1,30 @@ +from django.contrib.auth import get_user_model +from django.db import models +from utils.base_model import BaseModel +# Create your models here. +CUserModel = get_user_model() + + +class ProjectCodeHead(BaseModel): + title = models.CharField(max_length=300, null=False, blank=False) + count = models.IntegerField(null=False, blank=False) + + def __str__(self): + return self.title + + class Meta: + ordering = ['-created_at'] + + +class ProjectCode(BaseModel): + project_code_head = models.ForeignKey(ProjectCodeHead, on_delete=models.CASCADE) + code = models.CharField(max_length=20, null=False, blank=False) + is_active = models.BooleanField(default=True) + is_used = models.BooleanField(default=False) + mojri_user = models.ForeignKey(CUserModel, on_delete=models.CASCADE, null=True, blank=True) + + def __str__(self): + return self.code + + class Meta: + ordering = ['-created_at'] diff --git a/apps/projectcode/selector.py b/apps/projectcode/selector.py new file mode 100644 index 0000000..23e3667 --- /dev/null +++ b/apps/projectcode/selector.py @@ -0,0 +1,43 @@ +from apps.projectcode.models import ProjectCodeHead as ProjectCodeHeadModel, ProjectCode as ProjectCodeModel +from rest_framework.validators import ValidationError + + +def update_project_code_used(code: str, user): + try: + project_code = ProjectCodeModel.objects.get(code=code, is_used=False) + project_code.is_used = True + project_code.mojri_user = user + project_code.save() + return project_code + except Exception as e: + raise ValidationError(e) + + +def project_code_validation(code): + try: + return ProjectCodeModel.objects.get(code=code, is_used=False, is_active=True) + except Exception as e: + raise ValidationError(e) + + +def project_code_head_list(): + try: + return ProjectCodeHeadModel.objects.all() + except Exception as e: + raise ValidationError(e) + + +def project_code_list(id: int, is_used: str): + try: + + base_filter = { + 'project_code_head_id': id, + } + + if is_used == 'true': + base_filter['is_used'] = True + + return ProjectCodeModel.objects.filter(**base_filter) + + except Exception as e: + raise ValidationError(e) diff --git a/apps/projectcode/services.py b/apps/projectcode/services.py new file mode 100644 index 0000000..135fe5b --- /dev/null +++ b/apps/projectcode/services.py @@ -0,0 +1,23 @@ +from apps.projectcode.models import ProjectCodeHead as ProjectCodeHeadModel, ProjectCode as ProjectCodeModel +from apps.projectcode.task import generate_code +from rest_framework.validators import ValidationError +from django.db import transaction + + +@transaction.atomic +def create_project_code(title: str, count: int) -> ProjectCodeHeadModel: + + try: + + project_code_head = ProjectCodeHeadModel.objects.create( + title=title, + count=count + ) + + generate_code.delay(count=count, project_code_head_id=project_code_head.id) + + return project_code_head + + except Exception as e: + raise ValidationError(e) + diff --git a/apps/projectcode/task.py b/apps/projectcode/task.py new file mode 100644 index 0000000..d70d387 --- /dev/null +++ b/apps/projectcode/task.py @@ -0,0 +1,28 @@ +from celery import shared_task +import random +from apps.projectcode.models import ProjectCode as ProjectCodeModel, ProjectCodeHead as ProjectCodeHeadModel +from django.db import transaction + +@shared_task +@transaction.atomic +def generate_code(count: int, project_code_head_id): + codes = [] + #code_head = ProjectCodeHeadModel.objects.create(title='مقدسی', count=count) + code_head = project_code_head_id + project_codes = [] + for i in range(count): + chars = 'abcdefghijklmnopqrstuvwxyz' + char1 = random.choice(chars) + char2 = random.choice(chars) + char4 = random.choice(chars) + num = random.randint(10000000, 99999999) + code = f'{char1}{char2}{char4}{num}' + codes.append(code) + + project_code = ProjectCodeModel(project_code_head_id=code_head, code=code) + project_codes.append(project_code) + + ProjectCodeModel.objects.bulk_create(project_codes) + + + diff --git a/apps/projectcode/tests.py b/apps/projectcode/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/projectcode/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/projectcode/urls.py b/apps/projectcode/urls.py new file mode 100644 index 0000000..0720ed0 --- /dev/null +++ b/apps/projectcode/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from apps.projectcode.views import (ProjectCodeHeadView, ProjectCodeView, CreateProjectCodeView, + ProjectCodeValidationView, ProjectCodeExcelExportView) + +urlpatterns = [ + path('list/', ProjectCodeHeadView.as_view(), name='projectcode_head_list'), + path('list//', ProjectCodeView.as_view(), name='projectcode_list'), + path('excel//', ProjectCodeExcelExportView.as_view(), name='projectcode_excel'), + path('create/', CreateProjectCodeView.as_view(), name='projectcode_create'), + path('validation//', ProjectCodeValidationView.as_view(), name='projectcode_validation'), +] diff --git a/apps/projectcode/views.py b/apps/projectcode/views.py new file mode 100644 index 0000000..ad1aa5a --- /dev/null +++ b/apps/projectcode/views.py @@ -0,0 +1,89 @@ +from io import BytesIO + +from django.http import HttpResponse +from django.shortcuts import render +from rest_framework.views import APIView +from apps.projectcode.selector import project_code_head_list, project_code_list, project_code_validation +from apps.projectcode.services import create_project_code +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework import serializers +from apps.projectcode.models import ProjectCodeHead as ProjectCodeHeadModel, ProjectCode as ProjectCodeModel +from rest_framework import status +import pandas as pd +# Create your views here. + + +class ProjectCodeExcelExportView(APIView): + #permission_classes = (IsAuthenticated,) + + def get(self, request, id: int): + # export to excel and send excel file to response + + project_code = project_code_list(id=id, is_used='false') + + excel_file = BytesIO() + order_df = pd.DataFrame(project_code.values('id', 'code','is_used')) + + order_df.to_excel(excel_file, engine='xlsxwriter', index=False) + excel_file.seek(0) + + response = HttpResponse(excel_file.read(), content_type='application/ms-excel') + response['Content-Disposition'] = 'attachment; filename="output.xlsx"' + return response + + + +class ProjectCodeValidationView(APIView): + permission_classes = (IsAuthenticated,) + + def get(self, request, code): + project_code = project_code_validation(code) + return Response({'project_code_id': project_code.id}, status=status.HTTP_200_OK) + + +class CreateProjectCodeView(APIView): + permission_classes = (IsAuthenticated,) + + class InputSerializer(serializers.Serializer): + title = serializers.CharField(max_length=200) + count = serializers.IntegerField() + + def post(self, request): + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + project_code_head = create_project_code(**serializer.validated_data) + return Response({'projectcode_id': project_code_head.id}, status=status.HTTP_201_CREATED) + + +class ProjectCodeView(APIView): + + permission_classes = (IsAuthenticated,) + + class OutputSerializer(serializers.ModelSerializer): + project_code_head = serializers.StringRelatedField() + + class Meta: + model = ProjectCodeModel + fields = '__all__' + + def get(self, request, id): + is_used = request.query_params.get('is_used', 'false') + codes = project_code_list(id=id, is_used=is_used) + output = self.OutputSerializer(codes, many=True) + return Response(output.data, status=status.HTTP_200_OK) + + +class ProjectCodeHeadView(APIView): + permission_classes = (IsAuthenticated,) + + class OutputSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectCodeHeadModel + fields = '__all__' + + def get(self, request): + code_head = project_code_head_list() + output = self.OutputSerializer(code_head, many=True) + return Response(output.data, status=status.HTTP_200_OK) + diff --git a/apps/user_auth/__init__.py b/apps/user_auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/user_auth/admin.py b/apps/user_auth/admin.py new file mode 100644 index 0000000..84e586c --- /dev/null +++ b/apps/user_auth/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin +from apps.user_auth.models import SmsOtp as SmsOtpModel, JwtToken as JwtTokenModel +# Register your models here. + + +@admin.register(SmsOtpModel) +class SmsOtpAdmin(admin.ModelAdmin): + pass + + +@admin.register(JwtTokenModel) +class JwtTokenAdmin(admin.ModelAdmin): + pass + diff --git a/apps/user_auth/apps.py b/apps/user_auth/apps.py new file mode 100644 index 0000000..38d8ddf --- /dev/null +++ b/apps/user_auth/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AuthConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.user_auth' diff --git a/apps/user_auth/auth_service.py b/apps/user_auth/auth_service.py new file mode 100644 index 0000000..eb34679 --- /dev/null +++ b/apps/user_auth/auth_service.py @@ -0,0 +1,64 @@ +import datetime +from abc import ABC, abstractmethod +from utils.services.sms import SmsProviderABC +from django.db import models +from django.contrib.auth import authenticate +from typing import Protocol, Any +import random + + +class DjangoAuthProtocol(Protocol): + def authenticate(self, username:str, password:str) -> Any: + pass + + +class SmsOtpService: + + def __init__(self, sms_provider: SmsProviderABC): + self.provider = sms_provider + + def send_code(self, mobile: str, otp_code: int): + message = f'کد ورود شما در باشگاه مجریان لوله گستر گلپایگان: {otp_code} @mojrian.loolegostar.com #{otp_code}' + return self.provider.send_sms(mobile, message) + + +class AuthenticatorABC(ABC): + @abstractmethod + def authenticate(self) -> bool: + pass + + +class SMSAuth(AuthenticatorABC): + + def __init__(self, mobile: str, otp_code: int, otp_model: models.Model): + self.mobile = int(mobile) + self.otp_code = otp_code + self.otp_model = otp_model + + def authenticate(self) -> models.Model | bool: + # check code is in DB + try: + datetime_now = datetime.datetime.now() + otp_obj = self.otp_model.objects.get( + mobile=self.mobile, otp=self.otp_code, + expire__gte=datetime_now + ) + except Exception as exp: + self.otp_model.objects.filter(mobile=self.mobile).delete() + return False + + return otp_obj + + +class UsernameAuth(AuthenticatorABC): + + def __init__(self, username: str, password: str, auth_class: DjangoAuthProtocol): + self.username = username + self.password = password + self.auth_class = auth_class + + def authenticate(self) -> bool: + user = self.auth_class.authenticate(username=self.username, password=self.password) + if user: + return True + diff --git a/apps/user_auth/migrations/0001_initial.py b/apps/user_auth/migrations/0001_initial.py new file mode 100644 index 0000000..18cc645 --- /dev/null +++ b/apps/user_auth/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.1 on 2024-03-30 19:59 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='SmsOtp', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('mobile', models.BigIntegerField()), + ('otp', models.IntegerField()), + ('expire', models.DateTimeField(blank=True, null=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='JwtToken', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('refresh', models.CharField(max_length=500)), + ('access', models.CharField(blank=True, max_length=500, null=True)), + ('expire', models.DateTimeField(blank=True, null=True)), + ('is_blocked', models.BooleanField(default=False)), + ('cuser', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/user_auth/migrations/__init__.py b/apps/user_auth/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/user_auth/models.py b/apps/user_auth/models.py new file mode 100644 index 0000000..e297830 --- /dev/null +++ b/apps/user_auth/models.py @@ -0,0 +1,27 @@ +from django.contrib.auth import get_user_model +from django.db import models +from utils.base_model import BaseModel + +# Create your models here. + +CUserModel = get_user_model() + + +class JwtToken(BaseModel): + refresh = models.CharField(max_length=500, null=False, blank=False) + access = models.CharField(max_length=500, null=True, blank=True) + expire = models.DateTimeField(null=True, blank=True) + cuser = models.ForeignKey(CUserModel, null=False, blank=False, on_delete=models.CASCADE) + is_blocked = models.BooleanField(default=False) + + def __str__(self): + return f'{self.cuser}' + + +class SmsOtp(BaseModel): + mobile = models.BigIntegerField(null=False, blank=False) + otp = models.IntegerField(null=False, blank=False) + expire = models.DateTimeField(null=True, blank=True) + + def __str__(self): + return f'{self.mobile} - {self.otp}' diff --git a/apps/user_auth/services.py b/apps/user_auth/services.py new file mode 100644 index 0000000..6fc19a5 --- /dev/null +++ b/apps/user_auth/services.py @@ -0,0 +1,91 @@ +from datetime import datetime, timedelta +from django.contrib.auth import get_user_model +from rest_framework import serializers +from apps.user_auth.auth_service import SmsOtpService +from utils.services.sms import DefaultSmsProvider +from apps.user_auth.models import SmsOtp as SmsOtpModel +from apps.user_auth.auth_service import SMSAuth +from apps.user_auth.token_service import get_tokens_for_user +from rest_framework import exceptions +from django.db import transaction +from apps.user_auth.models import JwtToken as JwtTokenModel +from django.contrib.auth import authenticate +from django.conf import settings +from typing import Annotated +import random + + +CUserModel = get_user_model() + + +def login_by_user_pass(username: str, password: str): + user_obj = authenticate(username=username, password=password) + + if user_obj: + try: + token = get_tokens_for_user(user_obj) + JwtTokenModel.objects.create( + refresh=token['refresh'], + access=token['access'], + cuser=user_obj, + ) + except Exception as exp: + raise exceptions.ValidationError({"error": str(exp)}) + return token + else: + raise exceptions.ValidationError({"error": "invalid user or password"}) + + +@transaction.atomic +def send_otp_code(user_mobile: int) -> bool: + try: + CUserModel.objects.get(mobile=int(user_mobile)) + except CUserModel.DoesNotExist: + raise exceptions.ValidationError({'error': 'User not found'}) + + default_sms_provider = DefaultSmsProvider() + otp_service = SmsOtpService(sms_provider=default_sms_provider) + + if settings.OTP_BYPASS: + random_otp = 123456 + else: + random_otp = random.randint(100000, 999999) + otp_is_send = otp_service.send_code(mobile=str(user_mobile), otp_code=random_otp) + if not otp_is_send: + raise exceptions.ValidationError({'error': 'Cannot Send Otp Code'}) + + # remove old otps + SmsOtpModel.objects.filter(mobile=str(user_mobile)).delete() + + # save otp to database + expire_time = datetime.now() + timedelta(minutes=4) + otp_obj = SmsOtpModel.objects.create( + mobile=str(user_mobile), otp=random_otp, + expire=expire_time, + ) + + return True + + +def sms_auth(mobile, otp_code: int) -> dict | None: + + sms_auth = SMSAuth( + mobile=str(mobile), otp_code=otp_code, + otp_model=SmsOtpModel, + ) + otp_obj = sms_auth.authenticate() + + if otp_obj: + try: + user_obj = CUserModel.objects.get(mobile=otp_obj.mobile) + token = get_tokens_for_user(user_obj) + JwtTokenModel.objects.create( + refresh=token['refresh'], + access=token['access'], + cuser=user_obj, + ) + except Exception as exp: + raise exceptions.ValidationError({"error": str(exp)}) + return token + else: + raise exceptions.ValidationError({"error": "invalid otp code or mobile"}) diff --git a/apps/user_auth/tests.py b/apps/user_auth/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/user_auth/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/user_auth/token_service.py b/apps/user_auth/token_service.py new file mode 100644 index 0000000..ac38c0f --- /dev/null +++ b/apps/user_auth/token_service.py @@ -0,0 +1,48 @@ +from rest_framework_simplejwt.tokens import RefreshToken, AccessToken, Token, TokenError +from datetime import datetime, timedelta +from django.contrib.auth import get_user_model +from rest_framework import exceptions + +from apps.user_auth.models import JwtToken as JwtTokenModel + + + +CUserModel = get_user_model() + + +def get_tokens_for_user(user): + token = RefreshToken.for_user(user) + return { + 'refresh': str(token), + 'access': str(token.access_token), + } + + +def get_refresh_token(ref_token: str) -> dict: + + try: + user_token = RefreshToken(token=ref_token) + except TokenError as exp: + JwtTokenModel.objects.filter(refresh=ref_token).delete() + raise exceptions.ValidationError({'error': str(exp)}) + + if not user_token: + raise exceptions.ValidationError({'error': 'Invalid Token in Verify'}) + + try: + jwt_token = JwtTokenModel.objects.get(refresh=ref_token) + except JwtTokenModel.DoesNotExist: + raise exceptions.ValidationError({'error': 'Invalid Token'}) + + if jwt_token.is_blocked: + raise exceptions.ValidationError({'error': 'Token is blocked'}) + + token = get_tokens_for_user(jwt_token.cuser) + JwtTokenModel.objects.create( + refresh=token['refresh'], + access=token['access'], + cuser=jwt_token.cuser, + ) + # remove old token + jwt_token.delete() + return token diff --git a/apps/user_auth/urls.py b/apps/user_auth/urls.py new file mode 100644 index 0000000..30ab683 --- /dev/null +++ b/apps/user_auth/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from apps.user_auth.views import (MojriByMobileApiView, MojriMobileVerificationApiView, + CustomRefreshToken, LoginAdmin) + + +urlpatterns = [ + path('login-admin/', LoginAdmin.as_view(), name='login-admin'), + path('mojri/mobile/', MojriByMobileApiView.as_view(), name='mojri-mobile'), + path('mojri/mobile/verify/', MojriMobileVerificationApiView.as_view(), name='mojri-mobile-verify'), + path('refresh-token/', CustomRefreshToken.as_view(), name='refresh-token') +] \ No newline at end of file diff --git a/apps/user_auth/views.py b/apps/user_auth/views.py new file mode 100644 index 0000000..91dde15 --- /dev/null +++ b/apps/user_auth/views.py @@ -0,0 +1,81 @@ +from datetime import datetime, timedelta +from django.contrib.auth import get_user_model +from django.shortcuts import render +from rest_framework.views import APIView +from rest_framework import serializers +from apps.user_auth.auth_service import SMSAuth +from rest_framework.response import Response +from apps.user_auth.models import SmsOtp as SmsOtpModel +from apps.user_auth.services import send_otp_code, sms_auth, login_by_user_pass +from apps.user_auth.token_service import get_refresh_token +from rest_framework import status + +from rest_framework import exceptions +CUserModel = get_user_model() + + +class MojriByMobileApiView(APIView): + + class InputSerializer(serializers.Serializer): + mobile = serializers.IntegerField() + + def post(self, request): + + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + user_mobile = serializer.validated_data.get('mobile', None) + + send_otp_code(user_mobile=user_mobile) + + return Response({'status': 'otp code send'}, status=status.HTTP_200_OK) + + +class MojriMobileVerificationApiView(APIView): + + class InputSerializer(serializers.Serializer): + mobile = serializers.IntegerField() + otp_code = serializers.IntegerField() + + def post(self, request): + + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + token = sms_auth(**serializer.validated_data) + + return Response(token) + + +class LoginAdmin(APIView): + + class InputSerializers(serializers.Serializer): + username = serializers.CharField(required=True) + password = serializers.CharField(required=True) + + def post(self, request): + serializer = self.InputSerializers(data=request.data) + serializer.is_valid(raise_exception=True) + + token = login_by_user_pass(**serializer.validated_data) + + return Response(token, status=status.HTTP_200_OK) + +class CustomRefreshToken(APIView): + + class InputSerializer(serializers.Serializer): + refresh = serializers.CharField() + + def post(self, request): + + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + ref_token = serializer.validated_data.get('refresh') + + token = get_refresh_token(ref_token=ref_token) + + return Response(token) + + + diff --git a/apps/userprofile/__init__.py b/apps/userprofile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/userprofile/admin.py b/apps/userprofile/admin.py new file mode 100644 index 0000000..87e02b1 --- /dev/null +++ b/apps/userprofile/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from .models import UserProfile as UserProfileModel + + +# Register your models here. +@admin.register(UserProfileModel) +class UserProfileAdmin(admin.ModelAdmin): + list_display = ['cuser', 'city',] + search_fields = ['cuser__name_family', 'city__name',] + list_filter = ['city__is_ostan'] diff --git a/apps/userprofile/apps.py b/apps/userprofile/apps.py new file mode 100644 index 0000000..1cb31a3 --- /dev/null +++ b/apps/userprofile/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserprofileConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.userprofile' diff --git a/apps/userprofile/migrations/0001_initial.py b/apps/userprofile/migrations/0001_initial.py new file mode 100644 index 0000000..42912f4 --- /dev/null +++ b/apps/userprofile/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.1 on 2024-03-27 14:24 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('appsetting', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('codemeli', models.PositiveBigIntegerField()), + ('address', models.CharField(max_length=500)), + ('codeposti', models.CharField(max_length=15)), + ('ax_mojri', models.ImageField(blank=True, null=True, upload_to='mojri/')), + ('ax_meli', models.ImageField(blank=True, null=True, upload_to='meli/')), + ('ax_fani', models.ImageField(blank=True, null=True, upload_to='fani/')), + ('ax_sayer', models.ImageField(blank=True, null=True, upload_to='sayer/')), + ('city', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='appsetting.city')), + ('cuser', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user_profile', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/apps/userprofile/migrations/__init__.py b/apps/userprofile/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/userprofile/models.py b/apps/userprofile/models.py new file mode 100644 index 0000000..688b554 --- /dev/null +++ b/apps/userprofile/models.py @@ -0,0 +1,21 @@ +from django.contrib.auth import get_user_model +from django.db import models +from apps.appsetting.models import City as CityModel + +# Create your models here. +CUserModel = get_user_model() + + +class UserProfile(models.Model): + cuser = models.OneToOneField(CUserModel, on_delete=models.CASCADE, related_name='user_profile') + codemeli = models.PositiveBigIntegerField(null=False, blank=False) + city = models.ForeignKey(CityModel, on_delete=models.SET_NULL, null=True, blank=True) + address = models.CharField(max_length=500, null=False, blank=False) + codeposti = models.CharField(max_length=15, null=False, blank=False) + ax_mojri = models.ImageField(upload_to='mojri/', null=True, blank=True) + ax_meli = models.ImageField(upload_to='meli/', null=True, blank=True) + ax_fani = models.ImageField(upload_to='fani/', null=True, blank=True) + ax_sayer = models.ImageField(upload_to='sayer/', null=True, blank=True) + + def __str__(self): + return f'{self.cuser.name_family}' \ No newline at end of file diff --git a/apps/userprofile/selector.py b/apps/userprofile/selector.py new file mode 100644 index 0000000..c471ccc --- /dev/null +++ b/apps/userprofile/selector.py @@ -0,0 +1,11 @@ +from apps.userprofile.models import UserProfile as UserProfileModel +from rest_framework.validators import ValidationError + + +def get_user_profile(user_id: int): + try: + return UserProfileModel.objects.get(cuser_id=user_id) + except UserProfileModel.DoesNotExist: + raise ValidationError("User profile does not exist") + except Exception as exp: + raise ValidationError(exp) \ No newline at end of file diff --git a/apps/userprofile/services.py b/apps/userprofile/services.py new file mode 100644 index 0000000..554fdf9 --- /dev/null +++ b/apps/userprofile/services.py @@ -0,0 +1,36 @@ +from apps.userprofile.models import UserProfile as UserProfileModel +from rest_framework.validators import ValidationError +from apps.cuser.models import CUser as CUserModel + + +def update_userprofile(user_id: int, **kwargs): + try: + user_profile_obj = UserProfileModel.objects.get(cuser_id=user_id) + + if 'ax_mojri' in kwargs: + user_profile_obj.ax_mojri = kwargs.get('ax_mojri', None) + kwargs.pop('ax_mojri') + if 'ax_meli' in kwargs: + user_profile_obj.ax_meli = kwargs.get('ax_meli', None) + kwargs.pop('ax_meli') + if 'ax_fani' in kwargs: + user_profile_obj.ax_fani = kwargs.get('ax_fani', None) + kwargs.pop('ax_fani') + if 'ax_sayer' in kwargs: + user_profile_obj.ax_sayer = kwargs.get('ax_sayer', None) + kwargs.pop('ax_sayer') + + user_profile_obj.save() + + user_profile = UserProfileModel.objects.filter(cuser_id=user_id) + name_family = kwargs.get('name_family') + if name_family: + kwargs.pop('name_family') + CUserModel.objects.filter(id=user_id).update(name_family=name_family) + + user_profile.update(**kwargs) + + + return True + except Exception as e: + raise ValidationError(e) diff --git a/apps/userprofile/tests.py b/apps/userprofile/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/userprofile/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/userprofile/urls.py b/apps/userprofile/urls.py new file mode 100644 index 0000000..dfdc724 --- /dev/null +++ b/apps/userprofile/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from apps.userprofile.views import UpdateUserProfileView, UserProfileView +urlpatterns = [ + path('', UserProfileView.as_view(), name='profile'), + path('update/', UpdateUserProfileView.as_view(), name='update'), +] diff --git a/apps/userprofile/views.py b/apps/userprofile/views.py new file mode 100644 index 0000000..ec9b484 --- /dev/null +++ b/apps/userprofile/views.py @@ -0,0 +1,114 @@ +from django.shortcuts import render +from rest_framework import serializers +from rest_framework.views import APIView +from apps.userprofile.models import UserProfile as UserProfileModel +from apps.userprofile.services import update_userprofile +from apps.userprofile.selector import get_user_profile +from rest_framework.response import Response +from rest_framework import status +from utils.tools.inline_serializer import inline_serializer +from rest_framework.permissions import IsAuthenticated +from django.contrib.auth import get_user_model +from django.contrib.auth.models import User +from utils.services.sms_task import task_send_sms + +# Create your views here. + +Cuser = get_user_model() + +class UserProfileView(APIView): + permission_classes = (IsAuthenticated,) + + class OutputSerializer(serializers.ModelSerializer): + + cuser = inline_serializer(many=False, fields={ + "id": serializers.IntegerField(), + "username": serializers.CharField(), + "name_family": serializers.CharField(), + "mobile": serializers.IntegerField(), + }) + + city = inline_serializer(many=False, fields={ + "id": serializers.IntegerField(), + "name": serializers.CharField(), + "is_ostan": serializers.BooleanField(), + "ostan": inline_serializer(many=False, fields={ + "id": serializers.IntegerField(), + "name": serializers.CharField(), + }), + "pv_id": serializers.IntegerField + }) + + class Meta: + model = UserProfileModel + fields = "__all__" + + def get(self, request): + user_id = request.user.id + user_profile = get_user_profile(user_id) + serializer = self.OutputSerializer(user_profile) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class UpdateUserProfileView(APIView): + permission_classes = (IsAuthenticated, ) + + class InputSerializer(serializers.Serializer): + name_family = serializers.CharField(required=False) + codemeli = serializers.IntegerField(required=False) + city = serializers.IntegerField(required=False) + address = serializers.CharField(required=False) + codeposti = serializers.CharField(required=False) + ax_mojri = serializers.ImageField(required=False) + ax_meli = serializers.ImageField(required=False) + ax_fani = serializers.ImageField(required=False) + ax_sayer = serializers.ImageField(required=False) + + def patch(self, request): + user_id = request.user.id + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + update_userprofile(user_id, **serializer.validated_data) + return Response(status=status.HTTP_200_OK) + + +class RegisterUserView(APIView): + class InputSerializer(serializers.Serializer): + name = serializers.CharField() + mobile = serializers.CharField() + + def post(self, request): + serializer = self.InputSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + name = serializer.validated_data.get('name') + mobile = serializer.validated_data.get('mobile') + + user_mojri = Cuser.objects.create_user( + username=mobile, + password="Jv123456!@#$", + ) + user_mojri.name_family = name + user_mojri.mobile = mobile + user_mojri.is_active = True + user_mojri.save() + + user_profile = UserProfileModel.objects.create( + cuser=user_mojri, + codemeli=1111111, + address='آدرس', + codeposti=87878787 + ) + + + + user_profile.save() + + + try: + message = f'شرکت لوله گستر گلپایگان: مجری محترم، با سپاس از عضویت شما در باشگاه مجریان، خواهشمندیم به منظور بهره‌مندی کامل از امتیازات، پس از ورود به باشگاه مجریان نسبت به تکمیل اطلاعات پروفایل خود اقدام فرمایید.' + task_send_sms.apply_async(kwargs={'message': message, 'mobile': str(user_mojri.mobile)}, countdown=60) + except Exception as e: + print(e) + + return Response(status=status.HTTP_201_CREATED) \ No newline at end of file diff --git a/asgar b/asgar new file mode 100644 index 0000000..3c6880f --- /dev/null +++ b/asgar @@ -0,0 +1 @@ +Woops! I have deleted the content! \ No newline at end of file diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..61e7c2f --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,5 @@ +# django_celery/__init__.py + +from .celery import app as celery_app + +__all__ = ("celery_app",) diff --git a/config/asgi.py b/config/asgi.py new file mode 100644 index 0000000..90ef34f --- /dev/null +++ b/config/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for bashgah project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +application = get_asgi_application() diff --git a/config/celery.py b/config/celery.py new file mode 100644 index 0000000..97b59f4 --- /dev/null +++ b/config/celery.py @@ -0,0 +1,22 @@ +import os + +from celery import Celery + +# Set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +app = Celery('config') + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Load task modules from all registered Django apps. +app.autodiscover_tasks() + + +#@app.task(bind=True, ignore_result=True) +#def debug_task(self): +# print(f'Request: {self.request!r}') \ No newline at end of file diff --git a/config/settings/__init__.py b/config/settings/__init__.py new file mode 100644 index 0000000..9409a3f --- /dev/null +++ b/config/settings/__init__.py @@ -0,0 +1,7 @@ +import os +django_env = os.getenv('DJANGO_ENV', 'local') +if django_env == 'local': + from .local import * +elif django_env == 'production': + from .production import * + diff --git a/config/settings/base.py b/config/settings/base.py new file mode 100644 index 0000000..af81b06 --- /dev/null +++ b/config/settings/base.py @@ -0,0 +1,194 @@ +""" +Django settings for bashgah project. + +Generated by 'django-admin startproject' using Django 5.0.3. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" +import os +from pathlib import Path +from datetime import timedelta + + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-3sf^ag=#a2jfpv!*6v4=6suvdcn50e$o%+v6-46y)ck-*lfpu5' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True +OTP_BYPASS = False + +ALLOWED_HOSTS = ['*'] + +CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000", + "http://localhost:3001", + "https://bashgah.liara.run", + "https://lgg-bashgah.liara.run", + "https://bashgah.loolegostar.com", + "https://mojrian.loolegostar.com", + "https://admin-mojrian.loolegostar.com", + "https://admin-mojrian.liara.run", +] + +AUTH_USER_MODEL = 'cuser.CUser' + +# Application definition +LOCAL_APPS = [ + 'apps.cuser', + 'apps.product', + 'apps.appsetting', + 'apps.userprofile', + 'apps.dgcode', + 'apps.order', + 'apps.project', + 'apps.user_auth', + 'apps.emtiyazat', + 'apps.projectcode', +] + +THIRD_PARTY_APPS = [ + 'rest_framework', + 'drf_spectacular', + 'django_filters', + 'corsheaders', + 'debug_toolbar', + 'import_export', +] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + *THIRD_PARTY_APPS, + *LOCAL_APPS, +] + +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', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'debug_toolbar.middleware.DebugToolbarMiddleware', +] + +ROOT_URLCONF = 'config.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 = 'config.wsgi.application' + + +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=30), + "REFRESH_TOKEN_LIFETIME": timedelta(days=30), +} + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ) +} + +# Database ! +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'bashgah-dev2', + 'USER': 'postgres', + 'PASSWORD': '335577!@', + 'HOST': '127.0.0.1', + 'PORT': '5432' + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = 'static/' + +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +CELERY_BROKER_URL = "redis://127.0.0.1:6379/0" +#CELERY_TIMEZONE = "Australia/Tasmania" +CELERY_RESULT_BACKEND = "redis://localhost:6379" +CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True +CELERY_TIMEZONE = 'Asia/Tehran' + diff --git a/config/settings/local.py b/config/settings/local.py new file mode 100644 index 0000000..cd9b576 --- /dev/null +++ b/config/settings/local.py @@ -0,0 +1,19 @@ +from .base import * + +OTP_BYPASS = True + +INTERNAL_IPS = [ + # ... + "127.0.0.1", + # ... +] +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://127.0.0.1:6379/1", + "KEY_PREFIX": "bashgah", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + } + } +} diff --git a/config/settings/production.py b/config/settings/production.py new file mode 100644 index 0000000..bc059f3 --- /dev/null +++ b/config/settings/production.py @@ -0,0 +1,42 @@ +from .base import * + +DEBUG = os.getenv('DJANGO_DEBUG', False) +OTP_BYPASS = False + +BASE_DIR = Path(__file__).resolve().parent.parent +MEDIA_URL = '/media/' +MEDIA_ROOT = BASE_DIR.parent /'media' +INTERNAL_IPS = [ + # ... + "127.0.0.1", + "37.255.224.143", + # ... +] + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'bashgah', + 'USER': 'root', + 'PASSWORD': '1yI0Iw2Fpjm2BXhEnwUKVdDg', + 'HOST': 'loolegostar-db', + 'PORT': '5432' + } +} + +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://:rIlY5pkNzY11ZUSdXHw6hu0n@bashgah-redis:6379/1", + "KEY_PREFIX": "bashgah", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + } + } +} + + +CELERY_BROKER_URL = "redis://:rIlY5pkNzY11ZUSdXHw6hu0n@bashgah-redis:6379/0" +CELERY_TIMEZONE = 'Asia/Tehran' +CELERY_RESULT_BACKEND = "redis://:rIlY5pkNzY11ZUSdXHw6hu0n@bashgah-redis:6379/0" +CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True diff --git a/config/urls.py b/config/urls.py new file mode 100644 index 0000000..f1ac530 --- /dev/null +++ b/config/urls.py @@ -0,0 +1,41 @@ +""" +URL configuration for bashgah project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static +from apps.userprofile.views import RegisterUserView + +urlpatterns = [ + path('admin/', admin.site.urls), + path('op/', include('apps.op_panel.urls')), + path('mojri/', include('apps.cuser.mojri_urls')), + path('product/', include('apps.product.urls')), + path('project/', include('apps.project.urls')), + path('emtiyazat/', include('apps.emtiyazat.urls')), + path('projectcode/', include('apps.projectcode.urls')), + path('order/', include('apps.order.urls')), + path('userprofile/', include('apps.userprofile.urls')), + path('register/', RegisterUserView.as_view(), name='register'), + path('auth/', include('apps.user_auth.urls')), + path('app/', include('apps.appsetting.urls')), + path("__debug__/", include("debug_toolbar.urls")), +] + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..753f14b --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for bashgah project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + +application = get_wsgi_application() diff --git a/javad b/javad new file mode 100644 index 0000000..3c6880f --- /dev/null +++ b/javad @@ -0,0 +1 @@ +Woops! I have deleted the content! \ No newline at end of file diff --git a/kaftar.txt b/kaftar.txt new file mode 100644 index 0000000..3c6880f --- /dev/null +++ b/kaftar.txt @@ -0,0 +1 @@ +Woops! I have deleted the content! \ No newline at end of file diff --git a/liara.json b/liara.json new file mode 100644 index 0000000..78dba66 --- /dev/null +++ b/liara.json @@ -0,0 +1,10 @@ +{ + "platform": "django", + "app": "lgg-bashgah-api", + "disks": [ + { + "name": "media", + "mountTo": "media" + } + ] +} \ No newline at end of file diff --git a/liara_nginx.conf b/liara_nginx.conf new file mode 100644 index 0000000..793a034 --- /dev/null +++ b/liara_nginx.conf @@ -0,0 +1,29 @@ +gzip on; +gzip_disable "msie6"; +gzip_vary on; +gzip_proxied any; +gzip_comp_level 6; +gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; + +client_max_body_size 400M; + +location /media { + alias /usr/src/app/media; +} + +location /static { + alias /usr/src/app/config/staticfiles; +} + +location / { + try_files /dev/null @django_app; +} + +location ~\.sqlite3$ { + deny all; + error_page 403 =404 /; +} + +location ~ /\.well-known { + allow all; +} \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..8e7ac79 --- /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', 'config.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/mohammad b/mohammad new file mode 100644 index 0000000..3c6880f --- /dev/null +++ b/mohammad @@ -0,0 +1 @@ +Woops! I have deleted the content! \ No newline at end of file diff --git a/product/logo-looleh2.png b/product/logo-looleh2.png new file mode 100644 index 0000000000000000000000000000000000000000..dd913a3aef872d22fe4e045f4a7d30bba10278fc GIT binary patch literal 14866 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 bool: + pass + + +class DefaultSmsProvider(SmsProviderABC): + def send_sms(self, mobile: str, message: str) -> bool: + default_provider = DosefrNavadHashtSMS() + return default_provider.send_sms(mobile, message) + + +class DosefrNavadHashtSMS(SmsProviderABC): + def send_sms(self, mobile: str, message: str) -> bool: + payload = { + 'USERNAME': 'vsms7937', + 'PASSWORD': '335577775533', + 'FROM': '30005367', + 'TO': int(mobile), + 'TEXT': f'{message}' + } + r = requests.get('http://www.0098sms.com/sendsmslink.aspx', params=payload) + + if r.status_code == 200: + return True + else: + return False + diff --git a/utils/services/sms_task.py b/utils/services/sms_task.py new file mode 100644 index 0000000..14bb597 --- /dev/null +++ b/utils/services/sms_task.py @@ -0,0 +1,8 @@ +from celery import shared_task +from utils.services.sms import DefaultSmsProvider + + +@shared_task +def task_send_sms(message: str, mobile): + sms_provider = DefaultSmsProvider() + sms_provider.send_sms(mobile=str(mobile), message=str(message)) diff --git a/utils/standard_pagination.py b/utils/standard_pagination.py new file mode 100644 index 0000000..d23f223 --- /dev/null +++ b/utils/standard_pagination.py @@ -0,0 +1,7 @@ +from rest_framework.pagination import PageNumberPagination + + +class StandardPagination(PageNumberPagination): + page_size = 10 + page_size_query_param = 'page_size' + max_page_size = 10000 diff --git a/utils/tools/__init__.py b/utils/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/tools/inline_serializer.py b/utils/tools/inline_serializer.py new file mode 100644 index 0000000..1c1eca6 --- /dev/null +++ b/utils/tools/inline_serializer.py @@ -0,0 +1,18 @@ +from rest_framework import serializers + + +def create_serializer_class(name, fields): + return type(name, (serializers.Serializer,), fields) + + +def inline_serializer(*, fields, data=None, **kwargs): + # Important note if you are using `drf-spectacular` + # Please refer to the following issue: + # https://github.com/HackSoftware/Django-Styleguide/issues/105#issuecomment-1669468898 + # Since you might need to use unique names (uuids) for each inline serializer + serializer_class = create_serializer_class(name="inline_serializer", fields=fields) + + if data is not None: + return serializer_class(data=data, **kwargs) + + return serializer_class(**kwargs) \ No newline at end of file