diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 1c0207ce4b..fbbdde8e03 100644 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -109,7 +109,8 @@ def new_site( "--with-public-files", help="Restores the public files of the site, given path to its tar file" ) @click.option( - "--with-private-files", help="Restores the private files of the site, given path to its tar file" + "--with-private-files", + help="Restores the private files of the site, given path to its tar file", ) @click.option( "--force", @@ -191,7 +192,8 @@ def _restore( fg="red", ) click.secho( - "Use `bench partial-restore` to restore a partial backup to an existing site.", fg="yellow" + "Use `bench partial-restore` to restore a partial backup to an existing site.", + fg="yellow", ) _backup.decryption_rollback() sys.exit(1) @@ -222,7 +224,8 @@ def _restore( fg="red", ) click.secho( - "Use `bench partial-restore` to restore a partial backup to an existing site.", fg="yellow" + "Use `bench partial-restore` to restore a partial backup to an existing site.", + fg="yellow", ) _backup.decryption_rollback() sys.exit(1) @@ -324,7 +327,8 @@ def partial_restore(context, sql_file_path, verbose, encryption_key=None): # Check for full backup file if "Partial Backup" not in header: click.secho( - "Full backup file detected.Use `bench restore` to restore a Frappe Site.", fg="red" + "Full backup file detected.Use `bench restore` to restore a Frappe Site.", + fg="red", ) _backup.decryption_rollback() sys.exit(1) @@ -355,7 +359,8 @@ def partial_restore(context, sql_file_path, verbose, encryption_key=None): # Check for Full backup file. if "Partial Backup" not in header: click.secho( - "Full Backup file detected.Use `bench restore` to restore a Frappe Site.", fg="red" + "Full Backup file detected.Use `bench restore` to restore a Frappe Site.", + fg="red", ) _backup.decryption_rollback() sys.exit(1) @@ -391,7 +396,12 @@ def reinstall( def _reinstall( - site, admin_password=None, db_root_username=None, db_root_password=None, yes=False, verbose=False + site, + admin_password=None, + db_root_username=None, + db_root_password=None, + yes=False, + verbose=False, ): from frappe.installer import _new_site from frappe.utils.synchronization import filelock @@ -719,7 +729,10 @@ def use(site, sites_path="."): @click.option("--backup-path-private-files", default=None, help="Set path for saving private file") @click.option("--backup-path-conf", default=None, help="Set path for saving config file") @click.option( - "--ignore-backup-conf", default=False, is_flag=True, help="Ignore excludes/includes set in config" + "--ignore-backup-conf", + default=False, + is_flag=True, + help="Ignore excludes/includes set in config", ) @click.option("--verbose", default=False, is_flag=True, help="Add verbosity") @click.option("--compress", default=False, is_flag=True, help="Compress private and public files") @@ -774,7 +787,8 @@ def backup( continue if frappe.get_system_settings("encrypt_backup") and frappe.get_site_config().encryption_key: click.secho( - "Backup encryption is turned on. Please note the backup encryption key.", fg="yellow" + "Backup encryption is turned on. Please note the backup encryption key.", + fg="yellow", ) odb.print_summary() @@ -1120,14 +1134,31 @@ def stop_recording(context): @click.option( "--bind-tls", is_flag=True, default=False, help="Returns a reference to the https tunnel." ) +@click.option( + "--use-default-authtoken", + is_flag=True, + default=False, + help="Use the auth token present in ngrok's config.", +) @pass_context -def start_ngrok(context, bind_tls): +def start_ngrok(context, bind_tls, use_default_authtoken): """Start a ngrok tunnel to your local development server.""" from pyngrok import ngrok site = get_site(context) frappe.init(site=site) + ngrok_authtoken = frappe.conf.ngrok_authtoken + if not use_default_authtoken: + if not ngrok_authtoken: + click.echo( + f"\n{click.style('ngrok_authtoken', fg='yellow')} not found in site config.\n" + "Please register for a free ngrok account at: https://dashboard.ngrok.com/signup and place the obtained authtoken in the site config.", + ) + sys.exit(1) + + ngrok.set_auth_token(ngrok_authtoken) + port = frappe.conf.http_port or frappe.conf.webserver_port tunnel = ngrok.connect(addr=str(port), host_header=site, bind_tls=bind_tls) print(f"Public URL: {tunnel.public_url}") diff --git a/frappe/core/api/file.py b/frappe/core/api/file.py index c2354516a8..e3e6a9de08 100644 --- a/frappe/core/api/file.py +++ b/frappe/core/api/file.py @@ -13,7 +13,7 @@ def unzip_file(name: str): @frappe.whitelist() -def get_attached_images(doctype: str, names: list[str]) -> frappe._dict: +def get_attached_images(doctype: str, names: list[str] | str) -> frappe._dict: """get list of image urls attached in form returns {name: ['image.jpg', 'image.png']}""" diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index e3f8ffd503..e1bb23b388 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -195,10 +195,12 @@ class DocType(Document): def set_default_in_list_view(self): """Set default in-list-view for first 4 mandatory fields""" + not_allowed_in_list_view = get_fields_not_allowed_in_list_view(self.meta) + if not [d.fieldname for d in self.fields if d.in_list_view]: cnt = 0 for d in self.fields: - if d.reqd and not d.hidden and not d.fieldtype in table_fields: + if d.reqd and not d.hidden and not d.fieldtype in not_allowed_in_list_view: d.in_list_view = 1 cnt += 1 if cnt == 4: @@ -1446,10 +1448,7 @@ def validate_fields(meta): fields = meta.get("fields") fieldname_list = [d.fieldname for d in fields] - not_allowed_in_list_view = list(copy.copy(no_value_fields)) - not_allowed_in_list_view.append("Attach Image") - if meta.istable: - not_allowed_in_list_view.remove("Button") + not_allowed_in_list_view = get_fields_not_allowed_in_list_view(meta) for d in fields: if not d.permlevel: @@ -1490,6 +1489,14 @@ def validate_fields(meta): check_image_field(meta) +def get_fields_not_allowed_in_list_view(meta) -> list[str]: + not_allowed_in_list_view = list(copy.copy(no_value_fields)) + not_allowed_in_list_view.append("Attach Image") + if meta.istable: + not_allowed_in_list_view.remove("Button") + return not_allowed_in_list_view + + def validate_permissions_for_doctype(doctype, for_remove=False, alert=False): """Validates if permissions are set correctly.""" doctype = frappe.get_doc("DocType", doctype) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 2e74fd3a6a..e8226d4f9d 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -722,6 +722,28 @@ class TestDocType(FrappeTestCase): self.assertEqual(frappe.get_meta(doctype).get_field(field).default, "DELETETHIS") frappe.delete_doc("DocType", doctype) + def test_not_in_list_view_for_not_allowed_mandatory_field(self): + doctype = new_doctype( + fields=[ + { + "fieldname": "cover_image", + "fieldtype": "Attach Image", + "label": "Cover Image", + "reqd": 1, # mandatory + }, + { + "fieldname": "book_name", + "fieldtype": "Data", + "label": "Book Name", + "reqd": 1, # mandatory + }, + ], + ).insert() + + self.assertFalse(doctype.fields[0].in_list_view) + self.assertTrue(doctype.fields[1].in_list_view) + frappe.delete_doc("DocType", doctype.name) + def new_doctype( name: str | None = None, @@ -759,8 +781,7 @@ def new_doctype( } ) - if fields: - for f in fields: - doc.append("fields", f) + if fields and len(fields) > 0: + doc.set("fields", fields) return doc diff --git a/frappe/email/doctype/email_template/email_template.json b/frappe/email/doctype/email_template/email_template.json index c6ec971da4..00f1428475 100644 --- a/frappe/email/doctype/email_template/email_template.json +++ b/frappe/email/doctype/email_template/email_template.json @@ -57,18 +57,16 @@ ], "icon": "fa fa-comment", "links": [], - "modified": "2022-01-04 14:12:50.321633", + "modified": "2023-01-02 03:56:48.437280", "modified_by": "Administrator", "module": "Email", "name": "Email Template", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { - "create": 1, "read": 1, - "role": "All", - "share": 1, - "write": 1 + "role": "All" }, { "create": 1, @@ -85,5 +83,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/frappe/geo/country_info.py b/frappe/geo/country_info.py index 2aefa27170..3267149d4c 100644 --- a/frappe/geo/country_info.py +++ b/frappe/geo/country_info.py @@ -5,6 +5,7 @@ import json # all country info import os +from functools import lru_cache import frappe from frappe.utils.momentjs import get_all_timezones @@ -27,8 +28,13 @@ def get_all(): return all_data -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def get_country_timezone_info(): + return _get_country_timezone_info() + + +@lru_cache(maxsize=2) +def _get_country_timezone_info(): return {"country_info": get_all(), "all_timezones": get_all_timezones()} diff --git a/frappe/model/document.py b/frappe/model/document.py index 773ee4a764..7222cf4ad6 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -178,6 +178,7 @@ class Document(BaseDocument): "*", as_dict=True, order_by="idx asc", + for_update=self.flags.for_update, ) or [] ) diff --git a/frappe/public/scss/website/web_form.scss b/frappe/public/scss/website/web_form.scss index 8ecf753384..e1ca8b8f58 100644 --- a/frappe/public/scss/website/web_form.scss +++ b/frappe/public/scss/website/web_form.scss @@ -18,6 +18,10 @@ .page_content { max-width: 800px; margin: auto; + + .tooltip-content { + display: none; + } h1 { font-size: 2.25rem;