Merge branch 'develop' into aks-test-fixtures

This commit is contained in:
Suraj Shetty 2021-12-24 19:19:45 +05:30 committed by GitHub
commit 84f366bbfc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 154 additions and 67 deletions

View file

@ -740,17 +740,26 @@ def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=Fals
:param doc: [optional] Checks User permissions for given doc.
:param user: [optional] Check for given user. Default: current user.
:param parent_doctype: Required when checking permission for a child DocType (unless doc is specified)."""
import frappe.permissions
if not doctype and doc:
doctype = doc.doctype
import frappe.permissions
out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user,
raise_exception=throw, parent_doctype=parent_doctype)
if throw and not out:
if doc:
frappe.throw(_("No permission for {0}").format(doc.doctype + " " + doc.name))
else:
frappe.throw(_("No permission for {0}").format(doctype))
# mimics frappe.throw
document_label = f"{doc.doctype} {doc.name}" if doc else doctype
msgprint(
_("No permission for {0}").format(document_label),
raise_exception=ValidationError,
title=None,
indicator='red',
is_minimizable=None,
wide=None,
as_list=False
)
return out

View file

@ -32,6 +32,7 @@ def get_list(doctype, fields=None, filters=None, order_by=None,
args = frappe._dict(
doctype=doctype,
parent_doctype=parent,
fields=fields,
filters=filters,
or_filters=or_filters,

View file

@ -29,6 +29,7 @@ from frappe import _, conf, safe_decode
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, cstr, encode, get_files_path, get_hook_method, random_string, strip
from frappe.utils.image import strip_exif_data, optimize_image
from frappe.utils.file_manager import safe_b64decode
class MaxFileSizeReachedError(frappe.ValidationError):
pass
@ -436,7 +437,7 @@ class File(Document):
if b"," in self.content:
self.content = self.content.split(b",")[1]
self.content = base64.b64decode(self.content)
self.content = safe_b64decode(self.content)
if not self.is_private:
self.is_private = 0
@ -852,7 +853,7 @@ def extract_images_from_html(doc, content, is_private=False):
content = content.encode("utf-8")
if b"," in content:
content = content.split(b",")[1]
content = base64.b64decode(content)
content = safe_b64decode(content)
content = optimize_image(content, mtype)

View file

@ -1,4 +1,4 @@
// Copyright (c) 2016, {app_publisher} and contributors
// Copyright (c) {year}, {app_publisher} and contributors
// For license information, please see license.txt
/* eslint-disable */

View file

@ -1,5 +1,5 @@
# Copyright (c) 2013, {app_publisher} and contributors
# License: MIT. See LICENSE
# Copyright (c) {year}, {app_publisher} and contributors
# For license information, please see license.txt
# import frappe

View file

@ -135,9 +135,10 @@ class MariaDBDatabase(Database):
table_name = get_table_name(doctype)
return self.sql(f"DESC `{table_name}`")
def change_column_type(self, doctype: str, column: str, type: str) -> Union[List, Tuple]:
def change_column_type(self, doctype: str, column: str, type: str, nullable: bool = False) -> Union[List, Tuple]:
table_name = get_table_name(doctype)
return self.sql(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} NOT NULL")
null_constraint = "NOT NULL" if not nullable else ""
return self.sql(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} {null_constraint}")
# exception types
@staticmethod

View file

@ -183,9 +183,12 @@ class PostgresDatabase(Database):
table_name = get_table_name(doctype)
return self.sql(f"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = '{table_name}'")
def change_column_type(self, doctype: str, column: str, type: str) -> Union[List, Tuple]:
def change_column_type(self, doctype: str, column: str, type: str, nullable: bool = False) -> Union[List, Tuple]:
table_name = get_table_name(doctype)
return self.sql(f'ALTER TABLE "{table_name}" ALTER COLUMN "{column}" TYPE {type}')
null_constraint = "SET NOT NULL" if not nullable else "DROP NOT NULL"
return self.sql(f"""ALTER TABLE "{table_name}"
ALTER COLUMN "{column}" TYPE {type},
ALTER COLUMN "{column}" {null_constraint}""")
def create_auth_table(self):
self.sql_ddl("""create table if not exists "__Auth" (

View file

@ -382,10 +382,10 @@ class Leaderboard {
let timespan = this.options.selected_timespan.toLowerCase();
let current_date = frappe.datetime.now_date();
let date_range_map = {
"this week": [frappe.datetime.week_start(), current_date],
"this month": [frappe.datetime.month_start(), current_date],
"this quarter": [frappe.datetime.quarter_start(), current_date],
"this year": [frappe.datetime.year_start(), current_date],
"this week": [frappe.datetime.week_start(), frappe.datetime.week_end()],
"this month": [frappe.datetime.month_start(), frappe.datetime.month_end()],
"this quarter": [frappe.datetime.quarter_start(), frappe.datetime.quarter_end()],
"this year": [frappe.datetime.year_start(), frappe.datetime.year_end()],
"last week": [frappe.datetime.add_days(current_date, -7), current_date],
"last month": [frappe.datetime.add_months(current_date, -1), current_date],
"last quarter": [frappe.datetime.add_months(current_date, -3), current_date],

View file

@ -36,10 +36,12 @@ class DatabaseQuery(object):
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
update=None, add_total_row=None, user_settings=None, reference_doctype=None,
run=True, strict=True, pluck=None, ignore_ddl=False, parent_doctype=None) -> List:
if not ignore_permissions and \
not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype) and \
not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype):
if (
not ignore_permissions
and not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype)
and not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype)
):
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
raise frappe.PermissionError(self.doctype)
@ -787,12 +789,15 @@ class DatabaseQuery(object):
def check_parent_permission(parent, child_doctype):
if parent:
# User may pass fake parent and get the information from the child table
if child_doctype and not frappe.db.exists('DocField',
{'parent': parent, 'options': child_doctype}):
if child_doctype and not (
frappe.db.exists('DocField', {'parent': parent, 'options': child_doctype})
or frappe.db.exists('Custom Field', {'dt': parent, 'options': child_doctype})
):
raise frappe.PermissionError
if frappe.permissions.has_permission(parent):
return
# Either parent not passed or the user doesn't have permission on parent doctype of child table!
raise frappe.PermissionError

