TIL 30 | Westagram Posting & Comment

임종성·2021년 7월 24일
1

Django

목록 보기
11/17
post-thumbnail

Westagram 프로젝트를 진행하며 Django 세팅을 완료하고 User Model과 회원가입, 로그인 기능까지 구현했었다. 인증과 인가는 아직 진행하지 않았지만, 가입한 회원의 게시물 등록과 댓글 기능을 구현해봤다. 구현에 앞서 Westagram Project는 Github으로 연동되어 있으므로 새로 Github Repository를 만들어 SignIn 기능까지 완료하고 시작했다.

Modify User APP

User Table에 Nickname 추가

기존 User Table은 name, email, password, phone_number, age만 가지고 있었다. 그런데 게시물 등록 기능을 구현하다 생각해보니, 등록했을때 유저의 정보를 표출할 경우 email을 넣자니 너무 난잡하고 이름을 넣자니 중복될 수 있어 애매했다. 그래서 Unique하면서 특징을 그대로 살릴 수있는 nickname field를 추가했다.

class User(models.Model):
	.
    	.
        .
    nickname = models.CharField(max_length=45, default="", unique=True)

Nickname 중복사용 금지

추가로, UserView에서 email이 존재할 경우 EMAIL_ALREADY_EXIST라는 에러를 return했었는데 nickname이 존재할 경우도 DATA_ALREADY_EXIST 에러를 return하도록 했다.

if User.objects.filter(email = data['email']).exists():
	# return JsonResponse({"MESSAGE":"EMAIL_ALREADY_EXIST"}, status=400)
    	return JsonResponse({"MESSAGE":"DATA_ALREADY_EXIST"}, status=400)
if User.objects.filter(nickname = data['nickname']).exists():
	return JsonResponse({"MESSAGE":"DATA_ALREADY_EXIST"}, status=400)

Migration Error

nicknameunique=True 옵션을 설정하고 Migration하니, IntegrityError가 발생했다.

에러를 해결하기 위해 우선 Migration 파일을 지우고 unique속성을 없앤 후 migrate했다. 그리고 Query를 이용해 직접 User들의 nickname에 서로 다른 값을 부여한 후 unique=True로 수정하고 migrate했다.

Post & Comment Model

우선 게시물의 데이터와 사용자의 데이터는 그 성질이 달라 데이터베이스에서 테이블을 따로 관리한다고 하기에 앱을 분리하기 위해 Posts App을 추가하였다. App 이름을 Postings로 해야 했는데 너무 늦게 알아차렸고, 이름을 일일히 변경하고 나니 이전 Migration Data와 충돌해서 일단 posts로 진행하였다.

Post와 Comment의 Model을 구현하기에 앞서 Database 관계도를 만들기 위해 AQueryTrool을 이용했다. ERD는 다음과 같이 구성했다.

Post의 Comment의 User는 User Table과 ForeginKey로 연결되고, Comment의 Post는 Post Table과 ForeginKey로 연결된다.

DateTimeField과 TimeZone 설정

이전에 Model에 대해 포스팅한적이 있는데, DateTimeField의 argument중 auto_now_add는 오로지 Field가 Create될때 현재시간으로 지정한다. 데이터를 수정하고 싶다면 default argument를 사용하는 것이 좋은데, 생성 시간은 수정할 일이 없으므로 auto_now_add=True를 사용했다.

또한 Django에서 기본 시간은 UTC이므로 Setting에서 TimeZone 설정을 바꿔주었다.

# westagram/settings.py

TIME_ZONE = 'Asia/Seoul'
USE_TZ	  = False

다른 App의 Table을 ForeignKey로 사용

posts App에서 users App의 User Class를 ForeignKey를 사용할 경우 주석처리한 코드로 했더니 에러가 나타났다. 참조할 Table의 이름을 AppName.TableName으로 지정하니 제대로 적용되었다.

# posts/models.py
# user = models.ForeignKey('User', on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='comments')

Post & Comment Model

여러 시행착오 끝에 필요한 data에 따라 Post와 Comment의 Model을 작성해봤다. Model을 토대로 View Logic을 구현해보자.

from django.db import models

class Post(models.Model):
    user          = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='posts')
    creation_time = models.DateTimeField(auto_now_add=True)
    image         = models.URLField()
    post          = models.TextField()

    class Meta:
        db_table  = 'posts'

class Comment(models.Model):
    user          = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='comments')
    post          = models.ForeignKey('Post', on_delete=models.CASCADE, related_name='comments')
    creation_time = models.DateTimeField(auto_now_add=True)
    comment       = models.TextField()

    class Meta:
        db_table  = 'comments'

Post View

PostView의 기능은 Post와 Get을 사용하여 게시물을 등록하고, 표출해주는 것이다. 각 기능을 구현하기 위한 조건들은 다음과 같이 설정하여 Model을 작성했다.

게시물 등록

  • 게시물을 등록하기 위해 사용자, 생성 시간, 이미지 url이 필요하다.
  • 게시물 생성 시간은 등록하는 현재 시간이어야 한다.
  • 등록된 사용자가 아닐 경우 Invalid User 에러를 반환한다.
  • 필요한 정보가 누락되었을 경우 KeyError를 반환한다.

