Récupération des custom_field

Bonjour, je commence l’application pour mon entreprise, je souhaitais savoir comment faire pour récupérer uniquement les champs que j’ai rajouté pour mon application.
Par exemple :
J’ai ajouté 2 champs aux devis :

  • SJE_note
  • SJE_date_visite_prealable
    Et j’ai modifié le champs default et options de « title »

Quand j’exporte les fixtures, j’ai tous les champs modifié qui sont ajouté à mon application (y compris ceux de construction). J’ai la création de 3 JSON
image

Ci-dessous le JSON quotation

{
 "custom_fields": [
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "allow_in_quick_entry": 0,
   "allow_on_submit": 0,
   "bold": 0,
   "collapsible": 0,
   "collapsible_depends_on": null,
   "columns": 0,
   "creation": "2025-01-26 23:15:53.272490",
   "default": null,
   "depends_on": null,
   "description": null,
   "docstatus": 0,
   "dt": "Quotation",
   "fetch_from": null,
   "fetch_if_empty": 0,
   "fieldname": "construction_items_section",
   "fieldtype": "Section Break",
   "hidden": 0,
   "hide_border": 0,
   "hide_days": 0,
   "hide_seconds": 0,
   "idx": 25,
   "ignore_user_permissions": 0,
   "ignore_xss_filter": 0,
   "in_global_search": 0,
   "in_list_view": 0,
   "in_preview": 0,
   "in_standard_filter": 0,
   "insert_after": "items",
   "is_system_generated": 1,
   "is_virtual": 0,
   "label": null,
   "length": 0,
   "link_filters": null,
   "mandatory_depends_on": null,
   "modified": "2025-01-26 23:15:53.272490",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-construction_items_section",
   "no_copy": 0,
   "non_negative": 0,
   "options": null,
   "owner": "Administrator",
   "permlevel": 0,
   "placeholder": null,
   "precision": "",
   "print_hide": 1,
   "print_hide_if_no_value": 0,
   "print_width": null,
   "read_only": 0,
   "read_only_depends_on": null,
   "report_hide": 0,
   "reqd": 0,
   "search_index": 0,
   "show_dashboard": 0,
   "sort_options": 0,
   "translatable": 0,
   "unique": 0,
   "width": null
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "allow_in_quick_entry": 0,
   "allow_on_submit": 0,
   "bold": 0,
   "collapsible": 0,
   "collapsible_depends_on": null,
   "columns": 0,
   "creation": "2025-01-26 23:15:53.554930",
   "default": null,
   "depends_on": null,
   "description": null,
   "docstatus": 0,
   "dt": "Quotation",
   "fetch_from": null,
   "fetch_if_empty": 0,
   "fieldname": "item_builder_html",
   "fieldtype": "HTML",
   "hidden": 0,
   "hide_border": 0,
   "hide_days": 0,
   "hide_seconds": 0,
   "idx": 26,
   "ignore_user_permissions": 0,
   "ignore_xss_filter": 0,
   "in_global_search": 0,
   "in_list_view": 0,
   "in_preview": 0,
   "in_standard_filter": 0,
   "insert_after": "construction_items_section",
   "is_system_generated": 1,
   "is_virtual": 0,
   "label": "Item Builder",
   "length": 0,
   "link_filters": null,
   "mandatory_depends_on": null,
   "modified": "2025-01-26 23:15:53.554930",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-item_builder_html",
   "no_copy": 0,
   "non_negative": 0,
   "options": null,
   "owner": "Administrator",
   "permlevel": 0,
   "placeholder": null,
   "precision": "",
   "print_hide": 1,
   "print_hide_if_no_value": 0,
   "print_width": null,
   "read_only": 0,
   "read_only_depends_on": null,
   "report_hide": 0,
   "reqd": 0,
   "search_index": 0,
   "show_dashboard": 0,
   "sort_options": 0,
   "translatable": 0,
   "unique": 0,
   "width": null
  }
 ],
 "custom_perms": [],
 "doctype": "Quotation",
 "links": [],
 "property_setters": [
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-26 22:43:19.182496",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "base_rounded_total",
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-26 22:43:19.182496",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-base_rounded_total-hidden",
   "owner": "Administrator",
   "property": "hidden",
   "property_type": "Check",
   "row_name": null,
   "value": "0"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-26 22:43:19.688692",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "base_rounded_total",
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-26 22:43:19.688692",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-base_rounded_total-print_hide",
   "owner": "Administrator",
   "property": "print_hide",
   "property_type": "Check",
   "row_name": null,
   "value": "1"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-26 22:43:19.758980",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "disable_rounded_total",
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-26 22:43:19.758980",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-disable_rounded_total-default",
   "owner": "Administrator",
   "property": "default",
   "property_type": "Text",
   "row_name": null,
   "value": "0"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-26 22:43:20.616865",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "in_words",
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-26 22:43:20.616865",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-in_words-hidden",
   "owner": "Administrator",
   "property": "hidden",
   "property_type": "Check",
   "row_name": null,
   "value": "0"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-26 22:43:20.641559",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "in_words",
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-26 22:43:20.641559",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-in_words-print_hide",
   "owner": "Administrator",
   "property": "print_hide",
   "property_type": "Check",
   "row_name": null,
   "value": "0"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-30 19:23:47.155754",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocType",
   "field_name": null,
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-30 19:23:47.155754",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-main-field_order",
   "owner": "Administrator",
   "property": "field_order",
   "property_type": "Data",
   "row_name": null,
   "value": "[\"customer_section\", \"title\", \"naming_series\", \"quotation_to\", \"party_name\", \"customer_name\", \"sje_note\", \"sje_date_visite_prealable\", \"column_break_7\", \"transaction_date\", \"valid_till\", \"column_break1\", \"company\", \"order_type\", \"recurrence_period\", \"amended_from\", \"currency_and_price_list\", \"currency\", \"conversion_rate\", \"column_break2\", \"selling_price_list\", \"price_list_currency\", \"plc_conversion_rate\", \"ignore_pricing_rule\", \"items_section\", \"scan_barcode\", \"items\", \"construction_items_section\", \"item_builder_html\", \"sec_break23\", \"total_qty\", \"total_net_weight\", \"column_break_28\", \"base_total\", \"base_net_total\", \"column_break_31\", \"total\", \"net_total\", \"taxes_section\", \"taxes_and_charges\", \"column_break_36\", \"tax_category\", \"column_break_34\", \"shipping_rule\", \"incoterm\", \"named_place\", \"section_break_36\", \"taxes\", \"section_break_39\", \"base_total_taxes_and_charges\", \"column_break_42\", \"total_taxes_and_charges\", \"totals\", \"base_grand_total\", \"base_rounding_adjustment\", \"base_rounded_total\", \"base_in_words\", \"column_break3\", \"grand_total\", \"rounding_adjustment\", \"rounded_total\", \"disable_rounded_total\", \"in_words\", \"section_break_44\", \"apply_discount_on\", \"base_discount_amount\", \"coupon_code\", \"column_break_46\", \"additional_discount_percentage\", \"discount_amount\", \"referral_sales_partner\", \"sec_tax_breakup\", \"other_charges_calculation\", \"bundle_items_section\", \"packed_items\", \"pricing_rule_details\", \"pricing_rules\", \"address_and_contact_tab\", \"billing_address_section\", \"customer_address\", \"address_display\", \"col_break98\", \"contact_person\", \"contact_display\", \"contact_mobile\", \"contact_email\", \"shipping_address_section\", \"shipping_address_name\", \"column_break_81\", \"shipping_address\", \"company_address_section\", \"company_address\", \"column_break_87\", \"company_address_display\", \"terms_tab\", \"payment_schedule_section\", \"payment_terms_template\", \"payment_schedule\", \"terms_section_break\", \"tc_name\", \"terms\", \"more_info_tab\", \"subscription_section\", \"auto_repeat\", \"update_auto_repeat_reference\", \"print_settings\", \"letter_head\", \"group_same_items\", \"column_break_73\", \"select_print_heading\", \"language\", \"lost_reasons_section\", \"lost_reasons\", \"competitors\", \"column_break_117\", \"order_lost_reason\", \"additional_info_section\", \"status\", \"customer_group\", \"territory\", \"column_break_108\", \"campaign\", \"source\", \"column_break4\", \"opportunity\", \"supplier_quotation\", \"enq_det\", \"margin_section\", \"markup_percentage\", \"column_break_htdb\", \"gross_profit_percentage\", \"connections_tab\"]"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-26 22:43:19.712546",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "rounded_total",
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-26 22:43:19.712546",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-rounded_total-hidden",
   "owner": "Administrator",
   "property": "hidden",
   "property_type": "Check",
   "row_name": null,
   "value": "0"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-26 22:43:19.735776",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "rounded_total",
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-26 22:43:19.735776",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-rounded_total-print_hide",
   "owner": "Administrator",
   "property": "print_hide",
   "property_type": "Check",
   "row_name": null,
   "value": "0"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-26 22:43:24.132891",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "scan_barcode",
   "idx": 0,
   "is_system_generated": 1,
   "modified": "2025-01-26 22:43:24.132891",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-scan_barcode-hidden",
   "owner": "Administrator",
   "property": "hidden",
   "property_type": "Check",
   "row_name": null,
   "value": "0"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-30 19:23:47.345264",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "title",
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-30 19:23:47.345264",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-title-default",
   "owner": "Administrator",
   "property": "default",
   "property_type": "Text",
   "row_name": null,
   "value": "{customer_name} - {sje_note}"
  },
  {
   "_assign": null,
   "_comments": null,
   "_liked_by": null,
   "_user_tags": null,
   "creation": "2025-01-30 19:23:47.265030",
   "default_value": null,
   "doc_type": "Quotation",
   "docstatus": 0,
   "doctype_or_field": "DocField",
   "field_name": "title",
   "idx": 0,
   "is_system_generated": 0,
   "modified": "2025-01-30 19:23:47.265030",
   "modified_by": "Administrator",
   "module": null,
   "name": "Quotation-title-options",
   "owner": "Administrator",
   "property": "options",
   "property_type": "Text",
   "row_name": null,
   "value": "{customer_name} - {sje_note}"
  }
 ],
 "sync_on_migrate": 1
}

