fix: Resolve conflicts
This commit is contained in:
commit
2243c5ebe3
152 changed files with 3129 additions and 2080 deletions
2
.github/helper/install.sh
vendored
2
.github/helper/install.sh
vendored
|
|
@ -66,6 +66,6 @@ bench --site test_site reinstall --yes
|
|||
|
||||
if [ "$TYPE" == "server" ]
|
||||
then
|
||||
# wait till assets are built succesfully
|
||||
# wait till assets are built successfully
|
||||
wait $build_pid
|
||||
fi
|
||||
|
|
|
|||
7
.github/helper/roulette.py
vendored
7
.github/helper/roulette.py
vendored
|
|
@ -73,8 +73,9 @@ def has_label(pr_number, label, repo="frappe/frappe"):
|
|||
)
|
||||
|
||||
|
||||
def is_py(file):
|
||||
return file.endswith("py")
|
||||
def is_server_side_code(file):
|
||||
"""File exclusively affects server side code"""
|
||||
return file.endswith("py") or file.endswith(".po")
|
||||
|
||||
|
||||
def is_ci(file):
|
||||
|
|
@ -112,7 +113,7 @@ if __name__ == "__main__":
|
|||
ci_files_changed = any(f for f in files_list if is_ci(f))
|
||||
only_docs_changed = len(list(filter(is_docs, files_list))) == len(files_list)
|
||||
only_frontend_code_changed = len(list(filter(is_frontend_code, files_list))) == len(files_list)
|
||||
updated_py_file_count = len(list(filter(is_py, files_list)))
|
||||
updated_py_file_count = len(list(filter(is_server_side_code, files_list)))
|
||||
only_py_changed = updated_py_file_count == len(files_list)
|
||||
|
||||
if has_skip_ci_label(pr_number, repo):
|
||||
|
|
|
|||
2
.github/workflows/linters.yml
vendored
2
.github/workflows/linters.yml
vendored
|
|
@ -82,7 +82,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
|
||||
|
|
|
|||
4
.github/workflows/patch-mariadb-tests.yml
vendored
4
.github/workflows/patch-mariadb-tests.yml
vendored
|
|
@ -76,7 +76,7 @@ jobs:
|
|||
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
|
||||
|
|
@ -88,7 +88,7 @@ jobs:
|
|||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
|
|||
6
.github/workflows/server-tests.yml
vendored
6
.github/workflows/server-tests.yml
vendored
|
|
@ -43,6 +43,8 @@ jobs:
|
|||
needs: checkrun
|
||||
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
NODE_ENV: "production"
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
@ -104,7 +106,7 @@ jobs:
|
|||
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
|
||||
|
|
@ -116,7 +118,7 @@ jobs:
|
|||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
|
|||
8
.github/workflows/ui-tests.yml
vendored
8
.github/workflows/ui-tests.yml
vendored
|
|
@ -42,6 +42,8 @@ jobs:
|
|||
needs: checkrun
|
||||
if: ${{ needs.checkrun.outputs.build == 'strawberry' && github.repository_owner == 'frappe' }}
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
NODE_ENV: "production"
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
@ -87,7 +89,7 @@ jobs:
|
|||
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
|
||||
|
|
@ -99,7 +101,7 @@ jobs:
|
|||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -108,7 +110,7 @@ jobs:
|
|||
${{ runner.os }}-yarn-ui-
|
||||
|
||||
- name: Cache cypress binary
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ hooks.py,frappe.gettext.extractors.navbar.extract
|
|||
**/report/*/*.json,frappe.gettext.extractors.report.extract
|
||||
**.py,frappe.gettext.extractors.python.extract
|
||||
**.js,frappe.gettext.extractors.javascript.extract
|
||||
**.html,frappe.gettext.extractors.jinja2.extract
|
||||
**.html,frappe.gettext.extractors.html_template.extract
|
||||
|
|
|
@ -158,7 +158,7 @@ async function update_assets_json_from_built_assets(apps) {
|
|||
}
|
||||
|
||||
async function update_assets_obj(app, assets, assets_rtl) {
|
||||
const app_path = get_app_path(app);
|
||||
const app_path = path.join(apps_path, app, app);
|
||||
const dist_path = path.join(app_path, "public, dist");
|
||||
const files = await glob("**/*.bundle.*.{js,css}", { cwd: dist_path });
|
||||
const prefix = path.join("/", "assets", app, "dist");
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import inspect
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, TypeAlias, overload
|
||||
|
|
@ -130,9 +131,45 @@ def _lt(msg: str, lang: str | None = None, context: str | None = None):
|
|||
|
||||
Note: Result is not guaranteed to equivalent to pure strings for all operations.
|
||||
"""
|
||||
from frappe.translate import LazyTranslate
|
||||
return _LazyTranslate(msg, lang, context)
|
||||
|
||||
return LazyTranslate(msg, lang, context)
|
||||
|
||||
@functools.total_ordering
|
||||
class _LazyTranslate:
|
||||
__slots__ = ("msg", "lang", "context")
|
||||
|
||||
def __init__(self, msg: str, lang: str | None = None, context: str | None = None) -> None:
|
||||
self.msg = msg
|
||||
self.lang = lang
|
||||
self.context = context
|
||||
|
||||
@property
|
||||
def value(self) -> str:
|
||||
return _(str(self.msg), self.lang, self.context)
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, (str, _LazyTranslate)):
|
||||
return self.value + str(other)
|
||||
raise NotImplementedError
|
||||
|
||||
def __radd__(self, other):
|
||||
if isinstance(other, (str, _LazyTranslate)):
|
||||
return str(other) + self.value
|
||||
return NotImplementedError
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"'{self.value}'"
|
||||
|
||||
# NOTE: it's required to override these methods and raise error as default behaviour will
|
||||
# return `False` in all cases.
|
||||
def __eq__(self, other):
|
||||
raise NotImplementedError
|
||||
|
||||
def __lt__(self, other):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def as_unicode(text, encoding: str = "utf-8") -> str:
|
||||
|
|
@ -269,10 +306,6 @@ def init(site: str, sites_path: str = ".", new_site: bool = False, force=False)
|
|||
|
||||
local.initialised = True
|
||||
|
||||
# Set the user as database name if not set in config
|
||||
if local.conf and local.conf.db_name is not None and local.conf.db_user is None:
|
||||
local.conf.db_user = local.conf.db_name
|
||||
|
||||
|
||||
def connect(
|
||||
site: str | None = None, db_name: str | None = None, set_admin_as_user: bool = True
|
||||
|
|
@ -280,7 +313,7 @@ def connect(
|
|||
"""Connect to site database instance.
|
||||
|
||||
:param site: (Deprecated) If site is given, calls `frappe.init`.
|
||||
:param db_name: Optional. Will use from `site_config.json`.
|
||||
:param db_name: (Deprecated) Optional. Will use from `site_config.json`.
|
||||
:param set_admin_as_user: Set Administrator as current user.
|
||||
"""
|
||||
from frappe.database import get_db
|
||||
|
|
@ -293,12 +326,24 @@ def connect(
|
|||
"Instead, explicitly invoke frappe.init(site) prior to calling frappe.connect(), if initializing the site is necessary."
|
||||
)
|
||||
init(site)
|
||||
if db_name:
|
||||
from frappe.utils.deprecations import deprecation_warning
|
||||
|
||||
deprecation_warning(
|
||||
"Calling frappe.connect with the db_name argument is deprecated and will be removed in next major version. "
|
||||
"Instead, explicitly invoke frappe.init(site) with the right config prior to calling frappe.connect(), if necessary."
|
||||
)
|
||||
|
||||
assert db_name or local.conf.db_user, "site must be fully initialized, db_user missing"
|
||||
assert db_name or local.conf.db_name, "site must be fully initialized, db_name missing"
|
||||
assert local.conf.db_password, "site must be fully initialized, db_password missing"
|
||||
|
||||
local.db = get_db(
|
||||
host=local.conf.db_host,
|
||||
port=local.conf.db_port,
|
||||
user=local.conf.db_user or db_name,
|
||||
password=None,
|
||||
password=local.conf.db_password,
|
||||
cur_db_name=local.conf.db_name or db_name,
|
||||
)
|
||||
if set_admin_as_user:
|
||||
set_user("Administrator")
|
||||
|
|
@ -318,7 +363,13 @@ def connect_replica() -> bool:
|
|||
user = local.conf.replica_db_user or local.conf.replica_db_name
|
||||
password = local.conf.replica_db_password
|
||||
|
||||
local.replica_db = get_db(host=local.conf.replica_host, user=user, password=password, port=port)
|
||||
local.replica_db = get_db(
|
||||
host=local.conf.replica_host,
|
||||
port=port,
|
||||
user=user,
|
||||
password=password,
|
||||
cur_db_name=local.conf.db_name,
|
||||
)
|
||||
|
||||
# swap db connections
|
||||
local.primary_db = local.db
|
||||
|
|
@ -380,6 +431,23 @@ def get_site_config(sites_path: str | None = None, site_path: str | None = None)
|
|||
os.environ.get("FRAPPE_DB_PORT") or config.get("db_port") or db_default_ports(config["db_type"])
|
||||
)
|
||||
|
||||
# Set the user as database name if not set in config
|
||||
config["db_user"] = (
|
||||
os.environ.get("FRAPPE_DB_USER") or config.get("db_user") or config.get("db_name")
|
||||
)
|
||||
|
||||
# Allow externally extending the config with hooks
|
||||
if extra_config := config.get("extra_config"):
|
||||
if isinstance(extra_config, str):
|
||||
extra_config = [extra_config]
|
||||
for hook in extra_config:
|
||||
try:
|
||||
module, method = hook.rsplit(".", 1)
|
||||
config |= getattr(importlib.import_module(module), method)()
|
||||
except Exception:
|
||||
print(f"Config hook {hook} failed")
|
||||
traceback.print_exc()
|
||||
|
||||
return config
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ import base64
|
|||
import binascii
|
||||
from urllib.parse import quote, urlencode, urlparse
|
||||
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
import frappe
|
||||
import frappe.database
|
||||
import frappe.utils
|
||||
import frappe.utils.user
|
||||
from frappe import _
|
||||
from frappe.core.doctype.activity_log.activity_log import add_authentication_log
|
||||
from frappe.sessions import Session, clear_sessions, delete_session
|
||||
from frappe.sessions import Session, clear_sessions, delete_session, get_expiry_in_seconds
|
||||
from frappe.translate import get_language
|
||||
from frappe.twofactor import (
|
||||
authenticate_for_2factor,
|
||||
|
|
@ -272,9 +274,7 @@ class LoginManager:
|
|||
if self.user in frappe.STANDARD_USERS:
|
||||
return False
|
||||
|
||||
reset_pwd_after_days = cint(
|
||||
frappe.db.get_single_value("System Settings", "force_user_to_reset_password")
|
||||
)
|
||||
reset_pwd_after_days = cint(frappe.get_system_settings("force_user_to_reset_password"))
|
||||
|
||||
if reset_pwd_after_days:
|
||||
last_password_reset_date = (
|
||||
|
|
@ -356,12 +356,19 @@ class CookieManager:
|
|||
if not frappe.local.session.get("sid"):
|
||||
return
|
||||
|
||||
# sid expires in 3 days
|
||||
expires = datetime.datetime.now() + datetime.timedelta(days=3)
|
||||
if frappe.session.sid:
|
||||
self.set_cookie("sid", frappe.session.sid, expires=expires, httponly=True)
|
||||
self.set_cookie("sid", frappe.session.sid, max_age=get_expiry_in_seconds(), httponly=True)
|
||||
|
||||
def set_cookie(self, key, value, expires=None, secure=False, httponly=False, samesite="Lax"):
|
||||
def set_cookie(
|
||||
self,
|
||||
key,
|
||||
value,
|
||||
expires=None,
|
||||
secure=False,
|
||||
httponly=False,
|
||||
samesite="Lax",
|
||||
max_age=None,
|
||||
):
|
||||
if not secure and hasattr(frappe.local, "request"):
|
||||
secure = frappe.local.request.scheme == "https"
|
||||
|
||||
|
|
@ -371,6 +378,7 @@ class CookieManager:
|
|||
"secure": secure,
|
||||
"httponly": httponly,
|
||||
"samesite": samesite,
|
||||
"max_age": max_age,
|
||||
}
|
||||
|
||||
def delete_cookie(self, to_delete):
|
||||
|
|
@ -379,7 +387,7 @@ class CookieManager:
|
|||
|
||||
self.to_delete.extend(to_delete)
|
||||
|
||||
def flush_cookies(self, response):
|
||||
def flush_cookies(self, response: Response):
|
||||
for key, opts in self.cookies.items():
|
||||
response.set_cookie(
|
||||
key,
|
||||
|
|
@ -388,6 +396,7 @@ class CookieManager:
|
|||
secure=opts.get("secure"),
|
||||
httponly=opts.get("httponly"),
|
||||
samesite=opts.get("samesite"),
|
||||
max_age=opts.get("max_age"),
|
||||
)
|
||||
|
||||
# expires yesterday!
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from frappe.cache_manager import clear_doctype_map, get_doctype_map
|
|||
from frappe.desk.form import assign_to
|
||||
from frappe.model import log_types
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.data import comma_and
|
||||
|
||||
|
||||
class AssignmentRule(Document):
|
||||
|
|
@ -55,14 +56,10 @@ class AssignmentRule(Document):
|
|||
|
||||
def validate_assignment_days(self):
|
||||
assignment_days = self.get_assignment_days()
|
||||
|
||||
if len(set(assignment_days)) != len(assignment_days):
|
||||
repeated_days = get_repeated(assignment_days)
|
||||
plural = "s" if len(repeated_days) > 1 else ""
|
||||
|
||||
frappe.throw(
|
||||
_("Assignment Day{0} {1} has been repeated.").format(
|
||||
plural, frappe.bold(", ".join(repeated_days))
|
||||
_("The following Assignment Days have been repeated: {0}").format(
|
||||
comma_and([_(day) for day in get_repeated(assignment_days)], add_quotes=False)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -550,7 +550,7 @@ def get_auto_repeat_doctypes(doctype, txt, searchfield, start, page_len, filters
|
|||
docs += [r.name for r in res]
|
||||
docs = set(list(docs))
|
||||
|
||||
return [[d] for d in docs]
|
||||
return [[d] for d in docs if txt in d]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -772,12 +772,8 @@ def run_tests(
|
|||
click.secho(f"bench --site {site} set-config allow_tests true", fg="green")
|
||||
return
|
||||
|
||||
frappe.init(site=site)
|
||||
|
||||
frappe.flags.skip_before_tests = skip_before_tests
|
||||
frappe.flags.skip_test_records = skip_test_records
|
||||
|
||||
ret = frappe.test_runner.main(
|
||||
site,
|
||||
app,
|
||||
module,
|
||||
doctype,
|
||||
|
|
@ -790,6 +786,8 @@ def run_tests(
|
|||
doctype_list_path=doctype_list_path,
|
||||
failfast=failfast,
|
||||
case=case,
|
||||
skip_test_records=skip_test_records,
|
||||
skip_before_tests=skip_before_tests,
|
||||
)
|
||||
|
||||
if len(ret.failures) == 0 and len(ret.errors) == 0:
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class AddressTemplate(Document):
|
|||
|
||||
if not self.is_default and not self._get_previous_default():
|
||||
self.is_default = 1
|
||||
if frappe.db.get_single_value("System Settings", "setup_complete"):
|
||||
if frappe.get_system_settings("setup_complete"):
|
||||
frappe.msgprint(_("Setting this Address Template as default as there is no other default"))
|
||||
|
||||
def on_update(self):
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ class CommunicationEmailMixin:
|
|||
return get_formatted_email(self.mail_sender_fullname(), mail=self.mail_sender())
|
||||
|
||||
def get_content(self, print_format=None):
|
||||
if print_format and frappe.db.get_single_value("System Settings", "attach_view_link"):
|
||||
if print_format and frappe.get_system_settings("attach_view_link"):
|
||||
return self.content + self.get_attach_link(print_format)
|
||||
return self.content
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ const can_export = (frm) => {
|
|||
if (!doctype) {
|
||||
frappe.msgprint(__("Please select the Document Type."));
|
||||
} else if (!parent_multicheck_options.length) {
|
||||
frappe.msgprint(__("Atleast one field of Parent Document Type is mandatory"));
|
||||
frappe.msgprint(__("At least one field of Parent Document Type is mandatory"));
|
||||
} else {
|
||||
is_valid_form = true;
|
||||
}
|
||||
|
|
@ -153,7 +153,7 @@ const add_doctype_field_multicheck_control = (doctype, parent_wrapper) => {
|
|||
|
||||
const options = fields.map((df) => {
|
||||
return {
|
||||
label: df.label,
|
||||
label: __(df.label),
|
||||
value: df.fieldname,
|
||||
danger: df.reqd,
|
||||
checked: 1,
|
||||
|
|
@ -163,7 +163,7 @@ const add_doctype_field_multicheck_control = (doctype, parent_wrapper) => {
|
|||
const multicheck_control = frappe.ui.form.make_control({
|
||||
parent: parent_wrapper,
|
||||
df: {
|
||||
label: doctype,
|
||||
label: __(doctype),
|
||||
fieldname: doctype + "_fields",
|
||||
fieldtype: "MultiCheck",
|
||||
options: options,
|
||||
|
|
|
|||
|
|
@ -135,34 +135,29 @@ frappe.ui.form.on("Data Import", {
|
|||
let failed_records = cint(r.message.failed);
|
||||
let total_records = cint(r.message.total_records);
|
||||
|
||||
if (!total_records) return;
|
||||
let action, message;
|
||||
if (frm.doc.import_type === "Insert New Records") {
|
||||
action = "imported";
|
||||
} else {
|
||||
action = "updated";
|
||||
if (!total_records) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (failed_records === 0) {
|
||||
let message_args = [action, successful_records];
|
||||
if (successful_records === 1) {
|
||||
message = __("Successfully {0} 1 record.", message_args);
|
||||
} else {
|
||||
message = __("Successfully {0} {1} records.", message_args);
|
||||
}
|
||||
let message;
|
||||
if (frm.doc.import_type === "Insert New Records") {
|
||||
message = __("Successfully imported {0} out of {1} records.", [
|
||||
successful_records,
|
||||
total_records,
|
||||
]);
|
||||
} else {
|
||||
let message_args = [action, successful_records, total_records];
|
||||
if (successful_records === 1) {
|
||||
message = __(
|
||||
"Successfully {0} {1} record out of {2}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
message = __("Successfully updated {0} out of {1} records.", [
|
||||
successful_records,
|
||||
total_records,
|
||||
]);
|
||||
}
|
||||
|
||||
if (failed_records > 0) {
|
||||
message +=
|
||||
"<br/>" +
|
||||
__(
|
||||
"Please click on 'Export Errored Rows', fix the errors and import again."
|
||||
);
|
||||
} else {
|
||||
message = __(
|
||||
"Successfully {0} {1} records out of {2}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If the job timed out, display an extra hint
|
||||
|
|
@ -506,13 +501,7 @@ frappe.ui.form.on("Data Import", {
|
|||
},
|
||||
|
||||
show_import_log(frm) {
|
||||
if (!frm.doc.show_failed_logs) {
|
||||
frm.toggle_display("import_log_preview", false);
|
||||
return;
|
||||
}
|
||||
|
||||
frm.toggle_display("import_log_section", false);
|
||||
frm.toggle_display("import_log_preview", true);
|
||||
|
||||
if (frm.import_in_progress) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@
|
|||
"default": "0",
|
||||
"fieldname": "show_failed_logs",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Failed Logs"
|
||||
"label": "Show Only Failed Logs"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal && !doc.import_file",
|
||||
|
|
@ -171,7 +171,7 @@
|
|||
],
|
||||
"hide_toolbar": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-15 12:45:49.452834",
|
||||
"modified": "2024-01-30 17:08:05.566686",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Data Import",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ class DataImport(Document):
|
|||
submit_after_import: DF.Check
|
||||
template_options: DF.Code | None
|
||||
template_warnings: DF.Code | None
|
||||
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
|
|
@ -93,7 +92,8 @@ class DataImport(Document):
|
|||
def start_import(self):
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
run_now = frappe.flags.in_test or frappe.conf.developer_mode
|
||||
if is_scheduler_inactive() and not run_now:
|
||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||
|
||||
job_id = f"data_import::{self.name}"
|
||||
|
|
@ -106,7 +106,7 @@ class DataImport(Document):
|
|||
event="data_import",
|
||||
job_id=job_id,
|
||||
data_import=self.name,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||
now=run_now,
|
||||
)
|
||||
return True
|
||||
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.is_virtual",
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List View",
|
||||
|
|
@ -580,7 +581,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-16 11:26:56.364594",
|
||||
"modified": "2024-02-01 15:55:44.007917",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ class DocField(Document):
|
|||
unique: DF.Check
|
||||
width: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
def get_link_doctype(self):
|
||||
"""Return the Link doctype for the `docfield` (if applicable).
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,24 @@ class TestDocShare(FrappeTestCase):
|
|||
with self.assertRowsRead(1):
|
||||
self.assertTrue(self.event.has_permission())
|
||||
|
||||
def test_list_permission(self):
|
||||
frappe.set_user(self.user)
|
||||
with self.assertRaises(frappe.PermissionError):
|
||||
frappe.get_list("Web Page")
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
doc = frappe.new_doc("Web Page")
|
||||
doc.update({"title": "test document for docshare permissions"})
|
||||
doc.insert()
|
||||
frappe.share.add("Web Page", doc.name, self.user)
|
||||
|
||||
frappe.set_user(self.user)
|
||||
self.assertEqual(len(frappe.get_list("Web Page")), 1)
|
||||
|
||||
doc.delete(ignore_permissions=True)
|
||||
with self.assertRaises(frappe.PermissionError):
|
||||
frappe.get_list("Web Page")
|
||||
|
||||
def test_share_permission(self):
|
||||
frappe.share.add("Event", self.event.name, self.user, write=1, share=1)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,19 @@ frappe.listview_settings["DocType"] = {
|
|||
this.new_doctype_dialog();
|
||||
},
|
||||
|
||||
new_doctype_dialog() {
|
||||
new_doctype_dialog(args) {
|
||||
const {
|
||||
doctype_name = "",
|
||||
doctype_module = "",
|
||||
is_submittable = 0,
|
||||
is_child = 0,
|
||||
is_virtual = 0,
|
||||
is_single = 0,
|
||||
is_tree = 0,
|
||||
is_custom = 0,
|
||||
editable_grid = 1,
|
||||
} = args || {};
|
||||
|
||||
let non_developer = frappe.session.user !== "Administrator" || !frappe.boot.developer_mode;
|
||||
let fields = [
|
||||
{
|
||||
|
|
@ -11,6 +23,7 @@ frappe.listview_settings["DocType"] = {
|
|||
fieldname: "name",
|
||||
fieldtype: "Data",
|
||||
reqd: 1,
|
||||
default: doctype_name,
|
||||
},
|
||||
{ fieldtype: "Column Break" },
|
||||
{
|
||||
|
|
@ -19,6 +32,7 @@ frappe.listview_settings["DocType"] = {
|
|||
fieldtype: "Link",
|
||||
options: "Module Def",
|
||||
reqd: 1,
|
||||
default: doctype_module,
|
||||
},
|
||||
{ fieldtype: "Section Break" },
|
||||
{
|
||||
|
|
@ -29,6 +43,7 @@ frappe.listview_settings["DocType"] = {
|
|||
"Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended."
|
||||
),
|
||||
depends_on: "eval:!doc.istable && !doc.issingle",
|
||||
default: is_submittable,
|
||||
},
|
||||
{
|
||||
label: __("Is Child Table"),
|
||||
|
|
@ -36,13 +51,14 @@ frappe.listview_settings["DocType"] = {
|
|||
fieldtype: "Check",
|
||||
description: __("Child Tables are shown as a Grid in other DocTypes"),
|
||||
depends_on: "eval:!doc.is_submittable && !doc.issingle",
|
||||
default: is_child,
|
||||
},
|
||||
{
|
||||
label: __("Editable Grid"),
|
||||
fieldname: "editable_grid",
|
||||
fieldtype: "Check",
|
||||
depends_on: "istable",
|
||||
default: 1,
|
||||
default: editable_grid,
|
||||
},
|
||||
{
|
||||
label: __("Is Single"),
|
||||
|
|
@ -52,12 +68,13 @@ frappe.listview_settings["DocType"] = {
|
|||
"Single Types have only one record no tables associated. Values are stored in tabSingles"
|
||||
),
|
||||
depends_on: "eval:!doc.istable && !doc.is_submittable",
|
||||
default: is_single,
|
||||
},
|
||||
{
|
||||
label: "Is Tree",
|
||||
fieldname: "is_tree",
|
||||
fieldtype: "Check",
|
||||
default: "0",
|
||||
default: is_tree,
|
||||
depends_on: "eval:!doc.istable",
|
||||
description: "Tree structures are implemented using Nested Set",
|
||||
},
|
||||
|
|
@ -65,7 +82,7 @@ frappe.listview_settings["DocType"] = {
|
|||
label: __("Custom?"),
|
||||
fieldname: "custom",
|
||||
fieldtype: "Check",
|
||||
default: non_developer,
|
||||
default: non_developer || is_custom,
|
||||
read_only: non_developer,
|
||||
},
|
||||
];
|
||||
|
|
@ -75,7 +92,7 @@ frappe.listview_settings["DocType"] = {
|
|||
label: "Is Virtual",
|
||||
fieldname: "is_virtual",
|
||||
fieldtype: "Check",
|
||||
default: "0",
|
||||
default: is_virtual,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ class TestNamingSeries(FrappeTestCase):
|
|||
|
||||
def get_valid_serieses(self):
|
||||
VALID_SERIES = ["SINV-", "SI-.{field}.", "SI-#.###", ""]
|
||||
exisiting_series = self.dns.get_transactions_and_prefixes()["prefixes"]
|
||||
return VALID_SERIES + exisiting_series
|
||||
existing_series = self.dns.get_transactions_and_prefixes()["prefixes"]
|
||||
return VALID_SERIES + existing_series
|
||||
|
||||
def test_naming_preview(self):
|
||||
self.dns.transaction_type = self.ns_doctype
|
||||
|
|
|
|||
|
|
@ -756,6 +756,13 @@ class File(Document):
|
|||
self.save_file(content=optimized_content, overwrite=True)
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def unique_url(self) -> str:
|
||||
"""Unique URL contains file ID in URL to speed up permisison checks."""
|
||||
from urllib.parse import urlencode
|
||||
|
||||
return self.file_url + "?" + urlencode({"fid": self.name})
|
||||
|
||||
@staticmethod
|
||||
def zip_files(files):
|
||||
zip_file = io.BytesIO()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@
|
|||
"section_break_sgro",
|
||||
"form_dict",
|
||||
"section_break_9jhm",
|
||||
"sql_queries"
|
||||
"sql_queries",
|
||||
"section_break_optn",
|
||||
"profile"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -107,6 +109,16 @@
|
|||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Event Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_optn",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "profile",
|
||||
"fieldtype": "Code",
|
||||
"label": "cProfile Output",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
|
|
@ -114,7 +126,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"is_virtual": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-03 16:45:47.110048",
|
||||
"modified": "2024-02-01 22:13:26.505174",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Recorder",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class Recorder(Document):
|
|||
method: DF.Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
|
||||
number_of_queries: DF.Int
|
||||
path: DF.Data | None
|
||||
profile: DF.Code | None
|
||||
request_headers: DF.Code | None
|
||||
sql_queries: DF.Table[RecorderQuery]
|
||||
time: DF.Datetime | None
|
||||
|
|
|
|||
|
|
@ -10,12 +10,7 @@ frappe.listview_settings["Recorder"] = {
|
|||
}
|
||||
|
||||
listview.page.add_button(__("Clear"), () => {
|
||||
frappe.call({
|
||||
method: "frappe.recorder.delete",
|
||||
callback: function () {
|
||||
listview.refresh();
|
||||
},
|
||||
});
|
||||
frappe.xcall("frappe.recorder.delete").then(listview.refresh);
|
||||
});
|
||||
|
||||
listview.page.add_menu_item(__("Import"), () => {
|
||||
|
|
@ -88,18 +83,125 @@ frappe.listview_settings["Recorder"] = {
|
|||
},
|
||||
|
||||
setup_recorder_controls(listview) {
|
||||
let me = this;
|
||||
listview.page.set_primary_action(listview.enabled ? __("Stop") : __("Start"), () => {
|
||||
frappe.call({
|
||||
method: listview.enabled ? "frappe.recorder.stop" : "frappe.recorder.start",
|
||||
callback: function () {
|
||||
listview.refresh();
|
||||
},
|
||||
});
|
||||
listview.enabled = !listview.enabled;
|
||||
this.refresh_controls(listview);
|
||||
if (listview.enabled) {
|
||||
me.stop_recorder(listview);
|
||||
} else {
|
||||
me.start_recorder(listview);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
stop_recorder(listview) {
|
||||
let me = this;
|
||||
frappe.xcall("frappe.recorder.stop", {}).then(() => {
|
||||
listview.refresh();
|
||||
listview.enabled = false;
|
||||
me.refresh_controls(listview);
|
||||
});
|
||||
},
|
||||
|
||||
start_recorder(listview) {
|
||||
let me = this;
|
||||
frappe.prompt(
|
||||
[
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
fieldname: "req_job_section",
|
||||
},
|
||||
{
|
||||
fieldtype: "Column Break",
|
||||
fieldname: "web_request_columns",
|
||||
label: "Web Requests",
|
||||
},
|
||||
{
|
||||
fieldname: "record_requests",
|
||||
fieldtype: "Check",
|
||||
label: "Record Web Requests",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "request_filter",
|
||||
fieldtype: "Data",
|
||||
label: "Request path filter",
|
||||
default: "/",
|
||||
depends_on: "record_requests",
|
||||
description: `This will be used for filtering paths which will be recorded.
|
||||
You can use this to avoid slowing down other traffic.
|
||||
e.g. <code>/api/method/erpnext</code>. Leave it empty to record every request.`,
|
||||
},
|
||||
{
|
||||
fieldtype: "Column Break",
|
||||
fieldname: "background_col",
|
||||
label: "Background Jobs",
|
||||
},
|
||||
|
||||
{
|
||||
fieldname: "record_jobs",
|
||||
fieldtype: "Check",
|
||||
label: "Record Background Jobs",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "jobs_filter",
|
||||
fieldtype: "Data",
|
||||
label: "Background Jobs filter",
|
||||
default: "",
|
||||
depends_on: "record_jobs",
|
||||
description: `This will be used for filtering jobs which will be recorded.
|
||||
You can use this to avoid slowing down other jobs. e.g. <code>email_queue.pull</code>.
|
||||
Leave it empty to record every job.`,
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
fieldname: "sql_section",
|
||||
label: "SQL",
|
||||
},
|
||||
{
|
||||
fieldname: "record_sql",
|
||||
fieldtype: "Check",
|
||||
label: "Record SQL queries",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "explain",
|
||||
fieldtype: "Check",
|
||||
label: "Generate EXPLAIN for SQL queries",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "capture_stack",
|
||||
fieldtype: "Check",
|
||||
label: "Capture callstack of SQL queries",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
fieldname: "python_section",
|
||||
label: "Python",
|
||||
},
|
||||
{
|
||||
fieldname: "profile",
|
||||
fieldtype: "Check",
|
||||
label: "Run cProfile",
|
||||
default: 0,
|
||||
description:
|
||||
"Warning: cProfile adds a lot of overhead. For best results, disable stack capturing when using cProfile.",
|
||||
},
|
||||
],
|
||||
(values) => {
|
||||
frappe.xcall("frappe.recorder.start", values).then(() => {
|
||||
listview.refresh();
|
||||
listview.enabled = true;
|
||||
me.refresh_controls(listview);
|
||||
});
|
||||
},
|
||||
__("Configure Recorder"),
|
||||
__("Start Recordig")
|
||||
);
|
||||
},
|
||||
|
||||
update_indicators(listview) {
|
||||
if (listview.enabled) {
|
||||
listview.page.set_indicator(__("Active"), "green");
|
||||
|
|
|
|||
|
|
@ -25,7 +25,11 @@ class RoleProfile(Document):
|
|||
self.name = self.role_profile
|
||||
|
||||
def on_update(self):
|
||||
self.queue_action("update_all_users", now=frappe.flags.in_test)
|
||||
self.queue_action(
|
||||
"update_all_users",
|
||||
now=frappe.flags.in_test or frappe.flags.in_install,
|
||||
enqueue_after_commit=True,
|
||||
)
|
||||
|
||||
def update_all_users(self):
|
||||
"""Changes in role_profile reflected across all its user"""
|
||||
|
|
|
|||
|
|
@ -238,7 +238,6 @@ frappe.qb.from_(todo).select(todo.name).where(todo.name == "{todo.name}").run()
|
|||
script.execute_method()
|
||||
|
||||
def test_server_script_rate_limiting(self):
|
||||
# why not
|
||||
script1 = frappe.get_doc(
|
||||
doctype="Server Script",
|
||||
name="rate_limited_server_script",
|
||||
|
|
|
|||
|
|
@ -94,7 +94,9 @@
|
|||
"dormant_days",
|
||||
"telemetry_section",
|
||||
"allow_error_traceback",
|
||||
"enable_telemetry"
|
||||
"enable_telemetry",
|
||||
"search_section",
|
||||
"link_field_results_limit"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -634,12 +636,24 @@
|
|||
{
|
||||
"fieldname": "column_break_uhqk",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "search_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Search"
|
||||
},
|
||||
{
|
||||
"default": "10",
|
||||
"fieldname": "link_field_results_limit",
|
||||
"fieldtype": "Int",
|
||||
"label": "Link Field Results Limit",
|
||||
"non_negative": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-08 15:52:37.525003",
|
||||
"modified": "2024-01-26 11:29:20.924425",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ class SystemSettings(Document):
|
|||
hide_footer_in_auto_email_reports: DF.Check
|
||||
language: DF.Link
|
||||
lifespan_qrcode_image: DF.Int
|
||||
link_field_results_limit: DF.Int
|
||||
login_with_email_link: DF.Check
|
||||
login_with_email_link_expiry: DF.Int
|
||||
logout_on_password_reset: DF.Check
|
||||
|
|
@ -94,6 +95,7 @@ class SystemSettings(Document):
|
|||
two_factor_method: DF.Literal["OTP App", "SMS", "Email"]
|
||||
welcome_email_template: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
from frappe.twofactor import toggle_two_factor_auth
|
||||
|
||||
|
|
@ -130,6 +132,16 @@ class SystemSettings(Document):
|
|||
self.validate_backup_limit()
|
||||
self.validate_file_extensions()
|
||||
|
||||
if not self.link_field_results_limit:
|
||||
self.link_field_results_limit = 10
|
||||
|
||||
if self.link_field_results_limit > 50:
|
||||
self.link_field_results_limit = 50
|
||||
label = _(self.meta.get_label("link_field_results_limit"))
|
||||
frappe.msgprint(
|
||||
_("{0} can not be more than {1}").format(label, 50), alert=True, indicator="yellow"
|
||||
)
|
||||
|
||||
def validate_user_pass_login(self):
|
||||
if not self.disable_user_pass_login:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -725,12 +725,8 @@ class User(Document):
|
|||
3. If allow_login_using_user_name is set, you can use username while finding the user.
|
||||
"""
|
||||
|
||||
login_with_mobile = cint(
|
||||
frappe.db.get_single_value("System Settings", "allow_login_using_mobile_number")
|
||||
)
|
||||
login_with_username = cint(
|
||||
frappe.db.get_single_value("System Settings", "allow_login_using_user_name")
|
||||
)
|
||||
login_with_mobile = cint(frappe.get_system_settings("allow_login_using_mobile_number"))
|
||||
login_with_username = cint(frappe.get_system_settings("allow_login_using_user_name"))
|
||||
|
||||
or_filters = [{"name": user_name}]
|
||||
if login_with_mobile:
|
||||
|
|
@ -840,8 +836,8 @@ def update_password(
|
|||
else:
|
||||
user = res["user"]
|
||||
|
||||
logout_all_sessions = cint(logout_all_sessions) or frappe.db.get_single_value(
|
||||
"System Settings", "logout_on_password_reset"
|
||||
logout_all_sessions = cint(logout_all_sessions) or frappe.get_system_settings(
|
||||
"logout_on_password_reset"
|
||||
)
|
||||
_update_password(user, new_password, logout_all_sessions=cint(logout_all_sessions))
|
||||
|
||||
|
|
@ -933,7 +929,7 @@ def _get_user_for_update_password(key, old_password):
|
|||
result.user, last_reset_password_key_generated_on = user or (None, None)
|
||||
if result.user:
|
||||
reset_password_link_expiry = cint(
|
||||
frappe.db.get_single_value("System Settings", "reset_password_link_expiry_duration")
|
||||
frappe.get_system_settings("reset_password_link_expiry_duration")
|
||||
)
|
||||
if (
|
||||
reset_password_link_expiry
|
||||
|
|
@ -1018,7 +1014,7 @@ def sign_up(email: str, full_name: str, redirect_to: str) -> tuple[int, str]:
|
|||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@rate_limit(limit=get_password_reset_limit, seconds=24 * 60 * 60)
|
||||
@rate_limit(limit=get_password_reset_limit, seconds=60 * 60)
|
||||
def reset_password(user: str) -> str:
|
||||
if user == "Administrator":
|
||||
return "not allowed"
|
||||
|
|
@ -1254,34 +1250,37 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False):
|
|||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
else:
|
||||
contact = frappe.get_doc("Contact", contact_name)
|
||||
contact.first_name = user.first_name
|
||||
contact.last_name = user.last_name
|
||||
contact.gender = user.gender
|
||||
try:
|
||||
contact = frappe.get_doc("Contact", contact_name)
|
||||
contact.first_name = user.first_name
|
||||
contact.last_name = user.last_name
|
||||
contact.gender = user.gender
|
||||
|
||||
# Add mobile number if phone does not exists in contact
|
||||
if user.phone and not any(new_contact.phone == user.phone for new_contact in contact.phone_nos):
|
||||
# Set primary phone if there is no primary phone number
|
||||
contact.add_phone(
|
||||
user.phone,
|
||||
is_primary_phone=not any(
|
||||
new_contact.is_primary_phone == 1 for new_contact in contact.phone_nos
|
||||
),
|
||||
)
|
||||
# Add mobile number if phone does not exists in contact
|
||||
if user.phone and not any(new_contact.phone == user.phone for new_contact in contact.phone_nos):
|
||||
# Set primary phone if there is no primary phone number
|
||||
contact.add_phone(
|
||||
user.phone,
|
||||
is_primary_phone=not any(
|
||||
new_contact.is_primary_phone == 1 for new_contact in contact.phone_nos
|
||||
),
|
||||
)
|
||||
|
||||
# Add mobile number if mobile does not exists in contact
|
||||
if user.mobile_no and not any(
|
||||
new_contact.phone == user.mobile_no for new_contact in contact.phone_nos
|
||||
):
|
||||
# Set primary mobile if there is no primary mobile number
|
||||
contact.add_phone(
|
||||
user.mobile_no,
|
||||
is_primary_mobile_no=not any(
|
||||
new_contact.is_primary_mobile_no == 1 for new_contact in contact.phone_nos
|
||||
),
|
||||
)
|
||||
# Add mobile number if mobile does not exists in contact
|
||||
if user.mobile_no and not any(
|
||||
new_contact.phone == user.mobile_no for new_contact in contact.phone_nos
|
||||
):
|
||||
# Set primary mobile if there is no primary mobile number
|
||||
contact.add_phone(
|
||||
user.mobile_no,
|
||||
is_primary_mobile_no=not any(
|
||||
new_contact.is_primary_mobile_no == 1 for new_contact in contact.phone_nos
|
||||
),
|
||||
)
|
||||
|
||||
contact.save(ignore_permissions=True)
|
||||
contact.save(ignore_permissions=True)
|
||||
except frappe.TimestampMismatchError:
|
||||
raise frappe.RetryBackgroundJobError
|
||||
|
||||
|
||||
def get_restricted_ip_list(user):
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ frappe.listview_settings["User Permission"] = {
|
|||
callback: function (r) {
|
||||
if (r.message === 1) {
|
||||
frappe.show_alert({
|
||||
message: __("User Permissions created sucessfully"),
|
||||
message: __("User Permissions created successfully"),
|
||||
indicator: "blue",
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -253,6 +253,10 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
if (!d.is_submittable && ["submit", "cancel", "amend"].includes(r)) return;
|
||||
if (d.in_create && ["create", "delete"].includes(r)) return;
|
||||
this.add_check(perm_container, d, r);
|
||||
|
||||
if (d.if_owner && r == "report") {
|
||||
perm_container.find("div[data-fieldname='report']").toggle(false);
|
||||
}
|
||||
});
|
||||
|
||||
// buttons
|
||||
|
|
@ -414,6 +418,13 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
chk.prop("checked", !chk.prop("checked"));
|
||||
} else {
|
||||
me.get_perm(args.role)[args.ptype] = args.value;
|
||||
|
||||
if (args.ptype == "if_owner") {
|
||||
let report_checkbox = chk
|
||||
.closest("div.row")
|
||||
.find("div[data-fieldname='report']");
|
||||
report_checkbox.toggle(!args.value);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -129,8 +129,15 @@ def update(
|
|||
frappe.clear_cache(doctype=doctype)
|
||||
|
||||
frappe.only_for("System Manager")
|
||||
|
||||
if ptype == "report" and value == "1" and if_owner == "1":
|
||||
frappe.throw(_("Cannot set 'Report' permission if 'Only If Creator' permission is set"))
|
||||
|
||||
out = update_permission_property(doctype, role, permlevel, ptype, value, if_owner=if_owner)
|
||||
|
||||
if ptype == "if_owner" and value == "1":
|
||||
update_permission_property(doctype, role, permlevel, "report", "0", if_owner=value)
|
||||
|
||||
frappe.db.after_commit.add(clear_cache)
|
||||
|
||||
return "refresh" if out else None
|
||||
|
|
|
|||
|
|
@ -13,21 +13,71 @@
|
|||
"label": "Build",
|
||||
"links": [
|
||||
{
|
||||
"description": "Customize properties, naming, fields and more for standard doctypes",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Models",
|
||||
"link_count": 0,
|
||||
"label": "Customization",
|
||||
"link_count": 4,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "DocType",
|
||||
"label": "Customize Form",
|
||||
"link_count": 0,
|
||||
"link_to": "DocType",
|
||||
"link_to": "Customize Form",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Custom Field",
|
||||
"link_count": 0,
|
||||
"link_to": "Custom Field",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Custom Translation",
|
||||
"link_count": 0,
|
||||
"link_to": "Translation",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Navbar Settings",
|
||||
"link_count": 0,
|
||||
"link_to": "Navbar Settings",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"description": "Group your custom doctypes under modules",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Modules",
|
||||
"link_count": 2,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Module Def",
|
||||
"link_count": 0,
|
||||
"link_to": "Module Def",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
|
|
@ -36,22 +86,112 @@
|
|||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Workflow",
|
||||
"label": "Module Onboarding",
|
||||
"link_count": 0,
|
||||
"link_to": "Workflow",
|
||||
"link_to": "Module Onboarding",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"description": "Monitor logs for errors, background jobs, communications, and user activity",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "System Logs",
|
||||
"link_count": 5,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Background Jobs",
|
||||
"link_count": 0,
|
||||
"link_to": "RQ Job",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Scheduled Jobs Logs",
|
||||
"link_count": 0,
|
||||
"link_to": "Scheduled Job Log",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Error Logs",
|
||||
"link_count": 0,
|
||||
"link_to": "Error Log",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Communication Logs",
|
||||
"link_count": 0,
|
||||
"link_to": "Communication",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Activity Log",
|
||||
"link_count": 0,
|
||||
"link_to": "Activity Log",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"description": "Packages are lightweight apps (collection of Module Defs) that can be created, imported, or released right from the UI",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Packages",
|
||||
"link_count": 2,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Package",
|
||||
"link_count": 0,
|
||||
"link_to": "Package",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Package Import",
|
||||
"link_count": 0,
|
||||
"link_to": "Package Import",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"description": "Automate processes and extend standard functionality using scripts and background jobs",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Scripting",
|
||||
"link_count": 0,
|
||||
"link_count": 3,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
|
|
@ -88,38 +228,12 @@
|
|||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Packages",
|
||||
"link_count": 2,
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Package",
|
||||
"link_count": 0,
|
||||
"link_to": "Package",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Package Import",
|
||||
"link_count": 0,
|
||||
"link_to": "Package Import",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"description": "Build your own reports, print formats, and dashboards. Create personalized workspaces for easier navigation",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Views",
|
||||
"link_count": 5,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
|
|
@ -177,115 +291,10 @@
|
|||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"description": "Create new forms and views with doctypes. Set up multi-level workflows for approval",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Customization",
|
||||
"link_count": 4,
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Customize Form",
|
||||
"link_count": 0,
|
||||
"link_to": "Customize Form",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Custom Field",
|
||||
"link_count": 0,
|
||||
"link_to": "Custom Field",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Custom Translation",
|
||||
"link_count": 0,
|
||||
"link_to": "Translation",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Navbar Settings",
|
||||
"link_count": 0,
|
||||
"link_to": "Navbar Settings",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "System Logs",
|
||||
"link_count": 5,
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Background Jobs",
|
||||
"link_count": 0,
|
||||
"link_to": "RQ Job",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Scheduled Jobs Logs",
|
||||
"link_count": 0,
|
||||
"link_to": "Scheduled Job Log",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Error Logs",
|
||||
"link_count": 0,
|
||||
"link_to": "Error Log",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Communication Logs",
|
||||
"link_count": 0,
|
||||
"link_to": "Communication",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Activity Log",
|
||||
"link_count": 0,
|
||||
"link_to": "Activity Log",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Modules",
|
||||
"label": "Models",
|
||||
"link_count": 2,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
|
|
@ -294,9 +303,9 @@
|
|||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Module Def",
|
||||
"label": "DocType",
|
||||
"link_count": 0,
|
||||
"link_to": "Module Def",
|
||||
"link_to": "DocType",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
|
|
@ -305,16 +314,16 @@
|
|||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Module Onboarding",
|
||||
"label": "Workflow",
|
||||
"link_count": 0,
|
||||
"link_to": "Module Onboarding",
|
||||
"link_to": "Workflow",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2024-01-02 15:38:42.806824",
|
||||
"modified": "2024-01-23 17:27:44.769958",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Build",
|
||||
|
|
@ -325,7 +334,7 @@
|
|||
"quick_lists": [],
|
||||
"restrict_to_domain": "",
|
||||
"roles": [],
|
||||
"sequence_id": 16.0,
|
||||
"sequence_id": 27.0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"color": "Grey",
|
||||
|
|
|
|||
|
|
@ -112,27 +112,30 @@ frappe.ui.form.on("Custom Field", {
|
|||
}
|
||||
},
|
||||
add_rename_field(frm) {
|
||||
frm.add_custom_button(__("Rename Fieldname"), () => {
|
||||
frappe.prompt(
|
||||
{
|
||||
fieldtype: "Data",
|
||||
label: __("Fieldname"),
|
||||
fieldname: "fieldname",
|
||||
reqd: 1,
|
||||
},
|
||||
function (data) {
|
||||
frappe.call({
|
||||
method: "frappe.custom.doctype.custom_field.custom_field.rename_fieldname",
|
||||
args: {
|
||||
custom_field: frm.doc.name,
|
||||
fieldname: data.fieldname,
|
||||
},
|
||||
});
|
||||
},
|
||||
__("Rename Fieldname"),
|
||||
__("Rename")
|
||||
);
|
||||
});
|
||||
if (!frm.is_new()) {
|
||||
frm.add_custom_button(__("Rename Fieldname"), () => {
|
||||
frappe.prompt(
|
||||
{
|
||||
fieldtype: "Data",
|
||||
label: __("Fieldname"),
|
||||
fieldname: "fieldname",
|
||||
reqd: 1,
|
||||
default: frm.doc.fieldname,
|
||||
},
|
||||
function (data) {
|
||||
frappe.call({
|
||||
method: "frappe.custom.doctype.custom_field.custom_field.rename_fieldname",
|
||||
args: {
|
||||
custom_field: frm.doc.name,
|
||||
fieldname: data.fieldname,
|
||||
},
|
||||
});
|
||||
},
|
||||
__("Rename Fieldname"),
|
||||
__("Rename")
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.is_virtual",
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List View"
|
||||
|
|
@ -483,7 +484,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-08 15:52:37.525003",
|
||||
"modified": "2024-02-01 15:56:39.171633",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -110,4 +110,5 @@ class CustomizeFormField(Document):
|
|||
unique: DF.Check
|
||||
width: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -23,17 +23,17 @@ def setup_database(force, verbose=None, no_mariadb_socket=False):
|
|||
)
|
||||
|
||||
|
||||
def bootstrap_database(db_name, verbose=None, source_sql=None):
|
||||
def bootstrap_database(verbose=None, source_sql=None):
|
||||
import frappe
|
||||
|
||||
if frappe.conf.db_type == "postgres":
|
||||
import frappe.database.postgres.setup_db
|
||||
|
||||
return frappe.database.postgres.setup_db.bootstrap_database(db_name, verbose, source_sql)
|
||||
return frappe.database.postgres.setup_db.bootstrap_database(verbose, source_sql)
|
||||
else:
|
||||
import frappe.database.mariadb.setup_db
|
||||
|
||||
return frappe.database.mariadb.setup_db.bootstrap_database(db_name, verbose, source_sql)
|
||||
return frappe.database.mariadb.setup_db.bootstrap_database(verbose, source_sql)
|
||||
|
||||
|
||||
def drop_user_and_database(db_name, db_user):
|
||||
|
|
@ -49,17 +49,19 @@ def drop_user_and_database(db_name, db_user):
|
|||
return frappe.database.mariadb.setup_db.drop_user_and_database(db_name, db_user)
|
||||
|
||||
|
||||
def get_db(host=None, user=None, password=None, port=None):
|
||||
def get_db(host=None, user=None, password=None, port=None, cur_db_name=None):
|
||||
import frappe
|
||||
|
||||
if frappe.conf.db_type == "postgres":
|
||||
import frappe.database.postgres.database
|
||||
|
||||
return frappe.database.postgres.database.PostgresDatabase(host, user, password, port=port)
|
||||
return frappe.database.postgres.database.PostgresDatabase(
|
||||
host, user, password, port, cur_db_name
|
||||
)
|
||||
else:
|
||||
import frappe.database.mariadb.database
|
||||
|
||||
return frappe.database.mariadb.database.MariaDBDatabase(host, user, password, port=port)
|
||||
return frappe.database.mariadb.database.MariaDBDatabase(host, user, password, port, cur_db_name)
|
||||
|
||||
|
||||
def get_command(
|
||||
|
|
@ -73,12 +75,7 @@ def get_command(
|
|||
else:
|
||||
bin, bin_name = which("psql"), "psql"
|
||||
|
||||
host = frappe.utils.esc(host, "$ ")
|
||||
user = frappe.utils.esc(user, "$ ")
|
||||
db_name = frappe.utils.esc(db_name, "$ ")
|
||||
|
||||
if password:
|
||||
password = frappe.utils.esc(password, "$ ")
|
||||
conn_string = f"postgresql://{user}:{password}@{host}:{port}/{db_name}"
|
||||
else:
|
||||
conn_string = f"postgresql://{user}@{host}:{port}/{db_name}"
|
||||
|
|
@ -94,10 +91,6 @@ def get_command(
|
|||
else:
|
||||
bin, bin_name = which("mariadb") or which("mysql"), "mariadb"
|
||||
|
||||
host = frappe.utils.esc(host, "$ ")
|
||||
user = frappe.utils.esc(user, "$ ")
|
||||
db_name = frappe.utils.esc(db_name, "$ ")
|
||||
|
||||
command = [
|
||||
f"--user={user}",
|
||||
f"--host={host}",
|
||||
|
|
@ -105,7 +98,6 @@ def get_command(
|
|||
]
|
||||
|
||||
if password:
|
||||
password = frappe.utils.esc(password, "$ ")
|
||||
command.append(f"--password={password}")
|
||||
|
||||
if dump:
|
||||
|
|
|
|||
|
|
@ -73,27 +73,20 @@ class Database:
|
|||
host=None,
|
||||
user=None,
|
||||
password=None,
|
||||
ac_name=None,
|
||||
use_default=0,
|
||||
port=None,
|
||||
cur_db_name=None,
|
||||
):
|
||||
self.setup_type_map()
|
||||
self.host = host or frappe.conf.db_host
|
||||
self.port = port or frappe.conf.db_port
|
||||
self.user = user or frappe.conf.db_user or frappe.conf.db_name
|
||||
self.cur_db_name = frappe.conf.db_name
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.cur_db_name = cur_db_name
|
||||
self._conn = None
|
||||
|
||||
if ac_name:
|
||||
self.user = ac_name or frappe.conf.db_name
|
||||
|
||||
if use_default:
|
||||
self.user = frappe.conf.db_name
|
||||
|
||||
self.transaction_writes = 0
|
||||
self.auto_commit_on_many_writes = 0
|
||||
|
||||
self.password = password or frappe.conf.db_password
|
||||
self.value_cache = {}
|
||||
self.logger = frappe.logger("database")
|
||||
self.logger.setLevel("WARNING")
|
||||
|
|
|
|||
|
|
@ -16,21 +16,7 @@ class DbManager:
|
|||
def create_user(self, user, password, host=None):
|
||||
host = host or self.get_current_host()
|
||||
password_predicate = f" IDENTIFIED BY '{password}'" if password else ""
|
||||
self.db.sql(f"CREATE USER '{user}'@'{host}'{password_predicate}")
|
||||
|
||||
def does_user_exist(self, username: str, host: str | None = None) -> bool:
|
||||
return (
|
||||
self.db.sql(
|
||||
f"SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = '{username}' and "
|
||||
f"host = '{host or self.get_current_host()}')"
|
||||
)[0][0]
|
||||
== 1
|
||||
)
|
||||
|
||||
def set_user_password(self, username: str, password: str, host: str | None = None) -> None:
|
||||
self.db.sql(
|
||||
f"SET PASSWORD FOR '{username}'@'{host or self.get_current_host()}' = PASSWORD('{password}')"
|
||||
)
|
||||
self.db.sql(f"CREATE USER IF NOT EXISTS '{user}'@'{host}'{password_predicate}")
|
||||
|
||||
def delete_user(self, target, host=None):
|
||||
host = host or self.get_current_host()
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class MariaDBConnectionUtil:
|
|||
"use_unicode": True,
|
||||
}
|
||||
|
||||
if self.user not in (frappe.flags.root_login, "root"):
|
||||
if self.cur_db_name:
|
||||
conn_settings["database"] = self.cur_db_name
|
||||
|
||||
if self.port:
|
||||
|
|
@ -383,6 +383,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
|||
WHERE Column_name = "{fieldname}"
|
||||
AND Seq_in_index = 1
|
||||
AND Non_unique={int(not unique)}
|
||||
AND Index_type != 'FULLTEXT'
|
||||
""",
|
||||
as_dict=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,15 +34,9 @@ def setup_database(force, verbose, no_mariadb_socket=False):
|
|||
if no_mariadb_socket:
|
||||
dbman_kwargs["host"] = "%"
|
||||
|
||||
if dbman.does_user_exist(db_user):
|
||||
print("User exists", db_user)
|
||||
dbman.set_user_password(db_user, frappe.conf.db_password, **dbman_kwargs)
|
||||
if verbose:
|
||||
print("Re-used existing user %s" % db_user)
|
||||
else:
|
||||
dbman.create_user(db_user, frappe.conf.db_password, **dbman_kwargs)
|
||||
if verbose:
|
||||
print("Created user %s" % db_user)
|
||||
dbman.create_user(db_user, frappe.conf.db_password, **dbman_kwargs)
|
||||
if verbose:
|
||||
print(f"Created or updated user {db_user}")
|
||||
|
||||
if force or (db_name not in dbman.get_database_list()):
|
||||
dbman.drop_database(db_name)
|
||||
|
|
@ -73,17 +67,17 @@ def drop_user_and_database(
|
|||
dbman.delete_user(db_user)
|
||||
|
||||
|
||||
def bootstrap_database(db_name, verbose, source_sql=None):
|
||||
def bootstrap_database(verbose, source_sql=None):
|
||||
import sys
|
||||
|
||||
frappe.connect(db_name=db_name)
|
||||
frappe.connect()
|
||||
if not check_database_settings():
|
||||
print("Database settings do not match expected values; stopping database setup.")
|
||||
sys.exit(1)
|
||||
|
||||
import_db_from_sql(source_sql, verbose)
|
||||
frappe.connect(db_name=db_name)
|
||||
|
||||
frappe.connect()
|
||||
if "tabDefaultValue" not in frappe.db.get_tables(cached=False):
|
||||
from click import secho
|
||||
|
||||
|
|
@ -179,6 +173,7 @@ def get_root_connection():
|
|||
port=frappe.conf.db_port,
|
||||
user=frappe.flags.root_login,
|
||||
password=frappe.flags.root_password,
|
||||
cur_db_name=None,
|
||||
)
|
||||
|
||||
return frappe.local.flags.root_connection
|
||||
|
|
|
|||
|
|
@ -161,12 +161,11 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
|
|||
|
||||
def get_connection(self):
|
||||
conn_settings = {
|
||||
"dbname": self.cur_db_name,
|
||||
"user": self.user,
|
||||
"host": self.host,
|
||||
"password": self.password,
|
||||
}
|
||||
if self.user not in (frappe.flags.root_login, "root"):
|
||||
conn_settings["dbname"] = self.cur_db_name
|
||||
if self.port:
|
||||
conn_settings["port"] = self.port
|
||||
|
||||
|
|
|
|||
|
|
@ -29,11 +29,12 @@ def setup_database():
|
|||
root_conn.close()
|
||||
|
||||
|
||||
def bootstrap_database(db_name, verbose, source_sql=None):
|
||||
frappe.connect(db_name=db_name)
|
||||
import_db_from_sql(source_sql, verbose)
|
||||
frappe.connect(db_name=db_name)
|
||||
def bootstrap_database(verbose, source_sql=None):
|
||||
|
||||
frappe.connect()
|
||||
import_db_from_sql(source_sql, verbose)
|
||||
|
||||
frappe.connect()
|
||||
if "tabDefaultValue" not in frappe.db.get_tables():
|
||||
import sys
|
||||
|
||||
|
|
@ -80,6 +81,7 @@ def get_root_connection():
|
|||
port=frappe.conf.db_port,
|
||||
user=frappe.flags.root_login,
|
||||
password=frappe.flags.root_password,
|
||||
cur_db_name=frappe.flags.root_login,
|
||||
)
|
||||
|
||||
return frappe.local.flags.root_connection
|
||||
|
|
|
|||
|
|
@ -44,9 +44,13 @@ frappe.ui.form.on("Event", {
|
|||
|
||||
const [ends_on_date] = frm.doc.ends_on
|
||||
? frm.doc.ends_on.split(" ")
|
||||
: frm.doc.starts_on.split(" ");
|
||||
: frm.doc.starts_on?.split(" ") || [];
|
||||
|
||||
if (frm.doc.google_meet_link && frappe.datetime.now_date() <= ends_on_date) {
|
||||
if (
|
||||
ends_on_date &&
|
||||
frm.doc.google_meet_link &&
|
||||
frappe.datetime.now_date() <= ends_on_date
|
||||
) {
|
||||
frm.dashboard.set_headline(
|
||||
__("Join video conference with {0}", [
|
||||
`<a target='_blank' href='${frm.doc.google_meet_link}'>Google Meet</a>`,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ class Note(Document):
|
|||
# expire this notification in a week (default)
|
||||
self.expire_notification_on = frappe.utils.add_days(self.creation, 7)
|
||||
|
||||
if not self.public and self.notify_on_login:
|
||||
self.notify_on_login = 0
|
||||
|
||||
if not self.content:
|
||||
self.content = "<span></span>"
|
||||
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ class Workspace(Document):
|
|||
"label": card.get("label"),
|
||||
"type": "Card Break",
|
||||
"icon": card.get("icon"),
|
||||
"description": card.get("description"),
|
||||
"hidden": card.get("hidden") or False,
|
||||
"link_count": card.get("link_count"),
|
||||
"idx": 1 if not self.links else self.links[-1].idx + 1,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"type",
|
||||
"label",
|
||||
"icon",
|
||||
"description",
|
||||
"hidden",
|
||||
"link_details_section",
|
||||
"link_type",
|
||||
|
|
@ -107,12 +108,20 @@
|
|||
"fieldtype": "Int",
|
||||
"hidden": 1,
|
||||
"label": "Link Count"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.type == \"Card Break\"",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "HTML Editor",
|
||||
"ignore_xss_filter": 1,
|
||||
"label": "Description",
|
||||
"max_height": "7rem"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-01 11:23:28.990593",
|
||||
"modified": "2024-01-23 17:39:16.833318",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Workspace Link",
|
||||
|
|
@ -121,5 +130,6 @@
|
|||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ class WorkspaceLink(Document):
|
|||
from frappe.types import DF
|
||||
|
||||
dependencies: DF.Data | None
|
||||
description: DF.HTMLEditor | None
|
||||
hidden: DF.Check
|
||||
icon: DF.Data | None
|
||||
is_query_report: DF.Check
|
||||
|
|
@ -29,4 +30,5 @@ class WorkspaceLink(Document):
|
|||
parenttype: DF.Data
|
||||
type: DF.Literal["Link", "Card Break"]
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -11,10 +11,3 @@ class TestForm(FrappeTestCase):
|
|||
results = get_linked_docs("Role", "System Manager", linkinfo=get_linked_doctypes("Role"))
|
||||
self.assertTrue("User" in results)
|
||||
self.assertTrue("DocType" in results)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import unittest
|
||||
|
||||
frappe.connect()
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ def get_notifications():
|
|||
"open_count_doctype": {},
|
||||
"targets": {},
|
||||
}
|
||||
if frappe.flags.in_install or not frappe.db.get_single_value("System Settings", "setup_complete"):
|
||||
if frappe.flags.in_install or not frappe.get_system_settings("setup_complete"):
|
||||
return out
|
||||
|
||||
config = get_notification_config()
|
||||
|
|
|
|||
|
|
@ -196,6 +196,9 @@ def update_system_settings(args): # nosemgrep
|
|||
|
||||
def create_or_update_user(args): # nosemgrep
|
||||
email = args.get("email")
|
||||
if not email:
|
||||
return
|
||||
|
||||
first_name, last_name = args.get("full_name", ""), ""
|
||||
if " " in first_name:
|
||||
first_name, last_name = first_name.split(" ", 1)
|
||||
|
|
|
|||
|
|
@ -291,9 +291,9 @@ def get_prepared_report_result(report, filters, dn="", user=None):
|
|||
try:
|
||||
if data := json.loads(doc.get_prepared_data().decode("utf-8")):
|
||||
report_data = get_report_data(doc, data)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
doc.log_error("Prepared report render failed")
|
||||
frappe.msgprint(_("Prepared report render failed"))
|
||||
frappe.msgprint(_("Prepared report render failed") + f": {str(e)}")
|
||||
doc = None
|
||||
|
||||
return report_data | {"prepared_report": True, "doc": doc}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"to_date_field",
|
||||
"column_break_17",
|
||||
"dynamic_date_period",
|
||||
"use_first_day_of_period",
|
||||
"email_settings",
|
||||
"email_to",
|
||||
"day_of_week",
|
||||
|
|
@ -87,6 +88,7 @@
|
|||
},
|
||||
{
|
||||
"default": "100",
|
||||
"depends_on": "eval:doc.report_type=='Report Builder'",
|
||||
"fieldname": "no_of_rows",
|
||||
"fieldtype": "Int",
|
||||
"label": "No of Rows (Max 500)"
|
||||
|
|
@ -207,10 +209,18 @@
|
|||
"fieldtype": "Link",
|
||||
"label": "Sender",
|
||||
"options": "Email Account"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.dynamic_date_period != 'Daily'",
|
||||
"description": "To begin the date range at the start of the chosen period. For example, if 'Year' is selected as the period, the report will start from January 1st of the current year.",
|
||||
"fieldname": "use_first_day_of_period",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use First Day of Period"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2022-09-08 15:31:55.031023",
|
||||
"modified": "2024-02-04 13:31:08.624648",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Auto Email Report",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# License: MIT. See LICENSE
|
||||
|
||||
import calendar
|
||||
import datetime
|
||||
from datetime import timedelta
|
||||
from email.utils import formataddr
|
||||
|
||||
|
|
@ -14,8 +15,13 @@ from frappe.utils import (
|
|||
add_to_date,
|
||||
cint,
|
||||
format_time,
|
||||
get_first_day,
|
||||
get_first_day_of_week,
|
||||
get_link_to_form,
|
||||
get_quarter_start,
|
||||
get_url_to_report,
|
||||
get_year_start,
|
||||
getdate,
|
||||
global_date_format,
|
||||
now,
|
||||
now_datetime,
|
||||
|
|
@ -57,8 +63,10 @@ class AutoEmailReport(Document):
|
|||
send_if_data: DF.Check
|
||||
sender: DF.Link | None
|
||||
to_date_field: DF.Literal
|
||||
use_first_day_of_period: DF.Check
|
||||
user: DF.Link
|
||||
# end: auto-generated types
|
||||
|
||||
def autoname(self):
|
||||
self.name = _(self.report)
|
||||
if frappe.db.exists("Auto Email Report", self.name):
|
||||
|
|
@ -92,7 +100,7 @@ class AutoEmailReport(Document):
|
|||
|
||||
max_reports_per_user = (
|
||||
cint(frappe.local.conf.max_reports_per_user) # kept for backward compatibilty
|
||||
or cint(frappe.db.get_single_value("System Settings", "max_auto_email_report_per_user"))
|
||||
or cint(frappe.get_system_settings("max_auto_email_report_per_user"))
|
||||
or 20
|
||||
)
|
||||
|
||||
|
|
@ -207,17 +215,37 @@ class AutoEmailReport(Document):
|
|||
self.filters = frappe.parse_json(self.filters)
|
||||
|
||||
to_date = today()
|
||||
from_date_value = {
|
||||
"Daily": ("days", -1),
|
||||
"Weekly": ("weeks", -1),
|
||||
"Monthly": ("months", -1),
|
||||
"Quarterly": ("months", -3),
|
||||
"Half Yearly": ("months", -6),
|
||||
"Yearly": ("years", -1),
|
||||
}[self.dynamic_date_period]
|
||||
|
||||
from_date = add_to_date(to_date, **{from_date_value[0]: from_date_value[1]})
|
||||
if self.use_first_day_of_period:
|
||||
from_date = to_date
|
||||
if self.dynamic_date_period == "Daily":
|
||||
from_date = add_to_date(to_date, days=-1)
|
||||
elif self.dynamic_date_period == "Weekly":
|
||||
from_date = get_first_day_of_week(from_date)
|
||||
elif self.dynamic_date_period == "Monthly":
|
||||
from_date = get_first_day(from_date)
|
||||
elif self.dynamic_date_period == "Quarterly":
|
||||
from_date = get_quarter_start(from_date)
|
||||
elif self.dynamic_date_period == "Half Yearly":
|
||||
from_date = get_half_year_start(from_date)
|
||||
elif self.dynamic_date_period == "Yearly":
|
||||
from_date = get_year_start(from_date)
|
||||
|
||||
self.set_date_filters(from_date, to_date)
|
||||
else:
|
||||
from_date_value = {
|
||||
"Daily": ("days", -1),
|
||||
"Weekly": ("weeks", -1),
|
||||
"Monthly": ("months", -1),
|
||||
"Quarterly": ("months", -3),
|
||||
"Half Yearly": ("months", -6),
|
||||
"Yearly": ("years", -1),
|
||||
}[self.dynamic_date_period]
|
||||
|
||||
from_date = add_to_date(to_date, **{from_date_value[0]: from_date_value[1]})
|
||||
self.set_date_filters(from_date, to_date)
|
||||
|
||||
def set_date_filters(self, from_date, to_date):
|
||||
self.filters[self.from_date_field] = from_date
|
||||
self.filters[self.to_date_field] = to_date
|
||||
|
||||
|
|
@ -332,3 +360,23 @@ def update_field_types(columns):
|
|||
col.fieldtype = "Data"
|
||||
col.options = ""
|
||||
return columns
|
||||
|
||||
|
||||
DATE_FORMAT = "%Y-%m-%d"
|
||||
|
||||
|
||||
def get_half_year_start(as_str=False):
|
||||
"""
|
||||
Returns the first day of the current half-year based on the current date.
|
||||
"""
|
||||
today_date = getdate(today())
|
||||
|
||||
half_year = 1 if today_date.month <= 6 else 2
|
||||
|
||||
year = today_date.year if half_year == 1 else today_date.year + 1
|
||||
month = 1 if half_year == 1 else 7
|
||||
day = 1
|
||||
|
||||
result_date = datetime.date(year, month, day)
|
||||
|
||||
return result_date if not as_str else result_date.strftime(DATE_FORMAT)
|
||||
|
|
|
|||
|
|
@ -131,12 +131,12 @@ class EmailQueue(Document):
|
|||
def attachments_list(self):
|
||||
return json.loads(self.attachments) if self.attachments else []
|
||||
|
||||
def get_email_account(self):
|
||||
def get_email_account(self, raise_error=False):
|
||||
if self.email_account:
|
||||
return frappe.get_cached_doc("Email Account", self.email_account)
|
||||
|
||||
return EmailAccount.find_outgoing(
|
||||
match_by_email=self.sender, match_by_doctype=self.reference_doctype
|
||||
match_by_email=self.sender, match_by_doctype=self.reference_doctype, _raise_error=raise_error
|
||||
)
|
||||
|
||||
def is_to_be_sent(self):
|
||||
|
|
@ -158,6 +158,7 @@ class EmailQueue(Document):
|
|||
return
|
||||
|
||||
with SendMailContext(self, smtp_server_instance) as ctx:
|
||||
ctx.fetch_smtp_server()
|
||||
message = None
|
||||
for recipient in self.recipients:
|
||||
if recipient.is_mail_sent():
|
||||
|
|
@ -233,14 +234,16 @@ class SendMailContext:
|
|||
smtp_server_instance: SMTPServer = None,
|
||||
):
|
||||
self.queue_doc: EmailQueue = queue_doc
|
||||
self.email_account_doc = queue_doc.get_email_account()
|
||||
|
||||
self.smtp_server: SMTPServer = smtp_server_instance or self.email_account_doc.get_smtp_server()
|
||||
|
||||
self.smtp_server: SMTPServer = smtp_server_instance
|
||||
self.sent_to_atleast_one_recipient = any(
|
||||
rec.recipient for rec in self.queue_doc.recipients if rec.is_mail_sent()
|
||||
)
|
||||
|
||||
def fetch_smtp_server(self):
|
||||
self.email_account_doc = self.queue_doc.get_email_account(raise_error=True)
|
||||
if not self.smtp_server:
|
||||
self.smtp_server = self.email_account_doc.get_smtp_server()
|
||||
|
||||
def __enter__(self):
|
||||
self.queue_doc.update_status(status="Sending", commit=True)
|
||||
return self
|
||||
|
|
@ -733,7 +736,7 @@ class QueueBuilder:
|
|||
recipients = list(set([r] + self.final_cc() + self.bcc))
|
||||
q = EmailQueue.new({**queue_data, **{"recipients": recipients}}, ignore_permissions=True)
|
||||
if not smtp_server_instance:
|
||||
email_account = q.get_email_account()
|
||||
email_account = q.get_email_account(raise_error=True)
|
||||
smtp_server_instance = email_account.get_smtp_server()
|
||||
|
||||
with suppress(Exception):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"allow_guest_to_view": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2013-01-10 16:34:31",
|
||||
"description": "Create and Send Newsletters",
|
||||
"description": "Create and send emails to a specific group of subscribers periodically.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"engine": "InnoDB",
|
||||
|
|
@ -244,8 +244,7 @@
|
|||
"fieldname": "campaign",
|
||||
"fieldtype": "Link",
|
||||
"label": "Campaign",
|
||||
"options": "Marketing Campaign",
|
||||
"reqd": 0
|
||||
"options": "Marketing Campaign"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
|
|
@ -254,7 +253,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"modified": "2023-12-29 18:04:13.270523",
|
||||
"modified": "2024-01-30 14:05:50.645802",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Newsletter",
|
||||
|
|
|
|||
|
|
@ -673,7 +673,7 @@ class InboundMail(Email):
|
|||
content = self.content
|
||||
for file in attachments:
|
||||
if file.name in self.cid_map and self.cid_map[file.name]:
|
||||
content = content.replace(f"cid:{self.cid_map[file.name]}", file.file_url)
|
||||
content = content.replace(f"cid:{self.cid_map[file.name]}", file.unique_url)
|
||||
return content
|
||||
|
||||
def is_notification(self):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"allow_rename": 1,
|
||||
"autoname": "field:currency_name",
|
||||
"creation": "2013-01-28 10:06:02",
|
||||
"description": "**Currency** Master",
|
||||
"description": "Currency list stores the currency value, its symbol and fraction unit",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-17 15:37:31.605278",
|
||||
"modified": "2024-01-30 13:18:12.053557",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Geo",
|
||||
"name": "Currency",
|
||||
|
|
|
|||
26
frappe/gettext/extractors/html_template.py
Normal file
26
frappe/gettext/extractors/html_template.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
from jinja2.ext import babel_extract
|
||||
|
||||
from .utils import extract_messages_from_code
|
||||
|
||||
|
||||
def extract(*args, **kwargs):
|
||||
"""Extract messages from Jinja and JS microtemplates.
|
||||
|
||||
Reuse the babel_extract function from jinja2.ext, but handle our own implementation of `_()`.
|
||||
To handle JS microtemplates, parse all code again using regex."""
|
||||
fileobj = args[0] or kwargs["fileobj"]
|
||||
print(fileobj.name)
|
||||
code = fileobj.read().decode("utf-8")
|
||||
|
||||
for lineno, funcname, messages, comments in babel_extract(*args, **kwargs):
|
||||
if funcname == "_" and isinstance(messages, tuple) and len(messages) > 1:
|
||||
funcname = "pgettext"
|
||||
messages = (messages[-1], messages[0]) # (context, message)
|
||||
|
||||
yield lineno, funcname, messages, comments
|
||||
|
||||
for lineno, message, context in extract_messages_from_code(code):
|
||||
if context:
|
||||
yield lineno, "pgettext", (context, message), []
|
||||
else:
|
||||
yield lineno, "_", message, []
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
from jinja2.ext import babel_extract
|
||||
|
||||
|
||||
def extract(*args, **kwargs):
|
||||
"""Reuse the babel_extract function from jinja2.ext, but handle our own implementation of `_()`"""
|
||||
for lineno, funcname, messages, comments in babel_extract(*args, **kwargs):
|
||||
if funcname == "_" and isinstance(messages, tuple) and len(messages) > 1:
|
||||
funcname = "pgettext"
|
||||
messages = (messages[-1], messages[0]) # (context, message)
|
||||
|
||||
yield lineno, funcname, messages, comments
|
||||
81
frappe/gettext/extractors/utils.py
Normal file
81
frappe/gettext/extractors/utils.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import re
|
||||
|
||||
import frappe
|
||||
|
||||
TRANSLATE_PATTERN = re.compile(
|
||||
r"_\(\s*" # starts with literal `_(`, ignore following whitespace/newlines
|
||||
# BEGIN: message search
|
||||
r"([\"']{,3})" # start of message string identifier - allows: ', ", """, '''; 1st capture group
|
||||
r"(?P<message>((?!\1).)*)" # Keep matching until string closing identifier is met which is same as 1st capture group
|
||||
r"\1" # match exact string closing identifier
|
||||
# END: message search
|
||||
# BEGIN: python context search
|
||||
r"(\s*,\s*context\s*=\s*" # capture `context=` with ignoring whitespace
|
||||
r"([\"'])" # start of context string identifier; 5th capture group
|
||||
r"(?P<py_context>((?!\5).)*)" # capture context string till closing id is found
|
||||
r"\5" # match context string closure
|
||||
r")?" # match 0 or 1 context strings
|
||||
# END: python context search
|
||||
# BEGIN: JS context search
|
||||
r"(\s*,\s*(.)*?\s*(,\s*" # skip message format replacements: ["format", ...] | null | []
|
||||
r"([\"'])" # start of context string; 11th capture group
|
||||
r"(?P<js_context>((?!\11).)*)" # capture context string till closing id is found
|
||||
r"\11" # match context string closure
|
||||
r")*"
|
||||
r")*" # match one or more context string
|
||||
# END: JS context search
|
||||
r"\s*\)" # Closing function call ignore leading whitespace/newlines
|
||||
)
|
||||
|
||||
|
||||
def extract_messages_from_code(code):
|
||||
"""
|
||||
Extracts translatable strings from a code file
|
||||
:param code: code from which translatable files are to be extracted
|
||||
"""
|
||||
from jinja2 import TemplateError
|
||||
|
||||
from frappe.model.utils import InvalidIncludePath, render_include
|
||||
|
||||
try:
|
||||
code = frappe.as_unicode(render_include(code))
|
||||
|
||||
# Exception will occur when it encounters John Resig's microtemplating code
|
||||
except (TemplateError, ImportError, InvalidIncludePath, OSError) as e:
|
||||
if isinstance(e, InvalidIncludePath) and hasattr(frappe.local, "message_log"):
|
||||
frappe.clear_last_message()
|
||||
|
||||
messages = []
|
||||
|
||||
for m in TRANSLATE_PATTERN.finditer(code):
|
||||
message = m.group("message")
|
||||
context = m.group("py_context") or m.group("js_context")
|
||||
pos = m.start()
|
||||
|
||||
if is_translatable(message):
|
||||
messages.append([pos, message, context])
|
||||
|
||||
return add_line_number(messages, code)
|
||||
|
||||
|
||||
def is_translatable(m):
|
||||
return bool(
|
||||
re.search("[a-zA-Z]", m)
|
||||
and not m.startswith("fa fa-")
|
||||
and not m.endswith("px")
|
||||
and not m.startswith("eval:")
|
||||
)
|
||||
|
||||
|
||||
def add_line_number(messages, code):
|
||||
ret = []
|
||||
messages = sorted(messages, key=lambda x: x[0])
|
||||
newlines = [m.start() for m in re.compile(r"\n").finditer(code)]
|
||||
line = 1
|
||||
newline_i = 0
|
||||
for pos, message, context in messages:
|
||||
while newline_i < len(newlines) and pos > newlines[newline_i]:
|
||||
line += 1
|
||||
newline_i += 1
|
||||
ret.append([line, message, context])
|
||||
return ret
|
||||
|
|
@ -250,7 +250,7 @@ def check_write_permission(doctype: str = None, name: str = None):
|
|||
if doctype and name:
|
||||
try:
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
doc.has_permission("write")
|
||||
doc.check_permission("write")
|
||||
except frappe.DoesNotExistError:
|
||||
# doc has not been inserted yet, name is set to "new-some-doctype"
|
||||
check_doctype = True
|
||||
|
|
|
|||
|
|
@ -532,15 +532,15 @@ standard_help_items = [
|
|||
|
||||
# log doctype cleanups to automatically add in log settings
|
||||
default_log_clearing_doctypes = {
|
||||
"Error Log": 30,
|
||||
"Activity Log": 90,
|
||||
"Error Log": 14,
|
||||
"Email Queue": 30,
|
||||
"Scheduled Job Log": 90,
|
||||
"Route History": 90,
|
||||
"Submission Queue": 30,
|
||||
"Prepared Report": 30,
|
||||
"Scheduled Job Log": 7,
|
||||
"Submission Queue": 7,
|
||||
"Prepared Report": 14,
|
||||
"Webhook Request Log": 30,
|
||||
"Integration Request": 90,
|
||||
"Unhandled Email": 30,
|
||||
"Reminder": 30,
|
||||
"Integration Request": 90,
|
||||
"Activity Log": 90,
|
||||
"Route History": 90,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,10 @@ from frappe.utils.dashboard import sync_dashboards
|
|||
from frappe.utils.synchronization import filelock
|
||||
|
||||
|
||||
def _is_scheduler_enabled() -> bool:
|
||||
def _is_scheduler_enabled(site) -> bool:
|
||||
enable_scheduler = False
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
enable_scheduler = cint(frappe.db.get_single_value("System Settings", "enable_scheduler"))
|
||||
except Exception:
|
||||
|
|
@ -78,7 +79,7 @@ def _new_site(
|
|||
|
||||
try:
|
||||
# enable scheduler post install?
|
||||
enable_scheduler = _is_scheduler_enabled()
|
||||
enable_scheduler = _is_scheduler_enabled(site)
|
||||
except Exception:
|
||||
enable_scheduler = False
|
||||
|
||||
|
|
@ -170,7 +171,6 @@ def install_db(
|
|||
setup_database(force, verbose, no_mariadb_socket)
|
||||
|
||||
bootstrap_database(
|
||||
db_name=frappe.conf.db_name,
|
||||
verbose=verbose,
|
||||
source_sql=source_sql,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ def check_google_calendar(account, google_calendar):
|
|||
# If no Calendar ID create a new Calendar
|
||||
calendar = {
|
||||
"summary": account.calendar_name,
|
||||
"timeZone": frappe.db.get_single_value("System Settings", "time_zone"),
|
||||
"timeZone": frappe.get_system_settings("time_zone"),
|
||||
}
|
||||
created_calendar = google_calendar.calendars().insert(body=calendar).execute()
|
||||
frappe.db.set_value(
|
||||
|
|
|
|||
|
|
@ -13,13 +13,14 @@ frappe.ui.form.on("Google Drive", {
|
|||
|
||||
frappe.realtime.on("upload_to_google_drive", (data) => {
|
||||
if (data.progress) {
|
||||
const progress_title = __("Uploading to Google Drive");
|
||||
frm.dashboard.show_progress(
|
||||
"Uploading to Google Drive",
|
||||
progress_title,
|
||||
(data.progress / data.total) * 100,
|
||||
__("{0}", [data.message])
|
||||
data.message
|
||||
);
|
||||
if (data.progress === data.total) {
|
||||
frm.dashboard.hide_progress("Uploading to Google Drive");
|
||||
frm.dashboard.hide_progress(progress_title);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ def upload_system_backup_to_google_drive():
|
|||
validate_file_size()
|
||||
|
||||
if frappe.flags.create_new_backup:
|
||||
set_progress(1, "Backing up Data.")
|
||||
set_progress(1, _("Backing up Data."))
|
||||
backup = new_backup()
|
||||
file_urls = []
|
||||
file_urls.append(backup.backup_path_db)
|
||||
|
|
@ -196,12 +196,12 @@ def upload_system_backup_to_google_drive():
|
|||
frappe.throw(_("Google Drive - Could not locate - {0}").format(e))
|
||||
|
||||
try:
|
||||
set_progress(2, "Uploading backup to Google Drive.")
|
||||
set_progress(2, _("Uploading backup to Google Drive."))
|
||||
google_drive.files().create(body=file_metadata, media_body=media, fields="id").execute()
|
||||
except HttpError as e:
|
||||
send_email(False, "Google Drive", "Google Drive", "email", error_status=e)
|
||||
|
||||
set_progress(3, "Uploading successful.")
|
||||
set_progress(3, _("Uploading successful."))
|
||||
frappe.db.set_single_value("Google Drive", "last_backup_on", frappe.utils.now_datetime())
|
||||
send_email(True, "Google Drive", "Google Drive", "email")
|
||||
return _("Google Drive Backup Successful.")
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ class LDAP_TestCase:
|
|||
for user_role in updated_user_roles: # match each users role mapped to ldap groups
|
||||
self.assertTrue(
|
||||
role_to_group_map[user_role] in test_user_data[test_user],
|
||||
f"during sync_roles(), the user was given role {user_role} which should not have occured",
|
||||
f"during sync_roles(), the user was given role {user_role} which should not have occurred",
|
||||
)
|
||||
|
||||
@mock_ldap_connection
|
||||
|
|
|
|||
|
|
@ -41,9 +41,6 @@ def send_email(success, service_name, doctype, email_field, error_status=None):
|
|||
|
||||
|
||||
def get_recipients(doctype, email_field):
|
||||
if not frappe.db:
|
||||
frappe.connect()
|
||||
|
||||
return split_emails(frappe.db.get_value(doctype, None, email_field))
|
||||
|
||||
|
||||
|
|
|
|||
1401
frappe/locale/de.po
1401
frappe/locale/de.po
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -134,22 +134,22 @@ log_types = (
|
|||
)
|
||||
|
||||
std_fields = [
|
||||
{"fieldname": "name", "fieldtype": "Link", "label": _lt("ID")},
|
||||
{"fieldname": "owner", "fieldtype": "Link", "label": _lt("Created By"), "options": "User"},
|
||||
{"fieldname": "idx", "fieldtype": "Int", "label": _lt("Index")},
|
||||
{"fieldname": "creation", "fieldtype": "Datetime", "label": _lt("Created On")},
|
||||
{"fieldname": "modified", "fieldtype": "Datetime", "label": _lt("Last Updated On")},
|
||||
{"fieldname": "name", "fieldtype": "Link", "label": "ID"},
|
||||
{"fieldname": "owner", "fieldtype": "Link", "label": "Created By", "options": "User"},
|
||||
{"fieldname": "idx", "fieldtype": "Int", "label": "Index"},
|
||||
{"fieldname": "creation", "fieldtype": "Datetime", "label": "Created On"},
|
||||
{"fieldname": "modified", "fieldtype": "Datetime", "label": "Last Updated On"},
|
||||
{
|
||||
"fieldname": "modified_by",
|
||||
"fieldtype": "Link",
|
||||
"label": _lt("Last Updated By"),
|
||||
"label": "Last Updated By",
|
||||
"options": "User",
|
||||
},
|
||||
{"fieldname": "_user_tags", "fieldtype": "Data", "label": _lt("Tags")},
|
||||
{"fieldname": "_liked_by", "fieldtype": "Data", "label": _lt("Liked By")},
|
||||
{"fieldname": "_comments", "fieldtype": "Text", "label": _lt("Comments")},
|
||||
{"fieldname": "_assign", "fieldtype": "Text", "label": _lt("Assigned To")},
|
||||
{"fieldname": "docstatus", "fieldtype": "Int", "label": _lt("Document Status")},
|
||||
{"fieldname": "_user_tags", "fieldtype": "Data", "label": "Tags"},
|
||||
{"fieldname": "_liked_by", "fieldtype": "Data", "label": "Liked By"},
|
||||
{"fieldname": "_comments", "fieldtype": "Text", "label": "Comments"},
|
||||
{"fieldname": "_assign", "fieldtype": "Text", "label": "Assigned To"},
|
||||
{"fieldname": "docstatus", "fieldtype": "Int", "label": "Document Status"},
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -230,6 +230,9 @@ def get_permitted_fields(
|
|||
if permission_type is None:
|
||||
permission_type = "select" if frappe.only_has_select_perm(doctype, user=user) else "read"
|
||||
|
||||
meta_fields = meta.default_fields.copy()
|
||||
optional_meta_fields = [x for x in optional_fields if x in valid_columns]
|
||||
|
||||
if permitted_fields := meta.get_permitted_fieldnames(
|
||||
parenttype=parenttype,
|
||||
user=user,
|
||||
|
|
@ -239,15 +242,12 @@ def get_permitted_fields(
|
|||
if permission_type == "select":
|
||||
return permitted_fields
|
||||
|
||||
meta_fields = meta.default_fields.copy()
|
||||
optional_meta_fields = [x for x in optional_fields if x in valid_columns]
|
||||
|
||||
if meta.istable:
|
||||
meta_fields.extend(child_table_fields)
|
||||
|
||||
return meta_fields + permitted_fields + optional_meta_fields
|
||||
|
||||
return []
|
||||
return meta_fields + optional_meta_fields
|
||||
|
||||
|
||||
def is_default_field(fieldname: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -217,6 +217,10 @@ class DatabaseQuery:
|
|||
args = self.prepare_args()
|
||||
args.limit = self.add_limit()
|
||||
|
||||
if not args.fields:
|
||||
# apply_fieldlevel_read_permissions has likely removed ALL the fields that user asked for
|
||||
return []
|
||||
|
||||
if args.conditions:
|
||||
args.conditions = "where " + args.conditions
|
||||
|
||||
|
|
@ -754,7 +758,7 @@ class DatabaseQuery:
|
|||
ref_doctype = field.options if field else f.doctype
|
||||
lft, rgt = "", ""
|
||||
if f.value:
|
||||
lft, rgt = frappe.db.get_value(ref_doctype, f.value, ["lft", "rgt"])
|
||||
lft, rgt = frappe.db.get_value(ref_doctype, f.value, ["lft", "rgt"]) or (0, 0)
|
||||
|
||||
# Get descendants elements of a DocType with a tree structure
|
||||
if f.operator.lower() in (
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ def delete_doc(
|
|||
doctype=doc.doctype,
|
||||
name=doc.name,
|
||||
now=frappe.flags.in_test,
|
||||
enqueue_after_commit=True,
|
||||
)
|
||||
|
||||
# clear cache for Document
|
||||
|
|
|
|||
|
|
@ -679,23 +679,15 @@ class Document(BaseDocument):
|
|||
return same
|
||||
|
||||
def apply_fieldlevel_read_permissions(self):
|
||||
"""Remove values the user is not allowed to read (called when loading in desk)"""
|
||||
|
||||
"""Remove values the user is not allowed to read."""
|
||||
if frappe.session.user == "Administrator":
|
||||
return
|
||||
|
||||
has_higher_permlevel = False
|
||||
|
||||
all_fields = self.meta.fields.copy()
|
||||
for table_field in self.meta.get_table_fields():
|
||||
all_fields += frappe.get_meta(table_field.options).fields or []
|
||||
|
||||
for df in all_fields:
|
||||
if df.permlevel > 0:
|
||||
has_higher_permlevel = True
|
||||
break
|
||||
|
||||
if not has_higher_permlevel:
|
||||
if all(df.permlevel == 0 for df in all_fields):
|
||||
return
|
||||
|
||||
has_access_to = self.get_permlevel_access("read")
|
||||
|
|
|
|||
|
|
@ -197,11 +197,7 @@ def map_fields(source_doc, target_doc, table_map, source_parent):
|
|||
for d in source_doc.meta.get("fields")
|
||||
if (d.no_copy == 1 or d.fieldtype in table_fields)
|
||||
]
|
||||
+ [
|
||||
d.fieldname
|
||||
for d in target_doc.meta.get("fields")
|
||||
if (d.no_copy == 1 or d.fieldtype in table_fields)
|
||||
]
|
||||
+ [d.fieldname for d in target_doc.meta.get("fields") if (d.fieldtype in table_fields)]
|
||||
+ list(default_fields)
|
||||
+ list(child_table_fields)
|
||||
+ list(table_map.get("field_no_map", []))
|
||||
|
|
|
|||
|
|
@ -596,6 +596,10 @@ class Meta(Document):
|
|||
self.get_permlevel_access(permission_type=permission_type, parenttype=parenttype, user=user)
|
||||
)
|
||||
|
||||
if 0 not in permlevel_access and permission_type in ("read", "select"):
|
||||
if frappe.share.get_shared(self.name, user, rights=[permission_type], limit=1):
|
||||
permlevel_access.add(0)
|
||||
|
||||
permitted_fieldnames.extend(
|
||||
df.fieldname
|
||||
for df in self.get_fieldnames_with_value(
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ def render_include(content):
|
|||
if "{% include" in content:
|
||||
paths = INCLUDE_DIRECTIVE_PATTERN.findall(content)
|
||||
if not paths:
|
||||
frappe.throw(_("Invalid include path"), InvalidIncludePath)
|
||||
raise InvalidIncludePath
|
||||
|
||||
for path in paths:
|
||||
app, app_path = path.split("/", 1)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,60 @@
|
|||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Letter Head", {
|
||||
setup(frm) {
|
||||
frm.get_field("instructions").html(INSTRUCTIONS);
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.flag_public_attachments = true;
|
||||
},
|
||||
|
||||
validate: (frm) => {
|
||||
["header_script", "footer_script"].forEach((field) => {
|
||||
if (!frm.doc[field]) return;
|
||||
|
||||
try {
|
||||
eval(frm.doc[field]);
|
||||
} catch (e) {
|
||||
frappe.throw({
|
||||
title: __("Error in Header/Footer Script"),
|
||||
indicator: "orange",
|
||||
message: '<pre class="small"><code>' + e.stack + "</code></pre>",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const INSTRUCTIONS = `<h4>${__("Letter Head Scripts")}</h4>
|
||||
<p>${__("Header/Footer scripts can be used to add dynamic behaviours.")}</p>
|
||||
<pre>
|
||||
<code>
|
||||
// ${__(
|
||||
"The following Header Script will add the current date to an element in 'Header HTML' with class 'header-content'"
|
||||
)}
|
||||
var el = document.getElementsByClassName("header-content");
|
||||
if (el.length > 0) {
|
||||
el[0].textContent += " " + new Date().toGMTString();
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>${__("You can also access wkhtmltopdf variables (valid only in PDF print):")}</p>
|
||||
<pre>
|
||||
<code>
|
||||
// ${__("Get Header and Footer wkhtmltopdf variables")}
|
||||
// ${__("Snippet and more variables: {0}", ["https://wkhtmltopdf.org/usage/wkhtmltopdf.txt"])}
|
||||
var vars = {};
|
||||
var query_strings_from_url = document.location.search.substring(1).split('&');
|
||||
for (var query_string in query_strings_from_url) {
|
||||
if (query_strings_from_url.hasOwnProperty(query_string)) {
|
||||
var temp_var = query_strings_from_url[query_string].split('=', 2);
|
||||
vars[temp_var[0]] = decodeURI(temp_var[1]);
|
||||
}
|
||||
}
|
||||
var el = document.getElementsByClassName("header-content");
|
||||
if (el.length > 0 && vars["page"] == 1) {
|
||||
el[0].textContent += " : " + vars["date"];
|
||||
}
|
||||
</code>
|
||||
</pre>`;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@
|
|||
"footer_image",
|
||||
"footer_image_height",
|
||||
"footer_image_width",
|
||||
"footer_align"
|
||||
"footer_align",
|
||||
"scripts_section",
|
||||
"header_script",
|
||||
"footer_script",
|
||||
"instructions"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -162,13 +166,40 @@
|
|||
"fieldtype": "Select",
|
||||
"label": "Footer Based On",
|
||||
"options": "Image\nHTML"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal && doc.source==='HTML'",
|
||||
"fieldname": "header_script",
|
||||
"fieldtype": "Code",
|
||||
"label": "Header Script",
|
||||
"options": "Javascript"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal && doc.footer_source==='HTML'",
|
||||
"fieldname": "footer_script",
|
||||
"fieldtype": "Code",
|
||||
"label": "Footer Script",
|
||||
"options": "Javascript"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.header_script || doc.footer_script",
|
||||
"fieldname": "scripts_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Scripts"
|
||||
},
|
||||
{
|
||||
"fieldname": "instructions",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Instructions",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-font",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"max_attachments": 3,
|
||||
"modified": "2023-12-08 15:52:37.525003",
|
||||
"modified": "2023-12-21 16:19:37.525003",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Printing",
|
||||
"name": "Letter Head",
|
||||
|
|
|
|||
|
|
@ -384,13 +384,13 @@ frappe.ui.form.PrintView = class {
|
|||
this.print_wrapper.find(".preview-beta-wrapper").hide();
|
||||
this.print_wrapper.find(".print-preview-wrapper").show();
|
||||
|
||||
const $print_format = this.print_wrapper.find("iframe");
|
||||
this.$print_format_body = $print_format.contents();
|
||||
this.get_print_html((out) => {
|
||||
if (!out.html) {
|
||||
out.html = this.get_no_preview_html();
|
||||
}
|
||||
|
||||
const $print_format = this.print_wrapper.find("iframe");
|
||||
this.$print_format_body = $print_format.contents();
|
||||
this.setup_print_format_dom(out, $print_format);
|
||||
|
||||
const print_height = $print_format.get(0).offsetHeight;
|
||||
|
|
|
|||
|
|
@ -121,9 +121,7 @@
|
|||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" id="icon-external-link">
|
||||
<path d="M9.75003 7.83333V9C9.75003 9.82843 9.07846 10.5 8.25003 10.5H3.25C2.42157 10.5 1.75 9.82843 1.75 9V4C1.75 3.17158 2.42151 2.50001 3.24993 2.50001C3.62327 2.5 4.02808 2.5 4.4167 2.5" stroke="var(--icon-stroke)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.75 1.5H10.25V4.5" stroke="var(--icon-stroke)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.75 5L9.75 2" stroke="var(--icon-stroke)" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" stroke-width="0" clip-rule="evenodd" d="M4.52409 7.47575C4.3889 7.34059 4.3889 7.12141 4.52409 6.98623L9.31795 2.19237H6.69231C6.50113 2.19237 6.34615 2.03739 6.34615 1.84622C6.34615 1.65505 6.50113 1.50007 6.69231 1.50007H10.1469C10.2283 1.49846 10.3104 1.52546 10.3764 1.5811C10.452 1.64459 10.5 1.7398 10.5 1.84622V5.30773C10.5 5.4989 10.345 5.65388 10.1538 5.65388C9.9627 5.65388 9.80769 5.4989 9.80769 5.30773V2.68173L5.01362 7.47575C4.87844 7.61095 4.65927 7.61095 4.52409 7.47575ZM2.19231 3.23082C2.19231 2.6573 2.65724 2.19237 3.23077 2.19237H4.47692C4.6681 2.19237 4.82308 2.03739 4.82308 1.84622C4.82308 1.65505 4.6681 1.50007 4.47692 1.50007H3.23077C2.27489 1.50007 1.5 2.27496 1.5 3.23082V8.76924C1.5 9.72511 2.27489 10.5 3.23077 10.5H8.76923C9.7251 10.5 10.5 9.72511 10.5 8.76924V7.5231C10.5 7.33193 10.345 7.17695 10.1538 7.17695C9.9627 7.17695 9.80769 7.33193 9.80769 7.5231V8.76924C9.80769 9.34275 9.34274 9.8077 8.76923 9.8077H3.23077C2.65724 9.8077 2.19231 9.34275 2.19231 8.76924V3.23082Z" fill="var(--icon-stroke)"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" fill="#112B42" id="icon-up">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
|
|
@ -185,6 +185,11 @@ function is_filter_applied() {
|
|||
}
|
||||
}
|
||||
|
||||
function open_child_doctype() {
|
||||
if (!props.field?.df?.options) return;
|
||||
window.open(`/app/doctype/${props.field.df.options}`, "_blank");
|
||||
}
|
||||
|
||||
onMounted(() => selected.value && label_input.value.focus_on_label());
|
||||
</script>
|
||||
|
||||
|
|
@ -199,6 +204,7 @@ onMounted(() => selected.value && label_input.value.focus_on_label());
|
|||
<component
|
||||
:is="component"
|
||||
:df="field.df"
|
||||
:is-customize-form="store.is_customize_form"
|
||||
:data-fieldname="field.df.fieldname"
|
||||
:data-fieldtype="field.df.fieldtype"
|
||||
>
|
||||
|
|
@ -216,7 +222,7 @@ onMounted(() => selected.value && label_input.value.focus_on_label());
|
|||
class="help-icon"
|
||||
v-if="field.df.documentation_url"
|
||||
v-html="frappe.utils.icon('help', 'sm')"
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
|
|
@ -227,7 +233,7 @@ onMounted(() => selected.value && label_input.value.focus_on_label());
|
|||
:class="is_filter_applied()"
|
||||
@click="edit_filters"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('filter', 'sm')"></div>
|
||||
<div v-html="frappe.utils.icon('filter', 'sm')" />
|
||||
</button>
|
||||
<AddFieldButton ref="add_field_ref" :column="column" :field="field">
|
||||
<div v-html="frappe.utils.icon('add', 'sm')" />
|
||||
|
|
@ -240,21 +246,29 @@ onMounted(() => selected.value && label_input.value.focus_on_label());
|
|||
"
|
||||
@click="move_fields_to_column"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('move', 'sm')"></div>
|
||||
<div v-html="frappe.utils.icon('move', 'sm')" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Duplicate field')"
|
||||
@click.stop="duplicate_field"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('duplicate', 'sm')"></div>
|
||||
<div v-html="frappe.utils.icon('duplicate', 'sm')" />
|
||||
</button>
|
||||
<button
|
||||
v-if="field.df.fieldtype === 'Table' && field.df.options"
|
||||
class="btn btn-xs btn-icon"
|
||||
@click="open_child_doctype"
|
||||
:title="__(`Edit ${field.df.options} Doctype`)"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('external-link', 'sm')" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-xs btn-icon"
|
||||
:title="__('Remove field')"
|
||||
@click.stop="remove_field"
|
||||
>
|
||||
<div v-html="frappe.utils.icon('remove', 'sm')"></div>
|
||||
<div v-html="frappe.utils.icon('remove', 'sm')" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { get_table_columns, load_doctype_model } from "../../utils";
|
||||
import { computedAsync } from "@vueuse/core";
|
||||
|
||||
const props = defineProps(["df"]);
|
||||
const props = defineProps(["df", "is-customize-form"]);
|
||||
|
||||
let table_columns = computedAsync(async () => {
|
||||
let doctype = props.df.options;
|
||||
|
|
@ -13,6 +13,13 @@ let table_columns = computedAsync(async () => {
|
|||
let child_doctype = frappe.get_meta(doctype);
|
||||
return get_table_columns(props.df, child_doctype);
|
||||
}, []);
|
||||
|
||||
function open_new_child_doctype_dialog() {
|
||||
let is_custom = props.isCustomizeForm;
|
||||
frappe.model.with_doctype("DocType").then(() => {
|
||||
frappe.listview_settings["DocType"].new_doctype_dialog({ is_child: 1, is_custom });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -46,7 +53,15 @@ let table_columns = computedAsync(async () => {
|
|||
:alt="__('Grid Empty State')"
|
||||
class="grid-empty-illustration"
|
||||
/>
|
||||
{{ __("No Data") }}
|
||||
<!-- render this button when there are no columns, which means that options is not added for the table -->
|
||||
<button
|
||||
class="btn btn-xs btn-secondary"
|
||||
@click="open_new_child_doctype_dialog"
|
||||
v-if="!table_columns.length"
|
||||
>
|
||||
{{ __("Create Child Doctype") }}
|
||||
</button>
|
||||
<p v-else>{{ __("No Data") }}</p>
|
||||
</div>
|
||||
|
||||
<!-- description -->
|
||||
|
|
|
|||
|
|
@ -85,8 +85,13 @@ export const useStore = defineStore("form-builder-store", () => {
|
|||
|
||||
async function fetch() {
|
||||
doc.value = frm.value.doc;
|
||||
if (doctype.value.startsWith("new-doctype-") && !doc.value.fields) {
|
||||
doc.value.fields = [get_df("Data", "", __("Title"))];
|
||||
if (doctype.value.startsWith("new-doctype-") && !doc.value.fields?.length) {
|
||||
frappe.model.with_doctype("DocType").then(() => {
|
||||
frappe.listview_settings["DocType"].new_doctype_dialog();
|
||||
});
|
||||
// redirect to /doctype
|
||||
frappe.set_route("List", "DocType");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!get_docfields.value.length) {
|
||||
|
|
|
|||
|
|
@ -19,11 +19,22 @@ frappe.require = function (items, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
frappe.assets = {
|
||||
check: function () {
|
||||
class AssetManager {
|
||||
constructor() {
|
||||
this._executed = [];
|
||||
this._handlers = {
|
||||
js: function (txt) {
|
||||
frappe.dom.eval(txt);
|
||||
},
|
||||
css: function (txt) {
|
||||
frappe.dom.set_style(txt);
|
||||
},
|
||||
};
|
||||
}
|
||||
check() {
|
||||
// if version is different then clear localstorage
|
||||
if (window._version_number != localStorage.getItem("_version_number")) {
|
||||
frappe.assets.clear_local_storage();
|
||||
this.clear_local_storage();
|
||||
console.log("Cleared App Cache.");
|
||||
}
|
||||
|
||||
|
|
@ -33,160 +44,79 @@ frappe.assets = {
|
|||
// Evict cache if page is reloaded within 10 seconds. Which could be user trying to
|
||||
// refresh if things feel broken.
|
||||
if ((not_updated_since < 5000 && is_reload()) || not_updated_since > 2 * 86400000) {
|
||||
frappe.assets.clear_local_storage();
|
||||
this.clear_local_storage();
|
||||
}
|
||||
} else {
|
||||
frappe.assets.clear_local_storage();
|
||||
this.clear_local_storage();
|
||||
}
|
||||
|
||||
frappe.assets.init_local_storage();
|
||||
},
|
||||
this.init_local_storage();
|
||||
}
|
||||
|
||||
init_local_storage: function () {
|
||||
init_local_storage() {
|
||||
localStorage._last_load = new Date();
|
||||
localStorage._version_number = window._version_number;
|
||||
if (frappe.boot) localStorage.metadata_version = frappe.boot.metadata_version;
|
||||
},
|
||||
}
|
||||
|
||||
clear_local_storage: function () {
|
||||
$.each(
|
||||
["_last_load", "_version_number", "metadata_version", "page_info", "last_visited"],
|
||||
function (i, key) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
clear_local_storage() {
|
||||
["_last_load", "_version_number", "metadata_version", "page_info", "last_visited"].forEach(
|
||||
(key) => localStorage.removeItem(key)
|
||||
);
|
||||
|
||||
// clear assets
|
||||
for (var key in localStorage) {
|
||||
for (let key in localStorage) {
|
||||
if (
|
||||
key.indexOf("desk_assets:") === 0 ||
|
||||
key.indexOf("_page:") === 0 ||
|
||||
key.indexOf("_doctype:") === 0 ||
|
||||
key.indexOf("preferred_breadcrumbs:") === 0
|
||||
key.startsWith("_page:") ||
|
||||
key.startsWith("_doctype:") ||
|
||||
key.startsWith("preferred_breadcrumbs:")
|
||||
) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
console.log("localStorage cleared");
|
||||
},
|
||||
}
|
||||
|
||||
// keep track of executed assets
|
||||
executed_: [],
|
||||
eval_assets(path, content) {
|
||||
if (!this._executed.includes(path)) {
|
||||
this._handlers[this.extn(path)](content);
|
||||
this._executed.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
// pass on to the handler to set
|
||||
execute: function (items, callback) {
|
||||
var to_fetch = [];
|
||||
for (var i = 0, l = items.length; i < l; i++) {
|
||||
if (!frappe.assets.exists(items[i])) {
|
||||
to_fetch.push(items[i]);
|
||||
}
|
||||
}
|
||||
if (to_fetch.length) {
|
||||
frappe.assets.fetch(to_fetch, function () {
|
||||
frappe.assets.eval_assets(items, callback);
|
||||
});
|
||||
} else {
|
||||
frappe.assets.eval_assets(items, callback);
|
||||
}
|
||||
},
|
||||
|
||||
eval_assets: function (items, callback) {
|
||||
for (var i = 0, l = items.length; i < l; i++) {
|
||||
// execute js/css if not already.
|
||||
var path = items[i];
|
||||
if (frappe.assets.executed_.indexOf(path) === -1) {
|
||||
// execute
|
||||
frappe.assets.handler[frappe.assets.extn(path)](frappe.assets.get(path), path);
|
||||
frappe.assets.executed_.push(path);
|
||||
}
|
||||
}
|
||||
callback && callback();
|
||||
},
|
||||
|
||||
// check if the asset exists in
|
||||
// localstorage
|
||||
exists: function (src) {
|
||||
if (frappe.assets.executed_.indexOf(src) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (frappe.boot.developer_mode) {
|
||||
return false;
|
||||
}
|
||||
if (frappe.assets.get(src)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// load an asset via
|
||||
fetch: function (items, callback) {
|
||||
execute(items, callback) {
|
||||
// this is virtual page load, only get the the source
|
||||
// *without* the template
|
||||
|
||||
if (items.length === 0) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
let me = this;
|
||||
|
||||
const version_string =
|
||||
frappe.boot.developer_mode || window.dev_server ? Date.now() : window._version_number;
|
||||
|
||||
async function fetch_item(item) {
|
||||
async function fetch_item(path) {
|
||||
// Add the version to the URL to bust the cache for non-bundled assets
|
||||
let url = new URL(item, window.location.origin);
|
||||
let url = new URL(path, window.location.origin);
|
||||
|
||||
if (!item.includes(".bundle.") && !url.searchParams.get("v")) {
|
||||
if (!path.includes(".bundle.") && !url.searchParams.get("v")) {
|
||||
url.searchParams.append("v", version_string);
|
||||
}
|
||||
const response = await fetch(url.toString());
|
||||
const body = await response.text();
|
||||
frappe.assets.add(item, body);
|
||||
me.eval_assets(path, body);
|
||||
}
|
||||
|
||||
frappe.dom.freeze();
|
||||
const fetch_promises = items.map(fetch_item);
|
||||
Promise.all(fetch_promises).then(() => {
|
||||
frappe.dom.unfreeze();
|
||||
callback();
|
||||
callback?.();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
add: function (src, txt) {
|
||||
if ("localStorage" in window) {
|
||||
try {
|
||||
frappe.assets.set(src, txt);
|
||||
} catch (e) {
|
||||
// if quota is exceeded, clear local storage and set item
|
||||
frappe.assets.clear_local_storage();
|
||||
frappe.assets.set(src, txt);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get: function (src) {
|
||||
return localStorage.getItem("desk_assets:" + src);
|
||||
},
|
||||
|
||||
set: function (src, txt) {
|
||||
localStorage.setItem("desk_assets:" + src, txt);
|
||||
},
|
||||
|
||||
extn: function (src) {
|
||||
extn(src) {
|
||||
if (src.indexOf("?") != -1) {
|
||||
src = src.split("?").slice(-1)[0];
|
||||
}
|
||||
return src.split(".").slice(-1)[0];
|
||||
},
|
||||
|
||||
handler: {
|
||||
js: function (txt, src) {
|
||||
frappe.dom.eval(txt);
|
||||
},
|
||||
css: function (txt, src) {
|
||||
frappe.dom.set_style(txt);
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
bundled_asset(path, is_rtl = null) {
|
||||
if (!path.startsWith("/assets") && path.includes(".bundle.")) {
|
||||
|
|
@ -197,8 +127,8 @@ frappe.assets = {
|
|||
return path;
|
||||
}
|
||||
return path;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function is_reload() {
|
||||
try {
|
||||
|
|
@ -211,3 +141,5 @@ function is_reload() {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
frappe.assets = new AssetManager();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ frappe.realtime.on("build_event", (data) => {
|
|||
if (parts.length === 2) {
|
||||
let filename = parts[0].split("/").slice(-1)[0];
|
||||
|
||||
frappe.assets.executed_ = frappe.assets.executed_.filter(
|
||||
frappe.assets._executed = frappe.assets._executed.filter(
|
||||
(asset) => !asset.includes(`${filename}.bundle`)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ frappe.data_import.ImportPreview = class ImportPreview {
|
|||
<div class="col-sm-12">
|
||||
<div class="table-actions margin-bottom">
|
||||
</div>
|
||||
<div class="table-preview border"></div>
|
||||
<div class="table-preview"></div>
|
||||
<div class="table-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ frappe.ui.form.ControlCode = class ControlCode extends frappe.ui.form.ControlTex
|
|||
JS: "ace/mode/javascript",
|
||||
Python: "ace/mode/python",
|
||||
Py: "ace/mode/python",
|
||||
PythonExpression: "ace/mode/python",
|
||||
HTML: "ace/mode/html",
|
||||
CSS: "ace/mode/css",
|
||||
Markdown: "ace/mode/markdown",
|
||||
|
|
|
|||
|
|
@ -87,10 +87,10 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
|
|||
return this.is_translatable() ? __(value) : value;
|
||||
}
|
||||
is_translatable() {
|
||||
return frappe.boot?.translated_doctypes || [].includes(this.get_options());
|
||||
return (frappe.boot?.translated_doctypes || []).includes(this.get_options());
|
||||
}
|
||||
is_title_link() {
|
||||
return frappe.boot?.link_title_doctypes || [].includes(this.get_options());
|
||||
return (frappe.boot?.link_title_doctypes || []).includes(this.get_options());
|
||||
}
|
||||
async set_link_title(value) {
|
||||
const doctype = this.get_options();
|
||||
|
|
@ -252,6 +252,7 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
|
|||
doctype: doctype,
|
||||
ignore_user_permissions: me.df.ignore_user_permissions,
|
||||
reference_doctype: me.get_reference_doctype() || "",
|
||||
page_length: cint(frappe.boot.sysdefaults.link_field_results_limit) || 10,
|
||||
};
|
||||
|
||||
me.set_custom_query(args);
|
||||
|
|
@ -476,7 +477,8 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
|
|||
filter[3].push("...");
|
||||
}
|
||||
|
||||
let value = filter[3] == null || filter[3] === "" ? __("empty") : String(filter[3]);
|
||||
let value =
|
||||
filter[3] == null || filter[3] === "" ? __("empty") : String(__(filter[3]));
|
||||
|
||||
return [__(label).bold(), __(filter[2]), value.bold()].join(" ");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -550,11 +550,34 @@ class FormTimeline extends BaseTimeline {
|
|||
title: communication_doc ? __("Reply") : null,
|
||||
last_email: communication_doc,
|
||||
subject: communication_doc && communication_doc.subject,
|
||||
reply_all: reply_all,
|
||||
};
|
||||
|
||||
if (communication_doc && reply_all) {
|
||||
args.cc = communication_doc.cc;
|
||||
args.bcc = communication_doc.bcc;
|
||||
const email_accounts = frappe.boot.email_accounts
|
||||
.filter((account) => {
|
||||
return (
|
||||
!["All Accounts", "Sent", "Spam", "Trash"].includes(account.email_account) &&
|
||||
account.enable_outgoing
|
||||
);
|
||||
})
|
||||
.map((e) => e.email_id);
|
||||
|
||||
if (communication_doc && args.is_a_reply) {
|
||||
args.cc = "";
|
||||
if (
|
||||
email_accounts.includes(frappe.session.user_email) &&
|
||||
communication_doc.sender != frappe.session.user_email
|
||||
) {
|
||||
// add recipients to cc if replying sender is different from last email
|
||||
const recipients = communication_doc.recipients.split(",").map((r) => r.trim());
|
||||
args.cc =
|
||||
recipients.filter((r) => r != frappe.session.user_email).join(", ") + ", ";
|
||||
}
|
||||
if (reply_all) {
|
||||
// if reply_all then add cc and bcc as well.
|
||||
args.cc += communication_doc.cc;
|
||||
args.bcc = communication_doc.bcc;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.frm.doctype === "Communication") {
|
||||
|
|
|
|||
|
|
@ -1463,20 +1463,20 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
// Remove actions from menu
|
||||
delete this.custom_buttons[label];
|
||||
let menu_item_label = group ? `${group} > ${label}` : label;
|
||||
let $linkBody = this.page
|
||||
.is_in_group_button_dropdown(
|
||||
this.page.menu,
|
||||
"li > a.grey-link > span",
|
||||
menu_item_label
|
||||
)
|
||||
?.parent()
|
||||
?.parent();
|
||||
let $btn = this.page.is_in_group_button_dropdown(
|
||||
this.page.menu,
|
||||
"li > a.grey-link > span",
|
||||
menu_item_label
|
||||
);
|
||||
|
||||
if ($linkBody) {
|
||||
// If last button, remove divider too
|
||||
let $divider = $linkBody.next(".dropdown-divider");
|
||||
if ($divider) $divider.remove();
|
||||
$linkBody.remove();
|
||||
if ($btn) {
|
||||
let $linkBody = $btn.parent().parent();
|
||||
if ($linkBody) {
|
||||
// If last button, remove divider too
|
||||
let $divider = $linkBody.next(".dropdown-divider");
|
||||
if ($divider) $divider.remove();
|
||||
$linkBody.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ frappe.form.formatters = {
|
|||
return __(frappe.form.formatters["Data"](value, df));
|
||||
},
|
||||
Float: function (value, docfield, options, doc) {
|
||||
if (value === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// don't allow 0 precision for Floats, hence or'ing with null
|
||||
var precision =
|
||||
docfield.precision ||
|
||||
|
|
@ -73,12 +77,20 @@ frappe.form.formatters = {
|
|||
}
|
||||
},
|
||||
Int: function (value, docfield, options) {
|
||||
if (value === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (cstr(docfield.options).trim() === "File Size") {
|
||||
return frappe.form.formatters.FileSize(value);
|
||||
}
|
||||
return frappe.form.formatters._right(value == null ? "" : cint(value), options);
|
||||
},
|
||||
Percent: function (value, docfield, options) {
|
||||
if (value === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const precision =
|
||||
docfield.precision ||
|
||||
cint(frappe.boot.sysdefaults && frappe.boot.sysdefaults.float_precision) ||
|
||||
|
|
@ -105,6 +117,10 @@ frappe.form.formatters = {
|
|||
</div>`;
|
||||
},
|
||||
Currency: function (value, docfield, options, doc) {
|
||||
if (value === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var currency = frappe.meta.get_field_currency(docfield, doc);
|
||||
|
||||
let precision;
|
||||
|
|
|
|||
|
|
@ -1012,15 +1012,17 @@ export default class Grid {
|
|||
|
||||
let user_settings = frappe.get_user_settings(this.frm.doctype, "GridView");
|
||||
if (user_settings && user_settings[this.doctype] && user_settings[this.doctype].length) {
|
||||
this.user_defined_columns = user_settings[this.doctype].map((row) => {
|
||||
let column = frappe.meta.get_docfield(this.doctype, row.fieldname);
|
||||
this.user_defined_columns = user_settings[this.doctype]
|
||||
.map((row) => {
|
||||
let column = frappe.meta.get_docfield(this.doctype, row.fieldname);
|
||||
|
||||
if (column) {
|
||||
column.in_list_view = 1;
|
||||
column.columns = row.columns;
|
||||
return column;
|
||||
}
|
||||
});
|
||||
if (column) {
|
||||
column.in_list_view = 1;
|
||||
column.columns = row.columns;
|
||||
return column;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -461,6 +461,7 @@ export default class GridRow {
|
|||
fieldname: "fields",
|
||||
options: docfields,
|
||||
columns: 2,
|
||||
sort_options: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -495,12 +496,31 @@ export default class GridRow {
|
|||
|
||||
const show_field = (f) => always_allow.includes(f) || !blocked_fields.includes(f);
|
||||
|
||||
// First, add selected fields
|
||||
selected_fields.forEach((selectedField) => {
|
||||
const selectedColumn = this.docfields.find(
|
||||
(column) => column.fieldname === selectedField
|
||||
);
|
||||
if (selectedColumn && !selectedColumn.hidden && show_field(selectedColumn.fieldtype)) {
|
||||
fields.push({
|
||||
label: selectedColumn.label,
|
||||
value: selectedColumn.fieldname,
|
||||
checked: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Then, add the rest of the fields
|
||||
this.docfields.forEach((column) => {
|
||||
if (!column.hidden && show_field(column.fieldtype)) {
|
||||
if (
|
||||
!selected_fields.includes(column.fieldname) &&
|
||||
!column.hidden &&
|
||||
show_field(column.fieldtype)
|
||||
) {
|
||||
fields.push({
|
||||
label: column.label,
|
||||
value: column.fieldname,
|
||||
checked: selected_fields ? selected_fields.includes(column.fieldname) : false,
|
||||
checked: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@
|
|||
<li>
|
||||
<span class="form-sidebar-items">
|
||||
<span>
|
||||
<svg class="es-icon ml-0 icon-sm"><use href="#es-line-star"></use></svg>
|
||||
<svg class="es-icon ml-0 icon-sm"><use href="#es-line-tag"></use></svg>
|
||||
<span class="tags-label ellipsis">{%= __("Tags") %}</span>
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue