PySide6 Model/View 기반 회원관리 프로그램 구현

 

이번 실습에서는 PySide6Model/View 구조를 활용하여 회원가입, 로그인, 메인화면, 프로필 수정 기능을 구현하였다.

핵심은 회원 정보를 각 화면에 따로 저장하지 않고, MemberModel 하나에서 관리하도록 구성한 점이다.

MemberModel
 └── 회원 정보 관리

Login View
 └── 로그인 처리

SignUp View
 └── 회원가입 처리

Main View
 └── 로그인한 회원 정보 출력

Profile View
 └── 회원 정보 수정

1. 프로젝트 구조

프로젝트는 다음과 같이 파일을 나누어 구성하였다.

PythonProject4/
├── main.py
├── main_window.py
├── member_model.py
├── login_view.py
├── sign_up.py
├── main_view.py
├── profile_view.py
└── member.json
파일명 역할
main.py 프로그램 실행 파일
main_window.py 전체 탭 화면 관리
member_model.py 회원 정보 저장, 로그인 검사, 프로필 수정 처리
login_view.py 로그인 화면 구성
sign_up.py 회원가입 화면 구성
main_view.py 로그인한 회원 정보 출력
profile_view.py 회원 정보 수정

2. main.py

main.py는 프로그램 실행만 담당하도록 구성하였다.

import sys
from PySide6.QtWidgets import QApplication
from main_window import MainWindow


if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())

3. MemberModel 구현

MemberModel은 회원 정보를 저장하고 관리하는 Model 클래스이다. 회원가입 시 입력한 정보를 member.json에 저장하고, 로그인 시 입력한 아이디와 비밀번호를 검사하도록 구현하였다.

비밀번호는 그대로 저장하지 않고 hashlib를 사용해 SHA256 Hash 값으로 변환하여 저장하였다.

member_model.py

import json
import hashlib


class MemberModel:

    def __init__(self):
        # 회원 정보를 저장할 JSON 파일명
        self.file_name = "member.json"

        # 현재 로그인한 회원 정보를 저장할 변수
        self.current_member = None

        # 프로그램 시작 시 JSON 파일에서 회원 목록 불러오기
        self.load_members()

    def load_members(self):
        # member.json 파일에 저장된 회원 목록을 불러오는 함수
        try:
            with open(self.file_name, "r", encoding="utf-8") as file:
                self.members = json.load(file)

        except:
            # 파일이 없거나 읽기 실패 시 빈 리스트로 시작
            self.members = []

    def save_members(self):
        # 현재 회원 목록을 member.json 파일에 저장하는 함수
        with open(self.file_name, "w", encoding="utf-8") as file:
            json.dump(
                self.members,
                file,
                ensure_ascii=False,
                indent=4
            )

    def register_member(self, user_id, user_pw, name, email, phone):
        # 이미 존재하는 아이디인지 중복 검사
        for member in self.members:
            if member["id"] == user_id:
                return False

        # 비밀번호를 SHA256 Hash 값으로 변환
        password_hash = hashlib.sha256(
            user_pw.encode()
        ).hexdigest()

        # 새 회원 정보 생성
        new_member = {
            "id": user_id,
            "pw": password_hash,
            "name": name,
            "email": email,
            "phone": phone
        }

        # 회원 목록에 새 회원 추가
        self.members.append(new_member)

        # JSON 파일에 저장
        self.save_members()

        return True

    def check_login(self, input_id, input_pw):
        # 입력한 비밀번호를 Hash 값으로 변환
        password_hash = hashlib.sha256(
            input_pw.encode()
        ).hexdigest()

        # 회원 목록에서 아이디와 비밀번호가 일치하는 회원 찾기
        for member in self.members:
            if member["id"] == input_id and member["pw"] == password_hash:

                # 로그인 성공 시 현재 로그인 회원으로 저장
                self.current_member = member
                return True

        return False

    def get_member_info(self):
        # 현재 로그인한 회원 정보를 반환
        return self.current_member

    def update_profile(self, name, email, phone):
        # 현재 로그인한 회원이 있을 때만 수정
        if self.current_member:
            self.current_member["name"] = name
            self.current_member["email"] = email
            self.current_member["phone"] = phone

            # 수정된 정보를 JSON 파일에 저장
            self.save_members()

4. 회원가입 View 구현

회원가입 View에서는 아이디, 비밀번호, 비밀번호 확인, 이름, 이메일, 전화번호를 입력받는다.

한 칸이라도 빈칸이 있으면 경고창을 출력하고, 비밀번호와 비밀번호 확인 값이 다르면 회원가입이 진행되지 않도록 구현하였다.

sign_up.py

from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QLabel, QLineEdit,
    QPushButton, QMessageBox, QFormLayout
)
from PySide6.QtCore import Qt


