├── __init__.py ├── license.txt ├── requirements.txt ├── model_generator ├── patches.txt ├── config │ ├── __init__.py │ ├── desktop.py │ ├── docs.py │ └── model_generator.py ├── templates │ ├── __init__.py │ └── pages │ │ └── __init__.py ├── model_generator │ ├── __init__.py │ └── doctype │ │ ├── __init__.py │ │ ├── model_generator │ │ ├── __init__.py │ │ ├── test_model_generator.py │ │ ├── model_generator.py │ │ ├── model_generator.json │ │ └── model_generator.js │ │ ├── language_data_type_map │ │ ├── __init__.py │ │ ├── language_data_type_map.py │ │ └── language_data_type_map.json │ │ └── language_model_configuration │ │ ├── __init__.py │ │ ├── language_model_configuration.js │ │ ├── test_language_model_configuration.py │ │ ├── language_model_configuration.py │ │ └── language_model_configuration.json ├── modules.txt ├── api │ ├── __init__.py │ └── model_generator.py ├── __init__.py ├── hooks.py └── fixtures │ └── language_model_configuration.json ├── demo.gif ├── .gitignore ├── MANIFEST.in ├── setup.py └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | License: MIT -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | frappe -------------------------------------------------------------------------------- /model_generator/patches.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model_generator/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model_generator/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model_generator/model_generator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model_generator/modules.txt: -------------------------------------------------------------------------------- 1 | Model Generator -------------------------------------------------------------------------------- /model_generator/templates/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/model_generator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/language_data_type_map/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/assemmarwan/model_generator/HEAD/demo.gif -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/language_model_configuration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | model_generator/docs/current -------------------------------------------------------------------------------- /model_generator/api/__init__.py: -------------------------------------------------------------------------------- 1 | from model_generator.api.model_generator import generate_model 2 | -------------------------------------------------------------------------------- /model_generator/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | __version__ = '0.7.1' 5 | 6 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/language_model_configuration/language_model_configuration.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Assem Marwan and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('Language Model Configuration', { 5 | // refresh: function(frm) { 6 | 7 | // } 8 | }); 9 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/model_generator/test_model_generator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2019, Assem Marwan and Contributors 3 | # See license.txt 4 | from __future__ import unicode_literals 5 | 6 | # import frappe 7 | import unittest 8 | 9 | class TestModelGenerator(unittest.TestCase): 10 | pass 11 | -------------------------------------------------------------------------------- /model_generator/config/desktop.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from frappe import _ 4 | 5 | def get_data(): 6 | return [ 7 | { 8 | "module_name": "Model Generator", 9 | "color": "grey", 10 | "icon": "octicon octicon-settings", 11 | "type": "module", 12 | "label": _("Model Generator") 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/language_model_configuration/test_language_model_configuration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2019, Assem Marwan and Contributors 3 | # See license.txt 4 | from __future__ import unicode_literals 5 | 6 | # import frappe 7 | import unittest 8 | 9 | class TestLanguageModelConfiguration(unittest.TestCase): 10 | pass 11 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/model_generator/model_generator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2019, Assem Marwan and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | # import frappe 7 | from frappe.model.document import Document 8 | 9 | class ModelGenerator(Document): 10 | pass 11 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/language_data_type_map/language_data_type_map.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2020, Assem Marwan and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | # import frappe 7 | from frappe.model.document import Document 8 | 9 | class LanguageDataTypeMap(Document): 10 | pass 11 | -------------------------------------------------------------------------------- /model_generator/config/docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for docs 3 | """ 4 | 5 | # source_link = "https://github.com/[org_name]/model_generator" 6 | # docs_base_url = "https://[org_name].github.io/model_generator" 7 | # headline = "App that does everything" 8 | # sub_heading = "Yes, you got that right the first time, everything" 9 | 10 | def get_context(context): 11 | context.brand_html = "Model Generator" 12 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/language_model_configuration/language_model_configuration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2019, Assem Marwan and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | # import frappe 7 | from frappe.model.document import Document 8 | 9 | class LanguageModelConfiguration(Document): 10 | pass 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | include *.json 4 | include *.md 5 | include *.py 6 | include *.txt 7 | recursive-include model_generator *.css 8 | recursive-include model_generator *.csv 9 | recursive-include model_generator *.html 10 | recursive-include model_generator *.ico 11 | recursive-include model_generator *.js 12 | recursive-include model_generator *.json 13 | recursive-include model_generator *.md 14 | recursive-include model_generator *.png 15 | recursive-include model_generator *.py 16 | recursive-include model_generator *.svg 17 | recursive-include model_generator *.txt 18 | recursive-exclude model_generator *.pyc -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup, find_packages 3 | 4 | with open('requirements.txt') as f: 5 | install_requires = f.read().strip().split('\n') 6 | 7 | # get version from __version__ variable in model_generator/__init__.py 8 | from model_generator import __version__ as version 9 | 10 | setup( 11 | name='model_generator', 12 | version=version, 13 | description='Generate models to different languages based on Doctype', 14 | author='Assem Marwan', 15 | author_email='assem905@gmail.com', 16 | packages=find_packages(), 17 | zip_safe=False, 18 | include_package_data=True, 19 | install_requires=install_requires 20 | ) 21 | -------------------------------------------------------------------------------- /model_generator/config/model_generator.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from frappe import _ 3 | 4 | 5 | def get_data(): 6 | data = [ 7 | { 8 | "label": _("Model Generator"), 9 | "icon": "fa fa-wrench", 10 | "items": [ 11 | { 12 | "type": "doctype", 13 | "name": "Model Generator", 14 | "label": _("Model Generator"), 15 | "onboard": 1, 16 | "description": _("Generate Models for target language") 17 | }, 18 | { 19 | "type": "doctype", 20 | "name": "Language Model Configuration", 21 | "label": _("Language Model Configuration"), 22 | "onboard": 1, 23 | "description": _("Configure language settings for model generator") 24 | } 25 | ] 26 | } 27 | 28 | ] 29 | return data 30 | -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/language_data_type_map/language_data_type_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "creation": "2020-01-02 12:17:32.135636", 3 | "doctype": "DocType", 4 | "editable_grid": 1, 5 | "engine": "InnoDB", 6 | "field_order": [ 7 | "field_type", 8 | "data_type" 9 | ], 10 | "fields": [ 11 | { 12 | "fieldname": "data_type", 13 | "fieldtype": "Data", 14 | "in_list_view": 1, 15 | "label": "Data Type", 16 | "reqd": 1 17 | }, 18 | { 19 | "fieldname": "field_type", 20 | "fieldtype": "Select", 21 | "in_list_view": 1, 22 | "in_preview": 1, 23 | "label": "Field Type", 24 | "options": "Data\nSelect\nCheck\nLink\nDynamic Link\nInt\nFloat\nCurrency\nAttach Image\nAttach\nCode\nDate\nDatetime\nHTML Editor\nImage\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nSmall Text\nText\nText Editor\nTime", 25 | "reqd": 1 26 | } 27 | ], 28 | "istable": 1, 29 | "modified": "2020-01-02 22:43:54.851116", 30 | "modified_by": "Administrator", 31 | "module": "Model Generator", 32 | "name": "Language Data Type Map", 33 | "owner": "Administrator", 34 | "permissions": [], 35 | "quick_entry": 1, 36 | "sort_field": "modified", 37 | "sort_order": "DESC", 38 | "track_changes": 1 39 | } -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/model_generator/model_generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "creation": "2019-12-27 00:14:49.324624", 3 | "doctype": "DocType", 4 | "editable_grid": 1, 5 | "engine": "InnoDB", 6 | "field_order": [ 7 | "reference_doctype", 8 | "column_break_2", 9 | "language_config", 10 | "include_standard_fields", 11 | "reference_doctype_section", 12 | "fields_multicheck" 13 | ], 14 | "fields": [ 15 | { 16 | "fieldname": "reference_doctype", 17 | "fieldtype": "Link", 18 | "in_list_view": 1, 19 | "label": "DocType", 20 | "options": "DocType", 21 | "reqd": 1 22 | }, 23 | { 24 | "fieldname": "column_break_2", 25 | "fieldtype": "Column Break" 26 | }, 27 | { 28 | "fieldname": "reference_doctype_section", 29 | "fieldtype": "Section Break", 30 | "label": "Reference DocType" 31 | }, 32 | { 33 | "fieldname": "fields_multicheck", 34 | "fieldtype": "HTML" 35 | }, 36 | { 37 | "fieldname": "language_config", 38 | "fieldtype": "Link", 39 | "label": "Language Config", 40 | "options": "Language Model Configuration", 41 | "reqd": 1 42 | }, 43 | { 44 | "default": "0", 45 | "description": "Whether to include all the implicit fields of a document like name, owner, modified, etc...", 46 | "fieldname": "include_standard_fields", 47 | "fieldtype": "Check", 48 | "label": "Include Standard Fields" 49 | } 50 | ], 51 | "hide_toolbar": 1, 52 | "issingle": 1, 53 | "modified": "2020-03-31 18:03:16.576179", 54 | "modified_by": "Administrator", 55 | "module": "Model Generator", 56 | "name": "Model Generator", 57 | "owner": "Administrator", 58 | "permissions": [ 59 | { 60 | "create": 1, 61 | "delete": 1, 62 | "email": 1, 63 | "print": 1, 64 | "read": 1, 65 | "role": "System Manager", 66 | "share": 1, 67 | "write": 1 68 | } 69 | ], 70 | "quick_entry": 1, 71 | "sort_field": "modified", 72 | "sort_order": "DESC" 73 | } -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/language_model_configuration/language_model_configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_rename": 1, 3 | "autoname": "field:title", 4 | "creation": "2019-12-21 00:46:36.398570", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "title", 10 | "default_type", 11 | "templates_section", 12 | "to_camel_case", 13 | "decorator", 14 | "child_doctype_template", 15 | "type_and_variable_template", 16 | "column_break_5", 17 | "signature_start", 18 | "signature_end", 19 | "section_break_6", 20 | "data_type_map" 21 | ], 22 | "fields": [ 23 | { 24 | "fieldname": "title", 25 | "fieldtype": "Data", 26 | "in_list_view": 1, 27 | "label": "Title", 28 | "reqd": 1, 29 | "unique": 1 30 | }, 31 | { 32 | "fieldname": "data_type_map", 33 | "fieldtype": "Table", 34 | "label": "Data Type Map", 35 | "options": "Language Data Type Map" 36 | }, 37 | { 38 | "description": "The default type in case the field type is not defined in the map table.", 39 | "fieldname": "default_type", 40 | "fieldtype": "Data", 41 | "label": "Default Type", 42 | "reqd": 1 43 | }, 44 | { 45 | "default": "0", 46 | "fieldname": "to_camel_case", 47 | "fieldtype": "Check", 48 | "label": "To Camel Case" 49 | }, 50 | { 51 | "depends_on": "to_camel_case", 52 | "fieldname": "decorator", 53 | "fieldtype": "Code", 54 | "label": "Decorator" 55 | }, 56 | { 57 | "fieldname": "column_break_5", 58 | "fieldtype": "Column Break" 59 | }, 60 | { 61 | "fieldname": "section_break_6", 62 | "fieldtype": "Section Break" 63 | }, 64 | { 65 | "description": "The beginning of code. For example in Dart: class User {\n", 66 | "fieldname": "signature_start", 67 | "fieldtype": "Code", 68 | "label": "Signature Start", 69 | "reqd": 1 70 | }, 71 | { 72 | "description": "The end of the code. For example in Dart: }\n", 73 | "fieldname": "signature_end", 74 | "fieldtype": "Code", 75 | "label": "Signature End", 76 | "reqd": 1 77 | }, 78 | { 79 | "description": "Set the template for variable type per language. For example in Dart: {{fieldtype}} {{fieldname}};\n\nIn JS, {{fieldname}}:{{fieldtype}};", 80 | "fieldname": "type_and_variable_template", 81 | "fieldtype": "Code", 82 | "label": "Type And Variable template", 83 | "reqd": 1 84 | }, 85 | { 86 | "fieldname": "templates_section", 87 | "fieldtype": "Section Break", 88 | "label": "Templates" 89 | }, 90 | { 91 | "description": "The template to use if there's a child doctype. WARNING: Use HTML char encoding for > and <.", 92 | "fieldname": "child_doctype_template", 93 | "fieldtype": "Code", 94 | "label": "Child Doctype Template", 95 | "reqd": 1 96 | } 97 | ], 98 | "modified": "2020-03-28 05:31:18.397316", 99 | "modified_by": "Administrator", 100 | "module": "Model Generator", 101 | "name": "Language Model Configuration", 102 | "owner": "Administrator", 103 | "permissions": [ 104 | { 105 | "create": 1, 106 | "delete": 1, 107 | "email": 1, 108 | "export": 1, 109 | "print": 1, 110 | "read": 1, 111 | "report": 1, 112 | "role": "System Manager", 113 | "share": 1, 114 | "write": 1 115 | } 116 | ], 117 | "quick_entry": 1, 118 | "sort_field": "modified", 119 | "sort_order": "DESC", 120 | "track_changes": 1 121 | } -------------------------------------------------------------------------------- /model_generator/hooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from . import __version__ as app_version 4 | 5 | app_name = "model_generator" 6 | app_title = "Model Generator" 7 | app_publisher = "Assem Marwan" 8 | app_description = "Generate models to different languages based on Doctype" 9 | app_icon = "octicon octicon-file-directory" 10 | app_color = "grey" 11 | app_email = "assem905@gmail.com" 12 | app_license = "MIT" 13 | 14 | 15 | 16 | fixtures = [ 17 | { 18 | "dt": "Language Model Configuration" 19 | } 20 | ] 21 | 22 | 23 | # Includes in 24 | # ------------------ 25 | 26 | # include js, css files in header of desk.html 27 | # app_include_css = "/assets/model_generator/css/model_generator.css" 28 | # app_include_js = "/assets/model_generator/js/model_generator.js" 29 | 30 | # include js, css files in header of web template 31 | # web_include_css = "/assets/model_generator/css/model_generator.css" 32 | # web_include_js = "/assets/model_generator/js/model_generator.js" 33 | 34 | # include js in page 35 | # page_js = {"page" : "public/js/file.js"} 36 | 37 | # include js in doctype views 38 | # doctype_js = {"doctype" : "public/js/doctype.js"} 39 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} 40 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} 41 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 42 | 43 | # Home Pages 44 | # ---------- 45 | 46 | # application home page (will override Website Settings) 47 | # home_page = "login" 48 | 49 | # website user home page (by Role) 50 | # role_home_page = { 51 | # "Role": "home_page" 52 | # } 53 | 54 | # Website user home page (by function) 55 | # get_website_user_home_page = "model_generator.utils.get_home_page" 56 | 57 | # Generators 58 | # ---------- 59 | 60 | # automatically create page for each record of this doctype 61 | # website_generators = ["Web Page"] 62 | 63 | # Installation 64 | # ------------ 65 | 66 | # before_install = "model_generator.install.before_install" 67 | # after_install = "model_generator.install.after_install" 68 | 69 | # Desk Notifications 70 | # ------------------ 71 | # See frappe.core.notifications.get_notification_config 72 | 73 | # notification_config = "model_generator.notifications.get_notification_config" 74 | 75 | # Permissions 76 | # ----------- 77 | # Permissions evaluated in scripted ways 78 | 79 | # permission_query_conditions = { 80 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", 81 | # } 82 | # 83 | # has_permission = { 84 | # "Event": "frappe.desk.doctype.event.event.has_permission", 85 | # } 86 | 87 | # Document Events 88 | # --------------- 89 | # Hook on document methods and events 90 | 91 | # doc_events = { 92 | # "*": { 93 | # "on_update": "method", 94 | # "on_cancel": "method", 95 | # "on_trash": "method" 96 | # } 97 | # } 98 | 99 | # Scheduled Tasks 100 | # --------------- 101 | 102 | # scheduler_events = { 103 | # "all": [ 104 | # "model_generator.tasks.all" 105 | # ], 106 | # "daily": [ 107 | # "model_generator.tasks.daily" 108 | # ], 109 | # "hourly": [ 110 | # "model_generator.tasks.hourly" 111 | # ], 112 | # "weekly": [ 113 | # "model_generator.tasks.weekly" 114 | # ] 115 | # "monthly": [ 116 | # "model_generator.tasks.monthly" 117 | # ] 118 | # } 119 | 120 | # Testing 121 | # ------- 122 | 123 | # before_tests = "model_generator.install.before_tests" 124 | 125 | # Overriding Methods 126 | # ------------------------------ 127 | # 128 | # override_whitelisted_methods = { 129 | # "frappe.desk.doctype.event.event.get_events": "model_generator.event.get_events" 130 | # } 131 | # 132 | # each overriding function accepts a `data` argument; 133 | # generated from the base implementation of the doctype dashboard, 134 | # along with any modifications made in other Frappe apps 135 | # override_doctype_dashboards = { 136 | # "Task": "model_generator.task.get_dashboard_data" 137 | # } 138 | 139 | -------------------------------------------------------------------------------- /model_generator/api/model_generator.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import frappe 4 | from frappe.utils import cint 5 | 6 | # Constants used in the Language Model Configuration 7 | FIELD_TYPE = '{{fieldtype}}' 8 | FIELD_NAME = '{{fieldname}}' 9 | CHILD_DOCTYPE = '{{child_doctype}}' 10 | DOCTYPE = '{{doctype}}' 11 | STD_FIELDS = [ 12 | {'fieldname': 'name', 'fieldtype': 'Link'}, 13 | {'fieldname': 'owner', 'fieldtype': 'Link'}, 14 | {'fieldname': 'idx', 'fieldtype': 'Int'}, 15 | {'fieldname': 'creation', 'fieldtype': 'Date'}, 16 | {'fieldname': 'modified', 'fieldtype': 'Date'}, 17 | {'fieldname': 'modified_by', 'fieldtype': 'Data'}, 18 | {'fieldname': '_user_tags', 'fieldtype': 'Data'}, 19 | {'fieldname': '_liked_by', 'fieldtype': 'Data'}, 20 | {'fieldname': '_comments', 'fieldtype': 'Text'}, 21 | {'fieldname': '_assign', 'fieldtype': 'Text'}, 22 | {'fieldname': 'docstatus', 'fieldtype': 'Int'}, 23 | {'fieldname': 'parent', 'fieldtype': 'Data'}, 24 | {'fieldname': 'parenttype', 'fieldtype': 'Data'}, 25 | {'fieldname': 'parentfield', 'fieldtype': 'Data'} 26 | ] 27 | 28 | 29 | @frappe.whitelist(allow_guest=True) 30 | def generate_model(fields, lang_config, include_std_fields=None) -> str: 31 | if frappe.db.exists('Language Model Configuration', lang_config): 32 | language_config = frappe.get_doc('Language Model Configuration', lang_config).as_dict() 33 | else: 34 | frappe.throw('Language Model Configuration: {0} does not exist'.format(lang_config)) 35 | 36 | if isinstance(fields, str): 37 | fields_dict: dict = json.loads(fields) 38 | else: 39 | fields_dict: dict = frappe._dict(fields) 40 | 41 | doctype: str = list(fields_dict.keys())[0] 42 | 43 | if not doctype or doctype is '': 44 | frappe.throw('Doctype is not specified') 45 | 46 | fields: list = [field for field in fields_dict[doctype]] 47 | child_doctypes: list = [child_doctype for child_doctype in fields_dict[doctype] if child_doctype.get('doctype')] 48 | 49 | if include_std_fields: 50 | # Parse to int 51 | if isinstance(include_std_fields, str): 52 | include_std_fields = cint(include_std_fields) 53 | if include_std_fields: 54 | fields = STD_FIELDS + fields 55 | for child_doctype in child_doctypes: 56 | child_doctype['fields'] = STD_FIELDS + child_doctype['fields'] 57 | 58 | model = create_model(doctype, fields, language_config) 59 | for child_doctype in child_doctypes: 60 | model += create_model(child_doctype.get('doctype'), child_doctype.get('fields'), language_config) 61 | 62 | return model 63 | 64 | 65 | def create_model(doctype: str, fields: list, language_config: dict) -> str: 66 | final_string: str = begin_file(doctype, language_config.get('signature_start')) 67 | 68 | fields_parsed: str = '' 69 | for field in fields: 70 | fields_parsed += parse_field_with_type(field, language_config) 71 | 72 | final_string += fields_parsed 73 | final_string += language_config.get('signature_end') 74 | return final_string 75 | 76 | 77 | def begin_file(doctype: str, header: str) -> str: 78 | return header.replace(DOCTYPE, doctype.replace(' ', '')) 79 | 80 | 81 | def parse_field_with_type(field: dict, lang_config: dict) -> str: 82 | _fieldtype = field.get('fieldtype') 83 | fieldtype = get_type_from_lang_config(_fieldtype, lang_config) 84 | fieldname = field.get('fieldname') 85 | child_doctype_template: str = lang_config.get('child_doctype_template') or '' 86 | 87 | if lang_config.get('to_camel_case'): 88 | decorator: str = lang_config.get('decorator') or '' 89 | field_parsed = decorator.replace(FIELD_NAME, fieldname) + '\n' 90 | 91 | field_parsed += apply_variable_and_type_template(fieldname, fieldtype, 92 | lang_config.get('type_and_variable_template'), 93 | True, 94 | field.get('doctype'), child_doctype_template) 95 | else: 96 | field_parsed = apply_variable_and_type_template(fieldname, fieldtype, 97 | lang_config.get('type_and_variable_template'), 98 | False, 99 | field.get('doctype'), child_doctype_template) 100 | 101 | return field_parsed 102 | 103 | 104 | def apply_variable_and_type_template(fieldname: str, fieldtype: str, template: str, is_snake_to_camel=False, 105 | child_doctype=None, child_doc_template=None): 106 | if is_snake_to_camel: 107 | if child_doctype: 108 | print(child_doc_template) 109 | _fieldtype = child_doc_template.replace(CHILD_DOCTYPE, child_doctype.replace(' ', '')) 110 | print(_fieldtype) 111 | return template.replace(FIELD_TYPE, _fieldtype).replace(FIELD_NAME, snake_to_camel(fieldname)) 112 | else: 113 | return template.replace(FIELD_TYPE, fieldtype).replace(FIELD_NAME, snake_to_camel(fieldname)) 114 | else: 115 | if child_doctype: 116 | 117 | _fieldtype = child_doc_template.replace(CHILD_DOCTYPE, child_doctype.replace(' ', '')) 118 | 119 | return template.replace(FIELD_TYPE, _fieldtype).replace(FIELD_NAME, fieldname) 120 | else: 121 | return template.replace(FIELD_TYPE, fieldtype).replace(FIELD_NAME, fieldname) 122 | 123 | 124 | def get_type_from_lang_config(field_type: str, lang_config: dict) -> str: 125 | data_type_map: list = lang_config.get('data_type_map') 126 | existing_data_type = list(filter(lambda data_type: data_type.get('field_type') == field_type, data_type_map)) 127 | 128 | if len(existing_data_type): 129 | _fieldtype = existing_data_type[0].get('data_type') 130 | else: 131 | _fieldtype = lang_config.get('default_type') 132 | 133 | return _fieldtype 134 | 135 | 136 | def snake_to_camel(snake_string: str) -> str: 137 | components = snake_string.split('_') 138 | return components[0] + ''.join(x.title() for x in components[1:]) 139 | -------------------------------------------------------------------------------- /model_generator/fixtures/language_model_configuration.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "child_doctype_template": "List<{{child_doctype}}>", 4 | "data_type_map": [ 5 | { 6 | "data_type": "String", 7 | "field_type": "Data", 8 | "parent": "Dart", 9 | "parentfield": "data_type_map", 10 | "parenttype": "Language Model Configuration" 11 | }, 12 | { 13 | "data_type": "String", 14 | "field_type": "Select", 15 | "parent": "Dart", 16 | "parentfield": "data_type_map", 17 | "parenttype": "Language Model Configuration" 18 | }, 19 | { 20 | "data_type": "int", 21 | "field_type": "Check", 22 | "parent": "Dart", 23 | "parentfield": "data_type_map", 24 | "parenttype": "Language Model Configuration" 25 | }, 26 | { 27 | "data_type": "String", 28 | "field_type": "Link", 29 | "parent": "Dart", 30 | "parentfield": "data_type_map", 31 | "parenttype": "Language Model Configuration" 32 | }, 33 | { 34 | "data_type": "String", 35 | "field_type": "Dynamic Link", 36 | "parent": "Dart", 37 | "parentfield": "data_type_map", 38 | "parenttype": "Language Model Configuration" 39 | }, 40 | { 41 | "data_type": "int", 42 | "field_type": "Int", 43 | "parent": "Dart", 44 | "parentfield": "data_type_map", 45 | "parenttype": "Language Model Configuration" 46 | }, 47 | { 48 | "data_type": "double", 49 | "field_type": "Float", 50 | "parent": "Dart", 51 | "parentfield": "data_type_map", 52 | "parenttype": "Language Model Configuration" 53 | }, 54 | { 55 | "data_type": "num", 56 | "field_type": "Currency", 57 | "parent": "Dart", 58 | "parentfield": "data_type_map", 59 | "parenttype": "Language Model Configuration" 60 | }, 61 | { 62 | "data_type": "String", 63 | "field_type": "Attach Image", 64 | "parent": "Dart", 65 | "parentfield": "data_type_map", 66 | "parenttype": "Language Model Configuration" 67 | }, 68 | { 69 | "data_type": "String", 70 | "field_type": "Attach", 71 | "parent": "Dart", 72 | "parentfield": "data_type_map", 73 | "parenttype": "Language Model Configuration" 74 | }, 75 | { 76 | "data_type": "String", 77 | "field_type": "Code", 78 | "parent": "Dart", 79 | "parentfield": "data_type_map", 80 | "parenttype": "Language Model Configuration" 81 | }, 82 | { 83 | "data_type": "DateTime", 84 | "field_type": "Date", 85 | "parent": "Dart", 86 | "parentfield": "data_type_map", 87 | "parenttype": "Language Model Configuration" 88 | }, 89 | { 90 | "data_type": "String", 91 | "field_type": "Small Text", 92 | "parent": "Dart", 93 | "parentfield": "data_type_map", 94 | "parenttype": "Language Model Configuration" 95 | }, 96 | { 97 | "data_type": "num", 98 | "field_type": "Rating", 99 | "parent": "Dart", 100 | "parentfield": "data_type_map", 101 | "parenttype": "Language Model Configuration" 102 | } 103 | ], 104 | "decorator": "@JsonKey(name:\"{{fieldname}}\")", 105 | "default_type": "String", 106 | "docstatus": 0, 107 | "doctype": "Language Model Configuration", 108 | "modified": "2020-03-28 05:33:06.759739", 109 | "name": "Dart", 110 | "parent": null, 111 | "parentfield": null, 112 | "parenttype": null, 113 | "signature_end": "}", 114 | "signature_start": "class {{doctype}} {", 115 | "title": "Dart", 116 | "to_camel_case": 1, 117 | "type_and_variable_template": "{{fieldtype}} {{fieldname}};" 118 | }, 119 | { 120 | "child_doctype_template": "Array<{{child_doctype}}>", 121 | "data_type_map": [ 122 | { 123 | "data_type": "string", 124 | "field_type": "Data", 125 | "parent": "TS/JS", 126 | "parentfield": "data_type_map", 127 | "parenttype": "Language Model Configuration" 128 | }, 129 | { 130 | "data_type": "string", 131 | "field_type": "Select", 132 | "parent": "TS/JS", 133 | "parentfield": "data_type_map", 134 | "parenttype": "Language Model Configuration" 135 | }, 136 | { 137 | "data_type": "number", 138 | "field_type": "Check", 139 | "parent": "TS/JS", 140 | "parentfield": "data_type_map", 141 | "parenttype": "Language Model Configuration" 142 | }, 143 | { 144 | "data_type": "string", 145 | "field_type": "Link", 146 | "parent": "TS/JS", 147 | "parentfield": "data_type_map", 148 | "parenttype": "Language Model Configuration" 149 | }, 150 | { 151 | "data_type": "number", 152 | "field_type": "Dynamic Link", 153 | "parent": "TS/JS", 154 | "parentfield": "data_type_map", 155 | "parenttype": "Language Model Configuration" 156 | }, 157 | { 158 | "data_type": "number", 159 | "field_type": "Int", 160 | "parent": "TS/JS", 161 | "parentfield": "data_type_map", 162 | "parenttype": "Language Model Configuration" 163 | }, 164 | { 165 | "data_type": "number", 166 | "field_type": "Float", 167 | "parent": "TS/JS", 168 | "parentfield": "data_type_map", 169 | "parenttype": "Language Model Configuration" 170 | }, 171 | { 172 | "data_type": "number", 173 | "field_type": "Currency", 174 | "parent": "TS/JS", 175 | "parentfield": "data_type_map", 176 | "parenttype": "Language Model Configuration" 177 | }, 178 | { 179 | "data_type": "string", 180 | "field_type": "Attach Image", 181 | "parent": "TS/JS", 182 | "parentfield": "data_type_map", 183 | "parenttype": "Language Model Configuration" 184 | }, 185 | { 186 | "data_type": "number", 187 | "field_type": "Attach", 188 | "parent": "TS/JS", 189 | "parentfield": "data_type_map", 190 | "parenttype": "Language Model Configuration" 191 | }, 192 | { 193 | "data_type": "number", 194 | "field_type": "Code", 195 | "parent": "TS/JS", 196 | "parentfield": "data_type_map", 197 | "parenttype": "Language Model Configuration" 198 | }, 199 | { 200 | "data_type": "string", 201 | "field_type": "Date", 202 | "parent": "TS/JS", 203 | "parentfield": "data_type_map", 204 | "parenttype": "Language Model Configuration" 205 | }, 206 | { 207 | "data_type": "string", 208 | "field_type": "Small Text", 209 | "parent": "TS/JS", 210 | "parentfield": "data_type_map", 211 | "parenttype": "Language Model Configuration" 212 | }, 213 | { 214 | "data_type": "number", 215 | "field_type": "Rating", 216 | "parent": "TS/JS", 217 | "parentfield": "data_type_map", 218 | "parenttype": "Language Model Configuration" 219 | } 220 | ], 221 | "decorator": "", 222 | "default_type": "string", 223 | "docstatus": 0, 224 | "doctype": "Language Model Configuration", 225 | "modified": "2020-03-28 05:32:55.805434", 226 | "name": "TS/JS", 227 | "parent": null, 228 | "parentfield": null, 229 | "parenttype": null, 230 | "signature_end": "}", 231 | "signature_start": "export interface {{doctype}} {", 232 | "title": "TS/JS", 233 | "to_camel_case": 0, 234 | "type_and_variable_template": "{{fieldname}}:{{fieldtype}};" 235 | } 236 | ] -------------------------------------------------------------------------------- /model_generator/model_generator/doctype/model_generator/model_generator.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, Assem Marwan and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('Model Generator', { 5 | refresh: frm => { 6 | frm.disable_save(); 7 | frm.page.set_primary_action('Generate', () => { 8 | if (frm.doc.language_config && frm.doc.language_config !== '') 9 | generate_doc(frm); 10 | else 11 | frappe.throw("Please fill the required fields and make sure the fields are selected"); 12 | }); 13 | }, 14 | onload: frm => { 15 | frm.set_query("reference_doctype", () => { 16 | return { 17 | "filters": { 18 | "istable": 0 19 | } 20 | }; 21 | }); 22 | }, 23 | reference_doctype: frm => { 24 | const doctype = frm.doc.reference_doctype; 25 | if (doctype) 26 | frappe.model.with_doctype(doctype, () => set_field_options(frm)); 27 | else 28 | reset_filter_and_field(frm); 29 | 30 | } 31 | }); 32 | 33 | const reset_filter_and_field = (frm) => { 34 | const parent_wrapper = frm.fields_dict.fields_multicheck.$wrapper; 35 | parent_wrapper.empty(); 36 | frm.fields_multicheck = {}; 37 | }; 38 | 39 | const set_field_options = (frm) => { 40 | const parent_wrapper = frm.fields_dict.fields_multicheck.$wrapper; 41 | const doctype = frm.doc.reference_doctype; 42 | const related_doctypes = get_doctypes(doctype); 43 | 44 | parent_wrapper.empty(); 45 | // Add 'Select All' and 'Unselect All' button 46 | make_multiselect_buttons(parent_wrapper); 47 | frm.fields_multicheck = {}; 48 | related_doctypes.forEach(dt => 49 | frm.fields_multicheck[dt.doctype] = add_doctype_field_multicheck_control(dt.doctype, parent_wrapper, dt.fieldname)); 50 | 51 | frm.refresh(); 52 | }; 53 | 54 | const make_multiselect_buttons = parent_wrapper => { 55 | const button_container = $(parent_wrapper) 56 | .append('
') 57 | .find('.flex'); 58 | 59 | ["Select All", "Unselect All"].map(d => { 60 | frappe.ui.form.make_control({ 61 | parent: $(button_container), 62 | df: { 63 | label: __(d), 64 | fieldname: frappe.scrub(d), 65 | fieldtype: "Button", 66 | click: () => { 67 | checkbox_toggle(d !== 'Select All'); 68 | } 69 | }, 70 | render_input: true 71 | }); 72 | }); 73 | 74 | $(button_container).find('.frappe-control').map((index, button) => { 75 | $(button).css({"margin-right": "1em"}); 76 | }); 77 | 78 | function checkbox_toggle(checked) { 79 | $(parent_wrapper).find('[data-fieldtype="MultiCheck"]').map((index, element) => { 80 | $(element).find(`:checkbox`).prop("checked", checked).trigger('click'); 81 | }); 82 | } 83 | 84 | }; 85 | 86 | const get_doctypes = parentdt => [{doctype: parentdt}].concat( 87 | frappe.meta.get_table_fields(parentdt).map((df) => { 88 | return { 89 | doctype: df.options, fieldname: df.fieldname 90 | }; 91 | })); 92 | 93 | const add_doctype_field_multicheck_control = (doctype, parent_wrapper, field = '') => { 94 | const fields = get_fields(doctype); 95 | 96 | const options = fields 97 | .map(df => { 98 | return { 99 | label: `${df.label} (${df.fieldtype})`, 100 | value: `${df.fieldname}:${df.fieldtype}`, 101 | danger: df.reqd, 102 | checked: 1 103 | }; 104 | }); 105 | const multicheck_control = frappe.ui.form.make_control({ 106 | parent: parent_wrapper, 107 | df: { 108 | "label": doctype, 109 | "fieldname": field, 110 | "fieldtype": "MultiCheck", 111 | "options": options, 112 | "columns": 3, 113 | }, 114 | render_input: true 115 | }); 116 | 117 | multicheck_control.refresh_input(); 118 | return multicheck_control; 119 | }; 120 | 121 | const generate_doc = frm => { 122 | let columns = {}; 123 | let doctype; 124 | Object.keys(frm.fields_multicheck).forEach(dt => { 125 | const options = frm.fields_multicheck[dt].get_checked_options(); 126 | let key; 127 | // Target doctype 128 | if (frm.fields_multicheck[dt].df.fieldname === '') { 129 | key = dt; 130 | doctype = key; 131 | columns[doctype] = options.map(option => parse_field(option)); 132 | } 133 | // Table doctype 134 | else { 135 | let temp = columns[doctype]; 136 | temp.push({ 137 | doctype: dt, 138 | fieldname: frm.fields_multicheck[dt].df.fieldname, 139 | fields: options.map(option => parse_field(option)) 140 | }); 141 | columns[doctype] = temp; 142 | } 143 | }); 144 | console.log(columns); 145 | frappe.call('model_generator.api.generate_model', { 146 | fields: columns, 147 | lang_config: frm.doc.language_config, 148 | include_std_fields: frm.doc.include_standard_fields && frm.doc.include_standard_fields !== '' ? 1 : 0 149 | }) 150 | .then(result => frappe.msgprint(result, 'Successful Generation')); 151 | }; 152 | 153 | /** 154 | * Converts the columns selected into a predefined schema. 155 | * This is to standardize the schema if the API will be used to generate models. 156 | * Example: 157 | * 158 | * { 159 | "User": [ 160 | { 161 | "fieldname": "location", 162 | "fieldtype": "Data" 163 | }, 164 | { 165 | "fieldname": "bio", 166 | "fieldtype": "Small Text" 167 | }, 168 | { 169 | "fieldname": "last_ip", 170 | "fieldtype": "Read Only" 171 | }, 172 | { 173 | "fieldname": "last_active", 174 | "fieldtype": "Datetime" 175 | }, 176 | { 177 | "fieldname": "api_key", 178 | "fieldtype": "Data" 179 | }, 180 | { 181 | "fieldname": "api_secret", 182 | "fieldtype": "Password" 183 | }, 184 | { 185 | "doctype": "Has Role", 186 | "fieldname": "roles", 187 | "fields": [ 188 | { 189 | "fieldname": "role", 190 | "fieldtype": "Link" 191 | } 192 | ] 193 | }, 194 | { 195 | "doctype": "User Email", 196 | "fieldname": "user_emails", 197 | "fields": [ 198 | { 199 | "fieldname": "email_account", 200 | "fieldtype": "Link" 201 | }, 202 | { 203 | "fieldname": "email_id", 204 | "fieldtype": "Data" 205 | }, 206 | { 207 | "fieldname": "awaiting_password", 208 | "fieldtype": "Check" 209 | }, 210 | { 211 | "fieldname": "enable_outgoing", 212 | "fieldtype": "Check" 213 | } 214 | ] 215 | }, 216 | { 217 | "doctype": "Block Module", 218 | "fieldname": "block_modules", 219 | "fields": [ 220 | { 221 | "fieldname": "module", 222 | "fieldtype": "Data" 223 | } 224 | ] 225 | }, 226 | { 227 | "doctype": "DefaultValue", 228 | "fieldname": "defaults", 229 | "fields": [ 230 | { 231 | "fieldname": "defkey", 232 | "fieldtype": "Data" 233 | }, 234 | { 235 | "fieldname": "defvalue", 236 | "fieldtype": "Text" 237 | } 238 | ] 239 | }, 240 | { 241 | "doctype": "User Social Login", 242 | "fieldname": "social_logins", 243 | "fields": [ 244 | { 245 | "fieldname": "provider", 246 | "fieldtype": "Data" 247 | }, 248 | { 249 | "fieldname": "username", 250 | "fieldtype": "Data" 251 | }, 252 | { 253 | "fieldname": "userid", 254 | "fieldtype": "Data" 255 | } 256 | ] 257 | } 258 | ] 259 | } 260 | * 261 | */ 262 | const parse_field = (field) => { 263 | let splitField = field.split(':'); 264 | return {fieldname: splitField[0], fieldtype: splitField[1]}; 265 | }; 266 | 267 | 268 | const filter_fields = df => frappe.model.is_value_type(df); 269 | const get_fields = dt => frappe.meta.get_docfields(dt).filter(filter_fields); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Model Generator 2 | 3 | Generate models to different languages based on Doctype. 4 | 5 | ## Motive 6 | 7 | During development, the doctypes are defined usually by the backend team. Front-end development teams will require to write the models for these doctypes in the front-end as models which usually counts much as manual and repetitive task. 8 | 9 | Since it's repetitive and somewhat an annoying task, I saw an opportunity to some extent automate this task with some minor configurations in the doctype and refactoring in the frontend which will hopefully reduce the time wasted achieving this task. 10 | 11 | ### Doctypes included 12 | 13 | - Model Generator 14 | - Language Model Configuration 15 | 16 | 17 | #### Model Generator Demo 18 | 19 | ![Demo](demo.gif) 20 | 21 | #### Language Model Configuration 22 | 23 | A doctype to configure the templates and data types for each language. By default, there are two fixtures defined: 24 | - Dart/Flutter 25 | - TS/JS 26 | 27 | Configurations include: 28 | 29 | - **Data Type Map**. For instance, `Data` fieldtype is a `String` in Dart/Flutter. 30 | - Variable and type template. For instance for *Dart*: 31 | ``` 32 | {{fieldtype}} {{fieldname}}; 33 | ``` 34 | which results in: 35 | ```dart 36 | String fullName; 37 | ``` 38 | - **Signature Start** which indicates the beginning of the model like *class* or *interface*. 39 | - **Signature End** which indicates the end of the model of a *class* or *interface*. 40 | - **Child Doctype Template** which indicates the representation of a child doctype in a model. For instance in *Dart*: 41 | ``` 42 | List<{{child_doctype}}> 43 | ``` 44 | which results in: 45 | ```dart 46 | List roles; 47 | ``` 48 | - **To Camel Case** which converts the field names from the standard naming in Frappé, snake-case, to camel-case which is the convention, *Dart*, for instance. 49 | - **Decorator (Only used if To Camel Case is checked** where this is used for languages (Dart or C#, for instance), to convert the naming between backend and frontend. 50 | 51 | ### API 52 | In addition to utilizing this app through Frappé UI, it can also be utilized using an endpoint (`POST`). Example: 53 | ```json 54 | { 55 | "cmd": "model_generator.api.generate_model", 56 | "lang_config": "Dart", 57 | "include_std_fields": 0, 58 | "fields": { 59 | "User": [ 60 | { 61 | "fieldname": "enabled", 62 | "fieldtype": "Check" 63 | }, 64 | { 65 | "fieldname": "email", 66 | "fieldtype": "Data" 67 | }, 68 | { 69 | "fieldname": "first_name", 70 | "fieldtype": "Data" 71 | }, 72 | { 73 | "fieldname": "middle_name", 74 | "fieldtype": "Data" 75 | }, 76 | { 77 | "fieldname": "last_name", 78 | "fieldtype": "Data" 79 | }, 80 | { 81 | "fieldname": "full_name", 82 | "fieldtype": "Data" 83 | }, 84 | { 85 | "fieldname": "send_welcome_email", 86 | "fieldtype": "Check" 87 | }, 88 | { 89 | "fieldname": "unsubscribed", 90 | "fieldtype": "Check" 91 | }, 92 | { 93 | "fieldname": "username", 94 | "fieldtype": "Data" 95 | }, 96 | { 97 | "fieldname": "language", 98 | "fieldtype": "Link" 99 | }, 100 | { 101 | "fieldname": "time_zone", 102 | "fieldtype": "Select" 103 | }, 104 | { 105 | "fieldname": "user_image", 106 | "fieldtype": "Attach Image" 107 | }, 108 | { 109 | "fieldname": "role_profile_name", 110 | "fieldtype": "Link" 111 | }, 112 | { 113 | "fieldname": "gender", 114 | "fieldtype": "Link" 115 | }, 116 | { 117 | "fieldname": "phone", 118 | "fieldtype": "Data" 119 | }, 120 | { 121 | "fieldname": "mobile_no", 122 | "fieldtype": "Data" 123 | }, 124 | { 125 | "fieldname": "birth_date", 126 | "fieldtype": "Date" 127 | }, 128 | { 129 | "fieldname": "location", 130 | "fieldtype": "Data" 131 | }, 132 | { 133 | "fieldname": "banner_image", 134 | "fieldtype": "Attach Image" 135 | }, 136 | { 137 | "fieldname": "interest", 138 | "fieldtype": "Small Text" 139 | }, 140 | { 141 | "fieldname": "bio", 142 | "fieldtype": "Small Text" 143 | }, 144 | { 145 | "fieldname": "mute_sounds", 146 | "fieldtype": "Check" 147 | }, 148 | { 149 | "fieldname": "new_password", 150 | "fieldtype": "Password" 151 | }, 152 | { 153 | "fieldname": "logout_all_sessions", 154 | "fieldtype": "Check" 155 | }, 156 | { 157 | "fieldname": "reset_password_key", 158 | "fieldtype": "Data" 159 | }, 160 | { 161 | "fieldname": "last_password_reset_date", 162 | "fieldtype": "Date" 163 | }, 164 | { 165 | "fieldname": "redirect_url", 166 | "fieldtype": "Small Text" 167 | }, 168 | { 169 | "fieldname": "document_follow_notify", 170 | "fieldtype": "Check" 171 | }, 172 | { 173 | "fieldname": "document_follow_frequency", 174 | "fieldtype": "Select" 175 | }, 176 | { 177 | "fieldname": "thread_notify", 178 | "fieldtype": "Check" 179 | }, 180 | { 181 | "fieldname": "send_me_a_copy", 182 | "fieldtype": "Check" 183 | }, 184 | { 185 | "fieldname": "allowed_in_mentions", 186 | "fieldtype": "Check" 187 | }, 188 | { 189 | "fieldname": "email_signature", 190 | "fieldtype": "Small Text" 191 | }, 192 | { 193 | "fieldname": "home_settings", 194 | "fieldtype": "Code" 195 | }, 196 | { 197 | "fieldname": "simultaneous_sessions", 198 | "fieldtype": "Int" 199 | }, 200 | { 201 | "fieldname": "user_type", 202 | "fieldtype": "Select" 203 | }, 204 | { 205 | "fieldname": "login_after", 206 | "fieldtype": "Int" 207 | }, 208 | { 209 | "fieldname": "login_before", 210 | "fieldtype": "Int" 211 | }, 212 | { 213 | "fieldname": "restrict_ip", 214 | "fieldtype": "Data" 215 | }, 216 | { 217 | "fieldname": "bypass_restrict_ip_check_if_2fa_enabled", 218 | "fieldtype": "Check" 219 | }, 220 | { 221 | "fieldname": "last_login", 222 | "fieldtype": "Read Only" 223 | }, 224 | { 225 | "fieldname": "last_ip", 226 | "fieldtype": "Read Only" 227 | }, 228 | { 229 | "fieldname": "last_active", 230 | "fieldtype": "Datetime" 231 | }, 232 | { 233 | "fieldname": "last_known_versions", 234 | "fieldtype": "Text" 235 | }, 236 | { 237 | "fieldname": "api_key", 238 | "fieldtype": "Data" 239 | }, 240 | { 241 | "fieldname": "api_secret", 242 | "fieldtype": "Password" 243 | }, 244 | { 245 | "doctype": "Has Role", 246 | "fieldname": "roles", 247 | "fields": [ 248 | { 249 | "fieldname": "role", 250 | "fieldtype": "Link" 251 | } 252 | ] 253 | }, 254 | { 255 | "doctype": "User Email", 256 | "fieldname": "user_emails", 257 | "fields": [ 258 | { 259 | "fieldname": "email_account", 260 | "fieldtype": "Link" 261 | }, 262 | { 263 | "fieldname": "email_id", 264 | "fieldtype": "Data" 265 | }, 266 | { 267 | "fieldname": "awaiting_password", 268 | "fieldtype": "Check" 269 | }, 270 | { 271 | "fieldname": "enable_outgoing", 272 | "fieldtype": "Check" 273 | } 274 | ] 275 | }, 276 | { 277 | "doctype": "Block Module", 278 | "fieldname": "block_modules", 279 | "fields": [ 280 | { 281 | "fieldname": "module", 282 | "fieldtype": "Data" 283 | } 284 | ] 285 | }, 286 | { 287 | "doctype": "DefaultValue", 288 | "fieldname": "defaults", 289 | "fields": [ 290 | { 291 | "fieldname": "defkey", 292 | "fieldtype": "Data" 293 | }, 294 | { 295 | "fieldname": "defvalue", 296 | "fieldtype": "Text" 297 | } 298 | ] 299 | }, 300 | { 301 | "doctype": "User Social Login", 302 | "fieldname": "social_logins", 303 | "fields": [ 304 | { 305 | "fieldname": "provider", 306 | "fieldtype": "Data" 307 | }, 308 | { 309 | "fieldname": "username", 310 | "fieldtype": "Data" 311 | }, 312 | { 313 | "fieldname": "userid", 314 | "fieldtype": "Data" 315 | } 316 | ] 317 | } 318 | ] 319 | } 320 | } 321 | ``` 322 | 323 | 324 | ## Contribution 325 | Contributions in all shapes, forms and sizes are welcome :) 326 | 327 | ## License 328 | 329 | MIT 330 | --------------------------------------------------------------------------------