Merge branch 'develop' into new-ui-for-api-key

This commit is contained in:
Abdeali Chharchhoda 2025-05-29 23:13:10 +05:30
commit d4a2fe36d3
30 changed files with 453 additions and 175 deletions

View file

@ -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):

View file

@ -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):

View file

@ -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)

View file

@ -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

View file

@ -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")} <kbd>⇧+T</kbd>`
);
},
type: function (frm) {

View file

@ -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
}
}

View file

@ -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))

View file

@ -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

View file

@ -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:

View file

@ -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",

View file

@ -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 &lt;img /&gt; tag. Keep size as 200px x 30px"
msgstr "نام تجاری همان چیزی است که در سمت چپ بالای نوار ابزار ظاهر می‌شود. اگر تصویر است، مطمئن شوید که\n"
msgstr "برند همان چیزی است که در سمت چپ بالای نوار ابزار ظاهر می‌شود. اگر تصویر است، مطمئن شوید که\n"
"دارای پس‌زمینه شفاف است و از تگ &lt;img /&gt; استفاده کنید. اندازه را 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'

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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(

View file

@ -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()

View file

@ -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")}"
>
<svg class="es-icon es-line icon-sm" style="" aria-hidden="true">
<use class="" href="#es-line-copy-light"></use>
@ -241,6 +242,10 @@ frappe.ui.form.ControlCode = class ControlCode extends frappe.ui.form.ControlTex
return this.editor ? this.editor.session.getValue() : "";
}
set_focus() {
this.editor?.focus();
}
load_lib() {
if (this.library_loaded) return this.library_loaded;

View file

@ -1,5 +1,5 @@
import Quill from "quill";
import ImageResize from "quill-image-resize";
import ImageResize from "frappe-quill-image-resize";
import MagicUrl from "quill-magic-url";
Quill.register("modules/imageResize", ImageResize);
@ -7,6 +7,14 @@ Quill.register("modules/magicUrl", MagicUrl);
const CodeBlockContainer = Quill.import("formats/code-block-container");
CodeBlockContainer.tagName = "PRE";
Quill.register(CodeBlockContainer, true);
const Embed = Quill.import("blots/embed");
const Delta = Quill.import("delta");
class BreakBlot extends Embed {}
BreakBlot.blotName = "Break";
BreakBlot.tagName = "br";
Quill.register(BreakBlot);
// font size
let font_sizes = [
@ -146,6 +154,7 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
const toolbar = this.quill.getModule("toolbar");
toolbar.addHandler("table", this.handle_table_actions);
}
handle_table_actions(value) {
const table = this.quill.getModule("table");
@ -219,6 +228,9 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
imageResize: {},
magicUrl: true,
mention: this.get_mention_options(),
keyboard: {
bindings: this.get_keyboard_bindings(),
},
},
theme: this.df.theme || "snow",
readOnly: this.disabled,
@ -375,4 +387,35 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
set_focus() {
this.quill.focus();
}
get_keyboard_bindings() {
let bindings = {
"table enter": {
key: "Enter",
formats: ["table"],
handler: function (range) {
this.quill.updateContents(
new Delta()
.retain(range.index)
.delete(range.length)
.insert({ Break: true })
);
if (!this.quill.getLeaf(range.index + 1)[0].next) {
this.quill.updateContents(
new Delta()
.retain(range.index + 1)
.delete(0)
.insert({ Break: true }),
"user"
);
}
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
return false; // dont call other handlers
},
},
};
return bindings;
}
};

View file

@ -87,6 +87,10 @@ export default class Grid {
data-action="delete_rows">
${__("Delete")}
</button>
<button type="button" class="btn btn-xs btn-secondary grid-remove-rows hidden"
data-action="duplicate_rows">
${__("Duplicate Row")}
</button>
<button type="button" class="btn btn-xs btn-danger grid-remove-all-rows hidden"
data-action="delete_all_rows">
${__("Delete All")}
@ -234,6 +238,14 @@ export default class Grid {
}
}
duplicate_rows() {
let selected_children = this.get_selected_children();
selected_children.forEach((doc) => {
this.add_new_row(null, null, false, doc, false);
this.check_range(doc.name, doc.name, false);
});
}
delete_rows() {
var dirty = false;

View file

@ -0,0 +1,157 @@
export class DropdownConsole {
constructor() {
this.dialog = new frappe.ui.Dialog({
title: __("System Console"),
minimizable: true,
static: true,
no_cancel_flag: true, // hack: global escape handler kills the dialog
size: "large",
fields: [
{
description: `
${frappe.utils.icon("solid-warning", "xs")}
WARNING: Executing random untested code here is dangerous, use with extreme caution. <br>
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.
<a target="_blank" href="/app/console-log?owner=${frappe.session.user}" >View Logs</a>`);
}
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];
}
}

View file

@ -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"],

View file

@ -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"),
});

View file

@ -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 =

View file

@ -431,6 +431,7 @@ input.list-header-checkbox {
background-color: var(--control-bg);
min-width: 21px;
border-radius: 22px;
margin-left: 6px;
}
}

View file

@ -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:

View file

@ -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)

View file

@ -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

View file

@ -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",

View file

@ -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"