Compare commits

...

10 Commits

Author SHA1 Message Date
behruz
8ae2b9cce1 fix 500 bug 2025-12-12 11:03:50 +05:00
behruz
437e9aeef9 add search for contract list 2025-12-12 10:58:09 +05:00
behruz-dev
1fa76dad96 change two api 2025-12-03 13:42:06 +05:00
behruz-dev
9890bbea8e contractni ochirish uchun delete api qoshildi 2025-12-01 17:01:32 +05:00
behruz-dev
2587a220a6 fiux 2025-11-28 15:52:06 +05:00
behruz-dev
31d52667c6 change redis port 2025-11-28 15:48:56 +05:00
behruz-dev
2a77a18197 remove and recreate migration 2025-11-28 15:37:30 +05:00
behruz-dev
c54886d586 fix 2025-11-11 19:43:46 +05:00
behruz-dev
5626269999 fox 2025-11-05 13:54:25 +05:00
behruz-dev
dd6c7ce9dc accounts: add user profile and user update api 2025-11-05 12:09:13 +05:00
28 changed files with 367 additions and 17 deletions

View File

@@ -27,6 +27,7 @@ urlpatterns = [
[ [
path('', include('core.apps.accounts.urls')), path('', include('core.apps.accounts.urls')),
path('contracts/', include('core.apps.contracts.urls')), path('contracts/', include('core.apps.contracts.urls')),
path('shared/', include('core.apps.shared.urls')),
] ]
)), )),
] ]

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2 on 2025-07-22 15:26 # Generated by Django 5.2 on 2025-11-28 15:37
import core.apps.accounts.managers.user import core.apps.accounts.managers.user
import django.core.validators import django.core.validators

View File

@@ -0,0 +1,28 @@
from rest_framework import serializers
from core.apps.accounts.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'id', 'phone', 'indentification_num', 'profile_image', 'first_name', 'last_name', 'email', 'role'
]
class UserUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'indentification_num', 'profile_image', 'first_name', 'last_name', 'email'
]
def update(self, instance, validated_data):
instance.indentification_num = validated_data.get('indentification_num', instance.indentification_num)
instance.profile_image = validated_data.get('profile_image', instance.profile_image)
instance.first_name = validated_data.get('first_name', instance.first_name)
instance.last_name = validated_data.get('last_name', instance.last_name)
instance.email = validated_data.get('email', instance.email)
instance.save()
return instance

View File

@@ -2,6 +2,7 @@ from django.urls import path, include
from core.apps.accounts.views.auth import LoginApiView, RegisterApiView, ConfirUserApiView, ChoiceUserRoleApiView, SearchUserPhoneApiView from core.apps.accounts.views.auth import LoginApiView, RegisterApiView, ConfirUserApiView, ChoiceUserRoleApiView, SearchUserPhoneApiView
from core.apps.accounts.views.forgot_password import ConfirmCodeApiView, SendCodeApiView, ResetPasswordApiView from core.apps.accounts.views.forgot_password import ConfirmCodeApiView, SendCodeApiView, ResetPasswordApiView
from core.apps.accounts.views.user import UserProfileApiView, UserProfileUpdateApiView
urlpatterns = [ urlpatterns = [
path('auth/', include( path('auth/', include(
@@ -15,6 +16,8 @@ urlpatterns = [
path('user/', include( path('user/', include(
[ [
path('<str:number>/search/', SearchUserPhoneApiView.as_view()), path('<str:number>/search/', SearchUserPhoneApiView.as_view()),
path('profile/', UserProfileApiView.as_view()),
path('profile/update/', UserProfileUpdateApiView.as_view()),
] ]
)), )),
path('forgot_password/', include( path('forgot_password/', include(

View File

@@ -0,0 +1,29 @@
from rest_framework import generics, views
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from core.apps.accounts.serializers import user as serializers
from core.apps.accounts.models import User
class UserProfileApiView(views.APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
serializer = serializers.UserSerializer(user)
return Response(serializer.data, status=200)
class UserProfileUpdateApiView(generics.GenericAPIView):
serializer_class = serializers.UserUpdateSerializer
permission_classes = [IsAuthenticated]
queryset = User.objects.all()
def patch(self, request):
user = request.user
serializer = self.serializer_class(data=request.data, instance=user)
if serializer.is_valid(raise_exception=True):
data = serializer.save()
return Response(serializers.UserSerializer(data).data, status=200)
return Response(serializer.errors, status=400)

View File

@@ -0,0 +1,2 @@
from .contract import *
from .folder import *

View File

@@ -0,0 +1,8 @@
from django.contrib import admin
from core.apps.contracts.models.folder import Folder
@admin.register(Folder)
class FolderAdmin(admin.ModelAdmin):
list_display = ['id', 'name']

View File

@@ -6,4 +6,4 @@ class ContractsConfig(AppConfig):
name = 'core.apps.contracts' name = 'core.apps.contracts'
def ready(self): def ready(self):
import core.apps.contracts.admins.contract import core.apps.contracts.admins

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2 on 2025-07-17 14:48 # Generated by Django 5.2 on 2025-11-28 15:37
import django.db.models.deletion import django.db.models.deletion
import uuid import uuid
@@ -30,7 +30,7 @@ class Migration(migrations.Migration):
('attach_file', models.BooleanField(default=False)), ('attach_file', models.BooleanField(default=False)),
('add_folder', models.BooleanField(default=False)), ('add_folder', models.BooleanField(default=False)),
('add_notification', models.BooleanField(default=False)), ('add_notification', models.BooleanField(default=False)),
('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='c', to=settings.AUTH_USER_MODEL)), ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contracts', to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
'verbose_name': 'contract', 'verbose_name': 'contract',
@@ -94,4 +94,23 @@ class Migration(migrations.Migration):
'db_table': 'contract_signature_codes', 'db_table': 'contract_signature_codes',
}, },
), ),
migrations.CreateModel(
name='Folder',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=200)),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='folders', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Fayl',
'verbose_name_plural': 'Fayllar',
},
),
migrations.AddField(
model_name='contract',
name='folder',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contracts', to='contracts.folder'),
),
] ]

View File

@@ -1 +1,2 @@
from .contract import * from .contract import *
from .folder import *

View File

@@ -6,6 +6,7 @@ from django.contrib.auth import get_user_model
from django.utils import timezone from django.utils import timezone
from core.apps.shared.models.base import BaseModel from core.apps.shared.models.base import BaseModel
from core.apps.contracts.models.folder import Folder
from core.apps.contracts.enums.contract import SIDES, STATUS from core.apps.contracts.enums.contract import SIDES, STATUS
from core.apps.contracts.enums.contract_side import ROLE from core.apps.contracts.enums.contract_side import ROLE
from core.apps.contracts.enums.contract_signature import SIGNATURE_TYPE, SIGNATURE_STATUS from core.apps.contracts.enums.contract_signature import SIGNATURE_TYPE, SIGNATURE_STATUS
@@ -24,7 +25,8 @@ class Contract(BaseModel):
add_folder = models.BooleanField(default=False) add_folder = models.BooleanField(default=False)
add_notification = models.BooleanField(default=False) add_notification = models.BooleanField(default=False)
company = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='c') company = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='contracts')
folder = models.ForeignKey(Folder, on_delete=models.SET_NULL, null=True, blank=True, related_name='contracts')
def __str__(self): def __str__(self):
return f'{self.name}' return f'{self.name}'

View File

@@ -0,0 +1,16 @@
from django.db import models
from core.apps.shared.models import BaseModel
from core.apps.accounts.models import User
class Folder(BaseModel):
name = models.CharField(max_length=200)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='folders', null=True )
def __str__(self):
return f"{self.name} folder {self.user.username}"
class Meta:
verbose_name = "Fayl"
verbose_name_plural = "Fayllar"

View File

@@ -2,7 +2,7 @@ from django.db import transaction
from rest_framework import serializers from rest_framework import serializers
from core.apps.contracts.models.contract import Contract from core.apps.contracts.models.contract import Contract, Folder
from core.apps.contracts.serializers.contract_side import ContractSideCreateSerializer, ContractSideListSerializer from core.apps.contracts.serializers.contract_side import ContractSideCreateSerializer, ContractSideListSerializer
@@ -15,6 +15,15 @@ class ContractCreateSerializer(serializers.Serializer):
attach_file = serializers.BooleanField() attach_file = serializers.BooleanField()
add_folder = serializers.BooleanField() add_folder = serializers.BooleanField()
add_notification = serializers.BooleanField() add_notification = serializers.BooleanField()
folder_id = serializers.UUIDField(required=False)
def validate(self, data):
if data.get('folder_id'):
folder = Folder.objects.filter(id=data.get('folder_id')).first()
if not folder:
raise serializers.ValidationError("Folder not found")
data['folder'] = folder
return data
def create(self, validated_data): def create(self, validated_data):
with transaction.atomic(): with transaction.atomic():
@@ -28,8 +37,10 @@ class ContractCreateSerializer(serializers.Serializer):
attach_file=validated_data.pop('attach_file'), attach_file=validated_data.pop('attach_file'),
add_folder=validated_data.pop('add_folder'), add_folder=validated_data.pop('add_folder'),
add_notification=validated_data.pop('add_notification'), add_notification=validated_data.pop('add_notification'),
company=user company=user,
folder=validated_data.get('folder'),
) )
return contract.id return contract.id
@@ -49,3 +60,14 @@ class ContractDetailSerializer(serializers.ModelSerializer):
fields = [ fields = [
'id', 'name', 'file', 'status', 'contract_number', 'contract_sides', 'id', 'name', 'file', 'status', 'contract_number', 'contract_sides',
] ]
class ContractUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Contract
fields = ['folder']
def update(self, instance, validated_data):
instance.folder = validated_data.get('folder', instance.folder)
instance.save()
return instance

