[Flask] 플라스크로 회원가입 구현하기

2025. 1. 14. 02:19Calender Website

필수 입력, 선택 입력, 프로필 설정의 3단계로 회원가입 시스템을 구현해 보았다.


1. 필수 입력

우선 필수 입력 파트에서는 이메일. 비밀번호. 비밀번호 확인. 휴대폰 번호를 입력받고 유효성 검사를 거친 후 다음 필드인 선택 입력 파트로 넘어간다.


1-1. Email 중복확인

이메일을 입력 받은 후 이메일이 데이터베이스에 저장된 이메일과 중복되는지 확인한다.

<HTML>

               <div class="form-group">
                    <label for="user-email">이메일</label>
                    <div class="email-form">
                        <input type="email" id="user-email" name="user-email" placeholder="Email" required>
                        <button type="button" id="confirm-email">중복 확인</button>
                    </div>
                    <p id="email-result"></p>
                </div>

<Javascript>

우선 이메일 형식이 올바른지 검증한 후, 올바르지 않으면 경고 메세지를 emailResult에 띄우고 함수는 종료한다.

이메일 형식이 올바르면, AXIOS 통신을 통해 Flask로 전송한다. Flask에서 온 응답인 available에 따라 적절한 메세지를 emailResult에 띄운다.

document.getElementById('confirm-email').addEventListener('click', function(e) {
    e.preventDefault();

    const userEmail = document.getElementById('user-email').value;
    const emailResult = document.getElementById('email-result');
    
    // 이메일 형식 검증을 위한 정규표현식
    const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;

    if (!emailRegex.test(userEmail)) {
        emailResult.textContent = '올바른 이메일 형식이 아닙니다';
        emailResult.style.color = 'red';
        return;
    }

    axios.post('/confirm_email', {
        userEmail: userEmail
    })
    .then(function(response) {
        if (response.data.available) {
            emailResult.textContent = "사용 가능한 이메일입니다.";
            emailResult.style.color = 'green';
        }
        else {
            emailResult.textContent = "이미 등록된 이메일입니다.";
            emailResult.style.color = 'red';
        }
    })
    .catch(function(error) {
        console.error('에러 발생: ', error);
    });

});

 

<Flask>

Flask에서는 전달 받은 이메일을 User 테이블과 비교한 후, 일치하는 것이 없으면 available을 False로, 만약 일치하는 이메일이 존재한다면 True로 설정하여 Javascript로 응답을 보낸다.

@api.route('/confirm_email', methods=['POST'])
def confirm_email():
    data = request.json
    user_email = data.get('userEmail')

    user = User.query.filter_by(email=user_email).first()
    if user:
        return jsonify({
            'available' : False
        })
    else:
        return jsonify({
            'available' : True
        })

 

<결과 화면>


1-2. 비밀번호 확인

비밀번호와 비밀번호 확인이 동일한지 확인한다.

<HTML>

                <div class="form-group">
                    <label for="user-pwd">비밀번호</label>
                    <input type="password" name="user-pwd" placeholder="Password" required>
                </div>
                <div class="form-group">
                    <label for="user-repwd">비밀번호 확인</label>
                    <input type="password" name="user-repwd" placeholder="Comfirm password" required>
                    <p id="pwd-result"></p>
                </div>

 

<Javascipt>

입력 필드에 입력이 들어올 때마다 checkPasswordMatch를 실행하여 비밀번호 일치 여부를 확인한다.

- 비밀번호 o, 비밀번호 확인 x                                =>   메세지를 띄우지 않음

- 비밀번호 o, 비밀번호 확인 o, 두 입력값 일치o    =>   비밀번호 일치 메세지를 띄움

- 비밀번호 o, 비밀번호 확인 o, 두 입력값 불일치   =>  비밀번호 불일치 메세지를 띄움

document.addEventListener('DOMContentLoaded', function() {
    const userPwd = document.querySelector('input[name="user-pwd"]');
    const userRepwd = document.querySelector('input[name="user-repwd"]');
    const pwdResult = document.getElementById('pwd-result');

    function checkPasswordMatch() {
        if (userRepwd.value === '') {
            pwdResult.textContent = '';
        }
        else if (userPwd.value === userRepwd.value) {
            pwdResult.textContent = "비밀번호가 일치합니다.";
            pwdResult.style.color = "green";
        }
        else {
            pwdResult.textContent = "비밀번호가 일치하지 않습니다.";
            pwdResult.style.color = "red";
        }
    }

    userPwd.addEventListener('input', checkPasswordMatch);
    userRepwd.addEventListener('input', checkPasswordMatch);
});

 

<결과 화면>


1-3. 휴대폰 번호 유효성 검사

휴대폰 번호 형식이 올바른지 검사한다.

<HTML>

                <div class="form-group">
                    <label for="user-phone">휴대폰 번호</label>
                    <input type="tel" name="user-phone" placeholder="Phone number" required>
                    <p id="phone-result"></p>
                </div>

<Javascript>

비밀번호 유효성 검사와 동일하게 입력이 들어올 때마다 checkPhoneValidate를 실행한다. 미리 검증용 정규표현식을 만든다음 통과하지 않으면 경고 메세지를 띄운다.

document.addEventListener('DOMContentLoaded', function() {
    const phone = document.querySelector('input[name="user-phone"]');
    const phoneResult = document.getElementById('phone-result');

    function checkPhoneValidate() {
        const phoneRegex =  /^(01[016789]{1})[0-9]{3,4}[0-9]{4}$/;

        if (phone.value === '') {
            phoneResult.textContent = '';
        }
        else if (!phoneRegex.test(phone.value)) {
            phoneResult.textContent = "휴대폰 번호를 다시 확인해주세요.";
            phoneResult.style.color = "red";

        }
        else {
            phoneResult.textContent = '사용 가능한 휴대폰 번호입니다.';
            phoneResult.style.color = "green";
        }
    }

    phone.addEventListener('input', checkPhoneValidate);
})

 

<결과 화면>


1-4. 모든 입력이 완료되었는지 확인

필수 입력에 모두 입력되었는지 확인하고 다음 파트인 선택 입력으로 넘어간다.

<HTML>

button에 validateAndShowstep 함수를 달아 다음으로 넘어가기 전에 함수를 실행시켜 입력 필드를 확인한다.

                <div class="button-div">
                    <button type="button" id="comfirm-allfilled" class="step-button" onclick="validateAndShowstep(2)">다음</button>
                </div>
                <p id="warning-message" style="color: red; display: none">모든 필드를 입력해주세요.</p>

<Javascript>

required인 input들을 모두 불러와 forEach로 하나하나 확인한다. 만약 하나라도 입력이 되어있지 않다면 allFilled가 false로 변경되어 경고 메세지가 나타날 것이고, 모두 입력되었다면 showStep을 통해 선택 입력창으로 넘어간다.

function validateAndShowstep(step) {
    const inputs = document.querySelectorAll('#step1 input[required]');
    let allFilled = true;

    inputs.forEach(input => {
        if (input.value.trim() === '') {
            allFilled = false;
        }
    });

    if (allFilled) {
        document.getElementById('warning-message').style.display = 'none';
        showStep(step);
    }
    else {
        document.getElementById('warning-message').style.display = 'block';
    }
}

 

<결과 화면>


2. 선택 입력

2번째 단계인 선택 입력에서는 유저의 생년월일, 관심. 직장. 학교, 위치, 웹사이트를 입력받는다. 굳이 입력안해도 되며 따로 유효성 검사를 거칠 필요 없다.

<HTML>

            <div class="step" id="step2">
                <div class="form-group">
                    <label for="user-birth">생년월일</label>
                    <input type="date" max="9999-12-31" name="user-birth" class="user-birth" placeholder="Birthday">
                </div>
                <div class="form-group">
                    <label for="user-interest">관심</label>
                    <input type="text" name="user-interest" placeholder="Interest">
                </div>
                <div class="form-group">
                    <label for="user-work">직장, 학교</label>
                    <input type="text" name="user-work" placeholder="Work">
                </div>
                <div class="form-group">
                    <label for="user-location">위치</label>
                    <input type="text" name="user-location" placeholder="Location">
                </div>
                <div class="form-group">
                    <label for="user-website">웹사이트</label>
                    <input type="text" name="user-website" placeholder="Website">
                </div>
                <div class="button-div">
                    <button type="button" class="step-button" onclick="showStep(1)">이전</button>
                    <button type="button" class="step-button" onclick="showStep(3)">다음</button>
                </div>
            </div>

<결과 화면>


3. 프로필 설정

프로필 설정에서는 유저의 프로필 이미지, 이름, 설명을 입력받는다. 프로필 이미지를 데이터 베이스에 올리는 방법은 이전 포스팅에서 설명하였다. 이제 submit 버튼을 누르면 폼이 Flask에 제출되어 회원가입이 완료된다.

<HTML>

            <div class="step" id="step3">
                <div class="form-group">
                    <div class="image-div">
                        <img src="{{ url_for('static', filename='images/woman.png') }}" class="profile-image" id="profile-image">
                    </div>
                    <input type="file" id="image-input" name="user-image" style="display: none;" accept="image/*">
                </div>
                <div class="form-group">
                    <label for="user-name">이름</label>
                    <input type="text" name="user-name" placeholder="Name" required>
                </div>
                <div class="form-group">
                    <label for="user-description">설명</label>
                    <input type="text" name="user-description" placeholder="Description">
                </div>
                <div class="button-div">
                    <button type="button" class="step-button" onclick="showStep(2)">이전</button>
                    <button type="submit" class="submit-button">가입하기</button>    
                </div>
            </div>

 

<Javascript>

FormData를 Flask에 AXIOS 통신을 통해 제출한다. 정상적으로 회원가입이 완료되면 환영메세지를 띄운다.

document.addEventListener('DOMContentLoaded', function() {
    document.querySelector('.signup-form').addEventListener('submit', function(e) {
        e.preventDefault();
        const formData = new FormData(this);

        axios.post('/register', formData)
        .then(function(response) {
            document.querySelector('.form-container').style.display = 'none';
            document.querySelector('.welcome-message').style.display = 'block';
            document.getElementById('welcome-name').textContent = response.data.name;
        })
        .catch(function(error) {
            console.error('Error: ', error);
        });
    });
});

 

<Flask>

입력폼에 있는 모든 데이터들을 User 객체의 인자로 전달해준다. 비밀번호는 보안을 위해 generate_password_hash로 해쉬값을 사용해준다.

db.sesson.add를 통해 테이블에 추가해주고 커밋하면 회원가입이 완료된다. user name을 응답으로 전달하여 환영 메세지에 유저 이름이 띄워지도록 해준다.

@api.route('/register', methods=['POST'])
def register():
    user_email = request.form['user-email']
    user_pwd = request.form['user-pwd']
    hashed_password = generate_password_hash(user_pwd) # Hash 생성

    user_phone = request.form['user-phone']

    user_birth = request.form['user-birth']
    if (user_birth == ''):
        user_birth = None
    user_interest = request.form['user-interest']
    user_work = request.form['user-work']
    user_location = request.form['user-location']
    user_website = request.form['user-website']
    
    user_image = request.files['user-image']
    if user_image.filename == '':
        db_file_path = ''
    else:
        file_name = secure_filename(user_image.filename)
        file_path = os.path.join('app/static/images/profile', file_name)
        user_image.save(file_path)
        # db에 저장할 경로는 static 이후부터
        db_file_path = f'images/profile/{file_name}'
        
    user_name = request.form['user-name']
    user_description = request.form['user-description']

    new_user = User(username=user_name,
                    email=user_email,
                    password=hashed_password,
                    phone=user_phone,
                    birth=user_birth,
                    interest=user_interest,
                    work=user_work,
                    location=user_location,
                    website=user_website,
                    description=user_description,
                    image_path=db_file_path)
    
    db.session.add(new_user)
    db.session.commit()

    return jsonify ({
        'name': user_name
    })

 

<결과 화면>


4. 가입 환영 메세지

가입 환영 메세지에는 유저의 이름을 함께 보여주고, 로그인 페이지로 안내한다.

<HTML>

    <div class="welcome-message" style="display: none;">
        <img src="{{ url_for('static', filename='images/logo_v2.png') }}" class="logo-picture">
        <h1>안녕하세요, <span id="welcome-name"></span>님!</h1>
        <p>회원가입이 성공적으로 완료되었습니다.</p>
        <button id="goto-login">로그인 페이지로 이동</button>
    </div>

<Javascript>

document.getElementById('goto-login').addEventListener('click', function() {
    window.location.href = '/';
});

<결과 화면>