1. 스타일 시트 적용

import sys

from PySide6.QtWidgets import QApplication

from widgets import main_window

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyleSheet("TableView { border: 1px solid; font-size: 15px; margin: 0px; padding: 0px; }"
                      "QHeaderView::section:horizontal {"
                      " font-size: 15px;"
                      " font-weight: bold;"
                      " border: 1px solid;"
                      " margin: 0px;"
                      " border-bottom: 2px solid "
                      "}"
                      "QHeaderView::section:horizontal:last {"
                      " font-size: 15px;"
                      " font-weight: bold;"
                      " border: 1px solid;"
                      " margin: 0px;"
                      " border-bottom: 2px solid;"
                      " border-right: 2px solid }"
                      "QHeaderView::section:vertical {"
                      " font-size: 15px;"
                      " font-weight: bold;"
                      " border: 1px solid;"
                      " margin: 0px;"
                      " border-right: 2px solid }"
                      "QTableCornerButton::section {"
                      " border: 1px solid;"
                      " margin: 0px;"
                      "}"
                      )
    screen = main_window.MainWindow()
    sys.exit(app.exec())
  • main.py 에서 QApplication 인스턴스에 위와 같이 스타일시트를 설정하여 앱 내부의 해당 위젯 전체에 적용

 

 

2. 헤더 크기 조정

# 일일 정산서 계산서 위젯
class DayCal(QWidget):
    # 생성자
    def __init__(self):
        super().__init__()
        # 화주명단, 화주별 데이터, 기타 데이터, 결과 데이터를 DB 에서 읽어온다
        self.owner_list = actions.get_daycal_owner_list()
        self.owner_values = actions.get_daycal_owner_values()
        self.other_values = actions.get_daycal_other_values()
        self.result = actions.get_daycal_result()

        # 입력 테이블 생성(화주별 데이터)
        self.input_table = TableView(0)
        self.data_model = DayCalTableModel(self, actions.get_daycal_owner_list(), actions.get_daycal_owner_values())
        self.input_table.setModel(self.data_model)
        self.input_table.verticalHeader().setMinimumWidth(170)

        # 기타 테이블 생성(기타 데이터)
        self.other_table = TableView(1)
        self.other_data_model = DayCalOthersTableModel(self, actions.get_daycal_other_values())
        self.other_table.setModel(self.other_data_model)
        self.other_table.verticalHeader().setMinimumWidth(170)
        self.other_table.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)

        # 결과 테이블 생성
        self.result_table = TableView(2)
        self.result_data_model = DayCalResultTableModel(self, actions.get_daycal_result())
        self.result_table.setModel(self.result_data_model)
        self.result_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        self.init_ui()

	''' 생략 '''
  • input_table과 other_table에 대해 각각 setMinimumWidth 를 호출하여 수평 헤더의 너비를 동일하게 맞춤

  • other_table, result_table에 대해 각각 setSectionResizeMode(QHeaderView.Stretch) 를 호출하여
    오른쪽, 아래쪽으로 남는공간이 없도록 헤더 사이즈를 맞춘다.

  • 같은 작업을 DayCalQueryResult 위젯에서도 적용하여 과거 데이터 조회시에도 같은 형태가 되도록 한다.

 

디자인 변경 전

 

디자인 변경 후

위 작업들을 마치면 위와 같이 테이블의 디자인이 변경된 것을 확인할 수 있다.

'개인 프로젝트 > Accounting Program' 카테고리의 다른 글

#20 데이터 조회 - 수정기능 구현  (0) 2021.12.07
#19 데이터 조회기능 구현  (0) 2021.12.01
#18 저장기능 구현  (0) 2021.11.25
#17 QTableView  (0) 2021.11.22
#16 화주 삭제, 화주 이름 변경  (0) 2021.11.02

1. DayCalQueryResult 위젯 수정

# 날짜로 조회한 데이터를 표시하기 위한 위젯
# 기본적으로 DayCal 위젯과 거의 동일한 구조를 가짐
class DayCalQueryResult(QDialog):
    submitted = Signal()

    # 생성자
    def __init__(self, parent, owner_values, other_values, result, today):
        super().__init__(parent)
        # 화주별 데이터, 기타 데이터, 결과 데이터, 조회날짜를 인자로 받아온다
        self.owner_values = owner_values
        self.other_values = other_values
        self.result = result
        self.save = QPushButton('저장')
        self.save.clicked.connect(lambda: self.submitted.emit())
        self.today = today

        # 화주별 데이터를 기반으로 임시 화주 명단을 생성
        self.owner_list = []
        for values in self.owner_values:
            self.owner_list.append(DayCalOwner(values.get_owner_name(), values.get_owner_type(), values.get_owner_id()))

        # 입력 테이블 생성(화주별 데이터)
        self.input_table = TableView()
        self.data_model = DayCalTableModel(self, self.owner_list, self.owner_values)
        self.input_table.setModel(self.data_model)

        # 기타 테이블 생성(기타 데이터)
        self.other_table = TableView()
        self.other_data_model = DayCalOthersTableModel(self, self.other_values)
        self.other_table.setModel(self.other_data_model)

        # 결과 테이블 생성
        self.result_table = TableView()
        self.result_data_model = DayCalResultTableModel(self, self.result)
        self.result_table.setModel(self.result_data_model)

        self.init_ui()

    # ui 초기화
    def init_ui(self):
        # 그리드 레이아웃
        grid = QGridLayout()

        # 테이블위젯 추가
        grid.addWidget(self.input_table, 0, 0)
        grid.addWidget(self.other_table, 1, 0)
        grid.addWidget(self.result_table, 0, 1, 2, 1)
        grid.addWidget(self.save, 2, 1)
        grid.setRowStretch(0, 5)
        grid.setColumnStretch(0, 5)

        # 레이아웃 세팅
        self.setLayout(grid)

        # 초기 윈도우사이즈 설정
        self.setGeometry(100, 100, 1446, 620)

        # 스타일 설정
        self.setStyleSheet("background-color: #FFFFFF")

        # 타이틀 설정
        self.setWindowTitle("일일정산서 계산서: " + self.today)
  • save 버튼을 생성, 클릭시 submitted 시그널을 발생시키도록 설정
  • 버튼을 레이아웃에 추가

 

 

2. date_query 함수 수정

# 날짜로 데이터 조회
def date_query(parent, tab):
    date_select = DateSelect(parent)
    date_select.show_modal()

    if not date_select.canceled:
        if tab == 0:
            today = date_select.calendar.selectedDate().toString('yyyy-MM-dd')
            result = DayCalQueryResult(parent, get_daycal_owner_values(today), get_daycal_other_values(today), get_daycal_result(today), today)
            result.submitted.connect(save)
            result.show()
  • date_query 함수에서 DayCalQueryResult 위젯을 생성했을 때 submitted 시그널에 save 함수를 연결
  • save 함수에서는 db 세션에 커밋을 실행하여 변경된 데이터를 저장

 

위 두가지 작업을 수행하는 것 만으로 간단하게 조회한 과거 데이터에 대한 수정기능을 구현할 수 있다.

다만, 화주의 경우 과거 데이터의 화주는 현재 화주 리스트 테이블과 일치하지 않기때문에 당연히 수정할 수 없다.

'개인 프로젝트 > Accounting Program' 카테고리의 다른 글

#21 디자인 변경  (0) 2021.12.20
#19 데이터 조회기능 구현  (0) 2021.12.01
#18 저장기능 구현  (0) 2021.11.25
#17 QTableView  (0) 2021.11.22
#16 화주 삭제, 화주 이름 변경  (0) 2021.11.02

이번에는 달력 위젯에서 날짜를 선택하여 해당 날짜의 데이터를 조회하는 기능을 구현한다. 

 

1. DayCalOwnerValues 모델 변경

# 화주별 일일정산 데이터 모델
class DayCalOwnerValues(Base):
    __tablename__ = 'daycal_owner_values'
    date = Column(Date, primary_key=True)
    owner_id = Column(Integer, primary_key=True)
    owner_name = Column(String, nullable=False)
    owner_type = Column(Integer, nullable=False)
    kd_total = Column(Integer, nullable=False)
    kd_fare = Column(Integer, nullable=False)
    kd_drop = Column(Integer, nullable=False)
    kd_fee4 = Column(Integer, nullable=False)
    after_deduction = Column(Integer, nullable=False)
    match_fee5 = Column(Integer, nullable=False)
    owner_fare = Column(Integer, nullable=False)
    owner_drop = Column(Integer, nullable=False)
    listing_fee4 = Column(Integer, nullable=False)
    kd_pre = Column(Integer, nullable=False)
    deduction_total = Column(Integer, nullable=False)
    total_include_pre = Column(Integer, nullable=False)

    def __init__(self, date, owner_id, owner_name, owner_type):
        self.date = date
        self.owner_id = owner_id
        self.owner_name = owner_name
        self.owner_type = owner_type
        self.kd_total = 0
        self.kd_fare = 0
        self.kd_drop = 0
        self.kd_fee4 = 0
        self.after_deduction = 0
        self.match_fee5 = 0
        self.owner_fare = 0
        self.owner_drop = 0
        self.listing_fee4 = 0
        self.kd_pre = 0
        self.deduction_total = 0
        self.total_include_pre = 0

    def get(self, idx):
        if idx == 0:
            return self.kd_total
        elif idx == 1:
            return self.kd_fare
        elif idx == 2:
            return self.kd_drop
        elif idx == 3:
            return self.kd_fee4
        elif idx == 4:
            return self.after_deduction
        elif idx == 5:
            return self.match_fee5
        elif idx == 6:
            return self.owner_fare
        elif idx == 7:
            return self.owner_drop
        elif idx == 8:
            return self.listing_fee4
        elif idx == 9:
            return self.kd_pre
        elif idx == 10:
            return self.deduction_total
        elif idx == 11:
            return self.total_include_pre

    def set(self, idx, val):
        if idx == 0:
            self.kd_total = val
        elif idx == 1:
            self.kd_fare = val
        elif idx == 2:
            self.kd_drop = val
        elif idx == 3:
            self.kd_fee4 = val
        elif idx == 4:
            self.after_deduction = val
        elif idx == 5:
            self.match_fee5 = val
        elif idx == 6:
            self.owner_fare = val
        elif idx == 7:
            self.owner_drop = val
        elif idx == 8:
            self.listing_fee4 = val
        elif idx == 9:
            self.kd_pre = val
        elif idx == 10:
            self.deduction_total = val
        elif idx == 11:
            self.total_include_pre = val

    def get_owner_id(self):
        return self.owner_id

    def get_owner_name(self):
        return self.owner_name

    def get_owner_type(self):
        return self.owner_type
  • 예전 데이터를 가져올 경우 지금은 삭제된 화주가 그 당시 데이터에는 존재할 수도 있기 때문에
    화주 리스트를 DayCalOwnerValues 리스트를 토대로 임시로 생성해야한다.

  • 그러기 위해서는 DayCalOwner 객체의 정보가 DayCalOwnerValues 에도 존재해야하기 때문에
    DayCalOwnerValues 모델에 owner_type 필드를 추가하고 생성자도 수정해준다.

  • 물론 해당 모델의 인스턴스를 생성하는 메소드에서도 모두 매개변수로 화주 타입을 넘겨주도록 수정한다.

 

 

2. TimeLabel 위젯 수정

# 시간레이블 클래스(QLabel 상속)
# 1초마다 현재 날짜/시간을 갱신하여 표시하는 레이블
class TimeLabel(QLabel):
    clicked = Signal()

    def __init__(self):
        super().__init__()
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.timeout)
        self.timer.start(100)
        self.init_ui()

    def init_ui(self):
        self.setText(QDateTime.currentDateTime().toString('yyyy년 MM월 dd일 ddd hh:mm:ss'))

    def timeout(self):
        self.setText(QDateTime.currentDateTime().toString('yyyy년 MM월 dd일 ddd hh:mm:ss'))

    def mouseReleaseEvent(self, ev: QMouseEvent) -> None:
        self.clicked.emit()
  • 상태표시줄에 표시된 날짜/시간 부분을 클릭하면 데이터를 조회할 날짜를 선택하기 위한 달력 위젯이 표시되도록
    하기 위해 먼저 TimeLabel의 mouseReleaseEvent 를 오버라이드하여 clicked 시그널을 구현해준다.

 

 

3. MainWindow 수정

# 시간 레이블 추가
time_label = TimeLabel()
time_label.clicked.connect(lambda: actions.date_query(self, self.central_widget.get_selected_tab()))
status_bar.addPermanentWidget(time_label)
  • MainWindow의 time_label을 상태표시줄에 추가하는 부분에서 time_label의 clicked 시그널에 date_query를
    호출하는 람다함수를 연결해준다.  이 때, central_widget의 get_selected_tab 을 호출하여 현재 선택된 탭 정보를
    받아와 매개변수로 넘겨준다. 

 

 

4. CentralWidget 수정

# 중앙 위젯
class CentralWidget(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.doc_tab = DocTab()
        self.doc_tab.setParent(self)
        self.init_ui()

    def init_ui(self):
        self.setStyleSheet("background-color: #FFFFFF")

        # 그리드 레이아웃
        grid = QGridLayout()

        # 탭 위젯 추가
        grid.addWidget(self.doc_tab, 0, 0)

        # 중앙 위젯에 그리드 레이아웃 적용
        self.setLayout(grid)

    def get_selected_tab(self):
        cur = self.doc_tab.currentWidget()
        return 0 if cur == self.doc_tab.tab1 else 1 if self.doc_tab.tab2 else 2
  • CentralWidget에 선택된 탭이 몇번 탭인지 반환하는 get_selected_tab 메소드를 구현한다

  • tab1이라면 0, tab2라면 1, tab3라면 2를 반환한다.

 

 

5. date_query 함수 작성

# 날짜로 데이터 조회
def date_query(parent, tab):
    date_select = DateSelect(parent)
    date_select.show_modal()

    if not date_select.canceled:
        if tab == 0:
            today = date_select.calendar.selectedDate().toString('yyyy-MM-dd')
            result = DayCalQueryResult(parent, get_daycal_owner_values(today), get_daycal_other_values(today), get_daycal_result(today), today)
            result.show()
  • 호출시에 매개변수로 전달받은 parent 를 부모 위젯으로 하여 DataSelect 다이얼로그를 생성, modal하게 표시한다
    => DataSelect 는 달력 위젯을 사용하여 유저가 선택한 날짜를 가져오기 위한 위젯이다.
  • DataSelect 다이얼로그가 정상적으로 제출되었을 경우 받아온 날짜를 기준으로 DayCalOwnerValues, 
    DayCalOtherValues, DayCalResult 정보를 DB에서 읽어온다.

  • 읽어온 정보를 기반으로 DayCalQueryResult 위젯을 생성하여 표시한다.
    => DayCalQueryResult 는 조회된 데이터를 테이블로 나타내주기 위한 위젯이다.

 

 

6. DataSelect 다이얼로그 구현

# 날짜로 데이터 조회를 위한 달력 위젯
# 다이얼로그(QDialog 상속)
class DateSelect(QDialog):
    def __init__(self, parent):
        super().__init__(parent)
        self.calendar = QCalendarWidget()
        self.submit = QPushButton('조회')
        self.submit.clicked.connect(self.success)
        self.canceled = True
        self.init_ui()

    def init_ui(self):
        grid = QGridLayout()
        grid.addWidget(self.calendar, 0, 0, 1, 3)
        grid.addWidget(self.submit, 1, 0, 1, 1)
        self.setLayout(grid)

    def show_modal(self):
        return super().exec_()

    def success(self):
        self.canceled = False
        self.close()
  • QDialog 를 상속하여 구현
  • 날짜선택을 위한 달력(QCalendarWidget)과 선택된 날짜정보를 제출하기 위한 버튼(QPushButton)을 가진 위젯.

 

 

7. DayCalQueryResult 구현

# 날짜로 조회한 데이터를 표시하기 위한 위젯
# 기본적으로 DayCal 위젯과 거의 동일한 구조를 가짐
class DayCalQueryResult(QDialog):
    # 생성자
    def __init__(self, parent, owner_values, other_values, result, today):
        super().__init__(parent)
        # 화주별 데이터, 기타 데이터, 결과 데이터, 조회날짜를 인자로 받아온다
        self.owner_values = owner_values
        self.other_values = other_values
        self.result = result
        self.today = today

        # 화주별 데이터를 기반으로 임시 화주 명단을 생성
        self.owner_list = []
        for values in self.owner_values:
            self.owner_list.append(DayCalOwner(values.get_owner_name(), values.get_owner_type(), values.get_owner_id()))

        # 입력 테이블 생성(화주별 데이터)
        self.input_table = TableView()
        self.data_model = DayCalTableModel(self, self.owner_list, self.owner_values)
        self.input_table.setModel(self.data_model)

        # 기타 테이블 생성(기타 데이터)
        self.other_table = TableView()
        self.other_data_model = DayCalOthersTableModel(self, self.other_values)
        self.other_table.setModel(self.other_data_model)

        # 결과 테이블 생성
        self.result_table = TableView()
        self.result_data_model = DayCalResultTableModel(self, self.result)
        self.result_table.setModel(self.result_data_model)

        self.init_ui()

    # ui 초기화
    def init_ui(self):
        # 그리드 레이아웃
        grid = QGridLayout()

        # 테이블위젯 추가
        grid.addWidget(self.input_table, 0, 0)
        grid.addWidget(self.other_table, 1, 0)
        grid.addWidget(self.result_table, 0, 1, 2, 1)
        grid.setRowStretch(0, 5)
        grid.setColumnStretch(0, 5)

        # 레이아웃 세팅
        self.setLayout(grid)

        # 초기 윈도우사이즈 설정
        self.setGeometry(100, 100, 1446, 620)

        # 스타일 설정
        self.setStyleSheet("background-color: #FFFFFF")

        # 타이틀 설정
        self.setWindowTitle("일일정산서 계산서: " + self.today)
  • 날짜로 조회한 데이터를 테이블의 형태로 표시해주기 위한 위젯

  • 기본적으로 DayCal 위젯과 거의 동일한 구조를 가진다.

  • 다른 점이라면 과거의 데이터를 가져오는 것이기 때문에 화주를 추가/삭제할 일이 없으며
    이미 삭제된 화주나 추가된 화주는 여기에 영향을 미치지 않는다

  • 테이블 모델에서 필요로하는 owner_list는 owner_values를 기반으로 임시로 생성한다.

 

 

전날의 데이터를 조회하면 테이블 형태로 어제의 데이터가 표시되는 것을 볼 수 있다. 
화주2는 오늘 추가되었기 때문에 전날의 데이터에는 나타나지 않는다.

'개인 프로젝트 > Accounting Program' 카테고리의 다른 글

#21 디자인 변경  (0) 2021.12.20
#20 데이터 조회 - 수정기능 구현  (0) 2021.12.07
#18 저장기능 구현  (0) 2021.11.25
#17 QTableView  (0) 2021.11.22
#16 화주 삭제, 화주 이름 변경  (0) 2021.11.02

1. 데이터 관리방식 변경

  • 기존의 테이블 모델은 DB의 쿼리 결과로 얻은 객체를 리스트형태로 가져와 2차원 리스트 형태로 관리
  • 인덱싱은 편하지만 모델을 사용하는 의미가 희박해지고 수정사항을 DB에 반영하는 것도 비효율적

  • 모델에 getter, setter 추가
    # 화주별 일일정산 데이터 모델
    class DayCalOwnerValues(Base):
        __tablename__ = 'daycal_owner_values'
        date = Column(Date, primary_key=True)
        owner_id = Column(Integer, primary_key=True)
        owner_name = Column(String, nullable=False)
        kd_total = Column(Integer, nullable=False)
        kd_fare = Column(Integer, nullable=False)
        kd_drop = Column(Integer, nullable=False)
        kd_fee4 = Column(Integer, nullable=False)
        after_deduction = Column(Integer, nullable=False)
        match_fee5 = Column(Integer, nullable=False)
        owner_fare = Column(Integer, nullable=False)
        owner_drop = Column(Integer, nullable=False)
        listing_fee4 = Column(Integer, nullable=False)
        kd_pre = Column(Integer, nullable=False)
        deduction_total = Column(Integer, nullable=False)
        total_include_pre = Column(Integer, nullable=False)
    
        def __init__(self, date, owner_id, owner_name):
            self.date = date
            self.owner_id = owner_id
            self.owner_name = owner_name
            self.kd_total = 0
            self.kd_fare = 0
            self.kd_drop = 0
            self.kd_fee4 = 0
            self.after_deduction = 0
            self.match_fee5 = 0
            self.owner_fare = 0
            self.owner_drop = 0
            self.listing_fee4 = 0
            self.kd_pre = 0
            self.deduction_total = 0
            self.total_include_pre = 0
    
        def get(self, idx):
            if idx == 0:
                return self.kd_total
            elif idx == 1:
                return self.kd_fare
            elif idx == 2:
                return self.kd_drop
            elif idx == 3:
                return self.kd_fee4
            elif idx == 4:
                return self.after_deduction
            elif idx == 5:
                return self.match_fee5
            elif idx == 6:
                return self.owner_fare
            elif idx == 7:
                return self.owner_drop
            elif idx == 8:
                return self.listing_fee4
            elif idx == 9:
                return self.kd_pre
            elif idx == 10:
                return self.deduction_total
            elif idx == 11:
                return self.total_include_pre
    
        def set(self, idx, val):
            if idx == 0:
                self.kd_total = val
            elif idx == 1:
                self.kd_fare = val
            elif idx == 2:
                self.kd_drop = val
            elif idx == 3:
                self.kd_fee4 = val
            elif idx == 4:
                self.after_deduction = val
            elif idx == 5:
                self.match_fee5 = val
            elif idx == 6:
                self.owner_fare = val
            elif idx == 7:
                self.owner_drop = val
            elif idx == 8:
                self.listing_fee4 = val
            elif idx == 9:
                self.kd_pre = val
            elif idx == 10:
                self.deduction_total = val
            elif idx == 11:
                self.total_include_pre = val​
     
    • 모든 모델 클래스에 get, set 메소드를 위와같이 추가. 
    • 테이블 모델에서의 참조를 용이하게 하기 위해서 위 처럼 인덱스를 사용한 get, set을 구현

  • db액션 수정
    # 화주 명단 가져오기
    def get_daycal_owner_list():
        return session.query(DayCalOwner).order_by(DayCalOwner.id).all()
    
    
    # 화주별 데이터 가져오기
    def get_daycal_owner_values(today = None):
        if today:
            return session.query(DayCalOwnerValues).filter(DayCalOwnerValues.date == today).order_by(DayCalOwnerValues.owner_id).all()
    
        today = date.today()
        values = []
        owner_list = get_daycal_owner_list()
        for owner in owner_list:
            id = owner.get(0)
            name = owner.get(1)
            value = session.query(DayCalOwnerValues).filter(and_(DayCalOwnerValues.owner_id == id, DayCalOwnerValues.date == today)).first()
            if not value:
                value = DayCalOwnerValues(today, id, name)
                session.add(value)
                session.commit()
            values.append(value)
        return values
    
    
    # 기타 데이터 가져오기
    def get_daycal_other_values(today = date.today()):
        value = session.query(DayCalOtherValues).filter(DayCalOtherValues.date == today).first()
        if not value:
            value = DayCalOtherValues(today)
            session.add(value)
            session.commit()
        return value
    
    
    # 결과 데이터 가져오기
    def get_daycal_result(today = date.today()):
        value = session.query(DayCalResult).filter(DayCalResult.date == today).first()
        if not value:
            value = DayCalResult(today)
            session.add(value)
            session.commit()
        return value​
    • DB에서 쿼리를 통해 가져온 객체를 to_list를 호출하여 컬럼값이 들어있는 리스트의 형태로 반환하는
      기존의 방식에서 객체 자체를 반환하는 방식으로 변경.

  • 테이블 모델 수정
        def data(self, index, role):
            if index.isValid():
                if role == Qt.DisplayRole:
                    value = self.table_data[index.column()].get(index.row())
                    return format(value, ',')
                elif role == Qt.EditRole:
                    value = self.table_data[index.column()].get(index.row())
                    return value
                elif role == Qt.TextAlignmentRole:
                    return int(Qt.AlignRight | Qt.AlignVCenter)
    
        def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...):
            if role == Qt.DisplayRole:
                if orientation == Qt.Horizontal:
                    return self.owner_list[section].get(1)
                else:
                    return self.vertical_header[section]
            return None
            
        def setData(self, index: QModelIndex, value: int, role):
            if role == Qt.EditRole:
                r, c = index.row(), index.column()
                self.changed(r, c, self.table_data[c].get(r), value)
                self.table_data[c].set(r, value)
                return True
            return False​
     
    • 기존에 2차원 리스트 형태였던 데이터들이 객체의 배열로 변경되면서 참조 방식도 getter를 사용하도록 수정
    • 이렇게 객체를 직접 수정하는 방식을 사용하는 것으로 모델을 사용하는 의미도 퇴색되지 않으며
      저장기능의 구현도 훨씬 편해진다.

  • 저장 액션 구현
    def save():
        session.commit()​
    • 테이블에서 쿼리로 가져온 객체를 직접 수정하도록 변경한 덕분에 단순히 session.commit()을 호출하는 것으로
      모든 변경사항이 DB에 반영된다.

  • 저장 액션 메뉴와 툴바에 추가
            # 저장 액션 추가
            save_data = QAction(QIcon('src/img/save_icon.png'), '저장하기', self)
            save_data.setShortcut('Ctrl+S')
            save_data.setStatusTip('화주 이름 변경')
            save_data.triggered.connect(actions.save)
            file_menu.addAction(save_data)
            tool_bar.addAction(save_data)​
     
    • 액션을 메뉴와 툴바에 추가하고 단축키를 Ctrl+S 로 지정, 아이콘파일을 구하여 설정해준다.

 

