[{"data":1,"prerenderedAt":1776},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl":3,"surround:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl":1769},{"id":4,"title":5,"body":6,"description":1760,"extension":1761,"meta":1762,"navigation":121,"path":1763,"seo":1764,"stem":1767,"__hash__":1768},"docs\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl\u002Findex.md","Fill an Excel Template with Python and openpyxl",{"type":7,"value":8,"toc":1747},"minimark",[9,28,33,39,67,73,77,80,558,562,573,675,682,686,692,1009,1016,1309,1313,1324,1497,1501,1630,1635,1650,1654,1665,1682,1695,1709,1713,1716,1720,1743],[10,11,12,13,18,19,23,24,27],"p",{},"This guide is the hands-on companion to ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002F","Generating Excel Reports from Templates",". The job is concrete: you have a branded ",[20,21,22],"code",{},".xlsx",", and on every run you need to drop today's data into it and emit a fresh file. With ",[20,25,26],{},"openpyxl"," that is a load, a handful of cell assignments, a loop over your data rows, and a save under a new name. The care is in the details — the right start row, merged cells, and not writing over formulas.",[29,30,32],"h2",{"id":31},"prerequisites-and-install","Prerequisites and install",[10,34,35,36,38],{},"You need Python 3.8+ and ",[20,37,26],{},":",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-bash shiki shiki-themes github-light github-dark","pip install openpyxl pandas\n","bash","",[20,47,48],{"__ignoreMap":45},[49,50,53,57,61,64],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"sScJk","pip",[49,58,60],{"class":59},"sZZnC"," install",[49,62,63],{"class":59}," openpyxl",[49,65,66],{"class":59}," pandas\n",[10,68,69,72],{},[20,70,71],{},"pandas"," is optional — the table-fill loop works from any iterable — but it is the usual source of report data, so we show both.",[29,74,76],{"id":75},"step-1-generate-a-sample-template","Step 1: Generate a sample template",[10,78,79],{},"In production this file is handed to you. Here we build it so every later block runs. It has a merged title, a label\u002Fvalue pair for the report date, a styled header row at row 4, and a totals formula at row 10:",[40,81,85],{"className":82,"code":83,"language":84,"meta":45,"style":45},"language-python shiki shiki-themes github-light github-dark","from openpyxl import Workbook\nfrom openpyxl.styles import Font, PatternFill, Alignment\n\nwb = Workbook()\nws = wb.active\nws.title = \"Report\"\n\nws[\"A1\"] = \"Weekly Orders Report\"\nws[\"A1\"].font = Font(size=16, bold=True, color=\"1F4E78\")\nws.merge_cells(\"A1:C1\")\n\nws[\"A2\"] = \"Report date:\"\nws[\"A2\"].font = Font(italic=True)\n\nheader_fill = PatternFill(\"solid\", fgColor=\"4472C4\")\nfor col, name in enumerate([\"Customer\", \"Orders\", \"Total\"], start=1):\n    c = ws.cell(row=4, column=col, value=name)\n    c.font = Font(bold=True, color=\"FFFFFF\")\n    c.fill = header_fill\n    c.alignment = Alignment(horizontal=\"center\")\n\nws[\"A10\"] = \"Total\"\nws[\"A10\"].font = Font(bold=True)\nws[\"C10\"] = \"=SUM(C5:C9)\"          # live formula over the data range\nws[\"C10\"].number_format = \"#,##0.00\"\n\nwb.save(\"orders_template.xlsx\")\nprint(\"Wrote orders_template.xlsx\")\n","python",[20,86,87,103,116,123,135,146,157,162,179,228,239,244,259,281,286,312,357,394,421,432,453,458,473,494,513,528,533,544],{"__ignoreMap":45},[49,88,89,93,97,100],{"class":51,"line":52},[49,90,92],{"class":91},"szBVR","from",[49,94,96],{"class":95},"sVt8B"," openpyxl ",[49,98,99],{"class":91},"import",[49,101,102],{"class":95}," Workbook\n",[49,104,106,108,111,113],{"class":51,"line":105},2,[49,107,92],{"class":91},[49,109,110],{"class":95}," openpyxl.styles ",[49,112,99],{"class":91},[49,114,115],{"class":95}," Font, PatternFill, Alignment\n",[49,117,119],{"class":51,"line":118},3,[49,120,122],{"emptyLinePlaceholder":121},true,"\n",[49,124,126,129,132],{"class":51,"line":125},4,[49,127,128],{"class":95},"wb ",[49,130,131],{"class":91},"=",[49,133,134],{"class":95}," Workbook()\n",[49,136,138,141,143],{"class":51,"line":137},5,[49,139,140],{"class":95},"ws ",[49,142,131],{"class":91},[49,144,145],{"class":95}," wb.active\n",[49,147,149,152,154],{"class":51,"line":148},6,[49,150,151],{"class":95},"ws.title ",[49,153,131],{"class":91},[49,155,156],{"class":59}," \"Report\"\n",[49,158,160],{"class":51,"line":159},7,[49,161,122],{"emptyLinePlaceholder":121},[49,163,165,168,171,174,176],{"class":51,"line":164},8,[49,166,167],{"class":95},"ws[",[49,169,170],{"class":59},"\"A1\"",[49,172,173],{"class":95},"] ",[49,175,131],{"class":91},[49,177,178],{"class":59}," \"Weekly Orders Report\"\n",[49,180,182,184,186,189,191,194,198,200,204,207,210,212,215,217,220,222,225],{"class":51,"line":181},9,[49,183,167],{"class":95},[49,185,170],{"class":59},[49,187,188],{"class":95},"].font ",[49,190,131],{"class":91},[49,192,193],{"class":95}," Font(",[49,195,197],{"class":196},"s4XuR","size",[49,199,131],{"class":91},[49,201,203],{"class":202},"sj4cs","16",[49,205,206],{"class":95},", ",[49,208,209],{"class":196},"bold",[49,211,131],{"class":91},[49,213,214],{"class":202},"True",[49,216,206],{"class":95},[49,218,219],{"class":196},"color",[49,221,131],{"class":91},[49,223,224],{"class":59},"\"1F4E78\"",[49,226,227],{"class":95},")\n",[49,229,231,234,237],{"class":51,"line":230},10,[49,232,233],{"class":95},"ws.merge_cells(",[49,235,236],{"class":59},"\"A1:C1\"",[49,238,227],{"class":95},[49,240,242],{"class":51,"line":241},11,[49,243,122],{"emptyLinePlaceholder":121},[49,245,247,249,252,254,256],{"class":51,"line":246},12,[49,248,167],{"class":95},[49,250,251],{"class":59},"\"A2\"",[49,253,173],{"class":95},[49,255,131],{"class":91},[49,257,258],{"class":59}," \"Report date:\"\n",[49,260,262,264,266,268,270,272,275,277,279],{"class":51,"line":261},13,[49,263,167],{"class":95},[49,265,251],{"class":59},[49,267,188],{"class":95},[49,269,131],{"class":91},[49,271,193],{"class":95},[49,273,274],{"class":196},"italic",[49,276,131],{"class":91},[49,278,214],{"class":202},[49,280,227],{"class":95},[49,282,284],{"class":51,"line":283},14,[49,285,122],{"emptyLinePlaceholder":121},[49,287,289,292,294,297,300,302,305,307,310],{"class":51,"line":288},15,[49,290,291],{"class":95},"header_fill ",[49,293,131],{"class":91},[49,295,296],{"class":95}," PatternFill(",[49,298,299],{"class":59},"\"solid\"",[49,301,206],{"class":95},[49,303,304],{"class":196},"fgColor",[49,306,131],{"class":91},[49,308,309],{"class":59},"\"4472C4\"",[49,311,227],{"class":95},[49,313,315,318,321,324,327,330,333,335,338,340,343,346,349,351,354],{"class":51,"line":314},16,[49,316,317],{"class":91},"for",[49,319,320],{"class":95}," col, name ",[49,322,323],{"class":91},"in",[49,325,326],{"class":202}," enumerate",[49,328,329],{"class":95},"([",[49,331,332],{"class":59},"\"Customer\"",[49,334,206],{"class":95},[49,336,337],{"class":59},"\"Orders\"",[49,339,206],{"class":95},[49,341,342],{"class":59},"\"Total\"",[49,344,345],{"class":95},"], ",[49,347,348],{"class":196},"start",[49,350,131],{"class":91},[49,352,353],{"class":202},"1",[49,355,356],{"class":95},"):\n",[49,358,360,363,365,368,371,373,376,378,381,383,386,389,391],{"class":51,"line":359},17,[49,361,362],{"class":95},"    c ",[49,364,131],{"class":91},[49,366,367],{"class":95}," ws.cell(",[49,369,370],{"class":196},"row",[49,372,131],{"class":91},[49,374,375],{"class":202},"4",[49,377,206],{"class":95},[49,379,380],{"class":196},"column",[49,382,131],{"class":91},[49,384,385],{"class":95},"col, ",[49,387,388],{"class":196},"value",[49,390,131],{"class":91},[49,392,393],{"class":95},"name)\n",[49,395,397,400,402,404,406,408,410,412,414,416,419],{"class":51,"line":396},18,[49,398,399],{"class":95},"    c.font ",[49,401,131],{"class":91},[49,403,193],{"class":95},[49,405,209],{"class":196},[49,407,131],{"class":91},[49,409,214],{"class":202},[49,411,206],{"class":95},[49,413,219],{"class":196},[49,415,131],{"class":91},[49,417,418],{"class":59},"\"FFFFFF\"",[49,420,227],{"class":95},[49,422,424,427,429],{"class":51,"line":423},19,[49,425,426],{"class":95},"    c.fill ",[49,428,131],{"class":91},[49,430,431],{"class":95}," header_fill\n",[49,433,435,438,440,443,446,448,451],{"class":51,"line":434},20,[49,436,437],{"class":95},"    c.alignment ",[49,439,131],{"class":91},[49,441,442],{"class":95}," Alignment(",[49,444,445],{"class":196},"horizontal",[49,447,131],{"class":91},[49,449,450],{"class":59},"\"center\"",[49,452,227],{"class":95},[49,454,456],{"class":51,"line":455},21,[49,457,122],{"emptyLinePlaceholder":121},[49,459,461,463,466,468,470],{"class":51,"line":460},22,[49,462,167],{"class":95},[49,464,465],{"class":59},"\"A10\"",[49,467,173],{"class":95},[49,469,131],{"class":91},[49,471,472],{"class":59}," \"Total\"\n",[49,474,476,478,480,482,484,486,488,490,492],{"class":51,"line":475},23,[49,477,167],{"class":95},[49,479,465],{"class":59},[49,481,188],{"class":95},[49,483,131],{"class":91},[49,485,193],{"class":95},[49,487,209],{"class":196},[49,489,131],{"class":91},[49,491,214],{"class":202},[49,493,227],{"class":95},[49,495,497,499,502,504,506,509],{"class":51,"line":496},24,[49,498,167],{"class":95},[49,500,501],{"class":59},"\"C10\"",[49,503,173],{"class":95},[49,505,131],{"class":91},[49,507,508],{"class":59}," \"=SUM(C5:C9)\"",[49,510,512],{"class":511},"sJ8bj","          # live formula over the data range\n",[49,514,516,518,520,523,525],{"class":51,"line":515},25,[49,517,167],{"class":95},[49,519,501],{"class":59},[49,521,522],{"class":95},"].number_format ",[49,524,131],{"class":91},[49,526,527],{"class":59}," \"#,##0.00\"\n",[49,529,531],{"class":51,"line":530},26,[49,532,122],{"emptyLinePlaceholder":121},[49,534,536,539,542],{"class":51,"line":535},27,[49,537,538],{"class":95},"wb.save(",[49,540,541],{"class":59},"\"orders_template.xlsx\"",[49,543,227],{"class":95},[49,545,547,550,553,556],{"class":51,"line":546},28,[49,548,549],{"class":202},"print",[49,551,552],{"class":95},"(",[49,554,555],{"class":59},"\"Wrote orders_template.xlsx\"",[49,557,227],{"class":95},[29,559,561],{"id":560},"step-2-load-the-template-and-set-named-cells","Step 2: Load the template and set named cells",[10,563,564,565,568,569,572],{},"Open the file with ",[20,566,567],{},"load_workbook",", select the sheet, and assign the one-off cells: the report date and anything else that lives at a fixed address. Writing ",[20,570,571],{},"ws[\"B2\"] = value"," sets the value and leaves that cell's existing style untouched:",[40,574,576],{"className":82,"code":575,"language":84,"meta":45,"style":45},"from datetime import date\nfrom openpyxl import load_workbook\n\nwb = load_workbook(\"orders_template.xlsx\")\nws = wb[\"Report\"]\n\nws[\"B2\"] = date.today().isoformat()    # the report-date value cell\n\nprint(\"Date cell set to\", ws[\"B2\"].value)\n",[20,577,578,590,601,605,618,633,637,654,658],{"__ignoreMap":45},[49,579,580,582,585,587],{"class":51,"line":52},[49,581,92],{"class":91},[49,583,584],{"class":95}," datetime ",[49,586,99],{"class":91},[49,588,589],{"class":95}," date\n",[49,591,592,594,596,598],{"class":51,"line":105},[49,593,92],{"class":91},[49,595,96],{"class":95},[49,597,99],{"class":91},[49,599,600],{"class":95}," load_workbook\n",[49,602,603],{"class":51,"line":118},[49,604,122],{"emptyLinePlaceholder":121},[49,606,607,609,611,614,616],{"class":51,"line":125},[49,608,128],{"class":95},[49,610,131],{"class":91},[49,612,613],{"class":95}," load_workbook(",[49,615,541],{"class":59},[49,617,227],{"class":95},[49,619,620,622,624,627,630],{"class":51,"line":137},[49,621,140],{"class":95},[49,623,131],{"class":91},[49,625,626],{"class":95}," wb[",[49,628,629],{"class":59},"\"Report\"",[49,631,632],{"class":95},"]\n",[49,634,635],{"class":51,"line":148},[49,636,122],{"emptyLinePlaceholder":121},[49,638,639,641,644,646,648,651],{"class":51,"line":159},[49,640,167],{"class":95},[49,642,643],{"class":59},"\"B2\"",[49,645,173],{"class":95},[49,647,131],{"class":91},[49,649,650],{"class":95}," date.today().isoformat()    ",[49,652,653],{"class":511},"# the report-date value cell\n",[49,655,656],{"class":51,"line":164},[49,657,122],{"emptyLinePlaceholder":121},[49,659,660,662,664,667,670,672],{"class":51,"line":181},[49,661,549],{"class":202},[49,663,552],{"class":95},[49,665,666],{"class":59},"\"Date cell set to\"",[49,668,669],{"class":95},", ws[",[49,671,643],{"class":59},[49,673,674],{"class":95},"].value)\n",[10,676,677,678,681],{},"These are your \"named\" cells in the informal sense — known fixed addresses for known fields. If you prefer real Excel named ranges, look them up with ",[20,679,680],{},"wb.defined_names"," and resolve the destination, but fixed addresses are simpler and just as robust for a template you control.",[29,683,685],{"id":684},"step-3-fill-the-repeating-data-table","Step 3: Fill the repeating data table",[10,687,688,689,38],{},"The body of the report starts at the first row under the header. Our header is row 4, so data begins at row 5. Loop your records, computing each target row as ",[20,690,691],{},"START_ROW + offset",[40,693,695],{"className":82,"code":694,"language":84,"meta":45,"style":45},"from datetime import date\nfrom openpyxl import load_workbook\n\n# Your data, as a list of tuples (or DataFrame rows — see below)\nrows = [\n    (\"Acme Co\", 14, 2380.00),\n    (\"Globex\", 9, 1755.50),\n    (\"Initech\", 21, 3990.75),\n]\n\nwb = load_workbook(\"orders_template.xlsx\")\nws = wb[\"Report\"]\nws[\"B2\"] = date.today().isoformat()\n\nSTART_ROW = 5\nfor offset, (customer, orders, total) in enumerate(rows):\n    r = START_ROW + offset\n    ws.cell(row=r, column=1, value=customer)\n    ws.cell(row=r, column=2, value=orders)\n    ws.cell(row=r, column=3, value=total)\n\nwb.save(\"orders_report.xlsx\")\nprint(f\"Filled {len(rows)} rows starting at row {START_ROW}\")\n",[20,696,697,707,717,721,726,736,757,776,795,799,803,815,827,840,844,855,869,885,912,938,964,968,977],{"__ignoreMap":45},[49,698,699,701,703,705],{"class":51,"line":52},[49,700,92],{"class":91},[49,702,584],{"class":95},[49,704,99],{"class":91},[49,706,589],{"class":95},[49,708,709,711,713,715],{"class":51,"line":105},[49,710,92],{"class":91},[49,712,96],{"class":95},[49,714,99],{"class":91},[49,716,600],{"class":95},[49,718,719],{"class":51,"line":118},[49,720,122],{"emptyLinePlaceholder":121},[49,722,723],{"class":51,"line":125},[49,724,725],{"class":511},"# Your data, as a list of tuples (or DataFrame rows — see below)\n",[49,727,728,731,733],{"class":51,"line":137},[49,729,730],{"class":95},"rows ",[49,732,131],{"class":91},[49,734,735],{"class":95}," [\n",[49,737,738,741,744,746,749,751,754],{"class":51,"line":148},[49,739,740],{"class":95},"    (",[49,742,743],{"class":59},"\"Acme Co\"",[49,745,206],{"class":95},[49,747,748],{"class":202},"14",[49,750,206],{"class":95},[49,752,753],{"class":202},"2380.00",[49,755,756],{"class":95},"),\n",[49,758,759,761,764,766,769,771,774],{"class":51,"line":159},[49,760,740],{"class":95},[49,762,763],{"class":59},"\"Globex\"",[49,765,206],{"class":95},[49,767,768],{"class":202},"9",[49,770,206],{"class":95},[49,772,773],{"class":202},"1755.50",[49,775,756],{"class":95},[49,777,778,780,783,785,788,790,793],{"class":51,"line":164},[49,779,740],{"class":95},[49,781,782],{"class":59},"\"Initech\"",[49,784,206],{"class":95},[49,786,787],{"class":202},"21",[49,789,206],{"class":95},[49,791,792],{"class":202},"3990.75",[49,794,756],{"class":95},[49,796,797],{"class":51,"line":181},[49,798,632],{"class":95},[49,800,801],{"class":51,"line":230},[49,802,122],{"emptyLinePlaceholder":121},[49,804,805,807,809,811,813],{"class":51,"line":241},[49,806,128],{"class":95},[49,808,131],{"class":91},[49,810,613],{"class":95},[49,812,541],{"class":59},[49,814,227],{"class":95},[49,816,817,819,821,823,825],{"class":51,"line":246},[49,818,140],{"class":95},[49,820,131],{"class":91},[49,822,626],{"class":95},[49,824,629],{"class":59},[49,826,632],{"class":95},[49,828,829,831,833,835,837],{"class":51,"line":261},[49,830,167],{"class":95},[49,832,643],{"class":59},[49,834,173],{"class":95},[49,836,131],{"class":91},[49,838,839],{"class":95}," date.today().isoformat()\n",[49,841,842],{"class":51,"line":283},[49,843,122],{"emptyLinePlaceholder":121},[49,845,846,849,852],{"class":51,"line":288},[49,847,848],{"class":202},"START_ROW",[49,850,851],{"class":91}," =",[49,853,854],{"class":202}," 5\n",[49,856,857,859,862,864,866],{"class":51,"line":314},[49,858,317],{"class":91},[49,860,861],{"class":95}," offset, (customer, orders, total) ",[49,863,323],{"class":91},[49,865,326],{"class":202},[49,867,868],{"class":95},"(rows):\n",[49,870,871,874,876,879,882],{"class":51,"line":359},[49,872,873],{"class":95},"    r ",[49,875,131],{"class":91},[49,877,878],{"class":202}," START_ROW",[49,880,881],{"class":91}," +",[49,883,884],{"class":95}," offset\n",[49,886,887,890,892,894,897,899,901,903,905,907,909],{"class":51,"line":396},[49,888,889],{"class":95},"    ws.cell(",[49,891,370],{"class":196},[49,893,131],{"class":91},[49,895,896],{"class":95},"r, ",[49,898,380],{"class":196},[49,900,131],{"class":91},[49,902,353],{"class":202},[49,904,206],{"class":95},[49,906,388],{"class":196},[49,908,131],{"class":91},[49,910,911],{"class":95},"customer)\n",[49,913,914,916,918,920,922,924,926,929,931,933,935],{"class":51,"line":423},[49,915,889],{"class":95},[49,917,370],{"class":196},[49,919,131],{"class":91},[49,921,896],{"class":95},[49,923,380],{"class":196},[49,925,131],{"class":91},[49,927,928],{"class":202},"2",[49,930,206],{"class":95},[49,932,388],{"class":196},[49,934,131],{"class":91},[49,936,937],{"class":95},"orders)\n",[49,939,940,942,944,946,948,950,952,955,957,959,961],{"class":51,"line":434},[49,941,889],{"class":95},[49,943,370],{"class":196},[49,945,131],{"class":91},[49,947,896],{"class":95},[49,949,380],{"class":196},[49,951,131],{"class":91},[49,953,954],{"class":202},"3",[49,956,206],{"class":95},[49,958,388],{"class":196},[49,960,131],{"class":91},[49,962,963],{"class":95},"total)\n",[49,965,966],{"class":51,"line":455},[49,967,122],{"emptyLinePlaceholder":121},[49,969,970,972,975],{"class":51,"line":460},[49,971,538],{"class":95},[49,973,974],{"class":59},"\"orders_report.xlsx\"",[49,976,227],{"class":95},[49,978,979,981,983,986,989,992,995,998,1001,1004,1007],{"class":51,"line":475},[49,980,549],{"class":202},[49,982,552],{"class":95},[49,984,985],{"class":91},"f",[49,987,988],{"class":59},"\"Filled ",[49,990,991],{"class":202},"{len",[49,993,994],{"class":95},"(rows)",[49,996,997],{"class":202},"}",[49,999,1000],{"class":59}," rows starting at row ",[49,1002,1003],{"class":202},"{START_ROW}",[49,1005,1006],{"class":59},"\"",[49,1008,227],{"class":95},[10,1010,1011,1012,1015],{},"From a DataFrame, the only change is the source of the loop. Use ",[20,1013,1014],{},"itertuples"," so column access stays explicit:",[40,1017,1019],{"className":82,"code":1018,"language":84,"meta":45,"style":45},"import pandas as pd\nfrom openpyxl import load_workbook\n\ndf = pd.DataFrame({\n    \"customer\": [\"Acme Co\", \"Globex\", \"Initech\"],\n    \"orders\": [14, 9, 21],\n    \"total\": [2380.00, 1755.50, 3990.75],\n})\n\nwb = load_workbook(\"orders_template.xlsx\")\nws = wb[\"Report\"]\n\nSTART_ROW = 5\nfor offset, rec in enumerate(df.itertuples(index=False)):\n    r = START_ROW + offset\n    ws.cell(row=r, column=1, value=rec.customer)\n    ws.cell(row=r, column=2, value=rec.orders)\n    ws.cell(row=r, column=3, value=rec.total)\n\nwb.save(\"orders_report.xlsx\")\nprint(f\"Wrote {len(df)} rows from DataFrame\")\n",[20,1020,1021,1034,1044,1048,1058,1079,1098,1117,1122,1126,1138,1150,1154,1162,1187,1199,1224,1249,1274,1278,1286],{"__ignoreMap":45},[49,1022,1023,1025,1028,1031],{"class":51,"line":52},[49,1024,99],{"class":91},[49,1026,1027],{"class":95}," pandas ",[49,1029,1030],{"class":91},"as",[49,1032,1033],{"class":95}," pd\n",[49,1035,1036,1038,1040,1042],{"class":51,"line":105},[49,1037,92],{"class":91},[49,1039,96],{"class":95},[49,1041,99],{"class":91},[49,1043,600],{"class":95},[49,1045,1046],{"class":51,"line":118},[49,1047,122],{"emptyLinePlaceholder":121},[49,1049,1050,1053,1055],{"class":51,"line":125},[49,1051,1052],{"class":95},"df ",[49,1054,131],{"class":91},[49,1056,1057],{"class":95}," pd.DataFrame({\n",[49,1059,1060,1063,1066,1068,1070,1072,1074,1076],{"class":51,"line":137},[49,1061,1062],{"class":59},"    \"customer\"",[49,1064,1065],{"class":95},": [",[49,1067,743],{"class":59},[49,1069,206],{"class":95},[49,1071,763],{"class":59},[49,1073,206],{"class":95},[49,1075,782],{"class":59},[49,1077,1078],{"class":95},"],\n",[49,1080,1081,1084,1086,1088,1090,1092,1094,1096],{"class":51,"line":148},[49,1082,1083],{"class":59},"    \"orders\"",[49,1085,1065],{"class":95},[49,1087,748],{"class":202},[49,1089,206],{"class":95},[49,1091,768],{"class":202},[49,1093,206],{"class":95},[49,1095,787],{"class":202},[49,1097,1078],{"class":95},[49,1099,1100,1103,1105,1107,1109,1111,1113,1115],{"class":51,"line":159},[49,1101,1102],{"class":59},"    \"total\"",[49,1104,1065],{"class":95},[49,1106,753],{"class":202},[49,1108,206],{"class":95},[49,1110,773],{"class":202},[49,1112,206],{"class":95},[49,1114,792],{"class":202},[49,1116,1078],{"class":95},[49,1118,1119],{"class":51,"line":164},[49,1120,1121],{"class":95},"})\n",[49,1123,1124],{"class":51,"line":181},[49,1125,122],{"emptyLinePlaceholder":121},[49,1127,1128,1130,1132,1134,1136],{"class":51,"line":230},[49,1129,128],{"class":95},[49,1131,131],{"class":91},[49,1133,613],{"class":95},[49,1135,541],{"class":59},[49,1137,227],{"class":95},[49,1139,1140,1142,1144,1146,1148],{"class":51,"line":241},[49,1141,140],{"class":95},[49,1143,131],{"class":91},[49,1145,626],{"class":95},[49,1147,629],{"class":59},[49,1149,632],{"class":95},[49,1151,1152],{"class":51,"line":246},[49,1153,122],{"emptyLinePlaceholder":121},[49,1155,1156,1158,1160],{"class":51,"line":261},[49,1157,848],{"class":202},[49,1159,851],{"class":91},[49,1161,854],{"class":202},[49,1163,1164,1166,1169,1171,1173,1176,1179,1181,1184],{"class":51,"line":283},[49,1165,317],{"class":91},[49,1167,1168],{"class":95}," offset, rec ",[49,1170,323],{"class":91},[49,1172,326],{"class":202},[49,1174,1175],{"class":95},"(df.itertuples(",[49,1177,1178],{"class":196},"index",[49,1180,131],{"class":91},[49,1182,1183],{"class":202},"False",[49,1185,1186],{"class":95},")):\n",[49,1188,1189,1191,1193,1195,1197],{"class":51,"line":288},[49,1190,873],{"class":95},[49,1192,131],{"class":91},[49,1194,878],{"class":202},[49,1196,881],{"class":91},[49,1198,884],{"class":95},[49,1200,1201,1203,1205,1207,1209,1211,1213,1215,1217,1219,1221],{"class":51,"line":314},[49,1202,889],{"class":95},[49,1204,370],{"class":196},[49,1206,131],{"class":91},[49,1208,896],{"class":95},[49,1210,380],{"class":196},[49,1212,131],{"class":91},[49,1214,353],{"class":202},[49,1216,206],{"class":95},[49,1218,388],{"class":196},[49,1220,131],{"class":91},[49,1222,1223],{"class":95},"rec.customer)\n",[49,1225,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246],{"class":51,"line":359},[49,1227,889],{"class":95},[49,1229,370],{"class":196},[49,1231,131],{"class":91},[49,1233,896],{"class":95},[49,1235,380],{"class":196},[49,1237,131],{"class":91},[49,1239,928],{"class":202},[49,1241,206],{"class":95},[49,1243,388],{"class":196},[49,1245,131],{"class":91},[49,1247,1248],{"class":95},"rec.orders)\n",[49,1250,1251,1253,1255,1257,1259,1261,1263,1265,1267,1269,1271],{"class":51,"line":396},[49,1252,889],{"class":95},[49,1254,370],{"class":196},[49,1256,131],{"class":91},[49,1258,896],{"class":95},[49,1260,380],{"class":196},[49,1262,131],{"class":91},[49,1264,954],{"class":202},[49,1266,206],{"class":95},[49,1268,388],{"class":196},[49,1270,131],{"class":91},[49,1272,1273],{"class":95},"rec.total)\n",[49,1275,1276],{"class":51,"line":423},[49,1277,122],{"emptyLinePlaceholder":121},[49,1279,1280,1282,1284],{"class":51,"line":434},[49,1281,538],{"class":95},[49,1283,974],{"class":59},[49,1285,227],{"class":95},[49,1287,1288,1290,1292,1294,1297,1299,1302,1304,1307],{"class":51,"line":455},[49,1289,549],{"class":202},[49,1291,552],{"class":95},[49,1293,985],{"class":91},[49,1295,1296],{"class":59},"\"Wrote ",[49,1298,991],{"class":202},[49,1300,1301],{"class":95},"(df)",[49,1303,997],{"class":202},[49,1305,1306],{"class":59}," rows from DataFrame\"",[49,1308,227],{"class":95},[29,1310,1312],{"id":1311},"step-4-save-as-a-new-file-do-not-overwrite-the-template","Step 4: Save as a new file — do not overwrite the template",[10,1314,1315,1316,1319,1320,1323],{},"Notice every save above targets ",[20,1317,1318],{},"orders_report.xlsx",", never ",[20,1321,1322],{},"orders_template.xlsx",". The template is input; overwrite it once and the next run has nowhere to start from. Make it impossible to get wrong by stamping the output name and guarding against clobbering the source:",[40,1325,1327],{"className":82,"code":1326,"language":84,"meta":45,"style":45},"from datetime import date\nfrom pathlib import Path\nfrom openpyxl import load_workbook\n\nTEMPLATE = \"orders_template.xlsx\"\nout = f\"orders_report_{date.today():%Y%m%d}.xlsx\"\nassert Path(out).resolve() != Path(TEMPLATE).resolve(), \"Refusing to overwrite the template\"\n\nwb = load_workbook(TEMPLATE)\nwb[\"Report\"][\"B2\"] = date.today().isoformat()\nwb.save(out)\nprint(f\"Wrote {out}\")\n",[20,1328,1329,1339,1351,1361,1365,1375,1415,1437,1441,1453,1471,1476],{"__ignoreMap":45},[49,1330,1331,1333,1335,1337],{"class":51,"line":52},[49,1332,92],{"class":91},[49,1334,584],{"class":95},[49,1336,99],{"class":91},[49,1338,589],{"class":95},[49,1340,1341,1343,1346,1348],{"class":51,"line":105},[49,1342,92],{"class":91},[49,1344,1345],{"class":95}," pathlib ",[49,1347,99],{"class":91},[49,1349,1350],{"class":95}," Path\n",[49,1352,1353,1355,1357,1359],{"class":51,"line":118},[49,1354,92],{"class":91},[49,1356,96],{"class":95},[49,1358,99],{"class":91},[49,1360,600],{"class":95},[49,1362,1363],{"class":51,"line":125},[49,1364,122],{"emptyLinePlaceholder":121},[49,1366,1367,1370,1372],{"class":51,"line":137},[49,1368,1369],{"class":202},"TEMPLATE",[49,1371,851],{"class":91},[49,1373,1374],{"class":59}," \"orders_template.xlsx\"\n",[49,1376,1377,1380,1382,1385,1388,1391,1394,1397,1400,1402,1405,1407,1410,1412],{"class":51,"line":148},[49,1378,1379],{"class":95},"out ",[49,1381,131],{"class":91},[49,1383,1384],{"class":91}," f",[49,1386,1387],{"class":59},"\"orders_report_",[49,1389,1390],{"class":202},"{",[49,1392,1393],{"class":95},"date.today():",[49,1395,1396],{"class":91},"%",[49,1398,1399],{"class":95},"Y",[49,1401,1396],{"class":91},[49,1403,1404],{"class":95},"m",[49,1406,1396],{"class":91},[49,1408,1409],{"class":95},"d",[49,1411,997],{"class":202},[49,1413,1414],{"class":59},".xlsx\"\n",[49,1416,1417,1420,1423,1426,1429,1431,1434],{"class":51,"line":159},[49,1418,1419],{"class":91},"assert",[49,1421,1422],{"class":95}," Path(out).resolve() ",[49,1424,1425],{"class":91},"!=",[49,1427,1428],{"class":95}," Path(",[49,1430,1369],{"class":202},[49,1432,1433],{"class":95},").resolve(), ",[49,1435,1436],{"class":59},"\"Refusing to overwrite the template\"\n",[49,1438,1439],{"class":51,"line":164},[49,1440,122],{"emptyLinePlaceholder":121},[49,1442,1443,1445,1447,1449,1451],{"class":51,"line":181},[49,1444,128],{"class":95},[49,1446,131],{"class":91},[49,1448,613],{"class":95},[49,1450,1369],{"class":202},[49,1452,227],{"class":95},[49,1454,1455,1458,1460,1463,1465,1467,1469],{"class":51,"line":230},[49,1456,1457],{"class":95},"wb[",[49,1459,629],{"class":59},[49,1461,1462],{"class":95},"][",[49,1464,643],{"class":59},[49,1466,173],{"class":95},[49,1468,131],{"class":91},[49,1470,839],{"class":95},[49,1472,1473],{"class":51,"line":241},[49,1474,1475],{"class":95},"wb.save(out)\n",[49,1477,1478,1480,1482,1484,1486,1488,1491,1493,1495],{"class":51,"line":246},[49,1479,549],{"class":202},[49,1481,552],{"class":95},[49,1483,985],{"class":91},[49,1485,1296],{"class":59},[49,1487,1390],{"class":202},[49,1489,1490],{"class":95},"out",[49,1492,997],{"class":202},[49,1494,1006],{"class":59},[49,1496,227],{"class":95},[29,1498,1500],{"id":1499},"common-pitfalls","Common pitfalls",[1502,1503,1504,1520],"table",{},[1505,1506,1507],"thead",{},[1508,1509,1510,1514,1517],"tr",{},[1511,1512,1513],"th",{},"Symptom",[1511,1515,1516],{},"Cause",[1511,1518,1519],{},"Fix",[1521,1522,1523,1539,1558,1569,1580,1602],"tbody",{},[1508,1524,1525,1529,1532],{},[1526,1527,1528],"td",{},"Template loses all data next run",[1526,1530,1531],{},"Saved output back over the template path",[1526,1533,1534,1535,1538],{},"Always ",[20,1536,1537],{},"save()"," to a new, dated filename; assert it differs from the template",[1508,1540,1541,1544,1547],{},[1526,1542,1543],{},"First data row lands on the header",[1526,1545,1546],{},"Off-by-one start row",[1526,1548,1549,1550,1552,1553,1557],{},"Set ",[20,1551,848],{}," to the row ",[1554,1555,1556],"em",{},"below"," the header (header at 4 → data at 5)",[1508,1559,1560,1563,1566],{},[1526,1561,1562],{},"Value written to a merged cell does not appear",[1526,1564,1565],{},"Wrote to a non-anchor cell of a merge",[1526,1567,1568],{},"Write only to the top-left (anchor) cell of the merged range",[1508,1570,1571,1574,1577],{},[1526,1572,1573],{},"Totals show a static number, not a live sum",[1526,1575,1576],{},"Wrote a value into the formula cell",[1526,1578,1579],{},"Never write to formula cells; fill the data they reference and let Excel recalc",[1508,1581,1582,1585,1588],{},[1526,1583,1584],{},"Date sorts as text in Excel",[1526,1586,1587],{},"Stored an ISO string",[1526,1589,1590,1591,1594,1595,1598,1599],{},"Assign a ",[20,1592,1593],{},"datetime.date","\u002F",[20,1596,1597],{},"datetime"," object and set ",[20,1600,1601],{},"cell.number_format",[1508,1603,1604,1610,1616],{},[1526,1605,1606,1609],{},[20,1607,1608],{},"InvalidFileException"," on load",[1526,1611,1612,1613],{},"File is legacy ",[20,1614,1615],{},".xls",[1526,1617,1618,1619,1621,1622,1594,1624,1594,1627],{},"Convert to ",[20,1620,22],{}," first; openpyxl reads only ",[20,1623,22],{},[20,1625,1626],{},".xlsm",[20,1628,1629],{},".xltx",[1631,1632,1634],"h3",{"id":1633},"a-note-on-merged-cells","A note on merged cells",[10,1636,1637,1638,1641,1642,1645,1646,1649],{},"A merged range in Excel is backed by a single real cell — the top-left anchor. Assigning to any other cell in the range silently does nothing visible. If your template merges, say, ",[20,1639,1640],{},"A1:C1"," for a title, write to ",[20,1643,1644],{},"A1",". To find anchors programmatically, inspect ",[20,1647,1648],{},"ws.merged_cells.ranges",".",[29,1651,1653],{"id":1652},"frequently-asked-questions","Frequently asked questions",[10,1655,1656,1660,1661,1664],{},[1657,1658,1659],"strong",{},"How do I fill more rows than the template's formula covers?","\nTrack your row count and rewrite the formula to match: ",[20,1662,1663],{},"ws[f\"C{last+1}\"] = f\"=SUM(C5:C{last})\"",". Or size the template's data block for your largest run so the formula never needs touching.",[10,1666,1667,1670,1671,1674,1675,1677,1678,1681],{},[1657,1668,1669],{},"Can I clear old data before filling?","\nIf the template ships empty there is nothing to clear. If it carries sample rows, set those cells to ",[20,1672,1673],{},"None"," first, but leave the styling alone — assigning ",[20,1676,1673],{}," to ",[20,1679,1680],{},"cell.value"," clears the value without disturbing the format.",[10,1683,1684,1691,1692,1694],{},[1657,1685,1686,1687,1690],{},"Do I need ",[20,1688,1689],{},"data_only=True"," when loading?","\nNo, and avoid it here. ",[20,1693,1689],{}," returns Excel's last cached values and reading mode loses formulas. Load normally so formulas pass through untouched.",[10,1696,1697,1700,1701,1704,1705,1708],{},[1657,1698,1699],{},"What if the sheet name is unknown?","\nUse ",[20,1702,1703],{},"wb.active"," for the default sheet, or ",[20,1706,1707],{},"wb.sheetnames"," to list them and pick by position. Hard-coding the name is fine when you own the template.",[29,1710,1712],{"id":1711},"conclusion","Conclusion",[10,1714,1715],{},"Filling a template is load, set the fixed cells, loop the data table from the correct start row, and save a new dated file. The mistakes are predictable — overwriting the template, an off-by-one start row, writing to a merged non-anchor cell, or clobbering a formula — and every one is avoided by writing only to data cells and never to the template path. With those guardrails the same script runs unattended for years against the same designer-owned file.",[29,1717,1719],{"id":1718},"where-to-go-next","Where to go next",[1721,1722,1723,1729,1736],"ul",{},[1724,1725,1726,1728],"li",{},[14,1727,17],{"href":16}," — the cluster overview: why template injection beats building from scratch.",[1724,1730,1731,1735],{},[14,1732,1734],{"href":1733},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fpopulate-excel-template-without-losing-formatting\u002F","Populate an Excel Template Without Losing Formatting"," — exactly which styles survive the load-edit-save round-trip.",[1724,1737,1738,1742],{},[14,1739,1741],{"href":1740},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fopenpyxl-append-data-to-existing-excel-sheet\u002F","openpyxl: Append Data to an Existing Excel Sheet"," — when the table grows beyond a fixed region and you append instead.",[1744,1745,1746],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":45,"searchDepth":105,"depth":105,"links":1748},[1749,1750,1751,1752,1753,1754,1757,1758,1759],{"id":31,"depth":105,"text":32},{"id":75,"depth":105,"text":76},{"id":560,"depth":105,"text":561},{"id":684,"depth":105,"text":685},{"id":1311,"depth":105,"text":1312},{"id":1499,"depth":105,"text":1500,"children":1755},[1756],{"id":1633,"depth":118,"text":1634},{"id":1652,"depth":105,"text":1653},{"id":1711,"depth":105,"text":1712},{"id":1718,"depth":105,"text":1719},"Load a prepared .xlsx template with openpyxl, set named cells for title, date and totals, fill a repeating data table from a list or DataFrame, and save a new file.","md",{},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl",{"title":1765,"description":1766},"Fill an Excel Template with openpyxl","Step-by-step: load an .xlsx template with openpyxl, write title\u002Fdate\u002Ftotal cells, fill a table from a DataFrame at a known start row, and save without overwriting it.","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl\u002Findex","JaNsi87zV413FL4nUuBfSn5hPj0-pC6Xcj8V49T7rCA",[1770,1773],{"title":17,"path":1771,"stem":1772,"children":-1},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Findex",{"title":1734,"path":1774,"stem":1775,"children":-1},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fpopulate-excel-template-without-losing-formatting","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fpopulate-excel-template-without-losing-formatting\u002Findex",1781773160974]