55 Commits

Author SHA1 Message Date
xoliqberdiyev
d014f5a2fb fix 2026-04-30 16:02:04 +05:00
xoliqberdiyev
7d49929772 Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into behruz 2026-04-30 15:22:18 +05:00
xoliqberdiyev
b39c080de3 add tasks app 2026-04-29 18:40:51 +05:00
github-actions[bot]
7ad385af94 🔄 Update image to 145 [CI SKIP] 2026-04-29 12:30:39 +00:00
3781ce29e5 Merge pull request 'shaxob' (#127) from shaxob into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m19s
Reviewed-on: #127
2026-04-29 12:28:42 +00:00
Shaxobff
db7e34c1c2 update 2026-04-29 16:12:12 +05:00
Shaxobff
1cb9551e81 update 2026-04-29 14:21:33 +05:00
Shaxobff
51b30c2cc4 update 2026-04-29 11:57:23 +05:00
Shaxobff
dc4c98bfc9 update 2026-04-29 11:18:50 +05:00
github-actions[bot]
abed9e59b4 🔄 Update image to 144 [CI SKIP] 2026-04-28 13:41:10 +00:00
f238c92a09 Merge pull request 'fix' (#125) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m11s
Reviewed-on: #125
2026-04-28 13:32:46 +00:00
xoliqberdiyev
113f2da120 fix 2026-04-28 18:32:24 +05:00
github-actions[bot]
99b265f68f 🔄 Update image to 143 [CI SKIP] 2026-04-28 13:19:46 +00:00
c5d60e799c Merge pull request 'fix' (#124) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m17s
Reviewed-on: #124
2026-04-28 13:17:52 +00:00
xoliqberdiyev
7829c9c625 fix 2026-04-28 18:17:21 +05:00
github-actions[bot]
7f462674a8 🔄 Update image to 142 [CI SKIP] 2026-04-28 13:05:16 +00:00
f7be3be5d2 Merge pull request 'behruz' (#123) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m12s
Reviewed-on: #123
2026-04-28 13:03:29 +00:00
xoliqberdiyev
557f9f821d Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into behruz 2026-04-28 18:03:07 +05:00
xoliqberdiyev
5f70d69896 fux 2026-04-28 18:02:39 +05:00
github-actions[bot]
4ea7070a8f 🔄 Update image to 141 [CI SKIP] 2026-04-28 12:50:39 +00:00
8b02f3a3a3 Merge pull request 'add new admin user delete api' (#122) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m12s
Reviewed-on: #122
2026-04-28 12:48:46 +00:00
xoliqberdiyev
f0d93b10ac Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into behruz 2026-04-28 17:48:03 +05:00
xoliqberdiyev
172ddf4da4 add new admin user delete api 2026-04-28 17:47:55 +05:00
435dd56334 Merge pull request 'fix error' (#121) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Has been cancelled
Reviewed-on: #121
2026-04-28 12:40:16 +00:00
xoliqberdiyev
779c9db924 fix error 2026-04-28 17:39:55 +05:00
eaaba123b0 Merge pull request 'add new fields to request-evalution api' (#120) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Failing after 53s
Reviewed-on: #120
2026-04-28 12:30:43 +00:00
xoliqberdiyev
63c4ad81eb add new fields to request-evalution api 2026-04-28 17:30:11 +05:00
github-actions[bot]
d065891ad5 🔄 Update image to 138 [CI SKIP] 2026-04-28 11:11:54 +00:00
94c4d03925 Merge pull request 'remove unused fields from auto-evalution model' (#119) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m10s
Reviewed-on: #119
2026-04-28 11:10:06 +00:00
xoliqberdiyev
4a958f064b remove unused fields from auto-evalution model 2026-04-28 16:09:25 +05:00
github-actions[bot]
d1f0a5a9ae 🔄 Update image to 137 [CI SKIP] 2026-04-28 11:07:21 +00:00
0084d11c62 Merge pull request 'change response of permissions apis' (#118) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 4m25s
Reviewed-on: #118
2026-04-28 11:05:35 +00:00
xoliqberdiyev
d1340cdd52 change response of permissions apis 2026-04-28 16:04:59 +05:00
github-actions[bot]
d7ea1acba6 🔄 Update image to 136 [CI SKIP] 2026-04-27 12:57:34 +00:00
560cbe8000 Merge pull request 'fix certificate create api' (#117) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m11s
Reviewed-on: #117
2026-04-27 12:55:46 +00:00
xoliqberdiyev
37d6a93529 fix certificate create api 2026-04-27 17:55:21 +05:00
github-actions[bot]
e1b445d515 🔄 Update image to 135 [CI SKIP] 2026-04-27 12:49:45 +00:00
ef87112c79 Merge pull request 'behruz' (#116) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m8s
Reviewed-on: #116
2026-04-27 12:47:57 +00:00
xoliqberdiyev
8c01c1dc2d Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into behruz 2026-04-27 17:47:33 +05:00
xoliqberdiyev
921b54ab7c change url 2026-04-27 17:47:20 +05:00
github-actions[bot]
a74c348187 🔄 Update image to 134 [CI SKIP] 2026-04-27 12:44:14 +00:00
52fab30588 Merge pull request 'fix 500 error' (#115) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m8s
Reviewed-on: #115
2026-04-27 12:42:29 +00:00
xoliqberdiyev
0de50ec328 fix 500 error 2026-04-27 17:41:13 +05:00
github-actions[bot]
e346546d24 🔄 Update image to 133 [CI SKIP] 2026-04-27 12:09:19 +00:00
e97c6c7ab2 Merge pull request 'update deploy.yaml file' (#114) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 4m28s
Reviewed-on: #114
2026-04-27 12:07:23 +00:00
github-actions[bot]
f7706e77ee 🔄 Update image to 132 [CI SKIP] 2026-04-27 12:07:15 +00:00
xoliqberdiyev
e351ed5303 update deploy.yaml file 2026-04-27 17:06:43 +05:00
affd3e1221 Merge pull request 'add filter to archive apis' (#113) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Has been cancelled
Reviewed-on: #113
2026-04-27 12:05:24 +00:00
xoliqberdiyev
59ed3d23ac add filter to archive apis 2026-04-27 17:05:08 +05:00
github-actions[bot]
3ac6263035 🔄 Update image to 131 [CI SKIP] 2026-04-27 12:01:46 +00:00
2c6d7dd2f7 Merge pull request 'fix 500 error' (#112) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m24s
Reviewed-on: #112
2026-04-27 11:59:43 +00:00
xoliqberdiyev
a6e0fca165 fix 500 error 2026-04-27 16:59:21 +05:00
github-actions[bot]
b64073e1ad 🔄 Update image to 130 [CI SKIP] 2026-04-27 11:54:31 +00:00
e3ffdddc46 Merge pull request 'resolve the error' (#111) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m8s
Reviewed-on: #111
2026-04-27 11:52:47 +00:00
xoliqberdiyev
6f48632e2d resolve the error 2026-04-27 16:52:30 +05:00
61 changed files with 1207 additions and 226 deletions

View File

@@ -47,6 +47,24 @@ jobs:
- name: Copy env
run: |
cp .env.example .env
update_env() {
local env_file=".env"
for kv in "$@"; do
local key="${kv%%=*}"
local value="${kv#*=}"
if grep -q "^$key=" "$env_file"; then
sed -i "s|^$key=.*|$key=$value|" "$env_file"
else
echo "$key=$value" >> "$env_file"
fi
done
}
update_env \
"DB_HOST=postgres" \
"DB_NAME=sifatbahodb" \
"DB_PORT=5432" \
"DIDOX_PARTNER_TOKEN=${{ secrets.DIDOX_TOKEN }}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -134,24 +152,5 @@ jobs:
git reset --hard origin/main
cp .env.example .env
update_env() {
local env_file=".env"
for kv in "$@"; do
local key="${kv%%=*}"
local value="${kv#*=}"
if grep -q "^$key=" "$env_file"; then
sed -i "s|^$key=.*|$key=$value|" "$env_file"
else
echo "$key=$value" >> "$env_file"
fi
done
}
update_env \
"DB_HOST=postgres" \
"DB_NAME=sifatbahodb" \
"DB_PORT=5432" \
"DIDOX_PARTNER_TOKEN=${{ secrets.DIDOX_TOKEN }}"
export PORT=8085
docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} --with-registry-auth

View File

@@ -1,11 +1,11 @@
from config.env import env
APPS = [
"cacheops",
"rosetta",
"django_ckeditor_5",
"drf_spectacular",
"rest_framework",
"corsheaders",
@@ -14,9 +14,10 @@ APPS = [
"rest_framework_simplejwt",
"django_core",
"core.apps.accounts.apps.AccountsConfig",
'core.apps.tasks.apps.TasksConfig',
]
if env.bool("SILK_ENABLED", False):
APPS += [
]

View File

@@ -186,5 +186,57 @@ PAGES = [
"link": reverse_lazy("admin:shared_villagemodel_changelist"),
},
]
},
{
"title": _("Ruxsatlar"),
"separator": True,
"items": [
{
"title": _("Ruxsatlar"),
"icon": "attach_file",
"link": reverse_lazy("admin:accounts_permission_changelist"),
},
{
"title": _("Sahifa uchun ruxsatlar"),
"icon": "attach_file",
"link": reverse_lazy("admin:accounts_permissiontotab_changelist"),
},
{
"title": _("Actionlar uchun ruxsatlar"),
"icon": "attach_file",
"link": reverse_lazy("admin:accounts_permissiontoaction_changelist"),
},
{
"title": _("Role"),
"icon": "attach_file",
"link": reverse_lazy("admin:accounts_role_changelist"),
},
]
},
{
"title": _("Task Management"),
"separator": True,
"items": [
{
"title": _("Task"),
"icon": "task",
"link": reverse_lazy("admin:tasks_task_changelist"),
},
{
"title": _("Column"),
"icon": "tag",
"link": reverse_lazy("admin:tasks_column_changelist"),
},
{
"title": _("Comment"),
"icon": "message",
"link": reverse_lazy("admin:tasks_comment_changelist"),
},
{
"title": _("Label"),
"icon": "tag",
"link": reverse_lazy("admin:tasks_label_changelist"),
},
]
}
]

View File

@@ -13,7 +13,7 @@ from config.env import env
def home(request):
return HttpResponse("OK: #04e193bae67f39888d7740451c7fcd6b22c24522")
return HttpResponse("OK: #3781ce29e5447f1473964c4c47fbdef2a38c6751")
urlpatterns = [
@@ -23,6 +23,7 @@ urlpatterns = [
path("api/v1/", include("core.apps.evaluation.urls")),
path("api/v1/", include("core.apps.payment.urls")),
path("api/v1/", include("core.apps.chat.urls")),
path("api/v1/tasks/", include("core.apps.tasks.urls")),
]
urlpatterns += [
path("admin/", admin.site.urls),

View File

@@ -1,2 +1,3 @@
from .core import * # noqa
from .user import * # noqa
from .permission import *

View File

@@ -0,0 +1,77 @@
from django.contrib import admin
from core.apps.accounts.models.permission import (
PermissionToAction,
PermissionToTab,
Permission,
Role,
)
@admin.register(PermissionToAction)
class PermissionToActionAdmin(admin.ModelAdmin):
list_display = ("id", "name", "code", "created_at")
search_fields = ("name", "code")
fieldsets = (
("Asosiy", {
"fields": ("name", "code"),
}),
)
@admin.register(PermissionToTab)
class PermissionToTabAdmin(admin.ModelAdmin):
list_display = ("id", "name", "code", "created_at")
search_fields = ("name", "code")
filter_horizontal = ("permission_to_actions",)
fieldsets = (
("Asosiy", {
"fields": ("name", "code"),
}),
("Harakatlar", {
"fields": ("permission_to_actions",),
}),
)
@admin.register(Permission)
class PermissionAdmin(admin.ModelAdmin):
list_display = ("id", "name", "code", "created_at")
search_fields = ("name", "code")
filter_horizontal = ("permission_tabs",)
fieldsets = (
("Asosiy", {
"fields": ("name", "code"),
}),
("Boglanishlar", {
"fields": ("permission_tabs",),
}),
)
@admin.register(Role)
class RoleAdmin(admin.ModelAdmin):
list_display = ("id", "name")
search_fields = ("name",)
filter_horizontal = (
"permissions",
"permission_to_tabs",
"permission_to_actions",
)
fieldsets = (
("Asosiy ma'lumotlar", {
"fields": ("name", "comment"),
}),
("Sahifa ruxsatlari", {
"fields": ("permissions",),
}),
("Bolim ruxsatlari", {
"fields": ("permission_to_tabs",),
}),
("Harakat ruxsatlari", {
"fields": ("permission_to_actions",),
}),
)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-04-28 11:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_permissiontoaction_permissiontotab_permission_role_and_more'),
]
operations = [
migrations.RenameField(
model_name='permission',
old_name='permission_tab',
new_name='permission_tabs',
),
]

View File

@@ -33,7 +33,7 @@ class PermissionToTab(AbstractBaseModel):
class Permission(AbstractBaseModel):
name = models.CharField(max_length=200)
code = models.CharField(max_length=100, unique=True)
permission_tab = models.ManyToManyField(PermissionToTab, related_name='permissions')
permission_tabs = models.ManyToManyField(PermissionToTab, related_name='permissions')
def __str__(self):
return f'{self.name} - {self.code}'

View File

@@ -6,21 +6,50 @@ from core.apps.accounts.models.permission import PermissionToAction, PermissionT
class PermissionToActionSerializer(serializers.ModelSerializer):
class Meta:
model = PermissionToAction
fields = "__all__"
fields = ['id', 'name']
class PermissionToTabSerializer(serializers.ModelSerializer):
permission_to_actions = PermissionToActionSerializer(many=True, read_only=True)
class Meta:
model = PermissionToTab
fields = '__all__'
fields = ['id', 'name', 'permission_to_actions']
class PermissionSerializer(serializers.ModelSerializer):
permission_tabs = PermissionToTabSerializer(many=True, read_only=True)
class Meta:
model = Permission
fields = '__all__'
fields = ['id', 'name', 'permission_tabs']
class RoleSerializer(serializers.ModelSerializer):
class PermissionToActionListSerializer(serializers.ModelSerializer):
class Meta:
model = PermissionToAction
fields = ['id', 'name']
class PermissionToTabListSerializer(serializers.ModelSerializer):
class Meta:
model = PermissionToTab
fields = ['id', 'name']
class PermissionListSerializer(serializers.ModelSerializer):
class Meta:
model = Permission
fields = ['id', 'name']
class RoleListSerializer(serializers.ModelSerializer):
permissions = PermissionListSerializer(many=True)
permission_to_tabs = PermissionToTabListSerializer(many=True)
permission_to_actions = PermissionToActionListSerializer(many=True)
class Meta:
model = Role
fields = '__all__'
fields = [
'id', 'name', 'comment', 'permissions', 'permission_to_tabs', 'permission_to_actions',
]

View File

@@ -54,4 +54,22 @@ class UserCreateSerializer(serializers.ModelSerializer):
"first_name",
"last_name",
"password",
"role"]
"role"
]
class ShortUserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = [
'id',
'first_name',
'last_name',
'avatar',
]
def get_avatar(self, obj):
request = self.context.get('request')
if obj.avatar:
return request.build_absolute_uri(obj.avatar.url)
return None

View File

@@ -9,6 +9,7 @@ from .views import RegisterView, ResetPasswordView, MeView, ChangePasswordView,
from rest_framework.routers import DefaultRouter
from .views.permission import PermissionToActionViewSet, PermissionToTabViewSet, PermissionViewSet, RoleViewSet
from core.apps.accounts.views.user import DeleteAdminUserApiView, UserDetailAPIView
router = DefaultRouter()
router.register("auth", RegisterView, basename="auth")
@@ -26,13 +27,11 @@ urlpatterns = [
path("", include(router.urls)),
path("auth/token/", jwt_views.TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("auth/token/verify/", jwt_views.TokenVerifyView.as_view(), name="token_verify"),
path(
"auth/token/refresh/",
jwt_views.TokenRefreshView.as_view(),
name="token_refresh",
),
path("auth/token/refresh/",jwt_views.TokenRefreshView.as_view()),
path("user/list/", UserListApiView.as_view(), name="user-list"),
path("admin-user/list/", AdminUserListApiView.as_view(), name="admin-user-list"),
path("admin/create/", AdminCreateAPIView.as_view(), name="user-create"),
path("admin/update/", AdminUpdateAPIView.as_view(), name="user-update"),
path("admin/update/<int:pk>/", AdminUpdateAPIView.as_view(), name="user-update"),
path('user/admin/<int:pk>/delete/', DeleteAdminUserApiView.as_view(), name='user-delete'),
path('user/<int:pk>/', UserDetailAPIView.as_view(), name='user-detail'),
]

View File

@@ -5,7 +5,7 @@ from rest_framework.viewsets import ModelViewSet
from core.apps.accounts.models.permission import PermissionToAction, PermissionToTab, Permission, Role
from core.apps.accounts.serializers.permission import PermissionToActionSerializer, PermissionToTabSerializer, \
PermissionSerializer, RoleSerializer
PermissionSerializer, RoleListSerializer
@extend_schema(tags=["permission"])
@@ -30,7 +30,6 @@ class PermissionToTabViewSet(BaseViewSetMixin, ModelViewSet):
serializer_class = PermissionToTabSerializer
@extend_schema(tags=["permission"])
class PermissionViewSet(BaseViewSetMixin, ModelViewSet):
queryset = Permission.objects.all()
@@ -39,4 +38,4 @@ class PermissionViewSet(BaseViewSetMixin, ModelViewSet):
class RoleViewSet(BaseViewSetMixin, ModelViewSet):
queryset = Role.objects.all()
serializer_class = RoleSerializer
serializer_class = RoleListSerializer

View File

@@ -9,6 +9,8 @@ from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from core.apps.accounts.choices.user import RoleChoice
from core.apps.accounts.models import Role
from core.apps.accounts.serializers.permission import RoleListSerializer
from core.apps.accounts.serializers.user import UserSerializer, AdminUserSerializer, UserCreateSerializer
User = get_user_model()
@@ -64,11 +66,10 @@ class AdminCreateAPIView(APIView):
return Response(serializer.data, status=201)
@extend_schema(tags=['User'],
responses={200: UserSerializer},
request=UserCreateSerializer)
class AdminUpdateAPIView(APIView):
@extend_schema(tags=['User'], )
class AdminUpdateAPIView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
serializer_class = UserCreateSerializer
def put(self, request, pk):
if request.user.role not in (RoleChoice.SUPERUSER, RoleChoice.ADMIN):
@@ -80,3 +81,37 @@ class AdminUpdateAPIView(APIView):
serializer.save()
return Response(serializer.data, status=200)
class DeleteAdminUserApiView(APIView):
permission_classes = [IsAuthenticated]
def delete(self, request, pk):
if request.user.role != RoleChoice.SUPERUSER:
return Response({'detail': 'Forbidden'}, status=403)
user = get_object_or_404(User, pk=pk)
if user.role != RoleChoice.ADMIN:
return Response({'detail': 'This user is not an admin'}, status=400)
user.delete()
return Response(status=204)
class UserDetailAPIView(generics.RetrieveAPIView):
permission_classes = [IsAuthenticated]
serializer_class = UserSerializer
lookup_field = 'id'
class AdminPermissionsAPIView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
def get(self, request):
if request.user.role.name != RoleChoice.ADMIN:
return Response({'detail': 'Forbidden'}, status=403)
admin_role = Role.objects.get(name=RoleChoice.ADMIN)
serializer = RoleListSerializer(admin_role)
return Response(serializer.data)

View File

@@ -19,3 +19,8 @@ class RequestStatus(models.TextChoices):
IN_PROGRESS = "in_progress", _("Jarayonda")
COMPLETED = "completed", _("Bajarildi")
REJECTED = "rejected", _("Rad etildi")
class RequestPersonType(models.TextChoices):
INDIVIDUAL_PERSON = "individual_person", "Jismoniy shaxs"
LEGAL_PERSON = "legal_person", 'Yuridik shaxs',

View File

@@ -0,0 +1,53 @@
# Generated by Django 5.2.7 on 2026-04-28 11:07
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0035_autoevaluationmodel_is_archived'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name='autoevaluationmodel',
name='form_ownership',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='property_rights',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='rate_object_name',
),
migrations.AlterField(
model_name='autoevaluationmodel',
name='appraisers',
field=models.ManyToManyField(blank=True, null=True, to=settings.AUTH_USER_MODEL, verbose_name='appraisers'),
),
migrations.AlterField(
model_name='autoevaluationmodel',
name='evaluation_request',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='auto_evaluations_request', to='evaluation.evaluationrequestmodel'),
),
migrations.AlterField(
model_name='autoevaluationmodel',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='auto_evaluations_user', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='autoevaluationmodel',
name='valuation',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='auto_detail', to='evaluation.valuationmodel'),
),
migrations.AlterField(
model_name='autoevaluationmodel',
name='vehicle',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='evaluation', to='evaluation.vehiclemodel'),
),
]

View File

@@ -0,0 +1,49 @@
# Generated by Django 5.2.7 on 2026-04-28 11:41
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0036_remove_autoevaluationmodel_form_ownership_and_more'),
]
operations = [
migrations.AddField(
model_name='evaluationrequestmodel',
name='customer_and_owner_same',
field=models.BooleanField(default=False),
),
migrations.CreateModel(
name='EvaluationRequestCustomerModel',
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)),
('type', models.CharField(choices=[('individual_person', 'Jismoniy shaxs'), ('legal_person', 'Yuridik shaxs')], max_length=100)),
('jshshir', models.CharField(max_length=100)),
('evaluation_request', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='customer', to='evaluation.evaluationrequestmodel')),
],
options={
'verbose_name': 'Evaluation Request Customer',
'verbose_name_plural': 'Evaluation Request Customers',
},
),
migrations.CreateModel(
name='EvaluationRequestOwnerModel',
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)),
('type', models.CharField(choices=[('individual_person', 'Jismoniy shaxs'), ('legal_person', 'Yuridik shaxs')], max_length=100)),
('jshshir', models.CharField(max_length=100)),
('evaluation_request', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to='evaluation.evaluationrequestmodel')),
],
options={
'verbose_name': 'Evaluation Request Owner',
'verbose_name_plural': 'Evaluation Request Owners',
},
),
]