이제 데이터를 입력하고 저장기능을 사용하면 애플리케이션을 종료하고 다시 실행해도 작성한 오늘의 데이터가 
표시되는 것을 볼 수 있다.

처음에는 QTableWidget을 사용하여 표를 구성하였지만 조금 알아보니 QTableView를 사용하는 것이 성능면에서
더 낫다는 듯 해서 교체작업을 진행했다.

 

1. 테이블 뷰 생성

from PySide6.QtWidgets import QTabWidget, QWidget, QGridLayout, QTableView

from controller import actions
from models.table_models import DayCalTableModel, DayCalOthersTableModel, DayCalResultTableModel


# 일일 정산서 계산서 위젯
class DayCal(QWidget):
    # 생성자
    def __init__(self):
        super().__init__()
        self.input_table = QTableView()
        self.other_table = QTableView()
        self.result_table = QTableView()
        self.init_ui()

	''' 생략 '''

 화주별 데이터를 입력받고 일부 결과를 표시하기 위한 input_table, 화주와 관계없는 별도의 데이터를 입력하기
위한 other_table, 입력데이터를 기반으로 결과를 나타낼 result_table 로 총 세개의 QTableView를 생성하였다.

 

 

2. input_table 테이블 모델 작성

import operator

from PySide6.QtCore import QAbstractTableModel, Qt, SIGNAL, QModelIndex
from PySide6.QtGui import *


