[{"data":1,"prerenderedAt":2072},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-file-open-read-only-recommended":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-file-open-read-only-recommended":2039,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-file-open-read-only-recommended":2047,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-file-open-read-only-recommended":2052,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-file-open-read-only-recommended":2053},{"id":4,"title":5,"body":6,"dateModified":2006,"datePublished":2006,"description":2007,"extension":2008,"faq":2009,"keyword":2018,"kind":2019,"meta":2020,"navigation":266,"path":2029,"seo":2030,"slug":2033,"stem":2034,"topics":2035,"type":2037,"__hash__":2038},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-file-open-read-only-recommended\u002Findex.md","Make an Excel File Open Read-Only Recommended",{"type":7,"value":8,"toc":1992},"minimark",[9,28,33,62,69,237,241,244,527,537,541,556,951,1035,1041,1045,1048,1117,1163,1174,1178,1181,1281,1289,1426,1430,1443,1662,1668,1672,1678,1837,1847,1851,1867,1871,1910,1914,1927,1931,1937,1943,1949,1955,1959,1988],[10,11,12,13,17,18,21,22,27],"p",{},"A monthly report sits on a shared drive, and every month someone opens it, types a \"quick what-if\" into a cell, saves by reflex and changes the published numbers for everyone. Protecting the sheet with a password is heavy-handed for a report people are allowed to play with; what is wanted is a gentle nudge. Excel has two: ",[14,15,16],"em",{},"read-only recommended",", which makes Excel ask \"open as read-only?\" when the file is opened, and ",[14,19,20],{},"mark as final",", which opens the file with editing turned off and a yellow bar explaining why. Both can be set from Python. Neither is security — anyone can click past them — and the page says exactly where each stops. It is part of ",[23,24,26],"a",{"href":25},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002F","Protecting and Sharing Excel Workbooks",".",[29,30,32],"h2",{"id":31},"prerequisites","Prerequisites",[34,35,40],"pre",{"className":36,"code":37,"language":38,"meta":39,"style":39},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install xlsxwriter openpyxl\n","bash","",[41,42,43],"code",{"__ignoreMap":39},[44,45,48,52,56,59],"span",{"class":46,"line":47},"line",1,[44,49,51],{"class":50},"sMTad","pip",[44,53,55],{"class":54},"srMev"," install",[44,57,58],{"class":54}," xlsxwriter",[44,60,61],{"class":54}," openpyxl\n",[10,63,64,65,68],{},"The examples write small report workbooks and look inside the saved files to confirm the settings are there. Both settings are small XML elements in the ",[41,66,67],{},".xlsx"," package, which makes them easy to check without Excel — a useful property, because the prompts themselves only appear in Excel.",[70,71,79,80,79,84,79,88,79,96,79,104,79,111,79,118,79,121,79,125,79,128,79,132,79,137,79,143,79,145,79,150,79,152,79,155,79,157,79,160,79,163,79,167,79,169,79,173,79,175,79,178,79,180,79,183,79,186,79,190,79,192,79,196,79,198,79,201,79,203,79,206,79,209,79,213,79,215,79,219,79,221,79,232,79,234],"svg",{"viewBox":72,"role":73,"ariaLabelledBy":74,"xmlns":77,"style":78},"4 0 752 208","img",[75,76],"ro-options-t","ro-options-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[81,82,83],"title",{"id":75},"Nudges and protections compared",[85,86,87],"desc",{"id":76},"A comparison table. Read-only recommended prompts on open and can be declined. Mark as final opens with editing disabled until the user clicks Edit Anyway. Sheet protection blocks edits to locked cells until unprotected. A password to open encrypts the file.",[89,90],"rect",{"x":91,"y":92,"width":93,"height":94,"fill":95},"4","0","752","208","#ffffff",[89,97],{"x":98,"y":99,"width":100,"height":101,"rx":92,"fill":95,"stroke":102,"style":103},"20.0","16.0","190.5","40.0","var(--line,#cdd5e6)","stroke-width:1px",[89,105],{"x":106,"y":99,"width":107,"height":101,"rx":92,"fill":108,"stroke":109,"style":110},"210.5","176.5","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[112,113,117],"text",{"x":114,"y":115,"style":116},"298.8","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Prompts or blocks",[89,119],{"x":120,"y":99,"width":107,"height":101,"rx":92,"fill":108,"stroke":109,"style":110},"387.0",[112,122,124],{"x":123,"y":115,"style":116},"475.3","User can bypass",[89,126],{"x":127,"y":99,"width":107,"height":101,"rx":92,"fill":108,"stroke":109,"style":110},"563.5",[112,129,131],{"x":130,"y":115,"style":116},"651.8","Written by Python",[89,133],{"x":98,"y":134,"width":100,"height":135,"rx":92,"fill":136,"stroke":102,"style":103},"56.0","34.0","#e7ebef",[112,138,142],{"x":139,"y":140,"style":141},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Read-only recommended",[89,144],{"x":106,"y":134,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,146,149],{"x":114,"y":147,"style":148},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","prompt on open",[89,151],{"x":120,"y":134,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,153,154],{"x":123,"y":147,"style":148},"yes — one click",[89,156],{"x":127,"y":134,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,158,159],{"x":130,"y":147,"style":148},"xlsxwriter or patch",[89,161],{"x":98,"y":162,"width":100,"height":135,"rx":92,"fill":136,"stroke":102,"style":103},"90.0",[112,164,166],{"x":139,"y":165,"style":141},"112.0","Mark as final",[89,168],{"x":106,"y":162,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,170,172],{"x":114,"y":171,"style":148},"111.0","editing off on open",[89,174],{"x":120,"y":162,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,176,177],{"x":123,"y":171,"style":148},"yes — Edit Anyway",[89,179],{"x":127,"y":162,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,181,182],{"x":130,"y":171,"style":148},"xlsxwriter, openpyxl",[89,184],{"x":98,"y":185,"width":100,"height":135,"rx":92,"fill":136,"stroke":102,"style":103},"124.0",[112,187,189],{"x":139,"y":188,"style":141},"146.0","Sheet protection",[89,191],{"x":106,"y":185,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,193,195],{"x":114,"y":194,"style":148},"145.0","blocks locked cells",[89,197],{"x":120,"y":185,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,199,200],{"x":123,"y":194,"style":148},"with the password",[89,202],{"x":127,"y":185,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,204,205],{"x":130,"y":194,"style":148},"openpyxl",[89,207],{"x":98,"y":208,"width":100,"height":135,"rx":92,"fill":136,"stroke":102,"style":103},"158.0",[112,210,212],{"x":139,"y":211,"style":141},"180.0","Password to open",[89,214],{"x":106,"y":208,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,216,218],{"x":114,"y":217,"style":148},"179.0","cannot open",[89,220],{"x":120,"y":208,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[222,223,225],"g",{"transform":224},"translate(467.3,167.0) scale(0.667)",[226,227],"path",{"d":228,"fill":229,"stroke":230,"style":231},"M6 6l12 12 M18 6L6 18","none","var(--accent,#f43f8f)","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[89,233],{"x":127,"y":208,"width":107,"height":135,"rx":92,"fill":95,"stroke":102,"style":103},[112,235,236],{"x":130,"y":217,"style":148},"msoffcrypto-tool",[29,238,240],{"id":239},"step-1-set-read-only-recommended-with-xlsxwriter","Step 1 — Set read-only recommended with xlsxwriter",[10,242,243],{},"xlsxwriter has a method for it. Call it before closing the workbook:",[34,245,249],{"className":246,"code":247,"language":248,"meta":39,"style":39},"language-python shiki shiki-themes github-light github-dark-high-contrast","import xlsxwriter\n\nwb = xlsxwriter.Workbook(\"monthly_report.xlsx\")\nws = wb.add_worksheet(\"Summary\")\nws.write_row(0, 0, [\"Region\", \"Revenue\"])\nfor i, (region, revenue) in enumerate([(\"North\", 482100), (\"South\", 391250), (\"East\", 297800)], start=1):\n    ws.write_row(i, 0, [region, revenue])\nwb.read_only_recommended()\nwb.close()\n\nimport re\nimport zipfile\n\ndef file_sharing(path):\n    xml = zipfile.ZipFile(path).read(\"xl\u002Fworkbook.xml\").decode()\n    return re.findall(r\"\u003CfileSharing[^>]*\u002F>\", xml)\n\nprint(file_sharing(\"monthly_report.xlsx\"))\n","python",[41,250,251,261,268,286,302,330,392,403,409,415,420,428,436,441,454,471,508,513],{"__ignoreMap":39},[44,252,253,257],{"class":46,"line":47},[44,254,256],{"class":255},"s-kum","import",[44,258,260],{"class":259},"skGVy"," xlsxwriter\n",[44,262,264],{"class":46,"line":263},2,[44,265,267],{"emptyLinePlaceholder":266},true,"\n",[44,269,271,274,277,280,283],{"class":46,"line":270},3,[44,272,273],{"class":259},"wb ",[44,275,276],{"class":255},"=",[44,278,279],{"class":259}," xlsxwriter.Workbook(",[44,281,282],{"class":54},"\"monthly_report.xlsx\"",[44,284,285],{"class":259},")\n",[44,287,289,292,294,297,300],{"class":46,"line":288},4,[44,290,291],{"class":259},"ws ",[44,293,276],{"class":255},[44,295,296],{"class":259}," wb.add_worksheet(",[44,298,299],{"class":54},"\"Summary\"",[44,301,285],{"class":259},[44,303,305,308,311,314,316,319,322,324,327],{"class":46,"line":304},5,[44,306,307],{"class":259},"ws.write_row(",[44,309,92],{"class":310},"sP0c6",[44,312,313],{"class":259},", ",[44,315,92],{"class":310},[44,317,318],{"class":259},", [",[44,320,321],{"class":54},"\"Region\"",[44,323,313],{"class":259},[44,325,326],{"class":54},"\"Revenue\"",[44,328,329],{"class":259},"])\n",[44,331,333,336,339,342,345,348,351,353,356,359,362,364,367,369,372,374,377,380,384,386,389],{"class":46,"line":332},6,[44,334,335],{"class":255},"for",[44,337,338],{"class":259}," i, (region, revenue) ",[44,340,341],{"class":255},"in",[44,343,344],{"class":310}," enumerate",[44,346,347],{"class":259},"([(",[44,349,350],{"class":54},"\"North\"",[44,352,313],{"class":259},[44,354,355],{"class":310},"482100",[44,357,358],{"class":259},"), (",[44,360,361],{"class":54},"\"South\"",[44,363,313],{"class":259},[44,365,366],{"class":310},"391250",[44,368,358],{"class":259},[44,370,371],{"class":54},"\"East\"",[44,373,313],{"class":259},[44,375,376],{"class":310},"297800",[44,378,379],{"class":259},")], ",[44,381,383],{"class":382},"sa561","start",[44,385,276],{"class":255},[44,387,388],{"class":310},"1",[44,390,391],{"class":259},"):\n",[44,393,395,398,400],{"class":46,"line":394},7,[44,396,397],{"class":259},"    ws.write_row(i, ",[44,399,92],{"class":310},[44,401,402],{"class":259},", [region, revenue])\n",[44,404,406],{"class":46,"line":405},8,[44,407,408],{"class":259},"wb.read_only_recommended()\n",[44,410,412],{"class":46,"line":411},9,[44,413,414],{"class":259},"wb.close()\n",[44,416,418],{"class":46,"line":417},10,[44,419,267],{"emptyLinePlaceholder":266},[44,421,423,425],{"class":46,"line":422},11,[44,424,256],{"class":255},[44,426,427],{"class":259}," re\n",[44,429,431,433],{"class":46,"line":430},12,[44,432,256],{"class":255},[44,434,435],{"class":259}," zipfile\n",[44,437,439],{"class":46,"line":438},13,[44,440,267],{"emptyLinePlaceholder":266},[44,442,444,447,451],{"class":46,"line":443},14,[44,445,446],{"class":255},"def",[44,448,450],{"class":449},"s_Opv"," file_sharing",[44,452,453],{"class":259},"(path):\n",[44,455,457,460,462,465,468],{"class":46,"line":456},15,[44,458,459],{"class":259},"    xml ",[44,461,276],{"class":255},[44,463,464],{"class":259}," zipfile.ZipFile(path).read(",[44,466,467],{"class":54},"\"xl\u002Fworkbook.xml\"",[44,469,470],{"class":259},").decode()\n",[44,472,474,477,480,483,486,489,492,496,499,502,505],{"class":46,"line":473},16,[44,475,476],{"class":255},"    return",[44,478,479],{"class":259}," re.findall(",[44,481,482],{"class":255},"r",[44,484,485],{"class":54},"\"\u003CfileSharing",[44,487,488],{"class":310},"[",[44,490,491],{"class":255},"^",[44,493,495],{"class":494},"sSjpA",">",[44,497,498],{"class":310},"]",[44,500,501],{"class":255},"*",[44,503,504],{"class":54},"\u002F>\"",[44,506,507],{"class":259},", xml)\n",[44,509,511],{"class":46,"line":510},17,[44,512,267],{"emptyLinePlaceholder":266},[44,514,516,519,522,524],{"class":46,"line":515},18,[44,517,518],{"class":310},"print",[44,520,521],{"class":259},"(file_sharing(",[44,523,282],{"class":54},[44,525,526],{"class":259},"))\n",[10,528,529,530,533,534,27],{},"The method writes a ",[41,531,532],{},"\u003CfileSharing readOnlyRecommended=\"1\"\u002F>"," element into the workbook part. When Excel opens the file, it asks whether to open it read-only; answering yes opens it with saving to the same name disabled, so edits can only be kept under a new name. Answering no opens it normally, with full editing, so the flag guards against habit rather than intent. The prompt appears every time, for everyone, until someone saves the file with the option switched off in ",[14,535,536],{},"Save As → Tools → General Options",[29,538,540],{"id":539},"step-2-add-it-to-a-file-written-by-openpyxl","Step 2 — Add it to a file written by openpyxl",[10,542,543,544,547,548,551,552,555],{},"openpyxl knows the ",[41,545,546],{},"fileSharing"," element when it reads a file, but its writer never outputs it, so there is no attribute to set. The element is small and its place in the XML is fixed — right after ",[41,549,550],{},"\u003CfileVersion>"," if there is one, otherwise first inside ",[41,553,554],{},"\u003Cworkbook>"," — so a short post-save patch adds it:",[34,557,559],{"className":246,"code":558,"language":248,"meta":39,"style":39},"import shutil\nfrom openpyxl import Workbook\n\ndef add_read_only_recommended(path):\n    tmp = path + \".tmp\"\n    with zipfile.ZipFile(path) as src, zipfile.ZipFile(tmp, \"w\", zipfile.ZIP_DEFLATED) as dst:\n        for item in src.infolist():\n            data = src.read(item.filename)\n            if item.filename == \"xl\u002Fworkbook.xml\" and b\"\u003CfileSharing\" not in data:\n                xml = data.decode(\"utf-8\")\n                tag = '\u003CfileSharing readOnlyRecommended=\"1\"\u002F>'\n                if \"\u003CfileVersion\" in xml:\n                    xml = re.sub(r\"(\u003CfileVersion[^>]*\u002F>)\", r\"\\1\" + tag, xml, count=1)\n                else:\n                    xml = re.sub(r\"(\u003Cworkbook[^>]*>)\", r\"\\1\" + tag, xml, count=1)\n                data = xml.encode(\"utf-8\")\n            dst.writestr(item, data)\n    shutil.move(tmp, path)\n\nwb = Workbook()\nwb.active[\"A1\"] = \"Published figures — do not edit\"\nwb.save(\"report_openpyxl.xlsx\")\nadd_read_only_recommended(\"report_openpyxl.xlsx\")\nprint(file_sharing(\"report_openpyxl.xlsx\"))\n",[41,560,561,568,581,585,594,610,641,654,664,696,711,721,734,800,808,863,877,882,887,892,902,919,930,940],{"__ignoreMap":39},[44,562,563,565],{"class":46,"line":47},[44,564,256],{"class":255},[44,566,567],{"class":259}," shutil\n",[44,569,570,573,576,578],{"class":46,"line":263},[44,571,572],{"class":255},"from",[44,574,575],{"class":259}," openpyxl ",[44,577,256],{"class":255},[44,579,580],{"class":259}," Workbook\n",[44,582,583],{"class":46,"line":270},[44,584,267],{"emptyLinePlaceholder":266},[44,586,587,589,592],{"class":46,"line":288},[44,588,446],{"class":255},[44,590,591],{"class":449}," add_read_only_recommended",[44,593,453],{"class":259},[44,595,596,599,601,604,607],{"class":46,"line":304},[44,597,598],{"class":259},"    tmp ",[44,600,276],{"class":255},[44,602,603],{"class":259}," path ",[44,605,606],{"class":255},"+",[44,608,609],{"class":54}," \".tmp\"\n",[44,611,612,615,618,621,624,627,630,633,636,638],{"class":46,"line":332},[44,613,614],{"class":255},"    with",[44,616,617],{"class":259}," zipfile.ZipFile(path) ",[44,619,620],{"class":255},"as",[44,622,623],{"class":259}," src, zipfile.ZipFile(tmp, ",[44,625,626],{"class":54},"\"w\"",[44,628,629],{"class":259},", zipfile.",[44,631,632],{"class":310},"ZIP_DEFLATED",[44,634,635],{"class":259},") ",[44,637,620],{"class":255},[44,639,640],{"class":259}," dst:\n",[44,642,643,646,649,651],{"class":46,"line":394},[44,644,645],{"class":255},"        for",[44,647,648],{"class":259}," item ",[44,650,341],{"class":255},[44,652,653],{"class":259}," src.infolist():\n",[44,655,656,659,661],{"class":46,"line":405},[44,657,658],{"class":259},"            data ",[44,660,276],{"class":255},[44,662,663],{"class":259}," src.read(item.filename)\n",[44,665,666,669,672,675,678,681,684,687,690,693],{"class":46,"line":411},[44,667,668],{"class":255},"            if",[44,670,671],{"class":259}," item.filename ",[44,673,674],{"class":255},"==",[44,676,677],{"class":54}," \"xl\u002Fworkbook.xml\"",[44,679,680],{"class":255}," and",[44,682,683],{"class":255}," b",[44,685,686],{"class":54},"\"\u003CfileSharing\"",[44,688,689],{"class":255}," not",[44,691,692],{"class":255}," in",[44,694,695],{"class":259}," data:\n",[44,697,698,701,703,706,709],{"class":46,"line":417},[44,699,700],{"class":259},"                xml ",[44,702,276],{"class":255},[44,704,705],{"class":259}," data.decode(",[44,707,708],{"class":54},"\"utf-8\"",[44,710,285],{"class":259},[44,712,713,716,718],{"class":46,"line":422},[44,714,715],{"class":259},"                tag ",[44,717,276],{"class":255},[44,719,720],{"class":54}," '\u003CfileSharing readOnlyRecommended=\"1\"\u002F>'\n",[44,722,723,726,729,731],{"class":46,"line":430},[44,724,725],{"class":255},"                if",[44,727,728],{"class":54}," \"\u003CfileVersion\"",[44,730,692],{"class":255},[44,732,733],{"class":259}," xml:\n",[44,735,736,739,741,744,746,749,752,755,757,759,761,763,765,768,771,773,775,777,779,783,785,788,791,794,796,798],{"class":46,"line":438},[44,737,738],{"class":259},"                    xml ",[44,740,276],{"class":255},[44,742,743],{"class":259}," re.sub(",[44,745,482],{"class":255},[44,747,748],{"class":54},"\"",[44,750,751],{"class":310},"(",[44,753,754],{"class":54},"\u003CfileVersion",[44,756,488],{"class":310},[44,758,491],{"class":255},[44,760,495],{"class":494},[44,762,498],{"class":310},[44,764,501],{"class":255},[44,766,767],{"class":54},"\u002F>",[44,769,770],{"class":310},")",[44,772,748],{"class":54},[44,774,313],{"class":259},[44,776,482],{"class":255},[44,778,748],{"class":54},[44,780,782],{"class":781},"sx1sP","\\1",[44,784,748],{"class":54},[44,786,787],{"class":255}," +",[44,789,790],{"class":259}," tag, xml, ",[44,792,793],{"class":382},"count",[44,795,276],{"class":255},[44,797,388],{"class":310},[44,799,285],{"class":259},[44,801,802,805],{"class":46,"line":443},[44,803,804],{"class":255},"                else",[44,806,807],{"class":259},":\n",[44,809,810,812,814,816,818,820,822,825,827,829,831,833,835,837,839,841,843,845,847,849,851,853,855,857,859,861],{"class":46,"line":456},[44,811,738],{"class":259},[44,813,276],{"class":255},[44,815,743],{"class":259},[44,817,482],{"class":255},[44,819,748],{"class":54},[44,821,751],{"class":310},[44,823,824],{"class":54},"\u003Cworkbook",[44,826,488],{"class":310},[44,828,491],{"class":255},[44,830,495],{"class":494},[44,832,498],{"class":310},[44,834,501],{"class":255},[44,836,495],{"class":54},[44,838,770],{"class":310},[44,840,748],{"class":54},[44,842,313],{"class":259},[44,844,482],{"class":255},[44,846,748],{"class":54},[44,848,782],{"class":781},[44,850,748],{"class":54},[44,852,787],{"class":255},[44,854,790],{"class":259},[44,856,793],{"class":382},[44,858,276],{"class":255},[44,860,388],{"class":310},[44,862,285],{"class":259},[44,864,865,868,870,873,875],{"class":46,"line":473},[44,866,867],{"class":259},"                data ",[44,869,276],{"class":255},[44,871,872],{"class":259}," xml.encode(",[44,874,708],{"class":54},[44,876,285],{"class":259},[44,878,879],{"class":46,"line":510},[44,880,881],{"class":259},"            dst.writestr(item, data)\n",[44,883,884],{"class":46,"line":515},[44,885,886],{"class":259},"    shutil.move(tmp, path)\n",[44,888,890],{"class":46,"line":889},19,[44,891,267],{"emptyLinePlaceholder":266},[44,893,895,897,899],{"class":46,"line":894},20,[44,896,273],{"class":259},[44,898,276],{"class":255},[44,900,901],{"class":259}," Workbook()\n",[44,903,905,908,911,914,916],{"class":46,"line":904},21,[44,906,907],{"class":259},"wb.active[",[44,909,910],{"class":54},"\"A1\"",[44,912,913],{"class":259},"] ",[44,915,276],{"class":255},[44,917,918],{"class":54}," \"Published figures — do not edit\"\n",[44,920,922,925,928],{"class":46,"line":921},22,[44,923,924],{"class":259},"wb.save(",[44,926,927],{"class":54},"\"report_openpyxl.xlsx\"",[44,929,285],{"class":259},[44,931,933,936,938],{"class":46,"line":932},23,[44,934,935],{"class":259},"add_read_only_recommended(",[44,937,927],{"class":54},[44,939,285],{"class":259},[44,941,943,945,947,949],{"class":46,"line":942},24,[44,944,518],{"class":310},[44,946,521],{"class":259},[44,948,927],{"class":54},[44,950,526],{"class":259},[70,952,79,958,79,961,79,964,79,969,79,978,79,986,79,992,79,996,79,1001,79,1007,79,1012,79,1016,79,1019,79,1026,79,1031],{"viewBox":953,"role":73,"ariaLabelledBy":954,"xmlns":77,"style":957},"54 0 652 331",[955,956],"ro-parts-t","ro-parts-d","width:100%;max-width:652px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[81,959,960],{"id":955},"Where each setting lives in the .xlsx package",[85,962,963],{"id":956},"Three layers of the zip package. The read-only recommended flag is an element in xl\u002Fworkbook.xml, mark as final is a custom property in docProps\u002Fcustom.xml, and sheet protection is an element in each worksheet's XML.",[89,965],{"x":966,"y":92,"width":967,"height":968,"fill":95},"54","652","331",[89,970],{"x":971,"y":99,"width":972,"height":973,"rx":974,"fill":975,"stroke":976,"style":977},"70.0","620.0","93.0","12","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[222,979,981],{"transform":980},"translate(368.0,29.0) scale(1.000)",[226,982],{"d":983,"fill":229,"stroke":984,"style":985},"M6 3h9l4 4v14H6z M15 3v4h4","var(--gold-ink,#7a4e06)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[112,987,991],{"x":988,"y":989,"style":990},"380.0","75.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","xl\u002Fworkbook.xml",[112,993,995],{"x":988,"y":994,"style":148},"95.0","\u003CfileSharing readOnlyRecommended=\"1\"\u002F> — dropped by openpyxl",[89,997],{"x":971,"y":998,"width":972,"height":973,"rx":974,"fill":999,"stroke":1000,"style":977},"119.0","#d9f4f1","var(--teal,#0f9488)",[222,1002,1004],{"transform":1003},"translate(368.0,132.0) scale(1.000)",[226,1005],{"d":983,"fill":229,"stroke":1006,"style":985},"var(--teal-ink,#0b6157)",[112,1008,1011],{"x":988,"y":1009,"style":1010},"178.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","docProps\u002Fcustom.xml",[112,1013,1015],{"x":988,"y":1014,"style":148},"198.0","_MarkAsFinal = true — kept by openpyxl",[89,1017],{"x":971,"y":1018,"width":972,"height":973,"rx":974,"fill":108,"stroke":109,"style":977},"222.0",[222,1020,1022],{"transform":1021},"translate(368.0,235.0) scale(1.000)",[226,1023],{"d":1024,"fill":229,"stroke":1025,"style":985},"M6 11h12v9H6z M8.5 11V8a3.5 3.5 0 0 1 7 0v3","var(--brand-strong,#4338ca)",[112,1027,1030],{"x":988,"y":1028,"style":1029},"281.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","xl\u002Fworksheets\u002FsheetN.xml",[112,1032,1034],{"x":988,"y":1033,"style":148},"301.0","\u003CsheetProtection …\u002F> — written by openpyxl",[10,1036,1037,1038,1040],{},"The patch copies every part of the package unchanged except ",[41,1039,991],{},", where it inserts the element in the position the file format requires — Excel is strict about element order, and an element in the wrong place makes it offer to repair the file. It only adds the element when none is present, so running it twice is harmless. Writing to a temporary file and moving it into place means a failure halfway never leaves a half-written report behind.",[29,1042,1044],{"id":1043},"step-3-check-the-patched-file-still-opens","Step 3 — Check the patched file still opens",[10,1046,1047],{},"A patched package should be checked by something other than the code that patched it. Let LibreOffice open and convert it; a malformed workbook fails to convert:",[34,1049,1051],{"className":36,"code":1050,"language":38,"meta":39,"style":39},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to pdf report_openpyxl.xlsx > \u002Fdev\u002Fnull 2>&1\ntest -s report_openpyxl.pdf && echo \"converted OK\" || echo \"conversion failed\"\n",[41,1052,1053,1088],{"__ignoreMap":39},[44,1054,1055,1058,1061,1064,1067,1070,1073,1076,1079,1082,1085],{"class":46,"line":47},[44,1056,1057],{"class":50},"soffice",[44,1059,1060],{"class":310}," -env:UserInstallation=file:\u002F\u002F",[44,1062,1063],{"class":259},"$PWD",[44,1065,1066],{"class":310},"\u002Flo_profile",[44,1068,1069],{"class":310}," --headless",[44,1071,1072],{"class":310}," --convert-to",[44,1074,1075],{"class":54}," pdf",[44,1077,1078],{"class":54}," report_openpyxl.xlsx",[44,1080,1081],{"class":255}," >",[44,1083,1084],{"class":54}," \u002Fdev\u002Fnull",[44,1086,1087],{"class":255}," 2>&1\n",[44,1089,1090,1093,1096,1099,1102,1105,1108,1111,1114],{"class":46,"line":263},[44,1091,1092],{"class":310},"test",[44,1094,1095],{"class":310}," -s",[44,1097,1098],{"class":54}," report_openpyxl.pdf",[44,1100,1101],{"class":259}," && ",[44,1103,1104],{"class":310},"echo",[44,1106,1107],{"class":54}," \"converted OK\"",[44,1109,1110],{"class":255}," ||",[44,1112,1113],{"class":310}," echo",[44,1115,1116],{"class":54}," \"conversion failed\"\n",[34,1118,1120],{"className":246,"code":1119,"language":248,"meta":39,"style":39},"from openpyxl import load_workbook\n\nreloaded = load_workbook(\"report_openpyxl.xlsx\")\nprint(reloaded.active[\"A1\"].value)\n",[41,1121,1122,1133,1137,1151],{"__ignoreMap":39},[44,1123,1124,1126,1128,1130],{"class":46,"line":47},[44,1125,572],{"class":255},[44,1127,575],{"class":259},[44,1129,256],{"class":255},[44,1131,1132],{"class":259}," load_workbook\n",[44,1134,1135],{"class":46,"line":263},[44,1136,267],{"emptyLinePlaceholder":266},[44,1138,1139,1142,1144,1147,1149],{"class":46,"line":270},[44,1140,1141],{"class":259},"reloaded ",[44,1143,276],{"class":255},[44,1145,1146],{"class":259}," load_workbook(",[44,1148,927],{"class":54},[44,1150,285],{"class":259},[44,1152,1153,1155,1158,1160],{"class":46,"line":288},[44,1154,518],{"class":310},[44,1156,1157],{"class":259},"(reloaded.active[",[44,1159,910],{"class":54},[44,1161,1162],{"class":259},"].value)\n",[10,1164,1165,1166,1169,1170,1173],{},"Both LibreOffice and openpyxl open the patched file. That is also the key limitation of openpyxl here: it ",[14,1167,1168],{},"reads"," the file fine, and it will ",[14,1171,1172],{},"drop"," the flag the next time it saves it — the next step shows it.",[29,1175,1177],{"id":1176},"step-4-dont-lose-the-flag-on-a-re-save","Step 4 — Don't lose the flag on a re-save",[10,1179,1180],{},"Pipelines often build a workbook with one library and finish it with another. If openpyxl touches the file after the flag was set, the flag is gone:",[34,1182,1184],{"className":246,"code":1183,"language":248,"meta":39,"style":39},"wb = load_workbook(\"monthly_report.xlsx\")\nwb[\"Summary\"][\"C1\"] = \"Checked\"\nwb.save(\"monthly_report_resaved.xlsx\")\nprint(\"before re-save:\", file_sharing(\"monthly_report.xlsx\"))\nprint(\"after re-save: \", file_sharing(\"monthly_report_resaved.xlsx\"))\nadd_read_only_recommended(\"monthly_report_resaved.xlsx\")\nprint(\"after patch:   \", file_sharing(\"monthly_report_resaved.xlsx\"))\n",[41,1185,1186,1198,1218,1227,1243,1258,1266],{"__ignoreMap":39},[44,1187,1188,1190,1192,1194,1196],{"class":46,"line":47},[44,1189,273],{"class":259},[44,1191,276],{"class":255},[44,1193,1146],{"class":259},[44,1195,282],{"class":54},[44,1197,285],{"class":259},[44,1199,1200,1203,1205,1208,1211,1213,1215],{"class":46,"line":263},[44,1201,1202],{"class":259},"wb[",[44,1204,299],{"class":54},[44,1206,1207],{"class":259},"][",[44,1209,1210],{"class":54},"\"C1\"",[44,1212,913],{"class":259},[44,1214,276],{"class":255},[44,1216,1217],{"class":54}," \"Checked\"\n",[44,1219,1220,1222,1225],{"class":46,"line":270},[44,1221,924],{"class":259},[44,1223,1224],{"class":54},"\"monthly_report_resaved.xlsx\"",[44,1226,285],{"class":259},[44,1228,1229,1231,1233,1236,1239,1241],{"class":46,"line":288},[44,1230,518],{"class":310},[44,1232,751],{"class":259},[44,1234,1235],{"class":54},"\"before re-save:\"",[44,1237,1238],{"class":259},", file_sharing(",[44,1240,282],{"class":54},[44,1242,526],{"class":259},[44,1244,1245,1247,1249,1252,1254,1256],{"class":46,"line":304},[44,1246,518],{"class":310},[44,1248,751],{"class":259},[44,1250,1251],{"class":54},"\"after re-save: \"",[44,1253,1238],{"class":259},[44,1255,1224],{"class":54},[44,1257,526],{"class":259},[44,1259,1260,1262,1264],{"class":46,"line":332},[44,1261,935],{"class":259},[44,1263,1224],{"class":54},[44,1265,285],{"class":259},[44,1267,1268,1270,1272,1275,1277,1279],{"class":46,"line":394},[44,1269,518],{"class":310},[44,1271,751],{"class":259},[44,1273,1274],{"class":54},"\"after patch:   \"",[44,1276,1238],{"class":259},[44,1278,1224],{"class":54},[44,1280,526],{"class":259},[10,1282,1283,1284,1288],{},"Make setting the flag the last step of the pipeline, after every library has finished with the file — the same rule as for watermarks and anything else openpyxl does not round-trip. ",[23,1285,1287],{"href":1286},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fwhat-openpyxl-loses-when-saving-an-existing-workbook\u002F","What openpyxl Loses When Saving an Existing Workbook"," lists the rest.",[70,1290,79,1295,79,1298,79,1301,79,1304,79,1308,79,1315,79,1320,79,1323,79,1327,79,1332,79,1337,79,1340,79,1346,79,1350,79,1352,79,1355,79,1359,79,1362,79,1365,79,1370,79,1373,79,1376,79,1379,79,1383,79,1386,79,1389,79,1394,79,1399,79,1403,79,1407,79,1410,79,1413,79,1419,79,1423],{"viewBox":1291,"role":73,"ariaLabelledBy":1292,"xmlns":77,"style":78},"4 0 752 142",[1293,1294],"ro-pipeline-t","ro-pipeline-d",[81,1296,1297],{"id":1293},"Set the flag last",[85,1299,1300],{"id":1294},"A report pipeline where data is written, formatting is applied with openpyxl, the file is saved, the read-only flag is added by the patch, and the file is published. Adding the flag before the openpyxl step would lose it.",[89,1302],{"x":91,"y":92,"width":93,"height":1303,"fill":95},"142",[89,1305],{"x":98,"y":99,"width":1306,"height":1307,"rx":974,"fill":136,"stroke":102,"style":977},"115.2","110.5",[222,1309,1311],{"transform":1310},"translate(65.6,29.0) scale(1.000)",[226,1312],{"d":1313,"fill":229,"stroke":1314,"style":985},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--text,#172033)",[112,1316,1319],{"x":1317,"y":989,"style":1318},"77.6","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Write data",[112,1321,1322],{"x":1317,"y":994,"style":148},"pandas or",[112,1324,1326],{"x":1317,"y":1325,"style":148},"112.5","xlsxwriter",[46,1328],{"x1":1329,"y1":1330,"x2":1331,"y2":1330,"stroke":109,"style":977},"140.2","71.2","162.2",[1333,1334],"polygon",{"points":1335,"fill":1336},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[89,1338],{"x":1339,"y":99,"width":1306,"height":1307,"rx":974,"fill":108,"stroke":109,"style":977},"171.2",[222,1341,1343],{"transform":1342},"translate(216.8,29.0) scale(1.000)",[226,1344],{"d":1345,"fill":229,"stroke":1025,"style":985},"M12 9a3 3 0 1 0 .01 0z M12 2.5v3 M12 18.5v3 M2.5 12h3 M18.5 12h3 M5.3 5.3l2.1 2.1 M16.6 16.6l2.1 2.1 M5.3 18.7l2.1-2.1 M16.6 7.4l2.1-2.1",[112,1347,1349],{"x":1348,"y":989,"style":1029},"228.8","Finish with",[112,1351,205],{"x":1348,"y":973,"style":1029},[112,1353,1354],{"x":1348,"y":1325,"style":148},"styles, formulas",[46,1356],{"x1":1357,"y1":1330,"x2":1358,"y2":1330,"stroke":109,"style":977},"291.4","313.4",[1333,1360],{"points":1361,"fill":1336},"317.4,71.2 308.4,76.2 308.4,66.2",[89,1363],{"x":1364,"y":99,"width":1306,"height":1307,"rx":974,"fill":975,"stroke":976,"style":977},"322.4",[222,1366,1367],{"transform":980},[226,1368],{"d":1369,"fill":229,"stroke":984,"style":985},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[112,1371,1372],{"x":988,"y":989,"style":990},"Save",[112,1374,1375],{"x":988,"y":994,"style":148},"flag would be",[112,1377,1378],{"x":988,"y":1325,"style":148},"lost here",[46,1380],{"x1":1381,"y1":1330,"x2":1382,"y2":1330,"stroke":109,"style":977},"442.6","464.6",[1333,1384],{"points":1385,"fill":1336},"468.6,71.2 459.6,76.2 459.6,66.2",[89,1387],{"x":1388,"y":99,"width":1306,"height":1307,"rx":974,"fill":999,"stroke":1000,"style":977},"473.6",[222,1390,1392],{"transform":1391},"translate(519.2,37.8) scale(1.000)",[226,1393],{"d":1024,"fill":229,"stroke":1006,"style":985},[112,1395,1398],{"x":1396,"y":1397,"style":1010},"531.2","84.2","Add flag",[112,1400,1402],{"x":1396,"y":1401,"style":148},"103.8","last step",[46,1404],{"x1":1405,"y1":1330,"x2":1406,"y2":1330,"stroke":109,"style":977},"593.8","615.8",[1333,1408],{"points":1409,"fill":1336},"619.8,71.2 610.8,76.2 610.8,66.2",[89,1411],{"x":1412,"y":99,"width":1306,"height":1307,"rx":974,"fill":999,"stroke":1000,"style":977},"624.8",[222,1414,1416],{"transform":1415},"translate(670.4,37.8) scale(1.000)",[226,1417],{"d":1418,"fill":229,"stroke":1006,"style":985},"M3 6h6l2 2h10v11H3z",[112,1420,1422],{"x":1421,"y":1397,"style":1010},"682.4","Publish",[112,1424,1425],{"x":1421,"y":1401,"style":148},"shared drive",[29,1427,1429],{"id":1428},"step-5-mark-as-final","Step 5 — Mark as final",[10,1431,1432,1434,1435,1438,1439,1442],{},[14,1433,166],{}," is a stronger nudge: Excel opens the file with the ribbon's editing commands disabled and a yellow message bar saying the author marked it final, with an ",[14,1436,1437],{},"Edit Anyway"," button. It is stored as a custom document property named ",[41,1440,1441],{},"_MarkAsFinal",". Both libraries can write it:",[34,1444,1446],{"className":246,"code":1445,"language":248,"meta":39,"style":39},"from openpyxl.packaging.custom import BoolProperty\n\nwb = xlsxwriter.Workbook(\"final_xlsxwriter.xlsx\")\nwb.add_worksheet().write(\"A1\", \"Final figures\")\nwb.set_custom_property(\"_MarkAsFinal\", True)\nwb.close()\n\nwb = Workbook()\nwb.active[\"A1\"] = \"Final figures\"\nwb.custom_doc_props.append(BoolProperty(name=\"_MarkAsFinal\", value=True))\nwb.save(\"final_openpyxl.xlsx\")\n\nfor path in (\"final_xlsxwriter.xlsx\", \"final_openpyxl.xlsx\"):\n    props = load_workbook(path).custom_doc_props\n    print(path, [(p.name, p.value) for p in props.props])\n\nload_workbook(\"final_xlsxwriter.xlsx\").save(\"final_resaved.xlsx\")\nprint(\"after openpyxl re-save:\", [(p.name, p.value) for p in load_workbook(\"final_resaved.xlsx\").custom_doc_props.props])\n",[41,1447,1448,1460,1464,1477,1491,1506,1510,1514,1522,1535,1558,1567,1571,1590,1600,1618,1622,1637],{"__ignoreMap":39},[44,1449,1450,1452,1455,1457],{"class":46,"line":47},[44,1451,572],{"class":255},[44,1453,1454],{"class":259}," openpyxl.packaging.custom ",[44,1456,256],{"class":255},[44,1458,1459],{"class":259}," BoolProperty\n",[44,1461,1462],{"class":46,"line":263},[44,1463,267],{"emptyLinePlaceholder":266},[44,1465,1466,1468,1470,1472,1475],{"class":46,"line":270},[44,1467,273],{"class":259},[44,1469,276],{"class":255},[44,1471,279],{"class":259},[44,1473,1474],{"class":54},"\"final_xlsxwriter.xlsx\"",[44,1476,285],{"class":259},[44,1478,1479,1482,1484,1486,1489],{"class":46,"line":288},[44,1480,1481],{"class":259},"wb.add_worksheet().write(",[44,1483,910],{"class":54},[44,1485,313],{"class":259},[44,1487,1488],{"class":54},"\"Final figures\"",[44,1490,285],{"class":259},[44,1492,1493,1496,1499,1501,1504],{"class":46,"line":304},[44,1494,1495],{"class":259},"wb.set_custom_property(",[44,1497,1498],{"class":54},"\"_MarkAsFinal\"",[44,1500,313],{"class":259},[44,1502,1503],{"class":310},"True",[44,1505,285],{"class":259},[44,1507,1508],{"class":46,"line":332},[44,1509,414],{"class":259},[44,1511,1512],{"class":46,"line":394},[44,1513,267],{"emptyLinePlaceholder":266},[44,1515,1516,1518,1520],{"class":46,"line":405},[44,1517,273],{"class":259},[44,1519,276],{"class":255},[44,1521,901],{"class":259},[44,1523,1524,1526,1528,1530,1532],{"class":46,"line":411},[44,1525,907],{"class":259},[44,1527,910],{"class":54},[44,1529,913],{"class":259},[44,1531,276],{"class":255},[44,1533,1534],{"class":54}," \"Final figures\"\n",[44,1536,1537,1540,1543,1545,1547,1549,1552,1554,1556],{"class":46,"line":417},[44,1538,1539],{"class":259},"wb.custom_doc_props.append(BoolProperty(",[44,1541,1542],{"class":382},"name",[44,1544,276],{"class":255},[44,1546,1498],{"class":54},[44,1548,313],{"class":259},[44,1550,1551],{"class":382},"value",[44,1553,276],{"class":255},[44,1555,1503],{"class":310},[44,1557,526],{"class":259},[44,1559,1560,1562,1565],{"class":46,"line":422},[44,1561,924],{"class":259},[44,1563,1564],{"class":54},"\"final_openpyxl.xlsx\"",[44,1566,285],{"class":259},[44,1568,1569],{"class":46,"line":430},[44,1570,267],{"emptyLinePlaceholder":266},[44,1572,1573,1575,1577,1579,1582,1584,1586,1588],{"class":46,"line":438},[44,1574,335],{"class":255},[44,1576,603],{"class":259},[44,1578,341],{"class":255},[44,1580,1581],{"class":259}," (",[44,1583,1474],{"class":54},[44,1585,313],{"class":259},[44,1587,1564],{"class":54},[44,1589,391],{"class":259},[44,1591,1592,1595,1597],{"class":46,"line":443},[44,1593,1594],{"class":259},"    props ",[44,1596,276],{"class":255},[44,1598,1599],{"class":259}," load_workbook(path).custom_doc_props\n",[44,1601,1602,1605,1608,1610,1613,1615],{"class":46,"line":456},[44,1603,1604],{"class":310},"    print",[44,1606,1607],{"class":259},"(path, [(p.name, p.value) ",[44,1609,335],{"class":255},[44,1611,1612],{"class":259}," p ",[44,1614,341],{"class":255},[44,1616,1617],{"class":259}," props.props])\n",[44,1619,1620],{"class":46,"line":473},[44,1621,267],{"emptyLinePlaceholder":266},[44,1623,1624,1627,1629,1632,1635],{"class":46,"line":510},[44,1625,1626],{"class":259},"load_workbook(",[44,1628,1474],{"class":54},[44,1630,1631],{"class":259},").save(",[44,1633,1634],{"class":54},"\"final_resaved.xlsx\"",[44,1636,285],{"class":259},[44,1638,1639,1641,1643,1646,1649,1651,1653,1655,1657,1659],{"class":46,"line":515},[44,1640,518],{"class":310},[44,1642,751],{"class":259},[44,1644,1645],{"class":54},"\"after openpyxl re-save:\"",[44,1647,1648],{"class":259},", [(p.name, p.value) ",[44,1650,335],{"class":255},[44,1652,1612],{"class":259},[44,1654,341],{"class":255},[44,1656,1146],{"class":259},[44,1658,1634],{"class":54},[44,1660,1661],{"class":259},").custom_doc_props.props])\n",[10,1663,1664,1665,1667],{},"Unlike ",[41,1666,546],{},", custom properties survive an openpyxl round trip, because openpyxl reads and writes them. Use mark-as-final for documents that genuinely should not change — a signed-off month-end pack, say — and read-only recommended for working reports that people may reasonably copy and adapt.",[29,1669,1671],{"id":1670},"removing-the-flag-again","Removing the flag again",[10,1673,1674,1675,1677],{},"A report that was published read-only sometimes needs to become a working file again — for next month's version, say. Excel removes the flag through ",[14,1676,536],{},"; in a script, drop the element with the same kind of patch:",[34,1679,1681],{"className":246,"code":1680,"language":248,"meta":39,"style":39},"def remove_read_only_recommended(path):\n    tmp = path + \".tmp\"\n    with zipfile.ZipFile(path) as src, zipfile.ZipFile(tmp, \"w\", zipfile.ZIP_DEFLATED) as dst:\n        for item in src.infolist():\n            data = src.read(item.filename)\n            if item.filename == \"xl\u002Fworkbook.xml\":\n                data = re.sub(rb\"\u003CfileSharing[^>]*\u002F>\", b\"\", data)\n            dst.writestr(item, data)\n    shutil.move(tmp, path)\n\nshutil.copy(\"monthly_report.xlsx\", \"next_month.xlsx\")\nremove_read_only_recommended(\"next_month.xlsx\")\nprint(file_sharing(\"next_month.xlsx\"))\n",[41,1682,1683,1692,1704,1726,1736,1744,1756,1792,1796,1800,1804,1818,1827],{"__ignoreMap":39},[44,1684,1685,1687,1690],{"class":46,"line":47},[44,1686,446],{"class":255},[44,1688,1689],{"class":449}," remove_read_only_recommended",[44,1691,453],{"class":259},[44,1693,1694,1696,1698,1700,1702],{"class":46,"line":263},[44,1695,598],{"class":259},[44,1697,276],{"class":255},[44,1699,603],{"class":259},[44,1701,606],{"class":255},[44,1703,609],{"class":54},[44,1705,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724],{"class":46,"line":270},[44,1707,614],{"class":255},[44,1709,617],{"class":259},[44,1711,620],{"class":255},[44,1713,623],{"class":259},[44,1715,626],{"class":54},[44,1717,629],{"class":259},[44,1719,632],{"class":310},[44,1721,635],{"class":259},[44,1723,620],{"class":255},[44,1725,640],{"class":259},[44,1727,1728,1730,1732,1734],{"class":46,"line":288},[44,1729,645],{"class":255},[44,1731,648],{"class":259},[44,1733,341],{"class":255},[44,1735,653],{"class":259},[44,1737,1738,1740,1742],{"class":46,"line":304},[44,1739,658],{"class":259},[44,1741,276],{"class":255},[44,1743,663],{"class":259},[44,1745,1746,1748,1750,1752,1754],{"class":46,"line":332},[44,1747,668],{"class":255},[44,1749,671],{"class":259},[44,1751,674],{"class":255},[44,1753,677],{"class":54},[44,1755,807],{"class":259},[44,1757,1758,1760,1762,1764,1767,1769,1771,1773,1775,1777,1779,1781,1783,1786,1789],{"class":46,"line":394},[44,1759,867],{"class":259},[44,1761,276],{"class":255},[44,1763,743],{"class":259},[44,1765,1766],{"class":255},"rb",[44,1768,485],{"class":54},[44,1770,488],{"class":310},[44,1772,491],{"class":255},[44,1774,495],{"class":494},[44,1776,498],{"class":310},[44,1778,501],{"class":255},[44,1780,504],{"class":54},[44,1782,313],{"class":259},[44,1784,1785],{"class":255},"b",[44,1787,1788],{"class":54},"\"\"",[44,1790,1791],{"class":259},", data)\n",[44,1793,1794],{"class":46,"line":405},[44,1795,881],{"class":259},[44,1797,1798],{"class":46,"line":411},[44,1799,886],{"class":259},[44,1801,1802],{"class":46,"line":417},[44,1803,267],{"emptyLinePlaceholder":266},[44,1805,1806,1809,1811,1813,1816],{"class":46,"line":422},[44,1807,1808],{"class":259},"shutil.copy(",[44,1810,282],{"class":54},[44,1812,313],{"class":259},[44,1814,1815],{"class":54},"\"next_month.xlsx\"",[44,1817,285],{"class":259},[44,1819,1820,1823,1825],{"class":46,"line":430},[44,1821,1822],{"class":259},"remove_read_only_recommended(",[44,1824,1815],{"class":54},[44,1826,285],{"class":259},[44,1828,1829,1831,1833,1835],{"class":46,"line":438},[44,1830,518],{"class":310},[44,1832,521],{"class":259},[44,1834,1815],{"class":54},[44,1836,526],{"class":259},[10,1838,1839,1840,1842,1843,1846],{},"The same ",[41,1841,546],{}," element also stores Excel's ",[14,1844,1845],{},"password to modify"," when one is set, so removing it removes that password too — check whether a file has one before stripping the element from files you did not create. Or simply load and save the file with openpyxl, which has the same effect as a side-effect — but the explicit function says what it does, and leaves the rest of the package untouched.",[29,1848,1850],{"id":1849},"what-these-settings-do-not-do","What these settings do not do",[10,1852,1853,1854,1858,1859,1862,1863,27],{},"Both are conveniences. They do not encrypt anything, do not stop a determined edit, and are ignored by programs other than Excel — LibreOffice, Google Sheets and every Python library open the file normally. If a file must not be changed, protect the sheets with a password (",[23,1855,1857],{"href":1856},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Flock-cells-and-protect-a-sheet-with-openpyxl\u002F","Lock Cells and Protect a Sheet with openpyxl","), publish it as a PDF, or control write access on the shared drive itself. If it must not be ",[14,1860,1861],{},"read",", encrypt it: ",[23,1864,1866],{"href":1865},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fpassword-protect-an-excel-file-with-python\u002F","Password Protect an Excel File with Python",[29,1868,1870],{"id":1869},"common-pitfalls","Common pitfalls",[1872,1873,1874,1882,1888,1898,1904],"ul",{},[1875,1876,1877,1881],"li",{},[1878,1879,1880],"strong",{},"Expecting openpyxl to write the flag."," It has no setting for it; use xlsxwriter or patch after saving.",[1875,1883,1884,1887],{},[1878,1885,1886],{},"Setting the flag before a re-save."," openpyxl drops it; add it last.",[1875,1889,1890,1893,1894,1897],{},[1878,1891,1892],{},"Inserting the element in the wrong place."," It must come right after ",[41,1895,1896],{},"fileVersion",", or first in the workbook element; otherwise Excel repairs the file.",[1875,1899,1900,1903],{},[1878,1901,1902],{},"Treating it as protection."," It is a prompt, and only Excel shows it.",[1875,1905,1906,1909],{},[1878,1907,1908],{},"Forgetting it is permanent until removed."," Every user sees the prompt every time; remove it via Save As options when the file becomes a working copy.",[29,1911,1913],{"id":1912},"conclusion","Conclusion",[10,1915,1916,1917,1920,1921,1923,1924,1926],{},"For a report that should be opened read-only by default, call ",[41,1918,1919],{},"read_only_recommended()"," in xlsxwriter, or add ",[41,1922,532],{}," to the workbook part of an openpyxl file with a small zip patch as the very last step. For documents that are finished, set the ",[41,1925,1441],{}," custom property, which both libraries write and openpyxl preserves. Check the saved files, and keep real protection — passwords, PDFs, file permissions — for files that must not change.",[29,1928,1930],{"id":1929},"frequently-asked-questions","Frequently asked questions",[10,1932,1933,1936],{},[1878,1934,1935],{},"How do I make an Excel file open read-only recommended with Python?","\nWith xlsxwriter, call workbook.read_only_recommended() before closing the workbook. For files written by openpyxl, add the element fileSharing with readOnlyRecommended=\"1\" to xl\u002Fworkbook.xml after saving.",[10,1938,1939,1942],{},[1878,1940,1941],{},"Does openpyxl support read-only recommended?","\nIt can read the setting but never writes it, so saving a workbook with openpyxl removes the flag. Add it after the last openpyxl save.",[10,1944,1945,1948],{},[1878,1946,1947],{},"How do I mark an Excel workbook as final from Python?","\nAdd a boolean custom document property named _MarkAsFinal set to true — with xlsxwriter's set_custom_property or openpyxl's custom_doc_props. Excel then opens the file with editing disabled and an Edit Anyway button.",[10,1950,1951,1954],{},[1878,1952,1953],{},"Is read-only recommended secure?","\nNo. Users can decline the prompt, and programs other than Excel ignore it. Use sheet protection, a PDF, file permissions or encryption when the file must not be changed or read.",[29,1956,1958],{"id":1957},"related","Related",[1872,1960,1961,1967,1974,1981],{},[1875,1962,1963,1964,1966],{},"Up one level: ",[23,1965,26],{"href":25}," — protection, passwords and clean sharing.",[1875,1968,1969,1973],{},[23,1970,1972],{"href":1971},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fprotect-workbook-structure-with-openpyxl\u002F","Protect Workbook Structure with openpyxl"," — stopping sheets being added, deleted or renamed.",[1875,1975,1976,1980],{},[23,1977,1979],{"href":1978},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-workbook-properties-with-python\u002F","Set Workbook Properties with Python"," — the document properties mark-as-final lives in.",[1875,1982,1983,1987],{},[23,1984,1986],{"href":1985},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fremove-hidden-data-before-sharing-an-excel-file\u002F","Remove Hidden Data Before Sharing an Excel File"," — the other pre-publication check.",[1989,1990,1991],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sx1sP, html code.shiki .sx1sP{--shiki-default:#22863A;--shiki-dark:#72F088}",{"title":39,"searchDepth":263,"depth":263,"links":1993},[1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005],{"id":31,"depth":263,"text":32},{"id":239,"depth":263,"text":240},{"id":539,"depth":263,"text":540},{"id":1043,"depth":263,"text":1044},{"id":1176,"depth":263,"text":1177},{"id":1428,"depth":263,"text":1429},{"id":1670,"depth":263,"text":1671},{"id":1849,"depth":263,"text":1850},{"id":1869,"depth":263,"text":1870},{"id":1912,"depth":263,"text":1913},{"id":1929,"depth":263,"text":1930},{"id":1957,"depth":263,"text":1958},"2026-09-29","Stop colleagues from accidentally editing a published report: set the read-only recommended flag with xlsxwriter, add it to openpyxl workbooks with a small post-save patch, mark a workbook as final, keep the flag from being dropped when the file is re-saved, and understand what these settings do and do not protect.","md",[2010,2012,2014,2016],{"q":1935,"a":2011},"With xlsxwriter, call workbook.read_only_recommended() before closing the workbook. For files written by openpyxl, add the element fileSharing with readOnlyRecommended=\"1\" to xl\u002Fworkbook.xml after saving.",{"q":1941,"a":2013},"It can read the setting but never writes it, so saving a workbook with openpyxl removes the flag. Add it after the last openpyxl save.",{"q":1947,"a":2015},"Add a boolean custom document property named _MarkAsFinal set to true — with xlsxwriter's set_custom_property or openpyxl's custom_doc_props. Excel then opens the file with editing disabled and an Edit Anyway button.",{"q":1953,"a":2017},"No. Users can decline the prompt, and programs other than Excel ignore it. Use sheet protection, a PDF, file permissions or encryption when the file must not be changed or read.","make an excel file open read-only recommended",null,{"breadcrumb":2021},[2022,2025,2028],{"name":2023,"item":2024},"Home","\u002F",{"name":2026,"item":2027},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":26,"item":25},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-file-open-read-only-recommended",{"title":2031,"description":2032},"Read-Only Recommended and Mark as Final in Excel with Python","Make Excel prompt users to open a workbook read-only: xlsxwriter read_only_recommended(), a zip patch adding \u003CfileSharing readOnlyRecommended=\"1\"\u002F> for openpyxl files, the _MarkAsFinal custom property, and why openpyxl re-saves remove the flag.","make-an-excel-file-open-read-only-recommended","formatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-file-open-read-only-recommended\u002Findex",[2036,205],"protection-and-sharing","how-to","cQz3cbuUmnXU3MD7yuZWqSiGxBPPjlUSQ-iVB61PUJg",[2040,2043],{"title":1857,"path":2041,"stem":2042,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Flock-cells-and-protect-a-sheet-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Flock-cells-and-protect-a-sheet-with-openpyxl\u002Findex",{"title":2044,"path":2045,"stem":2046,"children":-1},"Make an Excel Report Accessible with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-report-accessible","formatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fmake-an-excel-report-accessible\u002Findex",[2048,2050],{"path":2049,"title":2026},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":2051,"title":26},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks",[],[2054,2057,2060,2063,2066,2069],{"path":2055,"title":2056,"kind":2037},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fanonymise-personal-data-in-an-excel-file-with-python","Anonymise Personal Data in an Excel File with Python",{"path":2058,"title":2059,"kind":2037},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fpassword-protect-a-pdf-exported-from-excel","Password-Protect a PDF Exported from Excel",{"path":2061,"title":2062,"kind":2037},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fsign-a-pdf-report-digitally-with-python","Sign a PDF Report Digitally with Python",{"path":2064,"title":2065,"kind":2037},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fdefine-names-and-protect-sheets-with-xlsxwriter","Define Names and Protect Sheets with XlsxWriter",{"path":2067,"title":2068,"kind":2037},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fadd-comments-and-notes-to-excel-cells-with-python","Add Comments and Notes to Excel Cells with Python",{"path":2070,"title":2071,"kind":2037},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fhide-sheets-rows-and-columns-with-openpyxl","Hide Sheets, Rows and Columns with openpyxl",1790972116943]