장고 심화과제 끄읕...(모두 고생 많으셨습니다!)

장고 심화과제 끄읕...(모두 고생 많으셨습니다!)

·

4 min read

필수 기능 - MVP(Minimum Viable Product)

  • 회원가입

    • Endpoint: /api/accounts

    • Method: POST

    • 조건: username, 비밀번호, 이메일, 이름, 닉네임, 생일 필수 입력하며 성별, 자기소개 생략 가능

    • 검증: username과 이메일은 유일해야 하며, 이메일 중복 검증(선택 기능).

    • 구현: 데이터 검증 후 저장.

  • 로그인

    • Endpoint: /api/accounts/login

    • Method: POST

    • 조건: 사용자명과 비밀번호 입력 필요.

    • 검증: 사용자명과 비밀번호가 데이터베이스의 기록과 일치해야 함.

    • 구현: 성공적인 로그인 시 토큰을 발급하고, 실패 시 적절한 에러 메시지를 반환.

  • 프로필 조회

    • Endpoint: /api/accounts/<str:username>

    • Method: GET

    • 조건: 로그인 상태 필요.

    • 검증: 로그인 한 사용자만 프로필 조회 가능

    • 구현: 로그인한 사용자의 정보를 JSON 형태로 반환.

views.py

from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from .models import CustomUser
from .serializers import UserSerializer