class DayCalTableModel(QAbstractTableModel):
    def __init__(self, parent, horizontal_header, data, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.setParent(parent)
        self.table_data = data
        self.horizontal_header = horizontal_header
        self.vertical_header = ['강동총금액', '강동운임', '강동하차비', '강동수수료 4%', '공제후 금액', '중매수수료 5%', '화주운임', '화주하차비', '상장수수료 4%', '강동선지급금', '공제합계', '선지급금포함 공제합계']
        self.row_count = len(self.vertical_header)
        self.column_count = len(self.horizontal_header)

    def rowCount(self, parent):
        return self.row_count

    def columnCount(self, parent):
        return self.column_count

    def data(self, index, role):
        if index.isValid():
            if role == Qt.DisplayRole or role == Qt.EditRole:
                value = self.table_data[index.column()][index.row()]
                return str(value)

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return self.horizontal_header[section]
            else:
                return self.vertical_header[section]
        return None

    def sort(self, col, order):
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
        self.table_data = sorted(self.table_data, key=operator.itemgetter(col))
        if order == Qt.DescendingOrder:
            self.table_data.reverse()
        self.emit(SIGNAL("layoutChanged()"))

    def flags(self, index):
        if index.row() in [3, 4, 10, 11]:
            return Qt.ItemIsSelectable | Qt.ItemIsEnabled
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable

    def setData(self, index, value, role):
        if role == Qt.EditRole:
            self.table_data[index.column()][index.row()] = value
            return True
        return False

    def owner_added(self, name):
        self.beginInsertColumns(QModelIndex(), self.column_count, self.column_count)
        self.horizontal_header.append(name)
        self.table_data.append([0]*12)
        self.column_count += 1
        self.endInsertColumns()

    def owner_removed(self, name):
        idx = self.horizontal_header.index(name)
        self.beginRemoveColumns(QModelIndex(), idx, idx)
        self.table_data.pop(idx)
        self.horizontal_header.pop(idx)
        self.column_count -= 1
        self.endRemoveColumns()

    def owner_modified(self, org, chg):
        idx = self.horizontal_header.index(org)
        self.horizontal_header[idx] = chg
        
    ''' 생략 '''

여기서는 QAbstractTableModel 을 상속한 테이블 모델을 작성하였다. (이 외에도 다른 여러가지 방법들이 있는 듯 했다.)

 

  • __init__(self, parent, horizontal_header, data, *args)
    • parent 위젯과 헤더, 그리고 표시할 데이터를 입력받는 초기화 메소드
    • 해당 모델의 parent를 입력받은 parent 위젯으로 설정
    • 해당 모델의 table_data 를 입력받은 데이터로 설정한다.  
    • horizontal_header를 입력받은 헤더 데이터로 설정한다. 이는 화주 명단과 같다.
    • vertical_header를 설정한다. 이 항목들은 변경되지 않기 때문에 하드코딩으로 설정해주었다.
    • row_count, column_count(행과 열의 길이) 값을 각각 vertical, horizontal 헤더의 길이로 설정한다.

 

  • rowCount(self, parent) / columnCount(self, parent)
    • QAbstractModel에 정의된 rowCount, columnCount 메소드를 오버라이딩
    • 테이블의 열 수와 행 수를 반환한다.

 

  • data(self, index, role)
    • QAbstractModel에 정의된 data 메소드를 오버라이딩

    • role은 이 메소드를 어떤 상태(역할)에서 호출하는지를 나타내며 index는 참조하려는 데이터의
      위치(행과 열)를 나타낸다. role 과 index에 따라 적절히 데이터를 반환하도록 구현한다.

    • 여기서는 Qt.DisplayRole(표시), Qt.EditRole(수정) 상태에서 호출할 경우 index에 해당하는 데이터를
      반환하도록 구현하였다. 만약 role == Qt.EditRole을 조건에서 뺄 경우 수정상태에 들어간 셀에서는
      데이터가 표시되지 않는다.

 

  • headerData(self, section, orientation, role)
    • QAbstractModel에 정의된 headerData 메소드를 오버라이딩

    • orientation은 방향(행방향 / 열방향)을 나타내며 section은 인덱스를 나타낸다.

    • horizontal, vertical 의 헤더를 반환하기 위해 사용한다.
    • orientation 이 horizontal인지 vertical인지에 따라 각각 해당하는 헤더값을 반환하도록 구현하였다.

 

  • flags(self, index)
    • QAbstractModel에 정의된 flags 메소드를 오버라이딩

    • index 에 해당하는 셀에 대해 flag를 설정하기 위해 사용한다

    • 플래그를 통해 특정 셀의 편집가능 여부를 수정하는 등의 작업이 가능하다.

    • 사용자가 값을 입력하는 셀이 아닌 부분을 편집 불가능하게 설정하였다.

 

  • setData(self, index, value, role)
    • QAbstractModel에 정의된 setData 메소드를 오버라이딩

    • 셀의 값 변경을 반영하기 위해 구현해야한다.

    • edit role 일 경우에 수정사항이 반영되도록 구현하였다.

    • 반영되었을 경우 True, 아닐 경우 False를 반환한다.

 

  • owner_added(self, name) / owner_removed(self, name) / owner_modified(self, org, chg)
    • 화주의 추가, 삭제, 이름변경을 반영하기 위해 직접 구현한 메소드이다.

    • 화주가 추가되고 그에따라 화주별 데이터도 추가되어 데이터베이스에 반영되지만
      데이터베이스에서 데이터를 읽어오는 것은 프로그램 실행시에만 수행하는 작업이며
      반영된 데이터를 굳이 데이터베이스에서 다시 읽어오는것도 비효율적이기에
      모델의 메소드를 호출하여 즉각 반영하도록 하였다. 

    • added 와 removed 의 경우 테이블 자체가 변경되는 작업이기에 (열의 추가/삭제)
      beginInsertColumns, beginRemoveColumns 등 테이블의 변경작업이 시작됨을 알리는
      메소드를 호출한 뒤 작업을 수행하고 마지막에 endInsertColumns, endRemoveColumns 등
      테이블의 변경작업이 끝났음을 알리는 메소드를 호출해줘야 변경사항이 정상적으로 반영된다.

    • modified 의 경우 단순히 값을 수정하는 것 뿐이기에 header 리스트를 수정해주는 것만으로
      간단하게 반영된다.

 

 

3. other_table 테이블 모델 작성

import operator

from PySide6.QtCore import QAbstractTableModel, Qt, SIGNAL, QModelIndex
from PySide6.QtGui import *

''' 생략 '''

class DayCalOthersTableModel(QAbstractTableModel):
    def __init__(self, parent, data, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.setParent(parent)
        self.table_data = [data]
        self.vertical_header = ['경매 사무실 입금', '가라경매 강동 입금', '직접 지출', '우리 경매', '강동 사입']
        self.row_count = len(self.vertical_header)
        self.column_count = 1

    def rowCount(self, parent):
        return self.row_count

    def columnCount(self, parent):
        return self.column_count

    def data(self, index, role):
        if index.isValid():
            if role == Qt.DisplayRole or role == Qt.EditRole:
                value = self.table_data[index.column()][index.row()]
                return str(value)

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...):
        if role == Qt.DisplayRole:
            if orientation == Qt.Vertical:
                return self.vertical_header[section]
        return None

    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable

    def setData(self, index, value, role):
        if role == Qt.EditRole:
            self.table_data[index.column()][index.row()] = value
            return True
        return False

''' 생략 '''
  • other_table 의 테이블 모델도 기본적으로 input_table의 그것과 거의 동일하게 구현

  • horizontal header가 필요하지 않기 때문에 vertical header만을 설정

  • 모두 사용자가 입력하는 데이터로만 이루어졌기에 특정 셀의 수정 불가등의 설정을 하지 않음

 

 

4. result_table 테이블 모델 작성

import operator

from PySide6.QtCore import QAbstractTableModel, Qt, SIGNAL, QModelIndex
from PySide6.QtGui import *

''' 생략 '''

class DayCalResultTableModel(QAbstractTableModel):
    def __init__(self, parent, result_data, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.setParent(parent)
        self.horizontal_header = ['계']
        self.vertical_header = ['강동총금액 합계', '강동운임 합계', '강동하차비 합계', '강동수수료 4% 합계', '공제후금액 합계',
                                '중매수수료 5% 합계', '화주운임 합계', '화주하차비 합계', '상장수수료 4% 합계', '경매확인',
                                '경매 차액', '중개수수료 5%', '경매 차익']
        self.row_count = len(self.vertical_header)
        self.column_count = 1

        self.table_data = [result_data]

    def rowCount(self, parent):
        return self.row_count

    def columnCount(self, parent):
        return self.column_count

    def data(self, index, role):
        if index.isValid():
            if role == Qt.DisplayRole or role == Qt.EditRole:
                value = self.table_data[index.column()][index.row()]
                return str(value)

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...):
        if role == Qt.DisplayRole:
            if orientation == Qt.Vertical:
                return self.vertical_header[section]
            else:
                return self.horizontal_header[section]
        return None

    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled
  • 마찬가지로 input_table과 거의 동일하게 구현

  • 해당 테이블은 모든 셀을 수정 불가능하도록 설정

 

 

5. 테이블 뷰에 모델 세팅

from PySide6.QtWidgets import QTabWidget, QWidget, QGridLayout, QTableView

from controller import actions
from models.table_models import DayCalTableModel, DayCalOthersTableModel, DayCalResultTableModel


# 일일 정산서 계산서 위젯
class DayCal(QWidget):
    # 생성자
    def __init__(self):
        super().__init__()
        self.input_table = QTableView()
        self.other_table = QTableView()
        self.result_table = QTableView()
        self.init_ui()

    # ui 초기화
    def init_ui(self):
        # 화주별 데이터 입력테이블
        self.data_model = DayCalTableModel(self, actions.get_daycal_owner_list(), actions.get_daycal_owner_values())
        self.input_table.setModel(self.data_model)

        # 기타 데이터 입력 테이블
        self.other_data_model = DayCalOthersTableModel(self, actions.get_daycal_other_values())
        self.other_table.setModel(self.other_data_model)

        # 결과 테이블
        self.result_data_model = DayCalResultTableModel(self, actions.get_daycal_result())
        self.result_table.setModel(self.result_data_model)

        # 그리드 레이아웃
        grid = QGridLayout()

        # 테이블위젯 추가
        grid.addWidget(self.input_table, 0, 0)
        grid.addWidget(self.other_table, 1, 0)
        grid.addWidget(self.result_table, 0, 1, 2, 1)
        grid.setRowStretch(0, 5)
        grid.setColumnStretch(0, 5)

        # 레이아웃 세팅
        self.setLayout(grid)
        
    # 화주 추가 반영
    def owner_added(self, added_user):
        self.data_model.owner_added(added_user)

    # 화주 삭제 반영
    def owner_removed(self, removed_name):
        self.data_model.owner_removed(removed_name)

    # 화주 이름 변경 반영
    def owner_modified(self, org_name, chg_name):
        self.data_model.owner_modified(org_name, chg_name)
        
''' 생략 '''
  • 작성한 모델의 인스턴스를 생성(이 때, DB에서 데이터를 읽어와서 인자로 넘겨준다.)

  • 테이블 뷰에 각각 해당하는 모델의 인스턴스를 세팅

  • 레이아웃에 추가한 뒤 위젯에 레이아웃을 세팅한다.

  • DayCal 위젯은 owner_added, owner_removed, owner_modified 메소드를 각각 구현하여
    화주 상태변경을 인식하고 각 테이블의 데이터모델의 메소드를 호출하여 이를 반영한다.

 

 

6. DB 작업을 위한 함수 작성

# 화주 명단 가져오기
def get_daycal_owner_list():
    return [q.name for q in session.query(DayCalOwner).order_by(DayCalOwner.id)]


# 화주별 데이터 가져오기
def get_daycal_owner_values():
    values = []
    today = date.today()
    for owner_id in session.query(DayCalOwner.id).order_by(DayCalOwner.id):
        id = owner_id[0]
        value = session.query(DayCalOwnerValues).filter(and_(DayCalOwnerValues.owner_id == id, DayCalOwnerValues.date == today)).first()
        if not value:
            value = DayCalOwnerValues(today, id)
            session.add(value)
            session.commit()
        values.append(value.to_list())
    return values


# 기타 데이터 가져오기
def get_daycal_other_values():
    today = date.today()
    value = session.query(DayCalOtherValues).filter(DayCalOtherValues.date == today).first()
    if not value:
        value = DayCalOtherValues(today)
        session.add(value)
        session.commit()
    return value.to_list()


# 결과 데이터 가져오기
def get_daycal_result():
    today = date.today()
    value = session.query(DayCalResult).filter(DayCalResult.date == today).first()
    if not value:
        value = DayCalResult(today)
        session.add(value)
        session.commit()
    return value.to_list()
  • 데이터 모델의 인스턴스 생성에 필요한 데이터를 DB에서 가져오기 위한 함수 구현

  • get_daycal_owner_list
    • 화주 이름 명단을 반환

    • 화주 id를 기준으로 오름차순 정렬

 

  • get_daycal_owner_values
    • 화주별 오늘의 데이터를 리스트 형태로 반환

    • 화주 id를 기준으로 오름차순 정렬

    • 오늘 날짜의 데이터가 존재하지 않을 경우 디폴트로 모든 값이 0인 기본 데이터를 생성하여
      데이터베이스에 추가하고 해당 데이터를 반환한다.

 

  • get_daycal_other_values
    • 오늘의 기타 데이터를 리스트 형태로 반환

    • 오늘 날짜의 데이터가 존재하지 않을 경우 디폴트로 모든 값이 0인 기본 데이터를 생성하여
      데이터베이스에 추가하고 해당 데이터를 반환한다.

 

  • get_daycal_result
    • 오늘의 결과 데이터를 리스트 형태로 반환

    • 오늘 날짜의 데이터가 존재하지 않을 경우 디폴트로 모든 값이 0인 기본 데이터를 생성하여
      데이터베이스에 추가하고 해당 데이터를 반환한다.

 

 

7. to_list 메소드 추가

# 화주별 일일정산 데이터 모델
class DayCalOwnerValues(Base):
    __tablename__ = 'daycal_owner_values'
    date = Column(Date, primary_key=True)
    owner_id = Column(Integer, primary_key=True)
    kd_total = Column(Integer, nullable=False)
    kd_fare = Column(Integer, nullable=False)
    kd_drop = Column(Integer, nullable=False)
    kd_fee4 = Column(Integer, nullable=False)
    after_deduction = Column(Integer, nullable=False)
    match_fee5 = Column(Integer, nullable=False)
    owner_fare = Column(Integer, nullable=False)
    owner_drop = Column(Integer, nullable=False)
    listing_fee4 = Column(Integer, nullable=False)
    kd_pre = Column(Integer, nullable=False)
    deduction_total = Column(Integer, nullable=False)
    total_include_pre = Column(Integer, nullable=False)

    def __init__(self, date, owner_id):
        self.date = date
        self.owner_id = owner_id
        self.kd_total = 0
        self.kd_fare = 0
        self.kd_drop = 0
        self.kd_fee4 = 0
        self.after_deduction = 0
        self.match_fee5 = 0
        self.owner_fare = 0
        self.owner_drop = 0
        self.listing_fee4 = 0
        self.kd_pre = 0
        self.deduction_total = 0
        self.total_include_pre = 0

    def to_list(self):
        return [self.kd_total, self.kd_fare, self.kd_drop, self.kd_fee4, self.after_deduction,
                self.match_fee5, self.owner_fare, self.owner_drop, self.listing_fee4, self.kd_pre,
                self.deduction_total, self.total_include_pre]


# 일일정산서에 기타 데이터 모델
class DayCalOtherValues(Base):
    __tablename__ = 'daycal_other_values'
    date = Column(Date, primary_key=True)
    office_deposit = Column(Integer)
    kd_deposit = Column(Integer)
    direct_exp = Column(Integer)
    our_auc = Column(Integer)
    kd_buy = Column(Integer)

    def __init__(self, date):
        self.date = date
        self.office_deposit = 0
        self.kd_deposit = 0
        self.direct_exp = 0
        self.our_auc = 0
        self.kd_buy = 0

    def to_list(self):
        return [self.office_deposit, self.kd_deposit, self.direct_exp, self.our_auc, self.kd_buy]


# 일일정산서 결과 모델
class DayCalResult(Base):
    __tablename__ = 'daycal_result'
    date = Column(Date, primary_key=True)
    kd_total = Column(Integer, nullable=False)
    kd_fare = Column(Integer, nullable=False)
    kd_drop = Column(Integer, nullable=False)
    kd_fee4 = Column(Integer, nullable=False)
    after_deduction = Column(Integer, nullable=False)
    match_fee5 = Column(Integer, nullable=False)
    owner_fare = Column(Integer, nullable=False)
    owner_drop = Column(Integer, nullable=False)
    listing_fee4 = Column(Integer, nullable=False)
    auc_check = Column(Integer, nullable=False)
    auc_diff = Column(Integer, nullable=False)
    match_fee5_final = Column(Integer, nullable=False)
    auc_profit = Column(Integer, nullable=False)

    def __init__(self, date):
        self.date = date
        self.kd_total = 0
        self.kd_fare = 0
        self.kd_drop = 0
        self.kd_fee4 = 0
        self.after_deduction = 0
        self.match_fee5 = 0
        self.owner_fare = 0
        self.owner_drop = 0
        self.listing_fee4 = 0
        self.auc_check = 0
        self.auc_diff = 0
        self.match_fee5_final = 0
        self.auc_profit = 0

    def to_list(self):
        return [self.kd_total, self.kd_fare, self.kd_drop, self.kd_fee4, self.after_deduction,
                self.match_fee5, self.owner_fare, self.owner_drop, self.listing_fee4, self.auc_check,
                self.auc_diff, self.match_fee5_final, self.auc_profit]
  • DB에서 모델단위로 읽어온 데이터를 보다 쉽게 리스트 형태로 전달하기 위해 to_list 메소드를 추가

  • 단순히 테이블에 표현하기 위해 필요한 column 값들을 리스트형태로 반환하도록 구현

 

 

이제 날짜가 바뀌면 자동으로 그날의 디폴트 데이터를 DB에 삽입하고 테이블에 표시해준다. 

또한 QTableWidget을 사용했을 때 보다 좀더 유연하게 테이블을 사용할 수 있게 되었다.

1. 액션 추가

from PySide6.QtCore import QObject
from PySide6.QtGui import QIcon, QAction
from PySide6.QtWidgets import QMainWindow, QWidget, QGridLayout

from controller.config_manager import set_geometry, get_geometry
from controller import actions
from widgets.docs_window import DocTab
from widgets.simple import TimeLabel


class MainWindow(QMainWindow, QObject):

        ``` 생략 ```

        # 화주삭제 액션 추가
        delete_owner = QAction(QIcon('src/img/delete_owner_icon.png'), '화주 삭제', self)
        delete_owner.setShortcut('Ctrl+Shift+R')
        delete_owner.setStatusTip('화주 추가')
        delete_owner.triggered.connect(lambda: actions.delete_owner(self, self.central_widget.doc_tab.tab1))
        file_menu.addAction(delete_owner)
        tool_bar.addAction(delete_owner)

        # 화주 이름 변경 액션 추가
        modify_owner = QAction(QIcon('src/img/modify_owner_icon.png'), '화주 이름 변경', self)
        modify_owner.setShortcut('Ctrl+Shift+C')
        modify_owner.setStatusTip('화주 이름 변경')
        modify_owner.triggered.connect(lambda: actions.modify_owner(self, self.central_widget.doc_tab.tab1))
        file_menu.addAction(modify_owner)
        tool_bar.addAction(modify_owner)
        
        
        ``` 생략 ```
  • 화주 삭제(delete_owner), 화주 이름 변경(modify_owner) 액션을 생성하여 메뉴바와 툴바에 추가

  • 각 액션의 triggered 시그널과 controller.actions 의 슬롯함수를 연결

 

 

2. 화주 삭제

# 화주 삭제
def delete_owner(main_window, table_widget, name_data=None):
    # 입력받은 이름이 이미 있는 경우
    if name_data:
        name = name_data

    # 이름을 입력받아야 하는 경우
    else:
        # 다이얼로그 위젯 생성
        delete_owner_dialog = Dialog()
        delete_owner_dialog.setWindowTitle('화주 삭제')
        delete_owner_dialog.setGeometry(500, 500, 300, 50)

        # 화주 이름을 입력받기 위한 다이얼로그 ui 세팅
        grid = QGridLayout()
        input_name = QLineEdit()
        input_name.setPlaceholderText('화주 이름')
        input_name.returnPressed.connect(delete_owner_dialog.success)
        submit = QPushButton('삭제')
        submit.clicked.connect(delete_owner_dialog.success)
        grid.addWidget(QLabel('화주 이름: '), 0, 0, 1, 1)
        grid.addWidget(input_name, 0, 1, 1, 2)
        grid.addWidget(submit, 1, 0, 1, 3)
        delete_owner_dialog.setLayout(grid)

        # 다이얼로그를 modal 하게 표시
        delete_owner_dialog.show_modal()

        # 입력이 취소된경우 (다이얼로그를 그냥 종료한 경우) 삭제절차 종료
        if delete_owner_dialog.canceled:
            return

        # 다이얼로그에서 입력이 완료되면 입력받은 이름을 name 으로 설정
        name = input_name.text()

    # 화주 삭제
    try:
        target = session.query(DayCalOwner).filter(DayCalOwner.name == name).first()
        session.delete(target)
        session.commit()
        table_widget.owner_removed(target.name)
    except UnmappedInstanceError:
        main_window.statusBar().showMessage('>> 등록되지 않은 화주입니다.')
  • 기본적인 흐름은 화주 추가와 동일

  • 쿼리를 통해 입력받은 이름에 해당하는 데이터를 찾아 삭제

  • 삭제를 테이블에 즉각 반영하기 위해 table_widget.owner_removed 실행
  • 해당하는 데이터가 존재하지 않을 경우 status bar에 등록되지 않은 화주임을 표시

 

 

3. 화주 삭제 반영

    # 화주 삭제 반영
    def owner_removed(self, removed_name):
        idx = self.owners.index(removed_name)
        self.input_table.removeColumn(idx)
        del self.owners[idx]
  • 화주 이름 리스트에서 화주의 인덱스를 찾아 해당 인덱스의 열을 삭제하고 화주 이름 리스트에서도 삭제

 

 

4. 화주 이름 변경

# 화주 이름 변경
def modify_owner(main_window, table_widget, name_data=None):
    # 다이얼로그 위젯 생성
    modify_owner_dialog = Dialog()
    modify_owner_dialog.setWindowTitle('화주 이름 변경')
    modify_owner_dialog.setGeometry(500, 500, 300, 50)
    grid = QGridLayout()

    # 화주 이름을 미리 입력받지 않은 경우(메뉴바나 툴바에서 변경액션에 접근한 경우)
    org_name = QLineEdit()
    if not name_data:
        # 화주 이름을 입력받기 위한 다이얼로그 ui 세팅
        org_name.setPlaceholderText('화주 이름')
        grid.addWidget(QLabel('화주 이름: '), 0, 0, 1, 1)
        grid.addWidget(org_name, 0, 1, 1, 2)

    chg_name = QLineEdit()
    chg_name.setPlaceholderText('바꿀 이름')
    chg_name.returnPressed.connect(modify_owner_dialog.success)
    submit = QPushButton('변경')
    submit.clicked.connect(modify_owner_dialog.success)

    grid.addWidget(QLabel('바꿀 이름: '), 1, 0, 1, 1)
    grid.addWidget(chg_name, 1, 1, 1, 2)
    grid.addWidget(submit, 2, 0, 1, 3)
    modify_owner_dialog.setLayout(grid)

    # 다이얼로그를 modal 하게 표시
    modify_owner_dialog.show_modal()

    # 입력이 취소된경우 (다이얼로그를 그냥 종료한 경우) 삭제절차 종료
    if modify_owner_dialog.canceled:
        return

    # 대상 화주이름 name 과 바꿀 이름 changed 를 설정
    name = name_data if name_data else org_name.text()
    changed = chg_name.text()

    # 화주 이름 변경
    try:
        target = session.query(DayCalOwner).filter(DayCalOwner.name == name).first()
        target.name = changed
        session.commit()
        table_widget.owner_modified(name, changed)
    except AttributeError:
        main_window.statusBar().showMessage('>> 등록되지 않은 화주입니다.')
    except DatabaseError:
        main_window.statusBar().showMessage('>> 이미 등록된 화주입니다.')
        session.rollback()
  • Dialog를 통해 변경할 화주의 이름(name)과 바꿀 이름(changed)을 입력받음

  • 데이터베이스에서 name에 해당하는 데이터를 가져와 이름을 변경한 뒤 commit

  • 이름 변경을 즉각 반영하기 위해 table_widget.owner_modified 실행
  • AttributeError 발생시 status bar에 이름을 바꾸려는 화주가 등록되지 않은 화주임을 표시
  • DatabaseError 발생시 status bar에 바꿀 이름이 이미 존재하는 다른 화주의 이름임을 표시

 

 

5. 화주 이름 변경 반영

    # 화주 이름 변경 반영
    def owner_modified(self, org_name, chg_name):
        idx = self.owners.index(org_name)
        self.owners[idx] = chg_name
        self.input_table.setHorizontalHeaderItem(idx, QTableWidgetItem(chg_name))
  • 기존 이름의 인덱스를 찾아 바꿀 이름으로 변경하고 해당 열의 헤더에도 반영

 

 

6. 테스트

 

  • 등록되지 않은 화주 삭제 시도

 

 

 

  • 등록되지 않은 화주의 이름 변경 시도

 

 

 

  • 바꿀 이름이 이미 존재하는 화주의 이름인 경우 

 

 

 

  • 정상적인 삭제

 

 

 

  • 정상적인 이름 변경

'개인 프로젝트 > Accounting Program' 카테고리의 다른 글

#18 저장기능 구현  (0) 2021.11.25
#17 QTableView  (0) 2021.11.22
#15 모델/구조 변경, 화주 추가 기능 수정  (0) 2021.11.02
#14 화주 추가  (0) 2021.11.01
#13 데이터베이스 연결  (0) 2021.10.30

1. DayCalOwner 모델 변경

# 화주 모델
class DayCalOwner(Base):
    __tablename__ = 'daycal_owner'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String, unique=True)

    def __init__(self, name):
        self.name = name
  • autoincrement 가 설정된 id 속성을 추가

  • primary key를 name에서 id로 변경
  • name을 primaryk 중복 불가능한 값으로 하기 위해 unique 설정

 

 

2. actions.py

from PySide6.QtWidgets import QLabel, QGridLayout, QPushButton, QLineEdit
from sqlalchemy.exc import DatabaseError
from sqlalchemy.orm.exc import UnmappedInstanceError

from controller.db_manager import session
from models.models import DayCalOwner
from widgets.simple import Dialog


# 화주 추가
def create_owner(main_window, table_widget, name_data=None):
    # 입력받은 이름이 이미 있는 경우
    if name_data:
        name = name_data

    # 이름을 입력받아야 하는 경우
    else:
        # 다이얼로그 위젯 생성
        create_owner_dialog = Dialog()
        create_owner_dialog.setWindowTitle('화주 추가')
        create_owner_dialog.setGeometry(500, 500, 300, 50)

        # 화주 이름을 입력받기 위한 다이얼로그 ui 세팅
        grid = QGridLayout()
        input_name = QLineEdit()
        input_name.setPlaceholderText('화주 이름')
        input_name.returnPressed.connect(create_owner_dialog.success)
        submit = QPushButton('추가')
        submit.clicked.connect(create_owner_dialog.success)
        grid.addWidget(QLabel('화주 이름: '), 0, 0, 1, 1)
        grid.addWidget(input_name, 0, 1, 1, 2)
        grid.addWidget(submit, 1, 0, 1, 3)
        create_owner_dialog.setLayout(grid)

        # 다이얼로그를 modal 하게 표시
        create_owner_dialog.show_modal()

        # 입력이 취소된경우 (다이얼로그를 그냥 종료한 경우) 추가절차 종료
        if create_owner_dialog.canceled:
            return

        # 다이얼로그에서 입력이 완료되면 입력받은 이름을 name 으로 설정
        name = input_name.text()

    # 새 화주 추가
    new_owner = DayCalOwner(name)
    try:
        session.add(new_owner)
        session.commit()
        table_widget.owner_added(name)
    except DatabaseError:
        main_window.statusBar().showMessage('>> 이미 등록된 화주입니다.')
        session.rollback()
  • main_window 에 구현했던 화주 추가 액션을 controller 디렉토리의 actions.py 로 이동

  • self 대신 메인윈도우의 인스턴스와 화주 추가 결과를 반영할 table이 위치한 위젯의 인스턴스를 인자로 받음

  • dialog를 통하지않고 이름을 입력받을 상황을 대비하여 name_data 인자를 추가

  • name_data가 입력되었다면 다이얼로그로 이름을 입력받지 않고 name_data를 name으로 설정

  • 중복된 이름일 경우 status bar에 이미 등록된 화주임을 표시

  • name이 더이상 primary key가 아닌 unique key이기 때문에 이름이 중복될 경우 add 가 아닌 commit단계에서
    예외가 발생한다. 그러므로 예외 발생시 이미 적용된 add 를 취소하기 위해 rollback을 호출해줘야한다.

 

