From 0c2dfec7ddf06c2a0afd2b4ada2e1e3fafa05c22 Mon Sep 17 00:00:00 2001 From: Akira Date: Tue, 5 Nov 2024 07:46:21 +0900 Subject: [PATCH] basic debugging step 1 --- .../excel_lib/docker/docker-compose.yml | 2 +- .../excel_lib/docker/python/Dockerfile | 11 +- SumasenLibs/excel_lib/requirements.txt | 2 +- .../excel_lib/sumaexcel/config_handler.py | 166 +++++++ SumasenLibs/excel_lib/sumaexcel/sumaexcel.py | 433 ++++++++++++------ .../testdata/certificate_template.xlsx | Bin 0 -> 10461 bytes SumasenLibs/excel_lib/testdata/sample.py | 62 +-- SumasenLibs/excel_lib/testdata/test.ini | 17 +- 8 files changed, 499 insertions(+), 194 deletions(-) create mode 100644 SumasenLibs/excel_lib/sumaexcel/config_handler.py create mode 100644 SumasenLibs/excel_lib/testdata/certificate_template.xlsx diff --git a/SumasenLibs/excel_lib/docker/docker-compose.yml b/SumasenLibs/excel_lib/docker/docker-compose.yml index 6355d86..e0b1267 100644 --- a/SumasenLibs/excel_lib/docker/docker-compose.yml +++ b/SumasenLibs/excel_lib/docker/docker-compose.yml @@ -9,6 +9,6 @@ services: - ..:/app environment: - PYTHONPATH=/app - command: /bin/bash + command: python ./testdata/sample.py tty: true diff --git a/SumasenLibs/excel_lib/docker/python/Dockerfile b/SumasenLibs/excel_lib/docker/python/Dockerfile index 01d4495..f7a1e5d 100644 --- a/SumasenLibs/excel_lib/docker/python/Dockerfile +++ b/SumasenLibs/excel_lib/docker/python/Dockerfile @@ -2,10 +2,10 @@ FROM python:3.9-slim WORKDIR /app -# 必要なシステムパッケージのインストール -RUN apt-get update && apt-get install -y \ - git \ - && rm -rf /var/lib/apt/lists/* +# GPGキーの更新とパッケージのインストール +RUN apt-get update --allow-insecure-repositories && \ + apt-get install -y --allow-unauthenticated python3-dev libpq-dev && \ + rm -rf /var/lib/apt/lists/* # Pythonパッケージのインストール COPY requirements.txt . @@ -16,12 +16,11 @@ COPY . . RUN pip install --no-cache-dir -r requirements.txt # 開発用パッケージのインストール -RUN pip install --no-cache-dir \ +RUN pip install --no-cache-dir --upgrade pip \ pytest \ pytest-cov \ flake8 - # パッケージのインストール RUN pip install -e . diff --git a/SumasenLibs/excel_lib/requirements.txt b/SumasenLibs/excel_lib/requirements.txt index 3522b34..8938e66 100644 --- a/SumasenLibs/excel_lib/requirements.txt +++ b/SumasenLibs/excel_lib/requirements.txt @@ -2,4 +2,4 @@ openpyxl>=3.0.0 pandas>=1.0.0 pillow>=8.0.0 configparser>=5.0.0 - +psycopg2-binary==2.9.9 diff --git a/SumasenLibs/excel_lib/sumaexcel/config_handler.py b/SumasenLibs/excel_lib/sumaexcel/config_handler.py new file mode 100644 index 0000000..d6f266a --- /dev/null +++ b/SumasenLibs/excel_lib/sumaexcel/config_handler.py @@ -0,0 +1,166 @@ +# config_handler.py +# +import configparser +import os +from typing import Any, Dict, Optional + +import configparser +import os +import re +from typing import Any, Dict, Optional + +class ConfigHandler: + """変数置換機能付きの設定ファイル管理クラス""" + + def __init__(self, ini_file_path: str, variables: Dict[str, str] = None): + """ + Args: + ini_file_path (str): INIファイルのパス + variables (Dict[str, str], optional): 置換用の変数辞書 + """ + self.ini_file_path = ini_file_path + self.variables = variables or {} + self.config = configparser.ConfigParser() + self.load_config() + + def _substitute_variables(self, text: str) -> str: + """ + テキスト内の変数を置換する + + Args: + text (str): 置換対象のテキスト + + Returns: + str: 置換後のテキスト + """ + # ${var}形式の変数を置換 + pattern1 = r'\${([^}]+)}' + # [var]形式の変数を置換 + pattern2 = r'\[([^\]]+)\]' + + def replace_var(match): + var_name = match.group(1) + return self.variables.get(var_name, match.group(0)) + + # 両方のパターンで置換を実行 + text = re.sub(pattern1, replace_var, text) + text = re.sub(pattern2, replace_var, text) + + return text + + def load_config(self) -> None: + """設定ファイルを読み込み、変数を置換する""" + if not os.path.exists(self.ini_file_path): + raise FileNotFoundError(f"設定ファイルが見つかりません: {self.ini_file_path}") + + # まず生のテキストとして読み込む + with open(self.ini_file_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 変数を置換 + substituted_content = self._substitute_variables(content) + + # 置換済みの内容を StringIO 経由で configparser に読み込ませる + from io import StringIO + self.config.read_file(StringIO(substituted_content)) + + def get_value(self, section: str, key: str, default: Any = None) -> Optional[str]: + """ + 指定されたセクションのキーの値を取得する + + Args: + section (str): セクション名 + key (str): キー名 + default (Any): デフォルト値(オプション) + + Returns: + Optional[str]: 設定値。存在しない場合はデフォルト値 + """ + try: + return self.config[section][key] + except KeyError: + return default + + def get_section(self, section: str) -> Dict[str, str]: + """ + 指定されたセクションの全ての設定を取得する + + Args: + section (str): セクション名 + + Returns: + Dict[str, str]: セクションの設定をディクショナリで返す + """ + try: + return dict(self.config[section]) + except KeyError: + return {} + + def get_all_sections(self) -> Dict[str, Dict[str, str]]: + """ + 全てのセクションの設定を取得する + + Returns: + Dict[str, Dict[str, str]]: 全セクションの設定をネストされたディクショナリで返す + """ + return {section: dict(self.config[section]) for section in self.config.sections()} + + + + + +# 使用例 +if __name__ == "__main__": + # サンプルのINIファイル作成 + sample_ini = """ +[Database] +host = localhost +port = 5432 +database = mydb +user = admin +password = secret + +[Application] +debug = true +log_level = INFO +max_connections = 100 + +[Paths] +data_dir = /var/data +log_file = /var/log/app.log +""" + + # サンプルINIファイルを作成 + with open('config.ini', 'w', encoding='utf-8') as f: + f.write(sample_ini) + + # 設定を読み込んで使用 + config = ConfigHandler('config.ini') + + # 特定の値を取得 + db_host = config.get_value('Database', 'host') + db_port = config.get_value('Database', 'port') + print(f"Database connection: {db_host}:{db_port}") + + # セクション全体を取得 + db_config = config.get_section('Database') + print("Database configuration:", db_config) + + # 全ての設定を取得 + all_config = config.get_all_sections() + print("All configurations:", all_config) + + +# サンプル: +# # 設定ファイルから値を取得 +# config = ConfigHandler('config.ini') +# +# # データベース設定を取得 +# db_host = config.get_value('Database', 'host') +# db_port = config.get_value('Database', 'port') +# db_name = config.get_value('Database', 'database') +# +# # アプリケーション設定を取得 +# debug_mode = config.get_value('Application', 'debug') +# log_level = config.get_value('Application', 'log_level') +# diff --git a/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py b/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py index 24581e5..26841e3 100644 --- a/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py +++ b/SumasenLibs/excel_lib/sumaexcel/sumaexcel.py @@ -10,183 +10,360 @@ import shutil from datetime import datetime from typing import Optional, Dict, Any, Union, Tuple from pathlib import Path +# psycopg2のインポートを追加 +import psycopg2 +import psycopg2.extras + +from .config_handler import ConfigHandler # ini file のロード from .styles import StyleManager from .merge import MergeManager from .image import ImageManager from .conditional import ConditionalFormatManager from .page import PageManager, PaperSizes +import logging class SumasenExcel: """Enhanced Excel handling class with extended functionality""" - def __init__(self, debug: bool = False): - self.debug = debug + def __init__(self, document: str, variables: Dict[str, Any], + docbase: str = "./docbase") -> Dict[str, str]: + + """Initialize Excel document with basic settings + + Args: + document: Document name + variables: Variables for document + docbase: Base directory for documents (default: "./docbase") + + Returns: + Dict with status (true/false) and optional error message + + """ + self.debug = True + self.workbook = None self.template_filepath = None self.output_filepath = None self.current_sheet = None + self.dbname = None + self.user = None + self.password = None + self.host = None + self.port = None + self._style_manager = None self._merge_manager = None self._image_manager = None self._conditional_manager = None self._page_manager = None - def init(self, username: str, project_id: str, document: str, - lang: str = "jp", docbase: str = "./docbase") -> Dict[str, str]: - """Initialize Excel document with basic settings - - Args: - username: User name for file operations - project_id: Project identifier - document: Document name - lang: Language code (default: "jp") - docbase: Base directory for documents (default: "./docbase") - - Returns: - Dict with status ("ACK"/"NCK") and optional error message - """ try: - self.username = username - self.project_id = project_id - self.language = lang - - # Setup directory structure + # document base を設定 self.docpath = docbase - self.docpath2 = f"{docbase}/{project_id}" + if not os.path.exists(docbase): + logging.error(f"Document base directory not found: {docbase}") + + # ini fileをロード + self.ini_file_path = f"{docbase}/{document}.ini" + self.conf = ConfigHandler(self.ini_file_path, variables) + if not os.path.exists(self.ini_file_path): + logging.error(f"INI file not found: {self.ini_file_path}") - # Create directories if they don't exist - for path in [docbase, self.docpath2]: - if not os.path.exists(path): - os.makedirs(path, mode=0o755) + # basic section をロード + basic = self.conf.get_section("basic") + if not basic: + logging.error(f"Basic section not found in INI file: {self.ini_file_path}") - # Load template - inifile = f"{document}.ini" - self.inifilepath = f"{self.docpath2}/{inifile}" + self.basic = basic - if not os.path.exists(self.inifilepath): - return {"status": "NCK", "message": f"INI file not found: {self.inifilepath}"} + # basicセクションから必要なパラメータを取得 + template_file = basic.get("template_file") + if not template_file: + logging.error("template_file not found in basic section") - # Load template workbook - template_file = self._get_ini_param("basic", f"templatefile_{lang}") - self.template_filepath = f"{self.docpath2}/{template_file}" + doc_file = basic.get("doc_file") + if not doc_file: + logging.error("doc_file not found in basic section") + self.maxcol = basic.get("maxcol") + if not self.maxcol: + self.maxcol = 100 # デフォルト値を設定 + logging.warning("maxcol not found in basic section. Defaulting to 100") + else: + self.maxcol = int(self.maxcol) + + sections = basic.get("sections") + if not sections: + logging.error("sections not found in basic section") + + + # セクションをリストに変換 + self.section_list = [s.strip() for s in sections.split(",")] + if not self.section_list: + logging.error("sections not found in basic section") + + # 出力ファイルパスを設定 + self.output_filepath = f"{self.docpath}/{doc_file}" + # 新規のExcelワークブックを作成 + self.workbook = openpyxl.Workbook() + # デフォルトで作成されるシートを削除 + self.workbook.remove(self.workbook.active) + + # テンプレートワークブックをロード + self.template_filepath = f"{self.docpath}/{template_file}" if not os.path.exists(self.template_filepath): - # Copy from default if not exists - default_template = f"{self.docpath}/{template_file}" - shutil.copy2(default_template, self.template_filepath) + logging.error(f"Template file not found: {self.template_filepath}") - self.workbook = openpyxl.load_workbook(self.template_filepath) - - return {"status": "ACK"} + self.template_workbook = openpyxl.load_workbook(self.template_filepath) + self.template_sheet = self.template_workbook.active except Exception as e: - return {"status": "NCK", "message": str(e)} + logging.error(f"Error initializing Excel document: {str(e)}") + + def make_report(self,variables: Dict[str, Any]): + """レポートを生成する""" + logging.info("make_report step-1") + try: + ret_status = True + # セクションごとに処理を実行(ワークシート単位) + for section in self.section_list: + ret = self.proceed_section(section,variables) + if ret["status"] == False: + message = ret.get("message", "No message provided") + return {"status": False, "message": f"Fail generating section: {section}...{message}"} - def _get_ini_param(self, section: str, param: str) -> Optional[str]: - """Get parameter from INI file + # 生成したワークブックを保存 + self.workbook.save(self.output_filepath) + return {"status": True, "message": f"Report generated successfully : {self.output_filepath}", "filepath":self.output_filepath} - Args: - section: INI file section - param: Parameter name + except Exception as 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]): + logging.info(f"make_report.proceed_section step-1:section={section}") - Returns: - Parameter value or None if not found - """ try: - # Use configparser to handle INI files - import configparser - config = configparser.ConfigParser() - config.read(self.inifilepath) - return config[section][param] - except: - return None + # セクションの設定を取得 + section_config = self.conf.get_section(section) + # セクションが存在しない場合はスキップ + if not section_config: + return {"status": False, "message": f"Error no section found: {section}"} - def make_report(self, db, data_rec: Dict[str, Any], - out_filename: Optional[str] = None, - screen_index: int = 0) -> None: - """Generate Excel report from template + # テンプレートシートをコピー + template_sheet_name = section_config.get("template_sheet") + if not template_sheet_name or template_sheet_name not in self.template_workbook.sheetnames: + return {"status": False, "message": f"Error no template sheet found: {template_sheet_name}"} + # シートの名前を設定 + 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') + 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"} + + # PostgreSQLに接続 + self.conn = psycopg2.connect( + self.dbname, + self.user, + self.password, + self.host, + 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) + + # シートの幅を設定 + fit_to_width = section_config.get("fit_to_width") + if fit_to_width: + new_sheet.sheet_view.zoomScaleNormal = float(fit_to_width) + + # シートの向きを設定 + orientation = section_config.get("orientation") + new_sheet.sheet_view.orientation = orientation if orientation else "portrait" + self.current_worksheet = new_sheet + + # グループ定義を取得 + groups = section_config.get("groups") + if not groups: + return {"status": False, "message": f"Error no group definitions found: {section}"} + + # グループをリストに変換 + group_list = [g.strip() for g in groups.split(",")] + if not group_list: + return {"status": False, "message": f"Error invalid group definitions found: {section}"} + + # 各グループの設定を取得 + for group in group_list: + ret = self.proceed_group(group,variables) + if ret["status"] == False: + return ret + + return {"status": True, "message": f"Success generating section: {section}"} + + except Exception as e: + return {"status": False, "message": f"Exception in proceed_section : Error generating report: {str(e)}"} + + def proceed_group(self,group:str,variables: Dict[str, Any]): + logging.info(f"make_report.proceed_group step-1:section={group}") + + try: + group_config = self.conf.get_section(group) + if not group_config: + return {"status": False, "message": f"Error no group section found: {group}"} + + # グループの処理パラメータを取得 + group_range = group_config.get("group_range") + table = group_config.get("table") + 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}"} + + sort = group_config.get("sort") + if not sort: + ret = self.proceed_one_record(table,where,group_range,variables) + if ret.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: + return {"status": True, "message": f"Success generating group: {group}"} + + except Exception as e: + logging.error(f"Error in proceed_group: {str(e)}") + return {"status": False, "message": f"Exception in proceed_group : Error generating report: {str(e)}"} + + def proceed_one_record(self,table:str,where:str,group_range:str,variables: Dict[str, Any]): + """1レコードのデータを取得してシートの値を置き換える + Args: - db: Database connection - data_rec: Data records to populate report - out_filename: Optional output filename - screen_index: Screen index for multi-screen reports - """ - # Get output filename - if out_filename: - outfile = f"{out_filename}_{self._get_ini_param('basic', 'doc_file')}" - else: - outfile = self._get_ini_param('basic', 'doc_file') + table: テーブル名 + where: WHERE句 + group_range: 処理対象範囲 + variables: DB接続情報を含む変数辞書 + """ + logging.info(f"make_report.proceed_one_record step-1:table={table},where={where},group_range={group_range}") - self.output_filepath = f"{self.docpath2}/{outfile}" + try: + # まずself.template_sheetの指定範囲のセルをself.current_sheetにコピーする。 + self.copy_template_to_current(self.range,self.range) - # Process sections - sections = self._get_ini_param('basic', 'sections') - if not sections: - return - - for section in sections.split(','): - self._process_section(section, db, data_rec, screen_index) - - # Save workbook - self.workbook.save(self.output_filepath) - - def _process_section(self, section: str, db, data_rec: Dict[str, Any], - screen_index: int) -> None: - """Process individual section of report + 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() + + if record: + # group_rangeの範囲内のセルを走査 + for row in self.current_worksheet: + for cell in row: + if cell.value and isinstance(cell.value, str): + # [field_name]形式の文字列を検索 + import re + matches = re.findall(r'\[(.*?)\]', cell.value) + + # マッチした場合、フィールド値で置換 + if matches: + new_value = cell.value + for field_name in matches: + if field_name in record: + new_value = new_value.replace( + f'[{field_name}]', + str(record[field_name]) + ) + 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]): + """複数レコードを取得してシートの値を置き換える + Args: - section: Section name - db: Database connection - data_rec: Data records - screen_index: Screen index - """ - # Get template sheet - sheet_orig = self._get_ini_param(section, 'sheet') - sheet_name = self._get_ini_param(section, f"sheetname_{self.language}") + table: テーブル名 + where: WHERE句 + sort: ORDER BY句 + group_range: 処理対象範囲 + variables: DB接続情報を含む変数辞書 + """ + logging.info(f"make_report.proceed_all_record step-1:table={table},where={where},group_range={group_range}") - if not sheet_orig or not sheet_name: - return + try: + # グループ範囲の行数を取得 + start_row, end_row = map(int, group_range.split(':')[0].split(',')) + template_rows = end_row - start_row + 1 - # Copy template sheet - template_sheet = self.workbook[sheet_orig] - new_sheet = self.workbook.copy_worksheet(template_sheet) - new_sheet.title = sheet_name + cursor = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) + + # SQLクエリを実行 + query = f"SELECT * FROM {table} WHERE {where} ORDER BY {sort}" + cursor.execute(query) + records = cursor.fetchall() + + 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}" + ) + + # コピーした範囲内のセルを走査して値を置換 + for row in range(current_row, current_row + template_rows): + for cell in self.current_worksheet[row]: + if cell.value and isinstance(cell.value, str): + # [field_name]形式の文字列を検索 + import re + matches = re.findall(r'\[(.*?)\]', cell.value) + + # マッチした場合、フィールド値で置換 + if matches: + new_value = cell.value + for field_name in matches: + if field_name in record: + new_value = new_value.replace( + f'[{field_name}]', + str(record[field_name]) + ) + cell.value = new_value + + current_row += template_rows + + cursor.close() + self.conn.close() + return {"status": True, "message": "Success processing all records"} + + except Exception as 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)}"} - # Process groups - groups = self._get_ini_param(section, 'groups') - if groups: - for group in groups.split(','): - self._process_group(new_sheet, section, group, db, data_rec, screen_index) - - def _process_group(self, sheet, section: str, group: str, - db, data_rec: Dict[str, Any], screen_index: int) -> None: - """Process group within section - - Args: - sheet: Worksheet to process - section: Section name - group: Group name - db: Database connection - data_rec: Data records - screen_index: Screen index - """ - pass # Implementation details will follow + def copy_template_to_current(self,range,target_range): + # テンプレートシートから現在のシートにデータをコピー + source_range = self.template_sheet.range(range) # コピーする範囲を指定 + target_range = self.current_sheet.range(target_range) # 貼り付け先の範囲を指定 + + # 値、数式、フォーマットをコピー + source_range.copy(target_range) - - def init_sheet(self, sheet_name: str) -> None: - """Initialize worksheet and managers""" - self.current_sheet = self.workbook[sheet_name] - 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) - # Style operations def apply_style( self, diff --git a/SumasenLibs/excel_lib/testdata/certificate_template.xlsx b/SumasenLibs/excel_lib/testdata/certificate_template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..19b4c60491d1ad90e9f3149867ff2938a49b5531 GIT binary patch literal 10461 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 literal 0 HcmV?d00001 diff --git a/SumasenLibs/excel_lib/testdata/sample.py b/SumasenLibs/excel_lib/testdata/sample.py index 3e8c542..2ccbdad 100644 --- a/SumasenLibs/excel_lib/testdata/sample.py +++ b/SumasenLibs/excel_lib/testdata/sample.py @@ -1,55 +1,19 @@ from sumaexcel import SumasenExcel +import logging # 初期化 -excel = SumasenExcel() -excel.init("username", "project_id", "document") +variables = {"zekken_number":"5033","event_code":"FC岐阜"} +excel = SumasenExcel(document="test", variables=variables, docbase="./testdata") + +logging.info("Excelファイル作成 step-1") # シート初期化 -excel.init_sheet("Sheet1") - -# スタイル適用 -excel.apply_style( - "A1:D10", - font={"name": "Arial", "size": 12, "bold": True}, - fill={"start_color": "FFFF00"}, - alignment={"horizontal": "center"} -) - -# セルのマージ -excel.merge_range(1, 1, 1, 4) - -# 画像追加 -excel.add_image( - "logo.png", - position=(1, 1), - size=(100, 100) -) - -# 条件付き書式 -excel.add_conditional_format( - "B2:B10", - format_type="color_scale", - min_color="00FF0000", - max_color="0000FF00" -) - -# ページ設定 -excel.setup_page( - orientation="landscape", - paper_size=PaperSizes.A4, - margins={ - "left": 1.0, - "right": 1.0, - "top": 1.0, - "bottom": 1.0 - }, - header_footer={ - "odd_header": "&L&BPage &P of &N&C&BConfidential", - "odd_footer": "&RDraft" - } -) - -# レポート生成 -excel.make_report(db, data_rec) - +ret = excel.make_report(variables=variables) +logging.info(f"Excelファイル作成 step-2 : ret={ret}") +if ret["status"]==True: + filepath=ret["filepath"] + logging.info(f"Excelファイル作成 : ret.filepath={filepath}") +else: + message = ret.get("message", "No message provided") + logging.error(f"Excelファイル作成失敗 : ret.message={message}") diff --git a/SumasenLibs/excel_lib/testdata/test.ini b/SumasenLibs/excel_lib/testdata/test.ini index a75b2f2..69ced26 100644 --- a/SumasenLibs/excel_lib/testdata/test.ini +++ b/SumasenLibs/excel_lib/testdata/test.ini @@ -1,25 +1,24 @@ [basic] -templatefile_jp="certificate_template.xlsx" -doc_file="certificate_[zekken_number].xlsx" +template_file=certificate_template.xlsx +doc_file=certificate_[zekken_number].xlsx sections=section1,section2 -developer=Sumasen maxcol=8 [section1] -sheet="certificate" -sheetname_jp="岐阜ロゲ通過証明書" -groups="group1,group2" +template_sheet=certificate +groups=group1,group2 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" +where=zekken_number='[zekken_number]' and event_code='[event_code]' +group_range=0,0,8,11 + [section1.group2] 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 group_range=0,12,8,12