diff --git a/frappe/auth.py b/frappe/auth.py index a9b1516b01..5ce4462161 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -487,7 +487,10 @@ def validate_ip_address(user): if bypass_restrict_ip_check: return - frappe.throw(_("Access not allowed from this IP Address"), frappe.AuthenticationError) + frappe.throw( + _("Access not allowed from this IP Address") + f": {frappe.local.request_ip}", + frappe.AuthenticationError, + ) def get_login_attempt_tracker(key: str, raise_locked_exception: bool = True): diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index ed830fd069..5ebfe48347 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -3,7 +3,7 @@ import hashlib import json -from datetime import datetime +from datetime import datetime, timedelta import click from croniter import CroniterBadCronError, croniter @@ -105,8 +105,7 @@ class ScheduledJobType(Document): # Maintenance jobs run at random time, the time is specific to the site though. # This is done to avoid scheduling all maintenance task on all sites at the same time in # multitenant deployments. - hourly_site_offset = int(hashlib.sha1(frappe.local.site.encode()).hexdigest(), 16) % 60 - daily_site_offset = (hourly_site_offset + 30) % 60 + maintenance_offset = int(hashlib.sha1(frappe.local.site.encode()).hexdigest(), 16) % 60 CRON_MAP = { "Yearly": "0 0 1 1 *", @@ -117,10 +116,10 @@ class ScheduledJobType(Document): "Weekly Long": "0 0 * * 0", "Daily": "0 0 * * *", "Daily Long": "0 0 * * *", - "Daily Maintenance": f"{daily_site_offset} 0 * * *", + "Daily Maintenance": "0 0 * * *", "Hourly": "0 * * * *", "Hourly Long": "0 * * * *", - "Hourly Maintenance": f"{hourly_site_offset} * * * *", + "Hourly Maintenance": "0 * * * *", "All": f"*/{(frappe.get_conf().scheduler_interval or 240) // 60} * * * *", } @@ -133,6 +132,9 @@ class ScheduledJobType(Document): # A dynamic fallback like current time might miss the scheduler interval and job will never start. last_execution = get_datetime(self.last_execution or self.creation) + next_execution = croniter(self.cron_format, last_execution).get_next(datetime) + if self.frequency in ("Hourly Maintenance", "Daily Maintenance"): + next_execution += timedelta(minutes=maintenance_offset) return croniter(self.cron_format, last_execution).get_next(datetime) def execute(self): diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index 8d27385bbb..dc6606a5b3 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -117,7 +117,7 @@ class TestScheduledJobType(IntegrationTestCase): sjt = frappe.new_doc( "Scheduled Job Type", frequency="Hourly Maintenance", - last_execution=get_datetime("2019-01-01 00:00:00"), + last_execution=get_datetime("2019-01-01 23:59:00"), ) # Should be within one hour self.assertGreaterEqual(sjt.next_execution, sjt.last_execution) diff --git a/frappe/database/database.py b/frappe/database/database.py index 9216eee61f..8662693346 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -1447,7 +1447,7 @@ class Database: values: Iterable[Sequence[Any]], ignore_duplicates=False, *, - chunk_size=10_000, + chunk_size=1000, ): """ Insert multiple records at a time diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js index 4b83fecfbd..bba9ea64a6 100644 --- a/frappe/desk/doctype/system_console/system_console.js +++ b/frappe/desk/doctype/system_console/system_console.js @@ -36,6 +36,10 @@ frappe.ui.form.on("System Console", { frm.set_value("type", "Python"); // defaults don't work on singles on old sites. } frm.trigger("load_completions"); + + frm.page.add_inner_message( + `${__("Tip: Try the new dropdown console using")} ⇧+T` + ); }, type: function (frm) { diff --git a/frappe/desk/doctype/system_console/system_console.json b/frappe/desk/doctype/system_console/system_console.json index a59ab01b0b..0e00e7bb6e 100644 --- a/frappe/desk/doctype/system_console/system_console.json +++ b/frappe/desk/doctype/system_console/system_console.json @@ -86,7 +86,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-03-23 16:03:39.355553", + "modified": "2025-05-28 09:26:57.614890", "modified_by": "Administrator", "module": "Desk", "name": "System Console", @@ -96,14 +96,15 @@ "email": 1, "print": 1, "read": 1, - "role": "System Manager", + "role": "Administrator", "share": 1, "write": 1 } ], "quick_entry": 1, + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py index 2334f3809e..714a2c8240 100644 --- a/frappe/desk/doctype/system_console/system_console.py +++ b/frappe/desk/doctype/system_console/system_console.py @@ -25,11 +25,13 @@ class SystemConsole(Document): # end: auto-generated types def run(self): - frappe.only_for("System Manager") + frappe.only_for(["System Manager", "Administrator"]) try: frappe.local.debug_log = [] if self.type == "Python": - safe_exec(self.console, script_filename="System Console") + safe_exec( + self.console, script_filename="System Console", restrict_commit_rollback=not self.commit + ) self.output = "\n".join(frappe.debug_log) elif self.type == "SQL": self.output = frappe.as_json(read_sql(self.console, as_dict=1)) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 7810bba752..5a371bc34d 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -14,7 +14,7 @@ from frappe.desk.reportview import clean_params, parse_json from frappe.model.utils import render_include from frappe.modules import get_module_path, scrub from frappe.monitor import add_data_to_monitor -from frappe.permissions import get_role_permissions, has_permission +from frappe.permissions import get_role_permissions, get_valid_perms, has_permission from frappe.utils import cint, cstr, flt, format_duration, get_html_format, sbool @@ -710,10 +710,8 @@ def has_match( # so that even if one of the sets allows a match, it is true if match: - if not frappe.has_permission( - doctype=ref_doctype, ptype="read", throw=False, ignore_share_permissions=True - ): - match = False + valid_perms = get_valid_perms(doctype=ref_doctype) + match = any(perm.get("read") and not perm.get("if_owner") for perm in valid_perms) matched_for_doctype = matched_for_doctype or match diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index 836fd927a6..1b42df7b2e 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -731,7 +731,8 @@ class QueueBuilder: if self.read_receipt: mail.msg_root["Disposition-Notification-To"] = self.sender if self.in_reply_to: - mail.set_in_reply_to(self.in_reply_to) + if message_id := frappe.db.get_value("Communication", self.in_reply_to, "message_id"): + mail.set_in_reply_to(get_string_between("<", message_id, ">")) return mail def process(self, send_now=False) -> EmailQueue | None: diff --git a/frappe/hooks.py b/frappe/hooks.py index 4c1d2397f2..09539b6e47 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -209,6 +209,7 @@ scheduler_events = { "frappe.utils.global_search.sync_global_search", "frappe.deferred_insert.save_to_db", "frappe.automation.doctype.reminder.reminder.send_reminders", + "frappe.model.utils.link_count.update_link_count", ], # 10 minutes "0/10 * * * *": [ @@ -231,7 +232,6 @@ scheduler_events = { # Use these for when you don't care about when the job runs but just need some guarantee for # frequency. "hourly_maintenance": [ - "frappe.model.utils.link_count.update_link_count", "frappe.model.utils.user_settings.sync_user_settings", "frappe.desk.page.backups.backups.delete_downloadable_backups", "frappe.desk.form.document_follow.send_hourly_updates", diff --git a/frappe/locale/fa.po b/frappe/locale/fa.po index aabdfc5e35..91f8593305 100644 --- a/frappe/locale/fa.po +++ b/frappe/locale/fa.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: frappe\n" "Report-Msgid-Bugs-To: developers@frappe.io\n" "POT-Creation-Date: 2025-05-18 09:33+0000\n" -"PO-Revision-Date: 2025-05-19 07:02\n" +"PO-Revision-Date: 2025-05-26 10:24\n" "Last-Translator: developers@frappe.io\n" "Language-Team: Persian\n" "MIME-Version: 1.0\n" @@ -115,7 +115,7 @@ msgstr "+ افزودن / حذف فیلدها" #. State' #: frappe/workflow/doctype/workflow_document_state/workflow_document_state.json msgid "0 - Draft; 1 - Submitted; 2 - Cancelled" -msgstr "0 - پیش‌نویس؛ 1 - ارسال شده 2 - لغو شد" +msgstr "0 - پیش‌نویس؛ 1 - ارسال شده؛ 2 - لغو شده" #. Description of the 'Priority' (Int) field in DocType 'Web Page' #: frappe/website/doctype/web_page/web_page.json @@ -584,7 +584,7 @@ msgstr "یک {0} {1} تکرارشونده از طریق تکرار خودکار #. Description of the 'Symbol' (Data) field in DocType 'Currency' #: frappe/geo/doctype/currency/currency.json msgid "A symbol for this currency. For e.g. $" -msgstr "ن برای این ارز. به عنوان مثال $" +msgstr "نمادی برای این ارز. مثلاً $" #: frappe/printing/doctype/print_format_field_template/print_format_field_template.py:49 msgid "A template already exists for field {0} of {1}" @@ -1153,7 +1153,7 @@ msgstr "افزودن یک نقش جدید" #: frappe/public/js/frappe/form/form_tour.js:211 msgid "Add a Row" -msgstr "" +msgstr "افزودن یک ردیف" #: frappe/templates/includes/comments/comments.html:30 #: frappe/templates/includes/comments/comments.html:47 @@ -2138,7 +2138,7 @@ msgstr "افزودن به می‌تواند یکی از {0} باشد" #. Description of the 'Append To' (Link) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "Append as communication against this DocType (must have fields: \"Sender\" and \"Subject\"). These fields can be defined in the email settings section of the appended doctype." -msgstr "به عنوان ارتباط در مقابل این DocType اضافه شود (باید دارای فیلدهای: \"فرستنده\" و \"موضوع\"). این فیلدها را می‌توان در قسمت تنظیمات ایمیل از doctype ضمیمه شده تعریف کرد." +msgstr "به عنوان ارتباط در مقابل این DocType اضافه شود (باید دارای فیلدهای: \"فرستنده\" و \"موضوع\"). این فیلدها را می‌توان در قسمت تنظیمات ایمیل از doctype پیوست شده تعریف کرد." #: frappe/core/doctype/user_permission/user_permission_list.js:105 msgid "Applicable Document Types" @@ -2524,7 +2524,7 @@ msgstr "پیوست" #: frappe/public/js/frappe/views/communication.js:152 msgid "Attach Document Print" -msgstr "ضمیمه چاپ سند" +msgstr "پیوست چاپ سند" #. Option for the 'Type' (Select) field in DocType 'DocField' #. Option for the 'Field Type' (Select) field in DocType 'Custom Field' @@ -2560,7 +2560,7 @@ msgstr "فایل ها / آدرس ها را پیوست کنید و در جدول #. Label of the attached_file (Code) field in DocType 'Notification Log' #: frappe/desk/doctype/notification_log/notification_log.json msgid "Attached File" -msgstr "فایل ضمیمه شده" +msgstr "فایل پیوست شده" #. Label of the attached_to_doctype (Link) field in DocType 'File' #: frappe/core/doctype/file/file.json @@ -3509,7 +3509,7 @@ msgstr "پرش کرد" #. Label of the brand (Section Break) field in DocType 'Website Settings' #: frappe/website/doctype/website_settings/website_settings.json msgid "Brand" -msgstr "نام تجاری" +msgstr "برند" #. Label of the brand_html (Code) field in DocType 'Website Settings' #: frappe/website/doctype/website_settings/website_settings.json @@ -3519,7 +3519,7 @@ msgstr "HTML برند" #. Label of the banner_image (Attach Image) field in DocType 'Website Settings' #: frappe/website/doctype/website_settings/website_settings.json msgid "Brand Image" -msgstr "تصویر نام تجاری" +msgstr "تصویر برند" #. Label of the brand_logo (Attach Image) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json @@ -3530,7 +3530,7 @@ msgstr "لوگوی برند" #: frappe/website/doctype/website_settings/website_settings.json msgid "Brand is what appears on the top-left of the toolbar. If it is an image, make sure it\n" "has a transparent background and use the <img /> tag. Keep size as 200px x 30px" -msgstr "نام تجاری همان چیزی است که در سمت چپ بالای نوار ابزار ظاهر می‌شود. اگر تصویر است، مطمئن شوید که\n" +msgstr "برند همان چیزی است که در سمت چپ بالای نوار ابزار ظاهر می‌شود. اگر تصویر است، مطمئن شوید که\n" "دارای پس‌زمینه شفاف است و از تگ <img /> استفاده کنید. اندازه را 200×30 پیکسل نگه دارید" #. Label of the breadcrumbs (Code) field in DocType 'Web Form' @@ -13912,7 +13912,7 @@ msgstr "LDAP فعال نیست." #. Label of the ldap_search_path_group (Data) field in DocType 'LDAP Settings' #: frappe/integrations/doctype/ldap_settings/ldap_settings.json msgid "LDAP search path for Groups" -msgstr "مسیر جستجوی LDAP برای گروه ها" +msgstr "مسیر جستجوی LDAP برای گروه‌ها" #. Label of the ldap_search_path_user (Data) field in DocType 'LDAP Settings' #: frappe/integrations/doctype/ldap_settings/ldap_settings.json @@ -16231,7 +16231,7 @@ msgstr "نظر جدید در مورد {0}: {1}" #: frappe/public/js/frappe/form/templates/contact_list.html:3 msgid "New Contact" -msgstr "تماس جدید" +msgstr "مخاطب جدید" #: frappe/public/js/frappe/widgets/widget_dialog.js:70 msgid "New Custom Block" @@ -17384,7 +17384,7 @@ msgstr "تعداد بک آپ های DB نمی‌تواند کمتر از 1 با #. Label of the number_of_groups (Int) field in DocType 'Dashboard Chart' #: frappe/desk/doctype/dashboard_chart/dashboard_chart.json msgid "Number of Groups" -msgstr "تعداد گروه ها" +msgstr "تعداد گروه‌ها" #. Label of the number_of_queries (Int) field in DocType 'Recorder' #: frappe/core/doctype/recorder/recorder.json @@ -20425,11 +20425,11 @@ msgstr "دلیل" #: frappe/public/js/frappe/views/reports/query_report.js:889 msgid "Rebuild" -msgstr "بازسازی کنید" +msgstr "بازسازی" #: frappe/public/js/frappe/views/treeview.js:509 msgid "Rebuild Tree" -msgstr "درخت را بازسازی کنید" +msgstr "بازسازی درخت" #: frappe/utils/nestedset.py:177 msgid "Rebuilding of tree is not supported for {}" @@ -21021,7 +21021,7 @@ msgstr "" #: frappe/core/doctype/communication/communication.js:43 #: frappe/desk/doctype/todo/todo.js:36 msgid "Reopen" -msgstr "دوباره باز کنید" +msgstr "باز کردن دوباره" #: frappe/public/js/frappe/form/toolbar.js:544 msgid "Repeat" @@ -21402,7 +21402,7 @@ msgstr "نیاز به گواهی مورد اعتماد" #. 'LDAP Settings' #: frappe/integrations/doctype/ldap_settings/ldap_settings.json msgid "Requires any valid fdn path. i.e. ou=groups,dc=example,dc=com" -msgstr "به هر مسیر fdn معتبر نیاز دارد. یعنی ou=گروه ها،dc=example,dc=com" +msgstr "به هر مسیر fdn معتبر نیاز دارد. یعنی ou=گروه‌ها،dc=example,dc=com" #. Description of the 'LDAP search path for Users' (Data) field in DocType #. 'LDAP Settings' @@ -27774,7 +27774,7 @@ msgstr "استفاده از POST" #. Label of the use_report_chart (Check) field in DocType 'Dashboard Chart' #: frappe/desk/doctype/dashboard_chart/dashboard_chart.json msgid "Use Report Chart" -msgstr "از نمودار گزارش استفاده کنید" +msgstr "استفاده از نمودار گزارش" #. Label of the use_ssl (Check) field in DocType 'Email Account' #. Label of the use_ssl_for_outgoing (Check) field in DocType 'Email Account' diff --git a/frappe/locale/sr_CS.po b/frappe/locale/sr_CS.po index 75fed5e46c..248ac74bde 100644 --- a/frappe/locale/sr_CS.po +++ b/frappe/locale/sr_CS.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: frappe\n" "Report-Msgid-Bugs-To: developers@frappe.io\n" "POT-Creation-Date: 2025-05-18 09:33+0000\n" -"PO-Revision-Date: 2025-05-19 07:02\n" +"PO-Revision-Date: 2025-05-25 10:26\n" "Last-Translator: developers@frappe.io\n" "Language-Team: Serbian (Latin)\n" "MIME-Version: 1.0\n" @@ -46,7 +46,7 @@ msgstr "\"Članovi tima\" ili \"Menadžment\"" #: frappe/public/js/frappe/form/form.js:1090 msgid "\"amended_from\" field must be present to do an amendment." -msgstr "Polje \"amended_from\" mora postojati da bi se izvršila promena." +msgstr "Polje \"Izmenjeno iz\" mora postojati da bi se izvršila promena." #: frappe/utils/csvutils.py:246 msgid "\"{0}\" is not a valid Google Sheets URL" @@ -1736,7 +1736,7 @@ msgstr "Svi zapisi" #: frappe/public/js/frappe/form/form.js:2222 msgid "All Submissions" -msgstr "Svi podneti podaci" +msgstr "Sve podneseno" #: frappe/custom/doctype/customize_form/customize_form.js:452 msgid "All customizations will be removed. Please confirm." @@ -1823,7 +1823,7 @@ msgstr "Dozvoli gosta" #. Label of the allow_guest_to_view (Check) field in DocType 'DocType' #: frappe/core/doctype/doctype/doctype.json msgid "Allow Guest to View" -msgstr "Dozvola gostu da pregleda" +msgstr "Dozvoli gostu da pregleda" #. Label of the allow_guest_to_comment (Check) field in DocType 'Blog Settings' #: frappe/website/doctype/blog_settings/blog_settings.json @@ -2143,7 +2143,7 @@ msgstr "Izmenjeni dokumenti" #: frappe/core/doctype/transaction_log/transaction_log.json #: frappe/website/doctype/personal_data_download_request/personal_data_download_request.json msgid "Amended From" -msgstr "Izmenjeno od" +msgstr "Izmenjeno iz" #: frappe/public/js/frappe/form/save.js:12 msgctxt "Freeze message while amending a document" @@ -3614,7 +3614,7 @@ msgstr "Podešavanje bloga" #. Label of the blog_title (Data) field in DocType 'Blog Settings' #: frappe/website/doctype/blog_settings/blog_settings.json msgid "Blog Title" -msgstr "Naziv bloga" +msgstr "Naslov bloga" #. Name of a role #. Label of the blogger (Link) field in DocType 'Blog Post' @@ -4043,7 +4043,7 @@ msgstr "Callback poruka" #. Label of the callback_title (Data) field in DocType 'Onboarding Step' #: frappe/desk/doctype/onboarding_step/onboarding_step.json msgid "Callback Title" -msgstr "Callback naziv" +msgstr "Callback naslov" #: frappe/public/js/frappe/file_uploader/FileUploader.vue:150 #: frappe/public/js/frappe/ui/capture.js:334 @@ -4743,7 +4743,7 @@ msgstr "Očisti filtere" #. Label of the days (Int) field in DocType 'Logs To Clear' #: frappe/core/doctype/logs_to_clear/logs_to_clear.json msgid "Clear Logs After (days)" -msgstr "Očisti dnevnike nakon (dana)" +msgstr "Očisti evidencije nakon (dana)" #: frappe/core/doctype/user_permission/user_permission_list.js:144 msgid "Clear User Permissions" @@ -5610,7 +5610,7 @@ msgstr "Sadržaj (Markdown)" #. Label of the content_hash (Data) field in DocType 'File' #: frappe/core/doctype/file/file.json msgid "Content Hash" -msgstr "Heš sadržaja" +msgstr "Heš sadržaj" #. Label of the content_type (Select) field in DocType 'Newsletter' #. Label of the content_type (Select) field in DocType 'Blog Post' @@ -6220,7 +6220,7 @@ msgstr "Prilagođene opcije" #. Label of the custom_overrides (Code) field in DocType 'Website Theme' #: frappe/website/doctype/website_theme/website_theme.json msgid "Custom Overrides" -msgstr "Prilagodi izmene" +msgstr "Prilagođene izmene" #. Option for the 'Report Type' (Select) field in DocType 'Report' #: frappe/core/doctype/report/report.json @@ -6413,7 +6413,7 @@ msgstr "Celodnevno" #. Option for the 'Frequency' (Select) field in DocType 'Scheduled Job Type' #: frappe/core/doctype/scheduled_job_type/scheduled_job_type.json msgid "Daily Maintenance" -msgstr "" +msgstr "Dnevno održavanje" #. Option for the 'Style' (Select) field in DocType 'Workflow State' #: frappe/workflow/doctype/workflow_state/workflow_state.json @@ -7141,7 +7141,7 @@ msgstr "Zavisnosti i licence" #: frappe/custom/doctype/custom_field/custom_field.json #: frappe/custom/doctype/customize_form_field/customize_form_field.json msgid "Depends On" -msgstr "Zavisni od" +msgstr "Zavisi od" #: frappe/public/js/frappe/ui/filters/filter.js:32 msgid "Descendants Of" @@ -7385,7 +7385,7 @@ msgstr "Onemogući SMTP server autentifikaciju" #. Settings' #: frappe/desk/doctype/list_view_settings/list_view_settings.json msgid "Disable Sidebar Stats" -msgstr "Onemogući bočnu traku" +msgstr "Onemogući statistiku u bočnoj traci" #: frappe/website/doctype/website_settings/website_settings.js:146 msgid "Disable Signup for your site" @@ -8755,7 +8755,7 @@ msgstr "Član imejl grupe" #. Label of the email_header (Data) field in DocType 'Notification Log' #: frappe/desk/doctype/notification_log/notification_log.json msgid "Email Header" -msgstr "" +msgstr "Zaglavlje imejla" #. Label of the email_id (Data) field in DocType 'Contact Email' #. Label of the email_id (Data) field in DocType 'User Email' @@ -8845,7 +8845,7 @@ msgstr "Imejl podešavanja" #. Label of the email_signature (Text Editor) field in DocType 'User' #: frappe/core/doctype/user/user.json msgid "Email Signature" -msgstr "Imejl potpi" +msgstr "Imejl potpis" #. Label of the email_status (Select) field in DocType 'Communication' #: frappe/core/doctype/communication/communication.json @@ -9106,7 +9106,7 @@ msgstr "Omogući kreiranje standardnog veb-šablona u razvojnom režimu" #. 'Blog Post' #: frappe/website/doctype/blog_post/blog_post.json msgid "Enable email notification for any comment or likes received on your Blog Post." -msgstr "Omogući obaveštenje putem imejla za svaki komentar illi lajk na Vašoj objavi na blogu." +msgstr "Omogući obaveštenje putem imejla za svaki komentar ili lajk na Vašoj objavi na blogu." #. Description of the 'Modal Trigger' (Check) field in DocType 'Form Tour Step' #: frappe/desk/doctype/form_tour_step/form_tour_step.json @@ -9257,7 +9257,7 @@ msgstr "Energetski poen" #. Label of the enqueued_by (Data) field in DocType 'Submission Queue' #: frappe/core/doctype/submission_queue/submission_queue.json msgid "Enqueued By" -msgstr "Stavljeno u redu od strane" +msgstr "Stavljeno u red od strane" #: frappe/core/doctype/recorder/recorder.py:125 msgid "Enqueued creation of indexes" @@ -9508,7 +9508,7 @@ msgstr "Primer: \"boje\": [\"#d1d8dd\", \"#ff5858\"]" #. Label of the exact_copies (Int) field in DocType 'Recorder Query' #: frappe/core/doctype/recorder_query/recorder_query.json msgid "Exact Copies" -msgstr "Identične kompije" +msgstr "Identične kopije" #. Label of the example (HTML) field in DocType 'Workflow Transition' #: frappe/workflow/doctype/workflow_transition/workflow_transition.json @@ -9696,7 +9696,7 @@ msgstr "Izvoz redova koji sadrže grešku" #. Label of the export_from (Data) field in DocType 'Access Log' #: frappe/core/doctype/access_log/access_log.json msgid "Export From" -msgstr "Izvoz od" +msgstr "Izvoz iz" #: frappe/core/doctype/data_import/data_import.js:518 msgid "Export Import Log" @@ -9901,7 +9901,7 @@ msgstr "Neuspešno dobijanje informacija o sajtu" #: frappe/model/virtual_doctype.py:63 msgid "Failed to import virtual doctype {}, is controller file present?" -msgstr "Neuspešan pokušaj uvoza virtuelnog doctype {}, da lije fajl kontrolera prisutan?" +msgstr "Neuspešan pokušaj uvoza virtuelnog doctype {}, da li je fajl kontrolera prisutan?" #: frappe/utils/image.py:75 msgid "Failed to optimize image: {0}" @@ -10522,7 +10522,7 @@ msgstr "Prilagodi" #. Label of the flag (Data) field in DocType 'Language' #: frappe/core/doctype/language/language.json msgid "Flag" -msgstr "Zastavica" +msgstr "Zastava" #. Option for the 'Type' (Select) field in DocType 'DocField' #. Option for the 'Fieldtype' (Select) field in DocType 'Report Column' @@ -12186,7 +12186,7 @@ msgstr "Tokom svakog časa" #. Option for the 'Frequency' (Select) field in DocType 'Scheduled Job Type' #: frappe/core/doctype/scheduled_job_type/scheduled_job_type.json msgid "Hourly Maintenance" -msgstr "" +msgstr "Održavanje na svakih sat vremena" #. Description of the 'Password Reset Link Generation Limit' (Int) field in #. DocType 'System Settings' @@ -14221,23 +14221,23 @@ msgstr "Poslednjih 10 aktivnih korisnika" #: frappe/public/js/frappe/ui/filters/filter.js:627 msgid "Last 14 Days" -msgstr "" +msgstr "Poslednjih 14 dana" #: frappe/public/js/frappe/ui/filters/filter.js:631 msgid "Last 30 Days" -msgstr "" +msgstr "Poslednjih 30 dana" #: frappe/public/js/frappe/ui/filters/filter.js:651 msgid "Last 6 Months" -msgstr "" +msgstr "Poslednjih 6 meseci" #: frappe/public/js/frappe/ui/filters/filter.js:623 msgid "Last 7 Days" -msgstr "" +msgstr "Poslednjih 7 dana" #: frappe/public/js/frappe/ui/filters/filter.js:635 msgid "Last 90 Days" -msgstr "" +msgstr "Poslednjih 90 dana" #. Label of the last_active (Datetime) field in DocType 'User' #: frappe/core/doctype/user/user.json @@ -15187,7 +15187,7 @@ msgstr "MIT licenca" #: frappe/desk/page/setup_wizard/install_fixtures.py:48 msgid "Madam" -msgstr "Gospođo" +msgstr "Gospođa" #. Label of the main_section (Text Editor) field in DocType 'Web Page' #: frappe/website/doctype/web_page/web_page.json @@ -15863,7 +15863,7 @@ msgstr "Mobilni" #: frappe/tests/test_translate.py:89 frappe/tests/test_translate.py:91 #: frappe/tests/test_translate.py:94 msgid "Mobile No" -msgstr "" +msgstr "Broj mobilnog telefona" #. Label of the modal_trigger (Check) field in DocType 'Form Tour Step' #: frappe/desk/doctype/form_tour_step/form_tour_step.json @@ -16089,7 +16089,7 @@ msgstr "Više članaka o {0}" #. Settings' #: frappe/website/doctype/about_us_settings/about_us_settings.json msgid "More content for the bottom of the page." -msgstr "Više sadržaja za dno stranice." +msgstr "Dodatni sadržaj za dno stranice." #: frappe/public/js/frappe/ui/sort_selector.js:193 msgid "Most Used" @@ -16667,19 +16667,19 @@ msgstr "Sledeće" #: frappe/public/js/frappe/ui/filters/filter.js:683 msgid "Next 14 Days" -msgstr "" +msgstr "Narednih 14 dana" #: frappe/public/js/frappe/ui/filters/filter.js:687 msgid "Next 30 Days" -msgstr "" +msgstr "Narednih 30 dana" #: frappe/public/js/frappe/ui/filters/filter.js:703 msgid "Next 6 Months" -msgstr "" +msgstr "Narednih 6 meseci" #: frappe/public/js/frappe/ui/filters/filter.js:679 msgid "Next 7 Days" -msgstr "" +msgstr "Narednih 7 dana" #. Label of the next_action_email_template (Link) field in DocType 'Workflow #. Document State' @@ -16704,11 +16704,11 @@ msgstr "Naredni korak obilaska obrasca" #: frappe/public/js/frappe/ui/filters/filter.js:695 msgid "Next Month" -msgstr "" +msgstr "Sledeći mesec" #: frappe/public/js/frappe/ui/filters/filter.js:699 msgid "Next Quarter" -msgstr "" +msgstr "Sledeći kvartal" #. Label of the next_schedule_date (Date) field in DocType 'Auto Repeat' #: frappe/automation/doctype/auto_repeat/auto_repeat.json @@ -16738,11 +16738,11 @@ msgstr "Sledeći token za sinhronizaciju" #: frappe/public/js/frappe/ui/filters/filter.js:691 msgid "Next Week" -msgstr "" +msgstr "Sledeće nedelje" #: frappe/public/js/frappe/ui/filters/filter.js:707 msgid "Next Year" -msgstr "" +msgstr "Sledeće godine" #: frappe/public/js/frappe/form/workflow.js:45 msgid "Next actions" @@ -20369,7 +20369,7 @@ msgstr "U redu stavljen" #. Label of the queued_by (Data) field in DocType 'Prepared Report' #: frappe/core/doctype/prepared_report/prepared_report.json msgid "Queued By" -msgstr "U redu od strane" +msgstr "Stavljeno u red od strane" #: frappe/core/doctype/submission_queue/submission_queue.py:174 msgid "Queued for Submission. You can track the progress over {0}." @@ -22110,7 +22110,7 @@ msgstr "Indeks reda" #. Label of the row_indexes (Code) field in DocType 'Data Import Log' #: frappe/core/doctype/data_import_log/data_import_log.json msgid "Row Indexes" -msgstr "Indeksi redova" +msgstr "Indeksi reda" #. Label of the row_name (Data) field in DocType 'Property Setter' #: frappe/custom/doctype/property_setter/property_setter.json @@ -23525,7 +23525,7 @@ msgstr "Funkcionalnost serverskih skripti nije dostupna na ovom sajtu." #: frappe/public/js/frappe/request.js:254 msgid "Server failed to process this request because of a concurrent conflicting request. Please try again." -msgstr "" +msgstr "Server nije uspeo da obradi zahtev zbog istovremenog konfliktnog zahteva. Molimo Vas da pokušate ponovo." #: frappe/public/js/frappe/request.js:246 msgid "Server was too busy to process this request. Please try again." @@ -25382,7 +25382,7 @@ msgstr "Simbol" #: frappe/integrations/doctype/google_calendar/google_calendar.json #: frappe/integrations/doctype/google_contacts/google_contacts.json msgid "Sync" -msgstr "SInhronizuj" +msgstr "Sinhronizuj" #: frappe/integrations/doctype/google_calendar/google_calendar.js:28 msgid "Sync Calendar" @@ -25648,7 +25648,7 @@ msgstr "Neophodno se privilegije sistem menadžera." #. Option for the 'Channel' (Select) field in DocType 'Notification' #: frappe/email/doctype/notification/notification.json msgid "System Notification" -msgstr "Sistemsko obaveštenja" +msgstr "Sistemsko obaveštenje" #. Label of the system_page (Check) field in DocType 'Page' #: frappe/core/doctype/page/page.json @@ -26060,7 +26060,7 @@ msgstr "Dokument je dodeljen korisniku {0}" #: frappe/desk/doctype/dashboard_chart/dashboard_chart.json #: frappe/desk/doctype/number_card/number_card.json msgid "The document type selected is a child table, so the parent document type is required." -msgstr "Izabrana vrsta dokumenta je podtabela, stoga je potrebna matična vrsta dokumenta." +msgstr "Izabrana vrsta dokumenta je zavisna tabela, stoga je potrebna matična vrsta dokumenta." #: frappe/core/doctype/user_type/user_type.py:110 msgid "The field {0} is mandatory" @@ -26317,19 +26317,19 @@ msgstr "Ova Kanban tabla će biti privatna" #: frappe/public/js/frappe/ui/filters/filter.js:665 msgid "This Month" -msgstr "" +msgstr "Ovaj mesec" #: frappe/public/js/frappe/ui/filters/filter.js:669 msgid "This Quarter" -msgstr "" +msgstr "Ovaj kvartal" #: frappe/public/js/frappe/ui/filters/filter.js:661 msgid "This Week" -msgstr "" +msgstr "Ove nedelje" #: frappe/public/js/frappe/ui/filters/filter.js:673 msgid "This Year" -msgstr "" +msgstr "Ove godine" #: frappe/custom/doctype/customize_form/customize_form.js:220 msgid "This action is irreversible. Do you wish to continue?" @@ -26663,7 +26663,7 @@ msgstr "Vremenska zona" #. Label of the time_zones (Text) field in DocType 'Country' #: frappe/geo/doctype/country/country.json msgid "Time Zones" -msgstr "Vremenske zona" +msgstr "Vremenske zone" #. Label of the time_format (Data) field in DocType 'Country' #: frappe/geo/doctype/country/country.json @@ -26926,7 +26926,7 @@ msgstr "Da biste izvršili izvoz ovog koraka kao JSON, povežite ga u dokumentu #: frappe/email/doctype/email_account/email_account.js:126 msgid "To generate password click {0}" -msgstr "" +msgstr "Za generisanje lozinke kliknite {0}" #: frappe/public/js/frappe/views/reports/query_report.js:857 msgid "To get the updated report, click on {0}." @@ -26934,7 +26934,7 @@ msgstr "Za ažurirani izveštaj kliknite na {0}." #: frappe/email/doctype/email_account/email_account.js:139 msgid "To know more click {0}" -msgstr "" +msgstr "Za više informacija kliknite {0}" #. Description of the 'Console' (Code) field in DocType 'System Console' #: frappe/desk/doctype/system_console/system_console.json @@ -27044,7 +27044,7 @@ msgstr "Token nedostaje" #: frappe/public/js/frappe/ui/filters/filter.js:738 msgid "Tomorrow" -msgstr "" +msgstr "Sutra" #: frappe/desk/doctype/bulk_update/bulk_update.py:68 #: frappe/model/workflow.py:254 @@ -27110,7 +27110,7 @@ msgstr "Najčešće greške" #: frappe/printing/doctype/print_format/print_format.json #: frappe/public/js/print_format_builder/PrintFormatControls.vue:244 msgid "Top Left" -msgstr "Gore desno" +msgstr "Gore levo" #. Option for the 'Position' (Select) field in DocType 'Form Tour Step' #. Option for the 'Page Number' (Select) field in DocType 'Print Format' @@ -27643,7 +27643,7 @@ msgstr "Ukloni dodeljivanje uslova" #: frappe/app.py:383 msgid "Uncaught Exception" -msgstr "Neočekivani izuzetak" +msgstr "Neuhvaćeni izuzetak" #: frappe/public/js/frappe/form/toolbar.js:103 msgid "Unchanged" @@ -27973,7 +27973,7 @@ msgstr "Otpremanje na Google Drive" #: frappe/desk/doctype/onboarding_step/onboarding_step.json #, python-format msgid "Use % for any non empty value." -msgstr "Koristite % fza bilo koju vrednost koja nije prazna." +msgstr "Koristite % za bilo koju vrednost koja nije prazna." #. Label of the ascii_encode_password (Check) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json @@ -28802,7 +28802,7 @@ msgstr "Prikaži {0}" #. Label of the viewed_by (Data) field in DocType 'View Log' #: frappe/core/doctype/view_log/view_log.json msgid "Viewed By" -msgstr "Pogledano od strane" +msgstr "Uvid od strane" #. Group in DocType's connections #. Label of a Card Break in the Build Workspace @@ -29706,7 +29706,7 @@ msgstr "Da" #: frappe/public/js/frappe/ui/filters/filter.js:726 msgid "Yesterday" -msgstr "" +msgstr "Juče" #: frappe/public/js/frappe/utils/user.js:33 msgctxt "Name of the current user. For example: You edited this 5 hours ago." @@ -30646,7 +30646,7 @@ msgstr "nonce" #. Label of the notified (Check) field in DocType 'Reminder' #: frappe/automation/doctype/reminder/reminder.json msgid "notified" -msgstr "obvešten" +msgstr "obavešten" #: frappe/public/js/frappe/utils/pretty_date.js:25 msgid "now" diff --git a/frappe/locale/sv.po b/frappe/locale/sv.po index f7b2558b57..378bcbf981 100644 --- a/frappe/locale/sv.po +++ b/frappe/locale/sv.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: frappe\n" "Report-Msgid-Bugs-To: developers@frappe.io\n" "POT-Creation-Date: 2025-05-18 09:33+0000\n" -"PO-Revision-Date: 2025-05-19 07:02\n" +"PO-Revision-Date: 2025-05-26 10:24\n" "Last-Translator: developers@frappe.io\n" "Language-Team: Swedish\n" "MIME-Version: 1.0\n" @@ -8299,12 +8299,12 @@ msgstr "Rullgardin Meny" #. Label of the date (Date) field in DocType 'ToDo' #: frappe/desk/doctype/todo/todo.json msgid "Due Date" -msgstr "Förfallodatum" +msgstr "Förfallo Datum" #. Label of the due_date_based_on (Select) field in DocType 'Assignment Rule' #: frappe/automation/doctype/assignment_rule/assignment_rule.json msgid "Due Date Based On" -msgstr "Förfallodatum Baserad På" +msgstr "Förfallo Datum Baserad På" #: frappe/public/js/frappe/form/grid_row_form.js:42 #: frappe/public/js/frappe/form/toolbar.js:419 @@ -9631,7 +9631,7 @@ msgstr "Förfallo Avisering Aktiverad" #. Option for the 'Delivery Status' (Select) field in DocType 'Communication' #: frappe/core/doctype/communication/communication.json msgid "Expired" -msgstr "Utgången" +msgstr "Utgått" #. Label of the expires_in (Int) field in DocType 'OAuth Bearer Token' #. Label of the expires_in (Int) field in DocType 'Token Cache' @@ -18262,7 +18262,7 @@ msgstr "Outlook.com" #: frappe/desk/doctype/system_console/system_console.json #: frappe/integrations/doctype/integration_request/integration_request.json msgid "Output" -msgstr "Utgång" +msgstr "Utdata" #: frappe/public/js/frappe/form/templates/form_dashboard.html:5 msgid "Overview" @@ -30299,7 +30299,7 @@ msgstr "Roll " #. Label of the profile (Code) field in DocType 'Recorder' #: frappe/core/doctype/recorder/recorder.json msgid "cProfile Output" -msgstr "cProfil Utgång" +msgstr "cProfil Utdata" #: frappe/public/js/frappe/ui/toolbar/search_utils.js:286 msgid "calendar" diff --git a/frappe/locale/zh.po b/frappe/locale/zh.po index 1a379867c5..2c8fd9cbe0 100644 --- a/frappe/locale/zh.po +++ b/frappe/locale/zh.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: frappe\n" "Report-Msgid-Bugs-To: developers@frappe.io\n" "POT-Creation-Date: 2025-05-18 09:33+0000\n" -"PO-Revision-Date: 2025-05-19 07:02\n" +"PO-Revision-Date: 2025-05-25 10:26\n" "Last-Translator: developers@frappe.io\n" "Language-Team: Chinese Simplified\n" "MIME-Version: 1.0\n" @@ -5169,7 +5169,7 @@ msgstr "每小时评论限制" #: frappe/desk/form/utils.py:75 msgid "Comment publicity can only be updated by the original author or a System Manager." -msgstr "" +msgstr "评论公开状态仅可由原作者或系统管理员修改。" #: frappe/model/meta.py:57 frappe/public/js/frappe/form/controls/comment.js:9 #: frappe/public/js/frappe/model/meta.js:209 @@ -6413,7 +6413,7 @@ msgstr "每日长格式" #. Option for the 'Frequency' (Select) field in DocType 'Scheduled Job Type' #: frappe/core/doctype/scheduled_job_type/scheduled_job_type.json msgid "Daily Maintenance" -msgstr "" +msgstr "日常维护" #. Option for the 'Style' (Select) field in DocType 'Workflow State' #: frappe/workflow/doctype/workflow_state/workflow_state.json @@ -8755,7 +8755,7 @@ msgstr "电子邮件组成员" #. Label of the email_header (Data) field in DocType 'Notification Log' #: frappe/desk/doctype/notification_log/notification_log.json msgid "Email Header" -msgstr "" +msgstr "邮件标头" #. Label of the email_id (Data) field in DocType 'Contact Email' #. Label of the email_id (Data) field in DocType 'User Email' @@ -8905,7 +8905,7 @@ msgstr "未通过{0}验证的电子邮件" #: frappe/email/doctype/email_queue/email_queue.js:19 msgid "Email queue is currently suspended. Resume to automatically send other emails." -msgstr "" +msgstr "邮件队列已暂停。恢复后系统将自动发送其他邮件。" #. Label of the section_break_udjs (Section Break) field in DocType 'System #. Health Report' @@ -12186,7 +12186,7 @@ msgstr "每小时长格式" #. Option for the 'Frequency' (Select) field in DocType 'Scheduled Job Type' #: frappe/core/doctype/scheduled_job_type/scheduled_job_type.json msgid "Hourly Maintenance" -msgstr "" +msgstr "按小时维护" #. Description of the 'Password Reset Link Generation Limit' (Int) field in #. DocType 'System Settings' @@ -14221,11 +14221,11 @@ msgstr "最近10个活跃用户" #: frappe/public/js/frappe/ui/filters/filter.js:627 msgid "Last 14 Days" -msgstr "" +msgstr "过去14天" #: frappe/public/js/frappe/ui/filters/filter.js:631 msgid "Last 30 Days" -msgstr "" +msgstr "过去30天" #: frappe/public/js/frappe/ui/filters/filter.js:651 msgid "Last 6 Months" @@ -14233,11 +14233,11 @@ msgstr "过去6个月" #: frappe/public/js/frappe/ui/filters/filter.js:623 msgid "Last 7 Days" -msgstr "" +msgstr "过去7天" #: frappe/public/js/frappe/ui/filters/filter.js:635 msgid "Last 90 Days" -msgstr "" +msgstr "过去90天" #. Label of the last_active (Datetime) field in DocType 'User' #: frappe/core/doctype/user/user.json @@ -16667,11 +16667,11 @@ msgstr "下一步" #: frappe/public/js/frappe/ui/filters/filter.js:683 msgid "Next 14 Days" -msgstr "" +msgstr "未来 14 天" #: frappe/public/js/frappe/ui/filters/filter.js:687 msgid "Next 30 Days" -msgstr "" +msgstr "未来30天" #: frappe/public/js/frappe/ui/filters/filter.js:703 msgid "Next 6 Months" @@ -16679,7 +16679,7 @@ msgstr "未来6个月" #: frappe/public/js/frappe/ui/filters/filter.js:679 msgid "Next 7 Days" -msgstr "" +msgstr "未来 7 天" #. Label of the next_action_email_template (Link) field in DocType 'Workflow #. Document State' @@ -18580,7 +18580,7 @@ msgstr "父项是数据将被添加到的文档名称" #: frappe/public/js/frappe/ui/group_by/group_by.js:251 msgid "Parent-to-child or child-to-parent grouping is not allowed." -msgstr "" +msgstr "禁止父子层级互相嵌套的分组方式。" #: frappe/permissions.py:798 msgid "Parentfield not specified in {0}: {1}" @@ -23525,7 +23525,7 @@ msgstr "此站点未启用服务器脚本功能" #: frappe/public/js/frappe/request.js:254 msgid "Server failed to process this request because of a concurrent conflicting request. Please try again." -msgstr "" +msgstr "因并发请求冲突,服务器未能处理本请求。请稍后重试。" #: frappe/public/js/frappe/request.js:246 msgid "Server was too busy to process this request. Please try again." @@ -26414,7 +26414,7 @@ msgstr "该文件已关联至受保护文档,不可删除。" #: frappe/public/js/frappe/file_uploader/FilePreview.vue:76 msgid "This file is public and can be accessed by anyone, even without logging in. Mark it private to limit access." -msgstr "" +msgstr "本文件为公开文件,无需登录即可访问。请设为私有以限制访问权限。" #: frappe/core/doctype/file/file.js:20 msgid "This file is public. It can be accessed without authentication." @@ -26926,7 +26926,7 @@ msgstr "导出此步骤为JSON需关联到入职文档并保存" #: frappe/email/doctype/email_account/email_account.js:126 msgid "To generate password click {0}" -msgstr "" +msgstr "生成密码请点击{0}" #: frappe/public/js/frappe/views/reports/query_report.js:857 msgid "To get the updated report, click on {0}." @@ -26934,7 +26934,7 @@ msgstr "获取更新后的报表请点击{0}" #: frappe/email/doctype/email_account/email_account.js:139 msgid "To know more click {0}" -msgstr "" +msgstr "了解更多请点击{0}" #. Description of the 'Console' (Code) field in DocType 'System Console' #: frappe/desk/doctype/system_console/system_console.json @@ -28831,7 +28831,7 @@ msgstr "可见性" #: frappe/public/js/frappe/form/templates/timeline_message_box.html:40 msgid "Visible to website/portal users." -msgstr "" +msgstr "对网站/门户用户可见。" #. Option for the 'Type' (Select) field in DocType 'Communication' #: frappe/core/doctype/communication/communication.json @@ -29578,11 +29578,11 @@ msgstr "工作区" #: frappe/public/js/frappe/form/footer/form_timeline.js:731 msgid "Would you like to publish this comment? This means it will become visible to website/portal users." -msgstr "" +msgstr "是否确认发布本评论?发布后网站/门户用户可见。" #: frappe/public/js/frappe/form/footer/form_timeline.js:735 msgid "Would you like to unpublish this comment? This means it will no longer be visible to website/portal users." -msgstr "" +msgstr "是否确认取消发布本评论?取消后网站/门户用户将不可见。" #: frappe/desk/page/setup_wizard/setup_wizard.py:35 msgid "Wrapping up" diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 0a730c8b06..5ccfbf0cce 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -825,6 +825,8 @@ class BaseDocument: return True elif df.fieldtype == "Code" and df.options == "HTML" and has_text_or_img_tag: return True + elif df.fieldtype == "Check": + return True # Checkboxes can't be mandatory, they're 0 by default else: return has_text_content diff --git a/frappe/model/document.py b/frappe/model/document.py index c37bb4b7a0..fad1bcbbed 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1,6 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import hashlib +import itertools import json import time from collections.abc import Generator, Iterable @@ -1320,7 +1321,8 @@ class Document(BaseDocument, DocRef): elif self._action == "update_after_submit": self.run_method("on_update_after_submit") - self.clear_cache() + if not (frappe.flags.in_import and self.is_new()): + self.clear_cache() if self.flags.get("notify_update", True): self.notify_update() @@ -1350,7 +1352,12 @@ class Document(BaseDocument, DocRef): def notify_update(self): """Publish realtime that the current document is modified""" - if frappe.flags.in_patch: + if ( + frappe.flags.in_import + or frappe.flags.in_patch + or frappe.flags.in_migrate + or frappe.flags.in_install + ): return frappe.publish_realtime( @@ -1456,14 +1463,13 @@ class Document(BaseDocument, DocRef): doc_to_compare = frappe.get_doc(self.doctype, amended_from) version = frappe.new_doc("Version") + + if not doc_to_compare and not self.flags.updater_reference: + return + if version.update_version_info(doc_to_compare, self): version.insert(ignore_permissions=True) - if not frappe.flags.in_migrate: - # follow since you made a change? - if frappe.get_cached_value("User", frappe.session.user, "follow_created_documents"): - follow_document(self.doctype, self.name, frappe.session.user) - @staticmethod def hook(f): """Decorator: Make method `hookable` (i.e. extensible by another app). @@ -1871,7 +1877,8 @@ def bulk_insert( doctype: str, documents: Iterable["Document"], ignore_duplicates: bool = False, - chunk_size=10_000, + chunk_size=1000, + commit_chunks=False, ): """Insert simple Documents objects to database in bulk. @@ -1882,31 +1889,37 @@ def bulk_insert( """ doctype_meta = frappe.get_meta(doctype) - documents = list(documents) valid_column_map = { doctype: doctype_meta.get_valid_columns(), } - values_map = { - doctype: _document_values_generator(documents, valid_column_map[doctype]), - } - for child_table in doctype_meta.get_table_fields(): + child_table_fields = doctype_meta.get_table_fields() + for child_table in child_table_fields: valid_column_map[child_table.options] = frappe.get_meta(child_table.options).get_valid_columns() - values_map[child_table.options] = _document_values_generator( - [ - ch_doc - for ch_doc in ( - child_docs for doc in documents for child_docs in doc.get(child_table.fieldname) - ) - ], - valid_column_map[child_table.options], - ) - for dt, docs in values_map.items(): - frappe.db.bulk_insert( - dt, valid_column_map[dt], docs, ignore_duplicates=ignore_duplicates, chunk_size=chunk_size - ) + documents = iter(documents) + while document_batch := list(itertools.islice(documents, chunk_size)): + values_map = { + doctype: _document_values_generator(document_batch, valid_column_map[doctype]), + } + + for child_table in child_table_fields: + values_map[child_table.options] = _document_values_generator( + [ + ch_doc + for ch_doc in ( + child_docs for doc in document_batch for child_docs in doc.get(child_table.fieldname) + ) + ], + valid_column_map[child_table.options], + ) + + for dt, docs in values_map.items(): + frappe.db.bulk_insert(dt, valid_column_map[dt], docs, ignore_duplicates=ignore_duplicates) + + if commit_chunks: + frappe.db.commit() def _document_values_generator( diff --git a/frappe/model/utils/link_count.py b/frappe/model/utils/link_count.py index e2bc3f85f6..4d67297afe 100644 --- a/frappe/model/utils/link_count.py +++ b/frappe/model/utils/link_count.py @@ -2,6 +2,7 @@ # License: MIT. See LICENSE from collections import defaultdict +from random import random import frappe @@ -29,10 +30,13 @@ ignore_doctypes = { } +LINK_COUNT_BUFFER_SIZE = 256 + + def notify_link_count(doctype, name): """updates link count for given document""" - if doctype in ignore_doctypes or not frappe.request: + if doctype in ignore_doctypes or not frappe.request or random() < 0.9: # Sample 10% return if not hasattr(frappe.local, "_link_count"): @@ -50,13 +54,18 @@ def flush_local_link_count(): link_count = frappe.cache.get_value("_link_count") or {} + flush = False for key, value in new_links.items(): if key in link_count: link_count[key] += value - else: + elif len(link_count) < LINK_COUNT_BUFFER_SIZE: link_count[key] = value + else: + continue + flush = True - frappe.cache.set_value("_link_count", link_count) + if flush: + frappe.cache.set_value("_link_count", link_count) new_links.clear() diff --git a/frappe/public/js/frappe/form/controls/code.js b/frappe/public/js/frappe/form/controls/code.js index b3bee4b051..f8d60a4322 100644 --- a/frappe/public/js/frappe/form/controls/code.js +++ b/frappe/public/js/frappe/form/controls/code.js @@ -29,6 +29,7 @@ frappe.ui.form.ControlCode = class ControlCode extends frappe.ui.form.ControlTex style="position: absolute; top: 32px; right: 5px;" onmouseover="this.classList.add('btn-default')" onmouseout="this.classList.remove('btn-default')" + title="${__("Copy to Clipboard")}" >
+ Usage: To execute press ctrl/cmd+enter. + To minimize this window press Escape. + Press shift+t to bring the window back. + `, + fieldname: "console", + fieldtype: "Code", + label: "Console", + options: "Python", + min_lines: 20, + max_lines: 20, + wrap: true, + }, + { + fieldname: "output", + fieldtype: "Code", + label: "Output", + read_only: 1, + }, + ], + }); + this.dialog.get_close_btn().show(); // framework hides it on static dialogs + this.editor = null; + + let me = this; + this.dialog.$wrapper.on("keydown", function (e) { + if (e.key === "Escape") { + e.preventDefault(); + if (!me.dialog.is_minimized) { + me.dialog.toggle_minimize(); + } + return false; + } + }); + } + + sleep(duration) { + return new Promise((r) => setTimeout(r, duration)); + } + + async wait_for_ace() { + // I can't find any other way to ensure that ace is loaded and ready + // This small delay shouldn't be noticable. + let retry_count = 0; + + while (retry_count++ < 10 && !this.editor) { + await this.sleep(25); + this.editor = this.dialog.get_field("console").editor; + } + + if (!this.editor) { + throw Error("Code editor not found"); + } + } + + async show() { + this.dialog.show(); + await this.wait_for_ace(); + this.bind_executer(); + this.load_completions(); + this.load_contextual_boilerplate(); + } + + async load_contextual_boilerplate() { + if (cur_frm && !cur_frm.is_new()) { + let current_code = this.dialog.get_value("console"); + if (!current_code) { + this.dialog + .get_field("console") + .editor?.insert( + `doc = frappe.get_doc("${cur_frm.doc.doctype}", "${cur_frm.doc.name}")\n` + ); + } + } + } + + async bind_executer() { + let me = this; + const field = this.dialog.get_field("console"); + let editor = field.editor; + editor.setKeyboardHandler(null); // sorry emacs/vim users + editor.commands.addCommand({ + name: "execute_code", + bindKey: { + // Shortcut keys + win: "Ctrl-Enter", + mac: "Command-Enter", + }, + exec: function (editor) { + me.execute_code(); + }, + }); + } + + async execute_code() { + await this.sleep(50); // ace often takes time to push changes + this.dialog.set_value("output", ""); + const output_field = this.dialog.get_field("output"); + output_field.set_description(""); + const start = frappe.datetime.now_datetime(true); + let { output } = await frappe.xcall( + "frappe.desk.doctype.system_console.system_console.execute_code", + { + doc: { + console: this.dialog.get_value("console"), + doctype: "System Console", + type: "Python", + }, + }, + "POST", + { + freeze: true, + freeze_message: __("Executing Code"), + } + ); + const end = frappe.datetime.now_datetime(true); + this.dialog.set_value("output", output); + const time_taken = moment(end).diff(start, "milliseconds"); + output_field.set_description(`Executed in ${time_taken} milliseconds. + View Logs`); + } + + async load_completions() { + let me = this; + let items = await frappe.xcall( + "frappe.core.doctype.server_script.server_script.get_autocompletion_items", + null, + "GET", + { cache: true } + ); + const field = me.dialog.get_field("console"); + const custom_completions = []; + if (cur_frm && !cur_frm.is_new()) { + frappe.meta + .get_fieldnames(cur_frm.doc.doctype, cur_frm.doc.parent, { + fieldtype: ["not in", frappe.model.no_value_type], + }) + .forEach((fieldname) => { + custom_completions.push(`doc.${fieldname}`); + }); + } + + field.df.autocompletions = [...items, ...custom_completions]; + } +} diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index b013cf5fb0..a0431999e1 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -42,6 +42,7 @@ frappe.ui.Filter = class { Date: ["like", "not like"], Datetime: ["like", "not like", "in", "not in", "=", "!="], Data: ["Between", "Timespan"], + Time: ["Between", "Timespan"], Select: ["like", "not like", "Between", "Timespan"], Link: ["Between", "Timespan", ">", "<", ">=", "<="], Currency: ["Between", "Timespan"], diff --git a/frappe/public/js/frappe/ui/keyboard.js b/frappe/public/js/frappe/ui/keyboard.js index 1ab57ddb3f..beda0b3feb 100644 --- a/frappe/public/js/frappe/ui/keyboard.js +++ b/frappe/public/js/frappe/ui/keyboard.js @@ -1,4 +1,5 @@ import "./alt_keyboard_shortcuts"; +import { DropdownConsole } from "./dropdown_console"; frappe.provide("frappe.ui.keys.handlers"); @@ -347,3 +348,20 @@ function close_grid_and_dialog() { return false; } } + +frappe.ui.keys.add_shortcut({ + shortcut: "shift+t", + action: function (e) { + if (!frappe.model.can_write("System Console")) { + return; + } + if (cur_dialog?.is_minimized) { + cur_dialog.toggle_minimize(); + cur_dialog.focus_on_first_input(); + } else { + let dropdown_console = new DropdownConsole(); + dropdown_console.show(); + } + }, + description: __("Open console"), +}); diff --git a/frappe/public/js/frappe/widgets/number_card_widget.js b/frappe/public/js/frappe/widgets/number_card_widget.js index d65315dc29..83c2c880dd 100644 --- a/frappe/public/js/frappe/widgets/number_card_widget.js +++ b/frappe/public/js/frappe/widgets/number_card_widget.js @@ -230,16 +230,14 @@ export default class NumberCardWidget extends Widget { const shortened_number = frappe.utils.shorten_number(this.number, default_country, 5); number_parts = shortened_number.split(" "); } - + const symbol = number_parts[1] || ""; // done to add multicurrency support in number card if (this.card_doc.currency) { this.formatted_number = - format_currency(number_parts[0], this.card_doc.currency) + - " " + - __(number_parts[1]); + format_currency(number_parts[0], this.card_doc.currency) + " " + symbol; return; } - const symbol = number_parts[1] || ""; + number_parts[0] = window.convert_old_to_new_number_format(number_parts[0]); const formatted_number = frappe.format(number_parts[0], df, null, doc); this.formatted_number = diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss index 9d97bafc33..858517495b 100644 --- a/frappe/public/scss/desk/list.scss +++ b/frappe/public/scss/desk/list.scss @@ -431,6 +431,7 @@ input.list-header-checkbox { background-color: var(--control-bg); min-width: 21px; border-radius: 22px; + margin-left: 6px; } } diff --git a/frappe/utils/data.py b/frappe/utils/data.py index df78493eff..94fa3c642e 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -640,12 +640,17 @@ def get_time( return time_str elif isinstance(time_str, datetime.timedelta): return (datetime.datetime.min + time_str).time() + try: - return parser.parse(time_str).time() - except ParserError as e: - if "day" in e.args[1] or "hour must be in" in e.args[0]: - return (datetime.datetime.min + parse_timedelta(time_str)).time() - raise e + # PERF: Our DATE_FORMAT is same as ISO format. + return datetime.time.fromisoformat(time_str) + except ValueError: + try: + return parser.parse(time_str).time() + except ParserError as e: + if "day" in e.args[1] or "hour must be in" in e.args[0]: + return (datetime.datetime.min + parse_timedelta(time_str)).time() + raise e def get_datetime_str(datetime_obj: DateTimeLikeObject) -> str: diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index ed1a93f95b..3ddb2d8a26 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -376,7 +376,7 @@ def sync_global_search(): yield value item_generator = get_search_queue_item_generator() - while search_items := tuple(islice(item_generator, 10_000)): + while search_items := tuple(islice(item_generator, 1000)): values = _get_deduped_search_item_values(search_items) sync_values(values) diff --git a/frappe/www/list.py b/frappe/www/list.py index 2dedaa4eb5..1bdda56757 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -155,8 +155,11 @@ def prepare_filters(doctype, controller, kwargs): filters[key] = val # filter the filters to include valid fields only + from frappe.model.meta import DEFAULT_FIELD_LABELS + for fieldname in list(filters.keys()): - if not meta.has_field(fieldname): + # add a check for default fields, as they are not present in meta.fields + if not meta.has_field(fieldname) and fieldname not in DEFAULT_FIELD_LABELS.keys(): del filters[fieldname] return filters diff --git a/package.json b/package.json index 3021f4ec12..2d55997045 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "popper.js": "^1.16.0", "postcss": "8", "quill": "2.0.3", - "quill-image-resize": "^3.0.9", + "frappe-quill-image-resize": "^3.0.9", "quill-magic-url": "^3.0.0", "qz-tray": "^2.0.8", "rtlcss": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index da1ad17608..2ea0ceccfb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1445,6 +1445,15 @@ frappe-gantt@^0.6.0: resolved "https://registry.yarnpkg.com/frappe-gantt/-/frappe-gantt-0.6.1.tgz#57ae0b5f024101fc7cd5ba92f605de97dba9c9a1" integrity sha512-1cSU9vLbwypjzaxnCfnEE03Xr3HlAV2S8dRtjxw62o+amkx1A8bBIFd2jp84mcDdTCM77Ij4LzZBslAKZB8oMg== +frappe-quill-image-resize@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/frappe-quill-image-resize/-/frappe-quill-image-resize-3.0.9.tgz#35e1d94aca458328be0db03ac25b58171af8c194" + integrity sha512-2g38/Z/jbi3gbkCgHRgDe9MVskcjJrUioB3RXhQGhjpGKRgBznyVOLmHAZ1a8sBKAHBfuVz9BRdQulkJUrKg0g== + dependencies: + lodash "^4.17.4" + quill "^1.2.2" + raw-loader "^0.5.1" + fs-exists-sync@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" @@ -2730,15 +2739,6 @@ quill-delta@^5.1.0: lodash.clonedeep "^4.5.0" lodash.isequal "^4.5.0" -quill-image-resize@^3.0.9: - version "3.0.9" - resolved "https://registry.yarnpkg.com/quill-image-resize/-/quill-image-resize-3.0.9.tgz#5677fb6257560bff951153ddbdb836758e49f804" - integrity sha512-5Dk0nixhbFsCwSWtPU9qqqtfM2gURfaP+pbBhQvAoMJoF4p99xbAibfAI3gsZJkbWUodoK2iAPf1V5oTSpv9ww== - dependencies: - lodash "^4.17.4" - quill "^1.2.2" - raw-loader "^0.5.1" - quill-magic-url@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/quill-magic-url/-/quill-magic-url-3.0.2.tgz#84654c749650e006250cbaf905295cb87796f3a7"