from controller import actions

    ``` 생략 ```
 
        # 화주추가 액션 추가
        create_owner = QAction(QIcon('src/img/create_owner_icon.png'), '화주 추가', self)
        create_owner.setShortcut('Ctrl+Shift+A')
        create_owner.setStatusTip('화주 추가')
        create_owner.triggered.connect(lambda: actions.create_owner(self, self.central_widget.doc_tab.tab1))
        file_menu.addAction(create_owner)
        tool_bar.addAction(create_owner)
        
    ``` 생략 ```
  • contoller.actions를 import 한 뒤 create_owner 액션의 triggered 시그널에 lambda 함수의 형태로 연결해준다.

 

 

3. simple.py

from PySide6.QtCore import QDateTime, QTimer
from PySide6.QtWidgets import QLabel, QDialog


# 시간레이블 클래스(QLabel 상속)
# 1초마다 현재 날짜/시간을 갱신하여 표시하는 레이블
class TimeLabel(QLabel):
    def __init__(self):
        super().__init__()
        self.setText(QDateTime.currentDateTime().toString('yyyy년 MM월 dd일 ddd hh:mm:ss'))
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.timeout)
        self.timer.start(100)

    def timeout(self):
        self.setText(QDateTime.currentDateTime().toString('yyyy년 MM월 dd일 ddd hh:mm:ss'))


# 다이얼로그(QDialog 상속)
class Dialog(QDialog):
    def __init__(self):
        super().__init__()
        self.canceled = True

    def show_modal(self):
        return super().exec_()

    def success(self):
        self.canceled = False
        self.close()
  • Dialog, TimeLabel 등의 구조가 간단한 위젯들을 widgets/simple.py 에 분리

 

 

4. DayCal 위젯

# 일일 정산서 계산서 위젯
class DayCal(QWidget):
    # 생성자
    def __init__(self):
        super().__init__()
        self.owners = [q.name for q in session.query(DayCalOwner).order_by(DayCalOwner.id)]
        self.num_of_owners = len(self.owners)
        self.input_table = QTableWidget()
        self.input_table.setParent(self)
        self.init_ui()

    ``` 생략 ```
  • 화주 데이터의 id를 기반으로 순서를 유지하도록 쿼리시에 order_by 를 통해 id를 기준으로 오름차순 정렬

 

 

5. create_owner 수정

# 화주 추가
def create_owner(main_window, table_widget, name_data=None):
    # 입력받은 이름이 이미 있는 경우
    if name_data:
        name = name_data

    # 이름을 입력받아야 하는 경우
    else:
        # 다이얼로그 위젯 생성
        create_owner_dialog = Dialog()
        create_owner_dialog.setWindowTitle('화주 추가')
        create_owner_dialog.setGeometry(500, 500, 300, 50)

        # 화주 이름을 입력받기 위한 다이얼로그 ui 세팅
        grid = QGridLayout()
        input_name = QLineEdit()
        input_name.setPlaceholderText('화주 이름')
        input_name.returnPressed.connect(create_owner_dialog.success)
        submit = QPushButton('추가')
        submit.clicked.connect(create_owner_dialog.success)
        grid.addWidget(QLabel('화주 이름: '), 0, 0, 1, 1)
        grid.addWidget(input_name, 0, 1, 1, 2)
        grid.addWidget(submit, 1, 0, 1, 3)
        create_owner_dialog.setLayout(grid)

        # 다이얼로그를 modal 하게 표시
        create_owner_dialog.show_modal()

        # 입력이 취소된경우 (다이얼로그를 그냥 종료한 경우) 추가절차 종료
        if create_owner_dialog.canceled:
            return

        # 다이얼로그에서 입력이 완료되면 입력받은 이름을 name 으로 설정
        name = input_name.text()

    # 새 화주 추가
    new_owner = DayCalOwner(name)
    try:
        session.add(new_owner)
        session.commit()
        table_widget.owner_added(name)
    except DatabaseError:
        main_window.statusBar().showMessage('>> 이미 등록된 화주입니다.')
        session.rollback()
  • 이름을 입력하기 위한 위젯을 QPlainText 에서 QLineEdit 으로 변경

  • returnPressed 시그널을 사용하여 엔터키에도 반응하도록 수정
  • 다이얼로그에 canceled 속성을 추가하여 단순 종료와 데이터를 정상적으로 제출한 종료를 구분

  • 데이터를 정상적으로 제출했을 때만 기능이 동작하도록 수정

 

'개인 프로젝트 > Accounting Program' 카테고리의 다른 글

#17 QTableView  (0) 2021.11.22
#16 화주 삭제, 화주 이름 변경  (0) 2021.11.02
#14 화주 추가  (0) 2021.11.01
#13 데이터베이스 연결  (0) 2021.10.30
#12 메인 화면 구성  (0) 2021.10.28

지난번에는 로컬 데이터베이스를 설치하고 연결 세션을 만들어 데이터베이스에 등록된 화주 명단에 따라 테이블이

구성되도록 하였다.  이번에는 화주 추가 기능을 구현하여 실제로 데이터베이스에 화주를 등록하고 정상적으로

반영되는지 확인해본다.

 

1. 화주 추가 액션

        # 화주추가 액션 추가
        create_owner = QAction(QIcon('src/img/create_owner_icon.png'), '화주 추가', self)
        create_owner.setShortcut('Ctrl+Shift+A')
        create_owner.setStatusTip('화주 추가')
        create_owner.triggered.connect(self.create_owner)
        file_menu.addAction(create_owner)
        tool_bar.addAction(create_owner)
  • widgets/main_window.py 에서  MainWindow 위젯의 init_ui 메소드에 위와 같은 내용을 추가
  • create_owner 액션을 만들어 메뉴와 툴바에 추가하고 self.create_owner 메소드에 연결

 

 

2. 화주 추가 메소드

# 화주 추가
    def create_owner(self):
        # 다이얼로그 위젯 생성
        create_owner_dialog = Dialog()
        create_owner_dialog.setWindowTitle('화주 추가')
        create_owner_dialog.setGeometry(500, 500, 300, 50)

        # 화주 이름을 입력받기 위한 다이얼로그 ui 세팅
        grid = QGridLayout()
        input_name = QPlainTextEdit()
        input_name.setPlaceholderText('화주 이름')
        submit = QPushButton('추가')
        submit.clicked.connect(create_owner_dialog.close)
        grid.addWidget(QLabel('화주 이름: '), 0, 0, 1, 1)
        grid.addWidget(input_name, 0, 1, 1, 2)
        grid.addWidget(submit, 1, 0, 1, 3)
        create_owner_dialog.setLayout(grid)

        # 다이얼로그를 modal 하게 표시
        create_owner_dialog.show_modal()

        # 다이얼로그에서 입력이 완료되면 입력받은 이름으로 화주 추가
        name = input_name.toPlainText()
        new_owner = DayCalOwner(name)
        try:
            session.add(new_owner)
            session.commit()
            self.central_widget.doc_tab.tab1.owner_added(name)
        except IntegrityError:
            self.statusBar().showMessage('>> 이미 등록된 화주입니다.')
  • widgets/main_window.py 에서  MainWindow 위젯에 create_owner 메소드를 위와 같이 정의

  • 추가할 화주의 이름을 입력받기 위한 다이얼로그를 생성하여 Modal 하게 표시한다.
    => Modal : 다이얼로그가 떠있는 동안 메인 프로세스와 상호작용할 수 없도록 하는 방식

  • 입력받은 이름을 key 값으로 하여 화주 모델인 DayCalOwner 클래스의 인스턴스를 생성

  • 데이터베이스 session에 인스턴스를 add, commit

  • central_widget의 doc_tab의 첫번째 탭인 DayCalWidget에서 화주가 추가됐을 때 이를 즉각 테이블에
    반영하기 위한 메소드인 owner_added를 실행

 

 

3. CentralWidget 

class CentralWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.doc_tab = DocTab()
        self.init_ui()

    def init_ui(self):
        self.setStyleSheet("background-color: #FFFFFF")

        # 그리드 레이아웃
        grid = QGridLayout()

        # 탭 위젯 추가
        grid.addWidget(self.doc_tab, 0, 0)

        # 중앙 위젯에 그리드 레이아웃 적용
        self.setLayout(grid)
  • DocTab 을 레이아웃을 통하지 않고 바로 참조하기 위해 CentralWidget을 QWidget을 상속하여 별도로 정의

 

 

4. Dialog

# 다이얼로그(QDialog 상속)
class Dialog(QDialog):
    def __init__(self):
        super().__init__()

    def show_modal(self):
        return super().exec_()
  • show_modal 기능을 갖춘 다이얼로그 위젯을 정의

 

 

5. 테이블에 추가된 화주 반영

    # 화주 추가 반영
    def owner_added(self, added_user):
        self.owners.append(added_user)
        self.input_table.insertColumn(self.input_table.columnCount())
        self.input_table.setHorizontalHeaderItem(self.input_table.columnCount()-1, QTableWidgetItem(added_user))
  • widgets/docs_window.py 에서 DayCal 위젯에 create_owner 메소드를 위와 같이 정의
  • 화주명 리스트에 추가된 화주이름을 추가하고 테이블에 열을 하나 추가하여 추가된 화주의 이름을 헤더로 설정

 

 

 

 

정상적으로 화주가 추가되는 것을 확인할 수 있다.

 

※ 간단하게 구현을 해봤지만 화주 이름을 추가할 때 추가 버튼을 엔터키로 대체할 수 없는 부분이나 
   아무것도 버튼을 누르지 않고 창을 닫기만 해도 입력된 것으로 처리되는 등의 문제가 있어 수정이 필요한 상태이다.

=> 현재는 수정되었다.

 

프로그램상에서 작성한 문서의 데이터는 로컬 데이터베이스에 저장하여 관리할 것이다. 그러기 위해서는

먼저 DBMS(DataBase Management System)를 설치해야한다.  여기서는 PostreSQL 을 사용할 것이다.

 

 

1. PostgreSQL 설치

먼저 PostgreSQL 공식 홈페이지에 접속하여 Download 페이지로 들어간다.

 

PostgreSQL

The world's most advanced open source database.

www.postgresql.org

 

 

 윈도우 환경에 설치할 것이기 때문에 Windows 를 선택한다.

 

 

Download the installer 를 클릭하고 운영체제에 맞는 최신버전을 다운로드한다. 다운로드 받은 설치파일을

실행하고 next를 눌러 설치를 진행하다보면 데이터베이스의 슈퍼유저(postgres) 계정을 생성하기 위해 암호를

입력받는다.  이후에 별도로 건드릴 것은 없으니 계속 next를 눌러 설치를 마친다. 

 

 

2. 데이터베이스 서버 생성

PostgreSQL을 설치했으니 이제 데이터를 저장할 DB 서버를 생성해야한다. 위의 설치과정에서 자동으로

설치됐을 PostgreSQL의 GUI툴인 pgAdmin 을 실행하여 위와 같이 server 를 우클릭하여 새 서버를 생성한다.

pgAdmin 실행시 요구하는 암호는 PostgreSQL 설치시에 입력한 슈퍼유저 계정의 암호를 입력해주면 된다.

 

먼저 General 탭의 Name 에는 서버의 이름을 입력해준다.  무엇으로 하든 상관없으니 원하는 이름을 입력한다.

 

Connection 탭에서는 호스트 이름 혹은 주소, 포트번호, 유저명, 패스워드를 입력한다. 로컬 DB로 사용할 것이기

때문에 호스트는 localhost로 해주고 포트번호는 PostgreSQL의 기본 포트번호인 5432를 그대로 사용한다.

유저명과 패스워드는 DB를 사용할 계정의 것을 입력해야한다.  여기서는 슈퍼유저 계정인 postgres를 사용한다.

 

모두 입력한 뒤 save를 누르면 서버가 생성된 것을 볼 수 있다.

 

 

3. sqlAlchemy 설치

 sqlAlchemy는 Python에서 데이터베이스를 다루기 위한 SQL 툴킷이며 ORM(Object Relational Mapper)이다.

ORM은 데이터베이스의 데이터들을 객체의 형태로 매핑해주는 것으로 사용자가 SQL문에 의존하지 않고

객체지향적인 코드로 DB를 다룰 수 있게 해준다.  sqlAlchemy 는 현재 Python 웹 프레임워크인 Flask에서도

사용되고있으며 마찬가지로 Python 웹 프레임워크인 Django 의 ORM과 함께 Python에서 가장 널리 사용되는

ORM 툴이다.  여기서는 sqlAlchemy 를 사용하여 애플리케이션을 DB와 연결해본다.

 

 

pip install sqlalchemy

설치는 매우 간단하다. 현재 개발환경인 Pycharm의 Terminal은 기본적으로 가상환경에 진입한 상태이기 때문에

터미널상에서 바로 pip 명령어를 사용하여 설치해주면 된다. 

 

 

4. db_manager.py

import json

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker

# 데이터베이스 패스워드를 저장한 secrete_file.json 을 읽는다
with open('secrete_file.json') as f:
    secretes = json.loads(f.read())

# postgresql 의 형식에 따라 엔진 생성
engine = create_engine('postgresql://{username}:{db_password}@{host}:{port}/{db_name}'.format(
    username='postgres',
    db_password=secretes['db_password'],
    host='localhost',
    port='5432',
    db_name='postgres'
))

# DB 세션 생성
session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))

# 모델 매핑
Base = declarative_base()
Base.query = session.query_property()

# db 초기화
def init_db():
    Base.metadata.create_all(engine)
  • controller 디렉토리에 db_manager.py 를 위와 같이 작성

  • secrete_file.json 에서 데이터베이스의 패스워드를 읽음

  • create_engine 에 postgresql의 형식에 맞춰 정보를 입력해주는 것으로 엔진을 생성

  • sessionmaker와 scoped_session 을 사용하여 엔진과 연결된 세션을 생성
    • autocommit과 autoflush를 False 로 하는 것은 여러개의 쿼리문을 적용해야할 때 한번에 commit을
      실행하기 위해서이다.(flush가 발생하면 db에 실제로 삽입, 삭제 등의 연산을 수행한 상태가 된다.
      다만 commit까지는 가지 않는다. commit은 내부적으로 flush를 먼저 수행한다.)

  • declarative_base 를 사용하여 Base를 상속하여 선언한 클래스(모델)들을 매핑해준다.

  • init_db를 호출하면 연결된 db에 선언한 모델들을 기반으로 테이블을 생성한다.

 

 

5. 모델 선언

from sqlalchemy import Column, Integer, String, DateTime
from controller.db_manager import Base


# 화주 모델
class DayCalOwner(Base):
    __tablename__ = 'daycal_owner'
    name = Column(String, primary_key=True)

    def __init__(self, name):
        self.name = name


# 화주별 일일정산 데이터 모델
class DayCalOwnerValues(Base):
    __tablename__ = 'daycal_owner_values'
    datetime = Column(DateTime, primary_key=True)
    owner_name = Column(String, primary_key=True)
    kd_total = Column(Integer)
    kd_fare = Column(Integer)
    kd_drop = Column(Integer)
    match_fee5 = Column(Integer)
    owner_fare = Column(Integer)
    owner_drop = Column(Integer)
    listing_fee4 = Column(Integer)
    kd_pre = Column(Integer)

    def __init__(self, datetime, owner):
        self.datetime = datetime
        self.owner = owner


# 일일정산서에 입력될 기타 데이터 모델
class DayCalOtherValues(Base):
    __tablename__ = 'daycal_other_values'
    datetime = Column(DateTime, primary_key=True)
    office_deposit = Column(Integer)
    kd_deposit = Column(Integer)
    direct_exp = Column(Integer)
    our_auc = Column(Integer)
    kd_buy = Column(Integer)

    def __init__(self, datetime):
        self.datetime = datetime
  • model 디렉토리의 models.py에 일일정산서에 필요한 모델들을 선언
  • 모델 클래스는 모두 Base 를 상속

 

 

6. DB 초기화

from PySide6.QtWidgets import QTabWidget, QWidget, QTableWidget, QGridLayout
from controller.db_manager import session, init_db
from models.models import DayCalOwner


# 일일 정산서 계산서 위젯
class DayCal(QWidget):
    # 생성자
    def __init__(self):
        super().__init__()
        self.init_ui()

    # ui 초기화
    def init_ui(self):
        init_db()
        # 화주 목록
        owners = [q.name for q in session.query(DayCalOwner)]
        input_table = QTableWidget()
        input_table.setColumnCount(len(owners))
        input_table.setHorizontalHeaderLabels(owners)
        input_table.setRowCount(19)
        input_table.setVerticalHeaderLabels([
            '강동총금액', '강동운임', '강동하차비', '강동수수료 4%', '공제후금액', '',
            '중매수수료 5%', '화주운임', '화주하차비', '상장수수료 4%', '강동선지급금', '공제합계', '선지급금포함 공제합계', '',
            '경매 사무실입금', '가라경매 강동입금', '직접지출', '우리경매', '강동사입'
                                       ])

        # 그리드 레이아웃
        grid = QGridLayout()

        # 테이블위젯 추가
        grid.addWidget(input_table, 0, 0)

        # 레이아웃 세팅
        self.setLayout(grid)

 이 작업은 DB 세팅시에 한번만 수행해주면 된다.  원래라면 이 세팅은 별도의 파일로 분리해야하지만 여기서는

우선 테스트를 위해 DB를 처음 사용할 DayCal 위젯의 init_ui에서 init_db 함수를 실행하여 수행해주자.  그리고

이전에는 임시로 화주1, 화주2, 화주3을 넣어주었던 owners를 DB 세션에서 query를 통해 DayCalOwner,

즉 화주 데이터를 가져온 후 그 이름으로 이루어진 리스트를 구성한다.  이제 column 의 헤더 레이블은 DB에

등록된  화주명단에 기반하여 붙여질 것이다. 

 

그런데 위의 작업가지 완료한 후 애플리케이션으 구동해보면 위와 같이 column이 아예 없어진 것을 볼 수 있다.

테이블만 생성했을 뿐 추가된 화주가 하나도 없기 때문이다.  다음 포스팅에서는 화주 추가 기능을 구현하여

실제로 데이터베이스에 등록된 화주 리스트에 따라 표가 나타나도록 해본다.

'개인 프로젝트 > Accounting Program' 카테고리의 다른 글

#15 모델/구조 변경, 화주 추가 기능 수정  (0) 2021.11.02
#14 화주 추가  (0) 2021.11.01
#12 메인 화면 구성  (0) 2021.10.28
#11 종료시 위치 기억  (0) 2021.10.26
#10 프로젝트 구조 변경  (0) 2021.10.20

프로그램의 메인화면이 될 Central Widget은 작성할 문서의 종류를 탭(tab) 형식으로 보여주도록 할 것이다.

문서의 종류는 일일 정산서 계산서(DayCal), 대차대조표(BalancedSheet), 수협 입금(SH) 의 세 종류이다.

 

1. CentralWidget

from PySide6.QtCore import QDateTime, QTimer
from PySide6.QtGui import QIcon, QAction
from PySide6.QtWidgets import QMainWindow, QWidget, QLabel, QGridLayout

from controller.config_manager import set_geometry, get_geometry
from widgets.docs_window import DocTab


class MainWindow(QMainWindow):
    # 생성자
    def __init__(self):
        super().__init__()
        self.init_ui()

    # ui 초기화
    def init_ui(self):
    
        ``` 생략 ```
        
        # 중앙 위젯
        central_widget = QWidget()
        central_widget.setStyleSheet("background-color: #FFFFFF")

        # 그리드 레이아웃
        grid = QGridLayout()

        # 탭 위젯 추가
        grid.addWidget(DocTab(), 0, 0)

        # 중앙 위젯에 그리드 레이아웃 적용
        central_widget.setLayout(grid)

        # 앱의 Central Widget 에 central_widget 설정
        self.setCentralWidget(central_widget)
    
        ``` 생략 ```
        
        # 윈도우를 화면에 띄운다
        self.show()
  • 빈 위젯(QWidget) central_widget 생성

  • 세 종류의 문서를 탭 형태로 표현하는 DocTab 위젯을 그리드 레이아웃에 추가

  • central_widget에 그리드 레이아웃을 적용
  • setCentralWidget 메소드를 호출하여 MainWindow 의 Central Widget으로 central_widget을 설정

 

 

2. DocTab

세 종류의 문서를 탭 형태로 나타내기 위해 QTabWidget 을 상속하는 DocTab 위젯을 생성한다.

 

# 문서 탭 위젯
class DocTab(QTabWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        tab1 = DayCal()
        tab2 = BalancedSheet()
        tab3 = SH()

        self.addTab(tab1, '일일정산서 계산서')
        self.addTab(tab2, '대차대조표')
        self.addTab(tab3, '수협 입금')
  • widgets/docs_window.py 에  QTabWidget을 상속하는 DocTab 클래스를 정의

  • 일일 정산서 계산서(DayCal), 대차대조표(BalancedSheet), 수협 입금(SH) 세 종류의 위젯을 각각 탭으로 추가

 

 

3. 일일 정산서 계산서(DayCal)

일일 정산서 계산서를 작성하기 위한 DayCal 위젯을 생성한다.

 

# 일일 정산서 계산서 위젯
class DayCal(QWidget):
    # 생성자
    def __init__(self):
        super().__init__()
        self.init_ui()

    # ui 초기화
    def init_ui(self):
        # 화주 목록
        owners = ['화주1', '화주2', '화주3']
        input_table = QTableWidget()
        input_table.setColumnCount(len(owners))
        input_table.setHorizontalHeaderLabels(owners)
        input_table.setRowCount(19)
        input_table.setVerticalHeaderLabels([
            '강동총금액', '강동운임', '강동하차비', '강동수수료 4%', '공제후금액', '',
            '중매수수료 5%', '화주운임', '화주하차비', '상장수수료 4%', '강동선지급금', '공제합계', '선지급금포함 공제합계', '',
            '경매 사무실입금', '가라경매 강동입금', '직접지출', '우리경매', '강동사입'
                                       ])

        # 그리드 레이아웃
        grid = QGridLayout()

        # 테이블위젯 추가
        grid.addWidget(input_table, 0, 0)

        # 레이아웃 세팅
        self.setLayout(grid)
  • widgets/docs_window.py에 QWidget을 상속하는 DayCal 클래스 생성
  • 화주 목록 owners 를 예시로 작성

  • 문서 작성을 위한 테이블 위젯 input_table 을 생성, 세로방향 헤더에는 입력받을 항목들이,
    가로방향 헤더에는 화주 목록의 화주들의 이름이 오도록 한다.

  • 그리드 레이아웃에 input_table을 추가
  • DayCal 에 그리드 레이아웃 적용

 

 

4. 대차대조표(BalancedSheet) / 수협입금(SH)

# 대차대조표 위젯
class BalancedSheet(QWidget):
    pass


# 수협 입금 위젯
class SH(QWidget):
    pass

이 두 종류의 문서는 추후에 기능을 추가할 예정이기에 우선은 구현을 생략한다.

 

 

 

 

이제 애플리케이션의 메인화면에 위와 같이 탭 형태로 세 종류의 문서를 작성할 수 있다.

간략하게 구현해둔 일일정산서 계산서 탭에는 테이블이 표시되는 것을 볼 수 있다.

 

 

 

 

'개인 프로젝트 > Accounting Program' 카테고리의 다른 글

#14 화주 추가  (0) 2021.11.01
#13 데이터베이스 연결  (0) 2021.10.30
#11 종료시 위치 기억  (0) 2021.10.26
#10 프로젝트 구조 변경  (0) 2021.10.20
#9 레이아웃(Layout)  (0) 2021.10.18

+ Recent posts