Generate Excel file step 1

This commit is contained in:
hayano
2024-11-06 07:22:24 +00:00
parent a714557eef
commit ceb783d6bd
6 changed files with 146 additions and 52 deletions

View File

@ -9,6 +9,12 @@ services:
- ..:/app - ..:/app
environment: environment:
- PYTHONPATH=/app - PYTHONPATH=/app
command: python ./testdata/sample.py - POSTGRES_DB=rogdb
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=admin123456
- POSTGRES_HOST=localhost
- POSTGRES_PORT=5432
network_mode: "host"
tty: true tty: true
container_name: python_container # コンテナ名を明示的に指定

View File

@ -4,7 +4,7 @@ WORKDIR /app
# GPGキーの更新とパッケージのインストール # GPGキーの更新とパッケージのインストール
RUN apt-get update --allow-insecure-repositories && \ RUN apt-get update --allow-insecure-repositories && \
apt-get install -y --allow-unauthenticated python3-dev libpq-dev && \ apt-get install -y --allow-unauthenticated python3-dev libpq-dev postgresql-client && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# Pythonパッケージのインストール # Pythonパッケージのインストール

View File

@ -13,16 +13,24 @@ from pathlib import Path
# psycopg2のインポートを追加 # psycopg2のインポートを追加
import psycopg2 import psycopg2
import psycopg2.extras import psycopg2.extras
from copy import copy
from openpyxl.utils import range_boundaries
from .config_handler import ConfigHandler # ini file のロード from .config_handler import ConfigHandler # ini file のロード
from .styles import StyleManager #from .styles import StyleManager
from .merge import MergeManager #from .merge import MergeManager
from .image import ImageManager #from .image import ImageManager
from .conditional import ConditionalFormatManager #from .conditional import ConditionalFormatManager
from .page import PageManager, PaperSizes from .page import PageManager, PaperSizes
import logging import logging
logging.basicConfig(
level=logging.INFO, # INFOレベル以上のログを表示
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
class SumasenExcel: class SumasenExcel:
"""Enhanced Excel handling class with extended functionality""" """Enhanced Excel handling class with extended functionality"""
@ -142,7 +150,7 @@ class SumasenExcel:
return {"status": False, "message": f"Exception in make_report: Error generating report: {str(e)}"} return {"status": False, "message": f"Exception in make_report: Error generating report: {str(e)}"}
def proceed_section(self, section: str, variables: Dict[str, Any]): def proceed_section(self, section: str, variables: Dict[str, Any]):
logging.info(f"make_report.proceed_section step-1:section={section}") print(f"make_report.proceed_section step-1:section={section}")
try: try:
# セクションの設定を取得 # セクションの設定を取得
@ -160,28 +168,29 @@ class SumasenExcel:
new_sheet = self.workbook.create_sheet(title=section_config.get("sheet_name", section)) new_sheet = self.workbook.create_sheet(title=section_config.get("sheet_name", section))
self.worksheet = new_sheet self.worksheet = new_sheet
self.dbname=variables.get('db'), self.dbname=variables.get('db')
self.user=variables.get('username'), self.user=variables.get('username')
self.password=variables.get('password'), self.password=variables.get('password')
self.host=variables.get('host'), self.host=variables.get('host','postgres')
self.port=variables.get('port') self.port=variables.get('port','5432')
if not self.dbname or not self.user or not self.password or not self.host or not self.port: if not self.dbname or not self.user or not self.password or not self.host or not self.port:
return {"status": False, "message": f"Error no database connection information"} return {"status": False, "message": f"Error no database connection information"}
print(f"db={self.dbname},user={self.user},pass={self.password},host={self.host},port={self.port}")
# PostgreSQLに接続 # PostgreSQLに接続
self.conn = psycopg2.connect( self.conn = psycopg2.connect(
self.dbname, dbname=self.dbname,
self.user, user=self.user,
self.password, password=self.password,
self.host, host=self.host,
self.port port=self.port
) )
self._style_manager = StyleManager() #self._style_manager = StyleManager()
self._merge_manager = MergeManager(self.current_sheet) #self._merge_manager = MergeManager(self.current_sheet)
self._image_manager = ImageManager(self.current_sheet) #self._image_manager = ImageManager(self.current_sheet)
self._conditional_manager = ConditionalFormatManager(self.current_sheet) #self._conditional_manager = ConditionalFormatManager(self.current_sheet)
self._page_manager = PageManager(self.current_sheet) #self._page_manager = PageManager(self.current_sheet)
# シートの幅を設定 # シートの幅を設定
fit_to_width = section_config.get("fit_to_width") fit_to_width = section_config.get("fit_to_width")
@ -205,7 +214,8 @@ class SumasenExcel:
# 各グループの設定を取得 # 各グループの設定を取得
for group in group_list: for group in group_list:
ret = self.proceed_group(group,variables) section_group = f"{section}.{group}"
ret = self.proceed_group(section_group,variables)
if ret["status"] == False: if ret["status"] == False:
return ret return ret
@ -224,7 +234,7 @@ class SumasenExcel:
# グループの処理パラメータを取得 # グループの処理パラメータを取得
group_range = group_config.get("group_range") group_range = group_config.get("group_range")
table = group_config.get("table") table = group_config.get("table_name")
where = group_config.get("where") where = group_config.get("where")
if not where or not table or not group_range: if not where or not table or not group_range:
return {"status": False, "message": f"Error invalid group parameters: {group_config}"} return {"status": False, "message": f"Error invalid group parameters: {group_config}"}
@ -232,11 +242,11 @@ class SumasenExcel:
sort = group_config.get("sort") sort = group_config.get("sort")
if not sort: if not sort:
ret = self.proceed_one_record(table,where,group_range,variables) ret = self.proceed_one_record(table,where,group_range,variables)
if ret.status == True: if ret.get("status") == True:
return {"status": True, "message": f"Success generating group: {group}"} return {"status": True, "message": f"Success generating group: {group}"}
else: else:
ret = self.proceed_all_records(table,where,sort,group_range,variables) ret = self.proceed_all_records(table,where,sort,group_range,variables)
if ret.status == True: if ret.get("status") == True:
return {"status": True, "message": f"Success generating group: {group}"} return {"status": True, "message": f"Success generating group: {group}"}
except Exception as e: except Exception as e:
@ -252,12 +262,13 @@ class SumasenExcel:
group_range: 処理対象範囲 group_range: 処理対象範囲
variables: DB接続情報を含む変数辞書 variables: DB接続情報を含む変数辞書
""" """
logging.info(f"make_report.proceed_one_record step-1:table={table},where={where},group_range={group_range}")
try: try:
print(f"make_report.proceed_one_record step-1:table={table},where={where},group_range={group_range}")
# まずself.template_sheetの指定範囲のセルをself.current_sheetにコピーする。 # まずself.template_sheetの指定範囲のセルをself.current_sheetにコピーする。
self.copy_template_to_current(self.range,self.range) self.copy_template_to_current(group_range,group_range)
print(f"step-1")
cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
# SQLクエリを実行 # SQLクエリを実行
@ -265,6 +276,8 @@ class SumasenExcel:
cursor.execute(query) cursor.execute(query)
record = cursor.fetchone() record = cursor.fetchone()
print(f"query={query}")
print(f"record={record}")
if record: if record:
# group_rangeの範囲内のセルを走査 # group_rangeの範囲内のセルを走査
for row in self.current_worksheet: for row in self.current_worksheet:
@ -286,14 +299,36 @@ class SumasenExcel:
cell.value = new_value cell.value = new_value
cursor.close() cursor.close()
self.conn.close()
return {"status": True, "message": f"Success generating group: "} return {"status": True, "message": f"Success generating group: "}
except Exception as e: except Exception as e:
logging.error(f"Error in proceed_one_record: {str(e)}") logging.error(f"Error in proceed_one_record: {str(e)}")
return {"status": False, "message": f"Exception in proceed_one_record:Error generating report: {str(e)}"} return {"status": False, "message": f"Exception in proceed_one_record:Error generating report: {str(e)}"}
def proceed_all_record(self, table: str, where: str, sort: str, group_range: str, variables: Dict[str, Any]): def get_column_letter(self, cell_reference):
"""
セル参照(例:'A12')から列文字を取得
"""
if isinstance(cell_reference, str):
# アルファベット部分を抽出
column = ''.join(c for c in cell_reference if c.isalpha())
if column:
return column
return 'A' # デフォルト値
def get_row_number(self,cell_reference):
"""
セル参照(例:'A12')から行番号を取得
"""
if isinstance(cell_reference, str):
# 数字部分のみを抽出
digits = ''.join(c for c in cell_reference if c.isdigit())
if digits:
return int(digits)
return int(cell_reference)
def proceed_all_records(self, table: str, where: str, sort: str, group_range: str, variables: Dict[str, Any]):
"""複数レコードを取得してシートの値を置き換える """複数レコードを取得してシートの値を置き換える
Args: Args:
@ -303,11 +338,27 @@ class SumasenExcel:
group_range: 処理対象範囲 group_range: 処理対象範囲
variables: DB接続情報を含む変数辞書 variables: DB接続情報を含む変数辞書
""" """
logging.info(f"make_report.proceed_all_record step-1:table={table},where={where},group_range={group_range}") print(f"make_report.proceed_all_record step-1:table={table},where={where},group_range={group_range}")
try: try:
# グループ範囲の行数を取得(セル参照対応)
if not group_range or ':' not in group_range:
raise ValueError(f"Invalid group_range format: {group_range}")
# グループ範囲の行数を取得 # グループ範囲の行数を取得
start_row, end_row = map(int, group_range.split(':')[0].split(',')) range_parts = group_range.split(':')
logging.info(f"Processing range_parts: {range_parts}") # デバッグ用ログ
start_row = self.get_row_number(range_parts[0].strip())
start_col = self.get_column_letter(range_parts[0].strip())
end_row = self.get_row_number(range_parts[1].strip())
end_col = self.get_column_letter(range_parts[1].strip())
if start_row > end_row:
raise ValueError(f"Invalid row range: start_row ({start_row}) > end_row ({end_row})")
template_rows = end_row - start_row + 1 template_rows = end_row - start_row + 1
cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
@ -316,13 +367,13 @@ class SumasenExcel:
query = f"SELECT * FROM {table} WHERE {where} ORDER BY {sort}" query = f"SELECT * FROM {table} WHERE {where} ORDER BY {sort}"
cursor.execute(query) cursor.execute(query)
records = cursor.fetchall() records = cursor.fetchall()
print(f"query={query}, records={len(records)}")
current_row = start_row current_row = start_row
for record in records: for record in records:
# テンプレート範囲をコピー # テンプレート範囲をコピー
self.copy_template_to_current( self.copy_template_to_current(f"{start_col}{start_row}:{end_col}{end_row}",
f"{start_row},{end_row}", f"{start_col}{current_row}:{end_col}{current_row + template_rows - 1}"
f"{current_row},{current_row + template_rows - 1}"
) )
# コピーした範囲内のセルを走査して値を置換 # コピーした範囲内のセルを走査して値を置換
@ -354,15 +405,43 @@ class SumasenExcel:
logging.error(f"Error in proceed_all_record: {str(e)}") logging.error(f"Error in proceed_all_record: {str(e)}")
return {"status": False, "message": f"Exception in proceed_all_record:Error processing records: {str(e)}"} return {"status": False, "message": f"Exception in proceed_all_record:Error processing records: {str(e)}"}
def copy_template_to_current(self,range,target_range): def copy_template_to_current(self, orig_range, target_range):
# テンプレートシートから現在のシートにデータをコピー try:
source_range = self.template_sheet.range(range) # コピーする範囲を指定 print(f"orig_rage={orig_range},target_range={target_range}")
target_range = self.current_sheet.range(target_range) # 貼り付け先の範囲を指定
# 値、数式、フォーマットをコピー # 範囲をパースする
source_range.copy(target_range) min_col, min_row, max_col, max_row = range_boundaries(orig_range)
print(f"min_col, min_row, max_col, max_row = {min_col}, {min_row}, {max_col}, {max_row}")
# 新しいシートを作成(必要な場合)
if 'Sheet' not in self.workbook.sheetnames:
self.current_sheet = self.workbook.create_sheet('Sheet')
else:
self.current_sheet = self.workbook['Sheet']
# セルの内容とスタイルをコピー
for row in range(min_row, max_row + 1):
for col in range(min_col, max_col + 1):
source_cell = self.template_sheet.cell(row=row, column=col)
target_cell = self.current_sheet.cell(row=row, column=col)
# 値のコピー
target_cell.value = source_cell.value
# スタイルのコピー
if source_cell.has_style:
target_cell.font = copy(source_cell.font)
target_cell.border = copy(source_cell.border)
target_cell.fill = copy(source_cell.fill)
target_cell.number_format = source_cell.number_format
target_cell.protection = copy(source_cell.protection)
target_cell.alignment = copy(source_cell.alignment)
return {"status": True, "message": "Successfully copied template range"}
except Exception as e:
logging.error(f"Error in copy_template_to_current: {str(e)}")
return {"status": False, "message": f"Exception in copy_template_to_current: {str(e)}"}
# Style operations # Style operations
def apply_style( def apply_style(

Binary file not shown.

View File

@ -2,7 +2,16 @@ from sumaexcel import SumasenExcel
import logging import logging
# 初期化 # 初期化
variables = {"zekken_number":"5033","event_code":"FC岐阜"} # 初期化
variables = {
"zekken_number":"5033",
"event_code":"FC岐阜",
"db":"rogdb",
"username":"admin",
"password":"admin123456",
"host":"localhost",
"port":"5432"
}
excel = SumasenExcel(document="test", variables=variables, docbase="./testdata") excel = SumasenExcel(document="test", variables=variables, docbase="./testdata")
logging.info("Excelファイル作成 step-1") logging.info("Excelファイル作成 step-1")

View File

@ -1,7 +1,7 @@
[basic] [basic]
template_file=certificate_template.xlsx template_file=certificate_template.xlsx
doc_file=certificate_[zekken_number].xlsx doc_file=certificate_[zekken_number].xlsx
sections=section1,section2 sections=section1
maxcol=8 maxcol=8
[section1] [section1]
@ -11,14 +11,14 @@ fit_to_width=1
orientation=portrait orientation=portrait
[section1.group1] [section1.group1]
table_name=rog_entry table_name=mv_entry_details
where=zekken_number='[zekken_number]' and event_code='[event_code]' where=zekken_number='[zekken_number]' and event_name='[event_code]'
group_range=0,0,8,11 group_range=A1:H11
[section1.group2] [section1.group2]
table_name=gps_checkins table_name=gps_checkins
where=zekken_number='[zekken_number]' and event_code='[event_code]' where=zekken_number='[zekken_number]' and event_code='[event_code]'
sort=order sort=path_order
group_range=0,12,8,12 group_range=A12:H12