View File

@@ -40,5 +40,7 @@ class ContractSideListSerializer(serializers.ModelSerializer):
] ]
def get_contract_signature(self, obj): def get_contract_signature(self, obj):
contract_signature = obj.contract_signatures signature = getattr(obj, "contract_signatures", None)
return ContractSignatureListSerializer(contract_signature).data if contract_signature else None if signature is None:
return None
return ContractSignatureListSerializer(signature).data if signature else None

View File

@@ -0,0 +1,42 @@
from rest_framework import serializers
from core.apps.contracts.models import Folder
from core.apps.contracts.serializers.contract import ContractListSerializer
class FolderListSerializer(serializers.ModelSerializer):
class Meta:
model = Folder
fields = [
'id', 'name'
]
class FolderSerializer(serializers.ModelSerializer):
class Meta:
model = Folder
fields = [
'name'
]
def create(self, validated_data):
folder = Folder.objects.create(
name=validated_data.get('name'),
user=self.context.get('user'),
)
return folder
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.save()
return instance
class FolderDetailSerializer(serializers.ModelSerializer):
contracts = ContractListSerializer(many=True)
class Meta:
model = Folder
fields = [
'id', 'name', 'contracts'
]

View File

@@ -3,6 +3,7 @@ from django.urls import path, include
from core.apps.contracts.views import contract as contract_views from core.apps.contracts.views import contract as contract_views
from core.apps.contracts.views import contract_side as contract_side_views from core.apps.contracts.views import contract_side as contract_side_views
from core.apps.contracts.views import contract_signature as contract_signature_views from core.apps.contracts.views import contract_signature as contract_signature_views
from core.apps.contracts.views import folder as folder_views
urlpatterns = [ urlpatterns = [
@@ -11,6 +12,8 @@ urlpatterns = [
path('create/', contract_views.ContractCreateApiView.as_view(), name='create-contract'), path('create/', contract_views.ContractCreateApiView.as_view(), name='create-contract'),
path('list/', contract_views.ContractListApiView.as_view(), name='list-contract'), path('list/', contract_views.ContractListApiView.as_view(), name='list-contract'),
path('<uuid:id>/', contract_views.ContractDetailApiView.as_view(), name='detail-contract'), path('<uuid:id>/', contract_views.ContractDetailApiView.as_view(), name='detail-contract'),
path('<uuid:id>/update/', contract_views.ContractUpdateApiView.as_view()),
path('<uuid:id>/delete/', contract_views.ContractDeleteApiView.as_view()),
] ]
)), )),
path('contract_side/', include([ path('contract_side/', include([
@@ -22,5 +25,14 @@ urlpatterns = [
path('send_signature_code/<uuid:signature_id>/', contract_signature_views.SendContractSignatureCodeApiView.as_view(), name='send-signature-code'), path('send_signature_code/<uuid:signature_id>/', contract_signature_views.SendContractSignatureCodeApiView.as_view(), name='send-signature-code'),
path('sign_contract/', contract_signature_views.SigningContractApiView.as_view(), name='sign-contract'), path('sign_contract/', contract_signature_views.SigningContractApiView.as_view(), name='sign-contract'),
] ]
)) )),
path('folder/', include(
[
path('list/', folder_views.FolderListApiView.as_view()),
path('create/', folder_views.FolderCreateApiView.as_view()),
path('<uuid:folder_id>/update/', folder_views.FolderUpdateApiView.as_view()),
path('<uuid:id>/contracts/', folder_views.ContractListApiView.as_view()),
path('<uuid:id>/delete/', folder_views.ContractFolderDeleteApiView.as_view()),
]
)),
] ]

View File

@@ -1,5 +1,8 @@
from django.shortcuts import get_object_or_404
from rest_framework import generics, views, status, permissions, parsers from rest_framework import generics, views, status, permissions, parsers
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.filters import SearchFilter
from core.apps.contracts.serializers import contract as contract_serializer from core.apps.contracts.serializers import contract as contract_serializer
from core.apps.contracts.models.contract import Contract from core.apps.contracts.models.contract import Contract
@@ -26,6 +29,8 @@ class ContractCreateApiView(generics.CreateAPIView):
class ContractListApiView(generics.ListAPIView): class ContractListApiView(generics.ListAPIView):
serializer_class = contract_serializer.ContractListSerializer serializer_class = contract_serializer.ContractListSerializer
queryset = Contract.objects.all() queryset = Contract.objects.all()
filter_backends = [SearchFilter]
search_fields = ['name']
def get_queryset(self): def get_queryset(self):
return Contract.objects.filter(contract_sides__user=self.request.user) return Contract.objects.filter(contract_sides__user=self.request.user)
@@ -38,3 +43,26 @@ class ContractDetailApiView(views.APIView):
return error_message("Contract not found", 404) return error_message("Contract not found", 404)
serializer = contract_serializer.ContractDetailSerializer(contract) serializer = contract_serializer.ContractDetailSerializer(contract)
return Response(serializer.data, status=200) return Response(serializer.data, status=200)
class ContractUpdateApiView(generics.GenericAPIView):
serializer_class = contract_serializer.ContractUpdateSerializer
queryset = Contract.objects.all()
permission_classes = [permissions.IsAuthenticated]
def patch(self, request, id):
contract = get_object_or_404(Contract, id=id)
serializer = self.serializer_class(data=request.data, instance=contract)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response({"success": True, 'message': 'updated'}, status=200)
return Response({'success': False, 'message': serializer.errors}, status=400)
class ContractDeleteApiView(views.APIView):
permission_classes = [permissions.IsAuthenticated]
def delete(self, request, id):
contract = get_object_or_404(Contract, id=id)
contract.delete()
return Response({'success': True, 'message': "deleted"}, status=204)

View File

