내일배움캠프 - My Little Shoes 개발일지

Dongwoo Kim·2022년 7월 6일
0

스파르타 코딩클럽

내일배움캠프 AI 웹개발자양성과정 2회차

유화제작 팀 프로젝트 개발일지

0. 프로젝트 정보

프로젝트 명

MLS - My Little Shoes (나만의 신발 스타일 만들기)

기간

2022.06.28-07.06

프로젝트 목표

  1. DRF를 이용한 프로젝트 만들기
  2. Generative models 이용하기
  3. CRUD 숙련

팀 정보

  • 팀명 : 사오이십
  • 팀원 : 김동우, 김진수, 최민기, 박진우

역할 분담

김동우 : 회원가입/로그인 기능 / Generative model 사용

김진수 : 추천 스타일 페이지

박진우 : 이미지 업로드 + 결과 페이지 (결과 페이지에서 저장누르면 히스토리에 저장됨)

최민기 : 히스토리(게시판) 페이지 (+ 좋아요 + 댓글 + 즐겨찾기)

Generative model

https://github.com/crowsonkb/style-transfer-pytorch

1. 이미지 생성

사용자로부터 image_one (content.png)와 image_two (style.png)를 받아서
style_transfer 명령어를 통해 image_result (out.png)를 생성

1) image upload

// upload.js
// 이미지 업로드
async function imageUpload() {
  const formData = new FormData();

  image_one = document.getElementById("a-upload-file").files[0];
  image_two = document.getElementById("b-upload-file").files[0];

  formData.append("image_one", image_one);
  formData.append("image_two", image_two);

  $.ajax({
    type: "POST",
    url: "http://127.0.0.1:8000/upload/",
    data: formData,
    cache: false,
    contentType: false,
    processData: false,
    success: function (response) {
      document
        .getElementById("result-file")
        .setAttribute("src", backend_url + "/out.png");
    },
    error: function (error) {
      alert(error.responseText);
    },
  });
}

2) Generative model 적용

# upload/views.py
# 이미지 등록 페이지
class ImageUploadView(APIView):

    # 새로운 이미지 만들기
    # 이미지 두장 받아서 새로운 스타일의 이미지 만들어서 반환
    def post(self, request):
        content = request.FILES.get("image_one", "") 
        style = request.FILES.get("image_two", "")

        BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        content_path = BASE_DIR + '\content.png'
        style_path = BASE_DIR + '\style.png'

        with open(content_path, 'wb+') as destination:
            for chunk in content.chunks():
                destination.write(chunk)

        with open(style_path, 'wb+') as destination:
            for chunk in style.chunks():
                destination.write(chunk)

        os.system('style_transfer content.png style.png')

        output_path = 'out.png'
            
        return Response({'output_path': output_path}, status=status.HTTP_200_OK)

2. 이미지 저장

content.png, style.png, out.png를 ImageField로 넣은 ImageModel 저장 후 HistoryModel 저장

// upload.js
// 결과 저장
async function image_save() {
  user = localStorage.getItem("user");
  const data = {
    user: user,
  };

  $.ajax({
    type: "POST",
    url: "http://127.0.0.1:8000/upload/result/",
    data: JSON.stringify(data),
    success: function (response) {
      alert(response["message"]);
      console.log(response["message"]);
      location.reload();
    },
    error: function (error) {
      alert(error.responseText);
      console.log(error.responseText);
    },
  });
}
# upload/views.py
# 이미지 결과 페이지
class ImageResultView(APIView):

    # 저장하기 버튼 클릭 : 이미지 저장
    def post(self, request):
        data = json.loads(request.body)
        user_id = data['user']

        image_object = ImageModel()
        
        image_one = Image.open('content.png')
        image_file = BytesIO()
        image_one.save(image_file, image_one.format)
        image_object.image_one.save('content.png',
                                    InMemoryUploadedFile(
                                        image_file,
                                        None, 'content.png',
                                        'image/png',
                                        image_one.size,
                                        None),
                                    save=False)
        image_one.close()

        image_two = Image.open('style.png')
        image_file = BytesIO()
        image_two.save(image_file, image_two.format)
        image_object.image_two.save('style.png',
                                    InMemoryUploadedFile(
                                        image_file,
                                        None, 'style.png',
                                        'image/png',
                                        image_two.size,
                                        None),
                                    save=False)
        image_two.close()

        image_result = Image.open('out.png')
        image_file = BytesIO()
        image_result.save(image_file, image_result.format)
        image_object.image_result.save('out.png',
                                    InMemoryUploadedFile(
                                        image_file,
                                        None, 'out.png',
                                        'image/png',
                                        image_result.size,
                                        None),
                                    save=False)
        image_result.close()

        image_object.save()

        history_data = {
            'exposure_start': "2022-06-30 00:00:00",
            'exposure_end': "2023-06-30 00:00:00",
        }
        
        user = UserModel.objects.get(id=user_id)

        history_serializer = HistorySerializer(data=history_data)
        if history_serializer.is_valid():
            history_serializer.save(user=user, image=image_object)


        return Response({'message': '저장 완료!'},status=status.HTTP_200_OK)