View File

@@ -0,0 +1,88 @@
# Generated by Django 5.2.7 on 2026-04-28 11:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0037_evaluationrequestmodel_customer_and_owner_same_and_more'),
]
operations = [
migrations.AddField(
model_name='evaluationrequestmodel',
name='distance_covered',
field=models.FloatField(blank=True, default=0.0, null=True),
),
migrations.AddField(
model_name='evaluationrequestmodel',
name='gov_number',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='chassi',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='customer_inn_number',
field=models.CharField(max_length=20),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='is_archive',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='location_lat',
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='location_lng',
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='location_name',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='need_delivering',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='object_type',
field=models.CharField(blank=True, choices=[('lightweight_auto', 'Yengil automobil'), ('truck_car', 'Yuk automobil'), ('special_tech', 'Maxsus texnika')], max_length=50, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='owner_inn_number',
field=models.CharField(max_length=20),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='rate_type',
field=models.CharField(choices=[('auto', 'Automobil'), ('real_estate', "Ko'chmas mulk"), ('equipment', 'Uskuna')], max_length=50),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='status',
field=models.CharField(choices=[('pending', 'Kutilmoqda'), ('in_progress', 'Jarayonda'), ('completed', 'Bajarildi'), ('rejected', 'Rad etildi')], default='pending', max_length=50),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='tex_passport',
field=models.CharField(blank=True, max_length=20, null=True),
),
migrations.AlterField(
model_name='evaluationrequestmodel',
name='worked_hours',
field=models.IntegerField(blank=True, null=True),
),
]

View File

@@ -26,7 +26,6 @@ class AutoEvaluationModel(AbstractBaseModel):
"accounts.User",
on_delete=models.SET_NULL,
related_name="auto_evaluations_user",
verbose_name=_("user"),
null=True,
blank=True,
)
@@ -34,7 +33,6 @@ class AutoEvaluationModel(AbstractBaseModel):
"evaluation.EvaluationRequestModel",
on_delete=models.SET_NULL,
related_name="auto_evaluations_request",
verbose_name=_("evaluation request"),
null=True,
blank=True,
)
@@ -42,7 +40,6 @@ class AutoEvaluationModel(AbstractBaseModel):
ValuationModel,
on_delete=models.CASCADE,
related_name="auto_detail",
verbose_name=_("valuation"),
null=True,
blank=True,
)
@@ -50,14 +47,12 @@ class AutoEvaluationModel(AbstractBaseModel):
VehicleModel,
on_delete=models.CASCADE,
related_name="evaluation",
verbose_name=_("vehicle"),
null=True,
blank=True,
)
appraisers = models.ManyToManyField(
"accounts.User",
verbose_name=_("appraisers"),
related_name="auto_evaluations",
blank=True,
null=True,
)
@@ -96,12 +91,6 @@ class AutoEvaluationModel(AbstractBaseModel):
blank=True,
null=True,
)
rate_object_name = models.CharField(
verbose_name=_("rate object name"),
max_length=255,
blank=True,
null=True,
)
object_type = models.CharField(
verbose_name=_("object type"),
max_length=50,
@@ -153,23 +142,6 @@ class AutoEvaluationModel(AbstractBaseModel):
blank=True,
null=True,
)
property_rights = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("property rights"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='evaluation_auto_property_rights'
)
form_ownership = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("form of ownership"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='evaluation_auto_form_ownership'
)
value_determined = models.ForeignKey(
'evaluation.ReferenceitemModel',
verbose_name=_("value determined"),

View File

@@ -8,64 +8,41 @@ from core.apps.evaluation.choices.request import (
EvaluationRateType,
RequestObjectType,
RequestStatus,
RequestPersonType,
)
from core.apps.evaluation.models import ReferenceitemModel
class EvaluationrequestModel(AbstractBaseModel):
# request sender
rate_type = models.CharField(max_length=50,choices=EvaluationRateType.choices)
object_type = models.CharField(max_length=50,choices=RequestObjectType.choices,blank=True,null=True)
status = models.CharField(max_length=50, choices=RequestStatus.choices, default=RequestStatus.PENDING)
distance_covered = models.FloatField(default=0.0, null=True, blank=True)
worked_hours = models.IntegerField(blank=True,null=True)
customer_inn_number = models.CharField(max_length=20)
owner_inn_number = models.CharField(max_length=20)
tex_passport = models.CharField(max_length=20,blank=True,null=True)
chassi = models.CharField(max_length=100,blank=True,null=True)
gov_number = models.CharField(max_length=100, null=True, blank=True)
location_name = models.CharField(max_length=255,blank=True,null=True)
location_lat = models.DecimalField(max_digits=9,decimal_places=6,blank=True, null=True)
location_lng = models.DecimalField(max_digits=9,decimal_places=6,blank=True,null=True)
need_delivering = models.BooleanField(default=True)
is_archive = models.BooleanField(default=False)
customer_and_owner_same = models.BooleanField(default=False)
###################
# Foreign Keys
###################
user = models.ForeignKey(
"accounts.User",
on_delete=models.CASCADE,
related_name="evaluation_requests",
verbose_name=_("user"),
)
# request type -> "Automobil", "Ko'chmas mulk", "Uskuna"
rate_type = models.CharField(
verbose_name=_("rate type"),
max_length=50,
choices=EvaluationRateType.choices,
)
###################
# Automobil fields
###################
object_type = models.CharField(
verbose_name=_("object type"),
max_length=50,
choices=RequestObjectType.choices,
blank=True,
null=True,
)
customer_inn_number = models.CharField(
verbose_name=_("customer INN number"),
max_length=20,
)
owner_inn_number = models.CharField(
verbose_name=_("owner INN number"),
max_length=20,
)
tex_passport = models.CharField(
verbose_name=_("tex passport"),
max_length=20,
blank=True,
null=True,
)
worked_hours = models.IntegerField(
verbose_name=_("worked hours"),
blank=True,
null=True,
)
chassi = models.CharField(
verbose_name=_("chassi"),
max_length=100,
blank=True,
null=True,
)
###################
# Value fields
###################
value_determined = models.ForeignKey(
"evaluation.ReferenceitemModel",
verbose_name=_("value determined"),
@@ -99,47 +76,6 @@ class EvaluationrequestModel(AbstractBaseModel):
null=True,
)
###################
# Location fields
###################
location_name = models.CharField(
verbose_name=_("location name"),
max_length=255,
blank=True,
null=True,
)
location_lat = models.DecimalField(
verbose_name=_("location latitude"),
max_digits=9,
decimal_places=6,
blank=True,
null=True,
)
location_lng = models.DecimalField(
verbose_name=_("location longitude"),
max_digits=9,
decimal_places=6,
blank=True,
null=True,
)
###################
# Other fields
###################
need_delivering = models.BooleanField(
verbose_name=_("need delivering"),
default=True,
)
status = models.CharField(
verbose_name=_("status"),
max_length=50,
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()}"
@@ -166,3 +102,29 @@ class EvaluationrequestModel(AbstractBaseModel):
db_table = "EvaluationRequest"
verbose_name = _("Evaluation Request")
verbose_name_plural = _("Evaluation Requests")
class EvaluationRequestOwnerModel(AbstractBaseModel):
evaluation_request = models.OneToOneField(EvaluationrequestModel, on_delete=models.CASCADE, related_name='owner')
type = models.CharField(max_length=100, choices=RequestPersonType.choices)
jshshir = models.CharField(max_length=100)
def __str__(self):
return f"Owner #{self.pk}{self.type}"
class Meta:
verbose_name = _("Evaluation Request Owner")
verbose_name_plural = _("Evaluation Request Owners")
class EvaluationRequestCustomerModel(AbstractBaseModel):
evaluation_request = models.OneToOneField(EvaluationrequestModel, on_delete=models.CASCADE, related_name='customer')
type = models.CharField(max_length=100, choices=RequestPersonType.choices)
jshshir = models.CharField(max_length=100)
def __str__(self):
return f"Customer #{self.pk}{self.type}"
class Meta:
verbose_name = _("Evaluation Request Customer")
verbose_name_plural = _("Evaluation Request Customers")

View File

@@ -17,8 +17,6 @@ class BaseAutoevaluationSerializer(serializers.ModelSerializer):
default=None)
rate_type = ListReferenceitemSerializer(read_only=True)
value_determined = ListReferenceitemSerializer(read_only=True)
property_rights = ListReferenceitemSerializer(read_only=True)
form_ownership = ListReferenceitemSerializer(read_only=True)
user = serializers.SerializerMethodField(method_name="get_user", read_only=True)
class Meta:
@@ -49,8 +47,6 @@ class BaseAutoevaluationSerializer(serializers.ModelSerializer):
"created_at",
"value_determined",
"rate_type",
"property_rights",
"form_ownership",
"user",
"evaluation_request",
]
@@ -75,13 +71,6 @@ 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
# )
# object_location_covenience_display = serializers.CharField(
# source="get_object_location_covenience_display", read_only=True, default=None
# )
class Meta(BaseAutoevaluationSerializer.Meta):
fields = BaseAutoevaluationSerializer.Meta.fields + [
# Step 1
@@ -89,7 +78,6 @@ class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
"object_inspection_date",
"rate_date",
"rate_report_date",
"rate_object_name",
# Step 2
"object_owner_type",
"object_owner_type_display",
@@ -118,21 +106,11 @@ class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
property_rights = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
form_ownership = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
@@ -153,7 +131,6 @@ class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
"object_inspection_date",
"rate_date",
"rate_report_date",
"rate_object_name",
"object_type",
# Step 2
"object_owner_type",
@@ -163,8 +140,6 @@ class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_individual_person_passport_num",
"object_owner_legal_entity",
"object_owner_legal_inn",
"property_rights",
"form_ownership",
"value_determined",
"rate_type",
# Step 4
@@ -226,21 +201,11 @@ class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
class CreateAutoevaluationSerializer(serializers.ModelSerializer):
property_rights = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
form_ownership = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
@@ -267,7 +232,6 @@ class CreateAutoevaluationSerializer(serializers.ModelSerializer):
"object_inspection_date",
"rate_date",
"rate_report_date",
"rate_object_name",
"object_type",
# Step 2
"object_owner_type",
@@ -277,8 +241,6 @@ class CreateAutoevaluationSerializer(serializers.ModelSerializer):
"object_owner_individual_person_passport_num",
"object_owner_legal_entity",
"object_owner_legal_inn",
"property_rights",
"form_ownership",
"value_determined",
"rate_type",
# Step 4
@@ -367,4 +329,9 @@ class AutoEvaluationSerializer(serializers.Serializer):
transmission = serializers.CharField()
condition = serializers.CharField()
fuel_type = serializers.CharField()
mileage = serializers.CharField()
mileage = serializers.CharField()
class AutoEvaluationModelSerializer(serializers.ModelSerializer):
class Meta:
model = AutoEvaluationModel
fields = "__all__"

View File

@@ -3,7 +3,7 @@ from core.apps.evaluation.models import CertificateModel
class BaseCertificateSerializer(serializers.ModelSerializer):
file = serializers.SerializerMethodField()
file = serializers.SerializerMethodField(method_name='get_file', read_only=True)
class Meta:
model = CertificateModel
@@ -16,7 +16,15 @@ class BaseCertificateSerializer(serializers.ModelSerializer):
def get_file(self, obj):
if obj.file:
request = self.context.get('request')
if request:
return request.build_absolute_uri(obj.file.url)
return obj.file.url
return None
return request.build_absolute_uri(obj.file.url)
return None
class CreateCertificateSerializer(serializers.ModelSerializer):
class Meta:
model = CertificateModel
fields = [
"id",
"title",
"file",
]

View File

@@ -128,3 +128,7 @@ class CreateQuickevaluationSerializer(serializers.ModelSerializer):
return super().create(validated_data)
class QuickEvaluationModelSerializer(serializers.ModelSerializer):
class Meta:
model = QuickEvaluationModel
fields = '__all__'

View File

@@ -4,8 +4,11 @@ from django.contrib.auth import get_user_model
from rest_framework import serializers
from core.apps.evaluation.models import EvaluationrequestModel, ReferenceitemModel
from core.apps.evaluation.models import EvaluationrequestModel, ReferenceitemModel, EvaluationRequestOwnerModel, EvaluationRequestCustomerModel
from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer
from core.apps.evaluation.serializers.request.owner import EvaluationRequestOwnerSerializer
from core.apps.evaluation.serializers.request.req_customer import EvaluationRequestCustomerSerializer
User = get_user_model()
@@ -29,6 +32,8 @@ class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
property_rights = ListReferenceitemSerializer(read_only=True)
form_ownership = ListReferenceitemSerializer(read_only=True)
user = serializers.SerializerMethodField(method_name="get_user")
customer = EvaluationRequestCustomerSerializer(read_only=True)
owner = EvaluationRequestOwnerSerializer(read_only=True)
class Meta:
model = EvaluationrequestModel
@@ -56,6 +61,8 @@ class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
"created_at",
"updated_at",
"is_archive",
"customer",
"owner",
]
def get_location(self, obj):
@@ -113,6 +120,8 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
rate_goal = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
property_rights = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
form_ownership = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
customer = EvaluationRequestCustomerSerializer()
owner = EvaluationRequestOwnerSerializer()
class Meta:
model = EvaluationrequestModel
@@ -131,6 +140,11 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
"need_delivering",
"location",
"locationName",
"customer",
"owner",
"customer_and_owner_same",
"distance_covered",
"gov_number"
]
def validate_tex_passport(self, value):
@@ -179,8 +193,32 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
if location_name:
validated_data["location_name"] = str(location_name)
validated_data["user"] = self.context["request"].user
return super().create(validated_data)
instance = super().create(validated_data)
customer = validated_data.pop("customer", None)
owner = validated_data.pop("owner", None)
EvaluationRequestCustomerModel.objects.create(
evaluation_request=instance,
type=customer.get("type"),
jshshir=customer.get("jshshir")
)
if not instance.customer_and_owner_same:
EvaluationRequestOwnerModel.objects.create(
evaluation_request=instance,
type=owner.get("type"),
jshshir=owner.get("jshshir")
)
else:
EvaluationRequestOwnerModel.objects.create(
evaluation_request=instance,
type=customer.get("type"),
jshshir=customer.get("jshshir")
)
return instance
class ArchiveEvaluationrequestSerializer(serializers.Serializer):
id = serializers.IntegerField(required=True)
is_archive = serializers.BooleanField(required=True)
is_archive = serializers.BooleanField(required=True)

