Merge branch 'develop' into form-builder-vue3
This commit is contained in:
commit
4db133a493
15 changed files with 108 additions and 198 deletions
148
.github/workflows/server-mariadb-tests.yml
vendored
148
.github/workflows/server-mariadb-tests.yml
vendored
|
|
@ -1,148 +0,0 @@
|
|||
name: Server (MariaDB)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
|
||||
concurrency:
|
||||
group: server-mariadb-develop-${{ github.event_name }}-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
checkrun:
|
||||
name: Build Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
build: ${{ steps.check-build.outputs.build }}
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check if build should be run
|
||||
id: check-build
|
||||
run: |
|
||||
python "${GITHUB_WORKSPACE}/.github/helper/roulette.py"
|
||||
env:
|
||||
TYPE: "server"
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REPO_NAME: ${{ github.repository }}
|
||||
|
||||
test:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: checkrun
|
||||
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
|
||||
timeout-minutes: 60
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
container: [1, 2]
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:10.6
|
||||
env:
|
||||
MARIADB_ROOT_PASSWORD: travis
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
python -m compileall -q -f "${GITHUB_WORKSPACE}"
|
||||
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
||||
then echo "Found merge conflicts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
run: |
|
||||
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
|
||||
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
env:
|
||||
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
|
||||
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
|
||||
TYPE: server
|
||||
DB: mariadb
|
||||
|
||||
- name: Run Tests
|
||||
run: cd ~/frappe-bench/sites && ../env/bin/python3 ../apps/frappe/.github/helper/ci.py
|
||||
env:
|
||||
SITE: test_site
|
||||
CI_BUILD_ID: ${{ github.run_id }}
|
||||
BUILD_NUMBER: ${{ matrix.container }}
|
||||
TOTAL_BUILDS: 2
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-${{ matrix.container }}
|
||||
path: /home/runner/frappe-bench/sites/coverage.xml
|
||||
|
||||
coverage:
|
||||
name: Coverage Wrap Up
|
||||
needs: [test, checkrun]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
name: MariaDB
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
flags: server-mariadb
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
name: Server (Postgres)
|
||||
name: Server
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
|
@ -7,9 +7,10 @@ on:
|
|||
branches: [ develop ]
|
||||
|
||||
concurrency:
|
||||
group: server-postgres-develop-${{ github.event_name }}-${{ github.event.number }}
|
||||
group: server-develop-${{ github.event_name }}-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
|
@ -44,9 +45,18 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
db: ["mariadb", "postgres"]
|
||||
container: [1, 2]
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:10.6
|
||||
env:
|
||||
MARIADB_ROOT_PASSWORD: travis
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
postgres:
|
||||
image: postgres:12.4
|
||||
env:
|
||||
|
|
@ -78,7 +88,7 @@ jobs:
|
|||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: 16
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
|
|
@ -114,7 +124,7 @@ jobs:
|
|||
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
|
||||
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
|
||||
TYPE: server
|
||||
DB: postgres
|
||||
DB: ${{ matrix.db }}
|
||||
|
||||
- name: Run Tests
|
||||
run: cd ~/frappe-bench/sites && ../env/bin/python3 ../apps/frappe/.github/helper/ci.py
|
||||
|
|
@ -127,14 +137,14 @@ jobs:
|
|||
- name: Upload coverage data
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-${{ matrix.container }}
|
||||
name: coverage-${{ matrix.db }}-${{ matrix.container }}
|
||||
path: /home/runner/frappe-bench/sites/coverage.xml
|
||||
|
||||
coverage:
|
||||
name: Coverage Wrap Up
|
||||
needs: [test, checkrun]
|
||||
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v3
|
||||
|
|
@ -145,7 +155,7 @@ jobs:
|
|||
- name: Upload coverage data
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
name: Postgres
|
||||
name: Server
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
flags: server-postgres
|
||||
flags: server
|
||||
|
|
@ -9,33 +9,33 @@ from frappe.utils.jinja import validate_template
|
|||
|
||||
|
||||
class EmailTemplate(Document):
|
||||
@property
|
||||
def response_(self):
|
||||
return self.response_html if self.use_html else self.response
|
||||
|
||||
def validate(self):
|
||||
if self.use_html:
|
||||
validate_template(self.response_html)
|
||||
else:
|
||||
validate_template(self.response)
|
||||
validate_template(self.subject)
|
||||
validate_template(self.response_)
|
||||
|
||||
def get_formatted_subject(self, doc):
|
||||
return frappe.render_template(self.subject, doc)
|
||||
|
||||
def get_formatted_response(self, doc):
|
||||
if self.use_html:
|
||||
return frappe.render_template(self.response_html, doc)
|
||||
|
||||
return frappe.render_template(self.response, doc)
|
||||
return frappe.render_template(self.response_, doc)
|
||||
|
||||
def get_formatted_email(self, doc):
|
||||
if isinstance(doc, str):
|
||||
doc = json.loads(doc)
|
||||
|
||||
return {"subject": self.get_formatted_subject(doc), "message": self.get_formatted_response(doc)}
|
||||
return {
|
||||
"subject": self.get_formatted_subject(doc),
|
||||
"message": self.get_formatted_response(doc),
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_email_template(template_name, doc):
|
||||
"""Returns the processed HTML of a email template with the given doc"""
|
||||
if isinstance(doc, str):
|
||||
doc = json.loads(doc)
|
||||
|
||||
email_template = frappe.get_doc("Email Template", template_name)
|
||||
return email_template.get_formatted_email(doc)
|
||||
|
|
|
|||
|
|
@ -707,8 +707,10 @@ def has_child_permission(
|
|||
|
||||
parent_meta = frappe.get_meta(parent_doctype)
|
||||
|
||||
if parent_meta.istable or all(
|
||||
df.options != child_doctype for df in parent_meta.get_table_fields()
|
||||
if parent_meta.istable or not (
|
||||
valid_parentfields := [
|
||||
df.fieldname for df in parent_meta.get_table_fields() if df.options == child_doctype
|
||||
]
|
||||
):
|
||||
push_perm_check_log(
|
||||
_("{0} is not a valid parent DocType for {1}").format(
|
||||
|
|
@ -717,15 +719,30 @@ def has_child_permission(
|
|||
)
|
||||
return False
|
||||
|
||||
if (
|
||||
child_doc
|
||||
and (permlevel := parent_meta.get_field(child_doc.parentfield).permlevel) > 0
|
||||
and permlevel not in parent_meta.get_permlevel_access(ptype, user=user)
|
||||
):
|
||||
push_perm_check_log(
|
||||
_("Insufficient Permission Level for {0}").format(frappe.bold(parent_doctype))
|
||||
)
|
||||
return False
|
||||
if child_doc:
|
||||
parentfield = child_doc.parentfield
|
||||
if not parentfield:
|
||||
push_perm_check_log(
|
||||
_("Parentfield not specified in {0}: {1}").format(
|
||||
frappe.bold(child_doctype), frappe.bold(child_doc.name)
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
if parentfield not in valid_parentfields:
|
||||
push_perm_check_log(
|
||||
_("{0} is not a valid parentfield for {1}").format(
|
||||
frappe.bold(parentfield), frappe.bold(child_doctype)
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
permlevel = parent_meta.get_field(parentfield).permlevel
|
||||
if permlevel > 0 and permlevel not in parent_meta.get_permlevel_access(ptype, user=user):
|
||||
push_perm_check_log(
|
||||
_("Insufficient Permission Level for {0}").format(frappe.bold(parent_doctype))
|
||||
)
|
||||
return False
|
||||
|
||||
return has_permission(
|
||||
parent_doctype,
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ frappe.ui.form.ControlInput = class ControlInput extends frappe.ui.form.Control
|
|||
"title",
|
||||
__(
|
||||
"This value is fetched from {0}'s {1} field",
|
||||
me.df.fetch_from.split(".")
|
||||
me.df.fetch_from.split(".").map((value) => __(frappe.unscrub(value)))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,12 @@ frappe.ui.form.Share = class Share {
|
|||
this.parent.find(".share-doc-btn").hide();
|
||||
}
|
||||
|
||||
this.parent.find(".share-doc-btn").on("click", () => {
|
||||
this.frm.share_doc();
|
||||
});
|
||||
this.parent
|
||||
.find(".share-doc-btn")
|
||||
.off("click")
|
||||
.on("click", () => {
|
||||
this.frm.share_doc();
|
||||
});
|
||||
|
||||
this.shares.empty();
|
||||
|
||||
|
|
@ -41,6 +44,8 @@ frappe.ui.form.Share = class Share {
|
|||
this.dialog = d;
|
||||
this.dirty = false;
|
||||
|
||||
$(d.body).html('<p class="text-muted">' + __("Loading...") + "</p>");
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.share.get_users",
|
||||
args: {
|
||||
|
|
@ -52,8 +57,6 @@ frappe.ui.form.Share = class Share {
|
|||
},
|
||||
});
|
||||
|
||||
$(d.body).html('<p class="text-muted">' + __("Loading...") + "</p>");
|
||||
|
||||
d.onhide = function () {
|
||||
// reload comments
|
||||
if (me.dirty) me.frm.sidebar.reload_docinfo();
|
||||
|
|
@ -188,7 +191,6 @@ frappe.ui.form.Share = class Share {
|
|||
}
|
||||
|
||||
me.dirty = true;
|
||||
me.render_shared();
|
||||
me.frm.shared.refresh();
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@
|
|||
<button class="btn btn-secondary btn-default btn-sm hide"></button>
|
||||
<div class="actions-btn-group hide">
|
||||
<button type="button" class="btn btn-primary btn-sm" data-toggle="dropdown" aria-expanded="false">
|
||||
<span class="hidden-xs">
|
||||
<span class="actions-btn-group-label">{%= __("Actions") %}</span>
|
||||
<span>
|
||||
<span class="hidden-xs actions-btn-group-label">{%= __("Actions") %}</span>
|
||||
<svg class="icon icon-xs">
|
||||
<use href="#icon-select">
|
||||
</use>
|
||||
|
|
@ -80,4 +80,4 @@
|
|||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -104,9 +104,8 @@ def patch_query_execute():
|
|||
# frame1: execute_query()
|
||||
# frame2: frame that called `query.run()`
|
||||
#
|
||||
# if frame2 is server script it wont have a filename and hence
|
||||
# if frame2 is server script <serverscript> is set as the filename
|
||||
# it shouldn't be allowed.
|
||||
# p.s. stack() returns `"<unknown>"` as filename if not a file.
|
||||
pass
|
||||
else:
|
||||
raise frappe.PermissionError("Only SELECT SQL allowed in scripting")
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ def remove(doctype, name, user, flags=None):
|
|||
@frappe.whitelist()
|
||||
def set_permission(doctype, name, user, permission_to, value=1, everyone=0):
|
||||
"""Expose function without flags to the client-side"""
|
||||
set_docshare_permission(doctype, name, user, permission_to, value=value, everyone=everyone)
|
||||
return set_docshare_permission(doctype, name, user, permission_to, value=value, everyone=everyone)
|
||||
|
||||
|
||||
def set_docshare_permission(doctype, name, user, permission_to, value=1, everyone=0, flags=None):
|
||||
|
|
|
|||
|
|
@ -665,6 +665,16 @@ class TestPermissions(FrappeTestCase):
|
|||
doc = user.append("defaults")
|
||||
doc.check_permission()
|
||||
|
||||
# false due to missing parentfield
|
||||
doc = user.append("roles")
|
||||
doc.parentfield = None
|
||||
self.assertRaises(frappe.PermissionError, doc.check_permission)
|
||||
|
||||
# false due to invalid parentfield
|
||||
doc = user.append("roles")
|
||||
doc.parentfield = "first_name"
|
||||
self.assertRaises(frappe.PermissionError, doc.check_permission)
|
||||
|
||||
# false by permlevel
|
||||
doc = user.append("roles")
|
||||
self.assertRaises(frappe.PermissionError, doc.check_permission)
|
||||
|
|
|
|||
|
|
@ -75,3 +75,10 @@ class TestSafeExec(FrappeTestCase):
|
|||
def test_unsafe_objects(self):
|
||||
unsafe_global = {"frappe": frappe}
|
||||
self.assertRaises(SyntaxError, safe_exec, """frappe.msgprint("Hello")""", unsafe_global)
|
||||
|
||||
def test_attrdict(self):
|
||||
# jinja
|
||||
frappe.render_template("{% set my_dict = _dict() %} {{- my_dict.works -}}")
|
||||
|
||||
# RestrictedPython
|
||||
safe_exec("my_dict = _dict()")
|
||||
|
|
|
|||
|
|
@ -4828,3 +4828,4 @@ CSV Quoting,Anführungszeichen,
|
|||
CSV Preview,Vorschau,
|
||||
Non-numeric,Nicht-numerische,
|
||||
Minimal,Minimal,
|
||||
This value is fetched from {0}'s {1} field,Dieser Wert ergibt sich aus dem Feld {1} von {0},
|
||||
|
|
|
|||
|
|
|
@ -60,7 +60,7 @@ def validate_template(html):
|
|||
frappe.throw(frappe._("Syntax error in template"))
|
||||
|
||||
|
||||
def render_template(template, context, is_path=None, safe_render=True):
|
||||
def render_template(template, context=None, is_path=None, safe_render=True):
|
||||
"""Render a template using Jinja
|
||||
|
||||
:param template: path or HTML containing the jinja template
|
||||
|
|
@ -76,6 +76,9 @@ def render_template(template, context, is_path=None, safe_render=True):
|
|||
if not template:
|
||||
return ""
|
||||
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
if is_path or guess_is_path(template):
|
||||
return get_jenv().get_template(template).render(context)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from functools import lru_cache
|
|||
|
||||
import RestrictedPython.Guards
|
||||
from RestrictedPython import compile_restricted, safe_globals
|
||||
from RestrictedPython.transformer import RestrictingNodeTransformer
|
||||
|
||||
import frappe
|
||||
import frappe.exceptions
|
||||
|
|
@ -45,6 +46,14 @@ class NamespaceDict(frappe._dict):
|
|||
return ret
|
||||
|
||||
|
||||
class FrappeTransformer(RestrictingNodeTransformer):
|
||||
def check_name(self, node, name, *args, **kwargs):
|
||||
if name == "_dict":
|
||||
return
|
||||
|
||||
return super().check_name(node, name, *args, **kwargs)
|
||||
|
||||
|
||||
def safe_exec(script, _globals=None, _locals=None, restrict_commit_rollback=False):
|
||||
# server scripts can be disabled via site_config.json
|
||||
# they are enabled by default
|
||||
|
|
@ -69,7 +78,11 @@ def safe_exec(script, _globals=None, _locals=None, restrict_commit_rollback=Fals
|
|||
|
||||
with safe_exec_flags(), patched_qb():
|
||||
# execute script compiled by RestrictedPython
|
||||
exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used
|
||||
exec(
|
||||
compile_restricted(script, filename="<serverscript>", policy=FrappeTransformer),
|
||||
exec_globals,
|
||||
_locals,
|
||||
)
|
||||
|
||||
return exec_globals, _locals
|
||||
|
||||
|
|
@ -106,6 +119,7 @@ def get_safe_globals():
|
|||
as_json=frappe.as_json,
|
||||
dict=dict,
|
||||
log=frappe.log,
|
||||
_dict=frappe._dict,
|
||||
args=form_dict,
|
||||
frappe=NamespaceDict(
|
||||
call=call_whitelisted_function,
|
||||
|
|
@ -116,7 +130,6 @@ def get_safe_globals():
|
|||
time_format=time_format,
|
||||
format_date=frappe.utils.data.global_date_format,
|
||||
form_dict=form_dict,
|
||||
as_dict=frappe._dict,
|
||||
bold=frappe.bold,
|
||||
copy_doc=frappe.copy_doc,
|
||||
errprint=frappe.errprint,
|
||||
|
|
|
|||
|
|
@ -62,10 +62,6 @@ class WebsiteTheme(Document):
|
|||
def generate_bootstrap_theme(self):
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
self.theme_scss = frappe.render_template(
|
||||
"frappe/website/doctype/website_theme/website_theme_template.scss", self.as_dict()
|
||||
)
|
||||
|
||||
# create theme file in site public files folder
|
||||
folder_path = abspath(frappe.utils.get_files_path("website_theme", is_private=False))
|
||||
# create folder if not exist
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue