[{"data":1,"prerenderedAt":440},["ShallowReactive",2],{"\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fpersonnaliser_les_patches":3,"section:\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fpersonnaliser_les_patches":439},{"id":4,"title":5,"body":6,"description":426,"extension":427,"meta":428,"navigation":157,"path":435,"seo":436,"stem":437,"__hash__":438},"docs\u002F2.next\u002F3.dodock\u002F10.developpeurs\u002Fpersonnaliser_les_patches.md","Organiser les patches d'une application",{"type":7,"value":8,"toc":418},"minimark",[9,13,31,38,44,48,117,129,188,195,209,213,243,253,260,264,279,365,374,378,390,394,414],[10,11,5],"h1",{"id":12},"organiser-les-patches-dune-application",[14,15,16,17,21,22,25,26,30],"p",{},"Un patch est un script Python qui modifie le schéma ou les données de la base pendant ",[18,19,20],"code",{},"bench migrate",". Le fichier ",[18,23,24],{},"patches.txt"," d'une application liste ses patches. Dodock exécute chaque patch une seule fois par site et l'enregistre dans le ",[27,28,29],"strong",{},"Journal de Patch",".",[32,33,35,36],"h2",{"id":34},"sections-du-fichier-patchestxt","Sections du fichier ",[18,37,24],{},[14,39,40,41,43],{},"Le fichier ",[18,42,24],{}," se divise en sections. La section d'un patch décide de son moment d'exécution :",[45,46,47],"mermaid",{},"\nflowchart TD\n    A[bench migrate] --> B[pre_model_sync]\n    B --> C[Synchronisation des DocTypes]\n    C --> D[post_model_sync]\n    D --> E[Synchronisation des fixtures et des personnalisations]\n    E --> F[post_fixture_sync]\n",[49,50,51,67],"table",{},[52,53,54],"thead",{},[55,56,57,61,64],"tr",{},[58,59,60],"th",{},"Section",[58,62,63],{},"Exécution",[58,65,66],{},"Usage",[68,69,70,84,97],"tbody",{},[55,71,72,78,81],{},[73,74,75],"td",{},[18,76,77],{},"[pre_model_sync]",[73,79,80],{},"Avant la synchronisation du schéma des DocTypes",[73,82,83],{},"Patch qui lit l'ancien schéma (colonne renommée ou supprimée par la nouvelle version)",[55,85,86,91,94],{},[73,87,88],{},[18,89,90],{},"[post_model_sync]",[73,92,93],{},"Après la synchronisation du schéma",[73,95,96],{},"Patch de données qui utilise le nouveau schéma",[55,98,99,104,114],{},[73,100,101],{},[18,102,103],{},"[post_fixture_sync]",[73,105,106,107,110,111],{},"Après la synchronisation des fixtures, des ",[27,108,109],{},"Champs personnalisés"," et des ",[27,112,113],{},"Property Setters",[73,115,116],{},"Patch qui utilise un champ ou un DocType ajouté par une fixture ou une personnalisation",[14,118,119,120,122,123,125,126,128],{},"Dans une section, les patches s'exécutent dans l'ordre du fichier. Les sections ",[18,121,77],{}," et ",[18,124,90],{}," sont obligatoires dans un fichier à sections. La section ",[18,127,103],{}," est facultative.",[130,131,136],"pre",{"className":132,"code":133,"language":134,"meta":135,"style":135},"language-ini shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[pre_model_sync]\nmon_app.patches.v2_0.renommer_ancienne_colonne\n\n[post_model_sync]\nmon_app.patches.v2_0.mettre_a_jour_statuts\n\n[post_fixture_sync]\nmon_app.patches.v2_0.remplir_remise_fidelite\n","ini","",[18,137,138,146,152,159,165,171,176,182],{"__ignoreMap":135},[139,140,143],"span",{"class":141,"line":142},"line",1,[139,144,145],{},"[pre_model_sync]\n",[139,147,149],{"class":141,"line":148},2,[139,150,151],{},"mon_app.patches.v2_0.renommer_ancienne_colonne\n",[139,153,155],{"class":141,"line":154},3,[139,156,158],{"emptyLinePlaceholder":157},true,"\n",[139,160,162],{"class":141,"line":161},4,[139,163,164],{},"[post_model_sync]\n",[139,166,168],{"class":141,"line":167},5,[139,169,170],{},"mon_app.patches.v2_0.mettre_a_jour_statuts\n",[139,172,174],{"class":141,"line":173},6,[139,175,158],{"emptyLinePlaceholder":157},[139,177,179],{"class":141,"line":178},7,[139,180,181],{},"[post_fixture_sync]\n",[139,183,185],{"class":141,"line":184},8,[139,186,187],{},"mon_app.patches.v2_0.remplir_remise_fidelite\n",[14,189,190,191,194],{},"Une ligne qui commence par ",[18,192,193],{},"execute:"," contient une instruction Python à la place d'un chemin de module :",[130,196,198],{"className":132,"code":197,"language":134,"meta":135,"style":135},"[post_model_sync]\nexecute:frappe.delete_doc_if_exists(\"Report\", \"Ancien rapport\")\n",[18,199,200,204],{"__ignoreMap":135},[139,201,202],{"class":141,"line":142},[139,203,164],{},[139,205,206],{"class":141,"line":148},[139,207,208],{},"execute:frappe.delete_doc_if_exists(\"Report\", \"Ancien rapport\")\n",[32,210,212],{"id":211},"créer-un-patch","Créer un patch",[214,215,216,237,240],"ol",{},[217,218,219,220],"li",{},"Lancez la commande interactive depuis le dossier du bench :",[130,221,225],{"className":222,"code":223,"language":224,"meta":135,"style":135},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","bench create-patch\n","bash",[18,226,227],{"__ignoreMap":135},[139,228,229,233],{"class":141,"line":142},[139,230,232],{"class":231},"sBMFI","bench",[139,234,236],{"class":235},"sfazB"," create-patch\n",[217,238,239],{},"Choisissez l'application, puis le DocType concerné.",[217,241,242],{},"Décrivez le patch. La description devient la docstring et propose un nom de fichier.",[14,244,245,246,249,250,252],{},"La commande crée le fichier dans le dossier ",[18,247,248],{},"patches"," du DocType. Elle ajoute aussi son chemin à la fin de ",[18,251,24],{},", donc dans la dernière section du fichier. Déplacez la ligne si le patch doit aller dans une autre section.",[14,254,255,256,259],{},"Le fichier contient une fonction ",[18,257,258],{},"execute()"," sans argument. Écrivez la modification dans cette fonction.",[32,261,263],{"id":262},"exemple","Exemple",[14,265,266,267,270,271,274,275,278],{},"Cet exemple est fictif. Une application ajoute à ",[27,268,269],{},"Client"," deux champs personnalisés par une fixture : ",[18,272,273],{},"custom_statut_fidelite"," (Bronze, Argent, Or) et ",[18,276,277],{},"custom_remise_fidelite",". Un patch remplit la remise selon le statut.",[130,280,284],{"className":281,"code":282,"language":283,"meta":135,"style":135},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# mon_app\u002Fpatches\u002Fv2_0\u002Fremplir_remise_fidelite.py\nimport frappe\n\nREMISES = {\"Bronze\": 5, \"Argent\": 10, \"Or\": 15}\n\n\ndef execute():\n    for statut, remise in REMISES.items():\n        frappe.db.set_value(\n            \"Customer\",\n            {\"custom_statut_fidelite\": statut},\n            \"custom_remise_fidelite\",\n            remise,\n            update_modified=False,\n        )\n","python",[18,285,286,291,296,300,305,309,313,318,323,329,335,341,347,353,359],{"__ignoreMap":135},[139,287,288],{"class":141,"line":142},[139,289,290],{},"# mon_app\u002Fpatches\u002Fv2_0\u002Fremplir_remise_fidelite.py\n",[139,292,293],{"class":141,"line":148},[139,294,295],{},"import frappe\n",[139,297,298],{"class":141,"line":154},[139,299,158],{"emptyLinePlaceholder":157},[139,301,302],{"class":141,"line":161},[139,303,304],{},"REMISES = {\"Bronze\": 5, \"Argent\": 10, \"Or\": 15}\n",[139,306,307],{"class":141,"line":167},[139,308,158],{"emptyLinePlaceholder":157},[139,310,311],{"class":141,"line":173},[139,312,158],{"emptyLinePlaceholder":157},[139,314,315],{"class":141,"line":178},[139,316,317],{},"def execute():\n",[139,319,320],{"class":141,"line":184},[139,321,322],{},"    for statut, remise in REMISES.items():\n",[139,324,326],{"class":141,"line":325},9,[139,327,328],{},"        frappe.db.set_value(\n",[139,330,332],{"class":141,"line":331},10,[139,333,334],{},"            \"Customer\",\n",[139,336,338],{"class":141,"line":337},11,[139,339,340],{},"            {\"custom_statut_fidelite\": statut},\n",[139,342,344],{"class":141,"line":343},12,[139,345,346],{},"            \"custom_remise_fidelite\",\n",[139,348,350],{"class":141,"line":349},13,[139,351,352],{},"            remise,\n",[139,354,356],{"class":141,"line":355},14,[139,357,358],{},"            update_modified=False,\n",[139,360,362],{"class":141,"line":361},15,[139,363,364],{},"        )\n",[14,366,367,368,370,371,373],{},"Le patch va dans ",[18,369,103],{},". Dans ",[18,372,90],{},", les champs personnalisés n'existent pas encore lors d'une première installation de la version, et le patch échoue.",[32,375,377],{"id":376},"écrire-un-patch-sûr","Écrire un patch sûr",[379,380,381,384,387],"ul",{},[217,382,383],{},"Rendez le patch rejouable : une deuxième exécution ne doit rien casser.",[217,385,386],{},"Testez la migration sur une copie de la base avant la production.",[217,388,389],{},"Gardez le patch court. Pour un gros volume, traitez les données par lots.",[32,391,393],{"id":392},"vérifier-lexécution","Vérifier l'exécution",[14,395,396,397,399,400,403,404,407,408,413],{},"Ouvrez la liste ",[27,398,29],{},". Chaque patch exécuté y figure avec sa date. Avec ",[18,401,402],{},"bench migrate --skip-failing",", un patch en échec y figure comme ignoré, et la migration continue. Pour interdire cela sur un patch critique, utilisez le hook ",[18,405,406],{},"never_skip_patches"," (voir la ",[409,410,412],"a",{"href":411},"\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fhooks","Référence des hooks",").",[415,416,417],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":135,"searchDepth":142,"depth":148,"links":419},[420,422,423,424,425],{"id":34,"depth":148,"text":421},"Sections du fichier patches.txt",{"id":211,"depth":148,"text":212},{"id":262,"depth":148,"text":263},{"id":376,"depth":148,"text":377},{"id":392,"depth":148,"text":393},"Ranger les patches dans les sections du fichier patches.txt pour choisir à quel moment de la migration chacun s'exécute.","md",{"reviewed":157,"last_verified":429,"sources":430},"2026-11-03",[431,432,433,434],"dodock@develop:frappe\u002Fmodules\u002Fpatch_handler.py","dodock@develop:frappe\u002Fmigrate.py","dodock@develop:frappe\u002Fcommands\u002Futils.py","dodock@develop:frappe\u002Futils\u002Fboilerplate.py","\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fpersonnaliser_les_patches",{"title":5,"description":426},"2.next\u002F3.dodock\u002F10.developpeurs\u002Fpersonnaliser_les_patches","B8KMEJpDowx9ZI0hTe3zusplFQYCSAJ3y8NK54KQp9U",{},1793871748505]