class SignUpView(QWidget):

    def __init__(self, member_model, tab_widget, parent=None):
        super().__init__(parent)

        # MemberModel과 QTabWidget 저장
        self.member_model = member_model
        self.tab_widget = tab_widget

        title_label = QLabel("회원가입")
        title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

        self.line_edit_id = QLineEdit()
        self.line_edit_id.setPlaceholderText("아이디를 입력하세요")

        self.line_edit_pw = QLineEdit()
        self.line_edit_pw.setPlaceholderText("비밀번호를 입력하세요")
        self.line_edit_pw.setEchoMode(QLineEdit.EchoMode.Password)

        self.line_edit_pw_check = QLineEdit()
        self.line_edit_pw_check.setPlaceholderText("비밀번호를 다시 입력하세요")
        self.line_edit_pw_check.setEchoMode(QLineEdit.EchoMode.Password)

        self.line_edit_name = QLineEdit()
        self.line_edit_name.setPlaceholderText("이름을 입력하세요")

        self.line_edit_email = QLineEdit()
        self.line_edit_email.setPlaceholderText("이메일을 입력하세요")

        self.line_edit_phone = QLineEdit()
        self.line_edit_phone.setPlaceholderText("전화번호를 입력하세요")

        register_button = QPushButton("가입 완료")
        register_button.clicked.connect(self.register_button_clicked)

        form_layout = QFormLayout()
        form_layout.addRow("아이디 :", self.line_edit_id)
        form_layout.addRow("비밀번호 :", self.line_edit_pw)
        form_layout.addRow("비밀번호 확인 :", self.line_edit_pw_check)
        form_layout.addRow("이름 :", self.line_edit_name)
        form_layout.addRow("이메일 :", self.line_edit_email)
        form_layout.addRow("전화번호 :", self.line_edit_phone)

        layout = QVBoxLayout()
        layout.addStretch()
        layout.addWidget(title_label)
        layout.addLayout(form_layout)
        layout.addWidget(register_button)
        layout.addStretch()

        self.setLayout(layout)

    def register_button_clicked(self):
        # 입력창 값 가져오기
        user_id = self.line_edit_id.text()
        user_pw = self.line_edit_pw.text()
        user_pw_check = self.line_edit_pw_check.text()
        name = self.line_edit_name.text()
        email = self.line_edit_email.text()
        phone = self.line_edit_phone.text()

        # 빈칸 검사
        if (
            user_id.strip() == "" or
            user_pw.strip() == "" or
            user_pw_check.strip() == "" or
            name.strip() == "" or
            email.strip() == "" or
            phone.strip() == ""
        ):
            QMessageBox.warning(
                self,
                "회원가입 실패",
                "빈칸을 채워주세요."
            )
            return

        # 비밀번호 확인 검사
        if user_pw != user_pw_check:
            QMessageBox.warning(
                self,
                "회원가입 실패",
                "비밀번호가 일치하지 않습니다."
            )
            return

        # MemberModel에 회원가입 요청
        result = self.member_model.register_member(
            user_id,
            user_pw,
            name,
            email,
            phone
        )

        # 회원가입 성공
        if result:
            QMessageBox.information(
                self,
                "회원가입 완료",
                "회원가입이 완료되었습니다."
            )

            self.clear_inputs()

            # 회원가입 완료 후 회원가입 탭 비활성화
            self.tab_widget.setTabEnabled(1, False)

            # 로그인 탭으로 이동
            self.tab_widget.setCurrentIndex(0)

        # 중복 아이디인 경우
        else:
            QMessageBox.warning(
                self,
                "회원가입 실패",
                "이미 사용 중인 아이디입니다."
            )

    def clear_inputs(self):
        # 회원가입 입력창 초기화
        self.line_edit_id.clear()
        self.line_edit_pw.clear()
        self.line_edit_pw_check.clear()
        self.line_edit_name.clear()
        self.line_edit_email.clear()
        self.line_edit_phone.clear()

5. Login View 구현

Login View에서는 회원가입한 아이디와 비밀번호로 로그인할 수 있도록 구현하였다. 로그인에 성공하면 메인화면 탭으로 이동하고, 실패하면 경고창을 출력하도록 구성하였다.

login_view.py

from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QPushButton, QLabel,
    QLineEdit, QMessageBox, QFormLayout
)


class Login(QWidget):

    def __init__(self, member_model, tab_widget, main_window):
        super().__init__()

        self.member_model = member_model
        self.tab_widget = tab_widget
        self.main_window = main_window

        title_label = QLabel("회원 로그인")

        self.line_edit_id = QLineEdit()
        self.line_edit_id.setPlaceholderText("아이디를 입력하세요")

        self.line_edit_pw = QLineEdit()
        self.line_edit_pw.setPlaceholderText("비밀번호를 입력하세요")
        self.line_edit_pw.setEchoMode(QLineEdit.EchoMode.Password)

        login_button = QPushButton("로그인")
        login_button.clicked.connect(self.login_button_clicked)

        signup_button = QPushButton("회원가입")
        signup_button.clicked.connect(self.signup_button_clicked)

        form_layout = QFormLayout()
        form_layout.addRow("아이디 :", self.line_edit_id)
        form_layout.addRow("비밀번호 :", self.line_edit_pw)

        layout = QVBoxLayout()
        layout.addWidget(title_label)
        layout.addLayout(form_layout)
        layout.addWidget(login_button)
        layout.addWidget(signup_button)

        self.setLayout(layout)

    def login_button_clicked(self):
        # 로그인 입력값 가져오기
        input_id = self.line_edit_id.text()
        input_pw = self.line_edit_pw.text()

        # MemberModel을 통해 로그인 검사
        if self.member_model.check_login(input_id, input_pw):
            QMessageBox.information(
                self,
                "로그인 성공",
                "로그인에 성공하였습니다."
            )

            # MainWindow의 로그인 성공 처리 함수 실행
            self.main_window.login_success()

        else:
            QMessageBox.warning(
                self,
                "로그인 실패",
                "아이디 또는 비밀번호가 올바르지 않습니다."
            )

            self.line_edit_id.clear()
            self.line_edit_pw.clear()
            self.line_edit_id.setFocus()

    def signup_button_clicked(self):
        # 회원가입 버튼 클릭 시 회원가입 탭 활성화 후 이동
        self.tab_widget.setTabEnabled(1, True)
        self.tab_widget.setCurrentIndex(1)

6. MainWindow 구성

MainWindow에서는 QTabWidget을 사용하여 로그인, 회원가입, 메인화면, 프로필 화면을 구성하였다.

로그인 전에는 메인화면과 프로필 화면에 접근할 수 없도록 제한하고, 회원가입 탭도 회원가입 버튼을 눌렀을 때만 활성화되도록 구현하였다.

main_window.py

from PySide6.QtWidgets import QMainWindow, QTabWidget, QMessageBox

from member_model import MemberModel
from login_view import Login
from sign_up import SignUpView
from main_view import MainView
from profile_view import ProfileView


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        self.setWindowTitle("Model/View 회원관리")
        self.resize(600, 400)

        # 로그인 상태 저장
        self.is_login = False

        # MemberModel 생성
        self.member_model = MemberModel()

        # 탭 위젯 생성
        self.tab_widget = QTabWidget()
        self.setCentralWidget(self.tab_widget)

        self.main_tab = MainView(self.member_model)

        self.profile_tab = ProfileView(
            self.member_model,
            self.main_tab,
            self.tab_widget
        )

        self.sign_up_tab = SignUpView(
            self.member_model,
            self.tab_widget
        )

        self.login_tab = Login(
            self.member_model,
            self.tab_widget,
            self
        )

        self.tab_widget.addTab(self.login_tab, "로그인")
        self.tab_widget.addTab(self.sign_up_tab, "회원가입")
        self.tab_widget.addTab(self.main_tab, "메인화면")
        self.tab_widget.addTab(self.profile_tab, "프로필")

        # 처음에는 회원가입 탭 비활성화
        self.tab_widget.setTabEnabled(1, False)

        # 탭 이동 시 접근 권한 검사
        self.tab_widget.currentChanged.connect(
            self.check_tab_access
        )

        self.tab_widget.setCurrentIndex(0)

    def check_tab_access(self, index):
        # 로그인하지 않은 상태에서 메인화면/프로필 접근 제한
        if self.is_login == False and index not in [0, 1]:

            QMessageBox.warning(
                self,
                "접근 제한",
                "로그인을 먼저 해주세요."
            )

            self.tab_widget.setCurrentIndex(0)

    def login_success(self):
        # 로그인 상태 변경
        self.is_login = True

        # 메인화면과 프로필 화면 갱신
        self.main_tab.update_view()
        self.profile_tab.update_view()

        # 메인화면 탭으로 이동
        self.tab_widget.setCurrentIndex(2)

7. JSON 파일 저장

 

회원 정보를 저장하는 JSON 파일 구성

회원가입 시 입력한 회원 정보를 저장하며 프로그램 재실행 후에도 회원 정보를 유지하도록 구현

 

( 파일 버튼 클릭 후 -> 원하는 파일명.JSON -> [ ] 입력해줘야 안에 내용이 입력이 됨.

 

 

8. 구현 결과 정리

  • 회원가입 탭을 추가하였다.
  • 아이디, 비밀번호, 비밀번호 확인, 이름, 이메일, 전화번호를 입력받도록 구현하였다.
  • 빈칸이 있을 경우 경고창을 출력하도록 구현하였다.
  • 비밀번호와 비밀번호 확인 값이 다르면 회원가입이 되지 않도록 구현하였다.
  • 중복 아이디는 가입할 수 없도록 구현하였다.
  • 비밀번호는 Hash 값으로 변환하여 저장하였다.
  • 회원 정보는 member.json에 저장되도록 구현하였다.
  • 회원가입한 아이디와 비밀번호로 로그인할 수 있도록 구현하였다.
  • 로그인 성공 시 현재 로그인한 회원 정보가 메인화면과 프로필 화면에 출력되도록 구현하였다.
  • 프로필 수정 시 현재 로그인한 회원 정보만 변경되도록 구현하였다.

회원 정보는 View마다 따로 저장하지 않고, MemberModel에서 관리하도록 구현하였다.

회원가입, 로그인, 프로필 수정 기능이 모두 하나의 Model을 기준으로 동작하도록 구성하였다.

 

 

스크린캐스트 2026-06-08 18-57-58.webm
4.10MB