class AccountsView(APIView):
    def post(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            username = serializer.validated_data['username']
            email = serializer.validated_data['email']

            # username과 email 중복 체크
            if User.objects.filter(username=username).exists():
                return Response({"error": "이미 사용 중인 이름인데...."}, status=status.HTTP_400_BAD_REQUEST)
            if User.objects.filter(email=email).exists():
                return Response({"error": "이미 사용 중인 이메일인데...."}, status=status.HTTP_400_BAD_REQUEST)

            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class signupView(APIView):
    permission_classes = [AllowAny] #접근 권한

    def post(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class LoginView(APIView):
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = authenticate(request, username=username, password=password)
        if user is not None:
            login(request, user)


            refresh_token = RefreshToken.for_user(user)
            data = {
                'user_id': user.id,
                'access_token': str(refresh_token.access_token),
                'refresh_token': str(refresh_token)
            }

            return Response(data, status=status.HTTP_200_OK)
        else:
            return Response({"error": "아이디 및 비밀번호가 이상한데요?"}, status=status.HTTP_400_BAD_REQUEST)

class LogoutView(APIView):
    def post(self, request):
        token = RefreshToken(request.data.get('refresh'))
        token.blacklist()
        logout(request)
        return Response({"message": "로그아웃 인가?"}, status=status.HTTP_200_OK)

class ProfileUpdateView(APIView):
    permission_classes = [IsAuthenticated]

    def put(self, request, username):
        user = get_object_or_404(CustomUser, username=username)

        # 인증된 사용자가 프로필의 소유자인지 확인
        if request.user != user:
            return Response({"error": "권한 읍서요"}, status=status.HTTP_403_FORBIDDEN)

        serializer = UserSerializer(user, data=request.data, partial=True)
        if serializer.is_valid():
            # 이메일 중복 확인
            new_email = serializer.validated_data.get('email')
            if new_email and CustomUser.objects.exclude(pk=user.pk).filter(email=new_email).exists():
                return Response({"error": "이미 존재한 이메일이에요."}, status=status.HTTP_400_BAD_REQUEST)

            # 사용자명(ID)
            new_username = serializer.validated_data.get('username')
            if new_username and CustomUser.objects.exclude(pk=user.pk).filter(username=new_username).exists():
                return Response({"error": "이미 존재한 아이디입니당."}, status=status.HTTP_400_BAD_REQUEST)

            # 이름
            new_name = serializer.validated_data.get('name')
            if new_name and CustomUser.objects.exclude(pk=user.pk).filter(name=new_name).exists():
                return Response({"error": "이미 존재한 이름입니당."}, status=status.HTTP_400_BAD_REQUEST)

            # 닉네임
            new_nickname = serializer.validated_data.get('nickname')
            if new_nickname and CustomUser.objects.exclude(pk=user.pk).filter(name=new_nickname).exists():
                return Response({"error": "이미 존재한 닉네임입니당."}, status=status.HTTP_400_BAD_REQUEST)

            # 생일
            new_birthday = serializer.validated_data.get('birthday')
            if new_birthday and CustomUser.objects.exclude(pk=user.pk).filter(birthday=new_birthday).exists():
                return Response(serializer.data, status=status.HTTP_200_OK)


            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class ProfileView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, username):
        user = get_object_or_404(CustomUser, username=username)
        serializer = UserSerializer(user)
        return Response(serializer.data, status=status.HTTP_200_OK)
urls.py
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from .views import AccountsView, signupView, LoginView, LogoutView, ProfileView, ProfileUpdateView

app_name = "accounts"

urlpatterns = [
    path('', AccountsView.as_view(), name='user_list'),
    path('signup/', signupView.as_view(), name='user_signup'),
    path('login/',LoginView.as_view(), name='user_login'),
    path('logout/', LogoutView.as_view(), name='user_logout'),
    path('<str:username>/', ProfileView.as_view(), name='user_profile'),
    path('<str:username>/update/', ProfileUpdateView.as_view(), name='user_profile_update'),
    path("admin/signup/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
    path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
]
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    email = models.EmailField(unique=True)
    name = models.CharField(max_length=100)
    nickname = models.CharField(max_length=100)
    birthday = models.DateField(null=True, blank=True)
    gender = models.CharField(max_length=10, null=True, blank=True)

    REQUIRED_FIELDS = ['name', 'nickname', 'birthday']
serializers.py
from rest_framework import serializers
from .models import CustomUser

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomUser
        fields = ['username', 'password', 'email', 'name', 'nickname', 'birthday', 'gender']
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = CustomUser.objects.create_user(
            username=validated_data['username'],
            email=validated_data['email'],
            name=validated_data['name'],
            nickname=validated_data['nickname'],
            birthday=validated_data['birthday'],
            gender=validated_data['gender'],
            password=validated_data['password']
        )
        return user

상품 관련 기능 및 조건

  • 상품 등록

  • 상품 목록 조회

  • 상품 수정

  • 상품 삭제

필수 기능 - MVP(Minimum Viable Product)

  • 상품 등록

    • Endpoint: /api/products

    • Method: POST

    • 조건: 로그인 상태, 제목과 내용, 상품 이미지 입력 필요.

    • 구현: 새 게시글 생성 및 데이터베이스 저장.

  • 상품 목록 조회

    • Endpoint: /api/products

    • Method: GET

    • 조건: 로그인 상태 불필요.

    • 구현: 모든 상품 목록 페이지네이션으로 반환.

  • 상품 수정

    • Endpoint: /api/products/<int:productId>

    • Method: PUT

    • 조건: 로그인 상태, 수정 권한 있는 사용자(게시글 작성자)만 가능.

    • 검증: 요청자가 게시글의 작성자와 일치하는지 확인.

    • 구현: 입력된 정보로 기존 상품 정보를 업데이트.

  • 상품 삭제

    • Endpoint: /api/products/<int:productId>

    • Method: DELETE

    • 조건: 로그인 상태, 삭제 권한 있는 사용자(게시글 작성자)만 가능.

    • 검증: 요청자가 게시글의 작성자와 일치하는지 확인.

    • 구현: 해당 상품을 데이터베이스에서 삭제.

view.py
from django.shortcuts import render, get_object_or_404
from django.core import serializers
from rest_framework. decorators import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from .models import Product
from .serializers import ProductSerializer



class product_view(APIView):

    permission_classes = [IsAuthenticatedOrReadOnly]
    def get(self, request):
        products = Product.objects.all()
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = ProductSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
urls.py
from django.urls import path
from . import views

app_name = "products"

urlpatterns = [
    path("", views.product_view.as_view(), name="list"),
    path("<int:pk>/", views.detail_view.as_view(), name="detail"),
]
serializers.py
from rest_framework import serializers
from .models import Product


class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = "__all__"
from django.db import models

# Create your models here.
class Product(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField(max_length=200)
    image = models.ImageField(upload_to='products/', null=True, blank=True)
    my_datetime = models.DateTimeField(auto_now_add=True)


    def __str__(self):
        return self.title

하면 기능 구현은 돼긴 하더라
그리고 https://github.com/aidenlim-dev/django-class 심화과제 해설 코드