[{"data":1,"prerenderedAt":600},["ShallowReactive",2],{"\u002Fnext\u002Fdodock\u002Fressources\u002Fcustom-app\u002Fcustom-fields":3,"section:\u002Fnext\u002Fdodock\u002Fressources\u002Fcustom-app\u002Fcustom-fields":599},{"id":4,"title":5,"body":6,"description":587,"extension":588,"meta":589,"navigation":141,"path":595,"seo":596,"stem":597,"__hash__":598},"docs\u002F2.next\u002F3.dodock\u002F14.ressources\u002F4.custom-app\u002F1.custom-fields.md","Champs additionnels",{"type":7,"value":8,"toc":581},"minimark",[9,14,29,34,69,73,97,101,120,532,536,577],[10,11,13],"h1",{"id":12},"créer-des-champs-additionnels-à-laide-dune-application-personnalisée","Créer des champs additionnels à l'aide d'une application personnalisée",[15,16,17,18,23,24,28],"p",{},"Une ",[19,20,22],"a",{"href":21},"\u002Fnext\u002Fdodock\u002Fressources\u002Fcustom-app","application personnalisée"," peut créer des champs personnalisés et des « Property Setter » par le code. Les mêmes champs arrivent alors sur tous les sites où l'application est installée (développement, recette, production), sans passer par ",[25,26,27],"strong",{},"Personnaliser un formulaire",".",[30,31,33],"h2",{"id":32},"principe","Principe",[35,36,37,46,55,62],"ol",{},[38,39,40,41,45],"li",{},"Le hook ",[42,43,44],"code",{},"after_install"," crée les champs à l'installation de l'application.",[38,47,40,48,51,52,28],{},[42,49,50],{},"after_migrate"," les met à jour à chaque ",[42,53,54],{},"bench migrate",[38,56,57,58,61],{},"La fonction ",[42,59,60],{},"create_custom_fields"," crée un champ absent et met à jour un champ existant.",[38,63,64,65,68],{},"La liste ",[42,66,67],{},"OLD_FIELDS"," supprime les champs que vous ne voulez plus.",[30,70,72],{"id":71},"déclarer-les-hooks","Déclarer les hooks",[74,75,81],"pre",{"className":76,"code":77,"filename":78,"language":79,"meta":80,"style":80},"language-py shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","after_install = \"my_app.install.after_install\"\nafter_migrate = \"my_app.install.after_migrate\"\n","my_app\u002Fhooks.py","py","",[42,82,83,91],{"__ignoreMap":80},[84,85,88],"span",{"class":86,"line":87},"line",1,[84,89,90],{},"after_install = \"my_app.install.after_install\"\n",[84,92,94],{"class":86,"line":93},2,[84,95,96],{},"after_migrate = \"my_app.install.after_migrate\"\n",[30,98,100],{"id":99},"écrire-le-module-dinstallation","Écrire le module d'installation",[15,102,103,104,107,108,111,112,115,116,119],{},"L'exemple ajoute le champ ",[42,105,106],{},"my_app_field"," après le champ ",[42,109,110],{},"customer_name"," du type de document ",[25,113,114],{},"Client"," (",[42,117,118],{},"Customer",").",[74,121,124],{"className":76,"code":122,"filename":123,"language":79,"meta":80,"style":80},"import frappe\nfrom frappe.custom.doctype.custom_field.custom_field import create_custom_fields\n\n# The existing OLD_FIELDS will always be deleted first.\n# Make sure to include all fields that you create in this list.\n# When removing a field from the CUSTOM_FIELDS list, keep its entry in the OLD_FIELDS.\n# This will ensure that future migrations will correctly remove obsolete fields.\n# The fields will be created in module MODULE.\n# Each property setter is deleted and inserted again at every migrate.\n# Keep it in the PROPERTY_SETTERS list until it becomes obsolete.\n\nMODULE = \"My App\"\n\nOLD_FIELDS = [\n    (\"Customer\", \"my_field\"),\n]\n\nCUSTOM_FIELDS = {\n    \"Customer\": [\n        {\n            \"fieldname\": \"my_app_field\",\n            \"label\": \"My Field\",\n            \"fieldtype\": \"Data\",\n            \"insert_after\": \"customer_name\",\n        },\n    ],\n}\n\nPROPERTY_SETTERS = [\n    # {\n    #   \"name\": \"DOCTYPE-FIELD-PROPERTY\",\n    #   \"doc_type\": \"DOCTYPE\",\n    #   \"doctype_or_field\": \"DocField\",\n    #   \"field_name\": \"FIELD\",\n    #   \"property\": \"PROPERTY\",\n    #   \"value\": \"VALUE\",\n    # },\n]\n\n\ndef after_install():\n    after_migrate()\n\n\ndef after_migrate():\n    for dt, fieldname in OLD_FIELDS:\n        if name := frappe.db.exists(\"Custom Field\", {\"dt\": dt, \"fieldname\": fieldname}):\n            frappe.delete_doc(\"Custom Field\", name)\n\n    # Add `module` property and `insert_after` if missing\n    for fields in CUSTOM_FIELDS.values():\n        prev_field = None\n        for field in fields:\n            field[\"module\"] = MODULE\n            if prev_field and not field.get(\"insert_after\"):\n                field[\"insert_after\"] = prev_field[\"fieldname\"]\n            prev_field = field\n\n    create_custom_fields(CUSTOM_FIELDS)\n\n    # Insert property setters\n    for ps in PROPERTY_SETTERS:\n        assert ps[\"name\"], \"Property Setter name is required\"\n        frappe.delete_doc_if_exists(\"Property Setter\", ps[\"name\"])\n\n        doc = frappe.new_doc(\"Property Setter\")  # type: ignore\n        doc.is_system_generated = True\n        doc.module = MODULE\n        doc.update(ps)\n        doc.insert()\n","my_app\u002Finstall.py",[42,125,126,131,136,143,149,155,161,167,173,179,185,190,196,201,207,213,219,224,230,236,242,248,254,260,266,272,278,284,289,295,301,307,313,319,325,331,337,343,348,353,358,364,370,375,380,386,392,398,404,409,415,421,427,433,439,445,451,457,462,468,473,479,485,491,497,502,508,514,520,526],{"__ignoreMap":80},[84,127,128],{"class":86,"line":87},[84,129,130],{},"import frappe\n",[84,132,133],{"class":86,"line":93},[84,134,135],{},"from frappe.custom.doctype.custom_field.custom_field import create_custom_fields\n",[84,137,139],{"class":86,"line":138},3,[84,140,142],{"emptyLinePlaceholder":141},true,"\n",[84,144,146],{"class":86,"line":145},4,[84,147,148],{},"# The existing OLD_FIELDS will always be deleted first.\n",[84,150,152],{"class":86,"line":151},5,[84,153,154],{},"# Make sure to include all fields that you create in this list.\n",[84,156,158],{"class":86,"line":157},6,[84,159,160],{},"# When removing a field from the CUSTOM_FIELDS list, keep its entry in the OLD_FIELDS.\n",[84,162,164],{"class":86,"line":163},7,[84,165,166],{},"# This will ensure that future migrations will correctly remove obsolete fields.\n",[84,168,170],{"class":86,"line":169},8,[84,171,172],{},"# The fields will be created in module MODULE.\n",[84,174,176],{"class":86,"line":175},9,[84,177,178],{},"# Each property setter is deleted and inserted again at every migrate.\n",[84,180,182],{"class":86,"line":181},10,[84,183,184],{},"# Keep it in the PROPERTY_SETTERS list until it becomes obsolete.\n",[84,186,188],{"class":86,"line":187},11,[84,189,142],{"emptyLinePlaceholder":141},[84,191,193],{"class":86,"line":192},12,[84,194,195],{},"MODULE = \"My App\"\n",[84,197,199],{"class":86,"line":198},13,[84,200,142],{"emptyLinePlaceholder":141},[84,202,204],{"class":86,"line":203},14,[84,205,206],{},"OLD_FIELDS = [\n",[84,208,210],{"class":86,"line":209},15,[84,211,212],{},"    (\"Customer\", \"my_field\"),\n",[84,214,216],{"class":86,"line":215},16,[84,217,218],{},"]\n",[84,220,222],{"class":86,"line":221},17,[84,223,142],{"emptyLinePlaceholder":141},[84,225,227],{"class":86,"line":226},18,[84,228,229],{},"CUSTOM_FIELDS = {\n",[84,231,233],{"class":86,"line":232},19,[84,234,235],{},"    \"Customer\": [\n",[84,237,239],{"class":86,"line":238},20,[84,240,241],{},"        {\n",[84,243,245],{"class":86,"line":244},21,[84,246,247],{},"            \"fieldname\": \"my_app_field\",\n",[84,249,251],{"class":86,"line":250},22,[84,252,253],{},"            \"label\": \"My Field\",\n",[84,255,257],{"class":86,"line":256},23,[84,258,259],{},"            \"fieldtype\": \"Data\",\n",[84,261,263],{"class":86,"line":262},24,[84,264,265],{},"            \"insert_after\": \"customer_name\",\n",[84,267,269],{"class":86,"line":268},25,[84,270,271],{},"        },\n",[84,273,275],{"class":86,"line":274},26,[84,276,277],{},"    ],\n",[84,279,281],{"class":86,"line":280},27,[84,282,283],{},"}\n",[84,285,287],{"class":86,"line":286},28,[84,288,142],{"emptyLinePlaceholder":141},[84,290,292],{"class":86,"line":291},29,[84,293,294],{},"PROPERTY_SETTERS = [\n",[84,296,298],{"class":86,"line":297},30,[84,299,300],{},"    # {\n",[84,302,304],{"class":86,"line":303},31,[84,305,306],{},"    #   \"name\": \"DOCTYPE-FIELD-PROPERTY\",\n",[84,308,310],{"class":86,"line":309},32,[84,311,312],{},"    #   \"doc_type\": \"DOCTYPE\",\n",[84,314,316],{"class":86,"line":315},33,[84,317,318],{},"    #   \"doctype_or_field\": \"DocField\",\n",[84,320,322],{"class":86,"line":321},34,[84,323,324],{},"    #   \"field_name\": \"FIELD\",\n",[84,326,328],{"class":86,"line":327},35,[84,329,330],{},"    #   \"property\": \"PROPERTY\",\n",[84,332,334],{"class":86,"line":333},36,[84,335,336],{},"    #   \"value\": \"VALUE\",\n",[84,338,340],{"class":86,"line":339},37,[84,341,342],{},"    # },\n",[84,344,346],{"class":86,"line":345},38,[84,347,218],{},[84,349,351],{"class":86,"line":350},39,[84,352,142],{"emptyLinePlaceholder":141},[84,354,356],{"class":86,"line":355},40,[84,357,142],{"emptyLinePlaceholder":141},[84,359,361],{"class":86,"line":360},41,[84,362,363],{},"def after_install():\n",[84,365,367],{"class":86,"line":366},42,[84,368,369],{},"    after_migrate()\n",[84,371,373],{"class":86,"line":372},43,[84,374,142],{"emptyLinePlaceholder":141},[84,376,378],{"class":86,"line":377},44,[84,379,142],{"emptyLinePlaceholder":141},[84,381,383],{"class":86,"line":382},45,[84,384,385],{},"def after_migrate():\n",[84,387,389],{"class":86,"line":388},46,[84,390,391],{},"    for dt, fieldname in OLD_FIELDS:\n",[84,393,395],{"class":86,"line":394},47,[84,396,397],{},"        if name := frappe.db.exists(\"Custom Field\", {\"dt\": dt, \"fieldname\": fieldname}):\n",[84,399,401],{"class":86,"line":400},48,[84,402,403],{},"            frappe.delete_doc(\"Custom Field\", name)\n",[84,405,407],{"class":86,"line":406},49,[84,408,142],{"emptyLinePlaceholder":141},[84,410,412],{"class":86,"line":411},50,[84,413,414],{},"    # Add `module` property and `insert_after` if missing\n",[84,416,418],{"class":86,"line":417},51,[84,419,420],{},"    for fields in CUSTOM_FIELDS.values():\n",[84,422,424],{"class":86,"line":423},52,[84,425,426],{},"        prev_field = None\n",[84,428,430],{"class":86,"line":429},53,[84,431,432],{},"        for field in fields:\n",[84,434,436],{"class":86,"line":435},54,[84,437,438],{},"            field[\"module\"] = MODULE\n",[84,440,442],{"class":86,"line":441},55,[84,443,444],{},"            if prev_field and not field.get(\"insert_after\"):\n",[84,446,448],{"class":86,"line":447},56,[84,449,450],{},"                field[\"insert_after\"] = prev_field[\"fieldname\"]\n",[84,452,454],{"class":86,"line":453},57,[84,455,456],{},"            prev_field = field\n",[84,458,460],{"class":86,"line":459},58,[84,461,142],{"emptyLinePlaceholder":141},[84,463,465],{"class":86,"line":464},59,[84,466,467],{},"    create_custom_fields(CUSTOM_FIELDS)\n",[84,469,471],{"class":86,"line":470},60,[84,472,142],{"emptyLinePlaceholder":141},[84,474,476],{"class":86,"line":475},61,[84,477,478],{},"    # Insert property setters\n",[84,480,482],{"class":86,"line":481},62,[84,483,484],{},"    for ps in PROPERTY_SETTERS:\n",[84,486,488],{"class":86,"line":487},63,[84,489,490],{},"        assert ps[\"name\"], \"Property Setter name is required\"\n",[84,492,494],{"class":86,"line":493},64,[84,495,496],{},"        frappe.delete_doc_if_exists(\"Property Setter\", ps[\"name\"])\n",[84,498,500],{"class":86,"line":499},65,[84,501,142],{"emptyLinePlaceholder":141},[84,503,505],{"class":86,"line":504},66,[84,506,507],{},"        doc = frappe.new_doc(\"Property Setter\")  # type: ignore\n",[84,509,511],{"class":86,"line":510},67,[84,512,513],{},"        doc.is_system_generated = True\n",[84,515,517],{"class":86,"line":516},68,[84,518,519],{},"        doc.module = MODULE\n",[84,521,523],{"class":86,"line":522},69,[84,524,525],{},"        doc.update(ps)\n",[84,527,529],{"class":86,"line":528},70,[84,530,531],{},"        doc.insert()\n",[30,533,535],{"id":534},"points-dattention","Points d'attention",[537,538,539,549,563,570],"ul",{},[38,540,541,542,544,545,548],{},"Gardez dans ",[42,543,67],{}," chaque champ retiré de ",[42,546,547],{},"CUSTOM_FIELDS",". Sans cela, le champ reste sur les sites déjà installés.",[38,550,551,552,555,556,559,560,28],{},"Le nom d'un « Property Setter » suit le format ",[42,553,554],{},"\u003Ctype de document>-\u003Cchamp>-\u003Cpropriété>",", par exemple ",[42,557,558],{},"Customer-customer_group-reqd",". Dodock recalcule ce nom à l'insertion : utilisez ce format dans ",[42,561,562],{},"PROPERTY_SETTERS",[38,564,565,566,569],{},"Pour un seul « Property Setter », ",[42,567,568],{},"frappe.make_property_setter()"," fait le même travail en un appel.",[38,571,572,573,576],{},"Préfixez le nom de vos champs (",[42,574,575],{},"my_app_…",") pour éviter un conflit avec un champ standard ou une autre application.",[578,579,580],"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);}",{"title":80,"searchDepth":87,"depth":93,"links":582},[583,584,585,586],{"id":32,"depth":93,"text":33},{"id":71,"depth":93,"text":72},{"id":99,"depth":93,"text":100},{"id":534,"depth":93,"text":535},"Créer des champs personnalisés et des propriétés depuis une application, pour synchroniser plusieurs sites sans l'interface.","md",{"reviewed":141,"last_verified":590,"sources":591},"2026-11-03",[592,593,594],"dodock@develop:frappe\u002Fcustom\u002Fdoctype\u002Fcustom_field\u002Fcustom_field.py","dodock@develop:frappe\u002Fcustom\u002Fdoctype\u002Fproperty_setter\u002Fproperty_setter.py","dodock@develop:frappe\u002Fmigrate.py","\u002Fnext\u002Fdodock\u002Fressources\u002Fcustom-app\u002Fcustom-fields",{"title":5,"description":587},"2.next\u002F3.dodock\u002F14.ressources\u002F4.custom-app\u002F1.custom-fields","lrhFSwDHgrnprrlAa1JeibD5Fq5DDUHEIbrZqubQehI",{},1794701640460]