diff --git a/.env.example b/.env.example index d97cac4..59166c3 100644 --- a/.env.example +++ b/.env.example @@ -75,7 +75,3 @@ STORAGE_PROTOCOL=http: # Didox configs DIDOX_PARTNER_TOKEN=... - - - -# Celery configs \ No newline at end of file diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 20b46d5..18acbf1 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -8,7 +8,6 @@ on: env: PROJECT_NAME: sifatbaho - permissions: contents: write @@ -95,7 +94,6 @@ jobs: sed -i "s|image: .*/${{ env.PROJECT_NAME }}:.*|image: ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.run_number }}|g" stack.yaml sed -i 's/return HttpResponse("OK.*"/return HttpResponse("OK: #${{ github.sha }}"/' config/urls.py - - name: Commit and push updated version run: | git config user.name "github-actions[bot]" @@ -154,6 +152,6 @@ jobs: "DB_HOST=postgres" \ "DB_NAME=sifatbahodb" \ "DB_PORT=5432" \ - "DIDOX_TOKEN=${{ secrets.DIDOX_TOKEN }}" + "DIDOX_PARTNER_TOKEN=${{ secrets.DIDOX_TOKEN }}" export PORT=8085 docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} --with-registry-auth diff --git a/config/urls.py b/config/urls.py index 0e857fb..036b0b5 100644 --- a/config/urls.py +++ b/config/urls.py @@ -13,7 +13,7 @@ from config.env import env def home(request): - return HttpResponse("OK: #88dedd85c79ccf732b2adac03616bd14e67a1579") + return HttpResponse("OK: #f83023581352d6c1a13ed27703ccd356a1f40df9") urlpatterns = [ diff --git a/core/apps/accounts/migrations/0004_permissiontoaction_permissiontotab_permission_role_and_more.py b/core/apps/accounts/migrations/0004_permissiontoaction_permissiontotab_permission_role_and_more.py deleted file mode 100644 index d877bdb..0000000 --- a/core/apps/accounts/migrations/0004_permissiontoaction_permissiontotab_permission_role_and_more.py +++ /dev/null @@ -1,78 +0,0 @@ -# Generated by Django 5.2.7 on 2026-04-24 12:33 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0003_user_avatar'), - ] - - operations = [ - migrations.CreateModel( - name='PermissionToAction', - 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=200)), - ('code', models.CharField(max_length=100, unique=True)), - ], - options={ - 'verbose_name': 'Harakatlar uchun ruxsatnoma', - 'verbose_name_plural': 'Harakatlar uchun ruxsatnomalar', - }, - ), - migrations.CreateModel( - name='PermissionToTab', - 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=200)), - ('code', models.CharField(max_length=100, unique=True)), - ('permission_to_actions', models.ManyToManyField(related_name='permission_to_tabs', to='accounts.permissiontoaction')), - ], - options={ - 'verbose_name': "Bo'lim uchun ruxsatnoma", - 'verbose_name_plural': "Bo'lim uchun ruxsatnomalar", - }, - ), - migrations.CreateModel( - name='Permission', - 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=200)), - ('code', models.CharField(max_length=100, unique=True)), - ('permission_tab', models.ManyToManyField(related_name='permissions', to='accounts.permissiontotab')), - ], - options={ - 'verbose_name': 'Sahifa uchun ruxsatnoma', - 'verbose_name_plural': 'Sahifa uchun ruxsatnomalar', - }, - ), - migrations.CreateModel( - name='Role', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, unique=True)), - ('comment', models.CharField(blank=True, max_length=200, null=True)), - ('permission_to_actions', models.ManyToManyField(blank=True, related_name='roles', to='accounts.permissiontoaction')), - ('permission_to_tabs', models.ManyToManyField(blank=True, related_name='roles', to='accounts.permissiontotab')), - ('permissions', models.ManyToManyField(blank=True, related_name='roles', to='accounts.permission')), - ], - options={ - 'verbose_name': 'Rol', - 'verbose_name_plural': 'Rollar', - }, - ), - migrations.AddField( - model_name='user', - name='permission', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.role'), - ), - ] diff --git a/core/apps/accounts/models/user.py b/core/apps/accounts/models/user.py index 78828ce..93102b4 100644 --- a/core/apps/accounts/models/user.py +++ b/core/apps/accounts/models/user.py @@ -18,7 +18,7 @@ class User(auth_models.AbstractUser): default=RoleChoice.USER, ) avatar = models.ImageField(upload_to="avatars/", null=True, blank=True) - permission = models.ForeignKey(Role, on_delete=models.SET_NULL, null=True) + permission = models.ForeignKey(Role, on_delete=models.SET_NULL, null=True, blank=True, related_name='users') USERNAME_FIELD = "phone" objects = UserManager() diff --git a/core/apps/evaluation/migrations/0035_autoevaluationmodel_is_archived.py b/core/apps/evaluation/migrations/0035_autoevaluationmodel_is_archived.py deleted file mode 100644 index 3ff54cc..0000000 --- a/core/apps/evaluation/migrations/0035_autoevaluationmodel_is_archived.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2026-04-24 12:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('evaluation', '0034_remove_certificatemodel_file_url_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='autoevaluationmodel', - name='is_archived', - field=models.BooleanField(default=False, verbose_name='is archived'), - ), - ] diff --git a/core/apps/evaluation/serializers/auto/AvgCost.py b/core/apps/evaluation/serializers/auto/AvgCost.py new file mode 100644 index 0000000..fe917ca --- /dev/null +++ b/core/apps/evaluation/serializers/auto/AvgCost.py @@ -0,0 +1,11 @@ +from rest_framework import serializers + + +class AvgCostSerializer(serializers.Serializer): + brand = serializers.CharField(max_length=100) + condition = serializers.CharField(max_length=100) + model = serializers.CharField(max_length=100) + complication = serializers.CharField(max_length=100) + manufacture_date = serializers.DateField() + distance_covered = serializers.IntegerField() + color = serializers.CharField(max_length=100) diff --git a/core/apps/evaluation/views/avg_cost.py b/core/apps/evaluation/views/avg_cost.py new file mode 100644 index 0000000..597b9cf --- /dev/null +++ b/core/apps/evaluation/views/avg_cost.py @@ -0,0 +1,18 @@ +from rest_framework import generics +from rest_framework.response import Response +from rest_framework import permissions + +from core.apps.evaluation.serializers.auto.AvgCost import AvgCostSerializer +from core.services.grpc.auto import get_auto_avg_cost + + +class AvgCostView(generics.GenericAPIView): + serializer_class = AvgCostSerializer + permission_classes = [permissions.IsAuthenticated] + + def post(self): + serializer = self.get_serializer(data=self.request.data) + if serializer.is_valid(): + avg_cost = get_auto_avg_cost(serializer.validated_data) + return Response(avg_cost, status=200) + return Response(serializer.errors, status=400) diff --git a/core/generated/auto_pb2.py b/core/generated/auto_pb2.py new file mode 100644 index 0000000..0cd42f6 --- /dev/null +++ b/core/generated/auto_pb2.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: auto.proto +# Protobuf Python Version: 6.31.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'auto.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nauto.proto\x12\x04\x61uto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xba\x01\n\x12\x41utoAvgCostRequest\x12\r\n\x05\x62rand\x18\x01 \x01(\t\x12\x11\n\tcondition\x18\x02 \x01(\t\x12\r\n\x05model\x18\x03 \x01(\t\x12\x14\n\x0c\x63omplication\x18\x04 \x01(\t\x12\x34\n\x10manufacture_date\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x64istance_covered\x18\x06 \x01(\t\x12\r\n\x05\x63olor\x18\x07 \x01(\t\"$\n\x13\x41\x64ImageListResponse\x12\r\n\x05image\x18\x01 \x01(\t\"t\n\x0e\x41\x64ListResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12)\n\x06images\x18\x02 \x03(\x0b\x32\x19.auto.AdImageListResponse\x12\r\n\x05title\x18\x03 \x01(\t\x12\r\n\x05price\x18\x04 \x01(\t\x12\r\n\x05model\x18\x05 \x01(\t\"J\n\x13\x41utoAvgCostResponse\x12\x10\n\x08\x61vg_cost\x18\x01 \x01(\x01\x12!\n\x03\x61\x64s\x18\x02 \x03(\x0b\x32\x14.auto.AdListResponse2X\n\x12\x41utoAvgCostService\x12\x42\n\x0b\x41utoAvgCost\x12\x18.auto.AutoAvgCostRequest\x1a\x19.auto.AutoAvgCostResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'auto_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_AUTOAVGCOSTREQUEST']._serialized_start=54 + _globals['_AUTOAVGCOSTREQUEST']._serialized_end=240 + _globals['_ADIMAGELISTRESPONSE']._serialized_start=242 + _globals['_ADIMAGELISTRESPONSE']._serialized_end=278 + _globals['_ADLISTRESPONSE']._serialized_start=280 + _globals['_ADLISTRESPONSE']._serialized_end=396 + _globals['_AUTOAVGCOSTRESPONSE']._serialized_start=398 + _globals['_AUTOAVGCOSTRESPONSE']._serialized_end=472 + _globals['_AUTOAVGCOSTSERVICE']._serialized_start=474 + _globals['_AUTOAVGCOSTSERVICE']._serialized_end=562 +# @@protoc_insertion_point(module_scope) diff --git a/core/generated/auto_pb2_grpc.py b/core/generated/auto_pb2_grpc.py new file mode 100644 index 0000000..9bf866b --- /dev/null +++ b/core/generated/auto_pb2_grpc.py @@ -0,0 +1,97 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from core.generated import auto_pb2 as auto__pb2 + +GRPC_GENERATED_VERSION = '1.80.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in auto_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class AutoAvgCostServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.AutoAvgCost = channel.unary_unary( + '/auto.AutoAvgCostService/AutoAvgCost', + request_serializer=auto__pb2.AutoAvgCostRequest.SerializeToString, + response_deserializer=auto__pb2.AutoAvgCostResponse.FromString, + _registered_method=True) + + +class AutoAvgCostServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def AutoAvgCost(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AutoAvgCostServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'AutoAvgCost': grpc.unary_unary_rpc_method_handler( + servicer.AutoAvgCost, + request_deserializer=auto__pb2.AutoAvgCostRequest.FromString, + response_serializer=auto__pb2.AutoAvgCostResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'auto.AutoAvgCostService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('auto.AutoAvgCostService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class AutoAvgCostService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def AutoAvgCost(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/auto.AutoAvgCostService/AutoAvgCost', + auto__pb2.AutoAvgCostRequest.SerializeToString, + auto__pb2.AutoAvgCostResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/core/services/grpc/auto.py b/core/services/grpc/auto.py new file mode 100644 index 0000000..9ad5198 --- /dev/null +++ b/core/services/grpc/auto.py @@ -0,0 +1,48 @@ +import grpc +from google.protobuf.timestamp_pb2 import Timestamp +from datetime import datetime + +from config.env import env +from core.generated import auto_pb2, auto_pb2_grpc + + +def get_auto_avg_cost( + brand, + condition, + model, + complication, + manufacture_date: datetime, + distance_covered, + color +): + # url = f"{env.str('RPC_IP')}:{env.str('RPC_PORT')}" + # channel = grpc.insecure_channel(url) + channel = grpc.insecure_channel("192.168.1.120:50051") + stub = auto_pb2_grpc.AutoAvgCostServiceStub(channel) + + ts = Timestamp() + ts.FromDatetime(manufacture_date) + + response = stub.AutoAvgCost(auto_pb2.AutoAvgCostRequest( + brand=brand, + condition=condition, + model=model, + complication=complication, + manufacture_date=ts, + distance_covered=distance_covered, + color=color, + )) + + return { + "avg_cost": response.avg_cost, + "ads": [ + { + "id": ad.id, + "title": ad.title, + "price": ad.price, + "model": ad.model, + "images": [img.image for img in ad.images], + } + for ad in response.ads + ] + } diff --git a/requirements.txt b/requirements.txt index f43fadb..9c56fdb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,3 +46,7 @@ boto3 # !NOTE: on-websocket # websockets # channels-redis + +grpcio>=1.62.0 +grpcio-tools>=1.62.0 +protobuf>=4.25.0 \ No newline at end of file diff --git a/stack.yaml b/stack.yaml index 1e72694..ea482c2 100644 --- a/stack.yaml +++ b/stack.yaml @@ -84,7 +84,7 @@ services: max-file: "5" web: - image: husanjon/sifatbaho:115 + image: husanjon/sifatbaho:124 env_file: - .env environment: @@ -129,7 +129,7 @@ services: max-file: "5" celery: - image: husanjon/sifatbaho:115 + image: husanjon/sifatbaho:124 env_file: - .env environment: