Aimmo 개발 과제로 게시판 Restful API를 작성해 보았다.
필수 조건은 아래와 같다.
Name | Git | Blog |
---|---|---|
김정수 | https://github.com/hollibleling | https://velog.io/@hollibleling |
윤현묵 | https://github.com/fall031-muk | https://velog.io/@fall031 |
최현수 | https://github.com/filola | https://velog.io/@chs_0303 |
21.11.1 ~ 21.11.3
Method | endpoint | Request Header | Request Body | Remark |
---|---|---|---|---|
POST | /user/signup | name, nickname, email, password | 회원가입 | |
POST | /user/login | email, password | 로그인 | |
POST | /post/list | Authorization | title, body, category | 게시물 작성 |
GET | /post/detail/<int:post_id> | Authorization, cookie | 게시물 조회 및 댓글 조회 | |
DELETE | /post/detail/<int:post_id> | Authorization | 게시물 삭제 | |
PUT | /post/detail/<int:post_id> | Authorization | title, body, category | 게시물 수정 |
GET | /post/list?page= | 게시물 목록 조회 | ||
GET | /post/list?category= | 게시물 카테고리 필터 | ||
POST | /post/<int:post_id>/comments | content, parent_comment_id | 댓글/대댓글 작성 | |
GET | /post/<int:post_id>/comments?limit=&offset=&comment_id= | 대댓글 조회 | ||
PATCH | /post/<int:post_id>/comments | comment_id | 댓글/대댓글 수정 | |
DELETE | /post/<int:post_id>/comments?comment_id= | 댓글/대댓글 삭제 |
초기 셋팅 및 기본 게시판 CRUD구현은 기존에 pre-onboarding 지원과제로 제출했던 git을 클론해와서 진행하였다.
필수 요청 사항 중, 기술적으로 3가지로 나눠 각자 하나씩 맡아 진행하였다.
그 중 내가 맡은 부분은 게시물 카테고리 적용과 게시물 조회수 구현 이였다.
django와 mongodb를 연결하기 위해 djongo 모듈을 사용하여 진행하였다.
djongo는 django의 ORM을 지원해주는 모델이다.
연결을 위해 아래의 강의를 보며 학습했다.
Using Django with MongoDB | MongoDB + Django CRUD API
또한 mongodb를 좀 더 쉽게 사용하기 위해 GUI환경인 MongoDB Compass를 사용하였다.
MongoDB Compass
카테고리는 RDBMS로 보았을 때 게시물과 1:M 관계로 모델링을 추가하고 카테고리가 출력 되어져야 하는 부분에 각각 데이터를 받아와주었다.
# models.py
class Post(TimeStamp):
...
category = models.ForeignKey("Category", on_delete=models.CASCADE)
...
class Category(models.Model):
name = models.CharField(max_length=100)
class Meta:
db_table="categories"
또한 카테고리를 통한 필터를 가능하게 만들어 필터에 대한 정보가 주어졌을 때 그에 맞는 카테고리를 가지고 있는 게시글들만 출력하게 구현하였다.
# view.py
class ListView(View):
def get(self, request):
if category:
category_id = Category.objects.get(name=category).id
posts = Post.objects.filter(category_id=category_id).order_by('-id')
else:
posts = Post.objects.all().order_by('-id')
또한 전체 게시글을 출력할 때 pagination을 통해 출력되는 게시글의 개수를 정해줬었는데 이 또한 동일하게 구현하였다.
# view.py
class ListView(View):
def get(self, request):
try:
page = request.GET.get("page")
page = int(page or 1)
page_size = 10
limit = page_size * page
offset = limit - page_size
...
result = [{
"count" : len(posts)-(offset+i),
"title" : post.title,
"hit" : post.hit,
"body" : post.body,
"nickname" : post.user.nickname
} for i,post in enumerate(posts [offset:limit])]
데이터를 가져올 때 게시글 앞에 번호를 매겨주고 싶었는데 데이터를 hard delete로 지울 경우 id값이 밀리는 현상이 생겨 고민을 하다가 전체 게시글의 수에서 offset과 반복되는 인덱스만큼의 값을 빼줘 중간에 데이터가 비어있다해도 번호가 이어지게 만들었다.
기존에 구현한 조회수 같은 경우 get으로 게시글이 불러오질 때마다 hit을 1씩 추가시켜 저장을 하여 구현했었다.
post.hit += 1
post.save()
하지만 위와 같이 구현할 경우 한 사용자가 중복으로 조회수를 올릴 수 있는 문제 점이 존재하였다.
이에 관하여 찾아보니 3가지 방법이 있었다.
- session
- ip
- cookie
세 가지 방식 중 가장 많이 사용되는 방식이 cookie 를 통하여 조회수를 관리하는 방법이였다. 기존 방식에는 cookie 만료 시간을 주어 지정한 시간이 지나면 자동으로 쿠키가 삭제되는 방식이였다. 하지만 이번 과제에는 "중복된 user가 조회수를 올릴 수 없게 구현" 이라 서술되어 있어 만료시간을 주지 않았다.
class PostView(View):
@transaction.atomic
@login_decorator
def get(self, request,post_id):
try:
post = Post.objects.get(id=post_id)
...
response = JsonResponse({"Result" : result, "Comment" : Result_comment}, status=200)
if request.COOKIES.get('hit'):
cookies = request.COOKIES.get('hit')
cookies_list = cookies.split('|')
if str(post.id) not in cookies_list:
post.hit += 1
post.save()
result["hit"] = post.hit
response = JsonResponse({"Result" : result, "Comment" : Result_comment}, status=200)
response.set_cookie('hit', cookies+f'|{post.id}', expires=None)
else:
post.hit += 1
post.save()
result["hit"] = post.hit
response = JsonResponse({"Result" : result, "Comment" : Result_comment}, status=200)
response.set_cookie('hit', post.id, expires=None)
return response
except Post.DoesNotExist:
return JsonResponse({"message" : "POSTS_NOT_FOUND"}, status=404)