이번에는 게시글을 작성할 수 있는 앱을 구현해보려고 한다.
- 앱 생성
- 모델링
- 뷰 작성
장고에서는 다루는 데이터를 기준으로 앱을 나눈다. 후에 게시글 데이터를 다루기 위한 모델링을 해줄 것이기 때문에 따로 앱을 생성해준다.
> python manage.py startapp postings
# settings.py
INSTALLED_APPS = [
'postings',
...
]
게시글에서 필요한 것은 사용자, 이미지 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
을 지정해주므로써 이름을 더 간결하거나 직관적으로 작성해 줄 수 있다.
게시글을 작성하고 이를 조회해볼 수 있는 뷰이다.
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를 사용하는 것을 구현해보았다.
둘째. 여러 모델에 공통적으로 들어가야하는 필드가 있다면 추상 클래스로 따로 빼서 이를 상속받을 수 있도록 하는 것이다. 이렇게 하면 중복되는 코드를 줄일 수 있다.