View file

@ -188,5 +188,5 @@ frappe.patches.v14_0.copy_mail_data #08.03.21
frappe.patches.v14_0.update_workspace2 # 20.09.2021
frappe.patches.v14_0.update_github_endpoints #08-11-2021
frappe.patches.v14_0.remove_db_aggregation
frappe.patches.v14_0.save_ratings_in_fraction
frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021
frappe.patches.v14_0.update_color_names_in_kanban_board_column

View file

@ -1,12 +1,39 @@
import frappe
from frappe.query_builder import DocType
def execute():
rating_fields = frappe.get_all("DocField", fields=["parent", "fieldname"], filters={"fieldtype": "Rating"})
RATING_FIELD_TYPE = "decimal(3,2)"
rating_fields = frappe.get_all(
"DocField", fields=["parent", "fieldname"], filters={"fieldtype": "Rating"}
)
custom_rating_fields = frappe.get_all("Custom Field", fields=["dt", "fieldname"], filters={"fieldtype": "Rating"})
custom_rating_fields = frappe.get_all(
"Custom Field", fields=["dt", "fieldname"], filters={"fieldtype": "Rating"}
)
for field in rating_fields + custom_rating_fields:
doctype_name = field.get("parent") or field.get("dt")
doctype = frappe.qb.DocType(doctype_name)
field = field.fieldname
(frappe.qb.update(doctype_name).set(doctype[field], doctype[field]/5)).run()
for _field in rating_fields + custom_rating_fields:
doctype_name = _field.get("parent") or _field.get("dt")
doctype = DocType(doctype_name)
field = _field.fieldname
# TODO: Add postgres support (for the check)
if (
frappe.conf.db_type == "mariadb"
and frappe.db.get_column_type(doctype_name, field) == RATING_FIELD_TYPE
):
continue
# commit any changes so far for upcoming DDL
frappe.db.commit()
# alter column types for rating fieldtype
frappe.db.change_column_type(doctype_name, column=field, type=RATING_FIELD_TYPE, nullable=True)
# update data: int => decimal
frappe.qb.update(doctype).set(
doctype[field], doctype[field] / 5
).run()
# commit to flush updated rows
frappe.db.commit()

View file

@ -190,7 +190,7 @@ frappe.ui.form.Form = class FrappeForm {
setup_std_layout() {
this.form_wrapper = $('<div></div>').appendTo(this.layout_main);
this.body = $('<div></div>').appendTo(this.form_wrapper);
this.body = $('<div class="std-form-layout"></div>').appendTo(this.form_wrapper);
// only tray
this.meta.section_style='Simple'; // always simple!
@ -211,12 +211,24 @@ frappe.ui.form.Form = class FrappeForm {
this.fields = this.layout.fields_list;
let dashboard_parent = $('<div class="form-dashboard">');
let dashboard_added = false;
if (this.layout.tabs.length) {
this.layout.tabs[0].wrapper.prepend(dashboard_parent);
this.layout.tabs.every(tab => {
if (tab.df.options === 'Dashboard') {
tab.wrapper.prepend(dashboard_parent);
dashboard_added = true;
return false;
}
return true;
});
if (!dashboard_added) {
this.layout.tabs[0].wrapper.prepend(dashboard_parent);
}
} else {
dashboard_parent.insertAfter(this.layout.wrapper.find('.form-message'));
this.layout.wrapper.find('.form-page').prepend(dashboard_parent);
}
this.dashboard = new frappe.ui.form.Dashboard(dashboard_parent, this);
this.tour = new frappe.ui.form.FormTour({

View file

@ -29,7 +29,7 @@ $.extend(frappe.contacts, {
}
},
get_last_doc: function(frm) {
const reverse_routes = frappe.route_history.reverse();
const reverse_routes = frappe.route_history.slice().reverse();
const last_route = reverse_routes.find(route => {
return route[0] === 'Form' && route[1] !== frm.doctype
})

View file

@ -1,4 +1,5 @@
.form-control {
height: inherit;
border: none;
font-size: var(--text-md);
position: relative;
@ -13,10 +14,9 @@
font-weight: normal;
font-size: var(--text-sm);
}
min-height: var(--input-height);
border-radius: $border-radius;
font-weight: 400;
padding: 8px 12px;
padding: 6px 12px;
cursor: default;
color: var(--disabled-text-color);
background-color: var(--disabled-control-bg);

View file

@ -79,10 +79,9 @@
.grid-static-col,
.row-index {
height: 39px;
padding: var(--padding-sm) var(--padding-md);
height: 34px;
padding: 8px;
max-height: 200px;
// border-right: 1px solid var(--border-color);
}
.grid-row-check {
@ -108,6 +107,7 @@
.grid-row > .row {
.col:last-child {
margin-right: calc(-1 * var(--margin-sm));
border-right: none;
}
.col {
@ -149,7 +149,7 @@
}
textarea {
height: 40px !important;
height: 37px !important;
}
.form-control {
@ -157,7 +157,7 @@
border: 0px;
padding-top: 8px;
padding-bottom: 9px;
height: 40px;
height: 34px;
}
.link-btn {
@ -196,6 +196,10 @@
}
}
.grid-static-col[data-fieldtype="Check"] .static-area {
padding-top: 2px;
}
.grid-static-col[data-fieldtype="Rating"] .field-area {
margin-top: 1rem;
margin-left: 1rem;

View file

@ -1,6 +1,12 @@
@import "../common/form.scss";
@import '~cropperjs/dist/cropper.min';
.std-form-layout > .form-layout > .form-page {
border-radius: var(--border-radius-md);
box-shadow: var(--card-shadow);
background-color: var(--card-bg);
}
.form-section, .form-dashboard-section {
margin: 0px;
@ -12,6 +18,7 @@
.section-head {
@extend .head-title;
font-size: var(--text-base);
width: 100%;
padding: var(--padding-sm) var(--padding-md);
margin: 0;
@ -47,8 +54,12 @@
.form-section.card-section,
.form-dashboard-section {
margin-bottom: var(--margin-lg);
@extend .frappe-card;
border-bottom: 1px solid var(--gray-200);
padding: var(--padding-xs);
}
.row.form-section.card-section.visible-section:last-child {
border-bottom: none;
}
.form-dashboard-section {
@ -57,9 +68,8 @@
}
.section-body {
display: block;
padding-left: var(--padding-md);
padding-right: var(--padding-md);
padding-bottom: var(--padding-md);
padding: var(--padding-md);
padding-top: 0;
}
}
@ -85,7 +95,8 @@
.comment-box {
@include card();
padding: 25px var(--padding-xl);
margin-top: var(--margin-lg);
padding: var(--padding-lg);
.comment-input-header {
@extend .head-title;
margin-bottom: var(--margin-sm);
@ -304,19 +315,18 @@
}
.form-tabs-list {
margin-bottom: var(--margin-lg);
padding-left: var(--padding-xs);
border-bottom: 1px solid var(--gray-200);
.form-tabs {
.nav-item {
.nav-link {
padding-bottom: var(--padding-md);
color: var(--gray-700);
padding-left: 0;
padding-right: 0;
margin-right: var(--margin-xl);
padding: var(--padding-md) 0;
margin: 0 var(--margin-md);
&.active {
font-weight: 500;
font-weight: 600;
border-bottom: 1px solid var(--primary);
color: var(--text-color);
}

View file

@ -507,10 +507,10 @@ def get_timespan_date_range(timespan):
"yesterday": lambda: (add_to_date(today, days=-1),) * 2,
"today": lambda: (today, today),
"tomorrow": lambda: (add_to_date(today, days=1),) * 2,
"this week": lambda: (get_first_day_of_week(today), today),
"this month": lambda: (get_first_day(today), today),
"this quarter": lambda: (get_quarter_start(today), today),
"this year": lambda: (get_year_start(today), today),
"this week": lambda: (get_first_day_of_week(today), get_last_day_of_week(today)),
"this month": lambda: (get_first_day(today), get_last_day(today)),
"this quarter": lambda: (get_quarter_start(today), get_quarter_ending(today)),
"this year": lambda: (get_year_start(today), get_year_ending(today)),
"next week": lambda: (get_first_day_of_week(add_to_date(today, days=7)), get_last_day_of_week(add_to_date(today, days=7))),
"next month": lambda: (get_first_day(add_to_date(today, months=1)), get_last_day(add_to_date(today, months=1))),
"next quarter": lambda: (get_quarter_start(add_to_date(today, months=3)), get_quarter_ending(add_to_date(today, months=3))),

View file

@ -1,4 +1,4 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
@ -17,6 +17,20 @@ class MaxFileSizeReachedError(frappe.ValidationError):
pass
def safe_b64decode(binary: bytes) -> bytes:
"""Adds padding if doesn't already exist before decoding.
This attempts to avoid the `binascii.Error: Incorrect padding` error raised
when the number of trailing = is simply not enough :crie:. Although, it may
be an indication of corrupted data.
Refs:
* https://en.wikipedia.org/wiki/Base64
* https://stackoverflow.com/questions/2941995/python-ignore-incorrect-padding-error-when-base64-decoding
"""
return base64.b64decode(binary + b"===")
def get_file_url(file_data_name):
data = frappe.db.get_value("File", file_data_name, ["file_name", "file_url"], as_dict=True)
return data.file_url or data.file_name
@ -112,7 +126,7 @@ def get_uploaded_content():
if 'filedata' in frappe.form_dict:
if "," in frappe.form_dict.filedata:
frappe.form_dict.filedata = frappe.form_dict.filedata.rsplit(",", 1)[1]
frappe.uploaded_content = base64.b64decode(frappe.form_dict.filedata)
frappe.uploaded_content = safe_b64decode(frappe.form_dict.filedata)
frappe.uploaded_filename = frappe.form_dict.filename
return frappe.uploaded_filename, frappe.uploaded_content
else:
@ -126,7 +140,7 @@ def save_file(fname, content, dt, dn, folder=None, decode=False, is_private=0, d
if b"," in content:
content = content.split(b",")[1]
content = base64.b64decode(content)
content = safe_b64decode(content)
file_size = check_max_file_size(content)
content_hash = get_content_hash(content)

View file

@ -69,15 +69,15 @@ frappe.ready(function() {
const confirm_password = $('#confirm_password').val()
if (!args.old_password && !args.key) {
frappe.msgprint({
title: "{{ _('Message') }}",
message: "{{ _('Old Password Required.') }}",
title: "{{ _('Missing Value') }}",
message: "{{ _('Please enter your old password.') }}",
clear: true
});
}
if (!args.new_password) {
frappe.msgprint({
title: "{{ _('Message') }}",
message: "{{ _('New Password Required.') }}",
title: "{{ _('Missing Value') }}",
message: "{{ _('Please enter your new password.') }}",
clear: true
});
}
@ -110,8 +110,8 @@ frappe.ready(function() {
.html("{{ _('Status Updated') }}");
if(r.message) {
frappe.msgprint({
title: "{{ _('Message') }}",
message: "{{ _('Password Updated') }}",
title: "{{ _('Password set') }}",
message: "{{ _('Your new password has been set successfully.') }}",
// password is updated successfully
// clear any server message
clear: true