Merci pour votre aide et conseil

Hello @oryxr,

Est-ce que t’as essayé un export des personnalisations ? (cf Custom app pour integrer toutes les personnalisations - #2 par Antoine_Maas)

C’est préférable à un export des fixtures car les infos se rangent toute seules bien proprement dans un dossier custom que Frappe saura installer automatiquement. De plus je pense que ça exporte aussi les modifs des tables enfants concernés.

A plus,
Antoine.

Oui, c’est par ce biais que j’exporte, mais certains champs personnalisés n’ont pas de module défini.
image

Du coup, ces champs et paramètres s’affectent directement à mon appli.

Pour l’instant, je teste aussi cette solution :

1 Like

Bonjour @oryxr,

Quand tu exportes les champs via l’interface dans ton application, le système exporte tous les champs personnalisés disponibles dans ta base. Il n’y a malheureusement pas de filtre sur les champs exportés, donc le système les exporte tous.

Tu peux soit supprimer les objets en trop directement dans ton application, soit faire comme nous et définir tes champs personnalisé dans un fichier python dédié:

Ensuite tu peux déclencher leur installation/mise à jour dans le hook dédié du fichier hooks.py: bookings/hooks.py · develop · Dokos / Dokos Bookings · GitLab

L’avantage de cette approche est que tes champs seront marqués comme system_generated et ne pourront pas être supprimé ou renommé dans la base de données.

Bonne journée !

Bonjour !

Effectivement, la page de documentation (Champs additionels - Dokos - Documentation) que tu as trouvé est la bonne pratique que l’on utilise chez Dokos pour nos applications complémentaires, comme par exemple Bookings que Charles-Henri mentionne. Les champs de Construction n’ont pas la mention du module, c’est un mini-bug en cours de correction.

Bonne journée

Très bien, merci. C’est ce que j’ai commencé à faire.
J’ai encore deux question:

  • pour les appliquer, c’est bien un migrate qu’il faut ?
  • Pour l’instant le serveur de dev est configuré en production, je n’ai pas réussi a le mettre en dev. Du coup, comment faire pour le basculer en dev ou comment rechargé le site avec les modif?

J’ai bien vu la différence entre les prod et dev que tu as posté corentin (Différences entre stack prod et dev)

Merci pour votre aide

Je suppose que tu es sur ton ordinateur perso, et que tu as un seul bench, installé par erreur en mode production, avec un seul site. Sinon, il va falloir ajuster les commandes.

Désactiver la production est quelque chose que je ne crois avoir jamais fait. Mais voici les étapes qui devraient pouvoir te permettre de le faire :

Lister les processus de production actifs :

corentin:~$ supervisorctl status

dokos-bench-redis:dokos-bench-redis-cache                     RUNNING
dokos-bench-redis:dokos-bench-redis-queue                     RUNNING
dokos-bench-web:dokos-bench-frappe-web                        RUNNING
dokos-bench-web:dokos-bench-node-socketio                     RUNNING
dokos-bench-workers:dokos-bench-frappe-long-worker-0          RUNNING
dokos-bench-workers:dokos-bench-frappe-schedule               RUNNING
dokos-bench-workers:dokos-bench-frappe-short-worker-0         RUNNING

S’il n’y a que des processus de dokos-bench-*, le bench que tu voudrais avoir en développement, tu peux éteindre les processus avec :

supervisorctl stop dokos-bench-web: dokos-bench-redis: dokos-bench-workers:
# OU ALORS si tu n'as qu'un seul bench
supervisorctl stop all

Ton bench est maintenant inerte, tu peux le re-lancer en mode développement avec :

bench start

Et aussi activer le mode développeur sur ton site

bench --site mon-site.local set-config developer_mode 1

Oui c’est bien bench migrate qu’il faut lancer

Je suis sur un serveur distant (une machine virtuelle créé sur proxmox).
Sur le même proxmox, j’ai aussi une machine virtuelle qui gère le site en production.
Je peux comme ça faire des instantanés.

J’ai bien activé le mode developer et ça fonctionne bien, mais quand je fais migrate, les champs ne sont pas ajouté.
Même après avoir fait bench restart.

J’avais essayé en develop, mais quand j’installais les applis, il me posait problème avec redis.

Tout est bon dans hooks.py ? Tu as bien installé l’application sur ton site ?

Ah oui, il faut démarrer le serveur avec bench start d’abord, et ensuite installer les applications.

hooks.py

before_install = "sje_custom.install.before_install"
after_install = "sje_custom.install.after_install"

install.py

import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
import sje_custom.sje_config.custom_fields as sje_config_fields

def after_install():
    after_migrate()

def after_migrate():
    inject_custom_fields(sje_config_fields.MODULE,
                         sje_config_fields.OLD_FIELDS,
                         sje_config_fields.CUSTOM_FIELDS,
                         sje_config_fields.PROPERTY_SETTERS)
    
    
def inject_custom_fields(module, old_fields, custom_fields, property_setters):
    for dt, fieldname in old_fields:
        if name := frappe.db.exists("Custom Field", {"dt": dt, "fieldname": fieldname}):
            frappe.delete_doc("Custom Field", name)

    # Add `module` property and `insert_after` if missing
    for fields in custom_fields.values():
        prev_field = None
        for field in fields:
            field["module"] = module
            if prev_field and not field.get("insert_after"):
                field["insert_after"] = prev_field["fieldname"]
            prev_field = field

    create_custom_fields(custom_fields)

    # Insert property setters
    for ps in property_setters:
        assert ps["name"], "Property Setter name is required"
        frappe.delete_doc_if_exists("Property Setter", ps["name"])

        doc = frappe.new_doc("Property Setter")  # type: ignore
        doc.is_system_generated = True
        doc.module = module
        doc.update(ps)
        doc.insert()

custom_fields.py

MODULE = "SJE config"

OLD_FIELDS = [
    # ("Customer", "my_field"),
]

CUSTOM_FIELDS = {
    "Quotation": [
        {
            "fieldname": "sje_note",
            "label": "Objet du devis",
            "fieldtype": "Data",
            "insert_after": "customer_name",
        },
        {
            "fieldname": "sje_visite_prealable",
            "label": "Date de visite préalable",
            "fieldype": "Datetime",
            "insert_after": "sje_note",
        },
    ],
    "sje_visite_prealable": [
        
    ]
}

PROPERTY_SETTERS = [
    {
      "name": "Quotation-title-default",
      "field_name": "title",
      "doc_type": "Quotation",
      "doctype_or_field": "DocField",
      "property": "options",
      "value": "{customer_name} - {sje_note}",
    },
    {
      "name": "Quotation-title-options",
      "field_name": "title",
      "doc_type": "Quotation",
      "doctype_or_field": "DocField",
      "property": "options",
      "value": "{customer_name} - {sje_note}",
    },
]

Je prévois plusieurs modules. Peut-être qu’il faut mieux avoir plusieurs applis, je ne sais pas trop.
module.txt

SJE config
SJE ndc
SJE RH

Question : est-ce que on peut avoir un hooks.py par modules ?

Ah j’ai trouvé : il te faut rajouter le hook after_migrate, et supprimer before_install.

-before_install = "sje_custom.install.before_install"
 after_install = "sje_custom.install.after_install"
+after_migrate = "sje_custom.install.after_migrate"
Résultat final
after_install = "sje_custom.install.after_install"
after_migrate = "sje_custom.install.after_migrate"

oups, désolé j aurai du voir l erreur. merci.
sino, as tu un avis sur la question plusieurs modules vs plusieurs appli et peux on avoir un hooks.py par module ou je continu comme j ai fait avec un import?

Typiquement je ne m’embête pas et je fais un seul module qui est identique au nom de l’application, éventuellement plusieurs modules mais c’est difficile à justifier quand l’application est simple, et enfin plusieurs applications c’est vraiment lourd à gérer, donc on ne le fait que pour des périmètres fonctionnels très larges ou si ça doit rester optionnel : HRMS, Construction, Bookings.

Non, ça n’est pas possible, donc il faut cumuler les hooks dans un seul fichier (hooks.py). Peut-être qu’on peut faire des imports ceci dit, mais ça n’est pas pratique car les noms seront écrasés, donc de toute façon il faut combiner les hooks.

Tu peux combiner les hooks avec une liste comme suit :

after_install = [
  "my_app.module_1.after_install",
  "my_app.module_2.after_install",
]

Oui ça ne dérange pas, j’aime bien le :

inject_custom_fields(sje_config_fields.MODULE,
                     sje_config_fields.OLD_FIELDS,
                     sje_config_fields.CUSTOM_FIELDS,
                     sje_config_fields.PROPERTY_SETTERS)

:+1:

super, merci. je vais continuer comme ca et vous tiens au courant

1 Like