Merge branch 'develop' into between-formatting
This commit is contained in:
commit
08ac2eda0a
29 changed files with 231 additions and 112 deletions
1
.github/helper/install.sh
vendored
1
.github/helper/install.sh
vendored
|
|
@ -50,6 +50,7 @@ if [ "$TYPE" == "server" ]; then sed -i 's/^socketio:/# socketio:/g' Procfile; f
|
|||
if [ "$TYPE" == "server" ]; then sed -i 's/^redis_socketio:/# redis_socketio:/g' Procfile; fi
|
||||
|
||||
if [ "$TYPE" == "ui" ]; then bench setup requirements --node; fi
|
||||
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
|
||||
|
||||
# install node-sass which is required for website theme test
|
||||
cd ./apps/frappe || exit
|
||||
|
|
|
|||
|
|
@ -131,3 +131,16 @@ rules:
|
|||
key `$X` is uselessly assigned twice. This could be a potential bug.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-using-db-sql
|
||||
pattern-either:
|
||||
- pattern: frappe.db.sql(...)
|
||||
- pattern: frappe.db.sql_ddl(...)
|
||||
- pattern: frappe.db.sql_list(...)
|
||||
paths:
|
||||
exclude:
|
||||
- "test_*.py"
|
||||
message: |
|
||||
The PR contains a SQL query that may be re-written with frappe.qb (https://frappeframework.com/docs/user/en/api/query-builder) or the Database API (https://frappeframework.com/docs/user/en/api/database)
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ codecov:
|
|||
|
||||
coverage:
|
||||
status:
|
||||
patch: off
|
||||
project:
|
||||
default: false
|
||||
server:
|
||||
|
|
@ -10,11 +11,6 @@ coverage:
|
|||
threshold: 0.5%
|
||||
flags:
|
||||
- server
|
||||
ui-tests:
|
||||
target: auto
|
||||
threshold: 0.5%
|
||||
flags:
|
||||
- ui-tests
|
||||
|
||||
comment:
|
||||
layout: "diff, flags"
|
||||
|
|
@ -28,4 +24,4 @@ flags:
|
|||
ui-tests:
|
||||
paths:
|
||||
- ".*\\.js"
|
||||
carryforward: true
|
||||
carryforward: true
|
||||
|
|
|
|||
|
|
@ -1,44 +1,47 @@
|
|||
context('Relative Timeframe', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/website');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_todo_records");
|
||||
});
|
||||
});
|
||||
it('sets relative timespan filter for last week and filters list', () => {
|
||||
cy.visit('/app/List/ToDo/List');
|
||||
cy.clear_filters();
|
||||
cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
cy.add_filter();
|
||||
cy.get('.fieldname-select-area').should('exist');
|
||||
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
cy.get('select.condition.form-control').select("Timespan");
|
||||
cy.get('.filter-field select.input-with-feedback.form-control').select("last week");
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
cy.wait('@list_refresh');
|
||||
cy.get('.list-row-container').its('length').should('eq', 1);
|
||||
cy.get('.list-row-container').should('contain', 'this is second todo');
|
||||
cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
.as('save_user_settings');
|
||||
cy.clear_filters();
|
||||
cy.wait('@save_user_settings');
|
||||
});
|
||||
it('sets relative timespan filter for next week and filters list', () => {
|
||||
cy.visit('/app/List/ToDo/List');
|
||||
cy.clear_filters();
|
||||
cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
cy.add_filter();
|
||||
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
cy.get('select.condition.form-control').select("Timespan");
|
||||
cy.get('.filter-field select.input-with-feedback.form-control').select("next week");
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
cy.wait('@list_refresh');
|
||||
cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
.as('save_user_settings');
|
||||
cy.clear_filters();
|
||||
cy.wait('@save_user_settings');
|
||||
});
|
||||
});
|
||||
// TODO: Enable this again
|
||||
// currently this is flaky possibly because of different timezone in CI
|
||||
|
||||
// context('Relative Timeframe', () => {
|
||||
// before(() => {
|
||||
// cy.login();
|
||||
// cy.visit('/app/website');
|
||||
// cy.window().its('frappe').then(frappe => {
|
||||
// frappe.call("frappe.tests.ui_test_helpers.create_todo_records");
|
||||
// });
|
||||
// });
|
||||
// it('sets relative timespan filter for last week and filters list', () => {
|
||||
// cy.visit('/app/List/ToDo/List');
|
||||
// cy.clear_filters();
|
||||
// cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
// cy.add_filter();
|
||||
// cy.get('.fieldname-select-area').should('exist');
|
||||
// cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
// cy.get('select.condition.form-control').select("Timespan");
|
||||
// cy.get('.filter-field select.input-with-feedback.form-control').select("last week");
|
||||
// cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
// cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
// cy.wait('@list_refresh');
|
||||
// cy.get('.list-row-container').its('length').should('eq', 1);
|
||||
// cy.get('.list-row-container').should('contain', 'this is second todo');
|
||||
// cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
// .as('save_user_settings');
|
||||
// cy.clear_filters();
|
||||
// cy.wait('@save_user_settings');
|
||||
// });
|
||||
// it('sets relative timespan filter for next week and filters list', () => {
|
||||
// cy.visit('/app/List/ToDo/List');
|
||||
// cy.clear_filters();
|
||||
// cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
// cy.add_filter();
|
||||
// cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
// cy.get('select.condition.form-control').select("Timespan");
|
||||
// cy.get('.filter-field select.input-with-feedback.form-control').select("next week");
|
||||
// cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
// cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
// cy.wait('@list_refresh');
|
||||
// cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
// .as('save_user_settings');
|
||||
// cy.clear_filters();
|
||||
// cy.wait('@save_user_settings');
|
||||
// });
|
||||
// });
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ context('Timeline', () => {
|
|||
cy.click_modal_primary_button('Yes');
|
||||
|
||||
//Deleting the added ToDo
|
||||
cy.get('.menu-btn-group [data-original-title="Menu"]').click();
|
||||
cy.get('.menu-btn-group .dropdown-item').contains('Delete').click();
|
||||
cy.get('[id="page-ToDo"] .menu-btn-group [data-original-title="Menu"]').click();
|
||||
cy.get('[id="page-ToDo"] .menu-btn-group .dropdown-item').contains('Delete').click();
|
||||
cy.findByRole('button', {name: 'Yes'}).click();
|
||||
});
|
||||
|
||||
|
|
|
|||
3
dev-requirements.txt
Normal file
3
dev-requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Faker~=8.1.0
|
||||
pyngrok~=5.0.5
|
||||
unittest-xml-reporting~=3.0.4
|
||||
|
|
@ -44,6 +44,11 @@ let argv = yargs
|
|||
type: "boolean",
|
||||
description: "Run in watch mode and rebuild on file changes"
|
||||
})
|
||||
.option("live-reload", {
|
||||
type: "boolean",
|
||||
description: `Automatically reload web pages when assets are rebuilt.
|
||||
Can only be used with the --watch flag.`
|
||||
})
|
||||
.option("production", {
|
||||
type: "boolean",
|
||||
description: "Run build in production mode"
|
||||
|
|
@ -478,7 +483,8 @@ async function notify_redis({ error, success }) {
|
|||
}
|
||||
if (success) {
|
||||
payload = {
|
||||
success: true
|
||||
success: true,
|
||||
live_reload: argv["live-reload"]
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ from .utils.lazy_loader import lazy_import
|
|||
|
||||
from frappe.query_builder import get_query_builder, patch_query_execute
|
||||
|
||||
# Lazy imports
|
||||
faker = lazy_import('faker')
|
||||
|
||||
__version__ = '14.0.0-dev'
|
||||
|
||||
__title__ = "Frappe Framework"
|
||||
|
|
@ -1480,7 +1477,10 @@ def get_value(*args, **kwargs):
|
|||
|
||||
def as_json(obj, indent=1):
|
||||
from frappe.utils.response import json_handler
|
||||
return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': '))
|
||||
try:
|
||||
return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': '))
|
||||
except TypeError:
|
||||
return json.dumps(obj, indent=indent, default=json_handler, separators=(',', ': '))
|
||||
|
||||
def are_emails_muted():
|
||||
from frappe.utils import cint
|
||||
|
|
@ -1835,6 +1835,7 @@ def parse_json(val):
|
|||
return parse_json(val)
|
||||
|
||||
def mock(type, size=1, locale='en'):
|
||||
import faker
|
||||
results = []
|
||||
fake = faker.Faker(locale)
|
||||
if type not in dir(fake):
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ from frappe.utils.minify import JavascriptMinify
|
|||
import click
|
||||
import psutil
|
||||
from urllib.parse import urlparse
|
||||
from simple_chalk import green
|
||||
from semantic_version import Version
|
||||
from requests import head
|
||||
from requests.exceptions import HTTPError
|
||||
|
|
@ -108,7 +107,7 @@ def fetch_assets(url, frappe_head):
|
|||
if not assets_archive:
|
||||
raise AssetsNotDownloadedError(f"Assets could not be retrived from {url}")
|
||||
|
||||
print(f"\n{green('✔')} Downloaded Frappe assets from {url}")
|
||||
click.echo(click.style("✔", fg="green") + f" Downloaded Frappe assets from {url}")
|
||||
|
||||
return assets_archive
|
||||
|
||||
|
|
@ -131,7 +130,7 @@ def setup_assets(assets_archive):
|
|||
directories_created.add(asset_directory)
|
||||
|
||||
tar.makefile(file, dest)
|
||||
print("{0} Restored {1}".format(green('✔'), show))
|
||||
click.echo(click.style("✔", fg="green") + f" Restored {show}")
|
||||
|
||||
return directories_created
|
||||
|
||||
|
|
@ -257,6 +256,13 @@ def watch(apps=None):
|
|||
if apps:
|
||||
command += " --apps {apps}".format(apps=apps)
|
||||
|
||||
live_reload = frappe.utils.cint(
|
||||
os.environ.get("LIVE_RELOAD", frappe.conf.live_reload)
|
||||
)
|
||||
|
||||
if live_reload:
|
||||
command += " --live-reload"
|
||||
|
||||
check_node_executable()
|
||||
frappe_app_path = frappe.get_app_path("frappe", "..")
|
||||
frappe.commands.popen(command, cwd=frappe_app_path, env=get_node_env())
|
||||
|
|
@ -372,7 +378,7 @@ def make_asset_dirs(hard_link=False):
|
|||
except Exception:
|
||||
print(fail_message, end="\r")
|
||||
|
||||
print(unstrip(f"{green('✔')} Application Assets Linked") + "\n")
|
||||
click.echo(unstrip(click.style("✔", fg="green") + " Application Assets Linked") + "\n")
|
||||
|
||||
|
||||
def link_assets_dir(source, target, hard_link=False):
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ class NavbarSettings(Document):
|
|||
if not frappe.flags.in_patch and (len(before_save_items) > len(after_save_items)):
|
||||
frappe.throw(_("Please hide the standard navbar items instead of deleting them"))
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_app_logo():
|
||||
app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo', cache=True)
|
||||
if not app_logo:
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class UserPermission(Document):
|
|||
ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name)
|
||||
frappe.throw(_("{0} has already assigned default value for {1}.").format(ref_link, self.allow))
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
def get_user_permissions(user=None):
|
||||
'''Get all users permissions for the user as a dict of doctype'''
|
||||
# if this is called from client-side,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from frappe.desk.form.document_follow import is_document_followed
|
|||
from frappe import _
|
||||
from urllib.parse import quote
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
def getdoc(doctype, name, user=None):
|
||||
"""
|
||||
Loads a doclist for a given document. This method is called directly from the client.
|
||||
|
|
@ -52,7 +52,7 @@ def getdoc(doctype, name, user=None):
|
|||
|
||||
frappe.response.docs.append(doc)
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
def getdoctype(doctype, with_parent=False, cached_timestamp=None):
|
||||
"""load doctype"""
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# License: MIT. See LICENSE
|
||||
import frappe
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
def get_list_settings(doctype):
|
||||
try:
|
||||
return frappe.get_cached_doc("List View Settings", doctype)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from frappe.utils import cstr, format_duration
|
|||
from frappe.model.base_document import get_controller
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get():
|
||||
args = get_form_params()
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ class IncompatibleApp(ValidationError): pass
|
|||
class InvalidDates(ValidationError): pass
|
||||
class DataTooLongException(ValidationError): pass
|
||||
class FileAlreadyAttachedException(Exception): pass
|
||||
class DocumentAlreadyRestored(Exception): pass
|
||||
class AttachmentLimitReached(Exception): pass
|
||||
class DocumentAlreadyRestored(ValidationError): pass
|
||||
class AttachmentLimitReached(ValidationError): pass
|
||||
# OAuth exceptions
|
||||
class InvalidAuthorizationHeader(CSRFTokenError): pass
|
||||
class InvalidAuthorizationPrefix(CSRFTokenError): pass
|
||||
|
|
|
|||
|
|
@ -336,7 +336,6 @@ def dropbox_auth_finish(return_access_token=False):
|
|||
_("Dropbox access is approved!") + close,
|
||||
indicator_color='green')
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def set_dropbox_access_token(access_token):
|
||||
frappe.db.set_value("Dropbox Settings", None, 'dropbox_access_token', access_token)
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -597,8 +597,8 @@ class DatabaseQuery(object):
|
|||
self.conditions.append(self.get_share_condition())
|
||||
|
||||
else:
|
||||
#if has if_owner permission skip user perm check
|
||||
if role_permissions.get("has_if_owner_enabled") and role_permissions.get("if_owner", {}):
|
||||
# skip user perm check if owner constraint is required
|
||||
if requires_owner_constraint(role_permissions):
|
||||
self.match_conditions.append(
|
||||
f"`tab{self.doctype}`.`owner` = {frappe.db.escape(self.user, percent=False)}"
|
||||
)
|
||||
|
|
@ -895,3 +895,22 @@ def get_date_range(operator, value):
|
|||
timespan = period_map[operator] + ' ' + timespan_map[value] if operator != 'timespan' else value
|
||||
|
||||
return get_timespan_date_range(timespan)
|
||||
|
||||
def requires_owner_constraint(role_permissions):
|
||||
"""Returns True if "select" or "read" isn't available without being creator."""
|
||||
|
||||
if not role_permissions.get("has_if_owner_enabled"):
|
||||
return
|
||||
|
||||
if_owner_perms = role_permissions.get("if_owner")
|
||||
if not if_owner_perms:
|
||||
return
|
||||
|
||||
# has select or read without if owner, no need for constraint
|
||||
for perm_type in ("select", "read"):
|
||||
if role_permissions.get(perm_type) and perm_type not in if_owner_perms:
|
||||
return
|
||||
|
||||
# not checking if either select or read if present in if_owner_perms
|
||||
# because either of those is required to perform a query
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -107,13 +107,9 @@ def get_doc_permissions(doc, user=None, ptype=None):
|
|||
meta = frappe.get_meta(doc.doctype)
|
||||
|
||||
def is_user_owner():
|
||||
doc_owner = doc.get('owner') or ''
|
||||
doc_owner = doc_owner.lower()
|
||||
session_user = frappe.session.user.lower()
|
||||
return doc_owner == session_user
|
||||
return (doc.get("owner") or "").lower() == frappe.session.user.lower()
|
||||
|
||||
|
||||
if has_controller_permissions(doc, ptype, user=user) == False :
|
||||
if has_controller_permissions(doc, ptype, user=user) is False:
|
||||
push_perm_check_log('Not allowed via controller permission check')
|
||||
return {ptype: 0}
|
||||
|
||||
|
|
@ -182,22 +178,23 @@ def get_role_permissions(doctype_meta, user=None, is_owner=None):
|
|||
|
||||
applicable_permissions = list(filter(is_perm_applicable, getattr(doctype_meta, 'permissions', [])))
|
||||
has_if_owner_enabled = any(p.get('if_owner', 0) for p in applicable_permissions)
|
||||
|
||||
perms['has_if_owner_enabled'] = has_if_owner_enabled
|
||||
|
||||
for ptype in rights:
|
||||
pvalue = any(p.get(ptype, 0) for p in applicable_permissions)
|
||||
# check if any perm object allows perm type
|
||||
perms[ptype] = cint(pvalue)
|
||||
if (pvalue
|
||||
and has_if_owner_enabled
|
||||
and not has_permission_without_if_owner_enabled(ptype)
|
||||
and ptype != 'create'):
|
||||
if (
|
||||
pvalue
|
||||
and has_if_owner_enabled
|
||||
and not has_permission_without_if_owner_enabled(ptype)
|
||||
and ptype != 'create'
|
||||
):
|
||||
perms['if_owner'][ptype] = cint(pvalue and is_owner)
|
||||
# has no access if not owner
|
||||
# only provide select or read access so that user is able to at-least access list
|
||||
# (and the documents will be filtered based on owner sin further checks)
|
||||
perms[ptype] = 1 if ptype in ['select', 'read'] else 0
|
||||
perms[ptype] = 1 if ptype in ('select', 'read') else 0
|
||||
|
||||
frappe.local.role_permissions[cache_key] = perms
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ frappe.ui.form.PrintView = class {
|
|||
|
||||
add_sidebar_item(df, is_dynamic) {
|
||||
if (df.fieldtype == 'Select') {
|
||||
df.input_class = 'btn btn-default btn-sm';
|
||||
df.input_class = 'btn btn-default btn-sm text-left';
|
||||
}
|
||||
|
||||
let field = frappe.ui.form.make_control({
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@
|
|||
v-if="is_shown"
|
||||
class="flex justify-between build-success-message align-center"
|
||||
>
|
||||
<div class="mr-4">Compiled successfully</div>
|
||||
<a class="text-white underline" href="/" @click.prevent="reload">
|
||||
Compiled successfully
|
||||
<a
|
||||
v-if="!live_reload"
|
||||
class="ml-4 text-white underline" href="/" @click.prevent="reload"
|
||||
>
|
||||
Refresh
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -14,11 +17,17 @@ export default {
|
|||
name: "BuildSuccess",
|
||||
data() {
|
||||
return {
|
||||
is_shown: false
|
||||
is_shown: false,
|
||||
live_reload: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
show(data) {
|
||||
if (data.live_reload) {
|
||||
this.live_reload = true;
|
||||
this.reload();
|
||||
}
|
||||
|
||||
this.is_shown = true;
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ frappe.realtime.on("build_event", data => {
|
|||
}
|
||||
});
|
||||
|
||||
function show_build_success() {
|
||||
function show_build_success(data) {
|
||||
if (error) {
|
||||
error.hide();
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
let target = $('<div class="build-success-container">')
|
||||
.appendTo($container)
|
||||
|
|
@ -27,7 +28,7 @@ function show_build_success() {
|
|||
});
|
||||
success = vm.$children[0];
|
||||
}
|
||||
success.show();
|
||||
success.show(data);
|
||||
}
|
||||
|
||||
function show_build_error(data) {
|
||||
|
|
|
|||
|
|
@ -302,9 +302,20 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
refresh(refresh_header=false) {
|
||||
super.refresh().then(() => {
|
||||
this.render_header(refresh_header);
|
||||
this.update_checkbox();
|
||||
});
|
||||
}
|
||||
|
||||
update_checkbox(target) {
|
||||
let $check_all_checkbox = this.$checkbox_actions.find(".list-check-all");
|
||||
|
||||
if ($check_all_checkbox.prop("checked") && target && !target.prop("checked")) {
|
||||
$check_all_checkbox.prop("checked", false);
|
||||
}
|
||||
|
||||
$check_all_checkbox.prop("checked", this.$checks.length === this.data.length);
|
||||
}
|
||||
|
||||
setup_freeze_area() {
|
||||
this.$freeze = $(
|
||||
`<div class="freeze flex justify-center align-center text-muted">${__(
|
||||
|
|
@ -1253,6 +1264,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
this.$checkbox_cursor = $target;
|
||||
|
||||
this.update_checkbox($target);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1398,6 +1411,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
this.$checkbox_actions.show();
|
||||
this.$list_head_subject.hide();
|
||||
}
|
||||
this.update_checkbox();
|
||||
this.toggle_actions_menu_button(this.$checks.length > 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1049,18 +1049,20 @@ Object.assign(frappe.utils, {
|
|||
return duration;
|
||||
},
|
||||
|
||||
seconds_to_duration(value, duration_options) {
|
||||
let secs = value;
|
||||
let total_duration = {
|
||||
days: Math.floor(secs / (3600 * 24)),
|
||||
hours: Math.floor(secs % (3600 * 24) / 3600),
|
||||
minutes: Math.floor(secs % 3600 / 60),
|
||||
seconds: Math.floor(secs % 60)
|
||||
seconds_to_duration(seconds, duration_options) {
|
||||
const round = seconds > 0 ? Math.floor : Math.ceil;
|
||||
const total_duration = {
|
||||
days: round(seconds / 86400), // 60 * 60 * 24
|
||||
hours: round(seconds % 86400 / 3600),
|
||||
minutes: round(seconds % 3600 / 60),
|
||||
seconds: round(seconds % 60)
|
||||
};
|
||||
|
||||
if (duration_options.hide_days) {
|
||||
total_duration.hours = Math.floor(secs / 3600);
|
||||
total_duration.hours = round(seconds / 3600);
|
||||
total_duration.days = 0;
|
||||
}
|
||||
|
||||
return total_duration;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
if (this.report_name !== frappe.get_route()[1]) {
|
||||
// this.toggle_loading(true);
|
||||
// different report
|
||||
this.load_report();
|
||||
}
|
||||
|
|
@ -556,6 +555,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
refresh() {
|
||||
this.toggle_message(true);
|
||||
this.toggle_report(false);
|
||||
this.show_loading_screen();
|
||||
let filters = this.get_filter_values(true);
|
||||
|
||||
// only one refresh at a time
|
||||
|
|
@ -645,6 +645,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
|
||||
this.show_footer_message();
|
||||
frappe.hide_progress();
|
||||
}).finally(() => {
|
||||
this.hide_loading_screen();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -869,6 +871,24 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
}
|
||||
|
||||
show_loading_screen() {
|
||||
const loading_state = `<div class="msg-box no-border">
|
||||
<div>
|
||||
<img src="/assets/frappe/images/ui-states/list-empty-state.svg" alt="Generic Empty State" class="null-state">
|
||||
</div>
|
||||
<p>${__('Loading')}...</p>
|
||||
</div>`;
|
||||
|
||||
this.$loading.find('div').html(loading_state);
|
||||
this.$report.hide();
|
||||
this.$loading.show();
|
||||
}
|
||||
|
||||
hide_loading_screen() {
|
||||
this.$loading.hide();
|
||||
this.$report.show();
|
||||
}
|
||||
|
||||
get_chart_options(data) {
|
||||
let options = this.report_settings.get_chart_data
|
||||
? this.report_settings.get_chart_data(data.columns, data.result)
|
||||
|
|
@ -1679,6 +1699,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
.hide().appendTo(this.page.main);
|
||||
|
||||
this.$chart = $('<div class="chart-wrapper">').hide().appendTo(this.page.main);
|
||||
|
||||
this.$loading = $(this.message_div('')).hide().appendTo(this.page.main);
|
||||
this.$report = $('<div class="report-wrapper">').appendTo(this.page.main);
|
||||
this.$message = $(this.message_div('')).hide().appendTo(this.page.main);
|
||||
}
|
||||
|
|
@ -1738,11 +1760,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
this.refresh();
|
||||
}
|
||||
|
||||
toggle_loading(flag) {
|
||||
this.toggle_message(flag, __('Loading') + '...');
|
||||
}
|
||||
|
||||
|
||||
toggle_nothing_to_show(flag) {
|
||||
let message = this.prepared_report
|
||||
? __('This is a background report. Please set the appropriate filters and then generate a new one.')
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@
|
|||
|
||||
.list-row-head {
|
||||
@extend .list-row;
|
||||
padding: 15px;
|
||||
cursor: default;
|
||||
|
||||
.list-subject {
|
||||
|
|
@ -214,6 +213,10 @@ input.list-check-all, input.list-row-checkbox {
|
|||
--checkbox-right-margin: calc(var(--checkbox-size) / 2 + #{$level-margin-right});
|
||||
}
|
||||
|
||||
input.list-check-all {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.render-list-checkbox {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ import redis
|
|||
from urllib.parse import unquote
|
||||
from frappe.cache_manager import clear_user_cache
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def clear(user=None):
|
||||
@frappe.whitelist()
|
||||
def clear():
|
||||
frappe.local.session_obj.update(force=True)
|
||||
frappe.local.db.commit()
|
||||
clear_user_cache(frappe.session.user)
|
||||
|
|
|
|||
|
|
@ -493,6 +493,34 @@ class TestPermissions(unittest.TestCase):
|
|||
frappe.set_user("test2@example.com")
|
||||
self.assertRaises(frappe.PermissionError, getdoc, 'Blog Post', doc.name)
|
||||
|
||||
def test_if_owner_permission_on_get_list(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Blog Post",
|
||||
"blog_category": "-test-blog-category",
|
||||
"blogger": "_Test Blogger 1",
|
||||
"title": "_Test If Owner Permissions on Get List",
|
||||
"content": "_Test Blog Post Content"
|
||||
})
|
||||
|
||||
doc.insert(ignore_if_duplicate=True)
|
||||
|
||||
update('Blog Post', 'Blogger', 0, 'if_owner', 1)
|
||||
update('Blog Post', 'Blogger', 0, 'read', 1)
|
||||
user = frappe.get_doc("User", "test2@example.com")
|
||||
user.add_roles("Website Manager")
|
||||
frappe.clear_cache(doctype="Blog Post")
|
||||
|
||||
frappe.set_user("test2@example.com")
|
||||
self.assertIn(doc.name, frappe.get_list("Blog Post", pluck="name"))
|
||||
|
||||
# Become system manager to remove role
|
||||
frappe.set_user("test1@example.com")
|
||||
user.remove_roles("Website Manager")
|
||||
frappe.clear_cache(doctype="Blog Post")
|
||||
|
||||
frappe.set_user("test2@example.com")
|
||||
self.assertNotIn(doc.name, frappe.get_list("Blog Post", pluck="name"))
|
||||
|
||||
def test_if_owner_permission_on_delete(self):
|
||||
update('Blog Post', 'Blogger', 0, 'if_owner', 1)
|
||||
update('Blog Post', 'Blogger', 0, 'read', 1)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import frappe
|
|||
from frappe.utils import set_request
|
||||
from frappe.website.serve import get_response, get_response_content
|
||||
from frappe.website.utils import (build_response, clear_website_cache, get_home_page)
|
||||
from tenacity import retry, stop_after_attempt, retry_if_exception_type
|
||||
|
||||
|
||||
class TestWebsite(unittest.TestCase):
|
||||
|
|
@ -196,6 +197,11 @@ class TestWebsite(unittest.TestCase):
|
|||
delattr(frappe.hooks, 'page_renderer')
|
||||
frappe.cache().delete_key('app_hooks')
|
||||
|
||||
# TODO: Get rid of this retry logic
|
||||
# Added since test is flaky and we can't figure out why at this point
|
||||
@retry(
|
||||
stop=stop_after_attempt(5), retry=retry_if_exception_type(AssertionError),
|
||||
)
|
||||
def test_printview_page(self):
|
||||
content = get_response_content('/Language/en')
|
||||
self.assertIn('<div class="print-format">', content)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ croniter~=1.0.11
|
|||
cryptography~=3.4.7
|
||||
dropbox~=11.7.0
|
||||
email-reply-parser~=0.5.12
|
||||
Faker~=8.1.0
|
||||
git-url-parse~=1.2.2
|
||||
gitdb~=4.0.7
|
||||
GitPython~=3.1.14
|
||||
|
|
@ -44,7 +43,6 @@ pyasn1~=0.4.8
|
|||
pycryptodome~=3.10.1
|
||||
PyJWT~=2.0.1
|
||||
PyMySQL~=1.0.2
|
||||
pyngrok~=5.0.5
|
||||
pyOpenSSL~=20.0.1
|
||||
pyotp~=2.6.0
|
||||
PyPDF2~=1.26.0
|
||||
|
|
@ -64,12 +62,10 @@ rq~=1.8.0
|
|||
rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
schedule~=1.1.0
|
||||
semantic-version~=2.8.5
|
||||
simple-chalk~=0.1.0
|
||||
six~=1.15.0
|
||||
sqlparse~=0.4.1
|
||||
stripe~=2.56.0
|
||||
terminaltables~=3.1.0
|
||||
unittest-xml-reporting~=3.0.4
|
||||
urllib3~=1.26.4
|
||||
Werkzeug~=0.16.1
|
||||
Whoosh~=2.7.4
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue