Merge branch 'develop' into enable-further-translations
This commit is contained in:
commit
6a3e43789b
14 changed files with 111 additions and 37 deletions
8
.github/helper/roulette.py
vendored
8
.github/helper/roulette.py
vendored
|
|
@ -77,13 +77,13 @@ if __name__ == "__main__":
|
|||
updated_py_file_count = len(list(filter(is_py, files_list)))
|
||||
only_py_changed = updated_py_file_count == len(files_list)
|
||||
|
||||
if ci_files_changed:
|
||||
print("CI related files were updated, running all build processes.")
|
||||
|
||||
elif has_skip_ci_label(pr_number, repo):
|
||||
if has_skip_ci_label(pr_number, repo):
|
||||
print("Found `Skip CI` label on pr, stopping build process.")
|
||||
sys.exit(0)
|
||||
|
||||
elif ci_files_changed:
|
||||
print("CI related files were updated, running all build processes.")
|
||||
|
||||
elif only_docs_changed:
|
||||
print("Only docs were updated, stopping build process.")
|
||||
sys.exit(0)
|
||||
|
|
|
|||
16
.github/workflows/linters.yml
vendored
16
.github/workflows/linters.yml
vendored
|
|
@ -11,10 +11,10 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.8
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install and Run Pre-commit
|
||||
uses: pre-commit/action@v3.0.0
|
||||
|
|
@ -22,10 +22,8 @@ jobs:
|
|||
- name: Download Semgrep rules
|
||||
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
||||
|
||||
- uses: returntocorp/semgrep-action@v1
|
||||
env:
|
||||
SEMGREP_TIMEOUT: 120
|
||||
with:
|
||||
config: >-
|
||||
r/python.lang.correctness
|
||||
./frappe-semgrep-rules/rules
|
||||
- name: Download semgrep
|
||||
run: pip install semgrep==0.97.0
|
||||
|
||||
- name: Run Semgrep rules
|
||||
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ class DocType(Document):
|
|||
|
||||
if docfield.fieldname in method_set:
|
||||
conflict_type = "controller method"
|
||||
if docfield.fieldname in property_set:
|
||||
if docfield.fieldname in property_set and not docfield.is_virtual:
|
||||
conflict_type = "class property"
|
||||
|
||||
if conflict_type:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from requests.exceptions import HTTPError, SSLError
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import call_hook_method, cint, get_files_path, get_hook_method
|
||||
from frappe.utils import call_hook_method, cint, get_files_path, get_hook_method, get_url
|
||||
from frappe.utils.file_manager import is_safe_path
|
||||
from frappe.utils.image import optimize_image, strip_exif_data
|
||||
|
||||
|
|
@ -61,7 +61,12 @@ class File(Document):
|
|||
self.set_file_name()
|
||||
self.validate_attachment_limit()
|
||||
|
||||
if not self.is_folder and not self.is_remote_file:
|
||||
if self.is_folder:
|
||||
return
|
||||
|
||||
if self.is_remote_file:
|
||||
self.validate_remote_file()
|
||||
else:
|
||||
self.save_file(content=self.get_content())
|
||||
self.flags.new_file = True
|
||||
frappe.local.rollback_observers.append(self)
|
||||
|
|
@ -255,6 +260,12 @@ class File(Document):
|
|||
title=_("Attachment Limit Reached"),
|
||||
)
|
||||
|
||||
def validate_remote_file(self):
|
||||
"""Validates if file uploaded using URL already exist"""
|
||||
site_url = get_url()
|
||||
if "/files/" in self.file_url and self.file_url.startswith(site_url):
|
||||
self.file_url = self.file_url.split(site_url, 1)[1]
|
||||
|
||||
def set_folder_name(self):
|
||||
"""Make parent folders if not exists based on reference doctype and name"""
|
||||
if self.folder:
|
||||
|
|
@ -445,6 +456,10 @@ class File(Document):
|
|||
|
||||
file_path = self.file_url or self.file_name
|
||||
|
||||
site_url = get_url()
|
||||
if "/files/" in file_path and file_path.startswith(site_url):
|
||||
file_path = file_path.split(site_url, 1)[1]
|
||||
|
||||
if "/" not in file_path:
|
||||
if self.is_private:
|
||||
file_path = f"/private/files/{file_path}"
|
||||
|
|
|
|||
|
|
@ -16,8 +16,11 @@
|
|||
"server_script",
|
||||
"frequency",
|
||||
"cron_format",
|
||||
"create_log",
|
||||
"status_section",
|
||||
"last_execution",
|
||||
"create_log"
|
||||
"column_break_9",
|
||||
"next_execution"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -72,6 +75,22 @@
|
|||
"options": "Server Script",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "next_execution",
|
||||
"fieldtype": "Datetime",
|
||||
"is_virtual": 1,
|
||||
"label": "Next Execution",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Status"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
|
|
@ -81,7 +100,7 @@
|
|||
"link_fieldname": "scheduled_job_type"
|
||||
}
|
||||
],
|
||||
"modified": "2020-10-07 10:39:24.519460",
|
||||
"modified": "2022-06-28 02:55:12.470915",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Scheduled Job Type",
|
||||
|
|
@ -103,5 +122,7 @@
|
|||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "method",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -50,6 +50,10 @@ class ScheduledJobType(Document):
|
|||
queued_jobs = get_jobs(site=frappe.local.site, key="job_type")[frappe.local.site]
|
||||
return self.method in queued_jobs
|
||||
|
||||
@property
|
||||
def next_execution(self):
|
||||
return self.get_next_execution()
|
||||
|
||||
def get_next_execution(self):
|
||||
CRON_MAP = {
|
||||
"Yearly": "0 0 1 1 *",
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@
|
|||
"otp_issuer_name",
|
||||
"email",
|
||||
"email_footer_address",
|
||||
"email_retry_limit",
|
||||
"column_break_18",
|
||||
"disable_standard_email_footer",
|
||||
"hide_footer_in_auto_email_reports",
|
||||
|
|
@ -495,8 +496,8 @@
|
|||
"fieldname": "allow_older_web_view_links",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Older Web View Links (Insecure)"
|
||||
},
|
||||
{
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_64",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
|
|
@ -518,12 +519,18 @@
|
|||
"fieldtype": "Duration",
|
||||
"label": "Reset Password Link Expiry Duration",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"default": "3",
|
||||
"fieldname": "email_retry_limit",
|
||||
"fieldtype": "Int",
|
||||
"label": "Email Retry Limit"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-19 00:00:18.095269",
|
||||
"modified": "2022-06-21 13:55:04.796152",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -156,8 +156,6 @@ def setup_group_by(data):
|
|||
**data
|
||||
)
|
||||
)
|
||||
if data.aggregate_on_field:
|
||||
data.fields.append(f"`tab{data.aggregate_on_doctype}`.`{data.aggregate_on_field}`")
|
||||
else:
|
||||
raise_invalid_field(data.aggregate_on_field)
|
||||
|
||||
|
|
@ -435,11 +433,20 @@ def append_totals_row(data):
|
|||
def get_labels(fields, doctype):
|
||||
"""get column labels based on column names"""
|
||||
labels = []
|
||||
doctype = doctype.lower()
|
||||
for key in fields:
|
||||
key = key.split(" as ")[0]
|
||||
aggregate_function = ""
|
||||
|
||||
key = key.casefold().split(" as ", maxsplit=1)[0]
|
||||
|
||||
if key.startswith(("count(", "sum(", "avg(")):
|
||||
continue
|
||||
# Get aggregate function and _aggregate_column
|
||||
# key = 'sum(`tabDocType`.`fieldname`)'
|
||||
if not key.rstrip().endswith(")"):
|
||||
continue
|
||||
_agg_fn, _key = key.split("(", maxsplit=1)
|
||||
aggregate_function = _agg_fn.lower() # aggregate_function = 'sum'
|
||||
key = _key[:-1] # key = `tabDocType`.`fieldname`
|
||||
|
||||
if "." in key:
|
||||
parenttype, fieldname = key.split(".")[0][4:-1], key.split(".")[1].strip("`")
|
||||
|
|
@ -455,7 +462,10 @@ def get_labels(fields, doctype):
|
|||
if parenttype != doctype:
|
||||
# If the column is from a child table, append the child doctype.
|
||||
# For example, "Item Code (Sales Invoice Item)".
|
||||
label += f" ({ _(parenttype) })"
|
||||
label += f" ({ _(parenttype.title()) })"
|
||||
|
||||
if aggregate_function:
|
||||
label = _("{0} of {1}").format(aggregate_function.capitalize(), label)
|
||||
|
||||
labels.append(label)
|
||||
|
||||
|
|
@ -464,7 +474,7 @@ def get_labels(fields, doctype):
|
|||
|
||||
def handle_duration_fieldtype_values(doctype, data, fields):
|
||||
for field in fields:
|
||||
key = field.split(" as ")[0]
|
||||
key = field.casefold().split(" as ", maxsplit=1)[0]
|
||||
|
||||
if key.startswith(("count(", "sum(", "avg(")):
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ from frappe.utils import (
|
|||
split_emails,
|
||||
)
|
||||
|
||||
MAX_RETRY_COUNT = 3
|
||||
|
||||
|
||||
class EmailQueue(Document):
|
||||
DOCTYPE = "Email Queue"
|
||||
|
|
@ -210,7 +208,7 @@ class SendMailContext:
|
|||
email_status = (self.sent_to and "Partially Sent") or "Not Sent"
|
||||
self.queue_doc.update_status(status=email_status, commit=True)
|
||||
elif exc_type:
|
||||
if self.queue_doc.retry < MAX_RETRY_COUNT:
|
||||
if self.queue_doc.retry < get_email_retry_limit():
|
||||
update_fields = {"status": "Not Sent", "retry": self.queue_doc.retry + 1}
|
||||
else:
|
||||
update_fields = {"status": (self.sent_to and "Partially Errored") or "Error"}
|
||||
|
|
@ -372,6 +370,10 @@ def on_doctype_update():
|
|||
)
|
||||
|
||||
|
||||
def get_email_retry_limit():
|
||||
return cint(frappe.db.get_system_setting("email_retry_limit")) or 3
|
||||
|
||||
|
||||
class QueueBuilder:
|
||||
"""Builds Email Queue from the given data"""
|
||||
|
||||
|
|
|
|||
|
|
@ -417,6 +417,8 @@ class DatabaseQuery(object):
|
|||
"extract(",
|
||||
"locate(",
|
||||
"strpos(",
|
||||
]
|
||||
aggregate_functions = [
|
||||
"count(",
|
||||
"sum(",
|
||||
"avg(",
|
||||
|
|
@ -427,6 +429,9 @@ class DatabaseQuery(object):
|
|||
if not ("tab" in field and "." in field) or any(x for x in sql_functions if x in field):
|
||||
continue
|
||||
|
||||
if any(x for x in aggregate_functions if x in field):
|
||||
field = field.split("(", 1)[1][:-1]
|
||||
|
||||
table_name = field.split(".")[0]
|
||||
|
||||
if table_name.lower().startswith("group_concat("):
|
||||
|
|
|
|||
|
|
@ -613,8 +613,8 @@
|
|||
<symbol viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" id="icon-expenses">
|
||||
<path d="M17 16a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V2.37a.2.2 0 0 1 .31-.168l1.75 1.143a.5.5 0 0 0 .547 0L7.393 2.18a.5.5 0 0 1 .547 0l1.787 1.166a.5.5 0 0 0 .546 0L12.06 2.18a.5.5 0 0 1 .547 0l1.786 1.166a.5.5 0 0 0 .547 0l1.75-1.143a.2.2 0 0 1 .31.167V16z"
|
||||
stroke="var(--icon-stroke)" stroke-miterlimit="10" stroke-linecap="square"></path>
|
||||
<path d="M12.925 9.097l-.298 1.052h-.904c-.174 1.15-.997 1.986-2.748 2.093l2.787 3.413v.072h-1.556l-3.05-3.703-.01-.83H8.66c.938 0 1.53-.372 1.705-1.045H7.04l.298-1.052h2.992c-.196-.601-.737-.963-1.67-.963H7.04L7.351 7h5.578l-.302 1.061-1.343-.008c.222.298.367.652.43 1.044h1.211z"
|
||||
fill="#192734" stroke="none"></path>
|
||||
<path d="m13.13,8.1l-0.3,1.05l-0.91,0c-0.17,1.15 -0.99,1.98 -2.74,2.09l2.78,3.41l0,0.08l-1.55,0l-3.05,-3.71l-0.01,-0.83l1.51,0c0.94,0 1.53,-0.37 1.71,-1.04l-3.33,0l0.3,-1.05l2.99,0c-0.2,-0.6 -0.74,-0.97 -1.67,-0.97l-1.62,0l0.31,-1.13l5.58,0l-0.3,1.06l-1.35,-0.01c0.23,0.3 0.37,0.66 0.43,1.05l1.22,0z"
|
||||
fill="var(--icon-stroke)" stroke="none"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" id="icon-income">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
|
@ -643,9 +643,7 @@ class TestReportview(unittest.TestCase):
|
|||
)
|
||||
|
||||
response = execute_cmd("frappe.desk.reportview.get")
|
||||
self.assertListEqual(
|
||||
response["keys"], ["field_label", "field_name", "_aggregate_column", "columns"]
|
||||
)
|
||||
self.assertListEqual(response["keys"], ["field_label", "field_name", "_aggregate_column"])
|
||||
|
||||
def test_cast_name(self):
|
||||
from frappe.core.doctype.doctype.test_doctype import new_doctype
|
||||
|
|
|
|||
|
|
@ -1993,7 +1993,7 @@ QR Code,QR-Code,
|
|||
QR Code for Login Verification,QR Code für Login-Bestätigung,
|
||||
QZ Tray Connection Active!,QZ-Tray-Verbindung aktiv!,
|
||||
QZ Tray Failed: ,QZ-Fach fehlgeschlagen:,
|
||||
Quarter Day,Quartalstag,
|
||||
Quarter Day,Viertel-Tag,
|
||||
Query,Abfrage,
|
||||
Query Report,Abfragebericht,
|
||||
Query must be a SELECT,Abfrage muss ein SELECT sein,
|
||||
|
|
@ -4793,3 +4793,17 @@ Reset to default,Auf Standard zurücksetzen,
|
|||
Column Width,Spaltenbreite,
|
||||
Choose Kanban Board,Kanban-Tafel auswählen,
|
||||
Create New Board,Neue Tafel erstellen,
|
||||
Only If Creator,Nur wenn Ersteller,
|
||||
Rebuild Tree,Baum neu aufbauen,
|
||||
Customize Dashboard,Dashboard anpassen,
|
||||
Reset Dashboard Customizations,Dashboard-Anpassungen zurücksetzen,
|
||||
Add {0},{0} hinzufügen,
|
||||
descending,absteigend,
|
||||
ascending,aufsteigend,
|
||||
Next Document,Nächstes Dokument,
|
||||
Previous Document,Vorheriges Dokument,
|
||||
Mark all as read,Alle als gelesen markieren,
|
||||
See all Activity,Alle Aktivitäten anzeigen,
|
||||
Go to Notification Settings List,Gehe zur Listenansicht Benachrichtigungseinstellungen,
|
||||
Load more,Mehr laden,
|
||||
Edit Full Form,Vollständiges Formular bearbeiten,
|
||||
|
|
|
|||
|
|
|
@ -199,7 +199,7 @@ class RedisWrapper(redis.Redis):
|
|||
frappe.local.cache[_name][key] = value
|
||||
elif generator:
|
||||
value = generator()
|
||||
self.hset(name, key, value)
|
||||
self.hset(name, key, value, shared=shared)
|
||||
return value
|
||||
|
||||
def hdel(self, name, key, shared=False):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue