로그인 데코레이터를 적용해 로그인 후 가능한 게시물/댓글/좋아요/팔로우 기능을 짜보았다.
python manage.py startapp postings
게시물/댓글/좋아요 기능은 회원과 다른 영역인 게시물의 영역에 있기 때문에 앱을 분리한다.
분리 후 회원가입에서 그랬던 것 처럼 모델링부터 시작한다.
# postings/models.py
from django.db import models
from users.models import User
class Posting(models.Model):
content = models.CharField(max_length=2200, null=True)
user = models.ForeignKey("users.User",on_delete=models.CASCADE, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "postings"
class Image(models.Model):
image_url = models.URLField(max_length=2000)
posting = models.ForeignKey('Posting',on_delete=models.CASCADE, related_name='images')
class Meta:
db_table = 'images'
users.User
의 형식으로 import해준다. 다른 앱에서 클래스를 가져올때는 (앱 이름).(클래스명)
으로 가져올 수 있다.null=True
를 사용한다.created_at
과 updated_at
을 auto_now_add
와 auto_now
를 사용하여 설정해준다.# postings.views.py
import json
from django.views import View
from django.http import JsonResponse
from json.decoder import JSONDecodeError
from users.models import User
from users.utils import login_decorator
from postings.models import Posting, Image, Comment, Like
class PostingView(View):
@login_decorator
def post(self,request):
try:
data = json.loads(request.body)
user = request.user
content = data.get('content')
image_url_list = data.get('image_url').split(',')
post = Posting.objects.create(
content = content,
user = user,
)
for image_url in image_url_list:
Image.objects.create(
image_url = image_url,
posting = post
)
return JsonResponse({'message':'SUCCESS'}, status=200)
except AttributeError:
return JsonResponse({'message':'NO_IMAGE_URL'}, status=400)
except JSONDecodeError:
return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
@login_decorator
def get(self, request):
try:
posts = Posting.objects.all()
result= [{
'Name' : User.objects.get(id=post.user.id).name,
'Content' : post.content,
'Images' : [image.image_url for image in post.images.all()],
'Created_at': post.created_at
} for post in posts
]
return JsonResponse({'message': result}, status=200)
except JSONDecodeError:
return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
post
request.user
에 저장한 JWT
의 user_id
를 변수 user
에 다시 저장한다.
여러 장의 이미지를 따옴표 안의 쉼표로 구분해서 요청받은 뒤에, 그것을 split()
함수를 이용해 배열로 저장한다.
posting
table에 게시글과 유저는 그대로 저장하지만, 이미지는 여러장이므로 for문을 이용하여 저장했던 배열을 불러와 여러 장을 저장한다.
이전에 회원정보를 받을 때 사용했던 것 처럼 data['image_url']
이 아니라data.get('image_url')
을 사용하였다.
get
메서드를 사용하였으므로, request에 이미지 URL이 존재하지 않을 때에는 파이썬 딕셔너리 key값 오류로 인해서 발생하는 KeyError
가 아닌 None
Type을 반환해준다.
여기에서 이미지 여러개를 받을 때를 대비해서 split()
메서드를 사용했는데, None
Type은 문자열이 아니므로 해당 속성(attribute)을 가지고 있지 않다. 따라서 AttributeError
로 예외처리 해준다.
게시글이 성공적으로 작성되었다.
POST
요청으로 작성했던 게시물도 GET
요청으로 성공적으로 조회했다.
댓글은 게시물과 똑같이 postings 앱에서 작성해준다.
# postings/models.py
class Comment(models.Model):
content = models.CharField(max_length=2200)
user = models.ForeignKey("users.User",on_delete=models.CASCADE, related_name='comments')
posting = models.ForeignKey("Posting",on_delete=models.CASCADE, related_name='comments')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'comments'
한 게시물에는 여러 댓글이 들어갈 수 있으므로 일대다 관계로 만들어준다.
마찬가지로 여러 유저가 댓글을 작성할 수 있으므로 일대다 관계로 만들어준다.
class CommentView(View):
@login_decorator
def post(self, request):
try:
data = json.loads(request.body)
user = request.user
content = data.get('content')
posting_id = data.get('posting')
if not (content and posting_id) :
return JsonResponse({'message' : 'KEY_ERROR'}, status=400)
if not Posting.objects.filter(id=posting_id).exists():
return JsonResponse({'message':"POSTING_DOES_NOT_EXIST"}, status=404)
posting = Posting.objects.get(id = posting_id)
Comment.objects.create(
content = content,
user = user,
posting = posting
)
return JsonResponse({'message':'SUCCESS'}, status=200)
except JSONDecodeError:
return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
def get(self, request):
try:
comments = Comment.objects.filter(posting_id=1)
result = [{
'Name' : User.objects.get(id=comment.user.id).name,
'Content' : comment.content,
'Created_at' : comment.created_at
} for comment in comments
]
return JsonResponse({'message': result}, status=200)
except JSONDecodeError:
return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
댓글과 작성자의 id정보가 없을때는 KeyError
반환
댓글을 달려는 게시물이 없을때는 exist()
메서드를 사용하여 DOESNOTEXIST
에러 반환
BODY에 아무 내용이 없을 경우에는 JSON_DECODE_ERROR
반환
POST
GET
전부 성공적으로 수행
# postings/models.py
from django.db import models
from users.models import User
class Posting(models.Model):
content = models.CharField(max_length=2200, null=True)
user = models.ForeignKey("users.User",on_delete=models.CASCADE, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
like = models.ManyToManyField("users.User", related_name= 'likes', through="Like")
class Meta:
db_table = "postings"
/...
class Like(models.Model):
posting = models.ForeignKey('Posting', on_delete=models.CASCADE, related_name='post_likes')
user = models.ForeignKey('users.User',on_delete=models.CASCADE,related_name='user_likes')
class Meta:
db_table = 'likes'
한 유저는 여러 게시물에 좋아요 가능
한 게시물은 여러명의 유저에게 좋아요를 받을 수 있음
좋아요는 유저와 게시물의 중간 테이블 역할을 수행, ManyToManyField
, through
옵션 사용
역참조에 쓰이는 related_name
은 복수형으로 설정(one 이 many 테이블을 참조하므로)
# postings/views.py
class LikeView(View):
@login_decorator
def post(self, request):
try:
data = json.loads(request.body)
user = request.user
posting_id = data.get('posting')
posting = Posting.objects.get(id = posting_id)
obj, created = Like.objects.get_or_create(user = user, posting = posting)
if not created:
obj.delete()
return JsonResponse({'message' : 'CANCEL_LIKE'},status =200)
else:
return JsonResponse({'message':'SUCCESS'}, status=200)
except Posting.DoesNotExist:
return JsonResponse({'message':"POSTING_DOES_NOT_EXIST"}, status=404)
except JSONDecodeError:
return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
좋아요가 이미 되어있는 상황일 경우에는, 좋아요를 취소하기 위해서 get_or_create
함수를 사용
get_or_create
조건을 만족하는 객체를 찾아 존재하지 않을 경우에는 객체를 생성하고, 객체와 객체의 존재 여부를 튜플 형태로 반환
객체가 존재하지 않을 때는 True, 존재할때는 False를 반환한다.
여기서는 객체를 obj
로, 존재 여부를 created
로 변수 선언했다.
created
가 False
일 경우 (존재할 때), 객체인 obj
를 delete
를 사용해 삭제하고, 그렇지 않을 때는 성공 메시지를 반환한다.
좋아요를 이미 되어있는 게시물에 했을 때 정상적으로 취소 처리되는 것을 확인할 수 있다.
팔로우는 유저(사용자)간의 관계를 나타내는 데이터이므로 다시 user app으로 돌아와서 작성한다.
# users/models.py
from re import A
from django.db import models
class User(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField(max_length=100, unique=True)
password = models.CharField(max_length=200)
phone_number = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
follow = models.ManyToManyField('self', through='Follow', symmetrical=False)
class Meta:
db_table = 'users'
class Follow(models.Model):
follower = models.ForeignKey('User',on_delete=models.CASCADE, related_name='following')
following = models.ForeignKey('User',on_delete=models.CASCADE, related_name='follower')
class Meta:
db_table = 'follows'
한 유저는 여러 유저를 팔로우 할 수 있다. 다른 유저도 여러 유저를 팔로우 할 수 있다.
즉 자기 자신을 참조하는 다대다 관계이다.
동일한 필드를 참조하므로 self
를 적어주고, Follow
클래스를 선언한 뒤 through
옵션을 이용해 중간테이블로 지정해준다.
어떤 유저가 다른 유저를 팔로우 할 때, 다른 유저도 자동으로 해당 유저를 팔로우 하는 것이 아니므로 대칭(symmetrical
)을 False
로 설정해준다.
자기 자신을 참조할때는 related_name
의 설정이 중요하다.
한 유저의 follower
는 이 유저를 following
하고 있는 것이고,
한 유저가 following
하는 유저는 그 유저의 follower
가 된다.
즉 역참조할때는 서로의 위치가 바뀌므로 related_name
을 역으로 설정해 준다.
# users/views.py
class FollowView(View):
@login_decorator
def post(self, request):
try:
data = json.loads(request.body)
user = request.user
following_id = data.get('user_id')
following = User.objects.get(id=following_id)
if not User.objects.filter(id = following_id).exists():
return JsonResponse({'message':"USER_DOES_NOT_EXIST"}, status=404)
if user.id == following.id:
return JsonResponse({'message' : 'CANNOT_FOLLOW_MYSELF'})
else:
obj, created = Follow.objects.get_or_create(follower = user, following = following)
if not created:
obj.delete()
return JsonResponse({'message' : 'CANCEL_FOLLOW'},status =200)
return JsonResponse({'message':'SUCCESS'}, status=200)
except JSONDecodeError:
return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
@login_decorator
def get(self, request):
try:
data = json.loads(request.body)
user_id = data.get('user_id')
target_user = User.objects.get(id=user_id)
result = {
'user' : [{
'name' : target_user.name,
'id' : target_user.id,
}],
'follower_list' : [{
'id' : follower.id,
'name' : follower.follower.name
} for follower in target_user.follower.all()],
'following_list' : [{
'id' : following.id,
'name' : following.following.name
} for following in target_user.following.all()]
}
return JsonResponse({'message':'SUCCESS' , 'Follow_list' : result}, status=200)
except JSONDecodeError:
return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
팔로우와 마찬가지로 get_or_create
를 활용해 이미 되어있는 팔로우를 다시 요청하면 취소하는 로직을 작성
자기 자신을 팔로우 할수는 없으므로 if문을 활용해 로직 작성
유저의 팔로우 리스트를 반환받는 GET
메서드는, 리스트 컴프리헨션과 역참조를 적절하게 활용, 팔로워와 팔로우 하고 있는 사람의 이름과 아이디를 중첩 딕셔너리 형태로 출력
팔로우 POST
요청
자기 자신 Follow 요청시 에러 출력
팔로우 리스트 GET
요청으로 출력