TIL 최종프로젝트(22) 최종 발표 준비-코드소개
2023.07.06 목요일
피드백도 어느정도 잔잔해지고 수정도 어느정도 잔잔해졌으니 이제 최종 발표 준비를 해야 합니다.
오늘은 제출해야 할 것 중 코드 소개 부분은 작성했습니다.
최근에는 프론트 부분을 중점적으로 다뤘지만, 백엔드 과정인만큼 코드소개 부분은 백엔드 코드만 작성하였습니다.
1.백엔드 코드
최종 프로젝트에서 event 부분을 담당하였습니다.
[행사]
[views.py]
행사 CRUD를 위한 views.py의 코드입니다.
class EventView(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly, CustomPermission]
def get(self, request):
event = Event.objects.all().order_by("-created_at")
serializer = EventListSerializer(event, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, *args, **kwargs):
serializer = EventCreateSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
return Response({"message": "작성완료"})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class EventDetailView(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly, CustomPermission]
def get(self, request, event_id):
event = get_object_or_404(Event, id=event_id)
serializer = EventSerializer(event)
return Response(serializer.data, status=status.HTTP_200_OK)
def put(self, request, event_id):
event = get_object_or_404(Event, id=event_id)
self.check_object_permissions(self.request, event)
serializer = EventEditSerializer(event, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response({"message": "수정완료"}, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, event_id):
event = get_object_or_404(Event, id=event_id)
self.check_object_permissions(self.request, event)
event.delete()
return Response({"message": "삭제완료"}, status=status.HTTP_200_OK)
처음 이 코드를 구현할 때에는 generic의 ListCreateAPIView를 사용하여 EventView를 구현하였지만, 이후 프론트와 연결하는 과정에서 약간의 문제가 발생하여 APIView로 변경하였습니다.
permission을 설정하여 프로젝트의 취지에 맞게 코드를 구현했습니다.
class CustomPermission(permissions.BasePermission):
"""
읽기 권한은 비로그인, 로그인 일반 회원 모두에게 주어집니다.
생성, 수정, 삭제 권한은 오직 admin에게만 주어집니다.
권한이 없을 경우 "권한이 없습니다" 메시지와 함께 상태메시지 403 에러를 발생시킵니다.
"""
message = "권한이 없습니다"
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
else:
return request.user.is_admin
제공해주는 permission을 오버라이딩하여 원하는 동작을 하게 커스텀하여 사용했습니다.
[serializers.py]
class EventCreateSerializer(serializers.ModelSerializer):
"""
공연정보를 작성하기 위해 사용합니다.
title (varchar),
content (text),
image (image),
event_start_date (date),
event_end_date (date),
time_slots (JSON),
max_booking (Positiveint),
money (int),
값이 필요합니다.
"""
class Meta:
model = Event
fields = (
"title",
"content",
"image",
"event_start_date",
"event_end_date",
"time_slots",
"max_booking",
"money",
)
class EventSerializer(serializers.ModelSerializer):
created_at = serializers.DateTimeField(format="%m월%d일 %H:%M", read_only=True)
updated_at = serializers.DateTimeField(format="%m월%d일 %H:%M", read_only=True)
event_start_date = serializers.DateTimeField(format="%Y-%m-%d", read_only=True)
event_end_date = serializers.DateTimeField(format="%Y-%m-%d", read_only=True)
review_count = serializers.SerializerMethodField()
likes_count = serializers.SerializerMethodField()
author = serializers.SerializerMethodField()
def get_author(self, obj):
author = obj.author.email.split("@")[0]
return author
def get_review_count(self, obj):
return obj.review_set.count()
def get_likes_count(self, obj):
return obj.likes.count()
class Meta:
model = Event
fields = (
"id",
"author",
"title",
"content",
"image",
"created_at",
"updated_at",
"event_start_date",
"event_end_date",
"time_slots",
"max_booking",
"money",
"likes",
"review_count",
"likes_count",
"event_bookmarks",
)
class EventListSerializer(EventSerializer):
class Meta:
model = Event
fields = (
"id",
"title",
"image",
"event_start_date",
"event_end_date",
"review_count",
"likes",
"likes_count",
"event_bookmarks",
)
class EventEditSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = "__all__"
def validate(self, attrs):
event_start_date = attrs.get("event_start_date")
event_end_date = attrs.get("event_end_date")
if event_start_date and event_end_date and event_end_date <= event_start_date:
raise serializers.ValidationError(
"event_end_date must be greater than event_start_date"
)
return attrs
행사 영역에서 사용하는 serializer는 행사 생성, 수정, 행사 상세, 행사 리스트 4가지로 나뉩니다.
프론트와 연결하기 전 행사 상세와 행사 리스트의 차별점은 리스트에서는 간단한 정보를 상세에서는 거의 모든 정보를 제공하는 것이 목표였습니다.
하지만, 프론트와 연결하여 필요한 데이터를 받아 구현하는 과정에서 행사 리스트 부분도 기존보다 많은 데이터를 사용하기 위해 더 많은 필드를 받아와야 했습니다.
행사 영역의 serializer 부분에서 조금 특이점으로 뽑을만한 것은 EventSerializer를 EventListSerializer 상속한다는 점입니다.
이전 팀 프로젝트에서 사용된 방식으로 어차피 같은 형태로 데이터를 사용한다면 재정의 없이 상속받아 사용하는 것이 라인수를 줄이고 관리하기 쉽기 때문에 이번 프로젝트에서 적용하여 사용하였습니다.
[models.py]
class Event(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
content = models.TextField()
image = models.ImageField(blank=True, upload_to="%Y/%m/")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
event_start_date = models.DateTimeField()
event_end_date = models.DateTimeField()
time_slots = models.JSONField()
max_booking = models.PositiveIntegerField(validators=[MinValueValidator(1)])
money = models.IntegerField()
likes = models.ManyToManyField(User, related_name="like_event", blank=True)
event_bookmarks = models.ManyToManyField(
User, related_name="bookmark_events", blank=True
)
Event 모델입니다.
날짜의 경우 시작일~종료일 형식으로 어느정도 고정된 형태로 표현이 가능했습니다.
하지만, 행사 시간의 경우 이를 어떻게 표현할까 많은 고민이 있었습니다.
가장 간단한 방법은 정시별로 구분하여 초이스 필드를 사용하는 것이었지만, 시간의 경우 분단위까지 고려해야 했습니다, 또한 행사 시간은 1회차: 11:30~ 12:30, 2회차: 14:30~ 15:30과 같이 회차별로 또한번 묶여야 했기에 더 많은 고민이 있었고 가장 알맞다 생각한 JSON필드를 이용하여 time_slots라는 필드명으로 이를 구현하게 되었습니다.
max_booking 필드는 PositiveIntegerField를 사용하였습니다.
예매 가능 수를 나타내기 때문에 1이상의 값만 들어갈 수 있게 검증 속성을 주어 관리 할 수 있게 구현했습니다.
프론트 부분을 구현하면서 느꼈던 Event 모델의 아쉬웠던 점은 2가지 정도입니다.
1.image 필드에 blank=True를 허용했다는 점
⇒서비스된 페이지에서는 행사를 보여줄 때, 행사를 생성할 때 입력한 이미지를 뒷배경에 깔아주었습니다, 그렇기 때문에 행사 이미지를 필수적으로 들어가야하는 부분이다 생각합니다.
물론 admin이 admin 페이지에서 행사를 만들기에 큰 문제는 없었지만, 실제 사용과 맞지 않는다 생각합니다.
2.category, tag 필드를 구현하지 않았다는 점
⇒배포 이후 피드백으로 category, tag 기능의 추가를 많이 받았습니다, category는 js에서 title을 통해 비슷한 기능을 하게 구현하였지만, tag는 끝내 추가하지 못했습니다. Event모델에 필드로 추가하여 간단히 구현이 가능했을 것이지만 결국 적용시키지 못한 부분에서 아쉬움이 남습니다.
[개인 피드백]
행사 부분은 제가 맡은 부분의 알파이자 오메가 이기에 상당히 많은 신경을 썼고 이후 모든 행사영역의 코드의 기반이 되었습니다. 하지만 프론트 영역에서는 GET을 통해 데이터를 보내 줄 뿐 POST, PUT, DELETE는 따로 프론트 영역에서 선보이지 못해 아쉬운 부분이 되었습니다.
물론 django 자체 admin 페이지에서 비슷한 역할을 제공해 주어 따로 구현하지 않아도 문제점은 없으며, 어떻게 보면 django를 사용한 취지에 가장 알맞은 방식이기도 하지만, 내보여줄 영역이 적다는 점에서 여전히 아쉬움이 아주 약간 남습니다.
[행사 리뷰]
[views.py]
class EventReviewView(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
serializer_class = EventReviewSerializer
def get(self, request, event_id):
event = get_object_or_404(Event, id=event_id)
review = EventReview.objects.filter(event=event).order_by("-created_at")
serializer = EventReviewSerializer(review, many=True)
return Response(
serializer.data,
status=status.HTTP_200_OK,
)
def post(self, request, *args, **kwargs):
serializer = EventReviewCreateSerializer(
data=request.data, context={"request": request}
)
event = get_object_or_404(Event, id=kwargs.get("event_id"))
if serializer.is_valid():
serializer.save(author=request.user, event=event)
return Response({"message": "작성완료"}, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class EventReviewDetailView(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
def put(self, request, **kwargs):
review = get_object_or_404(EventReview, id=kwargs.get("eventreview_id"))
serializer = EventReviewCreateSerializer(review, data=request.data)
self.check_object_permissions(self.request, review)
if serializer.is_valid():
serializer.save()
return Response({"message": "수정완료"}, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, **kwargs):
review = get_object_or_404(EventReview, id=kwargs.get("eventreview_id"))
self.check_object_permissions(self.request, review)
review.delete()
return Response({"message": "삭제완료"}, status=status.HTTP_204_NO_CONTENT)
크게 특별할 것 없이 구현했습니다.
permission을 사용하여, 인증된 사용자 즉, 로그인한 사용자의 경우 POST, PUT, DELETE가 가능하고 그렇지 않을 경우 읽기 권한만 부여해줍니다.
또한 수정, 삭제의 경우 오버라이딩한 permisson을 사용하여 요청자가 작성자와 같은지 확인하여 권한을 주게 만들었습니다.
class IsOwnerOrReadOnly(permissions.BasePermission):
message = "권한이 없습니다"
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
[serializers.py]
class EventReviewSerializer(serializers.ModelSerializer):
created_at = serializers.DateTimeField(format="%m월%d일 %H:%M", read_only=True)
updated_at = serializers.DateTimeField(format="%m월%d일 %H:%M", read_only=True)
author_name = serializers.SerializerMethodField()
def get_author_name(self, obj):
# author_name = obj.author.username
return obj.author.username
class Meta:
model = EventReview
fields = (
"id",
"author",
"author_name",
"event",
"content",
"review_image",
"created_at",
"updated_at",
"grade",
)
class EventReviewCreateSerializer(serializers.ModelSerializer):
class Meta:
model = EventReview
fields = (
"content",
"grade",
"review_image",
)
마찬가지로 크게 특별한 부분없이 구현하였습니다, FK로 연결된 User모델에서 사용자의 정보를 가져와 사용자의 이름을 사용할 수 있게 만들었습니다.
[models.py]
class EventReview(models.Model):
RATING_CHOICES = [
(1, "1점"),
(2, "2점"),
(3, "3점"),
(4, "4점"),
(5, "5점"),
]
author = models.ForeignKey(User, on_delete=models.CASCADE)
event = models.ForeignKey(
Event, on_delete=models.CASCADE, related_name="review_set"
)
content = models.TextField()
review_image = models.ImageField(blank=True, upload_to="%Y/%m/")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
grade = models.IntegerField(choices=RATING_CHOICES)
크게 특별한 부분은 없습니다, grade필드에서 int 형태로 값을 입력하면 거기에 맞는 데이터가 저장된다는 부분이 아주 조금 특별한 부분입니다.
프론트 부분을 구현하면서 느꼈던 아쉬운 부분이 있습니다.
1.review_image필드를 blank=True로 설정했다는 점
⇒사용자가 사용한 페이지에서 댓글을 작성하기 위해서는 img가 필수로 들어가야 했습니다. 백엔드 부분에서 blank=True로 설정되었기에 js를 사용하여 이미지가 들어가지 않는 댓글의 등록을 제한시켜 주었습니다.
2.content필드의 max_length 제한이 없었다는 점
⇒행사와는 달리 다양한 사용자가 댓글을 작성할 수 있기에 통제를 위한 max_length 제한을 두어야 했습니다. js를 사용하여 이 부분을 제한시켜 주었습니다.
[개인피드백]
무난하게 구현된 기능이였습니다.
하지만 프론트와 연결하여 구현할 때, 백엔드에서 구현했어야 했던 부분이 많이 보였습니다.
좀 더 사용자 관점에서 생각하며 백엔드를 구현했다면 더 좋았을 거 같습니다.
[티켓생성]
[views.py]
class TicketView(generics.ListCreateAPIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly, CustomPermission]
serializer_class = TicketCreateSerializer
# 이벤트_id를 사용하여 해당 이벤트에 생성된 티켓을 조회합니다.
def get(self, request, event_id):
tickets = Ticket.objects.filter(event=event_id)
serializer = TicketSerializer(tickets, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def get_serializer_context(self):
context = super(TicketView, self).get_serializer_context()
context.update({"event": self.kwargs["event_id"]})
return context
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
event_id = self.kwargs.get("event_id")
try:
event = Event.objects.get(id=event_id)
except Event.DoesNotExist:
return Response(
{"message": "유효한 이벤트를 선택해 주세요"}, status=status.HTTP_400_BAD_REQUEST
)
serializer = self.get_serializer(
data=request.data, context={"event_id": event_id, "event": event}
)
if serializer.is_valid():
serializer.save(author=request.user, event=event)
return Response({"message": "작성완료"}, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class TicketDetailView(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly, CustomPermission]
def get(self, request, ticket_id):
ticket = get_object_or_404(Ticket, id=ticket_id)
serializer = TicketSerializer(ticket)
return Response(serializer.data, status=status.HTTP_200_OK)
def delete(self, request, ticket_id):
ticket = get_object_or_404(Ticket, id=ticket_id)
self.check_object_permissions(self.request, ticket)
ticket.delete()
return Response({"message": "삭제완료"}, status=status.HTTP_200_OK)
class TicketDateDetailView(APIView):
"""
공연id, 공연날짜를 이용하여, 해당 값에 맞는 티켓의 정보를 조회합니다.
로그인한 회원만 사용가능합니다.
event_date의 타입이 date 타입이기 때문에 url에서 사용하기 위해서 event_date의 타입은 str형으로 사용되어야 합니다.
"""
permission_classes = [permissions.IsAuthenticated]
def get(self, request, event_id, event_date):
ticket = Ticket.objects.filter(event=event_id, event_date=str(event_date))
# ).exclude(current_booking=F("max_booking_count"))
serializer = TicketSerializer(ticket, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class TicketTimeDetailView(APIView):
"""
공연id, 공연날짜, 공연 시간을 이용하여, 해당 값에 맞는 티켓의 정보를 조회합니다.
로그인한 회원만 사용가능합니다.
event_date의 타입이 date 타입이기 때문에 url에서 사용하기 위해서 event_date의 타입은 str형으로 사용되어야 합니다.
event_time의 타입이 varchar 타입이기 때문에 url에서 사용하기 위해서 event_time의 타입은 str형으로 사용되어야 합니다.
"""
permission_classes = [permissions.IsAuthenticated]
def get(self, request, event_id, event_date, event_time):
ticket = Ticket.objects.filter(
event=event_id, event_date=str(event_date), event_time=str(event_time)
)
serializer = TicketSerializer(ticket, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
생성된 행사에 티켓을 생성하고 조회하고 삭제하는 역할을 합니다.
전체적으로 행사와 많이 닮은 모습을 가지고 있습니다, 티켓의 경우에는 PUT 기능을 구현하지 않았습니다.
잘못 생성된 티켓은 수정하기 보다는 빠르게 삭제하고 다시 만드는 것이 더 좋은 것이라 생각했기 때문입니다.
대부분의 기능은 admin만 사용이 가능하기 때문에 행사와 마찬가지로 프론트에서 보여줄 부분이 없었습니다.
예매 기능에서 티켓을 사용하기 위해 TicketDateDetailView와 TicketTimeDetailView를 만들었습니다.
[serializers.py]
class TicketCreateSerializer(serializers.ModelSerializer):
event_date = serializers.DateField()
event_time = serializers.CharField(max_length=11)
booked_users = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
current_booking = serializers.IntegerField(read_only=True)
def validate(self, attrs):
event_id = self.context.get("event_id")
event_date = attrs.get("event_date")
event_time = attrs.get("event_time")
max_booking_count = attrs.get("max_booking_count")
try:
event = Event.objects.get(id=event_id)
except Event.DoesNotExist:
raise serializers.ValidationError("유효한 이벤트를 선택해 주세요")
if (
event.event_start_date.date() > event_date
and event_date < event.event_end_date.date()
):
raise serializers.ValidationError("공연 기간을 확인해 주세요")
time_slots = event.time_slots
if event_time not in time_slots.values():
raise serializers.ValidationError("공연 시간을 확인해 주세요")
max_bookig = event.max_booking
if max_booking_count != max_bookig:
raise serializers.ValidationError("최대 관객수를 확인해 주세요")
attrs["event"] = event
return attrs
class Meta:
model = Ticket
fields = "__all__"
read_only_fields = ("author", "event")
class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = "__all__"
결제가 발생하는 부분이기 때문에 티켓을 생성할 때에는 많은 검증이 이루어지도록 하였습니다.
하지만, 행사를 다루는 관리자 페이지가 따로 없이 admin 페이지에서 티켓이 만들어졌기 때문에 실제로 저 많은 검증이 작동하는 것은 postman을 통해서만 확인하였고 효과적으로 사용되지는 못했습니다.
[models.py]
class Ticket(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
event_date = models.DateField()
event_time = models.CharField(max_length=11)
max_booking_count = models.PositiveIntegerField(validators=[MinValueValidator(1)])
current_booking = models.PositiveIntegerField(default=0)
money = models.IntegerField()
quantity = models.IntegerField(default=0)
Ticket 모델은 행사 모델 다음으로 많은 수정이 있었습니다.
max_booking_count, current_booking, quantity 등 티켓의 수량을 관리하기 위한 필드는 구현 초기에는 없었지만, 실제 예매와 연결하는 과정에서 필요해져 추가되었습니다.
이후 소개할 TicketBooking 모델에서 예매한 수량을 바탕으로 수량이 조절되며 반대로 Ticket모델의 수량을 바탕으로 TicketBooking모델이 작동하는데 영향을 주어 서로 영향을 주게 구현되었습니다.
프론트를 구현하는 과정에서 저는 Ticket모델의 데이터를 받아와 사용하지는 않았습니다.
결제 부분을 맡으신 세희님이 이 부분을 이용하여 프론트를 구현하셨기 때문에 이 부분에서 만난 문제점은 없습니다.
[개인 피드백]
티켓 부분은 프로젝트 중 가장 많은 고민을 하게한 부분입니다.
티켓을 생성하는 것부터 어떻게 예매가 이루어지게 할 것인지까지 구현 전 구상 단계에서 많은 어려움을 느꼈습니다.
개인적으로 행사영역과 연계하여 티켓 생성 및 삭제의 자동화를 원했지만, 기술적인 부족함으로 수동 생성 및 삭제 방식이 되었고, db에 부담을 줄여주기 위해 예매 가능 날짜를 두어 7월 6일 행사라면 7월 3일 4일 5일까지 예매가 가능하게 구현하려고 했지만, 마찬가지로 구현하지 못했습니다.
기술적, 시간적 부족함에 의해 결국 구현하지 못해 어떻게 보면 수동 생성+db 부담이라는 최악의 방향으로 구현되었다 생각되어 매우 아쉬운 부분입니다.
[티켓예매]
[views.py]
class BookingTicketDetailView(APIView):
"""
예매한 티켓을 조회하기 위해 사용됩니다.
id는 예약항목의 id를 나타냅니다.
조회하는 회원의 예약항목의 id를 사용하여, 해당 id를 가진 티켓의 정보를 조회합니다.
"""
permission_classes = [permissions.IsAuthenticated]
def get(self, request, id):
try:
user = self.request.user
ticket_booking = TicketBooking.objects.filter(id=id, author=user).first()
if not ticket_booking:
return Response(
{"message": "예매한 티켓이 없습니다."}, status=status.HTTP_404_NOT_FOUND
)
serializer = BookedTicketSerializer(ticket_booking)
return Response(serializer.data)
except TicketBooking.DoesNotExist:
return Response(
{"message": "예매한 티켓이 없습니다."}, status=status.HTTP_404_NOT_FOUND
)
class BookingTicketView(APIView):
permission_classes = [permissions.IsAuthenticated]
def post(self, request, ticket_id):
"""
예매를 진행하기 위해 사용됩니다.
ticket_id를 받아 해당 티켓이 존재하는지 먼저 확인합니다
해당, 티켓이 존재하지 않는다면, "티켓이 존재하지 않습니다." 메시지와 404 상태메시지를 출력합니다
시리얼라이저를 통해 입력값이 정확한 형태로 들어왔는지 확인합니다
quantity의 값을 0 이하로 입력할 시 "올바른 수량(quantity)을 입력해주세요." 메시지와 400 상태메시지를 출력합니다
current_booking(현재 예매된 티켓의 수량)의 값이 quantity(예매하고자 하는 수량)을 더하여 max_booking_count(최대 수량)을 넘을 경우
"예매가 불가능합니다." 메시지와 400 상태메시지를 출력합니다
예매가 가능한 상황이라면, current_booking에 quantity 값을 더해주고, 더해준 값을 해당 티켓에 저장해주고, 예매 내역을 ticket_booking에 저장해주고
"예매가 완료되었습니다." 메시지와 201 상태메시지를 출력합니다.
"""
try:
ticket = Ticket.objects.get(id=ticket_id)
except Ticket.DoesNotExist:
return Response(
{"message": "티켓이 존재하지 않습니다."}, status=status.HTTP_404_NOT_FOUND
)
serializer = BookedTicketCountSerializer(data=request.data)
if serializer.is_valid():
quantity = request.data.get("quantity", 0)
if quantity is None or quantity <= 0:
return Response(
{"message": "올바른 수량(quantity)을 입력해주세요."},
status=status.HTTP_400_BAD_REQUEST,
)
if ticket.current_booking + quantity > ticket.max_booking_count:
return Response(
{"message": "예매가 불가능합니다."}, status=status.HTTP_400_BAD_REQUEST
)
ticket_booking = TicketBooking(
author=request.user,
ticket=ticket,
money=ticket.money,
quantity=quantity,
)
ticket.current_booking += quantity
ticket.save()
ticket_booking.save()
serializer = BookedTicketCountSerializer(ticket_booking)
return Response({"message": "예매가 완료되었습니다."}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class BookingTicketListView(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = BookedTicketSerializer
def get_queryset(self):
user = self.request.user
booked_tickets = TicketBooking.objects.filter(author=user)
return booked_tickets
티켓을 예매하기 위해 구현되었습니다.
프로젝트 초기에는 예매기능을 add와 remove를 이용하여 정말 간단하게 구현하였습니다.
제 자신이 결제와 연결을 어떻게 할지 생각하지 않고 기능적인 부분만 생각하며 만들었기에 결국 예매 부분을 전체적으로 결제에 맞춰 수정하게 되었습니다.
BookingTicketDetailView에서는 예매한 티켓을 확인할 수 있게 만들었습니다.
사용자가 예매한 티켓이 없거나 예매한 티켓을 찾을 수 없을 때, 예매한 티켓이 없습니다. 메시지를 출력합니다.
BookingTicketView에서는 티켓의 전체 수량과 현재 수량을 바탕으로 구입하려는 티켓과 수량을 결정하여 예매가 이루어지게 만들었습니다.
BookingTicketView에서 quantity 값을 주면 default가 0인 Ticket에 그 값을 주고 연산이 이루어질 수 있게 구현하였습니다.
해당 연산이 끝나면 Ticket의 quantity의 값은 다시 0이되고 다시 예매가 발생하였을 때 받은 quantity의 값을 받아 연산이 되게 하였습니다.
BookingTicketListView는 요청자의 id를 사용하여 해당 요청자가 예매한 티켓을 보기 위해 만들었습니다.
[serializers.py]
class BookedTicketCountSerializer(serializers.ModelSerializer):
"""
티켓 예약을 위해 만들어진 시리얼라이저 입니다
current_booking과 max_booking_count을 이용하여, 티켓의 예약 가능여부를 판단합니다
"""
author = serializers.SerializerMethodField()
event = serializers.SerializerMethodField()
current_booking = serializers.IntegerField(read_only=True)
max_booking_count = serializers.IntegerField(read_only=True)
money = serializers.SerializerMethodField(read_only=True)
quantity = serializers.SerializerMethodField()
def get_current_booking(self, ticket):
return ticket.current_booking
def get_max_booking_count(self, ticket):
return ticket.max_booking_count
def get_author(self, ticket_booking):
return ticket_booking.author.username
def get_event(self, ticket_booking):
return ticket_booking.ticket.event.title
def get_money(self, ticket_booking):
return ticket_booking.ticket.money
def get_quantity(self, ticket_booking):
return ticket_booking.quantity
class Meta:
model = TicketBooking
fields = (
"author",
"event",
"money",
"quantity",
"current_booking",
"max_booking_count",
)
검증 과정을 view 부분에서 하였기에 serializer에서는 결제에 필요한 데이터를 넘겨주는 역할에 중점을 두어 구현했습니다.
결제에 필요한 구매자, 가격, 수량의 정보 등의 데이터를 받아볼 수 있게 구현하였습니다.
[models.py]
class TicketBooking(models.Model):
"""
author(ForeignKey): 예약을 한 회원을 표현합니다.
ticket(ForeignKey): 예약 대상이 된 티켓의 id를 표현합니다
money(int): 티켓의 가격을 표현합니다.
quantity(int): 구입할 수량을 표현합니다
"""
author = models.ForeignKey(User, on_delete=models.CASCADE)
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
money = models.IntegerField()
quantity = models.IntegerField(default=0)
TicketBooking 자체가 결제를 위한 정보를 넘겨주기 위해 구현되었다! 보여주는 모델입니다
이 모델을 구상하기 전에는 결제 부분도 Ticket 모델에서 넘겨 줄 수 있게 만들려고 했지만, 결제 부분에 넘겨줘야 할 quantity가 잘 넘어가지 않는 문제가 있었습니다.
이유를 찾다가 티켓이 기준이 되었기에 구매자의 기준에서 몇 개를 구매하였는지에 대한 정보가 저장되지 않는 것이었습니다.
그 결과 구매자가 기준이 되어 필요한 데이터를 결제에 넘겨줄 수 있는 코드가 전반적으로 추가되었습니다.
티켓과 마찬가지로 예매부분은 결제를 맡으신 분이 프론트에 적용시켜주셨기에 프론트 부분에서 발생한 문제나 아쉬웠던 점을 느끼지 못했습니다.
[개인 피드백]
캠프 기간 중 협업이 가장 잘 이루어졌다 생각되는 부분입니다.
각자 맡았던 영역을 하나로 합치는 과정에서 많은 것을 배웠습니다.
하지만 여전히 부족한 부분도 있습니다, 처음 구상단계에서 서로 좀 더 타이트하게 계획을 했더라면, 행사 영역에서 예매를 구현할 때, 결제 영역과 상의를 했더라면 시간을 들여 코드를 수정하는 일이 없었을 것이라 생각됩니다.
행사를 맡고 계획할 때 이렇게 구현할 것이다 정해놓고 한 것이 아닌 일단 아는 거 부터 빨리 만들고 생각해보자는 생각으로 프로젝트를 시작했기에 이러한 문제가 발생한 거 같습니다.
팀원간의 소통과 계획단계에서부터 사용할 기술에 대한 점검을 하여 이러한 문제점을 사전에 방지해야한다 느꼈습니다.
[느낀점]
막바지구나 느껴집니다, 이미 저의 생각은 개인피드백에서 충분히 보여줬다 생각하여 생략하겠습니다.
작성한 글 중 가장 긴 글이 아닐까 생각됩니다!