Нема описа

gen_report.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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
  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. def center_image_in_cell(sheet, img, cell_coordinate):
  78. """
  79. Center an image inside a specified cell.
  80. :param sheet: The worksheet
  81. :param img: The openpyxl Image object
  82. :param cell_coordinate: The cell to center the image in (e.g., "C3")
  83. """
  84. # Extract column and row from the cell coordinate
  85. col_letter = ''.join(filter(str.isalpha, cell_coordinate)) # Extract column letter
  86. row_number = int(''.join(filter(str.isdigit, cell_coordinate))) # Extract row number
  87. col_idx = column_index_from_string(col_letter) - 1 # Convert to zero-based column index
  88. # Get cell dimensions
  89. col_width = sheet.column_dimensions[col_letter].width or 10 # Default width if not set
  90. row_height = sheet.row_dimensions[row_number].height or 15 # Default height if not set
  91. # Convert dimensions to pixels (approximation)
  92. col_pixels = col_width * 7.5 # 1 Excel column width unit ≈ 7.5 pixels
  93. row_pixels = row_height * 0.75 # 1 Excel row height unit ≈ 0.75 pixels
  94. # Get image dimensions
  95. img_width, img_height = img.width, img.height
  96. # Calculate offsets to center the image
  97. offset_x = int((col_pixels - img_width) / 2 * pixels_to_EMU(1)) # Center horizontally
  98. offset_y = int((row_pixels - img_height) / 2 * pixels_to_EMU(1)) # Center vertically
  99. # Define the anchor for the image
  100. _from = AnchorMarker(col=col_idx, row=row_number - 1, colOff=offset_x, rowOff=offset_y)
  101. to = AnchorMarker(col=col_idx + 1, row=row_number, colOff=-offset_x, rowOff=-offset_y)
  102. # Use TwoCellAnchor for positioning
  103. img.anchor = TwoCellAnchor(editAs="oneCell", _from=_from, to=to)
  104. # Add the image to the sheet
  105. sheet.add_image(img)
  106. def gen_xlsx(template_file, selected_sheets, prefix_filename, data):
  107. """
  108. Generate an Excel file from a template, fill placeholders, and include only selected sheets.
  109. Args:
  110. template_file (str): Path to the Excel template file.
  111. selected_sheets (list): List of sheet names to include in the output file.
  112. prefix_filename (str): Prefix for the output filename.
  113. data (dict): Data dictionary with sheet-specific keys and fallback keys.
  114. Returns:
  115. str: Path of the generated Excel file.
  116. """
  117. checked_image_path = f"{settings.BASE_DIR}/report/checkbox_checked.jpg" # Path to the checked checkbox image
  118. unchecked_image_path = f"{settings.BASE_DIR}/report/checkbox_unchecked.jpg" # Path to the unchecked checkbox image
  119. # Load the template workbook
  120. print(f"before load workbook {template_file}")
  121. workbook = load_workbook(template_file)
  122. print("after load")
  123. # Remove sheets not in selected_sheets
  124. for sheet_name in workbook.sheetnames:
  125. if sheet_name not in selected_sheets:
  126. del workbook[sheet_name]
  127. # Process the selected sheets
  128. for sheet_name in selected_sheets:
  129. if sheet_name not in workbook.sheetnames:
  130. raise ValueError(f"Sheet '{sheet_name}' not found in the template.")
  131. sheet = workbook[sheet_name]
  132. # Replace placeholders with actual values
  133. # Handle hiding rows based on patterns in data
  134. for row in sheet.iter_rows():
  135. for cell in row:
  136. if cell.value and isinstance(cell.value, str) and cell.value.startswith("<") and cell.value.endswith(">"):
  137. placeholder = cell.value.strip("<>")
  138. # Determine value priority: `sheet_name.key` > `key`
  139. value = None
  140. sheet_specific_key = f"{sheet_name}.{placeholder}"
  141. if sheet_specific_key in data:
  142. value = data[sheet_specific_key]
  143. elif placeholder in data:
  144. value = data[placeholder]
  145. if value is not None:
  146. if isinstance(value, ImageFieldFile):
  147. pprint("ImageField")
  148. image_path = value.path
  149. if os.path.exists(image_path):
  150. # img = Image(image_path)
  151. # img.height = 40 # Adjust size as needed
  152. pil_img = PILImage.open(image_path)
  153. original_width, original_height = pil_img.size
  154. pprint(f"1.0 = f{image_path}")
  155. pprint(f"1.1 = {original_width}x{original_height}")
  156. # Desired height (e.g., 40), calculate the new width to maintain aspect ratio
  157. row_height = sheet.row_dimensions[cell.row].height
  158. desired_height = int(row_height)
  159. fixed_pixel_height = int(desired_height * 1.33) # Convert point → pixel
  160. aspect_ratio = original_width / original_height
  161. new_width = int(fixed_pixel_height * aspect_ratio)
  162. resized_img = pil_img.resize((new_width, fixed_pixel_height), PILImage.Resampling.LANCZOS)
  163. # Generate new filename by adding '_resized' before extension
  164. base, ext = os.path.splitext(image_path)
  165. resized_path = f"{base}_resized{ext}"
  166. resized_img.save(resized_path)
  167. # aspect_ratio = original_width / original_height
  168. # new_width = int(desired_height * aspect_ratio)
  169. #
  170. # # Resize the image using Pillow (optional, for saving memory during export)
  171. # resized_img = pil_img.resize((new_width, desired_height), PILImage.Resampling.LANCZOS)
  172. # resized_img.save(image_path) # Save the resized image back to the same path
  173. pprint(f"1.2 = {new_width}, {desired_height}")
  174. # Insert the resized image into the Excel sheet
  175. img = Image(resized_path)
  176. img.width, img.height = new_width, desired_height # Set the dimensions
  177. # sheet.add_image(img, cell.coordinate)
  178. print(f"1.3 = {row_height}")
  179. center_image_in_cell(sheet, img, cell.coordinate, )
  180. cell.value = None # Clear placeholder
  181. elif value is True:
  182. img = Image(checked_image_path)
  183. img.width = pixels_to_points(20)
  184. img.height = pixels_to_points(10)
  185. # img.width = 15
  186. # img.hegiht = 10
  187. print(f"{cell.coordinate}")
  188. # sheet.add_image(img, cell.coordinate)
  189. # set_image_with_offset(sheet, img, cell.coordinate, offset_x=100)
  190. center_image_in_cell(sheet,img, cell.coordinate, )
  191. cell.value = None # Remove the placeholder text
  192. elif value is False:
  193. img = Image(unchecked_image_path)
  194. # img.width = 15
  195. # img.height = 10
  196. img.width = pixels_to_points(20)
  197. img.height = pixels_to_points(10)
  198. # sheet.add_image(img, cell.coordinate)
  199. # set_image_with_offset(sheet, img, cell.coordinate, offset_x=100)
  200. center_image_in_cell(sheet, img, cell.coordinate, )
  201. cell.value = None # Remove the placeholder text
  202. else:
  203. # Insert the text value directly
  204. # cell.value = value
  205. if isinstance(value, str) and "[" in value and "]" in value:
  206. # Extract the part before "[i:j]"
  207. value = value.split("[")[0]
  208. # Insert the cleaned value into the cell
  209. cell.value = value
  210. for key, value in data.items():
  211. # if isinstance(value, str) and re.match(r"^\d+\[\d+:\d+\]$", value):
  212. if isinstance(value, str) and re.match(r"^[0-9\s]+\[\d+:\d+\]$", value):
  213. # Parse the prefix and row range
  214. prefix, row_range = value.split("[")
  215. row_start, row_end = map(int, row_range[:-1].split(":"))
  216. # Hide rows if the prefix matches the condition
  217. # if prefix == "0": # Adjust the condition as needed
  218. if " " in prefix or prefix.strip() == "0":
  219. sheet.row_dimensions.group(row_start, row_end, hidden=True)
  220. # Generate the output filename with a timestamp
  221. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  222. output_path = f"{prefix_filename}_{timestamp}.xlsx"
  223. workbook.save(output_path)
  224. return output_path
  225. if __name__ == "__main__":
  226. # Example usage
  227. data = {
  228. "customer": "Tum Coder",
  229. "inspect_date": "2025-01-15",
  230. "lot_no": "12345",
  231. "staff_name": "Tum 8888",
  232. "man_name": "Tum 999",
  233. "size": "Large",
  234. "pcs": "10 pcs",
  235. "spec": "Spec-A",
  236. "hardness.d1_act": "10",
  237. "hardness.d2_act": "0[24:28]", # Hide rows 24 to 28 if the prefix is "0"
  238. "hardness.acc": True, # Hide rows 24 to 28 if the prefix is "0"
  239. "hardness.spe_acc": False, # Hide rows 24 to 28 if the prefix is "0"
  240. "dimension_app.d1_act": "33",
  241. "dimension_app.d2_act": "0[26:32]", # Hide rows 24 to 28 if the prefix is "0"
  242. "dimension_app.acc": True, # Hide rows 24 to 28 if the prefix is "0"
  243. "dimension_app.spe_acc": True, # Hide rows 24 to 28 if the prefix is "0"
  244. }
  245. output_file = gen_xlsx(
  246. template_file="./hardness.xlsx",
  247. selected_sheets=["hardness", "dimension_app"], # Replace with your actual sheet names
  248. prefix_filename="./output/output",
  249. data=data
  250. )
  251. print(f"Generated file: {output_file}")