暫無描述

gen_report.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. from openpyxl import load_workbook, Workbook
  2. from datetime import datetime
  3. from openpyxl.drawing.image import Image
  4. import re
  5. from openpyxl.drawing.spreadsheet_drawing import AbsoluteAnchor
  6. from openpyxl.drawing.xdr import XDRPoint2D, XDRPositiveSize2D
  7. from openpyxl.utils.units import pixels_to_EMU, pixels_to_points
  8. from openpyxl.utils import get_column_letter, column_index_from_string
  9. from django.db import models
  10. import os
  11. from django.db.models.fields.files import ImageFieldFile
  12. from pprint import pprint
  13. from PIL import Image as PILImage
  14. from openpyxl.drawing.xdr import XDRPoint2D, XDRPositiveSize2D
  15. from openpyxl.utils.units import pixels_to_EMU
  16. from openpyxl.drawing.spreadsheet_drawing import AnchorMarker, TwoCellAnchor, OneCellAnchor
  17. from django.conf import settings
  18. def set_image_with_offset_old(sheet, img, cell_coordinate, offset_x=0, offset_y=0):
  19. """
  20. Add an image to the sheet with an offset relative to the top-left corner of a cell.
  21. :param sheet: The worksheet
  22. :param img: The openpyxl Image object
  23. :param cell_coordinate: Cell to place the image (e.g., "B2")
  24. :param offset_x: Horizontal offset in pixels
  25. :param offset_y: Vertical offset in pixels
  26. """
  27. col_letter = ''.join(filter(str.isalpha, cell_coordinate)) # Extract column letter
  28. row_number = int(''.join(filter(str.isdigit, cell_coordinate))) # Extract row number
  29. # Get column width and row height in pixels
  30. col_width = sheet.column_dimensions[get_column_letter(column_index_from_string(col_letter))].width or 10
  31. row_height = sheet.row_dimensions[row_number].height or 15
  32. # Approximate conversion of Excel units to pixels
  33. col_pixels = col_width * 7.5 # Excel's ~7.5 pixels per width unit
  34. row_pixels = row_height * 0.75 # Approximation for row height in pixels
  35. # Calculate absolute positions based on offsets
  36. anchor_x = col_pixels + offset_x
  37. anchor_y = row_pixels + offset_y
  38. # Set the anchor for the image
  39. img.anchor = cell_coordinate
  40. img.anchor.dx = int(anchor_x * 9525) # Convert to EMUs (Excel Measurement Units)
  41. img.anchor.dy = int(anchor_y * 9525) # Convert to EMUs (Excel Measurement Units)
  42. sheet.add_image(img)
  43. def set_image_with_offset(sheet, img, cell_coordinate, offset_x=0, offset_y=0):
  44. """
  45. Add an image to the sheet with an offset relative to the top-left corner of a cell.
  46. :param sheet: The worksheet
  47. :param img: The openpyxl Image object
  48. :param cell_coordinate: Cell to place the image (e.g., "B2")
  49. :param offset_x: Horizontal offset in pixels
  50. :param offset_y: Vertical offset in pixels
  51. """
  52. # Extract the column and row from the cell coordinate
  53. col_letter = ''.join(filter(str.isalpha, cell_coordinate)) # Extract column letter
  54. row_number = int(''.join(filter(str.isdigit, cell_coordinate))) # Extract row number
  55. # Get the zero-based indices for the cell
  56. col_idx = column_index_from_string(col_letter) - 1
  57. row_idx = row_number - 1
  58. # Approximate column width and row height to pixels
  59. col_width = sheet.column_dimensions[col_letter].width or 10 # Default column width
  60. row_height = sheet.row_dimensions[row_number].height or 15 # Default row height
  61. # Convert column width and row height to pixels
  62. col_pixels = col_width * 7.5 # Approximation: ~7.5 pixels per width unit
  63. row_pixels = row_height * 0.75 # Approximation: ~0.75 pixels per height unit
  64. # Calculate the position in pixels for the top-left corner of the cell
  65. cell_x = col_idx * col_pixels
  66. cell_y = row_idx * row_pixels
  67. # Apply the offsets
  68. final_x = cell_x + offset_x
  69. final_y = cell_y + offset_y
  70. # Convert to EMUs
  71. pos = XDRPoint2D(pixels_to_EMU(final_x), pixels_to_EMU(final_y))
  72. size = XDRPositiveSize2D(pixels_to_EMU(img.width), pixels_to_EMU(img.height))
  73. # Set the image's anchor with the position and size
  74. img.anchor = AbsoluteAnchor(pos=pos, ext=size)
  75. # Add the image to the worksheet
  76. sheet.add_image(img)
  77. from openpyxl.drawing.xdr import XDRPositiveSize2D
  78. def center_image_in_cell(sheet, img, cell_coordinate):
  79. """
  80. Center an image inside a specified cell using OneCellAnchor.
  81. """
  82. # Extract column and row (e.g. "C3")
  83. col_letter = ''.join(filter(str.isalpha, cell_coordinate))
  84. row_number = int(''.join(filter(str.isdigit, cell_coordinate)))
  85. col_idx = column_index_from_string(col_letter) - 1 # Zero-based index
  86. # Get cell size (Excel default values if not set)
  87. col_width = sheet.column_dimensions[col_letter].width or 8.43
  88. row_height = sheet.row_dimensions[row_number].height or 15
  89. # Convert Excel column/row units → pixels
  90. col_pixels = col_width * 7.5
  91. row_pixels = row_height * 0.75
  92. # Image size (already in pixels)
  93. img_width, img_height = img.width, img.height
  94. # Calculate centering offset inside the cell
  95. offset_x_px = max((col_pixels - img_width) / 2, 0)
  96. offset_y_px = max((row_pixels - img_height) / 2, 0)
  97. offset_x_px += 40
  98. offset_y_px -= 50
  99. # Convert pixel offset to EMU
  100. offset_x = pixels_to_EMU(offset_x_px)
  101. offset_y = pixels_to_EMU(offset_y_px)
  102. # Create the `_from` anchor marker (top-left corner)
  103. _from = AnchorMarker(
  104. col=col_idx,
  105. row=row_number - 1,
  106. colOff=offset_x,
  107. rowOff=offset_y
  108. )
  109. # Create XDRPositiveSize2D for ext (width & height in EMU)
  110. ext = XDRPositiveSize2D(
  111. cx=pixels_to_EMU(img_width),
  112. cy=pixels_to_EMU(img_height)
  113. )
  114. # Final anchor using OneCellAnchor
  115. img.anchor = OneCellAnchor(
  116. _from=_from,
  117. ext=ext
  118. )
  119. sheet.add_image(img)
  120. def center_image_in_cell_old(sheet, img, cell_coordinate):
  121. """
  122. Center an image inside a specified cell.
  123. :param sheet: The worksheet
  124. :param img: The openpyxl Image object
  125. :param cell_coordinate: The cell to center the image in (e.g., "C3")
  126. """
  127. # Extract column and row from the cell coordinate
  128. col_letter = ''.join(filter(str.isalpha, cell_coordinate)) # Extract column letter
  129. row_number = int(''.join(filter(str.isdigit, cell_coordinate))) # Extract row number
  130. col_idx = column_index_from_string(col_letter) - 1 # Convert to zero-based column index
  131. # Get cell dimensions
  132. col_width = sheet.column_dimensions[col_letter].width or 10 # Default width if not set
  133. row_height = sheet.row_dimensions[row_number].height or 15 # Default height if not set
  134. # Convert dimensions to pixels (approximation)
  135. col_pixels = col_width * 7.5 # 1 Excel column width unit ≈ 7.5 pixels
  136. row_pixels = row_height * 0.75 # 1 Excel row height unit ≈ 0.75 pixels
  137. # Get image dimensions
  138. img_width, img_height = img.width, img.height
  139. # Calculate offsets to center the image
  140. offset_x = int((col_pixels - img_width) / 2 * pixels_to_EMU(1)) + 100 # Center horizontally
  141. offset_y = int((row_pixels - img_height) / 2 * pixels_to_EMU(1)) # Center vertically
  142. # Define the anchor for the image
  143. _from = AnchorMarker(col=col_idx, row=row_number - 1, colOff=offset_x, rowOff=offset_y)
  144. to = AnchorMarker(col=col_idx + 1, row=row_number, colOff=-offset_x, rowOff=-offset_y)
  145. # Use TwoCellAnchor for positioning
  146. img.anchor = TwoCellAnchor(editAs="oneCell", _from=_from, to=to)
  147. # Add the image to the sheet
  148. sheet.add_image(img)
  149. def gen_xlsx(template_file, selected_sheets, prefix_filename, data):
  150. pprint(f"5.0 = {template_file}, {selected_sheets}, {prefix_filename}, {data}")
  151. """
  152. Generate an Excel file from a template, fill placeholders, and include only selected sheets.
  153. Args:
  154. template_file (str): Path to the Excel template file.
  155. selected_sheets (list): List of sheet names to include in the output file.
  156. prefix_filename (str): Prefix for the output filename.
  157. data (dict): Data dictionary with sheet-specific keys and fallback keys.
  158. Returns:
  159. str: Path of the generated Excel file.
  160. """
  161. checked_image_path = f"{settings.BASE_DIR}/report/checkbox_checked.jpg" # Path to the checked checkbox image
  162. unchecked_image_path = f"{settings.BASE_DIR}/report/checkbox_unchecked.jpg" # Path to the unchecked checkbox image
  163. # Load the template workbook
  164. print(f"before load workbook {template_file}")
  165. workbook = load_workbook(template_file)
  166. print("after load")
  167. # Remove sheets not in selected_sheets
  168. for sheet_name in workbook.sheetnames:
  169. if sheet_name not in selected_sheets:
  170. del workbook[sheet_name]
  171. # Process the selected sheets
  172. for sheet_name in selected_sheets:
  173. if sheet_name not in workbook.sheetnames:
  174. raise ValueError(f"Sheet '{sheet_name}' not found in the template.")
  175. sheet = workbook[sheet_name]
  176. # Replace placeholders with actual values
  177. # Handle hiding rows based on patterns in data
  178. for row in sheet.iter_rows():
  179. for cell in row:
  180. if cell.value and isinstance(cell.value, str) and cell.value.startswith("<") and cell.value.endswith(">"):
  181. placeholder = cell.value.strip("<>")
  182. # Determine value priority: `sheet_name.key` > `key`
  183. value = None
  184. sheet_specific_key = f"{sheet_name}.{placeholder}"
  185. pprint(f"5.01 = {sheet_specific_key}")
  186. pprint(sheet_specific_key in data)
  187. if sheet_specific_key in data:
  188. value = data[sheet_specific_key]
  189. elif placeholder in data:
  190. value = data[placeholder]
  191. if value is not None:
  192. if isinstance(value, ImageFieldFile):
  193. pprint("ImageField")
  194. image_path = value.path
  195. if os.path.exists(image_path):
  196. # img = Image(image_path)
  197. # img.height = 40 # Adjust size as needed
  198. pil_img = PILImage.open(image_path)
  199. original_width, original_height = pil_img.size
  200. pprint(f"1.0 = f{image_path}")
  201. pprint(f"1.1 = {original_width}x{original_height}")
  202. # Desired height (e.g., 40), calculate the new width to maintain aspect ratio
  203. row_height = sheet.row_dimensions[cell.row].height
  204. desired_height = int(row_height)
  205. fixed_pixel_height = int(desired_height * 1.33) # Convert point → pixel
  206. fixed_pixel_height = 100 # Convert point → pixel
  207. aspect_ratio = original_width / original_height
  208. new_width = int(fixed_pixel_height * aspect_ratio)
  209. resized_img = pil_img.resize((new_width, fixed_pixel_height), PILImage.Resampling.LANCZOS)
  210. # Generate new filename by adding '_resized' before extension
  211. base, ext = os.path.splitext(image_path)
  212. resized_path = f"{base}_resized{ext}"
  213. resized_img.save(resized_path)
  214. # aspect_ratio = original_width / original_height
  215. # new_width = int(desired_height * aspect_ratio)
  216. #
  217. # # Resize the image using Pillow (optional, for saving memory during export)
  218. # resized_img = pil_img.resize((new_width, desired_height), PILImage.Resampling.LANCZOS)
  219. # resized_img.save(image_path) # Save the resized image back to the same path
  220. pprint(f"1.2 = {new_width}, {desired_height}")
  221. # Insert the resized image into the Excel sheet
  222. img = Image(resized_path)
  223. img.width, img.height = new_width, fixed_pixel_height # Set the dimensions
  224. # sheet.add_image(img, cell.coordinate)
  225. print(f"1.3 = {row_height}")
  226. center_image_in_cell(sheet, img, cell.coordinate, )
  227. cell.value = None # Clear placeholder
  228. elif value is True:
  229. img = Image(checked_image_path)
  230. img.width = pixels_to_points(20)
  231. img.height = pixels_to_points(10)
  232. # img.width = 15
  233. # img.hegiht = 10
  234. print(f"{cell.coordinate}")
  235. # sheet.add_image(img, cell.coordinate)
  236. # set_image_with_offset(sheet, img, cell.coordinate, offset_x=100)
  237. center_image_in_cell_old(sheet,img, cell.coordinate, )
  238. cell.value = None # Remove the placeholder text
  239. elif value is False:
  240. img = Image(unchecked_image_path)
  241. # img.width = 15
  242. # img.height = 10
  243. img.width = pixels_to_points(20)
  244. img.height = pixels_to_points(10)
  245. # sheet.add_image(img, cell.coordinate)
  246. # set_image_with_offset(sheet, img, cell.coordinate, offset_x=100)
  247. center_image_in_cell_old(sheet, img, cell.coordinate, )
  248. cell.value = None # Remove the placeholder text
  249. else:
  250. # Insert the text value directly
  251. # cell.value = value
  252. if isinstance(value, str) and "[" in value and "]" in value:
  253. # Extract the part before "[i:j]"
  254. value = value.split("[")[0]
  255. # Insert the cleaned value into the cell
  256. cell.value = value
  257. for key, value in data.items():
  258. pprint(f"7 = {key} => {value}")
  259. # if isinstance(value, str) and re.match(r"^\d+\[\d+:\d+\]$", value):
  260. if isinstance(value, str) and re.match(r"^[0-9\s]+\[\d+:\d+\]$", value):
  261. # Parse the prefix and row range
  262. prefix, row_range = value.split("[")
  263. row_start, row_end = map(int, row_range[:-1].split(":"))
  264. # Hide rows if the prefix matches the condition
  265. # if prefix == "0": # Adjust the condition as needed
  266. pprint(f"5.1 = {sheet} {value} => {prefix} ({row_start}, {row_end})")
  267. if " " in prefix or prefix.strip() == "0":
  268. if key.split('.')[0] == sheet.title:
  269. pprint(f"HIDE !!! {key.split('.')[0]} {sheet.title} = {row_start}, {row_end}")
  270. sheet.row_dimensions.group(row_start, row_end, hidden=True)
  271. # Generate the output filename with a timestamp
  272. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  273. output_path = f"{prefix_filename}_{timestamp}.xlsx"
  274. workbook.save(output_path)
  275. return output_path
  276. if __name__ == "__main__":
  277. # Example usage
  278. data = {
  279. "customer": "Tum Coder",
  280. "inspect_date": "2025-01-15",
  281. "lot_no": "12345",
  282. "staff_name": "Tum 8888",
  283. "man_name": "Tum 999",
  284. "size": "Large",
  285. "pcs": "10 pcs",
  286. "spec": "Spec-A",
  287. "hardness.d1_act": "10",
  288. "hardness.d2_act": "0[24:28]", # Hide rows 24 to 28 if the prefix is "0"
  289. "hardness.acc": True, # Hide rows 24 to 28 if the prefix is "0"
  290. "hardness.spe_acc": False, # Hide rows 24 to 28 if the prefix is "0"
  291. "dimension_app.d1_act": "33",
  292. "dimension_app.d2_act": "0[26:32]", # Hide rows 24 to 28 if the prefix is "0"
  293. "dimension_app.acc": True, # Hide rows 24 to 28 if the prefix is "0"
  294. "dimension_app.spe_acc": True, # Hide rows 24 to 28 if the prefix is "0"
  295. }
  296. output_file = gen_xlsx(
  297. template_file="./hardness.xlsx",
  298. selected_sheets=["hardness", "dimension_app"], # Replace with your actual sheet names
  299. prefix_filename="./output/output",
  300. data=data
  301. )
  302. print(f"Generated file: {output_file}")