3. User 기능

1) User Model

class UserManager(BaseUserManager):
    def create_user(self, email, username, password=None):
        if not username:
            raise ValueError('Users must have an username')
        user = self.model(
            username=username,
            email=email,
        )
        user.set_password(password)
        user.save(using=self._db)
        return user
    
    # python manage.py createsuperuser 사용 시 해당 함수가 사용됨
    def create_superuser(self, email, username, password=None):
        user = self.create_user(
            email=email,
            username=username,
            password=password,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user


class User(AbstractBaseUser):
    username = models.CharField("사용자 아이디", max_length=12, unique=True)
    email = models.EmailField("이메일", max_length=100, unique=True)
    password = models.CharField("비밀번호", max_length=128)
    fullname = models.CharField('이름', max_length=20)
    join_date = models.DateTimeField('생성시각', auto_now_add=True)
    
    def __str__(self):
        return f"{self.username}"

    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    objects = UserManager()

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):
        return True

    @property
    def is_staff(self):
        return self.is_admin

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
    intro = models.TextField()
    age = models.IntegerField()

    def __str__(self):
        return f"{self.user.username}'s profile"

2) UserSerializer

# user/serializers.py
import re
from rest_framework import serializers
from .models import User, UserProfile

SPECIAL_CHAR = ['`', '~', '!', '@', '#', '%', '^', '&', '*', '(', ')',
                ',', '.', '/', '<', '>', '?', '[', ']', '{', '}']


class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfile
        fields = ['age', 'intro']

class UserSerializer(serializers.ModelSerializer):
    userprofile = UserProfileSerializer()
    brand = serializers.SerializerMethodField()

    def get_brand(self, obj):
        return "브랜드가 들어갈 자리"

    class Meta:
        model = User
        fields = ["username", "password", "fullname", "email", "userprofile", "brand"]

        extra_kwargs = {
            'password': {'write_only': True}
        }

    def validate(self, data):
        if self.instance is None or data.get('password'):
            password = data.get('password', "")
            
            if len(password) < 8 or len(password) > 14:
                raise serializers.ValidationError(
                        detail={"error": "비밀번호는 8글자 이상, 14글자 이하이어야합니다."},
                    )

            if not any(char in SPECIAL_CHAR for char in password):
                raise serializers.ValidationError(
                        detail={"error": "특수문자가 1개이상 포함되어야합니다."},
                    )

            if re.search('[0-9]+', password) is None:
                raise serializers.ValidationError(
                        detail={"error": "숫자가 1개이상 포함되어야합니다."},
                    )

            if (re.search('[a-z]+', password) is None) and (re.search('[A-Z]+', password) is None):
                raise serializers.ValidationError(
                        detail={"error": "영문 대문자 또는 소문자가 1개이상 포함되어야합니다."},
                    )

        return data

    def create(self, validated_data):
        userprofile = validated_data.pop('userprofile')
        password = validated_data.pop('password')
        user = User.objects.create(**validated_data)
        user.set_password(password)
        user.save()

        userprofile = UserProfile.objects.create(user=user, **userprofile)

        return validated_data


    # instance : 수정할 object
    # validated_data : 수정할 내용
    def update(self, instance, validated_data):
        # validated_data = {'username': 'dongwoo', 'email': 'esdx@daum.net', ...}
        for key, value in validated_data.items():
            if key == "password":
                instance.set_password(value)
                continue

            if key == "userprofile":
                for key_2, value_2 in value.items():
                    setattr(instance.userprofile, key_2, value_2)
                continue

            setattr(instance, key, value)

        instance.save()
        instance.userprofile.save()
        return instance

