[TIL / Django] Westagram 6 - 게시글 작성 기능 (postings app)

나른한 개발자·2022년 1월 23일
0

studylog

목록 보기
34/45
post-custom-banner

이번에는 게시글을 작성할 수 있는 앱을 구현해보려고 한다.

  1. 앱 생성
  2. 모델링
  3. 뷰 작성

1. 앱 생성

장고에서는 다루는 데이터를 기준으로 앱을 나눈다. 후에 게시글 데이터를 다루기 위한 모델링을 해줄 것이기 때문에 따로 앱을 생성해준다.

> python manage.py startapp postings
# settings.py

INSTALLED_APPS = [
	'postings', 
	...
    ]

2. 모델링

게시글에서 필요한 것은 사용자, 이미지 URL, 작성일자이다. 여기부터는 생성일자, 수정일자 필드가 여러 모델에 겹치게 되므로 users.models에 TimeStampModel라는 추상 클래스를 정의하여 이를 상속받도록 했다.

### users.model

class TimeStampModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True, null=True)
    updated_at = models.DateTimeField(auto_now=True, null=True)
    
    class Meta:
        abstract = True
        
class User(TimeStampModel):
    name = models.CharField(max_length=45, null=True)
    email = models.CharField(max_length=200, unique=True)
    password = models.CharField(max_length=400)
    phone_number = models.CharField(max_length=45, null=True)

    class Meta:
        db_table = 'users'
### postings.model

from django.models import Model

from users.models import TimeStampModel

class Post(TimeStampModel):
    user = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='related_post')
    description = models.TextField(null=True)
    
    class Meta:
        db_table = 'posts'
        
class Image(models.Model):
    post = model.ForeignKey('Post', on_delete=models.CASCADE, related_name='related_image')
    image_url = model.URLField(max_length=2000)
    
    class Meta:
        db_table = 'images'

  • Post와 User 사이의 one-to-many 관계를 표현하기 위해 Post 클래스에 외래키를 주었는데, 처음에 다음과 같이 입력하여 마이그레이션 시 에러가 떴었다.
user = models.ForeignKey('User', on_delete=models.CASCADE, related_name='related_post')
SystemCheckError: System check identified some issues:

ERRORS:
postings.Post.user: (fields.E300) Field defines a relation with model 'User', which is either not installed, or is abstract.
postings.Post.user: (fields.E307) The field postings.Post.user was declared with a lazy reference to 'postings.user', but app 'postings' doesn't provide model 'user'.
  • User라는 모델을 참고하려는데 이를 찾을 수가 없다는 에러이다. 따라서 'users.User' 처럼 명확히 명시해주어야 한다.
  • 한 사용자는 여러 게시물을 작성할 수 있으므로 일대다 관계로 설정하였다.

  • description 필드는 사진과 함께 작성하는 게시글이 저장되는 곳으로, 게시글 없이 사진만 업로드 할 수 있으므로 null=True로 주었다.

  • 한 포스트 당 여러 이미지가 있을 수 있으므로 post와 image도 일대다 관계로 설정하였다. 찾아보니, URL데이터를 다루는 URLField가 있어 이를 써보았다. CharField의 서브 클래스이므로 이 역시 max_length를 지정해준다.

  • related_name - ForeignKey에 related_name을 각각 지정해주었는데, 이는 역참조의 용이함 때문이다.
    예를 들어, User에서 Post를 참조할 경우 또는 Post에서 Image를 참조할 경우에 외래키로 참조할 수가 없어 _set으로 접근해야 했다. 하지만 related_name을 지정해주므로써 이름을 더 간결하거나 직관적으로 작성해 줄 수 있다.


3. 뷰 작성

게시글을 작성하고 이를 조회해볼 수 있는 뷰이다.

import json

from django.http import HttpResponse, JsonResponse
from django.views import View

from users.utils import token_auth
from users.models import User
from postings.models import Post, Image

class PostingView(View):
[1] @token_auth
    def post(self,request):
        data = json.loads(request.body)
        
        try:
[2]            user_id = request.user.id 		
[3]            description = request.get('description', None)
[4]            image_url = data['image_url']
            
[5]            user = Users.objects.get(id=user_id)
            
[6]            post = Post.objects.create(user=user ,description=description)
[7]            Image(post=post, image_url=image_url).save()
            
[8]            return HttpResponse(status=201)
[9]        except KeyError:
[10]            return JsonResponse({'message': 'KEY_ERROR}, status=400)

게시글 작성

  • [1]: 미리 작성해놓은 토큰 인증 데코레이터를 사용하여 로그인을 한 회원만 글을 작성할 수 있도록 하였다.

  • [2], [5]: 토큰 인증 후 받아온 user의 id를 이용하여 해당 사용자의 객체를 받아온다.

  • [3]: request에서 description에 해당하는 값을 가져오는데, 이 값은 null=True이기 때문에 키가 없을 시 None을 반환할 수 있도록 하였다.

  • [4]: 처음에는 image_url을 따로 변수에 저장하지 않고 바로 image_url = data['image_url'] 로 작성하였는데, 이 경우 image_url이 넘어오지 않아 KeyError가 났는데도 Post에 데이터가 저장되는 경우가 발생했다. 이를 방지하기 위해 image_url을 변수로 저장하여 값이 넘어오지 않았을 시 KeyError로 바로 처리될 수 있도록 하였다.

  • [8]: 별다른 메시지가 아닌 상태코드만 반환할 것이기 때문에 HttpResponse로 반환하였다.



@token_auth
def get(self,requset):
    try:
        post = Post.objects.get(id=request.get(post_id))
        
        result = [
            {
                    'user': post.user.id,
                    'image': [{'image': image.image_url} for image in post.related_image.all()],
                    'description': post.description,
                    'created_at': post.created_at
             }
        ]
        
        return JsonResponse({'results': result}, status=200)
    except Post.DoesNotExists:
        return JsonResponse({'message': 'POST_DOES_NOT_EXISTS'}, status=400)

게시글 조회

  • 게시글은 사용자, 이미지, 내용, 작성날짜를 불러오면 된다. 내가 생각한 게시글 조회는 sns에서 특정 게시물을 눌러 상세보기 하는 것이라고 생각하고 특정 게시물의 사진과 게시글 내용을 불러오도록 했다.

  • front 쪽에서 특정 게시글에 대한 요청을 할 때 반드시 post_id를 넘겨줄 거라고 생각하여 DoesNotExists 처리를 안하려고 했었다. 하지만 생각해보니 작성자가 어떤 게시글을 삭제하고, 다른 사용자는 새로고침하지 않은 상태에서 삭제된 게시글을 요청하는 경우가 있을 수 도 있겠다 싶어 DoesNotExists로 예외처리를 해주었다.




오늘의 배운점

첫째. 토큰 인증 후 넘겨받은 user_id 사용하기. 늘 하던 것처럼 http 요청에서 id를 받아오는 것이 아니라 토큰 인증 후 넘어온 user_id를 사용하는 것을 구현해보았다.
둘째. 여러 모델에 공통적으로 들어가야하는 필드가 있다면 추상 클래스로 따로 빼서 이를 상속받을 수 있도록 하는 것이다. 이렇게 하면 중복되는 코드를 줄일 수 있다.

profile
Start fast to fail fast
post-custom-banner

0개의 댓글