View File

@@ -1 +1 @@
from .EvaluationRequest import * # noqa
from .EvaluationRequest import * # noqa

View File

@@ -0,0 +1,9 @@
from rest_framework import serializers
from core.apps.evaluation.models.request import EvaluationRequestOwnerModel
class EvaluationRequestOwnerSerializer(serializers.ModelSerializer):
class Meta:
model = EvaluationRequestOwnerModel
fields = ["id", "evaluation_request", "type", "jshshir"]

View File

@@ -0,0 +1,9 @@
from rest_framework import serializers
from core.apps.evaluation.models.request import EvaluationRequestCustomerModel
class EvaluationRequestCustomerSerializer(serializers.ModelSerializer):
class Meta:
model = EvaluationRequestCustomerModel
fields = ["id", "evaluation_request", "type", "jshshir"]

View File

@@ -37,6 +37,7 @@ urlpatterns = [
# Quick Evaluation
path('quick-evaluation/', include(
[
path("admin/", views.AdminQuickEvalAPIView.as_view(), name="quick-evaluation"),
path(
'archive/', include(
[
@@ -51,6 +52,7 @@ urlpatterns = [
# Auto Evaluation
path("auto-evaluation/", include(
[
path("admin/", views.AdminEvaluationsAPIView.as_view(), name="admin-evaluations"),
path('archive/', include(
[
path('<int:pk>/', views.AutoEvaluationArchiveAPIView.as_view()),

View File

@@ -1,10 +1,9 @@
from django.db.models import Q
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 import generics
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.generics import GenericAPIView, ListAPIView
from rest_framework.permissions import AllowAny, IsAuthenticated
@@ -12,10 +11,12 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from core.apps.accounts.choices import RoleChoice
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 auto as serializers
from core.apps.evaluation.serializers import auto as serializers, AutoEvaluationModelSerializer
@extend_schema(tags=["AutoEvaluation"])
class AutoEvaluationView(BaseViewSetMixin, ModelViewSet):
@@ -23,7 +24,7 @@ class AutoEvaluationView(BaseViewSetMixin, ModelViewSet):
"valuation",
"valuation__customer",
"vehicle",
).all()
).filter(is_archived=False)
serializer_class = serializers.ListAutoevaluationSerializer
permission_classes = [AllowAny]
@@ -62,8 +63,6 @@ class AutoEvaluationView(BaseViewSetMixin, ModelViewSet):
"created_at",
"value_determined",
"rate_type",
"property_rights",
"form_ownership",
]
ordering = ["-created_at"]
@@ -177,3 +176,16 @@ class AutoEvaluationArchiveAPIView(APIView):
},
status=200
)
@extend_schema(tags=["AutoEvaluation"])
class AdminEvaluationsAPIView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
def get(self, request):
if request.user.role != RoleChoice.ADMIN:
return Response({'detail': 'Forbidden'}, status=403)
auto_eval = AutoEvaluationModel.objects.filter(
created_by=self.request.user
).select_related('appraisers').distinct()
serializer = AutoEvaluationModelSerializer(auto_eval, many=True)
return Response(serializer.data)

View File

@@ -12,7 +12,7 @@ from rest_framework.parsers import MultiPartParser, FormParser
# local apps
from core.apps.evaluation.models import CertificateModel
from core.apps.evaluation.serializers.certificate import BaseCertificateSerializer
from core.apps.evaluation.serializers.certificate import BaseCertificateSerializer, CreateCertificateSerializer
@extend_schema(tags=["Certificate"],request=BaseCertificateSerializer)
@@ -25,3 +25,6 @@ class CertificateView(BaseViewSetMixin, ModelViewSet):
search_fields = ["title"]
pagination_class = None
action_permission_classes = {}
action_serializer_class = {
"create": CreateCertificateSerializer
}

View File

@@ -1,17 +1,13 @@
# django
from django.shortcuts import get_object_or_404
# django core
from django_core.mixins import BaseViewSetMixin
# django filters
from django_filters.rest_framework import DjangoFilterBackend
# swagger
from drf_spectacular.utils import extend_schema
# rest framework
from rest_framework import status
from rest_framework import status, generics
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.generics import ListAPIView
from rest_framework.parsers import FormParser, MultiPartParser
@@ -20,10 +16,11 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from core.apps.accounts.choices import RoleChoice
# core apps
from core.apps.evaluation.filters.quick import QuickevaluationFilter
from core.apps.evaluation.models import QuickEvaluationModel
from core.apps.evaluation.serializers import quick as serializers
from core.apps.evaluation.serializers import quick as serializers, QuickEvaluationModelSerializer
@extend_schema(tags=["QuickEvaluation"])
@@ -83,6 +80,22 @@ class QuickEvaluationArchiveAPIView(APIView):
@extend_schema(tags=["QuickEvaluation"])
class QuickEvaluationArchivedListAPIView(ListAPIView):
permission_classes = [IsAuthenticated]
serializer_class = serializers.ListQuickevaluationSerializer
def get_queryset(self):
return QuickEvaluationModel.objects.filter(is_archive=True)
@extend_schema(tags=["QuickEvaluation"])
class AdminQuickEvalAPIView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]
def get(self, request):
if request.user.role != RoleChoice.ADMIN:
return Response({'detail': 'Forbidden'}, status=403)
quick_eval = QuickEvaluationModel.objects.filter(
created_by=self.request.user
).select_related('created_by').distinct()
serializer = QuickEvaluationModelSerializer(quick_eval, many=True)
return Response(serializer.data)

View File

@@ -72,7 +72,7 @@ class EvaluationrequestView(BaseViewSetMixin, viewsets.ModelViewSet):
def get_queryset(self):
return EvaluationrequestModel.objects.filter(
user=self.request.user
user=self.request.user, is_archive=False
).order_by("-created_at")
@@ -171,7 +171,7 @@ class RequestEvaluationArchivedListAPIView(generics.ListAPIView):
serializer_class = serializers.ListEvaluationrequestSerializer
def get_queryset(self):
return EvaluationrequestModel.objects.filter(is_archived=True)
return EvaluationrequestModel.objects.filter(is_archive=True)
@extend_schema(tags=["EvaluationRequest"])
@@ -181,7 +181,7 @@ class RequestEvaluationArchiveAPIView(views.APIView):
@transaction.atomic
def post(self, request, pk):
req_evaluation = get_object_or_404(EvaluationrequestModel, pk=pk)
req_evaluation.is_archived = request.data["is_archived"]
req_evaluation.is_archive = request.data["is_archived"]
req_evaluation.save()
return Response(
{

View File

@@ -9,7 +9,7 @@ from drf_spectacular.utils import extend_schema
# core apps
from core.services.tech_passport import TechPassportService
from core.apps.serializers import TechPassportSerializer
from core.apps.evaluation.serializers import TechPassportSerializer
class TechPassportAPIView(GenericAPIView):

View File

View File

@@ -0,0 +1,4 @@
from .column import *
from .comment import *
from .task import *
from .label import *

View File

@@ -0,0 +1,7 @@
from django.contrib import admin
from core.apps.tasks.models import Column
@admin.register(Column)
class ColumnAdmin(admin.ModelAdmin):
list_display = ('name',)

View File

@@ -0,0 +1,7 @@
from django.contrib import admin
from core.apps.tasks.models import Comment
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('created_by', 'type')

View File

@@ -0,0 +1,7 @@
from django.contrib import admin
from core.apps.tasks.models import Label
@admin.register(Label)
class LabelAdmin(admin.ModelAdmin):
list_display = ('name',)

View File

@@ -0,0 +1,7 @@
from django.contrib import admin
from core.apps.tasks.models import Task
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_display = ('name', 'created_by', 'priority')

8
core/apps/tasks/apps.py Normal file
View File

@@ -0,0 +1,8 @@
from django.apps import AppConfig
class TasksConfig(AppConfig):
name = "core.apps.tasks"
def ready(self):
from core.apps.tasks import admin

View File

@@ -0,0 +1,6 @@
from django.db import models
class MessageChoice(models.TextChoices):
FILE = "file", "File"
TEXT = "text", "Text"

View File

@@ -0,0 +1,7 @@
from django.db import models
class PriorityChoice(models.TextChoices):
LOW = "low", "Low"
MEDIUM = "medium", "Medium"
HIGH = "high", "High"

View File

@@ -0,0 +1,77 @@
# Generated by Django 5.2.7 on 2026-04-29 13:18
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Column',
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=255)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Label',
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=255)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Task',
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=255)),
('description', models.TextField(blank=True)),
('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], max_length=255)),
('from_date', models.DateField(blank=True, null=True)),
('to_date', models.DateField(blank=True, null=True)),
('assignees', models.ManyToManyField(related_name='assigned_tasks', to=settings.AUTH_USER_MODEL)),
('column', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='tasks.column')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_tasks', to=settings.AUTH_USER_MODEL)),
('labels', models.ManyToManyField(related_name='tasks', to='tasks.label')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Comment',
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)),
('message', models.TextField()),
('file', models.FileField(blank=True, null=True, upload_to='comments/')),
('type', models.CharField(choices=[('file', 'File'), ('text', 'Text')], max_length=255)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_comments', to=settings.AUTH_USER_MODEL)),
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='tasks.task')),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.7 on 2026-04-29 13:20
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tasks', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='comment',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL),
),
]

View File

View File

@@ -0,0 +1,4 @@
from .column import *
from .comment import *
from .task import *
from .label import *

View File

@@ -0,0 +1,10 @@
from django.db import models
from django_core.models import AbstractBaseModel
class Column(AbstractBaseModel):
name = models.CharField(max_length=255)
def __str__(self):
return self.name

View File

@@ -0,0 +1,16 @@
from django.db import models
from django_core.models import AbstractBaseModel
from core.apps.tasks.choices.comment import MessageChoice
class Comment(AbstractBaseModel):
task = models.ForeignKey('tasks.Task', on_delete=models.CASCADE, related_name='comments')
message = models.TextField()
file = models.FileField(upload_to='comments/', blank=True, null=True)
type = models.CharField(max_length=255, choices=MessageChoice.choices)
created_by = models.ForeignKey('accounts.User', on_delete=models.CASCADE, related_name='comments')
def __str__(self):
return f"{self.content} created by {self.created_by}"

View File

@@ -0,0 +1,10 @@
from django.db import models
from django_core.models import AbstractBaseModel
class Label(AbstractBaseModel):
name = models.CharField(max_length=255)
def __str__(self):
return self.name

View File

@@ -0,0 +1,20 @@
from django.db import models
from django_core.models import AbstractBaseModel
from core.apps.tasks.choices.task import PriorityChoice
class Task(AbstractBaseModel):
column = models.ForeignKey('tasks.Column', on_delete=models.CASCADE, related_name='tasks')
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
priority = models.CharField(max_length=255, choices=PriorityChoice.choices)
from_date = models.DateField(null=True, blank=True)
to_date = models.DateField(null=True, blank=True)
labels = models.ManyToManyField('tasks.Label', related_name='tasks')
assignees = models.ManyToManyField('accounts.User', related_name='assigned_tasks')
created_by = models.ForeignKey('accounts.User', on_delete=models.CASCADE, related_name='created_tasks')
def __str__(self):
return f"{self.name} created by {self.created_by}"

View File

@@ -0,0 +1,22 @@
from rest_framework import serializers
from core.apps.tasks.serializers.comment import CommentSerializer
from core.apps.tasks.serializers.task import TaskSerializer
from core.apps.tasks.models import Column, Task
class BoardTaskSerializer(TaskSerializer):
comments = CommentSerializer(many=True, read_only=True)
class Meta(TaskSerializer.Meta):
TaskSerializer.Meta.fields += ['comments']
class BoardSerializer(serializers.ModelSerializer):
tasks = BoardTaskSerializer(many=True, read_only=True)
class Meta:
model = Column
fields = [
'id', 'name', 'tasks',
]

View File

@@ -0,0 +1,11 @@
from rest_framework import serializers
from core.apps.tasks.models.column import Column
class ColumnSerializer(serializers.ModelSerializer):
class Meta:
model = Column
fields = [
'id', 'name'
]

View File

@@ -0,0 +1,44 @@
from django.db import transaction
from rest_framework import serializers
from core.apps.tasks.models.comment import Comment
from core.apps.tasks.models.task import Task
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = [
'id', 'message', 'file', 'type', 'created_by'
]
def get_created_by(self, obj):
request = self.context.get('request')
return {
"id": obj.created_by.id,
"first_name": obj.created_by.first_name,
"last_name": obj.created_by.last_name,
"avatar": request.build_absolute_uri(obj.created_by.avatar.url) if obj.created_by.avatar else None
}
class CommentCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = [
'id', 'message', 'file', 'type', 'task'
]
def validate(self, data):
task = Task.objects.filter(id=data['task']).first()
if not task:
raise serializers.ValidationError("Task not found")
data['task'] = task
return data
def create(self, validated_data):
with transaction.atomic():
task = validated_data.pop('task')
comment = Comment.objects.create(task=task, created_by=self.context['request'].user, **validated_data)
return comment

View File

@@ -0,0 +1,11 @@
from rest_framework import serializers
from core.apps.tasks.models.label import Label
class LabelSerializer(serializers.ModelSerializer):
class Meta:
model = Label
fields = [
'id', 'name'
]

View File

@@ -0,0 +1,52 @@
from rest_framework import serializers
from core.apps.tasks.models.task import Task
from core.apps.accounts.serializers.user import ShortUserSerializer
from core.apps.tasks.serializers.label import LabelSerializer
class TaskSerializer(serializers.ModelSerializer):
labels = LabelSerializer(many=True)
assignees = serializers.SerializerMethodField(method_name='get_assignees')
created_by = serializers.SerializerMethodField(method_name='get_created_by')
class Meta:
model = Task
fields = [
'id',
'column',
'name',
'description',
'priority',
'from_date',
'to_date',
'labels',
'assignees',
'created_by'
]
def get_assignees(self, obj):
return ShortUserSerializer(obj.assignees.all(), many=True, context={"request": self.context['request']}).data
def get_created_by(self, obj):
return ShortUserSerializer(obj.created_by, context={"request": self.context['request']}).data
class TaskCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = [
'id',
'column',
'name',
'description',
'priority',
'from_date',
'to_date',
'labels',
'assignees',
]
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)

34
core/apps/tasks/urls.py Normal file
View File

@@ -0,0 +1,34 @@
from django.urls import path, include
from core.apps.tasks.views import task, column, comment, label, board
urlpatterns = [
path('column/', include(
[
path('list/', column.ColumnListApiView.as_view()),
path('create/', column.ColumnCreateApiView.as_view()),
path('<int:id>/update/', column.ColumnUpdateApiView.as_view()),
path('<int:id>/delete/', column.ColumnDeleteApiView.as_view())
]
)),
path('label/', include(
[
path('', label.LabelListCreateApiView.as_view()),
path('<int:id>/', label.LabelRetrieveUpdateDestroyApiView.as_view()),
]
)),
path('task/', include(
[
path('list/', task.TaskListView.as_view()),
path('<int:id>/', task.TaskDetailView.as_view()),
path('create/', task.TaskCreateView.as_view()),
]
)),
path('comment/', include(
[
path('', comment.CommentListCreateAPIView.as_view()),
path('<int:pk>/', comment.CommentDetailAPIView.as_view()),
]
)),
path('board/', board.BoardListView.as_view()),
]

View File

@@ -0,0 +1,13 @@
from rest_framework import generics, permissions
from rest_framework.response import Response
from core.apps.tasks.serializers.board import BoardSerializer
from core.apps.tasks.models import Column
class BoardListView(generics.ListAPIView):
queryset = Column.objects.order_by('id')
serializer_class = BoardSerializer
permission_classes = [permissions.IsAuthenticated]

View File

@@ -0,0 +1,47 @@
from django.db import transaction
from rest_framework import generics, permissions
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from core.apps.tasks.serializers.column import ColumnSerializer
from core.apps.tasks.models.column import Column
@extend_schema(tags=['Tasks'])
class ColumnCreateApiView(generics.GenericAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ColumnSerializer
@transaction.atomic
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
column = serializer.save()
return Response(serializer.data)
@extend_schema(tags=['Tasks'])
class ColumnListApiView(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ColumnSerializer
def get_queryset(self):
return Column.objects.order_by('id')
@extend_schema(tags=['Tasks'])
class ColumnUpdateApiView(generics.UpdateAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ColumnSerializer
lookup_field = 'id'
queryset = Column.objects.all()
@extend_schema(tags=['Tasks'])
class ColumnDeleteApiView(generics.DestroyAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = ColumnSerializer
lookup_field = 'id'
queryset = Column.objects.all()

View File

@@ -0,0 +1,51 @@
from rest_framework import generics, permissions
from rest_framework.exceptions import PermissionDenied
from core.apps.tasks.models.comment import Comment
from core.apps.tasks.serializers.comment import CommentSerializer, CommentCreateSerializer
class CommentListCreateAPIView(generics.ListCreateAPIView):
queryset = Comment.objects.all()
permission_classes = [permissions.IsAuthenticated]
def get_serializer_class(self):
if self.request.method == 'POST':
return CommentCreateSerializer
return CommentSerializer
def get_queryset(self):
task_id = self.request.query_params.get('task_id')
queryset = self.queryset
if task_id:
queryset = queryset.filter(task_id=task_id)
return queryset.order_by('-id')
def get_serializer_context(self):
return {"request": self.request}
class CommentDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Comment.objects.all()
permission_classes = [permissions.IsAuthenticated]
def get_serializer_class(self):
if self.request.method in ['PUT', 'PATCH']:
return CommentCreateSerializer
return CommentSerializer
def get_serializer_context(self):
return {"request": self.request}
def perform_update(self, serializer):
comment = self.get_object()
if comment.created_by != self.request.user:
raise PermissionDenied("You cannot edit this comment")
serializer.save()
def perform_destroy(self, instance):
if instance.created_by != self.request.user:
raise PermissionDenied("You cannot delete this comment")
instance.delete()

View File

@@ -0,0 +1,22 @@
from rest_framework import generics, permissions
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from core.apps.tasks.serializers.label import LabelSerializer
from core.apps.tasks.models.label import Label
@extend_schema(tags=['Tasks'])
class LabelListCreateApiView(generics.ListCreateAPIView):
queryset = Label.objects.order_by('id')
serializer_class = LabelSerializer
permission_classes = [permissions.IsAuthenticated]
@extend_schema(tags=['Tasks'])
class LabelRetrieveUpdateDestroyApiView(generics.RetrieveUpdateDestroyAPIView):
queryset = Label.objects.order_by('id')
serializer_class = LabelSerializer
permission_classes = [permissions.IsAuthenticated]
lookup_field = 'id'

View File

@@ -0,0 +1,40 @@
from django.db import transaction
from rest_framework import permissions, generics
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema
from core.apps.tasks.models.task import Task
from core.apps.tasks.serializers.task import TaskSerializer, TaskCreateSerializer
@extend_schema(tags=['Tasks'])
class TaskCreateView(generics.GenericAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = TaskCreateSerializer
queryset = Task.objects.order_by('id')
@transaction.atomic
def post(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
@extend_schema(tags=['Tasks'])
class TaskListView(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = TaskSerializer
queryset = Task.objects.order_by('id')
def serializer_context(self):
return self.serializer_class(context={"request": self.request})
@extend_schema(tags=['Tasks'])
class TaskDetailView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = TaskSerializer
queryset = Task.objects.order_by('id')

View File

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