3) User View

# user/views.py
# 로그인/로그아웃 기능
class UserView(APIView):
    permission_classes = [permissions.AllowAny]

    # 로그인 기능
    def post(self, request):
        login_data = {
            'email': request.data.get('email', ''),
            'password': request.data.get('password', ''),
        } 
        user = authenticate(request, **login_data)
        
        if not user:
            msg = '아이디 또는 패스워드를 확인해주세요.'
            return Response({'message': msg}, status=status.HTTP_400_BAD_REQUEST)

        login(request, user)

        id = user.id
        msg = '로그인 성공!'
        return Response({'message': msg, 'id': id, 'fullname': user.fullname, 'email': user.email}, status=status.HTTP_200_OK)

    # 로그아웃 기능
    def delete(self, request):
        logout(request)
        msg = '로그아웃 성공!'
        return Response({'message': msg}, status=status.HTTP_200_OK)

# 회원 정보 기능
class UserInfoView(APIView):
    # 회원 정보 조회
    def get(self, request):
        user = request.user
        
        if not user.is_authenticated:
            msg = '로그인을 해주세요'
            return Response({'message': msg}, status=status.HTTP_400_BAD_REQUEST)

        return Response(UserSerializer(user).data, status=status.HTTP_200_OK)

    # 회원가입
    def post(self, request):
        signup_data = json.loads(request.body)
        user_serializer = UserSerializer(data=signup_data)
        if user_serializer.is_valid():  # validation
            user_serializer.save()      # create
            return Response({'message': '저장 완료!'}, status=status.HTTP_200_OK)

        return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
    # 회원수정
    def put(self, request):
        user = request.user

        confirm_data = { 
            'email': user.email,
            'password': request.data.get('password_old', ''),
        }
        user = authenticate(request, **confirm_data)
        if not user:
            return Response({'message': '비밀번호를 확인해주세요.'}, status=status.HTTP_400_BAD_REQUEST)

        if request.data.get('password_new'):
            request.data['password'] = request.data.get('password_new')

        user_serializer = UserSerializer(user, data=request.data, partial=True)
        if user_serializer.is_valid():  # validation
            user_serializer.save()      # update
            return Response({'message': '저장 완료!'}, status=status.HTTP_200_OK)

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

    # 회원탈퇴
    def delete(self, request):
        confirm_word = request.data.get('confirm_word', '')
        if confirm_word != '확인':
            return Response({'error': '회원 탈퇴 확인 메시지를 정확히 입력해주세요'}, 
                             status=status.HTTP_400_BAD_REQUEST)

        email = request.user.email
        password = request.data.get('password', '')
        
        confirm_data = { 
            'email': email,
            'password': password,
        }

        user = authenticate(request, **confirm_data)
        if not user:
            return Response({'error': '비밀번호를 확인해주세요.'}, status=status.HTTP_400_BAD_REQUEST)

        logout(request)
        user.delete()

        return Response({'message': '삭제 성공!'}, status=status.HTTP_200_OK)

# 회원 히스토리 기능
class UserHistoryView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    # 회원 히스토리 조회 기능
    def get(self, request):
        histories = History.objects.filter(user=request.user)
        history_ids = [history.id for history in histories]
        return Response(history_ids, status=status.HTTP_200_OK)

# 회원 히스토리 좋아요 기능
class UserHistoryLikeView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    # 좋아요누른 히스토리 조회 기능
    def get(self, request):
        likes = Like.objects.filter(user=request.user)
        histories = [like.history for like in likes]
        history_ids = [history.id for history in histories]
        return Response(history_ids, status=status.HTTP_200_OK)

4. 기타 정보

1) 프로젝트 문서

https://www.notion.so/kimphysicsman/MLS-My-Little-Shoes-2d7eafdb6a514ae7a569f11cc04411e1

2) github

https://github.com/nbcamp-AI-2-fantastic4/mylittleshoes_backend
https://github.com/nbcamp-AI-2-fantastic4/mylittleshoes_frontend

3) 발표 영상

https://youtu.be/-UBy-KnmZs4

profile
kimphysicsman

0개의 댓글