Browse Source

Merge branch 'develop' into aks-test-fixtures

version-14
Suraj Shetty 3 years ago
committed by GitHub
parent
commit
84f366bbfc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 154 additions and 67 deletions
  1. +14
    -5
      frappe/__init__.py
  2. +1
    -0
      frappe/client.py
  3. +3
    -2
      frappe/core/doctype/file/file.py
  4. +1
    -1
      frappe/core/doctype/report/boilerplate/controller.js
  5. +2
    -2
      frappe/core/doctype/report/boilerplate/controller.py
  6. +3
    -2
      frappe/database/mariadb/database.py
  7. +5
    -2
      frappe/database/postgres/database.py
  8. +4
    -4
      frappe/desk/page/leaderboard/leaderboard.js
  9. +10
    -5
      frappe/model/db_query.py
  10. +1
    -1
      frappe/patches.txt
  11. +34
    -7
      frappe/patches/v14_0/save_ratings_in_fraction.py
  12. +15
    -3
      frappe/public/js/frappe/form/form.js
  13. +1
    -1
      frappe/public/js/frappe/utils/address_and_contact.js
  14. +2
    -2
      frappe/public/scss/common/form.scss
  15. +9
    -5
      frappe/public/scss/common/grid.scss
  16. +22
    -12
      frappe/public/scss/desk/form.scss
  17. +4
    -4
      frappe/utils/data.py
  18. +17
    -3
      frappe/utils/file_manager.py
  19. +6
    -6
      frappe/www/update-password.html

+ 14
- 5
frappe/__init__.py 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



+ 1
- 0
frappe/client.py 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,


+ 3
- 2
frappe/core/doctype/file/file.py 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)



+ 1
- 1
frappe/core/doctype/report/boilerplate/controller.js 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 */



+ 2
- 2
frappe/core/doctype/report/boilerplate/controller.py 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



+ 3
- 2
frappe/database/mariadb/database.py 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


+ 5
- 2
frappe/database/postgres/database.py 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" (


+ 4
- 4
frappe/desk/page/leaderboard/leaderboard.js 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],


+ 10
- 5
frappe/model/db_query.py 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



+ 1
- 1
frappe/patches.txt 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

+ 34
- 7
frappe/patches/v14_0/save_ratings_in_fraction.py 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"}
)

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)

custom_rating_fields = frappe.get_all("Custom Field", fields=["dt", "fieldname"], filters={"fieldtype": "Rating"})
# update data: int => decimal
frappe.qb.update(doctype).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 = frappe.qb.DocType(doctype_name)
field = field.fieldname
(frappe.qb.update(doctype_name).set(doctype[field], doctype[field]/5)).run()
# commit to flush updated rows
frappe.db.commit()

+ 15
- 3
frappe/public/js/frappe/form/form.js 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({


+ 1
- 1
frappe/public/js/frappe/utils/address_and_contact.js 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
})


+ 2
- 2
frappe/public/scss/common/form.scss 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);


+ 9
- 5
frappe/public/scss/common/grid.scss 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;


+ 22
- 12
frappe/public/scss/desk/form.scss 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);
}


+ 4
- 4
frappe/utils/data.py 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))),


+ 17
- 3
frappe/utils/file_manager.py 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)


+ 6
- 6
frappe/www/update-password.html 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


Loading…
Cancel
Save