게시물 표출

  • 등록된 모든 게시물을 나열해야 한다.
  • 게시물을 나타낼 때에는 등록한 사람, 게시물, 게시된 내용, 게시된 시각이 포함되어야 한다.

작성한 PostView Class

class PostView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)

            if not User.objects.filter(email = data['email']).exists():
                return JsonResponse({"MESSAGE":"INVALID_USER"}, status=400)

            Post.objects.create(
                user            = User.objects.get(email=data['email']),
                image           = data['image'],
                post            = data['post'],
            )
        
            return JsonResponse({"MESSAGE":"SUCCESS"}, status=201)
        except KeyError:
            return JsonResponse({"MESSAGE":"KEY_ERROR"}, status=400)
    def get(self, request):
        posts = Post.objects.all()
        results = []

        for post in posts:
            results.append(
                {
                    "user"          : post.user.nickname,
                    "image"         : post.image,
                    "post"          : post.post,
                    "creation_time" : post.creation_time
                }

            )
        return JsonResponse({'resutls':results}, status=200)

UserView와 크게 다르지 않게 Model Class를 만들 수 있었고, GET Method에서 return하는 useremail이 아닌 nickname으로 하였다.

httpie request

게시물 등록, 표출 기능 모두 잘 작동하는 걸 볼 수 있다.

# 게시물 등록 기능
http -v POST 127.0.0.1:8000/posts/post email="qkqndudnxld@gmail.com" image="https://img.hani.co.kr/imgdb/resize/2019/0121/00501111_20190121.JPG" post="갱얼쥐입니다"

# 게시물 표출 기능
http-v GET 127.0.0.1:8000/posts/post

Comment View

CommentView의 기능은 등록된 게시물에 댓글을 등록하고, 등록된 댓글을 나열하는 것이다. 각 기능을 구현하기 위한 조건들은 다음과 같이 설정하여 Model을 작성했다.

댓글 등록

  • 댓글을 등록하기 위해 댓글이 달리는 게시물, 사용자, 생성시간, 댓글 내용이 필요하다.
  • 댓글을 생성한 시간은 현재 시간이어야 한다.
  • 등록된 사용자가 없다면 Invalid User, 등록된 게시물이 없다면 No Post Error를 반환한다.
  • 필요한 정보가 누락될 경우 KeyError를 반환한다.

댓글 나열

  • 1번 게시물에 등록된 댓글만을 표출할 수 있도록 구현하라.
  • 댓글을 나열할 경우 댓글을 등록한 사용자, 댓글 내용, 생성시간이 포함되어야 한다.

작성한 CommentView Class

class CommentView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)

            if not User.objects.filter(email=data['email']).exists():
                return JsonResponse({"MESSAGE":"INVALID_USER"}, status=400)

            if not Post.objects.filter(id = data['post']).exists():
                return JsonResponse({"MESSAGE":"NO_POST"}, status=400)

            Comment.objects.create(
                user            = User.objects.get(email=data['email']),
                post_id         = data['post'],
                comment         = data['comment']         
            )

            return JsonResponse({"MESSAGE":"SUCCESS"}, status=201)
        except KeyError:
            return JsonResponse({"MESSAGE":"KEY_ERROR"}, status=400)
    def get(self, request):
        comments = Comment.objects.filter(post_id=1)
        results = []

        for comment in comments:
            results.append(
                {
                    "user"          : comment.user.nickname,
                    "comment"       : comment.comment,
                    "creation_time" : comment.creation_time
                }
            )
        return JsonResponse({'resutls':results}, status=200)
  • httpie로 입력받는 게시물 정보(data['post']는 마땅히 사용할 field가 없어 게시물의 id로 하였다.(image나 creation_time으로 할순 없으므로)
  • 1번 게시물에 달린 댓글만 표출하기 위해 Comment.objects.filter(post_id=1)을 사용하여 result 리스트에 추가하였다.
  • 댓글을 단 사용자의 정보는 nickname으로 하였다.

httpie request

댓글 작성, 나열 모두 잘 구현된 것을 확인할 수 있었다.

# 댓글 등록 기능
http -v POST 127.0.0.1:8000/posts/comment email="qkqndudnxld@gmail.com" comment="장동건입니다" post=1

# 댓글 나열 기능
http -v GET 127.0.0.1:8000/posts/comment

🤔 Curious

게시물을 GET할 경우 모든 게시물을 가져왔다. 특정 사용자가 등록한 모든 게시물을 가져오거나, 특정 날짜 이후에 등록한 게시물을 가져오는 등 조건이 붙어 있을 경우 어떻게 해결할까? 단순 View에서 해결하는 것이 아니라 사용자가 httipe로 지정하는 경우는?

Comment의 경우에도 첫번째 게시글의 댓글만 나열했다. Get Method의 Url에 posts/1/comments는 첫번째 게시물의 댓글들, posts/2/comments는 두번째 게시물의 댓글들을 가져오는 것처럼 url을 이용해 특정 게시물의 댓글을 가져올 수 있을까? Get에 Body가 없는데 어떻게 가능할까?

profile
어디를 가든 마음을 다해 가자

0개의 댓글