Generate Excel file step 1
This commit is contained in:
@ -9,6 +9,12 @@ services:
|
||||
- ..:/app
|
||||
environment:
|
||||
- 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
|
||||
container_name: python_container # コンテナ名を明示的に指定
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ WORKDIR /app
|
||||
|
||||
# GPGキーの更新とパッケージのインストール
|
||||
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/*
|
||||
|
||||
# Pythonパッケージのインストール
|
||||
|
||||
@ -13,16 +13,24 @@ from pathlib import Path
|
||||
# psycopg2のインポートを追加
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
from copy import copy
|
||||
from openpyxl.utils import range_boundaries
|
||||
|
||||
from .config_handler import ConfigHandler # ini file のロード
|
||||
|
||||
from .styles import StyleManager
|
||||
from .merge import MergeManager
|
||||
from .image import ImageManager
|
||||
from .conditional import ConditionalFormatManager
|
||||
#from .styles import StyleManager
|
||||
#from .merge import MergeManager
|
||||
#from .image import ImageManager
|
||||
#from .conditional import ConditionalFormatManager
|
||||
from .page import PageManager, PaperSizes
|
||||
|
||||
import logging
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, # INFOレベル以上のログを表示
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
class SumasenExcel:
|
||||
"""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)}"}
|
||||
|
||||
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:
|
||||
# セクションの設定を取得
|
||||
@ -160,28 +168,29 @@ class SumasenExcel:
|
||||
new_sheet = self.workbook.create_sheet(title=section_config.get("sheet_name", section))
|
||||
self.worksheet = new_sheet
|
||||
|
||||
self.dbname=variables.get('db'),
|
||||
self.user=variables.get('username'),
|
||||
self.password=variables.get('password'),
|
||||
self.host=variables.get('host'),
|
||||
self.port=variables.get('port')
|
||||
self.dbname=variables.get('db')
|
||||
self.user=variables.get('username')
|
||||
self.password=variables.get('password')
|
||||
self.host=variables.get('host','postgres')
|
||||
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:
|
||||
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に接続
|
||||
self.conn = psycopg2.connect(
|
||||
self.dbname,
|
||||
self.user,
|
||||
self.password,
|
||||
self.host,
|
||||
self.port
|
||||
dbname=self.dbname,
|
||||
user=self.user,
|
||||
password=self.password,
|
||||
host=self.host,
|
||||
port=self.port
|
||||
)
|
||||
|
||||
self._style_manager = StyleManager()
|
||||
self._merge_manager = MergeManager(self.current_sheet)
|
||||
self._image_manager = ImageManager(self.current_sheet)
|
||||
self._conditional_manager = ConditionalFormatManager(self.current_sheet)
|
||||
self._page_manager = PageManager(self.current_sheet)
|
||||
#self._style_manager = StyleManager()
|
||||
#self._merge_manager = MergeManager(self.current_sheet)
|
||||
#self._image_manager = ImageManager(self.current_sheet)
|
||||
#self._conditional_manager = ConditionalFormatManager(self.current_sheet)
|
||||
#self._page_manager = PageManager(self.current_sheet)
|
||||
|
||||
# シートの幅を設定
|
||||
fit_to_width = section_config.get("fit_to_width")
|
||||
@ -205,7 +214,8 @@ class SumasenExcel:
|
||||
|
||||
# 各グループの設定を取得
|
||||
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:
|
||||
return ret
|
||||
|
||||
@ -224,7 +234,7 @@ class SumasenExcel:
|
||||
|
||||
# グループの処理パラメータを取得
|
||||
group_range = group_config.get("group_range")
|
||||
table = group_config.get("table")
|
||||
table = group_config.get("table_name")
|
||||
where = group_config.get("where")
|
||||
if not where or not table or not group_range:
|
||||
return {"status": False, "message": f"Error invalid group parameters: {group_config}"}
|
||||
@ -232,11 +242,11 @@ class SumasenExcel:
|
||||
sort = group_config.get("sort")
|
||||
if not sort:
|
||||
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}"}
|
||||
else:
|
||||
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}"}
|
||||
|
||||
except Exception as e:
|
||||
@ -252,19 +262,22 @@ class SumasenExcel:
|
||||
group_range: 処理対象範囲
|
||||
variables: DB接続情報を含む変数辞書
|
||||
"""
|
||||
logging.info(f"make_report.proceed_one_record step-1:table={table},where={where},group_range={group_range}")
|
||||
|
||||
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.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)
|
||||
|
||||
# SQLクエリを実行
|
||||
query = f"SELECT * FROM {table} WHERE {where} LIMIT 1"
|
||||
cursor.execute(query)
|
||||
record = cursor.fetchone()
|
||||
|
||||
|
||||
print(f"query={query}")
|
||||
print(f"record={record}")
|
||||
if record:
|
||||
# group_rangeの範囲内のセルを走査
|
||||
for row in self.current_worksheet:
|
||||
@ -286,14 +299,36 @@ class SumasenExcel:
|
||||
cell.value = new_value
|
||||
|
||||
cursor.close()
|
||||
self.conn.close()
|
||||
return {"status": True, "message": f"Success generating group: "}
|
||||
|
||||
except Exception as 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)}"}
|
||||
|
||||
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:
|
||||
@ -303,11 +338,27 @@ class SumasenExcel:
|
||||
group_range: 処理対象範囲
|
||||
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:
|
||||
# グループ範囲の行数を取得(セル参照対応)
|
||||
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
|
||||
|
||||
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}"
|
||||
cursor.execute(query)
|
||||
records = cursor.fetchall()
|
||||
|
||||
print(f"query={query}, records={len(records)}")
|
||||
|
||||
current_row = start_row
|
||||
for record in records:
|
||||
# テンプレート範囲をコピー
|
||||
self.copy_template_to_current(
|
||||
f"{start_row},{end_row}",
|
||||
f"{current_row},{current_row + template_rows - 1}"
|
||||
self.copy_template_to_current(f"{start_col}{start_row}:{end_col}{end_row}",
|
||||
f"{start_col}{current_row}:{end_col}{current_row + template_rows - 1}"
|
||||
)
|
||||
|
||||
# コピーした範囲内のセルを走査して値を置換
|
||||
@ -354,15 +405,43 @@ class SumasenExcel:
|
||||
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)}"}
|
||||
|
||||
def copy_template_to_current(self,range,target_range):
|
||||
# テンプレートシートから現在のシートにデータをコピー
|
||||
source_range = self.template_sheet.range(range) # コピーする範囲を指定
|
||||
target_range = self.current_sheet.range(target_range) # 貼り付け先の範囲を指定
|
||||
def copy_template_to_current(self, orig_range, target_range):
|
||||
try:
|
||||
print(f"orig_rage={orig_range},target_range={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']
|
||||
|
||||
# 値、数式、フォーマットをコピー
|
||||
source_range.copy(target_range)
|
||||
|
||||
|
||||
# セルの内容とスタイルをコピー
|
||||
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
|
||||
def apply_style(
|
||||
|
||||
BIN
SumasenLibs/excel_lib/testdata/certificate_5033.xlsx
vendored
Normal file
BIN
SumasenLibs/excel_lib/testdata/certificate_5033.xlsx
vendored
Normal file
Binary file not shown.
11
SumasenLibs/excel_lib/testdata/sample.py
vendored
11
SumasenLibs/excel_lib/testdata/sample.py
vendored
@ -2,7 +2,16 @@ from sumaexcel import SumasenExcel
|
||||
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")
|
||||
|
||||
logging.info("Excelファイル作成 step-1")
|
||||
|
||||
12
SumasenLibs/excel_lib/testdata/test.ini
vendored
12
SumasenLibs/excel_lib/testdata/test.ini
vendored
@ -1,7 +1,7 @@
|
||||
[basic]
|
||||
template_file=certificate_template.xlsx
|
||||
doc_file=certificate_[zekken_number].xlsx
|
||||
sections=section1,section2
|
||||
sections=section1
|
||||
maxcol=8
|
||||
|
||||
[section1]
|
||||
@ -11,14 +11,14 @@ fit_to_width=1
|
||||
orientation=portrait
|
||||
|
||||
[section1.group1]
|
||||
table_name=rog_entry
|
||||
where=zekken_number='[zekken_number]' and event_code='[event_code]'
|
||||
group_range=0,0,8,11
|
||||
table_name=mv_entry_details
|
||||
where=zekken_number='[zekken_number]' and event_name='[event_code]'
|
||||
group_range=A1:H11
|
||||
|
||||
|
||||
[section1.group2]
|
||||
table_name=gps_checkins
|
||||
where=zekken_number='[zekken_number]' and event_code='[event_code]'
|
||||
sort=order
|
||||
group_range=0,12,8,12
|
||||
sort=path_order
|
||||
group_range=A12:H12
|
||||
|
||||
|
||||
Reference in New Issue
Block a user