Merge branch 'develop' into fix/user-type
This commit is contained in:
commit
b237c653a3
39 changed files with 211 additions and 117 deletions
|
|
@ -7,6 +7,7 @@ pull_request_rules:
|
|||
- author!=gavindsouza
|
||||
- author!=deepeshgarg007
|
||||
- author!=ankush
|
||||
- author!=mergify[bot]
|
||||
- or:
|
||||
- base=version-13
|
||||
- base=version-12
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ context('Awesome Bar', () => {
|
|||
|
||||
cy.get('.title-text').should('contain', 'To Do');
|
||||
|
||||
cy.findByPlaceholderText('Name')
|
||||
cy.findByPlaceholderText('ID')
|
||||
.should('have.value', '%test%');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ context('Data Control', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('check custom formatters', () => {
|
||||
cy.visit(`/app/doctype/User`);
|
||||
cy.get('[data-fieldname="fields"] .grid-row[data-idx="2"] [data-fieldname="fieldtype"] .static-area').should('have.text', '🔵 Section Break');
|
||||
});
|
||||
|
||||
it('Verifying data control by inputting different patterns for "Name" field', () => {
|
||||
cy.new_form('Test Data Control');
|
||||
|
||||
|
|
@ -54,7 +60,7 @@ context('Data Control', () => {
|
|||
|
||||
//Checking if the border color of the field changes to red
|
||||
cy.get('.frappe-control[data-fieldname="name1"]').should('have.class', 'has-error');
|
||||
cy.findByRole('button', {name: 'Save'}).click();
|
||||
cy.save();
|
||||
|
||||
//Checking for the error message
|
||||
cy.get('.modal-title').should('have.text', 'Message');
|
||||
|
|
@ -64,7 +70,7 @@ context('Data Control', () => {
|
|||
cy.get_field('name1', 'Data').clear({force: true});
|
||||
cy.fill_field('name1', 'Komal{}/!', 'Data');
|
||||
cy.get('.frappe-control[data-fieldname="name1"]').should('have.class', 'has-error');
|
||||
cy.findByRole('button', {name: 'Save'}).click();
|
||||
cy.save();
|
||||
cy.get('.modal-title').should('have.text', 'Message');
|
||||
cy.get('.msgprint').should('have.text', 'Komal{}/! is not a valid Name');
|
||||
cy.hide_dialog();
|
||||
|
|
@ -76,14 +82,14 @@ context('Data Control', () => {
|
|||
cy.get_field('email', 'Data').clear({force: true});
|
||||
cy.fill_field('email', 'komal', 'Data');
|
||||
cy.get('.frappe-control[data-fieldname="email"]').should('have.class', 'has-error');
|
||||
cy.findByRole('button', {name: 'Save'}).click();
|
||||
cy.save();
|
||||
cy.get('.modal-title').should('have.text', 'Message');
|
||||
cy.get('.msgprint').should('have.text', 'komal is not a valid Email Address');
|
||||
cy.hide_dialog();
|
||||
cy.get_field('email', 'Data').clear({force: true});
|
||||
cy.fill_field('email', 'komal@test', 'Data');
|
||||
cy.get('.frappe-control[data-fieldname="email"]').should('have.class', 'has-error');
|
||||
cy.findByRole('button', {name: 'Save'}).click();
|
||||
cy.save();
|
||||
cy.get('.modal-title').should('have.text', 'Message');
|
||||
cy.get('.msgprint').should('have.text', 'komal@test is not a valid Email Address');
|
||||
cy.hide_dialog();
|
||||
|
|
@ -125,4 +131,4 @@ context('Data Control', () => {
|
|||
cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click();
|
||||
cy.click_modal_primary_button('Yes');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const test_button_names = [
|
|||
"Porcupine Tree (the GOAT)",
|
||||
"AC / DC",
|
||||
`Electronic Dance "music"`,
|
||||
"l'imperatrice",
|
||||
];
|
||||
|
||||
const add_button = (label, group = "TestGroup") => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
context('Customize Form', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/customize-form');
|
||||
});
|
||||
it('Changing to naming rule should update autoname', () => {
|
||||
|
|
@ -19,4 +20,4 @@ context('Customize Form', () => {
|
|||
cy.get_field("autoname", "Data").should("have.value", value);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import "cypress-real-events/support";
|
|||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... });
|
||||
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
if (!email) {
|
||||
email = 'Administrator';
|
||||
|
|
@ -265,9 +266,14 @@ Cypress.Commands.add('get_open_dialog', () => {
|
|||
return cy.get('.modal:visible').last();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('save', () => {
|
||||
cy.intercept('/api').as('api');
|
||||
cy.get(`button[data-label="Save"]:visible`).click({scrollBehavior: false, force: true});
|
||||
cy.wait('@api');
|
||||
});
|
||||
Cypress.Commands.add('hide_dialog', () => {
|
||||
cy.wait(300);
|
||||
cy.get_open_dialog().find('.btn-modal-close').click();
|
||||
cy.get_open_dialog().focus().find('.btn-modal-close').click();
|
||||
cy.get('.modal:visible').should('not.exist');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
coverage==5.5
|
||||
Faker~=8.1.0
|
||||
Faker~=13.12.1
|
||||
pyngrok~=5.0.5
|
||||
unittest-xml-reporting~=3.0.4
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import os
|
|||
# imports - standard imports
|
||||
import re
|
||||
import shutil
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
# imports - module imports
|
||||
import frappe
|
||||
|
|
@ -35,6 +36,9 @@ from frappe.query_builder.functions import Concat
|
|||
from frappe.utils import cint
|
||||
from frappe.website.utils import clear_cache
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.custom.doctype.customize_form.customize_form import CustomizeForm
|
||||
|
||||
DEPENDS_ON_PATTERN = re.compile(r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+')
|
||||
ILLEGAL_FIELDNAME_PATTERN = re.compile("""['",./%@()<>{}]""")
|
||||
WHITESPACE_PADDING_PATTERN = re.compile(r"^[ \t\n\r]+|[ \t\n\r]+$", flags=re.ASCII)
|
||||
|
|
@ -916,11 +920,11 @@ def validate_series(dt, autoname=None, name=None):
|
|||
frappe.throw(_("Series {0} already used in {1}").format(prefix, used_in[0][0]))
|
||||
|
||||
|
||||
def validate_autoincrement_autoname(dt: DocType) -> bool:
|
||||
def validate_autoincrement_autoname(dt: Union[DocType, "CustomizeForm"]) -> bool:
|
||||
"""Checks if can doctype can change to/from autoincrement autoname"""
|
||||
|
||||
def get_autoname_before_save(dt: DocType) -> str:
|
||||
if dt.name == "Customize Form":
|
||||
def get_autoname_before_save(dt: Union[DocType, "CustomizeForm"]) -> str:
|
||||
if dt.doctype == "Customize Form":
|
||||
property_value = frappe.db.get_value(
|
||||
"Property Setter", {"doc_type": dt.doc_type, "property": "autoname"}, "value"
|
||||
)
|
||||
|
|
@ -943,10 +947,10 @@ def validate_autoincrement_autoname(dt: DocType) -> bool:
|
|||
or (not is_autoname_autoincrement and autoname_before_save == "autoincrement")
|
||||
):
|
||||
|
||||
if frappe.get_meta(dt.name).issingle:
|
||||
if dt.name == "Customize Form":
|
||||
frappe.throw(_("Cannot change to/from autoincrement autoname in Customize Form"))
|
||||
if dt.doctype == "Customize Form":
|
||||
frappe.throw(_("Cannot change to/from autoincrement autoname in Customize Form"))
|
||||
|
||||
if frappe.get_meta(dt.name).issingle:
|
||||
return False
|
||||
|
||||
if not frappe.get_all(dt.name, limit=1):
|
||||
|
|
|
|||
|
|
@ -341,9 +341,9 @@ class File(Document):
|
|||
|
||||
size = width, height
|
||||
if crop:
|
||||
image = ImageOps.fit(image, size, Image.ANTIALIAS)
|
||||
image = ImageOps.fit(image, size, Image.Resampling.LANCZOS)
|
||||
else:
|
||||
image.thumbnail(size, Image.ANTIALIAS)
|
||||
image.thumbnail(size, Image.Resampling.LANCZOS)
|
||||
|
||||
thumbnail_url = f"{filename}_{suffix}.{extn}"
|
||||
path = os.path.abspath(frappe.get_site_path("public", thumbnail_url.lstrip("/")))
|
||||
|
|
|
|||
|
|
@ -32,6 +32,19 @@ class TestVersion(unittest.TestCase):
|
|||
self.assertEqual(get_old_values(diff)[1], "01-01-2014 00:00:00")
|
||||
self.assertEqual(get_new_values(diff)[1], "07-20-2017 00:00:00")
|
||||
|
||||
def test_no_version_on_new_doc(self):
|
||||
from frappe.desk.form.load import get_versions
|
||||
|
||||
t = frappe.get_doc(doctype="ToDo", description="something")
|
||||
t.save(ignore_version=False)
|
||||
|
||||
self.assertFalse(get_versions(t))
|
||||
|
||||
t = frappe.get_doc(t.doctype, t.name)
|
||||
t.description = "changed"
|
||||
t.save(ignore_version=False)
|
||||
self.assertTrue(get_versions(t))
|
||||
|
||||
|
||||
def get_fieldnames(change_array):
|
||||
return [d[0] for d in change_array]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# License: MIT. See LICENSE
|
||||
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
import frappe
|
||||
from frappe.model import no_value_fields, table_fields
|
||||
|
|
@ -9,7 +10,15 @@ from frappe.model.document import Document
|
|||
|
||||
|
||||
class Version(Document):
|
||||
def set_diff(self, old, new):
|
||||
def update_version_info(self, old: Optional[Document], new: Document) -> bool:
|
||||
"""Update changed info and return true if change contains useful data."""
|
||||
if not old:
|
||||
# Check if doc has some information about creation source like data import
|
||||
return self.for_insert(new)
|
||||
else:
|
||||
return self.set_diff(old, new)
|
||||
|
||||
def set_diff(self, old: Document, new: Document) -> bool:
|
||||
"""Set the data property with the diff of the docs if present"""
|
||||
diff = get_diff(old, new)
|
||||
if diff:
|
||||
|
|
@ -20,8 +29,11 @@ class Version(Document):
|
|||
else:
|
||||
return False
|
||||
|
||||
def for_insert(self, doc):
|
||||
def for_insert(self, doc: Document) -> bool:
|
||||
updater_reference = doc.flags.updater_reference
|
||||
if not updater_reference:
|
||||
return False
|
||||
|
||||
data = {
|
||||
"creation": doc.creation,
|
||||
"updater_reference": updater_reference,
|
||||
|
|
@ -29,7 +41,8 @@ class Version(Document):
|
|||
}
|
||||
self.ref_doctype = doc.doctype
|
||||
self.docname = doc.name
|
||||
self.data = frappe.as_json(data)
|
||||
self.data = frappe.as_json(data, indent=None, separators=(",", ":"))
|
||||
return True
|
||||
|
||||
def get_data(self):
|
||||
return json.loads(self.data)
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ class Document(BaseDocument):
|
|||
|
||||
def get_title(self):
|
||||
"""Get the document title based on title_field or `title` or `name`"""
|
||||
return self.get(self.meta.get_title_field())
|
||||
return self.get(self.meta.get_title_field()) or ""
|
||||
|
||||
def set_title_field(self):
|
||||
"""Set title field based on template"""
|
||||
|
|
@ -1198,11 +1198,10 @@ class Document(BaseDocument):
|
|||
return
|
||||
|
||||
version = frappe.new_doc("Version")
|
||||
if not self._doc_before_save:
|
||||
version.for_insert(self)
|
||||
version.insert(ignore_permissions=True)
|
||||
elif version.set_diff(self._doc_before_save, self):
|
||||
|
||||
if is_useful_diff := version.update_version_info(self._doc_before_save, self):
|
||||
version.insert(ignore_permissions=True)
|
||||
|
||||
if not frappe.flags.in_migrate:
|
||||
# follow since you made a change?
|
||||
if frappe.get_cached_value("User", frappe.session.user, "follow_created_documents"):
|
||||
|
|
|
|||
|
|
@ -252,10 +252,15 @@ class Meta(Document):
|
|||
else:
|
||||
label = {
|
||||
"name": _("ID"),
|
||||
"owner": _("Created By"),
|
||||
"modified_by": _("Modified By"),
|
||||
"creation": _("Created On"),
|
||||
"modified": _("Last Modified On"),
|
||||
"docstatus": _("Document Status"),
|
||||
"idx": _("Index"),
|
||||
"modified": _("Last Updated On"),
|
||||
"modified_by": _("Last Updated By"),
|
||||
"owner": _("Created By"),
|
||||
"_user_tags": _("Tags"),
|
||||
"_liked_by": _("Liked By"),
|
||||
"_comments": _("Comments"),
|
||||
"_assign": _("Assigned To"),
|
||||
}.get(fieldname) or _("No Label")
|
||||
return label
|
||||
|
|
|
|||
|
|
@ -5,6 +5,21 @@ frappe.provide("frappe.model");
|
|||
apply to both DocType form and customize form.
|
||||
*/
|
||||
frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form.Controller {
|
||||
setup() {
|
||||
// setup formatters for fieldtype
|
||||
frappe.meta.docfield_map[this.frm.doctype==='DocType' ? 'DocField' : 'Customize Form Field'].fieldtype.formatter = (value) => {
|
||||
const prefix = {
|
||||
'Tab Break': '🔴',
|
||||
'Section Break': '🔵',
|
||||
'Column Break': '🟡',
|
||||
};
|
||||
if (prefix[value]) {
|
||||
value = prefix[value] + ' ' + value;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
max_attachments() {
|
||||
if (!this.frm.doc.max_attachments) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ frappe.ui.form.Dashboard = class FormDashboard {
|
|||
});
|
||||
|
||||
this.heatmap_area = this.make_section({
|
||||
label: __("Overview"),
|
||||
label: __("Activity"),
|
||||
css_class: 'form-heatmap',
|
||||
hidden: 1,
|
||||
collapsible: 1,
|
||||
|
|
|
|||
|
|
@ -15,17 +15,35 @@ frappe.form.formatters = {
|
|||
return "<div style='text-align: right'>" + value + "</div>";
|
||||
}
|
||||
},
|
||||
_apply_custom_formatter: function(value, df) {
|
||||
/* you can add a custom formatter in df.formatter
|
||||
example:
|
||||
frappe.meta.docfield_map[df.parent][df.fieldname].formatter = (value) => {
|
||||
if (value==='Test') return '😜';
|
||||
}
|
||||
*/
|
||||
|
||||
if (df) {
|
||||
const std_df = frappe.meta.docfield_map[df.parent] && frappe.meta.docfield_map[df.parent][df.fieldname];
|
||||
if (std_df && std_df.formatter && typeof std_df.formatter==='function') {
|
||||
value = std_df.formatter(value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
Data: function(value, df) {
|
||||
if (df && df.options == "URL") {
|
||||
return `<a href="${value}" title="Open Link" target="_blank">${value}</a>`;
|
||||
}
|
||||
return value==null ? "" : value;
|
||||
value = value==null ? "" : value;
|
||||
|
||||
return frappe.form.formatters._apply_custom_formatter(value, df);
|
||||
},
|
||||
Autocomplete: function(value) {
|
||||
return __(frappe.form.formatters["Data"](value));
|
||||
Autocomplete: function(value, df) {
|
||||
return __(frappe.form.formatters["Data"](value, df));
|
||||
},
|
||||
Select: function(value) {
|
||||
return __(frappe.form.formatters["Data"](value));
|
||||
Select: function(value, df) {
|
||||
return __(frappe.form.formatters["Data"](value, df));
|
||||
},
|
||||
Float: function(value, docfield, options, doc) {
|
||||
// don't allow 0 precision for Floats, hence or'ing with null
|
||||
|
|
@ -183,7 +201,7 @@ frappe.form.formatters = {
|
|||
return "";
|
||||
}
|
||||
},
|
||||
Text: function(value) {
|
||||
Text: function(value, df) {
|
||||
if(value) {
|
||||
var tags = ["<p", "<div", "<br", "<table"];
|
||||
var match = false;
|
||||
|
|
@ -200,7 +218,7 @@ frappe.form.formatters = {
|
|||
}
|
||||
}
|
||||
|
||||
return frappe.form.formatters.Data(value);
|
||||
return frappe.form.formatters.Data(value, df);
|
||||
},
|
||||
Time: function(value) {
|
||||
if (value) {
|
||||
|
|
|
|||
|
|
@ -122,7 +122,8 @@ frappe.ui.form.Layout = class Layout {
|
|||
}
|
||||
|
||||
if (this.is_tabbed_layout()) {
|
||||
let default_tab = {label: __('Details'), fieldname: 'details', fieldtype: "Tab Break"};
|
||||
// add a tab without `fieldname` to avoid conflicts
|
||||
let default_tab = {label: __('Details'), fieldtype: "Tab Break"};
|
||||
let first_tab = this.fields[1].fieldtype === "Tab Break" ? this.fields[1] : null;
|
||||
if (!first_tab) {
|
||||
this.fields.splice(1, 0, default_tab);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export default class Tab {
|
|||
this.parent = parent;
|
||||
this.df = df || {};
|
||||
this.frm = frm;
|
||||
this.doctype = 'User';
|
||||
this.doctype = this.frm.doctype;
|
||||
this.label = this.df && this.df.label;
|
||||
this.tabs_list = tabs_list;
|
||||
this.tabs_content = tabs_content;
|
||||
|
|
|
|||
|
|
@ -740,7 +740,7 @@ class FilterArea {
|
|||
let fields = [
|
||||
{
|
||||
fieldtype: "Data",
|
||||
label: "Name",
|
||||
label: "ID",
|
||||
condition: "like",
|
||||
fieldname: "name",
|
||||
onchange: () => this.refresh_list_view(),
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ export default class BulkOperations {
|
|||
const default_field = field_options.find(value => status_regex.test(value));
|
||||
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __('Edit'),
|
||||
title: __('Bulk Edit'),
|
||||
fields: [
|
||||
{
|
||||
'fieldtype': 'Select',
|
||||
|
|
@ -225,7 +225,9 @@ export default class BulkOperations {
|
|||
'fieldtype': 'Data',
|
||||
'label': __('Value'),
|
||||
'fieldname': 'value',
|
||||
'reqd': 1
|
||||
onchange() {
|
||||
show_help_text();
|
||||
}
|
||||
}
|
||||
],
|
||||
primary_action: ({ value }) => {
|
||||
|
|
@ -239,7 +241,7 @@ export default class BulkOperations {
|
|||
docnames: docnames,
|
||||
action: 'update',
|
||||
data: {
|
||||
[fieldname]: value
|
||||
[fieldname]: value || null
|
||||
}
|
||||
}
|
||||
}).then(r => {
|
||||
|
|
@ -254,10 +256,11 @@ export default class BulkOperations {
|
|||
frappe.show_alert(__('Updated successfully'));
|
||||
});
|
||||
},
|
||||
primary_action_label: __('Update')
|
||||
primary_action_label: __('Update {0} records', [docnames.length]),
|
||||
});
|
||||
|
||||
if (default_field) set_value_field(dialog); // to set `Value` df based on default `Field`
|
||||
show_help_text();
|
||||
|
||||
function set_value_field (dialogObj) {
|
||||
const new_df = Object.assign({},
|
||||
|
|
@ -275,9 +278,20 @@ export default class BulkOperations {
|
|||
new_df.default = options[0] || options[1];
|
||||
}
|
||||
new_df.label = __('Value');
|
||||
new_df.reqd = 1;
|
||||
new_df.onchange = show_help_text;
|
||||
|
||||
delete new_df.depends_on;
|
||||
dialogObj.replace_field('value', new_df);
|
||||
show_help_text();
|
||||
}
|
||||
|
||||
function show_help_text() {
|
||||
let value = dialog.get_value('value');
|
||||
if (value == null || value === '') {
|
||||
dialog.set_df_property('value', 'description', __('You have not entered a value. The field will be set to empty.'));
|
||||
} else {
|
||||
dialog.set_df_property('value', 'description', '');
|
||||
}
|
||||
}
|
||||
|
||||
dialog.refresh();
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ export default class ListSettings {
|
|||
let me = this;
|
||||
|
||||
me.subject_field = {
|
||||
label: "Name",
|
||||
label: "ID",
|
||||
fieldname: "name"
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
this.columns.push({
|
||||
type: "Subject",
|
||||
df: {
|
||||
label: __("Name"),
|
||||
label: __("ID"),
|
||||
fieldname: "name",
|
||||
},
|
||||
});
|
||||
|
|
@ -398,7 +398,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
this.columns.push({
|
||||
type: "Field",
|
||||
df: {
|
||||
label: __("Name"),
|
||||
label: __("ID"),
|
||||
fieldname: "name",
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ $.extend(frappe.meta, {
|
|||
var fields = $.map(frappe.meta.get_docfields(doctype, name), function(df) {
|
||||
return (df.fieldtype==="Link" && df.ignore_user_permissions!==1) ? df : null;
|
||||
});
|
||||
fields = fields.concat({label: "Name", fieldname: name, options: doctype});
|
||||
fields = fields.concat({label: "ID", fieldname: name, options: doctype});
|
||||
return fields;
|
||||
},
|
||||
|
||||
|
|
@ -177,12 +177,17 @@ $.extend(frappe.meta, {
|
|||
|
||||
get_label: function(dt, fn, dn) {
|
||||
var standard = {
|
||||
'owner': __('Owner'),
|
||||
'name': __('ID'),
|
||||
'creation': __('Created On'),
|
||||
'modified': __('Last Modified On'),
|
||||
'idx': __('Idx'),
|
||||
'name': __('Name'),
|
||||
'modified_by': __('Last Modified By')
|
||||
'docstatus': __('Document Status'),
|
||||
'idx': __('Index'),
|
||||
'modified': __('Last Updated On'),
|
||||
'modified_by': __('Last Updated By'),
|
||||
'owner': __('Created By'),
|
||||
'_user_tags': __('Tags'),
|
||||
'_liked_by': __('Liked By'),
|
||||
'_comments': __('Comments'),
|
||||
'_assign': __('Assigned To'),
|
||||
}
|
||||
if(standard[fn]) {
|
||||
return standard[fn];
|
||||
|
|
|
|||
|
|
@ -510,7 +510,7 @@ frappe.ui.Page = class Page {
|
|||
|
||||
if (!label || !parent) return false;
|
||||
|
||||
const item_selector = `${selector}[data-label='${encodeURIComponent(label)}']`;
|
||||
const item_selector = `${selector}[data-label="${encodeURIComponent(label)}"]`;
|
||||
|
||||
const existing_items = $(parent).find(item_selector);
|
||||
return existing_items?.length > 0 && existing_items;
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ export default class OnboardingWidget extends Widget {
|
|||
const on_finish = () => {
|
||||
let msg_dialog = frappe.msgprint({
|
||||
message: __("Let's take you back to onboarding"),
|
||||
title: __("Great Job"),
|
||||
title: __("Onboarding complete"),
|
||||
primary_action: {
|
||||
action: () => {
|
||||
frappe.set_route(current_route).then(() => {
|
||||
|
|
@ -265,7 +265,7 @@ export default class OnboardingWidget extends Widget {
|
|||
|
||||
if (success) {
|
||||
args.message = __("Let's take you back to onboarding");
|
||||
args.title = __("Looks Great");
|
||||
args.title = __("Action Complete");
|
||||
args.primary_action = {
|
||||
action: () => {
|
||||
frappe.set_route(current_route).then(() => {
|
||||
|
|
@ -278,7 +278,7 @@ export default class OnboardingWidget extends Widget {
|
|||
custom_onhide = () => args.primary_action.action();
|
||||
} else {
|
||||
args.message = __("Looks like you didn't change the value");
|
||||
args.title = __("Oops");
|
||||
args.title = __("Try Again");
|
||||
args.secondary_action = {
|
||||
action: () => frappe.set_route(current_route),
|
||||
label: __("Go Back"),
|
||||
|
|
@ -314,7 +314,7 @@ export default class OnboardingWidget extends Widget {
|
|||
const on_finish = () => {
|
||||
frappe.msgprint({
|
||||
message: __("Awesome, now try making an entry yourself"),
|
||||
title: __("Great"),
|
||||
title: __("Document Saved"),
|
||||
primary_action: {
|
||||
action: () => {
|
||||
frappe.set_route(current_route).then(() => {
|
||||
|
|
@ -337,8 +337,8 @@ export default class OnboardingWidget extends Widget {
|
|||
|
||||
let callback = () => {
|
||||
frappe.msgprint({
|
||||
message: __("You're doing great, let's take you back to the onboarding page."),
|
||||
title: __("Good Work 🎉"),
|
||||
message: __("Let's take you back to onboarding"),
|
||||
title: __("Action Complete"),
|
||||
primary_action: {
|
||||
action: () => {
|
||||
frappe.set_route(current_route).then(() => {
|
||||
|
|
@ -358,7 +358,7 @@ export default class OnboardingWidget extends Widget {
|
|||
frappe.route_hooks.after_save = () => {
|
||||
frappe.msgprint({
|
||||
message: __("Submit this document to complete this step."),
|
||||
title: __("Great")
|
||||
title: __("Document Saved")
|
||||
});
|
||||
};
|
||||
frappe.route_hooks.after_submit = callback;
|
||||
|
|
@ -377,7 +377,7 @@ export default class OnboardingWidget extends Widget {
|
|||
if (frappe.get_route_str() != current_route) {
|
||||
let success_dialog = frappe.msgprint({
|
||||
message: __("Let's take you back to onboarding"),
|
||||
title: __("Looks Great"),
|
||||
title: __("Document Saved"),
|
||||
primary_action: {
|
||||
action: () => {
|
||||
success_dialog.hide();
|
||||
|
|
@ -397,7 +397,7 @@ export default class OnboardingWidget extends Widget {
|
|||
} else {
|
||||
frappe.msgprint({
|
||||
message: __("Let us continue with the onboarding"),
|
||||
title: __("Looks Great")
|
||||
title: __("Document Saved")
|
||||
});
|
||||
this.mark_complete(step);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@
|
|||
}
|
||||
|
||||
.link-btn {
|
||||
top: 8px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
|
|
|
|||
|
|
@ -421,8 +421,8 @@ body {
|
|||
display: none;
|
||||
}
|
||||
|
||||
i {
|
||||
color: var(--green-600);
|
||||
.icon {
|
||||
stroke: var(--white);
|
||||
}
|
||||
|
||||
span {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ body {
|
|||
.for-forgot,
|
||||
.for-signup,
|
||||
.for-email-login {
|
||||
padding: max(15vh, 70px) 0;
|
||||
padding: max(10vh, 60px) 0;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.page-card {
|
||||
|
|
@ -177,6 +177,7 @@ body {
|
|||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 1rem;
|
||||
font-size: var(--text-xl);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ class TestFormLoad(unittest.TestCase):
|
|||
self.assertEqual(len(docinfo.comments), 1)
|
||||
self.assertIn("test", docinfo.comments[0].content)
|
||||
|
||||
self.assertGreaterEqual(len(docinfo.versions), 2)
|
||||
self.assertGreaterEqual(len(docinfo.versions), 1)
|
||||
|
||||
self.assertEqual(set(docinfo.tags.split(",")), {"more_tag", "test_tag"})
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import io
|
||||
import unittest
|
||||
|
||||
from PyPDF2 import PdfFileReader
|
||||
from PyPDF2 import PdfReader
|
||||
|
||||
import frappe
|
||||
import frappe.utils.pdf as pdfgen
|
||||
|
|
@ -42,7 +42,7 @@ class TestPdf(unittest.TestCase):
|
|||
def test_pdf_encryption(self):
|
||||
password = "qwe"
|
||||
pdf = pdfgen.get_pdf(self.html, options={"password": password})
|
||||
reader = PdfFileReader(io.BytesIO(pdf))
|
||||
reader = PdfReader(io.BytesIO(pdf))
|
||||
self.assertTrue(reader.isEncrypted)
|
||||
self.assertTrue(reader.decrypt(password))
|
||||
|
||||
|
|
|
|||
|
|
@ -1349,7 +1349,7 @@ def get_thumbnail_base64_for_image(src):
|
|||
|
||||
original_size = image.size
|
||||
size = 50, 50
|
||||
image.thumbnail(size, Image.ANTIALIAS)
|
||||
image.thumbnail(size, Image.Resampling.LANCZOS)
|
||||
|
||||
base64_string = image_to_base64(image, extn)
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ from PIL import Image
|
|||
|
||||
|
||||
def resize_images(path, maxdim=700):
|
||||
from PIL import Image
|
||||
|
||||
size = (maxdim, maxdim)
|
||||
for basepath, folders, files in os.walk(path):
|
||||
for fname in files:
|
||||
|
|
@ -16,7 +14,7 @@ def resize_images(path, maxdim=700):
|
|||
if extn in ("jpg", "jpeg", "png", "gif"):
|
||||
im = Image.open(os.path.join(basepath, fname))
|
||||
if im.size[0] > size[0] or im.size[1] > size[1]:
|
||||
im.thumbnail(size, Image.ANTIALIAS)
|
||||
im.thumbnail(size, Image.Resampling.LANCZOS)
|
||||
im.save(os.path.join(basepath, fname))
|
||||
|
||||
print("resized {0}".format(os.path.join(basepath, fname)))
|
||||
|
|
@ -56,7 +54,7 @@ def optimize_image(
|
|||
image = Image.open(io.BytesIO(content))
|
||||
image_format = content_type.split("/")[1]
|
||||
size = max_width, max_height
|
||||
image.thumbnail(size, Image.LANCZOS)
|
||||
image.thumbnail(size, Image.Resampling.LANCZOS)
|
||||
|
||||
output = io.BytesIO()
|
||||
image.save(
|
||||
|
|
|
|||
|
|
@ -213,21 +213,16 @@ def decrypt(txt, encryption_key=None):
|
|||
|
||||
try:
|
||||
cipher_suite = Fernet(encode(encryption_key or get_encryption_key()))
|
||||
plain_text = cstr(cipher_suite.decrypt(encode(txt)))
|
||||
return plain_text
|
||||
return cstr(cipher_suite.decrypt(encode(txt)))
|
||||
except InvalidToken:
|
||||
# encryption_key in site_config is changed and not valid
|
||||
frappe.throw(
|
||||
_("Encryption key is invalid") + "!"
|
||||
if encryption_key
|
||||
else _(", please check site_config.json.")
|
||||
)
|
||||
frappe.throw(_("Encryption key is invalid! Please check site_config.json"))
|
||||
|
||||
|
||||
def get_encryption_key():
|
||||
from frappe.installer import update_site_config
|
||||
|
||||
if "encryption_key" not in frappe.local.conf:
|
||||
from frappe.installer import update_site_config
|
||||
|
||||
encryption_key = Fernet.generate_key().decode()
|
||||
update_site_config("encryption_key", encryption_key)
|
||||
frappe.local.conf.encryption_key = encryption_key
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ import os
|
|||
import re
|
||||
import subprocess
|
||||
from distutils.version import LooseVersion
|
||||
from typing import Optional
|
||||
|
||||
import pdfkit
|
||||
from bs4 import BeautifulSoup
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
from PyPDF2 import PdfReader, PdfWriter
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
|
@ -23,7 +24,7 @@ PDF_CONTENT_ERRORS = [
|
|||
]
|
||||
|
||||
|
||||
def get_pdf(html, options=None, output=None):
|
||||
def get_pdf(html, options=None, output: Optional[PdfWriter] = None):
|
||||
html = scrub_urls(html)
|
||||
html, options = prepare_options(html, options)
|
||||
|
||||
|
|
@ -35,11 +36,10 @@ def get_pdf(html, options=None, output=None):
|
|||
|
||||
try:
|
||||
# Set filename property to false, so no file is actually created
|
||||
filedata = pdfkit.from_string(html, False, options=options or {})
|
||||
filedata = pdfkit.from_string(html, options=options or {}, verbose=True)
|
||||
|
||||
# https://pythonhosted.org/PyPDF2/PdfFileReader.html
|
||||
# create in-memory binary streams from filedata and create a PdfFileReader object
|
||||
reader = PdfFileReader(io.BytesIO(filedata))
|
||||
# create in-memory binary streams from filedata and create a PdfReader object
|
||||
reader = PdfReader(io.BytesIO(filedata))
|
||||
except OSError as e:
|
||||
if any([error in str(e) for error in PDF_CONTENT_ERRORS]):
|
||||
if not filedata:
|
||||
|
|
@ -47,8 +47,8 @@ def get_pdf(html, options=None, output=None):
|
|||
frappe.throw(_("PDF generation failed because of broken image links"))
|
||||
|
||||
# allow pdfs with missing images if file got created
|
||||
if output: # output is a PdfFileWriter object
|
||||
output.appendPagesFromReader(reader)
|
||||
if output:
|
||||
output.append_pages_from_reader(reader)
|
||||
else:
|
||||
raise
|
||||
finally:
|
||||
|
|
@ -58,11 +58,11 @@ def get_pdf(html, options=None, output=None):
|
|||
password = options["password"]
|
||||
|
||||
if output:
|
||||
output.appendPagesFromReader(reader)
|
||||
output.append_pages_from_reader(reader)
|
||||
return output
|
||||
|
||||
writer = PdfFileWriter()
|
||||
writer.appendPagesFromReader(reader)
|
||||
writer = PdfWriter()
|
||||
writer.append_pages_from_reader(reader)
|
||||
|
||||
if "password" in options:
|
||||
writer.encrypt(password)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
|
||||
from PyPDF2 import PdfFileWriter
|
||||
from PyPDF2 import PdfWriter
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
|
@ -58,7 +58,7 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options=
|
|||
|
||||
import json
|
||||
|
||||
output = PdfFileWriter()
|
||||
output = PdfWriter()
|
||||
|
||||
if isinstance(options, str):
|
||||
options = json.loads(options)
|
||||
|
|
@ -152,7 +152,7 @@ def print_by_server(
|
|||
cups.setServer(print_settings.server_ip)
|
||||
cups.setPort(print_settings.port)
|
||||
conn = cups.Connection()
|
||||
output = PdfFileWriter()
|
||||
output = PdfWriter()
|
||||
output = frappe.get_print(
|
||||
doctype, name, print_format, doc=doc, no_letterhead=no_letterhead, as_pdf=True, output=output
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,10 +55,10 @@ def get_context(context):
|
|||
)
|
||||
|
||||
signup_form_template = frappe.get_hooks("signup_form_template")
|
||||
if signup_form_template and len(signup_form_template) and signup_form_template[0]:
|
||||
path = signup_form_template[0]
|
||||
if signup_form_template and len(signup_form_template):
|
||||
path = signup_form_template[-1]
|
||||
if not guess_is_path(path):
|
||||
path = frappe.get_attr(signup_form_template[0])()
|
||||
path = frappe.get_attr(signup_form_template[-1])()
|
||||
else:
|
||||
path = "frappe/templates/signup.html"
|
||||
if path:
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ def get_context(context):
|
|||
"body": body,
|
||||
"print_style": print_style,
|
||||
"comment": frappe.session.user,
|
||||
"title": frappe.utils.strip_html(doc.get_title()),
|
||||
"title": frappe.utils.strip_html(doc.get_title() or doc.name),
|
||||
"lang": frappe.local.lang,
|
||||
"layout_direction": "rtl" if is_rtl() else "ltr",
|
||||
"doctype": frappe.form_dict.doctype,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ boto3~=1.17.53
|
|||
braintree~=4.8.0
|
||||
chardet~=4.0.0
|
||||
Click~=7.1.2
|
||||
croniter~=1.0.11
|
||||
cryptography~=3.4.7
|
||||
croniter~=1.3.5
|
||||
cryptography~=37.0.2
|
||||
dropbox~=11.7.0
|
||||
email-reply-parser~=0.5.12
|
||||
git-url-parse~=1.2.2
|
||||
|
|
@ -21,8 +21,8 @@ googlemaps~=4.4.5
|
|||
gunicorn~=20.1.0
|
||||
html2text==2020.1.16
|
||||
html5lib~=1.1
|
||||
ipython~=7.31.1
|
||||
Jinja2~=3.0.1
|
||||
ipython~=8.4.0
|
||||
Jinja2~=3.1.2
|
||||
ldap3~=2.9
|
||||
markdown2~=2.4.0
|
||||
maxminddb-geolite2==2018.703
|
||||
|
|
@ -32,10 +32,10 @@ openpyxl~=3.0.7
|
|||
parse~=1.19.0
|
||||
passlib~=1.7.4
|
||||
paytmchecksum~=1.7.0
|
||||
pdfkit~=0.6.1
|
||||
Pillow~=9.0.0
|
||||
pdfkit~=1.0.0
|
||||
Pillow~=9.1.1
|
||||
premailer~=3.8.0
|
||||
psutil~=5.8.0
|
||||
psutil~=5.9.1
|
||||
psycopg2-binary~=2.9.1
|
||||
pyasn1~=0.4.8
|
||||
pycryptodome~=3.10.1
|
||||
|
|
@ -43,29 +43,29 @@ PyJWT~=2.0.1
|
|||
PyMySQL~=1.0.2
|
||||
pyOpenSSL~=20.0.1
|
||||
pyotp~=2.6.0
|
||||
PyPDF2~=1.26.0
|
||||
PyPDF2~=2.1.0
|
||||
PyPika~=0.48.9
|
||||
pypng~=0.0.20
|
||||
PyQRCode~=1.2.1
|
||||
python-dateutil~=2.8.1
|
||||
pytz==2021.1
|
||||
pytz==2022.1
|
||||
PyYAML~=5.4.1
|
||||
rauth~=0.7.3
|
||||
razorpay~=1.2.0
|
||||
redis~=3.5.3
|
||||
requests-oauthlib~=1.3.0
|
||||
requests~=2.25.1
|
||||
requests~=2.27.1
|
||||
RestrictedPython~=5.1
|
||||
rq~=1.8.0
|
||||
rq~=1.10.1
|
||||
rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
schedule~=1.1.0
|
||||
semantic-version~=2.8.5
|
||||
semantic-version~=2.10.0
|
||||
sqlparse~=0.4.1
|
||||
stripe~=2.56.0
|
||||
terminaltables~=3.1.0
|
||||
traceback-with-variables~=2.0.4
|
||||
urllib3~=1.26.4
|
||||
Werkzeug~=2.0.3
|
||||
Werkzeug~=2.1.2
|
||||
Whoosh~=2.7.4
|
||||
xlrd~=2.0.1
|
||||
zxcvbn-python~=4.4.24
|
||||
|
|
@ -73,4 +73,3 @@ tenacity~=8.0.1
|
|||
cairocffi==1.2.0
|
||||
WeasyPrint==52.5
|
||||
phonenumbers==8.12.40
|
||||
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -53,7 +53,6 @@ setup(
|
|||
zip_safe=False,
|
||||
include_package_data=True,
|
||||
install_requires=install_requires,
|
||||
dependency_links=["https://github.com/frappe/python-pdfkit.git#egg=pdfkit"],
|
||||
cmdclass={"clean": CleanCommand},
|
||||
python_requires=">=3.8",
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue