first commit

This commit is contained in:
peregr1nus
2025-12-10 13:27:48 +09:00
commit 8019a7e4ba
19 changed files with 2028 additions and 0 deletions

2
utils/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
# Utils package

66
utils/google_drive.py Normal file
View File

@@ -0,0 +1,66 @@
import os
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
import pickle
SCOPES = ['https://www.googleapis.com/auth/drive.file']
class GoogleDriveUploader:
def __init__(self, credentials_file, token_file):
self.credentials_file = credentials_file
self.token_file = token_file
self.service = None
self._authenticate()
def _authenticate(self):
"""구글 드라이브 인증"""
creds = None
# 기존 토큰 파일이 있으면 로드
if os.path.exists(self.token_file):
with open(self.token_file, 'rb') as token:
creds = pickle.load(token)
# 유효한 인증 정보가 없으면 새로 인증
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
if not os.path.exists(self.credentials_file):
raise FileNotFoundError(
f"구글 드라이브 인증 파일을 찾을 수 없습니다: {self.credentials_file}\n"
"Google Cloud Console에서 OAuth 2.0 클라이언트 ID를 다운로드하여 "
"credentials.json으로 저장해주세요."
)
flow = InstalledAppFlow.from_client_secrets_file(
self.credentials_file, SCOPES)
creds = flow.run_local_server(port=0)
# 토큰 저장
with open(self.token_file, 'wb') as token:
pickle.dump(creds, token)
self.service = build('drive', 'v3', credentials=creds)
def upload_file(self, file_path, file_name, folder_id=None):
"""파일을 구글 드라이브에 업로드"""
if not self.service:
raise Exception("구글 드라이브 서비스가 초기화되지 않았습니다.")
file_metadata = {'name': file_name}
if folder_id:
file_metadata['parents'] = [folder_id]
media = MediaFileUpload(file_path, mimetype='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
file = self.service.files().create(
body=file_metadata,
media_body=media,
fields='id'
).execute()
return file.get('id')

118
utils/word_processor.py Normal file
View File

@@ -0,0 +1,118 @@
from docx import Document
from docx.shared import Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
import os
from datetime import datetime
class WordProcessor:
def __init__(self, template_dir):
self.template_dir = template_dir
self.output_dir = 'output'
os.makedirs(self.output_dir, exist_ok=True)
def generate_document(self, data, cult_name='기타'):
"""데이터를 워드 문서로 생성"""
# 템플릿 파일 경로
template_file = os.path.join(self.template_dir, f'{cult_name}_template.docx')
# 템플릿이 없으면 기본 템플릿 사용
if not os.path.exists(template_file):
template_file = os.path.join(self.template_dir, 'default_template.docx')
# 템플릿이 있으면 사용, 없으면 새 문서 생성
if os.path.exists(template_file):
doc = Document(template_file)
else:
doc = Document()
self._create_default_template(doc)
# 데이터 채우기
self._fill_document(doc, data)
# 출력 파일명
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
name = data.get('basic_info', {}).get('name', '무명')
output_filename = f'교리점검표_{name}_{timestamp}.docx'
output_path = os.path.join(self.output_dir, output_filename)
doc.save(output_path)
return output_path
def _create_default_template(self, doc):
"""기본 템플릿 생성"""
# 제목
title = doc.add_heading('이단 탈퇴자 교리점검표', 0)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 기본 정보 섹션
doc.add_heading('1. 기본 정보', level=1)
doc.add_paragraph('이름: {name}')
doc.add_paragraph('교구: {district}')
doc.add_paragraph('이단교단: {cult}')
# 기독교대한감리회 신앙고백
doc.add_heading('2. 기독교대한감리회 신앙고백 교리 점검', level=1)
doc.add_paragraph('{methodist_doctrine}')
# 이단 일반 교리 점검
doc.add_heading('3. 이단 일반 교리 점검', level=1)
doc.add_paragraph('{general_cult_doctrine}')
# 출신 이단별 교리 점검
doc.add_heading('4. 출신 이단별 교리 점검', level=1)
doc.add_paragraph('{specific_cult_doctrine}')
# 간증문
doc.add_heading('5. 간증문', level=1)
doc.add_paragraph('{testimony}')
doc.add_paragraph('')
doc.add_paragraph(f'작성일: {datetime.now().strftime("%Y년 %m월 %d")}')
def _fill_document(self, doc, data):
"""문서에 데이터 채우기"""
basic_info = data.get('basic_info', {})
methodist = data.get('methodist_doctrine', {})
general = data.get('general_cult_doctrine', {})
specific = data.get('specific_cult_doctrine', {})
testimony = data.get('testimony', {})
# 모든 단락을 순회하며 플레이스홀더 교체
replacements = {
'{name}': basic_info.get('name', ''),
'{district}': basic_info.get('district', ''),
'{cult}': basic_info.get('cult', ''),
'{methodist_doctrine}': self._format_answers(methodist),
'{general_cult_doctrine}': self._format_answers(general),
'{specific_cult_doctrine}': self._format_answers(specific),
'{testimony}': testimony.get('content', '')
}
for paragraph in doc.paragraphs:
for key, value in replacements.items():
if key in paragraph.text:
paragraph.text = paragraph.text.replace(key, str(value))
# 테이블도 처리
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for key, value in replacements.items():
if key in cell.text:
cell.text = cell.text.replace(key, str(value))
def _format_answers(self, answers_dict):
"""답변 딕셔너리를 텍스트로 포맷팅"""
if not answers_dict:
return '답변 없음'
result = []
for key, value in answers_dict.items():
if isinstance(value, dict):
# 중첩된 구조 처리
result.append(f"{key}: {self._format_answers(value)}")
else:
result.append(f"{key}: {value}")
return '\n'.join(result)