이전 시간에는 장고로 인스타그램 회원가입 기능과 로그인을 구현하고, bcrypt와 JWT로 인증 과정을 구현하였습니다. 이번에는 게시물을 등록하는 기능을 구현하도록 하겠습니다.
작성자
, 이미지
, 내용
, 생성 시간
이 필요합니다.내용
은 있어도 되고 없어도 됩니다.사진
은 여러 개 등록할 수 있습니다. 작성자
는 이미 서비스에 가입된 유저여야 합니다. 게시물 정보를 관리할 수 있는 posting
이라는 앱을 만들도록 하겠습니다.
python manage.py startapp posting
Django에서는 주로 다루는 데이터의 종류가 달라지는 시점에서 앱을 분리합니다. 인스타그램의 게시물은 이용자 데이터와는 그 성질이 달라 데이터베이스에서 테이블을 따로 관리합니다. 따라서, 주로 다루는 테이블이 달라지므로 앱을 분리하는 것이 좋습니다.
settings.py
의 INSTALLED_APPS
에 생성한 앱을 등록합니다.
# westagram/settings.py
INSTALLED_APPS = [
# 'django.contrib.admin',
# 'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'user',
'posting', ---> 입력!
]
# posting/models.py
from django.db import models
from user.models import User
class Posting(models.Model):
content = models.CharField(max_length=2000, null=True)
created_at = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey('user.User', on_delete=models.CASCADE)
class Meta:
db_table = 'postings'
class Image(models.Model):
image_url = models.URLField(max_length=2000)
posting = models.ForeignKey('Posting', on_delete=models.CASCADE)
class Meta:
db_table = 'images'
Q. content
에 null=True
를 지정한 이유는?
content
는 게시물 내용을 의미합니다. 이미지가 아닌 내용은 꼭 작성되지 않아도 되므로 null=True
로 지정하였습니다.
Q. Image
클래스를 따로 만든 이유는?
하나의 게시물에 이미지는 여러 개 등록될 수 있습니다. 즉, Posting
과 Image
는 One-to-many(1:N)의 관계입니다. 따라서 Posting을 참조하는 Image 클래스를 따로 만들고, ForeignKey 필드로 연결해주었습니다.
Q. User
모델 클래스를 import한 이유는?
게시물과 댓글의 작성자
는 이미 서비스에 가입된 유저여야 합니다. 이용자 데이터를 관리하고 있는 user 앱의 model에 있는 User
클래스를 사용하기 위해서 import했습니다.
Q. 어떻게 다른 앱에 있는 클래스를 참조할 수 있는지?
여기서 어떻게 posting
앱의 Posting
클래스가 user
앱의 User
클래스를 참조할 수 있을까요?
(우선 user
앱의 User
클래스를 import한 상태입니다.)
일반적으로 하듯이 ForeignKey 필드에User
클래스를 인자로 받았습니다.
user = models.ForeignKey('User', on_delete=models.CASCADE)
그리고 makemigrations
를 시도해봅시다.
🚨 에러가 발생합니다! User
클래스가 user
앱 출신이라는 것을 알려주지 않아서 발생하는 에러입니다.
User
를 user.User
로 변경합니다.
user = models.ForeignKey('user.User', on_delete=models.CASCADE)
이제 user
앱의 User
클래스와 posting
앱의 Posting
클래스의 One-to-many 관계가 결성되었습니다.
Model
작성이 완료되었으면 makemigrations
로 migration 파일을 만들어줍니다.
python manage.py makemigrations user
에러가 나지 않고 정상적으로 migarion 파일이 만들어졌으면 migrate
로 실제 DB에 변경 사항을 적용해봅시다.
python manage.py migrate user
먼저 게시물 등록에 필요한 user(작성자)
, content(내용)
, image_url(이미지)
정보를 받아옵니다.
user
변수에는 User
클래스에서 가져온 객체를 담습니다. image_url_list
라는 변수에는 리스트 형태로 받아온 image_url
을 담습니다. 이후에 반복문으로 리스트 안의 값들을 하나씩 빼올 것입니다. data = json.loads(request.body)
user = User.objects.get(username=data.get('user', None))
content = data.get('content', None)
image_url_list = data.get('image_url', None)
content
는 없어도 되는 정보이므로 user
와 image_url_list
에만 값이 잘 담겨져있는지 확인합니다. 값이 없을 경우에는 KeyError가 발생하지 않도록 처리합니다.
if not (user and image_url_list):
return JsonResponse({'message':'KEY_ERROR'}, status=400)
user
와 content
는 create()
를 사용하여 바로 데이터를 생성할 수 있습니다.
posting = Posting.objects.create(
user = user,
content = content
)
하지만 image_url_list
는 이미지 정보가 담긴 리스트입니다. for문을 사용하여 리스트의 각 값들을 하나씩 가져오면서, for문 안에서 create()
로 데이터를 생성합니다. 특히, image_url
의 foreign key인 posting
도 함께 생성해야 합니다.
for image_url in:
Image.objects.create(
image_url = image_url,
posting = posting
)
게시물을 두 가지 방법으로 가져올 수 있게 구분하였습니다. 바로 모든 게시물을 가져오는 방식과 특정 유저의 게시물만 가져오는 방식입니다.
먼저 모든 게시물을 표출하는 기능부터 구현하겠습니다.
def get(self, request):
posting_list = [{
"username" : User.objects.get(id=posting.user.id).username,
"content" : posting.content,
"image_url" : [i.image_url for i in Image.objects.filter(posting_id=posting.id)],
"create_at" : posting.created_at
} for posting in Posting.objects.all()
]
return JsonResponse({'data':posting_list}, status=200)
리스트 컴프리헨션을 사용해서 모든 게시물의 정보가 이쁘게 리스트에 담기도록 했습니다.
우선 Posting.objects.all()
은 모든 게시물을 정보를 QuerySet으로 가져옵니다. 그리고 for문으로 QuerySet안에 있는 객체를 하나씩 posting에 넣습니다. 이제 posting는 객체라고 할 수 있겠지요.
객체가 들어있는 posting
을 활용해서 게시물의 속성을 가져올 수 있습니다. posting.user.id
, posting.content
이런 식으로 말입니다. 이렇게 필요한 정보들을 잘 뽑아내서 key, value 형식의 딕셔너리에 담아냅니다.
이제 posting_list
는 모든 게시물의 각 정보들이 딕셔너리 형태로 담긴 리스트가 되었습니다. 이것을 프론트에게 잘 전달하기만 하면 됩니다.
user_id으로 조회해서 특정 유저의 게시물만 표출하는 로직을 구현하기 위해서 PostingSearchView
클래스를 새로 만들었습니다. 세부 정보를 검색하는 기능이라 따로 구분하였습니다.
class PostingSearchView(View):
def get(self, request, user_id):
if not User.objects.filter(id=user_id).exists():
return JsonResponse({'message':'USER_DOES_NOT_EXIST'}, status=404)
posting_list = [{
"username" : User.objects.get(id=user_id).username,
"content" : posting.content,
"image_url" : [i.image_url for i in Image.objects.filter(posting_id=posting.id)],
"create_at" : posting.created_at
} for posting in Posting.objects.filter(user_id=user_id)
]
return JsonResponse({'data':posting_list}, status=200)
url을 통해 user_id
를 받습니다.
만약 user_id
에 해당하는 유저가 없을 경우에는 'USER_DOES_NOT_EXIST'
라는 메시지와 404 에러코드를 응답합니다.
user_id
로 Posting.objects.filter(user_id=user_id)
를 하여 해당 유저의 정보만 가져옵니다.
그 외 리스트 컴프리헨션을 활용하는 방식은 모든 게시물을 가져오는 방식과 동일하니 생략하겠습니다.
# posting/views.py
import json
from json.decoder import JSONDecodeError
from django.http import JsonResponse
from django.views import View
from posting.models import Posting, Image
from user.models import User
class PostingView(View):
def post(self, request):
try:
data = json.loads(request.body)
user = User.objects.get(username=data.get('user', None))
content = data.get('content', None)
image_url_list = data.get('image_url', None)
if not (user and image_url_list):
return JsonResponse({'message':'KEY_ERROR'}, status=400)
posting = Posting.objects.create(
user = user,
content = content
)
for image_url in image_url_list:
Image.objects.create(
image_url = image_url,
posting = posting
)
return JsonResponse({'message':'SUCCESS'}, status=200)
except JSONDecodeError:
return JsonResponse({'message': 'JSON_DECODE_ERROR'}, status=400)
def get(self, request):
posting_list = [{
"username" : User.objects.get(id=posting.user.id).username,
"content" : posting.content,
"image_url" : [i.image_url for i in Image.objects.filter(posting_id=posting.id)],
"create_at" : posting.created_at
} for posting in Posting.objects.all()
]
return JsonResponse({'data':posting_list}, status=200)
class PostingSearchView(View):
def get(self, request, user_id):
if not User.objects.filter(id=user_id).exists():
return JsonResponse({'message':'USER_DOES_NOT_EXIST'}, status=404)
posting_list = [{
"username" : User.objects.get(id=user_id).username,
"content" : posting.content,
"image_url" : [i.image_url for i in Image.objects.filter(posting_id=posting.id)],
"create_at" : posting.created_at
} for posting in Posting.objects.filter(user_id=user_id)
]
return JsonResponse({'data':posting_list}, status=200)
# westagram/urls.py
from django.urls import path, include
urlpatterns = [
path('user', include('user.urls')),
path('posting', include('posting.urls')) --> 추가
]
# posting/urls.py
from django.urls import path
from .views import (
PostingView,
PostingSearchView,
)
urlpatterns = [
path('', PostingView.as_view()),
path('/search/<int:user_id>', PostingSearchView.as_view()),
]
다음 시간에는 인스타그램에 권한이 있는 유저만이 게시물 등록할 수 있도록 인가 과정을 구현하도록 하겠습니다.