diff --git a/core/apps/accounts/migrations/0003_role_comment.py b/core/apps/accounts/migrations/0003_role_comment.py new file mode 100644 index 0000000..c6055d4 --- /dev/null +++ b/core/apps/accounts/migrations/0003_role_comment.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-12-11 11:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_role_user_role'), + ] + + operations = [ + migrations.AddField( + model_name='role', + name='comment', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/core/apps/accounts/models/role.py b/core/apps/accounts/models/role.py index 0b46e49..7f36255 100644 --- a/core/apps/accounts/models/role.py +++ b/core/apps/accounts/models/role.py @@ -7,6 +7,7 @@ from core.apps.shared.models import BaseModel class Role(BaseModel): name = models.CharField(max_length=200, unique=True, db_index=True) + comment = models.TextField(null=True, blank=True) def __str__(self): return self.name diff --git a/core/apps/accounts/serializers/role/create.py b/core/apps/accounts/serializers/role/create.py new file mode 100644 index 0000000..48a4383 --- /dev/null +++ b/core/apps/accounts/serializers/role/create.py @@ -0,0 +1,26 @@ +# django +from django.db import transaction + +# rest framework +from rest_framework import serializers + + +# accounts +from core.apps.accounts.models import Role + + +class CreateRoleSerializer(serializers.Serializer): + name = serializers.CharField() + comment = serializers.CharField(required=False) + + def validate(self, data): + if Role.objects.filter(name=data['name']).exists(): + raise serializers.ValidationError({"name": "Role with this name already exists"}) + return data + + def create(self, validated_data): + with transaction.atomic(): + return Role.objects.create( + name=validated_data.get('name'), + comment=validated_data.get('comment'), + ) \ No newline at end of file diff --git a/core/apps/accounts/serializers/role/list.py b/core/apps/accounts/serializers/role/list.py new file mode 100644 index 0000000..8074fac --- /dev/null +++ b/core/apps/accounts/serializers/role/list.py @@ -0,0 +1,18 @@ +# rest framework +from rest_framework import serializers + + +# accounts +from core.apps.accounts.models import Role + + +class ListRoleSerializer(serializers.ModelSerializer): + class Meta: + model = Role + fields = [ + 'id', + 'name', + 'comment', + 'created_at', + 'updated_at', + ] \ No newline at end of file diff --git a/core/apps/accounts/serializers/role/update.py b/core/apps/accounts/serializers/role/update.py new file mode 100644 index 0000000..0821345 --- /dev/null +++ b/core/apps/accounts/serializers/role/update.py @@ -0,0 +1,26 @@ +# django +from django.db import transaction + +# rest framework +from rest_framework import serializers + + +# accounts +from core.apps.accounts.models import Role + + +class UpdateRoleSerializer(serializers.Serializer): + name = serializers.CharField() + comment = serializers.CharField() + + def validate(self, data): + if Role.objects.filter(name=data['name']).exists(): + raise serializers.ValidationError({"name": "Role with this name already exists"}) + return data + + def update(self, instance, validated_data): + with transaction.atomic(): + instance.name = validated_data.get('name', instance.name) + instance.comment = validated_data.get('comment', instance.comment) + instance.save() + return instance \ No newline at end of file diff --git a/core/apps/accounts/urls.py b/core/apps/accounts/urls.py index 43a9ff3..bae0b8a 100644 --- a/core/apps/accounts/urls.py +++ b/core/apps/accounts/urls.py @@ -6,17 +6,21 @@ from rest_framework.routers import DefaultRouter # accounts -# ------- user ------ +# ------- user ------- from core.apps.accounts.views.user import UserViewSet from core.apps.accounts.views.user.create import CreateUserApiView from core.apps.accounts.views.user.list import ListUserApiView from core.apps.accounts.views.user.update import UpdateUserApiView from core.apps.accounts.views.user.delete import SoftDeleteUserApiView, HardDeleteUserApiView -# ------- auth ------ +# ------- auth ------- from core.apps.accounts.views.auth.login import LoginApiView +# ------- role ------- +from core.apps.accounts.views.role.create import CreateRoleApiView +from core.apps.accounts.views.role.list import ListRoleApiView urlpatterns = [ + # ------ user ------ path('user/', include( [ path('create/', CreateUserApiView.as_view(), name='user-create-api'), @@ -32,6 +36,13 @@ urlpatterns = [ path('login/', LoginApiView.as_view(), name='login'), ] )), + # ------ role ------ + path('role/', include( + [ + path('create/', CreateRoleApiView.as_view(), name='create-role-api'), + path('list/', ListRoleApiView.as_view(), name='list-role-api'), + ] + )), ] router = DefaultRouter() diff --git a/core/apps/accounts/views/role/create.py b/core/apps/accounts/views/role/create.py new file mode 100644 index 0000000..9153b9a --- /dev/null +++ b/core/apps/accounts/views/role/create.py @@ -0,0 +1,110 @@ +# rest framework +from rest_framework import generics, permissions + +# drf yasg +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema + + +# accounts +from core.apps.accounts.models import Role +from core.apps.accounts.serializers.role.create import CreateRoleSerializer +from core.apps.accounts.serializers.role.list import ListRoleSerializer + +# utils +from core.utils.response.mixin import ResponseMixin + + +class CreateRoleApiView(generics.GenericAPIView, ResponseMixin): + serializer_class = CreateRoleSerializer + queryset = Role.objects.all() + permission_classes = [permissions.IsAuthenticated] + + @swagger_auto_schema( + tags=['role'], + operation_summary="Create a new role in the system", + operation_description=""" + Create a new role with a name and optional comment. + + Authentication: + - Requires a valid Bearer access token. + + Process: + - Accepts role details in JSON format, validated using CreateRoleSerializer. + - If the data is valid, a new role is created and returned. + + Request: + - Fields can include: + - name (string, required): The name of the role. + - comment (string, optional): Additional information about the role. + + Response: + - 200 OK: Role successfully created and returned. + - 400 Bad Request: Validation failed, invalid input data. + - 500 Internal Server Error: Unexpected error occurred while creating the role. + + Notes: + - Only authenticated users can create roles. + - The response includes the newly created role's details with timestamps. + """, + responses={ + 200: openapi.Response( + schema=None, + description="Success", + examples={ + "application/json": { + "status_code": 200, + "status": "success", + "message": "Role successfully created", + "data": { + "id": 0, + "name": "string", + "comment": "string", + "created_at": "string", + "updated_at": "string" + } + } + } + ), + 400: openapi.Response( + schema=None, + description="Failure", + examples={ + "application/json": { + "status_code": 400, + "status": "failure", + "message": "Kiritayotgan malumotingizni tekshirib ko'ring", + "data": "string" + } + } + ), + 500: openapi.Response( + schema=None, + description="Error", + examples={ + "application/json": { + "status_code": 500, + "status": "error", + "message": "Xatolik, Iltimos backend dasturchiga murojaat qiling", + "data": "string" + } + } + ) + } + ) + def post(self, request): + try: + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + obj = serializer.save() + return self.created_response( + data=ListRoleSerializer(obj).data, + message="Role successfully created" + ) + return self.failure_response( + data=serializer.errors + ) + except Exception as e: + return self.error_response( + data=str(e) + ) diff --git a/core/apps/accounts/views/role/list.py b/core/apps/accounts/views/role/list.py new file mode 100644 index 0000000..542c1dc --- /dev/null +++ b/core/apps/accounts/views/role/list.py @@ -0,0 +1,106 @@ +# rest framework +from rest_framework import generics, permissions + +# drf yasg +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema + + +# accounts +from core.apps.accounts.models import Role +from core.apps.accounts.serializers.role.list import ListRoleSerializer + +# utils +from core.utils.response.mixin import ResponseMixin + + +class ListRoleApiView(generics.GenericAPIView, ResponseMixin): + serializer_class = ListRoleSerializer + queryset = Role.objects.all() + permission_classes = [permissions.IsAuthenticated] + + @swagger_auto_schema( + tags=['role'], + operation_summary="Retrieve a paginated list of roles.", + operation_description=""" + Get a list of all roles in the system, with optional pagination. + + Authentication: + - Requires a valid Bearer access token. + + Process: + - Retrieves all roles that are not marked as deleted. + - Supports pagination using the configured pagination class (limit/offset). + - Returns serialized role data along with pagination metadata. + + Response: + - 200 OK: Successfully returns a paginated list of roles. + - Includes fields: count, next, previous, results. + - Each role includes id, name, comment, created_at, and updated_at. + - 500 Internal Server Error: Unexpected error occurred while fetching roles. + + Notes: + - Only authenticated users can access this endpoint. + - Roles marked as deleted (`is_deleted=True`) are excluded from the response. + - Pagination fields (`next` and `previous`) provide URLs to navigate pages if results exceed page limit. + + """, + responses={ + 200: openapi.Response( + description="Succes", + schema=None, + examples={ + "application/json": { + "status_code": 200, + "status": "success", + "message": "Roles list", + "data": { + "count": 0, + "next": "string", + "previous": "string", + "results": [ + { + "id": 1, + "name": "string", + "comment": "string", + "created_at": "string", + "updated_at": "string" + }, + ] + } + } + } + ), + 500: openapi.Response( + description="Error", + schema=None, + examples={ + "application/json": { + "status_code": 500, + "status": "error", + "message": "Xatolik, Iltimos backend dasturchiga murojaat qiling", + "data": "string" + } + } + ), + } + ) + def get(self, request): + try: + queryset = self.queryset.filter(is_deleted=False) + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.serializer_class(page, many=True) + return self.success_response( + data=self.get_paginated_response(serializer.data).data, + message="Roles list" + ) + serializer = self.serializer_class(queryset, many=True) + return self.success_response( + data=serializer.data, + message="Roles list" + ) + except Exception as e: + return self.error_response( + data=str(e) + ) \ No newline at end of file diff --git a/core/apps/accounts/views/role/update.py b/core/apps/accounts/views/role/update.py new file mode 100644 index 0000000..cfdd69d --- /dev/null +++ b/core/apps/accounts/views/role/update.py @@ -0,0 +1,52 @@ +# rest framework +from rest_framework import generics, permissions + +# drf yasg +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema + + +# accounts +from core.apps.accounts.models import Role +from core.apps.accounts.serializers.role.update import UpdateRoleSerializer +from core.apps.accounts.serializers.role.list import ListRoleSerializer + +# utils +from core.utils.response.mixin import ResponseMixin + + +class UpdateRoleApiView(generics.GenericAPIView, ResponseMixin): + serializer_class = UpdateRoleSerializer + queryset = Role.objects.filter(is_deleted=False) + permission_classes = [permissions.IsAuthenticated] + + @swagger_auto_schema( + tags=['role'], + operation_summary="", + operation_description=""" + + """, + responses={} + ) + def patch(self, request, id): + try: + instance = Role.objects.filter(id=id).first() + if not instance: + return self.not_found_response(data={}, message="Role not found with this id") + serializer = self.serializer_class(data=request.data, instance=instance, partial=True) + if serializer.is_valid(): + updated_instance = serializer.save() + return self.success_response( + data=ListRoleSerializer(updated_instance).data, + message="Role successfully updated" + ) + return self.failure_response( + data=serializer.errors, + ) + except Exception as e: + return self.error_response( + data=str(e) + ) + + +