Westagram 회고

haejun-kim·2020년 8월 16일
0

Django를 배우며

Django의 개념을 배웠을 땐 개념 자체는 그렇게 어렵다고 느끼지 못했다. 근데 이건 나의 완전한 착각이였다. 막상 회원가입 기능을 구현해보려고 하니 굉장히 막막했다. model에 field를 무엇으로 줄지, view에서는 어떤 로직으로 구현을 해야할지, 유효성 검사는 어떻게 하는게 가장 좋을지 등.. 하지만 언제까지 가만히 있을 순 없었다. 인터넷을 찾고 찾아서 하나하나 구현해나가기 시작했다.
먼저 app을 생성하면 setting에 생성한 app을 인식시켜주고 모델링을 시작한다. 회원 가입 기능을 구현하기 위해서 User라는 클래스(테이블)을 만들었고, 그 안에 emailpassword라는 Field를 만들어주었다. 각 column에 어떤 Field를 지정해줄지, 어떤 옵션값을 넣어줄지 하나하나 선택이였고, 모든 선택에는 이유가 있었다. 그냥은 없었다.
이렇게 모델링을 한 후 view에 Json 형식으로 request와 response를 받았을 때 각각 어떤 기능을 수행할건지에 대한 로직을 구현했다. 처음에 회원가입은 굉장히 어려웠지만 회원가입을 하고 나니 다른 기능들은 비교적 할만하다고 느껴졌다.(게시글과 댓글 기능은 또 달랐지만..)
각종 에러에 대해서 공부했고, 에러 코드에 대해서 공부했고, HTTP와 JSON 통신 방식에 대해서 공부했으며, 데이터베이스에서 객체를 어떻게 가져와야할지에 대해서 고민했다. 그리고 가져온 객체를 어떻게 활용할지 생각했다.
view까지 작성하고 나면 project의 url과 app의 url을 작성하고, View에서 구현한 기능을 연결시켜주어 해당 경로로 접속 했을 때 적절한 로직이 구현되도록 하였다.
그저 개념을 공부하는것이 아니라 내가 공부한 개념을 이제 직접 어떤 기능들을 구현한다고 생각하니까 어려웠지만 재밌었다.
이렇게 나는 점점 새로운 기술을 접하는데 생겼던 두려움을 조금씩 극복하고 있었다.

기억하고 싶은 코드

모든 코드를 기억하고싶다.(그래야 다음에 더 잘 구현할 수 있을테니..) 하지만 그 중에서도 다음과 같이 기억하고 싶은 코드를 분류했다.

가장 어려웠던 코드

회원가입
처음이라 구현하는데 굉장히 어려웠다. error가 발생했을 때 어떻게 error를 처리해야할지, 데이터베이스에서 어떻게 저장 된 데이터를 가지고 와서 써먹을지 많이 고민했다. 그리고 다음과 같이 구현했다.

class SignupView(View):
    def post(self, request):
        data = json.loads(request.body)

        try: 
            if not data['email'] or not data['password']:
                return JsonResponse({'message' : 'No value entered'}, status = 400)
            signup_user = User(
                email    = data['email'],
                password = data['password'],
            )
            signup_user.full_clean()
            signup_user.password = signup_user.password.encode('utf-8')
            signup_user.password = bcrypt.hashpw(signup_user.password, bcrypt.gensalt())
            signup_user.password = signup_user.password.decode('utf-8')
            signup_user.save()
            return JsonResponse({'message' : 'SUCCESS'}, status = 200)
        except ValidationError as e:
            trace_back = traceback.format_exc()
            print(f"{e} : {trace_back}")
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
        
        return JsonResponse({'message' : 'Invalid format or Duplicated Email'}, status = 400)

    # 유저 리스트
    def get(self, request):
        user = User.objects.values()
        return JsonResponse({'user' : list(user)}, status = 200)
  • 먼저 JSON 형태의 request는 딕셔너리 형태로 서로 요청과 응답을 주고받는데, 이 때 key값이 잘못입력돼 있다면 KEY ERROR가 발생한다. 이 KEY ERROR가 발생하면 해당 error를 except하여 적절한 에러 코드와 메세지를 반환해주었다. 나중에 알게 된 사실인데 파이썬의 입장에서 에러를 발생시키는것은 그렇게 좋은 방법이 아니라고 한다. 그래서 에러를 발생시키지 않고 처리할 수 있으면 최대한 발생시키지 않고 처리하는 것이 좋다고 한다. KEY ERROR부분에서는 KEY ERROR가 발생했을 때 예외처리하는 것이 아니라 다음과 같은 방법도 있었다.
email = 딕셔너리.get(‘email’, None)

키값으로 email이 있다면 email에 값을 저장하고, 만약 email이 아니라면 None을 저장한다. 따라서 KEY ERROR가 발생하지 않는다. 이렇게 하면 각각의 조건에 대한 로직만 구현해주면 되기 때문에 파이썬의 입장에서는 훨씬 무리가 덜 가는 방법이라고 볼 수 있다. 다음 프로젝트에서는 KEY ERROR를 위와 같은 방법으로 구현해봐야겠다.

  • 그리고 만약 사용자가 이메일이나 패스워드에 아무런 값도 입력하지 않았을 경우는 아무런 값이 입력되지 않았다는 메세지를 반환해준다.

  • full_clean()이라는 메소드에서 유효성 검사를 실행하고 이 메소드에서 정상적으로 통과하면 데이터를 save()하면서 SUCCESS라는 메세지를 반환해준다.

  • 사용자가 회원가입을 위해서 입력한 이메일이 이미 있거나, 이메일의 형식이 잘못되었다면 (@.이 없다면) 그와 관련된 메세지와 함께 에러코드를 반환해준다.

이제 와서 보면 꽤나 할만해 보이는 로직이다. 그러나 처음 이 기능을 구현할 때 굉장히 많이 고민했기 때문에 가장 어려웠던 코드로 기억이 된다.

Foreign Key
게시글을 등록할 수 있는 Post 기능을 구현했을 당시 Foreign Key 개념이 이해하기 어려워서, 개념을 이해하고 나서는 이걸 그래서 코드로 어떻게 구현해야 할 지 몰라서 어려웠다. 코드는 다음과 같다.

# models.py
class Post(models.Model):
    email      = models.ForeignKey(User, on_delete = models.CASCADE)
    content    = models.TextField(max_length = 300)
    image_url  = models.URLField()
    created_at = models.DateTimeField(auto_now_add = True)

    class Meta:
        db_table = "posts"

    def  __str__(self):
        return self.email

게시글을 등록했을 때, 어떤 사용자가 입력했는지 알아야했기 때문에 Foreign Key를 사용했다. 그리고 이것을 User 클래스와 묶었다. 그래야 user_id로 어떤 유저가 게시글을 등록했다라는 것을 표현할 수 있기 때문이다.

# views.py
class PostView(View):
    @login_required
    def post(self, request):
        data = json.loads(request.body)

        try:
            if User.objects.filter(id = data['user_id']).exists():
                email = User.objects.get(id = data['user_id'])
                Post(
                    email     = email,
                    content   = data['content'],
                    image_url = data['image_url'],
                ).save()
                return JsonResponse({'message' : 'SUCCESS'}, status = 200)

            return JsonResponse({'message' : 'UNAUTHORIZED'}, status = 401)
        except Exception as e:
            return JsonResponse({'message' : f'{e}'}, status = 400)

class PostDisplayView(View):
    @login_required
    def get(self, request):
        post_data = Post.objects.values()
        return JsonResponse({'post_data' : list(post_data)}, status = 200)

User 테이블에서 user_id에 대한 데이터를 filter()하여 해당 id가 존재한다면 (이미 회원가입이 되어있는 유저가 맞다면) 해당 id를 가진 객체를 가지고 와서 email 변수에 저장한다.
Post 테이블에는 email, content, image_url의 field에 데이터를 받아서 저장하고 성공적으로 해당 로직이 수행됐다면 SUCCESS를 return한다.
if문에서 걸리지 않는다는 것은 데이터베이스에 해당 user_id가 존재하지 않는다는 뜻이며, 그것은 회원가입이 된 유저가 아니라는 뜻이기 때문에 글을 쓴 권한이 없다는 뜻을 UNAUTHORIZED 메세지와 401 에러코드를 반환해주도록 했다.
그 외의 에러가 발생한다면 해당 에러의 내용 자체를 보여주도록 구현했다.
위의 코드는 데이터베이스를 배우기 전에 구현하고 있던 코드라서 Foreign Key에 대한 개념을 몰라서 이게 왜 그렇게 연결이 되는지 도무지 이해가 가지 않았다. 이걸 이해하기 위해서 공식문서를 뒤져보고, 유튜브에서 Foreign Key에 대한 영상들을 찾아보고 주변 동기들에게 내가 이해하고 있는 개념이 맞는지, 그렇다면 이런식으로 구현하면 되는지에 대한 물음을 계속 던졌다. 그렇기 때문에 어려웠던 코드로 기억이 남는다.

아직 미해결 된 코드

현재 회원가입, 로그인, 게시글, 댓글에 대한 기능까지 구현되어 있는 상태다. 다른 기능들은 다 구현되어 있지만 댓글을 보는 기능이 기능적으로 약간 부족하다. 멘토님들이 제시 해 주신 가이드라인에는 1번 사용자가 등록 한 댓글만 볼 수 있게 구현하면 된다고 써져있었지만 그건 하드코딩이라 마음에 들지 않았다. 1사용자가 아니라 2,3 등 내가 어떤 유저가 등록한 댓글을 보고싶다면 그저 보여주고 싶었다. 그래서 다음과 같이 로직을 구현했다.

class CommentView(View):
    @login_required
    def post(self, request):
        data = json.loads(request.body)

        try:
            if User.objects.filter(id = data['user_id']).exists() and Post.objects.filter(id = data['post_id']).exists():
                Comment(
                    email       = User.objects.get(id = data['user_id']),
                    post        = Post.objects.get(id  = data['post_id']),
                    comment     = data['comment'],
                    created_at  = data['created_at']
                ).save()
                return JsonResponse({"message" : "SUCCESS"}, status = 200)

            return JsonResponse({"message" : "USER OR POST DOES NOT EXIST"}, status = 400)
        except Exception as e:
            return JsonResponse({"message" : f'{e}'}, status = 400)

class  CommentDisplayView(View):
    @login_required
    def get(self, request, post_id):
        db_comment   = Comment.objects.all().values()
        comment_list = list(db_comment)
        return JsonResponse(comment_list, safe = False)

위와 같이 구현 할 경우 문제가 내가 user_id1인 유저가 등록 한 댓글만 보고 싶어도 모든 댓글에 대한 정보가 다 보여지더라. 그런 기능적인 문제가 생긴 이유는 내가 all().values()로 쿼리의 모든 정보를 다 가지고 와서 모두 보여지는 걸로 생각된다. 그렇기 때문에 get()메소드를 사용하여 해당 유저에 대한 댓글만 가지고 오고 싶었지만, 그렇게 하면 list 어쩌고 not iterable과 비슷한 에러가 발생한다. list는 분명 iterable한데 어째서 저런 에러가 발생하는지, 저 에러를 고치기 위해서는 어떻게 해야할지는 아직 해결하지 못했다.
여러 정보에 의하면 model_to_dict 를 사용하여 dictionary형태로 만들어주면 해결이 된다는데 이 부분은 추가적으로 공부가 필요한 부분이다.

흐뭇한 코드

가이드라인에 의하면 처음 회원가입 단계에서 이메일에 대한 유효성 검사를 실행하도록 가이드라인이 주어져있었다. 나는 이 유효성 검사를 실행한 과정에서 파이썬에서 지향하는 모듈화를 사용했기 때문에 스스로 조금은 흐뭇하다고 생각된다.
유효성 검사에 대한 함수를 처리 할 validation.py라는 파일을 하나 만들고 그 안에 이메일과 패스워드에 대한 유효성 검사 함수를 생성하고, 그 함수를 import 시켜서 사용했다.
이렇게 구현 한 이유는 views.py에 모든 로직을 구현할 수 있지만, 그렇게 되면 코드가 지저분해지고 가독성도 좋아지지 않으며, 유효성 검사를 할 때 마다 새로 구현해야하는 문제점이 있다. 그리고 views.py에는 request와 response에 대한 처리가 주로 있어야지 유효성 검사에 대한 로직이 views.py에 있는 것은 맞지 않다고 생각했다.
내가 구현한 코드는 다음과 같다.

# validation.py
import re

from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _

def validate_email(value):
    email_reg = r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
    regex = re.compile(email_reg)

    if not regex.match(value):
        raise ValidationError(
            _('%(value)s is not a valid number'),
            params = {'value' : value},
        )

def validate_password(password):
    if len(password) < 8:
        raise ValidationError("password is too short.")
  • 이메일의 경우 정규 표현식을 사용하여 해당 정규 표현식과 match 되지 않았을 경우 ValidationError를 발생하도록 하였고, 해당 error에 대한 표현을 해주었다.
  • 패스워드의 경우 8자리 이하의 경우 에러를 발생하도록 구현하라고 되어있었기 때문에 간단히 길이만 비교하였다.
# modesl.py
from django.db import models

from .validation import validate_email, validate_password

class User(models.Model): 
    email          = models.EmailField(
        max_length = 50,
        validators = [validate_email],
        unique     = True,
    )
    password       = models.CharField(
        max_length = 300,
        validators = [validate_password],
    )

    class Meta:
        db_table = "users"

    def  __str__(self):
        return self.email

내가 만든 함수들을 import하여 validators 옵션에 해당 함수들을 입력해 유효성 검사를 수행할 수 있도록 구현했다.

# views.py
            signup_user = User(
                email    = data['email'],
                password = data['password'],
            )
            signup_user.full_clean()
            signup_user.password = signup_user.password.encode('utf-8')
            signup_user.password = bcrypt.hashpw(signup_user.password, bcrypt.gensalt())
            signup_user.password = signup_user.password.decode('utf-8')
            signup_user.save()
            return JsonResponse({'message' : 'SUCCESS'}, status = 200)
        except ValidationError as e:
            trace_back = traceback.format_exc()
            print(f"{e} : {trace_back}")

full_clean()메소드를 사용하여 실제 유효성 검사를 실행하도록 했다. 해당 메소드를 사용한 이유는 모델에서 사용했던 validators라는 옵션은 사실은 Django의 Form에서의 유효성 검사를 위해서 만들어진 옵션이다. 따라서 view에서는 실제 유효성 검사를 실행하지 않는다. 이 문제를 해결하기 위해 Django에서 제공하는 메소드가 full_clean()이다. 해당 메소드를 사용하면 Form이 아니라 View에서도 실제 유효성 검사를 실행하도록 하는 기능을 하는 메소드다. 따라서 full_clean()부분에서 유효성 검사를 수행하고, 유효성 검사에 통과하지 못하면 Validation에러를 발생, 통과한다면 다음 로직을 구현한다.

처음엔 상당히 어려웠던 개념이기 때문에 이 기능을 구현할까 고민했다. 그래도 막상 구현하니 코드도 많이 깔끔해져서 '이렇게 구현하길 잘했다'라는 흐뭇하다는 생각이 들었던 코드다.

1차 프로젝트를 앞두고

욕심에는 인스타그램의 좋아요, 팔로우 기능도 구현하고 싶었다. 하지만 시간적으로 그쪽까지 구현하기에는 여유가 없었다. 하지만 웬만한 웹사이트에서 가장 기본적으로 회원가입과 로그인이라는 기능이 필요하기에 위의 경험은 굉장히 값진 경험이라고 생각한다.
아직은 이정도 구현한걸로 프로젝트 때 제대로 기능 하나라도 구현할 수 있을까 하는 불안함도 많이 있었다. 그런데 생각해보니 위스타그램을 처음 구현할 때도 내가 할 수 있을지에 대한 불안함과 걱정은 있었다. 근데 막상 결과를 보면 나는 해냈다. 그렇기 때문에 프로젝트도 나는 할 수 있다는 자신감을 가지고 임하려고 한다.
또한 개발 업계에서는 많은 다른 타 직군과의 커뮤니케이션이 필수적이기 때문에 협업에 대한 중요성이 굉장히 강조되는 직군중에 하나이다. 이러한 협업을 기업에 들어가기 전에 연습 해 볼 수 있어서 굉장히 좋은 기회라고 생각이 되고, 단순히 연습이라는 생각보다는 실전이라는 생각처럼 진지하게 임할 생각이다.

멘토님은 이런 말씀을 해주셨다. 프로젝트 때 팀의 분위기가 굉장히 좋았던 팀은 프로젝트의 결과도 좋았고, 팀의 분위기가 좋지 못했던 팀은 프로젝트의 결과도 상대적으로 좋지 못했다.

따라서 팀의 분위기를 위해, 좋은 결과를 위해 때로는 내 의견을 굽힐 때도 있을 것이며, 꼭 필요한 부분에 대해서는 설득을 할 때도 있을 것이다. 팀의 분위기를 해치지 않는 한에서 토의를, 그리고 필요하다면 팀의 분위기를 좋게 만들기 위해서 나를 버릴 수도 있게 최선을 다 해 달려 볼 생각이다.

나는 어떤 개발자가 되고 싶은가

개발자가 되기 위해 코딩 공부를 하면서 느끼게 된 것이 있다.
'세상이 똑똑한 사람은 너무 많다. 코딩을 잘 하는 사람은 너무나도 많다. 그렇기 때문에 나는 다른 쪽으로 두각을 나타내야겠다.'(개발도 물론 잘해야한다.)
많은 개발자들이 생각보다 커뮤니케이션을 어려워한다고 한다. 타 직군과 커뮤니케이션도 어렵고 같은 개발자끼리도 커뮤니케이션이 안되는 사람이 너무나도 많다더라.
한 개발 커뮤니티에서는 개발을 잘 하는 사람이 아니라 같이 일하고 싶은 사람을 뽑는다는 글도 본 적이 있다. 그 글을 본 이후 목표가 생겼다.

내가 다른 사람에게 일 하자고 찾는 개발자가 아닌,
다른 사람들이 나와 일을 하자고 찾아주는 개발자가 되자.

위의 목표에는 여러가지 뜻을 내포하고있다. 기본적으로 개발자이기 때문에 개발을 못하면 같이 일을 하자고 얘기를 하지 못할것이다. 또한 개발을 잘한다고 해도 대화가 통하지 않는 사람이면 그사람과는 같이 일하고 싶지 않다는 평을 받을것이다.
그렇기 때문에 나는 개발도 잘하며, 커뮤니케이션도, 협업에서도 뒤쳐지지 않아 다른 직군에서도 같은 직군에서도 타인이 먼저 나와 일을 하자고 이야기를 꺼내 줄 수 있는 그런 개발자가 되고 싶다.

0개의 댓글