멋쟁이 사자처럼 10기/중앙해커톤

너낼역 백엔드 개발 과정

cha2y0ung 2022. 8. 26. 17:13
728x90

아이디어톤이 끝나고 <너낼역>을 가지고 중앙해커톤에 참여하게 되었다

나는 백엔드이기 때문에,,! 나경이랑 같이 백엔드 개발을 했다

 

먼저 db 설계서를 작성했는데

db 설계서를 바탕으로 User/Warning/UsedEye/Eye 이렇게랑 Seat/Train 이렇게 쪼개서 하기로 했다

 

나도 그렇고 나경이도 그렇고 당연히 처음보는 기차나 좌석 개발보다는 유저 부분이 더 쉬울줄 알았고,,

그래서 사다리타기로 내가 유저 파트 맡았을때는 기분이 좋았는데,, ㅎㅎㅎㅎㅎㅎ

//그러지 말았어야했다//

 

drf 로그인 방법에는 세션로그인 방식과 토큰 로그인 방식이 있다

 

처음에는 멋사 정규 세션때 배운 세션 로그인 방식으로 개발을 했다

 

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser

# Create your models here.

class User(AbstractUser):
    name = models.CharField(max_length=255, unique=True, default = False)
    username = None

    USERNAME_FIELD = "name"
    REQUIRED_FIELDS = []

    def __str__(self):
        return f"{self.name}"

    def save_user(self):
        self.save()

    def delete_user(self):
        self.delete()


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    eye = models.IntegerField()
    

    def __str__(self):
        return f"{self.user.name}"

    def save_profile(self):
        self.save()

    def delete_profile(self):
        self.delete()

    @classmethod
    def update_profile(cls, user, eye):
        profile = cls.objects.filter(user=user).update(eye=eye)

        return profile


class Warnings(models.Model):
    user = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add=True)
    station = models.IntegerField()
    
class UsedEye(models.Model):
    user = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add = True)
    amount = models.IntegerField()

class Eye(models.Model):
    user = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add = True)
    amount = models.IntegerField()

 

serializers.py

from rest_framework import serializers
from .models import *
from django.contrib.auth import authenticate
from django.contrib.auth.password_validation import validate_password

class UserSerializer(serializers.ModelSerializer):
    profile='ProfileSerializer(many=False,read_only=True)'
    class Meta:
        model = User
        fields = ['pk','name','profile','username']
        extra_kwargs={
            "profile":{'read_only':True}
        }

class ProfileSerializer(serializers.ModelSerializer):
    user = UserSerializer(read_only=True)
    class Meta:
        model = Profile
        fields = "__all__"

class UpdateProfileSerializer(serializers.ModelSerializer):
    user = UserSerializer(read_only=True,many=False)
    class Meta:
        model = Profile
        fields = "__all__"

    def get_profile(self,instance,data):
        instance.bio = data.get('eye', instance.eye)
        instance = super().get_fields(instance, data)
        return instance

    def update(request, instance, validated_data):
        instance.eye = validated_data['eye']

        instance.save()
        instance=super().update(instance,validated_data)
        return instance


class LoginSerializer(serializers.Serializer):
    name = serializers.CharField(write_only=True, required=True)
    password = serializers.CharField(write_only=True, required=True)
    
    def validate_user(self):
        new_user = authenticate(
            name=self.validated_data["name"],
            password=self.validated_data["password"],
        )
        if new_user is not None:
            return new_user
        raise serializers.ValidationError("The User does not Exist")

# User create serializer
class UserCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "name", "username", "password")
        extra_kwargs = {"password": {"write_only": True}}

    def create(self, validated_data):
        user = User.objects.create_user(**validated_data)
        return user


# Changing the password
class ChangePasswordSerializer(serializers.ModelSerializer):
    
    password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
    password2 = serializers.CharField(write_only=True, required=True)
    old_password = serializers.CharField(write_only=True, required=True)

    class Meta:
        model = User
        fields = ('old_password', 'password', 'password2')

    def validate(self, attrs):
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError({"password": "Password fields didn't match."})

        return attrs

    def validate_old_password(self, value):
        user = self.context['request'].user
        if not user.check_password(value):
            raise serializers.ValidationError({"old_password": "Old password is not correct"})
        return value

            
    def update(self, instance, validated_data):
        user = self.context['request'].user

        if user.pk != instance.pk:
            raise serializers.ValidationError({"authorize": "You dont have permission for this user."})

        instance.set_password(validated_data['password'])
        instance.save()

        return instance
    
    
class WarningListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Warnings
        fields = '__all__'

class UsedEyeSerializer(serializers.ModelSerializer):
    class Meta:
        model = UsedEye
        fields = '__all__'

class EyeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Eye
        fields= '__all__'

 

views.py

from django.shortcuts import render
from .models import *
from django.views.generic import ListView
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from rest_framework.permissions import IsAuthenticated
from rest_framework import status, mixins, generics
from django.contrib.auth import authenticate, login, logout
from .serializers import *
from rest_framework.views import APIView
from drf_yasg.utils import swagger_auto_schema

class LoginAPIView(APIView):
    @swagger_auto_schema(request_body=LoginSerializer)
    def post(self, request, format=None):
        serializer = LoginSerializer(data=request.data)
        if serializer.is_valid():
            user = serializer.validate_user()
            data = {
                "message": "User logged in successfully",
                "name": user.name,
                "user_id":user.id
            }

            # get auth token
            token, created = Token.objects.get_or_create(user=user)
            data["token"] = token.key
            # data["User"]=user

            responseStatus = status.HTTP_200_OK

            return Response(data, status=responseStatus)

        else:
            Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class UserCreateAPIView(APIView):
    @swagger_auto_schema(request_body=UserCreateSerializer)
    def post(self, request, format=None):
        data = request.data
        print("data",data)
        name = data["name"]

        serializer = UserCreateSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(user_type=user_type)
            data = {"name": data["name"], "message": "User created successfully"}

            return Response(data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class LogoutAPIView(APIView):
    def get(self, request, format=None):
        logout(request)
        return Response(status=status.HTTP_200_OK)


class ChangePasswordView(generics.UpdateAPIView):

    queryset = User.objects.all()
    permission_classes = (IsAuthenticated,)
    serializer_class = ChangePasswordSerializer
    
class ProfileAPIview(generics.GenericAPIView):
    lookup_field = 'user'
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer

    def get(self, request, pk=None):
        instance = self.get_object()
        print("instance",instance)
        instance.eye=request.data['eye']
        instance.get_fields=['eye']
        return Response('done')



class ProfileUpdateAPIview(generics.GenericAPIView):
    lookup_field = 'user'
    queryset = Profile.objects.all()
    serializer_class = UpdateProfileSerializer

    def put(self, request, pk=None):
        instance = self.get_object()
        print("instance",instance)
        instance.eye=request.data['eye']
        instance.save(update_fields=['eye'])
        return Response('done')    
    


class WarningListView(generics.GenericAPIView, mixins.ListModelMixin):
    serializer_class = WarningListSerializer
    def get_queryset(self):
        return Warnings.objects.all().order_by('id')
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

class WarningView(ListView):
    def get_queryset(self, **kwargs):
        queryset = Warnings.objects.filter(id = self.request.session.get('user'))
        return queryset

class UsedEyeListView(generics.GenericAPIView, mixins.ListModelMixin):
    serializer_class = UsedEyeSerializer
    def get_queryset(self):
        return UsedEye.objects.all().order_by('id')
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

class UsedEyeView(ListView):
    def get_queryset(self, **kwargs):
        queryset = UsedEye.objects.filter(id = self.request.session.get('user'))
        return queryset

class EyeListView(generics.GenericAPIView, mixins.ListModelMixin):
    serializer_class = EyeSerializer
    def get_queryset(self):
        return Eye.objects.all().order_by('id')
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

class EyeView(ListView):
    def get_queryset(self, **kwargs):
        queryset = Eye.objects.filter(id = self.request.session.get('user'))
        return queryset

 

서버상으로 다 돌아가서 배포까지 완료했는데 

프론트와 연동하는 과정에서 세션ID가 발급되지 않는 문제가 발생했다

신기하게 회원가입은 다 되는데 로그인이 안되는...ㅎㅎㅎ

cors 도 허용하구,, 다 했는데도 해결이 안돼서 결국 token 로그인 방법으로 바꾸기로 했다

 

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.core.validators import MinValueValidator, MaxValueValidator

# Create your models here.

class CustomUserManager(BaseUserManager):
    def create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(email=self.normalize_email(email), **extra_fields)
        user.set_password(password)
        user.save()
        return user
    
    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self.create_user(email=self.normalize_email(email), password=password, **extra_fields)

class User(AbstractUser):
    email = models.EmailField(max_length=20, unique=True)
    username = models.CharField(max_length=40, unique=False, default='')
    eyes = models.CharField(max_length=40, default='')
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []
    objects = CustomUserManager()

class Profile(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    

class Train(models.Model):
    train_id = models.IntegerField()

class Seat(models.Model):
    train = models.ForeignKey(Train, on_delete=models.CASCADE, related_name='seat')
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    seat_num = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(36)])
    is_seated = models.BooleanField(default=False)
    station = models.IntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(63)])

class UsedEye(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='usedeye')
    created_at = models.DateTimeField(null = True, blank = True)
    amount = models.IntegerField()

class Eye(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='eye')
    created_at = models.DateTimeField(null = True, blank = True)
    amount = models.IntegerField()

class Warning(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='warning')
    created_at = models.DateTimeField(null = True, blank = True)
    station = models.IntegerField(null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(63)])

 

serializers.py

from rest_framework import serializers
from .models import *
from django.contrib.auth import login
from rest_framework.response import Response
from django.contrib.auth.hashers import make_password
from rest_framework_simplejwt.tokens import RefreshToken

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'email', 'password', 'username']

    def create(self, validated_data):
        user = User.objects.create(
            email=validated_data['email'])
        user.set_password(validated_data['password'])
        user.save()
        login(self.context['request'], user)
        return user


class UserLoginSerializer(serializers.Serializer):
    email = serializers.EmailField(max_length=64)
    password = serializers.CharField(max_length=128, write_only=True)

    def validate(self, data):
        email = data.get("email", None)
        password = data.get("password", None)

        if User.objects.filter(email=email).exists():
            user = User.objects.get(email=email)
            if not user.check_password(password):
                raise serializers.ValidationError('잘못된 비밀번호')
            else:
                token = RefreshToken.for_user(user)
                refresh = str(token)
                access = str(token.access_token)

                data = {
                    'user': user.email,
                    'access_token': access,
                    'username': user.username
                }

                return data
        else:
            raise serializers.ValidationError('존재하지 않는 유저')

class Profileserialzier(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'password', 'eyes']

class PasswordChangeSerializer(serializers.Serializer):
    password = serializers.CharField(max_length=128, write_only=True)

    def update(self, instance : User, validated_data):
        instance.password = make_password(validated_data['password'])
        instance.save()
        return Response()

    def create(self, validated_data):
        pass

class SeatSerializer(serializers.ModelSerializer):
    class Meta:
        model = Seat
        fields = ['id', 'user', 'train', 'seat_num', 'is_seated', 'station']

class TrainSerializer(serializers.ModelSerializer):
    seat = SeatSerializer(many=True, read_only=True)

    class Meta:
        model = Train
        fields = ['id', 'train_id', 'seat']

class UsedEyeSerializer(serializers.ModelSerializer):
    class Meta:
        model = UsedEye
        fields = ['user', 'created_at', 'amount']

class EyeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Eye
        fields = ['user', 'created_at', 'amount']

class WarningSerializer(serializers.ModelSerializer):
    class Meta:
        model = Warning
        fields = ['user', 'created_at', 'station']

 

views.py

from django.shortcuts import get_object_or_404
from .serializers import *
from .models import *
from rest_framework import views
from rest_framework.response import Response
from rest_framework.status import *
from django.contrib.auth import login, logout

# Create your views here.

class SignUpView(views.APIView):
    def post(self, request, format=None):
        serializer = UserSerializer(
            context={'request': request}, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({'message': '회원가입 성공', 'data': serializer.data})
        return Response({'message': '회원가입 실패', 'data': serializer.errors})

    def get(self, request):
        users = User.objects.all()
        serializer = UserSerializer(users, many=True)
        return Response({'message': '유저 목록 조회 성공', 'data': serializer.data}, status=HTTP_200_OK)

    def patch(self, request):
        user = User.objects.get(id=request.user.id)
        serializer = UserSerializer(user, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response({'message': 'username 입력 성공', 'data': serializer.data}, status=HTTP_200_OK)
        return Response({'message': 'username 입력 실패', 'data': serializer.errors}, status=HTTP_400_BAD_REQUEST)


class LoginView(views.APIView):
    def post(self, request):
        serializer = UserLoginSerializer(data=request.data)
        if serializer.is_valid():
            return Response({'message': "로그인 성공", 'data': serializer.validated_data}, status=HTTP_200_OK)
        return Response({'message': "로그인 실패", 'data': serializer.errors}, status=HTTP_400_BAD_REQUEST)


class LogoutView(views.APIView):
    def get(self, request, format=None):
        logout(request)
        return Response({'message': "로그아웃 성공"}, status=HTTP_200_OK)


class ProfileView(views.APIView):
    def get(self, request):
        user = User.objects.get(id = request.user.id)
        serializer = Profileserialzier(user)
        return Response({'data': serializer.data}, status=HTTP_200_OK)
    
    def patch(self, request):
        user = User.objects.get(id = request.user.id)
        serializer = Profileserialzier(user, data=request.data, partial = True)
        if serializer.is_valid():
            serializer.save(user=request.user) 
            return Response({'message': 'eye 성공', 'data': serializer.data}, status=HTTP_200_OK)
        return Response({'message': 'eye 실패', 'data': serializer.errors}, status=HTTP_400_BAD_REQUEST)


class PasswordChangeView(views.APIView):
    serializer_class = PasswordChangeSerializer
    
    def post(self, request):
        user = User.objects.filter(user=request.user)
        user.set_password(request.data.get('password'))
        print(request.data.get('password'))
        print(request.data)
        user.save()
        return Response({'message': "비밀번호 변경 성공"}, status=HTTP_200_OK)

class TrainListView(views.APIView):
    def get(self, request, format=None):
        trains=Train.objects.all()
        serializer=TrainSerializer(trains, many=True)
        return Response(serializer.data)

class TrainDetailView(views.APIView):
    def get(self, request, pk, format=None):
        train = get_object_or_404(Train, pk=pk)
        serializer = TrainSerializer(train)
        return Response(serializer.data)

class SeatListView(views.APIView):
    def get(self, request, format=None):
        seats=Seat.objects.all()
        serializer=SeatSerializer(seats, many=True)
        return Response(serializer.data)

class SeatDetailView(views.APIView):
    def get(self, request, pk, format=None):
        seat = get_object_or_404(Seat, pk=pk)
        serializer = SeatSerializer(seat)
        return Response(serializer.data)

    def get_object(self, pk):
        return Seat.objects.get(pk=pk)

    def patch(self, request, *args, **kwargs):
        pk = self.kwargs.get('pk')
        seat_object = self.get_object(pk)
        serializer = SeatSerializer(seat_object, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response({'message':'좌석 정보 기입 성공', 'data':serializer.data})
        return Response({'message':'좌석 정보 기입 실패', 'error':serializer.errors})

class UsedEyeView(views.APIView):
    def patch(self, request):
        serializer = UsedEyeSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(user=request.user) 
            return Response({'message': 'Used Eye 생성 성공', 'data': serializer.data}, status=HTTP_200_OK)
        return Response({'message': 'Used Eye 실패', 'data': serializer.errors}, status=HTTP_400_BAD_REQUEST)

    def get(self, request):
        usedeye = UsedEye.objects.filter(user = request.user.id)
        serializer = UsedEyeSerializer(usedeye, many=True)
        return Response({'message': 'Used Eye 조회 성공', 'data': serializer.data}, status=HTTP_200_OK)

class EyeView(views.APIView):
    def patch(self, request):
        serializer = EyeSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(user=request.user) 
            return Response({'message': 'EYE 생성 성공', 'data': serializer.data}, status=HTTP_200_OK)
        return Response({'message': 'EYE 생성 실패', 'data': serializer.errors}, status=HTTP_400_BAD_REQUEST)

    def get(self, request):
        eye = Eye.objects.filter(user = request.user)
        serializer = EyeSerializer(eye, many=True)
        return Response({'message': 'EYE 조회 성공', 'data': serializer.data}, status=HTTP_200_OK)

class WarningView(views.APIView):
    def patch(self, request):
        serializer = WarningSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save() 
            return Response({'message': '경고 생성 성공', 'data': serializer.data}, status=HTTP_200_OK)
        return Response({'message': '경고 생성 실패', 'data': serializer.errors}, status=HTTP_400_BAD_REQUEST)

    def get(self, request):
        warning = Warning.objects.filter(user = request.user)
        serializer = WarningSerializer(warning, many=True)
        return Response({'message': '경고 조회 성공', 'data': serializer.data}, status=HTTP_200_OK)

 

 

결국 이렇게 바꿔서 배포하고 프론트와 무사히 연동해서 잘 굴러가는 웹사이트를 만들었다ㅠㅠ

 

중앙해커톤이 얼마 남지 않은 시점에 session로그인이 안되는 것을 알게되어서

token 로그인 방식으로 바꾸느라 좀 고생했던것 같고,, (session 로그인 안되는 오류를 언니들도 처음본다고하구 서버 상으로는 멀쩡히 돌아가니까 프론트 언니랑 화면공유하면서 엥ㅇㅇ??안돼??에ㅔ?? 서버돌아가??에?? 이게 어떻게이래??.. 를 반복해따..)

배포를 몇번이나 했는지 모르겠지만^^ 다행히 잘 끝났다

 

다 끝내긴 했지만 궁금한점들이

1. 대체 왜 세션 로그인이 안됐던걸까?..?

2. username을 바꾸는거랑 password change 하는걸 profile 만들어서 url 하나로 수정할수 있게하고 싶었는데,, 

결국 방법을 찾지 못했다

-> 프론트에서도 합쳐줄수 있냐고 했었는데,, 일단 지금 코드로 돌아가니까 엎어버리지 말자는 결론이 나와서 기각

다행히 현재 eye 개수를 profile에 넣는거는 성공했다

3. 상식적으로 생각했을때 사용한 eye/ 충전한 eye/ 현재 eye 를 model 하나(eye model)로 관리할수 있어야하는거같은데

백엔드 운영진언니가 그렇게 하면 어려워질거라고 따로 분리하라구 해서 그렇게 했는데 내 실력으로는ㅋㅋㅋㅋ 그게 옳은 선택이었지만 아직도 궁금하긴하다.. 

 

세션 로그인 땜에&&==^^ 구글에다가 session login cors error token login drf 등등으로 검색해서 나오는 페이지를 거의 다 들어가본듯ㅋㅋㅋㅋㅋㅋㅋㅋㅋ아ㅠ

다음 글에다가 한번에 정리해보려구 한다!!

 

NNAERYEOK (github.com)

 

NNAERYEOK

NNAERYEOK has 3 repositories available. Follow their code on GitHub.

github.com

 

React App (nny-front-89dkx3f4w-nny.vercel.app)

 

React App

 

nny-front-89dkx3f4w-nny.vercel.app