7 Commits

Author SHA1 Message Date
Shaxobff
e27a9b7f11 UPDATE 2026-04-24 11:28:29 +05:00
Shaxobff
82489cf64c ADD archivedet evaluvation 2026-04-24 11:22:58 +05:00
Shaxobff
af559dadda ADD permission 2026-04-24 11:04:37 +05:00
Shaxobff
d2f8d73cdd /api/v1/admin-user/list/ edit va post 2026-04-23 20:05:16 +05:00
Shaxobff
c4b2a80b2e /api/v1/admin-user/list/ edit va post 2026-04-23 20:03:58 +05:00
Shaxobff
049cd6ff25 /api/v1/admin-user/list/ edit va post 2026-04-23 13:54:31 +05:00
Shaxobff
b8021c7728 add AutoEvaluationRequestView 2026-04-22 14:23:52 +05:00
19 changed files with 181 additions and 173 deletions

View File

@@ -153,7 +153,7 @@ jobs:
update_env \
"DB_HOST=postgres" \
"DB_NAME=sifatbahodb" \
"DB_PORT=5432" \
"DIDOX_TOKEN=${{ secrets.DIDOX_TOKEN }}"
"DB_PORT=5432"
export PORT=8085
docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} --with-registry-auth

View File

@@ -13,7 +13,7 @@ from config.env import env
def home(request):
return HttpResponse("OK: #d4e6d80c86fcf4422f71238c6552dfa4b42f9737")
return HttpResponse("OK: #46c96214578b78c1fe731f9a2ceb4429b81d4da9")
urlpatterns = [

View File

@@ -3,6 +3,7 @@ from django.db import models
from ..choices import RoleChoice
from ..managers import UserManager
from ...evaluation.permissions.permission import Role
class User(auth_models.AbstractUser):
@@ -17,6 +18,7 @@ class User(auth_models.AbstractUser):
default=RoleChoice.USER,
)
avatar = models.ImageField(upload_to="avatars/", null=True, blank=True)
role_permission = models.ForeignKey(Role, on_delete=models.SET_NULL, null=True)
USERNAME_FIELD = "phone"
objects = UserManager()

View File

@@ -30,3 +30,13 @@ class UserUpdateSerializer(serializers.ModelSerializer):
"last_name",
"avatar"
]
class UserCreateSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = [
"phone",
"first_name",
"last_name",
"password",
"role"
]

View File

@@ -4,7 +4,8 @@ Accounts app urls
from django.urls import path, include
from rest_framework_simplejwt import views as jwt_views
from .views import RegisterView, ResetPasswordView, MeView, ChangePasswordView, UserListApiView, AdminUserListApiView
from .views import RegisterView, ResetPasswordView, MeView, ChangePasswordView, UserListApiView, AdminUserListApiView, \
AdminUpdate, AdminCreate
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
@@ -25,4 +26,6 @@ urlpatterns = [
),
path("user/list/", UserListApiView.as_view(), name="user-list"),
path("admin-user/list/", AdminUserListApiView.as_view(), name="admin-user-list"),
path("admin/create/", AdminCreate.as_view(), name="user-create"),
path("admin/update/", AdminUpdate.as_view(), name="user-update"),
]

View File

@@ -1,15 +1,17 @@
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema
from h11 import Response
from rest_framework import generics, filters
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from drf_spectacular.utils import extend_schema
from core.apps.accounts.serializers.user import UserSerializer
from core.apps.accounts.choices.user import RoleChoice
from core.apps.accounts.serializers.user import UserSerializer, UserCreateSerializer
User = get_user_model()
@extend_schema(tags=['User'])
class UserListApiView(generics.ListAPIView):
queryset = User.objects.filter(role=RoleChoice.USER)
@@ -29,3 +31,39 @@ class AdminUserListApiView(generics.ListAPIView):
permission_classes = [IsAuthenticated]
filter_backends = [filters.SearchFilter]
search_fields = ['phone', 'first_name', 'last_name']
@extend_schema(tags=['User'],
responses={200: UserSerializer},
request=UserCreateSerializer)
class AdminCreate(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
if request.user.role not in (RoleChoice.SUPERUSER, RoleChoice.ADMIN):
return Response({'detail': 'Forbidden'}, status=403)
serializer = UserCreateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=201)
@extend_schema(tags=['User'],
responses={200: UserSerializer},
request=UserCreateSerializer)
class AdminUpdate(APIView):
permission_classes = [IsAuthenticated]
def put(self, request, pk):
if request.user.role not in (RoleChoice.SUPERUSER, RoleChoice.ADMIN):
return Response({'detail': 'Forbidden'}, status=403)
user = get_object_or_404(User, pk=pk)
serializer = UserCreateSerializer(user, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=200)

View File

@@ -1,18 +0,0 @@
# Generated by Django 6.0.4 on 2026-04-23 07:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0031_remove_autoevaluationmodel_object_location_city_and_more'),
]
operations = [
migrations.AddField(
model_name='evaluationrequestmodel',
name='is_archive',
field=models.BooleanField(default=False, verbose_name='is archive'),
),
]

View File

@@ -267,6 +267,12 @@ class AutoEvaluationModel(AbstractBaseModel):
choices=AutoEvaluationStatus.choices,
default=AutoEvaluationStatus.CREATED,
)
is_archived = models.BooleanField(
verbose_name=_("is archived"),
default=False,
)
def __str__(self):
return f"Auto Evaluation {self.registration_number or self.pk}"

View File

@@ -118,10 +118,6 @@ class EvaluationrequestModel(AbstractBaseModel):
choices=RequestStatus.choices,
default=RequestStatus.PENDING,
)
is_archive = models.BooleanField(
verbose_name=_("is archive"),
default=False,
)
def __str__(self):
return f"Requests #{self.pk}{self.get_rate_type_display()}"

View File

@@ -0,0 +1,21 @@
from django.db import models
class Role(models.Model):
name = models.CharField(max_length=50)
class Permission(models.Model):
class Action(models.TextChoices):
CREATE = "create_avto_valuation"
CREATE_FAST = "create_fast_auto_valuation"
CREATE_AVTO_APPEAL = "create_avto_appeal"
page = models.CharField(max_length=100)
section = models.CharField(max_length=100, null=True, blank=True)
action = models.CharField(max_length=20, choices=Action.choices)
class RolePermission(models.Model):
role = models.ForeignKey(Role, on_delete=models.CASCADE)
permission = models.ForeignKey(Permission, on_delete=models.CASCADE)

View File

@@ -1,18 +1,20 @@
import re
from django.contrib.auth import get_user_model
from django.contrib.auth import get_user_model
from rest_framework import serializers
from core.apps.evaluation.models import AutoEvaluationModel,ReferenceitemModel, EvaluationrequestModel
from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer
from core.apps.evaluation.choices.request import RequestStatus
from core.apps.evaluation.models import AutoEvaluationModel, ReferenceitemModel, EvaluationrequestModel
from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer
User = get_user_model()
class BaseAutoevaluationSerializer(serializers.ModelSerializer):
status_display = serializers.CharField(source="get_status_display", read_only=True)
object_type_display = serializers.CharField(source="get_object_type_display", read_only=True, default=None)
object_owner_type_display = serializers.CharField(source="get_object_owner_type_display", read_only=True, default=None)
object_owner_type_display = serializers.CharField(source="get_object_owner_type_display", read_only=True,
default=None)
rate_type = ListReferenceitemSerializer(read_only=True)
value_determined = ListReferenceitemSerializer(read_only=True)
property_rights = ListReferenceitemSerializer(read_only=True)
@@ -72,6 +74,7 @@ class ListAutoevaluationSerializer(BaseAutoevaluationSerializer):
class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
car_type_display = serializers.CharField(source="get_car_type_display", read_only=True, default=None)
car_wheel_display = serializers.CharField(source="get_car_wheel_display", read_only=True, default=None)
# object_location_highways_display = serializers.CharField(
# source="get_object_location_highways_display", read_only=True, default=None
# )
@@ -141,7 +144,6 @@ class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
allow_null=True,
)
class Meta:
model = AutoEvaluationModel
fields = [
@@ -222,6 +224,7 @@ class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
return attrs
class CreateAutoevaluationSerializer(serializers.ModelSerializer):
property_rights = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
@@ -254,7 +257,6 @@ class CreateAutoevaluationSerializer(serializers.ModelSerializer):
allow_null=True,
)
class Meta:
model = AutoEvaluationModel
fields = [
@@ -356,3 +358,13 @@ class AutoEvaluationAppraisersSerializer(serializers.Serializer):
raise serializers.ValidationError("Invalid appraisers IDs.")
data['users'] = users
return data
class AutoEvaluationSerializer(serializers.Serializer):
brand = serializers.CharField()
brand_model = serializers.CharField()
year = serializers.CharField()
color = serializers.CharField()
transmission = serializers.CharField()
condition = serializers.CharField()
fuel_type = serializers.CharField()
mileage = serializers.CharField()

View File

@@ -55,7 +55,6 @@ class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
"user",
"created_at",
"updated_at",
"is_archive",
]
def get_location(self, obj):
@@ -184,7 +183,3 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
validated_data["location_name"] = str(location_name)
validated_data["user"] = self.context["request"].user
return super().create(validated_data)
class ArchiveEvaluationrequestSerializer(serializers.Serializer):
id = serializers.IntegerField(required=True)
is_archive = serializers.BooleanField(required=True)

View File

@@ -29,7 +29,7 @@ from .views import (
DidoxCompanyInfoAPIView,
TechPassportAPIView,
EvaluationStatusChange,
ArchiveEvaluationrequestView,
AutoEvaluationRequestView, GetArchivedEvaluation
)
router = DefaultRouter()
@@ -60,7 +60,8 @@ urlpatterns = [
[
path("<int:id>/list/", AutoEvaluationListAppraisersView.as_view(), name="auto-evaluation-list-appraisers"),
path("<int:id>/set/", AutoEvaluationSetAppraisersView.as_view(), name="auto-evaluation-set-appraisers"),
path("<int:id>/remove/", AutoEvaluationRemoveAppraisersView.as_view(), name="auto-evaluation-remove-appraisers"),
path("<int:id>/remove/", AutoEvaluationRemoveAppraisersView.as_view(),
name="auto-evaluation-remove-appraisers"),
]
)),
path(
@@ -75,5 +76,8 @@ urlpatterns = [
),
path("evaluation-request/<int:pk>/change-status/", EvaluationStatusChange.as_view(),
name="evaluation-change-status"),
path("evaluation-request/archive/", ArchiveEvaluationrequestView.as_view(), name="evaluation-request-archive"),
path("auto-evaluation-request/", AutoEvaluationRequestView.as_view(),
name="evaluation-request-price-estimate"),
path("archived-evaluvation/", GetArchivedEvaluation.as_view(),
name="archived-evaluation"),
]

View File

@@ -1,17 +1,20 @@
import requests
from django.db.models import Q
from django_core.mixins import BaseViewSetMixin
from django.shortcuts import get_object_or_404
from django_core.mixins import BaseViewSetMixin
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.generics import GenericAPIView, ListAPIView
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from core.apps.accounts.serializers.user import UserSerializer
from core.apps.evaluation.filters.auto import AutoevaluationFilter
from core.apps.evaluation.models import AutoEvaluationModel
from core.apps.evaluation.serializers import AutoEvaluationSerializer
from core.apps.evaluation.serializers.auto import (
CreateAutoevaluationSerializer,
ListAutoevaluationSerializer,
@@ -154,3 +157,39 @@ class AutoEvaluationListAppraisersView(GenericAPIView):
return self.get_paginated_response(serializer.data)
except Exception as e:
return Response({"error": str(e)}, status=500)
@extend_schema(
tags=["AutoEvaluation"],
request=AutoEvaluationSerializer,
)
class AutoEvaluationRequestView(APIView):
authentication_classes = []
permission_classes = [AllowAny]
def post(self, request):
serializer = AutoEvaluationSerializer(data=request.data)
if serializer.is_valid():
data = serializer.validated_data
url = "https://uzxarid.felixits.uz/api/v1/ad/price-estimate/"
response = requests.post(url, json=data)
return Response({
"success": True,
"external_status": response.status_code,
"data": response.json(),
})
return Response({"error": serializer.errors}, status=400)
@extend_schema(tags=["AutoEvaluation"])
class GetArchivedEvaluation(ListAPIView):
authentication_classes = []
permission_classes = [IsAuthenticated]
def get_queryset(self):
return AutoEvaluationModel.objects.filter(is_archived=True)

View File

@@ -31,6 +31,7 @@ class DidoxCompanyInfoAPIView(GenericAPIView):
def get(self, request, *args, **kwargs):
tin = kwargs.get("tin")
# 🔥 TYPE CHECK
try:
tin = int(tin)
except (TypeError, ValueError):
@@ -47,14 +48,4 @@ class DidoxCompanyInfoAPIView(GenericAPIView):
status=status.HTTP_502_BAD_GATEWAY
)
# if both name and personalNum are null/empty -> 404
name = data.get("name")
personal_num = data.get("personalNum")
if not name and not personal_num:
return Response(
{"detail": "Company or person not found"},
status=status.HTTP_404_NOT_FOUND
)
return Response(data, status=status.HTTP_200_OK)

View File

@@ -11,17 +11,15 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from core.apps.accounts.choices import RoleChoice
from core.apps.evaluation.filters.request import EvaluationrequestFilter
from core.apps.evaluation.models import EvaluationrequestModel
from core.apps.evaluation.serializers.request import (
CreateEvaluationrequestSerializer,
ListEvaluationrequestSerializer,
RetrieveEvaluationrequestSerializer,
ArchiveEvaluationrequestSerializer,
)
from core.apps.evaluation.choices.request import RequestStatus
from rest_framework.generics import GenericAPIView
from drf_spectacular.utils import OpenApiResponse
# class RequestPagination(PageNumberPagination):
@@ -174,77 +172,4 @@ class EvaluationStatusChange(APIView):
'success': True,
'status': evaluation.status,
'id': evaluation.pk
})
@extend_schema(tags=["EvaluationRequest"])
class ArchiveEvaluationrequestView(GenericAPIView):
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if self.request.method == "GET":
return ListEvaluationrequestSerializer
return ArchiveEvaluationrequestSerializer
@extend_schema(
tags=["EvaluationRequest"],
summary="Get archived evaluation requests list",
description="""
Returns only archived evaluation requests.
This endpoint works like evaluation-request/,
but only records with is_archive=True are returned.
""",
responses={200: ListEvaluationrequestSerializer(many=True)},
)
def get(self, request, *args, **kwargs):
queryset = EvaluationrequestModel.objects.filter(
is_archive=True
).order_by("-created_at")
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@extend_schema(
tags=["EvaluationRequest"],
summary="Archive or unarchive evaluation request",
description="""
Update archive status for evaluation request.
- is_archive=true → archive
- is_archive=false → remove from archive
""",
request=ArchiveEvaluationrequestSerializer,
responses={
200: OpenApiResponse(
description="Archive status updated successfully"
),
400: OpenApiResponse(
description="Validation error"
),
404: OpenApiResponse(
description="Evaluation request not found"
),
},
)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data
obj = get_object_or_404(
EvaluationrequestModel,
id=validated_data["id"]
)
obj.is_archive = validated_data["is_archive"]
obj.save(update_fields=["is_archive"])
return Response(
{
"success": True,
"message": "Archive status updated successfully"
},
status=status.HTTP_200_OK
)
})

View File

@@ -33,27 +33,18 @@ class TechPassportAPIView(GenericAPIView):
def post(self, request, *args, **kwargs):
serializer = TechPassportSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
result = TechPassportService.get_auto_info(
autonumber=data["autonumber"],
tech_pass_number=data["tech_pass_number"],
tech_pass_series=data["tech_pass_series"],
)
response_data = result["data"]
status_code = result["status_code"]
# success bolsa faqat data ichidagi data qaytariladi
if status_code == 200:
return Response(
response_data.get("data", {}),
status=status.HTTP_200_OK
try:
result = TechPassportService.get_auto_info(
autonumber=data["autonumber"],
tech_pass_number=data["tech_pass_number"],
tech_pass_series=data["tech_pass_series"],
)
return Response(result, status=status.HTTP_200_OK)
# error bolsa original response qaytariladi
return Response(
response_data,
status=status_code
)
except Exception as e:
return Response(
{"detail": str(e)},
status=status.HTTP_400_BAD_REQUEST
)

View File

@@ -44,28 +44,21 @@ class TechPassportService:
verify=False
)
response.raise_for_status()
logger.info(
f"Tech passport response status: {response.status_code}"
f"Tech passport info fetched successfully: {response.status_code}"
)
try:
response_data = response.json()
except ValueError:
response_data = {
"detail": "Invalid response from external service"
}
response_data = response.json()
return {
"status_code": response.status_code,
"data": response_data
}
return response_data.get("data", {})
except requests.exceptions.RequestException as e:
logger.error(str(e))
logger.error(
f"Error while fetching tech passport info: {str(e)}"
)
return {
"status_code": 500,
"data": {
"detail": str(e)
}
"success": False,
"message": str(e)
}

View File

@@ -84,7 +84,7 @@ services:
max-file: "5"
web:
image: husanjon/sifatbaho:107
image: husanjon/sifatbaho:103
env_file:
- .env
environment:
@@ -129,7 +129,7 @@ services:
max-file: "5"
celery:
image: husanjon/sifatbaho:107
image: husanjon/sifatbaho:103
env_file:
- .env
environment: