⭐️ 장고 게시판(프로젝트생성, 회원가입)

팔리동·2021년 7월 13일
1

장고 프로젝트

목록 보기
1/2
  • 장고 폼과 유저모델을 사용하지 않고 회원가입 로그인 로그아웃 기능을 구현해보자.

폼과 유저 모델을 안 쓰는 이유

지금까지 장고 온라인 강의를 들으면서 만든 것 들은 대부분 클래스 기반 뷰와 장고 유저 모델 폼 들을 활용해서 웹 어플리케이션을 만들었다.
장고의 장점이 이미 만들어진 것(폼, 유저모델, 클래스 뷰)을 바로 웹어플리케이션을 만드는데 적용해서 빠르게 빌드할 수 있는 장점이다.
하지만 나는 이 유용한 도구들이 정확히 내부에 어떤 로직으로 이루어져 있는지 잘 모르고 왜 이런 도구들이 만들어 졌는지 알 수 없다.(필요성이 느껴서 도구를 배운게 아니라 처음부터 도구를 배웠기 때문)
그래서 최대한 이런 도구들을 안쓰고 게시판을 빌드해볼 것 이다.
우선 회원가입을 구현해보겠다.

무엇을 만들 것인가?

어제 밤에 멍때리다가 좋은 생각이 떠올랐다.
바로 개발자 취직을 준비하는 사람이 사이드 프로젝트를 할 때 팀원을 구하는 게시판이다!
(인프런에 이미 이런 게시판이 있는건 안 비밀)
인프런에 있다는 사실을 알기 전까지 이 프로젝트가 커져서 서버비용은 어떻게 감당할까 고민하는 달콤한 상상을 했다.
제삿밥에는 관심을 덜고 다시 원래의 목적으로 돌아가서 만들어보자.

프로젝트 생성

  • 장고 프로젝트 지금까지 정말 많이 생성했지만 매번 구글에 '파이썬 가상환경 만들기' 검색해서
    생성하고 프로젝트 생성 명령어를 자꾸 까먹어서 이참에 정리해본다.
mkdir 프로젝트명 
cd 프로젝트명 (이하  platform)
python3 -m venv '가상환경명'(이하 venv) 
source ./venv/bin/activate
pip install django
django-admin startproject '프로젝트명'
python manage.py startapp '앱명'
  • 까먹지 말자!!!

설정 변경

  • static 파일설정과 template 파일 설정을 해준다.
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'myplatform', 'static')
]
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

참고 링크: https://ssungkang.tistory.com/entry/Django-static-%ED%8C%8C%EC%9D%BC-%EB%8B%A4%EB%A3%A8%EA%B8%B0

유저 모델

  • 유저모델을 생성하기까지 큰 고민이 많았다.
    https://wikidocs.net/6651 -> 유저 모델 생성 4가지 방법 비교
  • 나의 결정은 장고의 그 어떤 인증절차도 사용하지 않고 User모델도 상속받지 않고 쌩으로 테이블을 만드는 방식을 선택했다.
  • 유저 생성, 인증 여부, 로그인 등을 전부 구현할 것 이다.
class Accounts(models.Model):
    user_id = models.CharField(
        max_length=128,
        unique=True,
    )
    email = models.EmailField(
        max_length=128,
    )
    password = models.CharField(
        max_length=2048,
    )
    created = models.DateTimeField(
        auto_now_add=True
        )
    class Meta:
        verbose_name = '계정'
        verbose_name_plural = '계정'
        ordering = ['created', ]

    def __str__(self):
        return self.user_id
  • 일단 이름을 User라고 짓지 않고 Accounts로 지었다.
    계정에 관련된 정보만 받을 거라서 Accounts로 정했다.
    프로필은 따로 만들고 One to One으로 연결할 에정이다. (하지만 만들 때 이걸 고려 하지 않아서 프로필 모델 만들 때 두고보자.)

  • 지금 글을 정리하면서 떠오른 생각인데 미리 데이터 베이스 설계를 하고 만들었어야 했는데 유저 모델에 너무 심취해서 잊어버렸다.
    하지만 데이터베이스를 미리 설계하고 만들어야 한다는 사실을 깨닫게 됐으니 개이득이다.
    점점 프로젝트를 진행하면서 이 문제가 어떻게 되는지 지켜보면서 베울 점이 많을 것 같다.

각 필드의 제한사항

  1. user_id = models.CharField( max_length=128, unique=True,)
    어떤 미친놈이 아이디를 128자 이상으로 사용하진 않을거라서 128자로 제한을 뒀다.
    아이디 값은 중복되면 혼동이 있기 때문에 유니크 옵션을 True로 줬다.

  2. email = models.EmailField( max_length=128, )
    이메일도 어지간해서 128자를 넘을리가 없으므로 128자로 했다.
    중복을 막으려고 했으나 나중에 소셜 로그인을 구현했을 때 어떻게 될지 몰라서 일단 두었다.

  3. password = models.CharField( max_length=2048, )
    비밀번호는 제한을 길게 할수록 좋다고 들어서 2048자로 했다. 세상에 비밀번호를 2048자로 하는 사람이 있을까 싶지만 강력한 암호로 만들 수 있으니까 최대한 길게 해주었다.

  4. created = models.DateTimeField( auto_now_add=True )
    계정을 생성한 날짜는 필요하니까 넣었다. 나중에 디비 쿼리를 날릴 때 기준을 삼기 위해 심은것도 있다.


🐟 회원가입

입력값 제한 중 일부는 프론트단에서 하기로 했다.
백엔드 단에서 전부 처리 할 수 있지만 번거롭기 때문에 input태그 옵션으로 막아주었다.

<form action="{% url 'login' %}" method="POST">
        {% csrf_token %}
        <span class="login_form_text">아이디</span>
        <input type="text" name="user_id" minlength="4" maxlength="128">
        <span class="login_form_text">비밀번호</span>
        <input type="password" name="password" minlength="8" maxlength="2048">      
        <button type="submit">로그인</button>
        <span>{{error}}</span>
    </form>

최소 비밀번호는 4자 최대는128 자로 설정했다. 모델 필드의 최대개수와 동일하게 설정했다.
비밀번호는 최소4자 최대128자로 했다.
생각해보니 비밀번호 최소 숫자를 더 늘렸어야 하는데 내가 직접 테스트 할때 길면 까먹기 때문에 일단 개발할 때는 4자로 제한해둔다.

signup view

def signup(request):
    if request.method == 'GET':
        return render(request, 'accounts/signup.html')

    elif request.method == 'POST':
        res_data = {}
        useremail = request.POST.get('email', None)
        userid = request.POST.get('user_id', None)
        password = request.POST.get('password', None)
        re_password = request.POST.get('password2', None)
        
        if Accounts.objects.filter(user_id=userid).exists():
            res_data['error'] = '이미 등록된 아이디입니다.'
            return render(request, 'accounts/signup.html', res_data) 

        if password==re_password:
            with transaction.atomic():
                accounts= Accounts(
                    user_id = userid,
                    password = make_password(password),
                    email = useremail,
                )
                accounts.save()
                return redirect('login')

        elif request.POST['password']!= request.POST['password2']:
            res_data['error'] = '비밀번호가 다릅니다.'
            return render(request, 'accounts/signup.html', res_data) 
            

GET요청이면 html을 뿌리고
POST요청이면 받아온 입력값을 검사하고 조건을 통과하면 데이터 베이스에 저장시키고 로그인 페이지로 리다이렉트 시킨다.

입력값 필터링 실패

아니나 다를까 입력값을 이메일 하나만 입력하고 서브밋을 누르니 빈값으로 데이터가 저장됐다.
다시 빈값으로 저장하려면 아이디가 중복이라고 막힌다.
정말 끔찍하다.
결론은 인풋태그로 입력값을 막고 백엔드 단에서 필터링을 안하면 큰일이 난다.

수정

def signup(request):
    if request.method == 'GET':
        return render(request, 'accounts/signup.html')

    elif request.method == 'POST':
        res_data = {}
        useremail = request.POST.get('email', None)
        userid = request.POST.get('user_id', None)
        password = request.POST.get('password', None)
        re_password = request.POST.get('password2', None)
        
        if len(userid) > 128 or len(userid) < 6:
            res_data['error'] = '아이디는 6자이상 128자 이하로 만들어야 합니다.'
            return render(request, 'accounts/signup.html', res_data) 
            
        if not (useremail and userid and password and re_password):
            res_data['error'] = '모든 값을 입력해주세요.'
            return render(request, 'accounts/signup.html', res_data) 

        if Accounts.objects.filter(user_id=userid).exists():
            res_data['error'] = '이미 등록된 아이디입니다.'
            return render(request, 'accounts/signup.html', res_data) 
        
        if password != re_password:
            res_data['error'] = '비밀번호가 다릅니다.'
            return render(request, 'accounts/signup.html', res_data) 
        
        if len(password) > 2048 or len(password) < 8:
            res_data['error'] = '비밀번호는 8자 이상 2048자 이하여야 합니다.'
            return render(request, 'accounts/signup.html', res_data) 
 
        elif password==re_password:
            with transaction.atomic():
                accounts= Accounts(
                    user_id = userid,
                    password = make_password(password),
                    email = useremail,
                )
                accounts.save()
                return redirect('login')

입력값 개수, 빈값, 중복 아이디, 비밀번호 확인 실패의 제한 사항을 전부 조건별로 구현했다.
아 이정도까지 해야겠어 생각했지만 129자를 넘게 치면 128자로 짤려서 저장이 되는 걸 확인해서 조건을 안세울 수 없었다.
정말 서비스를 한다고 생각하고 만들다 보니 이런 작은 부분도 신경쓰게 된다.

회원가입 로직을 만들면서 느낀 건 모든 케이스를 일일히 서버돌리고 홈페이지 들어가서 입력해보는 것이 너무 시간이 많이 걸리고 불편했다.

이래서 테스트 코드를 작성하는건가 싶다. 장고 테스트 코드에 대해서 얼른 배워야겠다.


🍎 로그인

다음 포스팅

profile
배움의 기록

0개의 댓글