From f0f4bf36ab0e8824e5a7c6ba11b0ef1bc6042fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=8D?= <79096808+0321minji@users.noreply.github.com> Date: Sat, 3 Aug 2024 16:18:50 +0900 Subject: [PATCH] #9 feat : add user profile model & profile api (#88) --- UpcyProject/settings/settings.py | 27 ++++- users/admin.py | 4 + ...5_remove_user_profile_image_userprofile.py | 26 +++++ .../migrations/0016_userprofile_introduce.py | 18 +++ users/models.py | 8 +- users/selectors.py | 21 +++- users/services.py | 19 ++- users/urls.py | 4 +- users/views.py | 108 +++++++++++++++++- 9 files changed, 222 insertions(+), 13 deletions(-) create mode 100644 users/migrations/0015_remove_user_profile_image_userprofile.py create mode 100644 users/migrations/0016_userprofile_introduce.py diff --git a/UpcyProject/settings/settings.py b/UpcyProject/settings/settings.py index c8c0fb2..a7a4c57 100644 --- a/UpcyProject/settings/settings.py +++ b/UpcyProject/settings/settings.py @@ -36,8 +36,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env.bool('DEBUG', default=False) - -ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['*']) +ALLOWED_HOSTS = ['localhost', '127.0.0.1', '.ngrok-free.app'] # Application definition @@ -52,6 +51,7 @@ PROJECT_APPS = [ "drf_yasg", + 'corsheaders', "users.apps.UsersConfig", "core.apps.CoreConfig", "products.apps.ProductsConfig", @@ -71,6 +71,13 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'corsheaders.middleware.CorsMiddleware' + +] +CORS_ORIGIN_ALLOW_ALL = True +CSRF_COOKIE_SECURE = False # 개발 중일 때는 False로 설정 +CSRF_TRUSTED_ORIGINS = [ + "https://3d49-165-132-5-152.ngrok-free.app", ] AUTH_USER_MODEL = "users.User" @@ -92,7 +99,7 @@ REST_USE_JWT = True SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=10), + 'ACCESS_TOKEN_LIFETIME': timedelta(days=1), 'REFRESH_TOKEN_LIFETIME': timedelta(days=28), 'ROTATE_REFRESH_TOKENS': False, # true면 토큰 갱신 시 refresh도 같이 갱신 'BLACKLIST_AFTER_ROTATION': True, @@ -175,3 +182,17 @@ AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY') AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME', 'upcy-bucket') AWS_S3_REGION_NAME = os.getenv('AWS_S3_REGION_NAME', 'ap-northeast-2') +# SWAGGER_SETTINGS = { +# 'USE_SESSION_AUTH': False, +# 'SECURITY_DEFINITIONS': { +# 'BearerAuth': { +# 'type': 'apiKey', +# 'name': 'Authorization', +# 'in': 'header', +# 'description': "Bearer Token" +# } +# }, +# 'SECURITY_REQUIREMENTS': [{ +# 'BearerAuth': [] +# }] +# } \ No newline at end of file diff --git a/users/admin.py b/users/admin.py index aac4fbd..7377bf4 100644 --- a/users/admin.py +++ b/users/admin.py @@ -36,4 +36,8 @@ class Intership(admin.ModelAdmin): @admin.register(models.Freelancer) class Freelancer(admin.ModelAdmin): + pass + +@admin.register(models.UserProfile) +class UserProfile(admin.ModelAdmin): pass \ No newline at end of file diff --git a/users/migrations/0015_remove_user_profile_image_userprofile.py b/users/migrations/0015_remove_user_profile_image_userprofile.py new file mode 100644 index 0000000..f14a5c6 --- /dev/null +++ b/users/migrations/0015_remove_user_profile_image_userprofile.py @@ -0,0 +1,26 @@ +# Generated by Django 4.0 on 2024-08-03 06:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0014_alter_certification_proof_document_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='profile_image', + ), + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('profile_image', models.URLField(blank=True, null=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='users.user')), + ], + ), + ] diff --git a/users/migrations/0016_userprofile_introduce.py b/users/migrations/0016_userprofile_introduce.py new file mode 100644 index 0000000..468591b --- /dev/null +++ b/users/migrations/0016_userprofile_introduce.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-08-03 06:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0015_remove_user_profile_image_userprofile'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='introduce', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/users/models.py b/users/models.py index 1cfd8fc..e1c1e68 100644 --- a/users/models.py +++ b/users/models.py @@ -62,7 +62,7 @@ class User(AbstractBaseUser, PermissionsMixin): phone = models.CharField(max_length = 15, blank=True, null=True) code = models.CharField(max_length=5, blank=True, null=True) nickname = models.CharField(max_length=20, blank=True) - profile_image = models.ImageField(upload_to=get_upload_path, blank=True, null=True) + #profile_image = models.ImageField(upload_to=get_upload_path, blank=True, null=True) agreement_terms = models.BooleanField(default = False) follows = models.ManyToManyField("users.User", related_name='followers', blank=True) is_superuser = models.BooleanField(default=False) @@ -97,7 +97,11 @@ def clean(self): raise ValidationError('메일 형식이 올바르지 않습니다.') # if not user_type_is_valid(self): # raise ValidationError('유저 타입이 잘못되었습니다.') - +class UserProfile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + profile_image = models.URLField(blank=True, null=True) + introduce=models.TextField(blank=True,null=True) + #Reformer profile 모델 class ReformerProfile(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='reformer_profile') diff --git a/users/selectors.py b/users/selectors.py index 63f5a66..b213d11 100644 --- a/users/selectors.py +++ b/users/selectors.py @@ -2,8 +2,8 @@ from django.http import Http404, HttpResponseBadRequest # from django.db.models import QuerySet, Q, F # from django.contrib.auth import authenticate - -from users.models import User, ReformerProfile +from django.core.exceptions import ObjectDoesNotExist +from users.models import User, ReformerProfile,UserProfile class UserSelector: def __init__(self): @@ -22,6 +22,23 @@ def get_user_by_email(email: str) -> User: def check_password(user: User, password: str): return user.check_password(password) + @staticmethod + def get_user_profile_by_email(email:str) -> UserProfile: + try: + user = User.objects.get(email=email) + user_profile = UserProfile.objects.get(user=user) + + data = { + "nickname": user.nickname, + "profile_image": user_profile.profile_image, + "introduce": user_profile.introduce, + } + return data + except ObjectDoesNotExist: + return { + "error": "User or UserProfile not found" + } + class ReformerSelector: def __init__(self): pass diff --git a/users/services.py b/users/services.py index 4cf7f7c..39cbfde 100644 --- a/users/services.py +++ b/users/services.py @@ -22,7 +22,7 @@ from django.core.files.uploadedfile import InMemoryUploadedFile from django.core.files import File from django.core.files.base import ContentFile -from users.models import User, ReformerProfile, Certification, Competition, Internship, Freelancer +from users.models import User, ReformerProfile, Certification, Competition, Internship, Freelancer, UserProfile from users.selectors import UserSelector # from core.exceptions import ApplicationError from core.utils import s3_file_upload_by_file_data @@ -74,6 +74,23 @@ def login(self, email: str, password: str): return data + def user_profile_image_register(self,user:User,img:ImageFile): + img_url=s3_file_upload_by_file_data( + upload_file=img, + region_name=settings.AWS_S3_REGION_NAME, + bucket_name=settings.AWS_STORAGE_BUCKET_NAME, + bucket_path=f'profile/{user.pk}/img' + ) + + user_profile=UserProfile(user=user,profile_image=img_url) + user_profile.save() + data={ + "email":user_profile.user.email, + "nickname":user_profile.user.nickname, + "img":user_profile.profile_image, + } + return data + def reformer_profile_register(self,user:User, nickname:str, market_name:str,market_intro:str,links:str, work_style:list[str],special_material:list[str]): diff --git a/users/urls.py b/users/urls.py index ae90d0d..e8b170f 100644 --- a/users/urls.py +++ b/users/urls.py @@ -1,6 +1,6 @@ from django.urls import path from rest_framework_simplejwt.views import TokenRefreshView, TokenVerifyView -from .views import UserSignUpApi,UserLoginApi,ReformerProfileApi,CertificationCreateApi,CompetitionCreateApi,IntershipCreateApi,FreelancerCreateApi +from .views import * app_name = "users" urlpatterns =[ @@ -15,4 +15,6 @@ path('intership_register/',IntershipCreateApi.as_view(),name='intership'), path('freelancer_register/',FreelancerCreateApi.as_view(),name='freelancer'), path('reformer_profile//',ReformerProfileApi.as_view(),name='profile'), + path('profile/img/',UserProfileImageApi.as_view(),name='profile_img'), + path('profile/',UserDetailApi.as_view(),name='profile'), ] \ No newline at end of file diff --git a/users/views.py b/users/views.py index 70a60e4..b074159 100644 --- a/users/views.py +++ b/users/views.py @@ -9,7 +9,7 @@ from users.models import User from users.services import UserService -from users.selectors import ReformerSelector +from users.selectors import ReformerSelector,UserSelector from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema @@ -127,7 +127,62 @@ def post(self, request): 'status': 'success', 'data': output_serializer.data, }, status = status.HTTP_200_OK) + +class UserProfileImageApi(APIView): + permission_classes=(IsAuthenticated,) + + class UserProfileInputSerializer(serializers.Serializer): + img=serializers.ImageField() + + class UserProfileOutputSerializer(serializers.Serializer): + email=serializers.CharField() + nickname=serializers.CharField() + img=serializers.URLField() + @swagger_auto_schema( + request_body=UserProfileInputSerializer, + security=[], + operation_description='유저 프로필 이미지를 등록하는 API 입니다.', + operation_id='유저 프로필 이미지 등록 API', + responses={ + "200": openapi.Response( + description="OK", + examples={ + "application/json": { + "status": "success", + "email":"sdptech@gmail.com", + "nickname":"sdptech", + "img": "https://upcy-bucket.s3.ap-northeast-2.amazonaws.com/profile/1/img/20240803152516_d10b2b3828f7403387ea.webp", + } + } + ), + "400": openapi.Response( + description="Bad Request", + ), + }, + + ) + def post(self,request): + serializers=self.UserProfileInputSerializer(data=request.data) + serializers.is_valid(raise_exception=True) + data=serializers.validated_data + service=UserService() + profile_data=service.user_profile_image_register( + user=request.user, + img=data.get('img') + ) + output_serializer = self.UserProfileOutputSerializer(data = profile_data) + output_serializer.is_valid(raise_exception=True) + try: + # data=service.user_profile_image_register( + # user=request.user, + # img=data.get('img'), + # ) + return Response({'status':'succes', + 'data':output_serializer.data},status=status.HTTP_200_OK) + except Exception as e: + print(f"Failed to upload img to s3:{e}") + return None class ReformerProfileApi(APIView): permission_classes = (AllowAny,) @@ -137,8 +192,12 @@ class ReformerProfileInputSerializer(serializers.Serializer): market_intro=serializers.CharField() links=serializers.CharField() - work_style=serializers.CharField() - special_material=serializers.CharField() + work_style = serializers.ListField( + child=serializers.CharField(), allow_empty=True + ) + special_material = serializers.ListField( + child=serializers.CharField(), allow_empty=True + ) class ReformerProfileOuputSerializer(serializers.Serializer): market_intro=serializers.CharField() @@ -432,4 +491,45 @@ def post(self,request): 'status':'success', },status=status.HTTP_200_OK) except Exception as e: - return Response({'status': 'error', 'message': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) \ No newline at end of file + return Response({'status': 'error', 'message': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + +class UserDetailApi(APIView): + permission_classes=(AllowAny,) + + class UserDetailOutputSerializer(serializers.Serializer): + nickname=serializers.CharField() + introduce=serializers.CharField() + profile_image=serializers.URLField() + + @swagger_auto_schema( + security=[], + operation_id='유저 기본정보 조회 API', + operation_description="유저의 기본 정보인 닉네임, 프로필 이미지, 소개글을 반환하는 API 입니다.", + responses={ + "200":openapi.Response( + description="OK", + examples={ + "application/json":{ + "status":"success", + "data":{"nickname": "test", + "introduce": "introduce~~~", + "profile_image": "https://upcy-bucket.s3.ap-northeast-2.amazonaws.com/profile/1/img/20240803152516_d10b2b3828f7403387ea.webp"} + } + } + ), + "400":openapi.Response( + description="Bad Request", + ), + } + ) + + def get(self,request): + user=UserSelector.get_user_profile_by_email(request.user.email) + + serializers=self.UserDetailOutputSerializer(user) + + return Response({ + 'status':'success', + 'data':serializers.data, + },status=status.HTTP_200_OK) + \ No newline at end of file