diff --git a/apps/account/admin.py b/apps/account/admin.py index 1f274f0..db5a31e 100644 --- a/apps/account/admin.py +++ b/apps/account/admin.py @@ -1,29 +1,40 @@ from django.contrib import admin from django.contrib.auth.models import Group - -# from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from apps.account.forms import UserChangeForm, UserCreationForm from apps.account.models import Role, User, UserPreferences, UserProfile +class UserProfileInline(admin.StackedInline): + model = UserProfile + verbose_name = "Profile" + can_delete = False + extra = 0 + exclude = ["deleted_at"] + + def has_add_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + @admin.register(User) class UserAdmin(admin.ModelAdmin): # The forms to add and change user instances add_form = UserCreationForm form = UserChangeForm model = User - - list_display = ["email", "role"] - list_filter = ["role"] - fieldsets = [ - (None, {"fields": ["email", "password"]}), - ( - "Permissions", - {"fields": ["state", "role"]}, - ), - ("Important dates", {"fields": ["created_at", "updated_at", "deleted_at"]}), - ] - + inlines = [UserProfileInline] + list_display = ["email", "role", "state", "is_profile_set"] + readonly_fields = ["created_at", "updated_at", "last_login", "role", "date_joined"] + exclude = ["deleted_at", "user_permissions", "groups"] + list_filter = ["role", "is_profile_set"] + search_fields = ["email"] + ordering = ["-created_at"] + filter_horizontal = [] add_fieldsets = [ ( None, @@ -41,14 +52,8 @@ class UserAdmin(admin.ModelAdmin): ) ] - search_fields = ["email"] - ordering = ["-created_at"] - readonly_fields = ["created_at", "updated_at"] - filter_horizontal = [] - admin.site.register(Role) -admin.site.register(UserProfile) admin.site.register(UserPreferences) # since we're not using Django's built-in permissions, diff --git a/apps/account/migrations/0002_alter_user_is_profile_set.py b/apps/account/migrations/0002_alter_user_is_profile_set.py new file mode 100644 index 0000000..0927a08 --- /dev/null +++ b/apps/account/migrations/0002_alter_user_is_profile_set.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.5 on 2025-07-02 10:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='is_profile_set', + field=models.BooleanField(default=False), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index d3893b3..0657828 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -31,7 +31,7 @@ class User(AbstractUser, AbstractBaseModel): Role, null=True, blank=True, on_delete=models.RESTRICT, related_name="+" ) - is_profile_set = models.BooleanField(default=True) + is_profile_set = models.BooleanField(default=False) state = models.ForeignKey( DataLookup, diff --git a/apps/account/serializers.py b/apps/account/serializers.py index 80da94c..7859944 100644 --- a/apps/account/serializers.py +++ b/apps/account/serializers.py @@ -115,21 +115,7 @@ def save(self, **kwargs): class UserProfileSerializer(serializers.ModelSerializer): class Meta: model = UserProfile - fields = [ - "first_name", - "last_name", - "phone", - "avatar", - "address", - "created_at", - "updated_at", - ] - - def create(self, validated_data): - user = self.context["request"].user - validated_data["user"] = user - - return super().create(validated_data) + fields = ["first_name", "last_name", "phone", "avatar", "address"] def to_representation(self, instance): return UserProfileResponseSerializer(instance, self.context).to_representation( @@ -138,8 +124,6 @@ def to_representation(self, instance): class UserProfileResponseSerializer(serializers.ModelSerializer): - user = UserSerializer() - class Meta: model = UserProfile fields = [ @@ -149,7 +133,6 @@ class Meta: "phone", "avatar", "address", - "user", "created_at", "updated_at", ] diff --git a/apps/account/services.py b/apps/account/services.py index e044882..b950bc4 100644 --- a/apps/account/services.py +++ b/apps/account/services.py @@ -1,7 +1,8 @@ from uuid import UUID from django.core.cache import cache from django.shortcuts import get_object_or_404 -from apps.account.models import Role +from apps.account.models import Role, UserProfile +from apps.core.exceptions import NotFoundException class RoleService: @@ -16,3 +17,14 @@ def get_cached_role(id: UUID): cache.set(key, result, timeout=3600) return result + + +class UserProfileService: + @staticmethod + def get_user_profile(user) -> UserProfile: + try: + return UserProfile.objects.select_related("user").get(user=user) + except UserProfile.DoesNotExist: + raise NotFoundException( + "Your profile is not not set. Please complete your profile." + ) diff --git a/apps/account/urls.py b/apps/account/urls.py index 649ba58..dbd14f1 100644 --- a/apps/account/urls.py +++ b/apps/account/urls.py @@ -2,7 +2,7 @@ from apps.account.views import ( RoleViewSet, UserViewSet, - USerProfileViewSet, + # USerProfileViewSet, PasswordChangeViewSet, ) @@ -10,7 +10,7 @@ router.register("roles", RoleViewSet, basename="roles") router.register("users", UserViewSet, "users") -router.register("profile", USerProfileViewSet, "company-profile") +# router.register("profile", USerProfileViewSet, "company-profile") router.register("change-password", PasswordChangeViewSet, "change-password") urlpatterns = router.urls diff --git a/apps/account/views.py b/apps/account/views.py index d4fc8d2..2c07e29 100644 --- a/apps/account/views.py +++ b/apps/account/views.py @@ -1,34 +1,67 @@ from rest_framework.permissions import IsAuthenticated, AllowAny -from rest_framework import viewsets, status +from rest_framework import viewsets, mixins, status +from rest_framework.decorators import action from rest_framework.response import Response from rest_framework_simplejwt.tokens import RefreshToken from django.contrib.auth import get_user_model -from apps.account.models import Role, UserProfile +from drf_spectacular.utils import extend_schema +from apps.account.services import UserProfileService +from apps.account.models import Role from apps.account.serializers import ( PasswordChangeSerializer, RoleSerializer, + UserProfileResponseSerializer, UserSerializer, UserProfileSerializer, ) -from apps.core.views import AbstractModelViewSet - User = get_user_model() -class RoleViewSet(AbstractModelViewSet): +class RoleViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [AllowAny] - http_method_names = ["get"] queryset = Role.objects.all() serializer_class = RoleSerializer -class UserViewSet(AbstractModelViewSet): +class UserViewSet( + mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): permission_classes = [AllowAny] serializer_class = UserSerializer queryset = User.objects.select_related("role", "state").all() + @extend_schema( + request=UserProfileSerializer, responses=UserProfileResponseSerializer + ) + @action(methods=["get", "post", "patch"], detail=False, url_path="me") + def profile(self, request, *args, **kwargs): + user = request.user + + if request.method == "GET": + profile = UserProfileService.get_user_profile(user) + serializer = UserProfileSerializer(profile) + return Response(serializer.data) + + if request.method == "POST": + serializer = UserProfileSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save(user=user) + user.is_profile_set = True + user.save() + return Response(serializer.data, status=status.HTTP_200_OK) + + elif request.method == "PATCH": + profile = UserProfileService.get_user_profile(user) + serializer = UserProfileSerializer(profile, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + class PasswordChangeViewSet(viewsets.ViewSet): permission_classes = [IsAuthenticated] @@ -50,22 +83,3 @@ def create(self, request): }, status=status.HTTP_200_OK, ) - - -class USerProfileViewSet(AbstractModelViewSet): - permission_classes = [IsAuthenticated] - http_method_names = ["get", "post", "patch"] - serializer_class = UserProfileSerializer - - def get_queryset(self): - return UserProfile.objects.filter(user=self.request.user) - - # @property - # def access_policy(self): - # return self.permission_classes[0] - - # def get_queryset(self): - # return self.access_policy.scope_queryset( - # request=self.request, - # queryset=UserProfile.objects.all() - # ) diff --git a/apps/core/exceptions.py b/apps/core/exceptions.py index ce8b0de..1ca6fc8 100644 --- a/apps/core/exceptions.py +++ b/apps/core/exceptions.py @@ -7,6 +7,12 @@ class ServiceBaseException(APIException): default_code = "TICKETING_ERROR" +class NotFoundException(APIException): + status_code = 404 + default_detail = "Not found" + default_code = "NOT_FOUND" + + class SerializationError(APIException): status_code = 500 default_detail = "Object Serialization Error" diff --git a/mediafiles/mediafiles/user_profiles/signature_EjKHIq4.png b/mediafiles/mediafiles/user_profiles/signature_EjKHIq4.png new file mode 100644 index 0000000..555df0b Binary files /dev/null and b/mediafiles/mediafiles/user_profiles/signature_EjKHIq4.png differ