From 097824e061f884c22dae5eaa7fb476a98a2f1b31 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Thu, 1 Aug 2024 23:26:19 +0900 Subject: [PATCH 1/8] =?UTF-8?q?fix:=20login=20response=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 로그인 성공 시: 기존 -> token 반환 변경 -> token, user_id, reliability, profile_img_url 반환 --- .../migrations/0003_alter_user_reliability.py | 17 +++++++++++++ ...emove_user_profile_pic_user_profile_img.py | 23 ++++++++++++++++++ accounts/models.py | 4 +-- accounts/serializers.py | 8 +++++- accounts/views.py | 13 ++++++++-- ...rofile_pic.jpg => default_profile_img.jpg} | Bin 6 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 accounts/migrations/0003_alter_user_reliability.py create mode 100644 accounts/migrations/0004_remove_user_profile_pic_user_profile_img.py rename media/{default_profile_pic.jpg => default_profile_img.jpg} (100%) diff --git a/accounts/migrations/0003_alter_user_reliability.py b/accounts/migrations/0003_alter_user_reliability.py new file mode 100644 index 0000000..46f53a2 --- /dev/null +++ b/accounts/migrations/0003_alter_user_reliability.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.14 on 2024-08-01 11:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("accounts", "0002_alter_user_groups_alter_user_is_active_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="reliability", + field=models.SmallIntegerField(default=100), + ), + ] diff --git a/accounts/migrations/0004_remove_user_profile_pic_user_profile_img.py b/accounts/migrations/0004_remove_user_profile_pic_user_profile_img.py new file mode 100644 index 0000000..23bff38 --- /dev/null +++ b/accounts/migrations/0004_remove_user_profile_pic_user_profile_img.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.14 on 2024-08-01 14:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("accounts", "0003_alter_user_reliability"), + ] + + operations = [ + migrations.RemoveField( + model_name="user", + name="profile_pic", + ), + migrations.AddField( + model_name="user", + name="profile_img", + field=models.ImageField( + default="default_profile_img.jpg", upload_to="profile_imgs" + ), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index fbf9ddc..4dff485 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -12,8 +12,8 @@ class User(AbstractUser): error_messages={"unique": "이미 사용 중인 닉네임입니다."}, ) - profile_pic = models.ImageField( - default="default_profile_pic.jpg", upload_to="profile_pics" + profile_img = models.ImageField( + default="default_profile_img.jpg", upload_to="profile_imgs" ) reliability = models.SmallIntegerField(default=100) diff --git a/accounts/serializers.py b/accounts/serializers.py index cca8254..a510ddd 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -57,6 +57,12 @@ def validate(self, data): user = authenticate(**data) if user: token = Token.objects.get(user=user) - return token + return { + "token": token.key, + "user_id": user.id, + "reliability": user.reliability, + "profile_img_url": user.profile_img.url, + } + # 가입된 유저가 없을 경우 raise serializers.ValidationError({"error": "유저 정보가 존재하지 않습니다."}) diff --git a/accounts/views.py b/accounts/views.py index 47055b8..397678b 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -17,6 +17,15 @@ class LoginView(generics.GenericAPIView): def post(self, request): serializer = self.get_serializer(data=request.data) if serializer.is_valid(): - token = serializer.validated_data - return Response({"token": token.key}, status=status.HTTP_200_OK) + validated_data = serializer.validated_data + return Response( + { + "token": validated_data["token"], + "user_id": validated_data["user_id"], + "reliability": validated_data["reliability"], + "profile_img_url": validated_data["profile_img_url"], + }, + status=status.HTTP_200_OK, + ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/media/default_profile_pic.jpg b/media/default_profile_img.jpg similarity index 100% rename from media/default_profile_pic.jpg rename to media/default_profile_img.jpg From 297065ed967bbf7cdc47b2ae98dac9352fa6c99e Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Fri, 2 Aug 2024 01:10:12 +0900 Subject: [PATCH 2/8] =?UTF-8?q?fix:=20=EC=8A=88=ED=8D=BC=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EC=96=B4=EB=93=9C=EB=AF=BC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/admin.py | 5 +++-- accounts/models.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/accounts/admin.py b/accounts/admin.py index 4185d36..c4a654c 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,3 +1,4 @@ -# from django.contrib import admin +from django.contrib import admin +from .models import User -# Register your models here. +admin.site.register(User) diff --git a/accounts/models.py b/accounts/models.py index 4dff485..6428c7f 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -19,7 +19,7 @@ class User(AbstractUser): reliability = models.SmallIntegerField(default=100) USERNAME_FIELD = "email" - REQUIRED_FIELDS = ["name"] + REQUIRED_FIELDS = ["name", "username"] def __str__(self): return self.name From 8d48dcc5b2222fe12256cbca6608565ac2803164 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Fri, 2 Aug 2024 15:50:15 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20admin=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=EB=A0=88=EC=8A=A4=ED=86=A0=EB=9E=91=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- restaurants/admin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/restaurants/admin.py b/restaurants/admin.py index 4185d36..069ce1a 100644 --- a/restaurants/admin.py +++ b/restaurants/admin.py @@ -1,3 +1,4 @@ -# from django.contrib import admin +from django.contrib import admin +from .models import Restaurant -# Register your models here. +admin.site.register(Restaurant) From 12d4c2d1fdef4aa306df1d8fc25fc856cf8d1d40 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Fri, 2 Aug 2024 16:05:54 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20admin=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=20friends=20=EB=AA=A8=EB=8D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- friends/admin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/friends/admin.py b/friends/admin.py index 4185d36..72ba356 100644 --- a/friends/admin.py +++ b/friends/admin.py @@ -1,3 +1,4 @@ -# from django.contrib import admin +from django.contrib import admin +from .models import Friend -# Register your models here. +admin.site.register(Friend) From b5d767c500f0426eba9a791e956f813d5cf296a3 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Mon, 5 Aug 2024 02:07:02 +0900 Subject: [PATCH 5/8] =?UTF-8?q?chore:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/admin.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/accounts/admin.py b/accounts/admin.py index c4a654c..8f8ef1f 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,4 +1,18 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin from .models import User -admin.site.register(User) + +class CustomUserAdmin(UserAdmin): + list_display = ( + "id", + "name", + "email", + "profile_img", + "reliability", + "date_joined", + "last_login", + ) + + +admin.site.register(User, CustomUserAdmin) From 3573bec839c9f0970c1b9b741dbd45c9ef9a3c39 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Mon, 5 Aug 2024 02:12:29 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD/=EC=88=98=EB=9D=BD/=EA=B1=B0=EC=A0=88=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- friends/admin.py | 14 ++- .../0005_remove_friend_state_friendrequest.py | 61 +++++++++++++ ..._friend_state_friend_unique_user_friend.py | 22 +++++ friends/migrations/0007_friend_created_at.py | 21 +++++ friends/models.py | 34 +++++-- friends/serializers.py | 6 ++ friends/urls.py | 2 + friends/views.py | 89 +++++++++++++++++++ 8 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 friends/migrations/0005_remove_friend_state_friendrequest.py create mode 100644 friends/migrations/0006_remove_friend_state_friend_unique_user_friend.py create mode 100644 friends/migrations/0007_friend_created_at.py diff --git a/friends/admin.py b/friends/admin.py index 72ba356..7156a9b 100644 --- a/friends/admin.py +++ b/friends/admin.py @@ -1,4 +1,14 @@ from django.contrib import admin -from .models import Friend +from .models import Friend, FriendRequest -admin.site.register(Friend) + +class FriendAdmin(admin.ModelAdmin): + list_display = ("id", "user", "friend", "created_at") + + +class FriendRequestAdmin(admin.ModelAdmin): + list_display = ("id", "from_user", "to_user", "state", "created_at") + + +admin.site.register(Friend, FriendAdmin) +admin.site.register(FriendRequest, FriendRequestAdmin) diff --git a/friends/migrations/0005_remove_friend_state_friendrequest.py b/friends/migrations/0005_remove_friend_state_friendrequest.py new file mode 100644 index 0000000..9475730 --- /dev/null +++ b/friends/migrations/0005_remove_friend_state_friendrequest.py @@ -0,0 +1,61 @@ +# Generated by Django 4.2.14 on 2024-08-04 16:07 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("friends", "0004_merge_20240801_0544"), + ] + + operations = [ + migrations.CreateModel( + name="FriendRequest", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "state", + models.CharField( + choices=[ + ("pending", "Pending"), + ("accepted", "Accepted"), + ("declined", "Declined"), + ], + default="pending", + max_length=20, + ), + ), + ( + "from_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="sent_friend_requests", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "to_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="received_friend_requests", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "unique_together": {("from_user", "to_user")}, + }, + ), + ] diff --git a/friends/migrations/0006_remove_friend_state_friend_unique_user_friend.py b/friends/migrations/0006_remove_friend_state_friend_unique_user_friend.py new file mode 100644 index 0000000..35d787c --- /dev/null +++ b/friends/migrations/0006_remove_friend_state_friend_unique_user_friend.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.14 on 2024-08-04 16:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("friends", "0005_remove_friend_state_friendrequest"), + ] + + operations = [ + migrations.RemoveField( + model_name="friend", + name="state", + ), + migrations.AddConstraint( + model_name="friend", + constraint=models.UniqueConstraint( + fields=("user", "friend"), name="unique_user_friend" + ), + ), + ] diff --git a/friends/migrations/0007_friend_created_at.py b/friends/migrations/0007_friend_created_at.py new file mode 100644 index 0000000..e844524 --- /dev/null +++ b/friends/migrations/0007_friend_created_at.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.14 on 2024-08-04 16:50 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + dependencies = [ + ("friends", "0006_remove_friend_state_friend_unique_user_friend"), + ] + + operations = [ + migrations.AddField( + model_name="friend", + name="created_at", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + ] diff --git a/friends/models.py b/friends/models.py index 3f0404b..41e315d 100644 --- a/friends/models.py +++ b/friends/models.py @@ -5,12 +5,34 @@ class Friend(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="friends") + friend = models.ForeignKey(User, on_delete=models.CASCADE, related_name="friend_of") + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["user", "friend"], name="unique_user_friend" + ) + ] + + +# 친구 신청 모델 +class FriendRequest(models.Model): STATE_CHOICES = [ - ("request", "Request"), - ("approve", "Approve"), - ("deny", "Deny"), + ("pending", "Pending"), + ("accepted", "Accepted"), + ("declined", "Declined"), ] - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="friends") - friend = models.ForeignKey(User, on_delete=models.CASCADE, related_name="friend_of") - state = models.CharField(max_length=20, choices=STATE_CHOICES, default="request") + from_user = models.ForeignKey( + User, on_delete=models.CASCADE, related_name="sent_friend_requests" + ) + to_user = models.ForeignKey( + User, on_delete=models.CASCADE, related_name="received_friend_requests" + ) + created_at = models.DateTimeField(auto_now_add=True) + state = models.CharField(max_length=20, choices=STATE_CHOICES, default="pending") + + class Meta: + unique_together = ("from_user", "to_user") diff --git a/friends/serializers.py b/friends/serializers.py index a44ccd1..08f1b43 100644 --- a/friends/serializers.py +++ b/friends/serializers.py @@ -9,6 +9,12 @@ class Meta: fields = "__all__" +class FriendRequestSerializer(serializers.ModelSerializer): + class Meta: + model = Friend + fields = "__all__" + + class RestaurantSerializer(serializers.ModelSerializer): # reviews = serializers.SerializerMethodField() diff --git a/friends/urls.py b/friends/urls.py index 7d66cd4..e3e2496 100644 --- a/friends/urls.py +++ b/friends/urls.py @@ -7,4 +7,6 @@ views.friend_restaurant_list, name="friend-restaurant-list", ), + # 추가 기능을 테스트하기 위한 url으로 실제로는 /friend 안에서 모두 진행됨 + path("friend-request/", views.FriendRequestView.as_view(), name="friend-request"), ] diff --git a/friends/views.py b/friends/views.py index 0d02607..7109984 100644 --- a/friends/views.py +++ b/friends/views.py @@ -1,12 +1,21 @@ # from django.shortcuts import render from rest_framework.decorators import api_view +from rest_framework.views import APIView + +# from rest_framework.authentication import TokenAuthentication +# from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import status # from django.contrib.auth.decorators import login_required from restaurants.models import UserRestaurantsList from .serializers import RestaurantSerializer + +# from .serializers import FriendSerializer, FriendRequestSerializer from accounts.models import User +from .models import Friend, FriendRequest + +from django.shortcuts import get_object_or_404 @api_view(["GET"]) @@ -25,3 +34,83 @@ def friend_restaurant_list(request, id): return Response( {"message": "Friend not found"}, status=status.HTTP_404_NOT_FOUND ) + + +# 친구신청 +class FriendRequestView(APIView): + # authentication_classes = [TokenAuthentication] + # permission_classes = [IsAuthenticated] + + def post(self, request): + action = request.data.get("action") + friend_id = request.data.get("friend_id") + + if action == "send": + return self.send_request(request, friend_id) + elif action == "accept": + return self.accept_request(request, friend_id) + elif action == "decline": + return self.decline_request(request, friend_id) + else: + return Response( + {"message": "올바르지 않은 요청입니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + # 친구 요청 + def send_request(self, request, friend_id): + to_user = get_object_or_404(User, id=friend_id) + from_user = request.user + + if FriendRequest.objects.filter(from_user=from_user, to_user=to_user).exists(): + return Response( + {"message": "이미 친구 요청을 보냈습니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + friend_request = FriendRequest( + from_user=from_user, to_user=to_user, state="pending" + ) + friend_request.save() + + return Response({"message": "친구 요청을 보냈습니다."}, status=status.HTTP_201_CREATED) + + # 친구 수락 + def accept_request(self, request, friend_id): + # 친구 요청을 보낸 사용자의 id와 일치하는 요청이 있는지 확인 + friend_request = get_object_or_404(FriendRequest, from_user__id=friend_id) + + if friend_request.to_user != request.user: + return Response({"message": "권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN) + if friend_request.state != "pending": + return Response( + {"message": "이미 처리된 요청입니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + friend_request.state = "accepted" + friend_request.save() + + # 양방향 친구 관계 설정 + from_user = friend_request.from_user + to_user = friend_request.to_user + + friend_relation = Friend.objects.create(user=from_user, friend=to_user) + friend_relation.save() + reverse_friend_relation = Friend.objects.create(user=to_user, friend=from_user) + reverse_friend_relation.save() + + return Response({"message": "친구 신청을 수락했습니다."}, status=status.HTTP_200_OK) + + # 친구 거절 + def decline_request(self, request, friend_id): + friend_request = get_object_or_404(FriendRequest, from_user__id=friend_id) + + if friend_request.to_user != request.user: + return Response({"message": "권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN) + if friend_request.state != "pending": + return Response( + {"message": "이미 처리된 요청입니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + friend_request.state = "declined" + friend_request.save() + + return Response({"message": "친구 신청을 거절했습니다."}, status=status.HTTP_200_OK) From eaeb8a01c101e10a9c7bb23a5f785f98cd87e935 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Mon, 5 Aug 2024 02:17:36 +0900 Subject: [PATCH 7/8] =?UTF-8?q?chore:=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95=20id=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80=ED=95=98=EB=A0=A4?= =?UTF-8?q?=EA=B3=A0=20=ED=96=88=EB=8D=98=EB=93=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/migrations/0005_alter_user_id.py | 17 +++++++++++++++++ accounts/migrations/0006_alter_user_id.py | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 accounts/migrations/0005_alter_user_id.py create mode 100644 accounts/migrations/0006_alter_user_id.py diff --git a/accounts/migrations/0005_alter_user_id.py b/accounts/migrations/0005_alter_user_id.py new file mode 100644 index 0000000..a2ebe9b --- /dev/null +++ b/accounts/migrations/0005_alter_user_id.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.14 on 2024-08-02 18:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("accounts", "0004_remove_user_profile_pic_user_profile_img"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="id", + field=models.AutoField(primary_key=True, serialize=False), + ), + ] diff --git a/accounts/migrations/0006_alter_user_id.py b/accounts/migrations/0006_alter_user_id.py new file mode 100644 index 0000000..fc62ce3 --- /dev/null +++ b/accounts/migrations/0006_alter_user_id.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.14 on 2024-08-02 18:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("accounts", "0005_alter_user_id"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ] From 345aae7487fb2d0646509ba3fe2f08e77ccfae7c Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Mon, 5 Aug 2024 02:25:15 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=EC=B6=A9=EB=8F=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- friends/serializers.py | 131 ++++++++++++++++++++++++++++++++++++++--- friends/urls.py | 4 +- friends/views.py | 110 ++++++++++++++++++++++++++++++++-- 3 files changed, 233 insertions(+), 12 deletions(-) diff --git a/friends/serializers.py b/friends/serializers.py index 08f1b43..13aee59 100644 --- a/friends/serializers.py +++ b/friends/serializers.py @@ -1,18 +1,135 @@ from rest_framework import serializers -from .models import Friend +from .models import Friend, FriendRequest from restaurants.models import Restaurant, UserRestaurantsList +from accounts.models import User -class FriendSerializer(serializers.ModelSerializer): +class UserSerializer(serializers.ModelSerializer): class Meta: - model = Friend - fields = "__all__" + model = User + fields = ["id", "name", "profile_img", "reliability"] class FriendRequestSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(source="from_user.id") + name = serializers.CharField(source="from_user.name") + profile_img = serializers.URLField(source="from_user.profile_img.url") + reliability = serializers.IntegerField(source="from_user.reliability") + common_restaurant_count = serializers.SerializerMethodField() + + class Meta: + model = FriendRequest + fields = [ + "id", + "name", + "profile_img", + "reliability", + "common_restaurant_count", + ] + + def get_common_restaurant_count(self, obj): + try: + user = obj.from_user + # friend_user = self.context.get('request').user + friend_user = User.objects.get(id=21) + + user_restaurants = set( + UserRestaurantsList.objects.filter(user=user).values_list( + "restaurant_id", flat=True + ) + ) + friend_restaurants = set( + UserRestaurantsList.objects.filter(user=friend_user).values_list( + "restaurant_id", flat=True + ) + ) + return len(user_restaurants.intersection(friend_restaurants)) + except User.DoesNotExist: + return 0 + + +class FriendRecommendSerializer(serializers.ModelSerializer): + common_restaurant_count = serializers.SerializerMethodField() + common_restaurants = serializers.SerializerMethodField() + + class Meta: + model = User + fields = [ + "id", + "name", + "profile_img", + "reliability", + "common_restaurant_count", + "common_restaurants", + ] + + def get_common_restaurant_count(self, obj): + user = self.context.get("user") + user_restaurants = set( + UserRestaurantsList.objects.filter(user=user).values_list( + "restaurant_id", flat=True + ) + ) + friend_restaurants = set( + UserRestaurantsList.objects.filter(user=obj).values_list( + "restaurant_id", flat=True + ) + ) + return len(user_restaurants.intersection(friend_restaurants)) + + def get_common_restaurants(self, obj): + user = self.context.get("user") + user_restaurants = set( + UserRestaurantsList.objects.filter(user=user).values_list( + "restaurant_id", flat=True + ) + ) + friend_restaurants = UserRestaurantsList.objects.filter( + user=obj, restaurant_id__in=user_restaurants + ).values("restaurant__name", "restaurant__image_url")[:2] + return friend_restaurants + + def to_representation(self, instance): + representation = super().to_representation(instance) + include_restaurants = self.context.get("include_restaurants", False) + if not include_restaurants: + representation.pop("common_restaurants") + return representation + + +class FriendSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(source="friend.id") + name = serializers.CharField(source="friend.name") + profile_img = serializers.URLField(source="friend.profile_img.url") + reliability = serializers.IntegerField(source="friend.reliability") + class Meta: model = Friend - fields = "__all__" + fields = [ + "id", + "name", + "profile_img", + "reliability", + ] + + +class RestaurantlistSerializer(serializers.ModelSerializer): + rating_average = serializers.SerializerMethodField() + + class Meta: + model = Restaurant + fields = [ + "id", + "name", + "food_type", + "rating_average", + "latitude", + "longitude", + "image_url", + ] + + def get_rating_average(self, obj): + return obj.rating_average() class RestaurantSerializer(serializers.ModelSerializer): @@ -23,8 +140,8 @@ class Meta: fields = "__all__" -class RestaurantSerializer(serializers.ModelSerializer): - restaurant = RestaurantSerializer() +class FriendRestaurantSerializer(serializers.ModelSerializer): + restaurant = RestaurantlistSerializer() class Meta: model = UserRestaurantsList diff --git a/friends/urls.py b/friends/urls.py index e3e2496..b4822af 100644 --- a/friends/urls.py +++ b/friends/urls.py @@ -3,10 +3,12 @@ urlpatterns = [ path( - "/restaurants/", + "friends//restaurants/", views.friend_restaurant_list, name="friend-restaurant-list", ), # 추가 기능을 테스트하기 위한 url으로 실제로는 /friend 안에서 모두 진행됨 path("friend-request/", views.FriendRequestView.as_view(), name="friend-request"), + path("friends/", views.friend_list, name="friend-list"), + path("friend-recommend/", views.friend_recommend, name="friend-recommend"), ] diff --git a/friends/views.py b/friends/views.py index 7109984..be52956 100644 --- a/friends/views.py +++ b/friends/views.py @@ -8,12 +8,20 @@ from rest_framework import status # from django.contrib.auth.decorators import login_required -from restaurants.models import UserRestaurantsList -from .serializers import RestaurantSerializer +from restaurants.models import UserRestaurantsList, Restaurant +from .serializers import ( + FriendSerializer, + FriendRequestSerializer, + RestaurantlistSerializer, + FriendRecommendSerializer, +) # from .serializers import FriendSerializer, FriendRequestSerializer from accounts.models import User from .models import Friend, FriendRequest +from django.views.decorators.csrf import csrf_exempt +from django.db.models import Count, Q +import random from django.shortcuts import get_object_or_404 @@ -27,15 +35,109 @@ def friend_restaurant_list(request, id): # 친구의 맛집 리스트를 가져옴 friend_restaurants = UserRestaurantsList.objects.filter(user=friend) - serializer = RestaurantSerializer(friend_restaurants, many=True) + restaurant_ids = friend_restaurants.values_list("restaurant_id", flat=True) + restaurants = Restaurant.objects.filter(id__in=restaurant_ids) - return Response({"restaurants": serializer.data}, status=status.HTTP_200_OK) + serializer = RestaurantlistSerializer(restaurants, many=True) + + return Response({"results": serializer.data}, status=status.HTTP_200_OK) except User.DoesNotExist: return Response( {"message": "Friend not found"}, status=status.HTTP_404_NOT_FOUND ) +@csrf_exempt +@api_view(["GET"]) +# @login_required +def friend_list(request): + try: + # user = request.user + user = User.objects.get(id=21) + + friend_request = FriendRequest.objects.filter(to_user=user, state="pending") + friend_request_serialized = FriendRequestSerializer( + friend_request, context={"request": request}, many=True + ).data + + friends = Friend.objects.filter(user=user) + friends_serialized = FriendSerializer(friends, many=True).data + + user_restaurants = set( + UserRestaurantsList.objects.filter(user=user).values_list( + "restaurant_id", flat=True + ) + ) + potential_friends = ( + User.objects.exclude(id=user.id) + .annotate( + common_restaurant_count=Count( + "userrestaurantslist__restaurant_id", + filter=Q(userrestaurantslist__restaurant_id__in=user_restaurants), + ) + ) + .order_by("-common_restaurant_count")[:7] + ) + + friend_recommend_serialized = FriendRecommendSerializer( + potential_friends, + many=True, + context={"request": request, "user": user, "include_restaurants": False}, + ).data + + data = { + "friend_request": friend_request_serialized, + "friends": friends_serialized, + "friend_recommend": friend_recommend_serialized, + } + + return Response(data) + + except User.DoesNotExist: + return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND) + + +@csrf_exempt +@api_view(["GET"]) +# @login_required +def friend_recommend(request): + try: + # user = request.user + user = User.objects.get(id=21) + + user_restaurants = set( + UserRestaurantsList.objects.filter(user=user).values_list( + "restaurant_id", flat=True + ) + ) + potential_friends = ( + User.objects.exclude(id=user.id) + .annotate( + common_restaurant_count=Count( + "userrestaurantslist__restaurant_id", + filter=Q(userrestaurantslist__restaurant_id__in=user_restaurants), + ) + ) + .order_by("-common_restaurant_count")[:7] + ) + + if potential_friends: + random_friend = random.choice(potential_friends) + friend_recommend_serialized = FriendRecommendSerializer( + random_friend, + context={"request": request, "user": user, "include_restaurants": True}, + ).data + return Response(friend_recommend_serialized) + + return Response( + {"message": "No recommended friends found"}, + status=status.HTTP_404_NOT_FOUND, + ) + + except User.DoesNotExist: + return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND) + + # 친구신청 class FriendRequestView(APIView): # authentication_classes = [TokenAuthentication]