@@ -0,0 +1,66 @@
from django.shortcuts import get_object_or_404
from rest_framework import generics, views
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from core.apps.contracts.models.folder import Folder
from core.apps.contracts.serializers import folder as serializers
class FolderListApiView(views.APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
folders = Folder.objects.filter(user=user)
serializer = serializers.FolderListSerializer(folders, many=True)
return Response(serializer.data, status=200)
class FolderCreateApiView(generics.GenericAPIView):
serializer_class = serializers.FolderSerializer
queryset = Folder.objects.all()
permission_classes = [IsAuthenticated]
def post(self, request):
user = request.user
serializer = self.serializer_class(data=request.data, context={"user": user})
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response({"message":"Folder qoshild"}, status=200)
return Response(serializer.errors, status=400)
class FolderUpdateApiView(generics.GenericAPIView):
serializer_class = serializers.FolderSerializer
queryset = Folder.objects.all()
permission_classes = [IsAuthenticated]
def patch(self, request, folder_id):
user = request.user
folder = get_object_or_404(Folder, id=folder_id, user=user)
serializer = self.serializer_class(data=request.data, instance=folder)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response({"message":"Folder tahrirlandi"}, status=200)
return Response(serializer.errors, status=400)
class ContractListApiView(generics.GenericAPIView):
serializer_class = serializers.FolderDetailSerializer
queryset = Folder.objects.all()
permission_classes = [IsAuthenticated]
def get(self, request, id):
folder = get_object_or_404(Folder, id=id, user=request.user)
serializer = self.serializer_class(folder)
return Response(serializer.data)
class ContractFolderDeleteApiView(views.APIView):
permission_classes = [IsAuthenticated]
def delete(self, request, id):
folder = get_object_or_404(Folder, id=id)
folder.delete()
return Response(status=204)

View File

@@ -0,0 +1,2 @@
from .contact_us import *
from .folder import *

View File

@@ -0,0 +1,6 @@
from django.contrib import admin
from core.apps.shared.models import ContactUs
admin.site.register(ContactUs)

View File

@@ -6,4 +6,4 @@ class SharedConfig(AppConfig):
name = 'core.apps.shared' name = 'core.apps.shared'
def ready(self): def ready(self):
import core.apps.shared.admins.folder import core.apps.shared.admins

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2 on 2025-07-15 15:36 # Generated by Django 5.2 on 2025-11-28 15:37
import uuid import uuid
from django.db import migrations, models from django.db import migrations, models
@@ -12,6 +12,19 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='ContactUs',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('phone_number', models.CharField(max_length=20)),
('telegram_url', models.URLField()),
],
options={
'abstract': False,
},
),
migrations.CreateModel( migrations.CreateModel(
name='Folder', name='Folder',
fields=[ fields=[

View File

@@ -1,2 +1,3 @@
from .base import * from .base import *
from .folder import * from .folder import *
from .contact_us import *

View File

@@ -0,0 +1,12 @@
from django.db import models
from core.apps.shared.models.base import BaseModel
class ContactUs(BaseModel):
phone_number = models.CharField(max_length=20)
telegram_url = models.URLField()
def __str__(self):
return self.phone_number

View File

@@ -0,0 +1,9 @@
from rest_framework import serializers
from core.apps.shared.models.contact_us import ContactUs
class ContactUsSerializer(serializers.ModelSerializer):
class Meta:
model = ContactUs
fields = '__all__'

8
core/apps/shared/urls.py Normal file
View File

@@ -0,0 +1,8 @@
from django.urls import path
from core.apps.shared.views.contact_us import ContactUsApiView
urlpatterns = [
path('contact_us/', ContactUsApiView.as_view()),
]

View File

@@ -0,0 +1,18 @@
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from core.apps.shared.utils.response import success_message, error_message
from core.apps.shared.serializers.contact_us import ContactUsSerializer
from core.apps.shared.models import ContactUs
class ContactUsApiView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
queryset = ContactUs.objects.all()
serializer_class = ContactUsSerializer
def get(self, request):
obj = ContactUs.objects.first()
serializer = self.serializer_class(obj)
return Response(serializer.data, status=200)

View File

@@ -79,7 +79,7 @@ services:
restart: always restart: always
image: redis:latest image: redis:latest
ports: ports:
- 6380:6379 - 6387:6379
# bot: # bot:
# build: # build: