오늘은 기존에 있던 장고 프로젝트인 drf_project와 user, blog 앱을 만들었던 폴더 내에 product 앱을 만들고 제품 관련 등록 및 수정과 등록된 해당 제품을 볼 수 있도록 하는 기능을 연습해 보았다.
우선 다음 명령어를 통해 앱을 만들어주었다.
그리고 생성된 product 앱 안에 urls.py와 serializers.py를 만들어주었다.
django-admin startapp product

우선 앱이 맨 처음 만들어지면 settings.py의 INSTALLED_APPS 안에 생성된 앱을 추가해줘야하기 때문에 product 앱을 적어주었다.
# drf_project/settings.py
~
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'user',
'blog',
'product',
]
~
이후 product/models.py에서 테이블 필드를 위한 값들을 만들어 주었다.
# product/models.py
from django.db import models
from django.utils import timezone
# Create your models here.
# 제품 모델
class Product(models.Model):
# 작성자 / 제목 / 썸네일 / 설명 / 등록일자 / 노출시작일 / 노출종료일
user = models.ForeignKey('user.User', verbose_name="사용자", on_delete=models.CASCADE)
title = models.CharField("제목", max_length=50)
# upload_to='img/product_img/%Y%m%d'
thumbnail = models.ImageField("썸네일", upload_to='product/img/%Y%m%d', width_field=None, height_field=None, max_length=100)
description = models.TextField("설명")
created_at = models.DateTimeField("등록일자", auto_now_add=True)
show_date_start = models.DateTimeField("노출 시작 일자", default=timezone.now)
show_date_end = models.DateTimeField("노출 종료 일자", default=timezone.now)
def __str__(self):
return f'{self.user.username}님이 등록한 {self.title}입니다.'
작성자, 제목, 썸네일, 설명, 등록일자, 노출시작일, 노출종료일에 관련된 필드들을 설정해주고, 작성자는 로그인된 사용자가 들어갈 수 있도록 ForeignKey로 관계를 지정해주었다.
이번에는 ImageField를 이용하여 이미지를 저장할 것이기 때문에 이미지 처리를 위한 모듈인 Pillow를 설치해주었다.
다음 명령어로 Pillow 모듈을 설치하고 requirements.txt 파일 내에 해당 모듈에 대한 정보를 추가해주었다.
모듈 설치
pip install pillow
모듈 정보 저장 (requirements.txt)
pip freeze > requirements.txt
product/views.py에 해당 제품에 대한 조회(GET), 등록(POST), 수정(PUT)을 위한 View 클래스를 ProductView 라는 이름으로 만들어 준 뒤, urls.py에 해당 URL 설정을 해주었다.
# drf_project/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('user.urls')),
path('blog/', include('blog.urls')),
path('product/', include('product.urls')),
]
# product/views.py
from rest_framework.response import Response
from rest_framework.views import APIView
# Create your views here.
class ProductView(APIView):
# 제품 조회
def get(self, request):
return Response({})
# 제품 등록
def post(self, request):
return Response({})
# 제품 수정
def put(self, request):
return Response({})
# product/urls.py
from django.urls import path
from product import views
urlpatterns = [
# product/
path('', views.ProductView.as_view()),
]
그리고 product/serializers.py 안에 ProductSerializer 설정을 해주었다.
# product/serializers.py
from rest_framework import serializers
from product.models import Product as ProductModel
# 제품
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = ProductModel
fields = ["user", "title", "thumbnail", "description",
"created_at", "show_date_start", "show_date_end"]
Product 모델에 대한 정보를 serializer 안에 넣어주고 해당 모델 안에 존재하는 필드들을 넣어주었다.
여기서 새로운 기능을 사용해보고자 하는데, 사용하고자 하는 기능에는 data validation, create, update 3가지를 사용해보고자 한다.
이 기능들은 따로 custom을 해주지 않아도 serializer에서 기본적으로 제공을 해주는 기능이기 때문에 따로 설정이 없어도 사용이 가능하다.
만약 해당 기능들에 대해 custom을 하여 사용하려면 serializer 내에 다음과 같이 사용해주면 된다.
# product/serializers.py
from rest_framework import serializers
from product.models import Product as ProductModel
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = ProductModel
fields = ["user", "title", "thumbnail", "description", "created_at", "show_date_start", "show_date_end"]
# 검증(validate)
def validate(self, attrs):
return super().validate(attrs)
# 생성(POST)
def create(self, validated_data):
return super().create(validated_data)
# 수정(PUT)
def update(self, instance, validated_data):
return super().update(instance, validated_data)
해당 기능들은 def validate/create/update를 만들 때 기본적으로 같이 생성되는 것들이다.
여기서 직접 custom을 해주려면 함수 내에 자신이 필요한 설정을 해주면 된다.
그렇게 custom을 해주면 기본 기능들을 사용하지 않고 커스텀된 기능들을 사용하는 것이 아니라 다음과 같이 실행이 된다.
→ 기존 validate → 커스텀 validate → 기존 or 커스텀 create 또는 기존 or 커스텀 update →
기존에 있던 validate를 거친 뒤 커스텀된 validate도 거치고 실행하고자 하는 create 또는 update가 실행되게 된다.
하지만 이번에는 직접 커스텀하여 사용하는 것이 아닌 제공해주는 기본 기능들을 가지고 코드를 구현해보았다.
기능들에 대한 설명을 알아봤다.
설정들은 거의 다 해준 상태이고 제품을 등록하기 위한 post 및 put 설정을 해주고자 한다.
이번에는 serializer 심화 과정으로 validate(검증)를 사용해볼 것이기 때문에 저번과 다르게 post 설정을 해주었다.
# product/views.py
from django.db.models.query_utils import Q
from django.utils import timezone
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from product.models import Product as ProductModel
from product.serializers import ProductSerializer
# Create your views here.
class ProductView(APIView):
# 제품 조회
def get(self, request):
return Response({})
# 제품 등록
def post(self, request):
request.data["user"] = request.user.id
product_serializer = ProductSerializer(data=request.data)
# validate 검증, 검증 시 저장하고 Response 반환
if product_serializer.is_valid():
product_serializer.save()
return Response(product_serializer.data, status=status.HTTP_200_OK)
# return Response({"success": "제품 등록 완료"}, status=status.HTTP_200_OK)
# 검증 실패 시 .errors를 통해 어디가 실패했는지 알려준다.
return Response(product_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 제품 수정
def put(self, request, product_id):
# product_id = 해당 product의 pk값
product = ProductModel.objects.get(id=product_id)
# 전체 수정이 아닌 부분적으로 수정하고 싶다면 partial=True 사용
product_serializer = ProductSerializer(product, data=request.data, partial=True)
# validate 검증, 검증 시 저장하고 Response 반환
if product_serializer.is_valid():
product_serializer.save()
return Response(product_serializer.data, status=status.HTTP_200_OK)
# return Response({"success": "제품 수정 완료"}, status=status.HTTP_200_OK)
# 검증 실패 시 .errors를 통해 어디가 실패했는지 알려준다.
return Response(product_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
if product_serializer.is_valid():를 통해 해당 ProductSerializer에 대한 검증이 되면 해당 데이터를 저장하고 저장되거나 수정된 값을 Response로 반환해주었다.
그리고 검증이 실패회면 product_serializer.errors를 통해 어디에서 에러가 발생했는지 Response 메시지를 반환해주도록 했다.
여기서 해당 코드를 살펴보면 post와 put 부분에서 검증 시 둘 다 serializer.save()를 하도록 되어있다.
어떤 것이 create인지 update인지 serializer 내에서 구분할 수 없는데, 이때 put 부분에 product = ProductModel.objects.get(id=product_id)를 사용하여 해당 object의 정보가 수정됨을 넣어주면 구분이 가능하게 된다.
쉽게 말하면 지정해준 object가 없으면 생성(create)하는 것이고, 지정해준 object가 있으면 수정(update)를 실행하는 것이다.
그리고 put 부분에서 partial=True 설정을 해줌으로써 해당 object의 전체가 수정되어야 하는 것이 아닌 일부만 수정되어도 수정된 정보가 저장되도록 설정을 해주었다.
마지막으로 product/urls.py에 자신이 수정하고 싶은 object들의 id를 구분지어 URL 설정을 해주었다.
# product/urls.py
from django.urls import path
from product import views
urlpatterns = [
# product/
path('', views.ProductView.as_view()),
path('<product_id>/', views.ProductView.as_view()),
]
Postman에서 해당 API 보내기 전에 admin 페이지에서도 확인을 할 수 있도록 product/admin.py 안에 ProductAdmin을 만들어서 직접 커스텀 해주었다.
# product/admin.py
from django.contrib import admin
from django.utils.html import mark_safe
from product.models import Product
class ProductAdmin(admin.ModelAdmin):
list_display = ('id', 'user', 'title', 'image_icon', 'description')
list_display_links = ('title', 'user', )
list_filter = ('user', )
search_fields = ('title', 'user', )
fieldsets = (
("info", {'fields': ('title', 'description', 'created_at',)}),
('show_date', {'fields': ('show_date_start', 'show_date_end', )}),
('thumbnail', {'fields': ('thumbnail', 'image_tag', )}),
)
def get_readonly_fields(self, request, obj=None):
return ('created_at', 'image_tag', )
# 상세 페이지 내 이미지
def image_tag(self, obj):
if obj.thumbnail:
return mark_safe(f'<img src="{obj.thumbnail.url}" width="300" height="300"/>')
return None
# 이미지 미리보기
def image_icon(self, obj):
if obj.thumbnail:
return mark_safe(f'<img src="{obj.thumbnail.url}" width="100" height="100"/>')
return None
# 이미지 태그 글자 수
image_tag.short_description = "Image"
image_tag.allow_tags = True
# Register your models here.
admin.site.register(Product, ProductAdmin)
관리자 페이지에서 확인할 때 등록한 이미지(thumbnail)도 확인하기 위해서 image_tag와 image_icon 함수를 추가해서 이미지에 대한 http 설정을 해주었다.
그리고 이미지 설정을 위해 settings.py에 다음 내용을 추가했다.
# drf_project/settings.py
import os
~
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
이미지에 대한 설정은 다음 블로그를 참고하였다.
https://velog.io/@duo22088/Django-Media-file-%EB%8B%A4%EB%A3%A8%EA%B8%B0
(Django) Media file 다루기
장고에서 어떻게 static 파일을 서빙하는지 알아봅시다.
velog.io
이제 Postman을 통해서 해당 API가 잘 작동하면서 관리자 페이지에서 확인이 가능한지 실행해보았다.
우선 POST 요청이다.


정상적으로 잘 등록되는 것을 볼 수 있다.
다음으로는 PUT 요청이다.


post와 put 요청에 따른 API가 잘 작동되는 것을 볼 수 있었다.
그런데 여기서 이미지 파일도 정상적으로 들어가지만 설정 오류 때문인가 관리자 페이지에서 볼 때 이미지가 깨져서 보여지는 것을 확인할 수 있었다.
설정을 할 때 어딘가 잘못 설정한 부분이 있는 것 같다.
추후에 해당 이미지를 정상적으로 출력할 수 있도록 다시 한 번 참고해야겠다.
이제 등록한 제품을 get 요청을 통해 볼 수 있도록 ProductView 내에 제품 조회에 관한 내용을 추가해주었다.
# product/views.py
from django.db.models.query_utils import Q
from django.utils import timezone
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from product.models import Product as ProductModel
from product.serializers import ProductSerializer
class ProductView(APIView):
# 제품 조회
def get(self, request):
user = request.user # 로그인된 사용자
if not user.is_authenticated:
return Response({"message": "로그인이 필요합니다."}, status=status.HTTP_401_UNAUTHORIZED)
show_date_now = timezone.now()
terms = Q(show_date_start__lte=show_date_now) & Q(show_date_end__gte=show_date_now)
products = ProductModel.objects.filter(terms).order_by("show_date_start")
return Response(ProductSerializer(products, many=True).data, status=status.HTTP_200_OK)
저번 포스팅에서 사용했던 노출 시작 일자 / 노출 종료 일자를 추가하여 Q를 사용해 해당 조건을 넣어주고 조회를 해보았다.

노출 시작 일자와 노출 종료 일자 내에 등록된 제품들에 대한 정보가 표시되는 것을 볼 수 있었다.
-
오늘은 새로운 앱인 product를 만들어서 serializer validate를 활영하여 내용을 등록하고 수정하고 조회하는 기능을 만들어 보았다.
장고에서 제공하는 validate 라는 것을 활용해보고 코드를 구현해보면서 공부하는 계기가 되었다.
차츰 이해해가는 과정에 있고, 기본 제공하는 것이 아닌 custom으로 따로 지정을 해주면서 연습해보는 것도 공부하는데 좋을 것 같다는 생각이 들었다.
:P
'TIL 및 WIL > TIL (Today I Learned)' 카테고리의 다른 글
| [TIL] 2022.06.28 (DRF 유화 제작 프로젝트, My Little Shoes 1) (0) | 2022.06.28 |
|---|---|
| [TIL] 2022.06.23 (Django 심화, DRF 6) (0) | 2022.06.23 |
| [TIL] 2022.06.21 (Django 심화, DRF 4) (0) | 2022.06.21 |
| [TIL] 2022.06.20 (Django 심화, DRF 3) (0) | 2022.06.21 |
| [TIL] 2022.06.16 (Django 심화, DRF 2) (0) | 2022.06.16 |