Merge branch 'develop' into fix/user-type

This commit is contained in:
Sagar Sharma 2022-06-13 03:00:21 -07:00 committed by GitHub
commit b237c653a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 211 additions and 117 deletions

View file

@ -7,6 +7,7 @@ pull_request_rules:
- author!=gavindsouza
- author!=deepeshgarg007
- author!=ankush
- author!=mergify[bot]
- or:
- base=version-13
- base=version-12

View file

@ -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%');
});

View file

@ -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');
});
});
});

View file

@ -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") => {

View file

@ -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);
});
});
});
});

View file

@ -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');
});

View file

@ -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

View file

@ -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):

View file

@ -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("/")))

View file

@ -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]

View file

@ -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)

View file

@ -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"):

View file

@ -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

View file

@ -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;

View file

@ -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,

View file

@ -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) {

View file

@ -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);

View file

@ -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;

View file

@ -740,7 +740,7 @@ class FilterArea {
let fields = [
{
fieldtype: "Data",
label: "Name",
label: "ID",
condition: "like",
fieldname: "name",
onchange: () => this.refresh_list_view(),

View file

@ -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();

View file

@ -310,7 +310,7 @@ export default class ListSettings {
let me = this;
me.subject_field = {
label: "Name",
label: "ID",
fieldname: "name"
};

View file

@ -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",
},
});

View file

@ -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];

View file

@ -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;

View file

@ -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);
}

View file

@ -201,7 +201,7 @@
}
.link-btn {
top: 8px;
top: 2px;
}
.form-control:focus {

View file

@ -421,8 +421,8 @@ body {
display: none;
}
i {
color: var(--green-600);
.icon {
stroke: var(--white);
}
span {

View file

@ -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);
}

View file

@ -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"})

View file

@ -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))

View file

@ -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 {

View file

@ -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(

View file

@ -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

View file

@ -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)

View file

@ -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
)

View file

@ -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:

View file

@ -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,

View file

@ -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

View file

@ -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",
)