Skip to content

Commit

Permalink
Merge pull request #22888 from frappe/version-14-hotfix
Browse files Browse the repository at this point in the history
chore: release v14
  • Loading branch information
ankush committed Oct 24, 2023
2 parents 25b5861 + aef8322 commit 480e560
Show file tree
Hide file tree
Showing 18 changed files with 230 additions and 26 deletions.
118 changes: 115 additions & 3 deletions cypress/integration/control_attach.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@ context("Attach Control", () => {
//Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype
cy.findByRole("button", { name: "Attach" }).click();

//Clicking on "Link" button to attach a file using the "Link" button
cy.findByRole("button", { name: "Link" }).click();
cy.findByPlaceholderText("Attach a web link").type(
"https://wallpaperplay.com/walls/full/8/2/b/72402.jpg",
{ force: true }
);

//Clicking on the Upload button to upload the file
cy.intercept("POST", "/api/method/upload_file").as("upload_image");
cy.get(".modal-footer").findByRole("button", { name: "Upload" }).click({ delay: 500 });
cy.wait("@upload_image");
cy.findByRole("button", { name: "Save" }).click();

//Navigating to the new form for the newly created doctype to check Library button
cy.new_form("Test Attach Control");

//Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype
cy.findByRole("button", { name: "Attach" }).click();

//Clicking on "Library" button to attach a file using the "Library" button
cy.findByRole("button", { name: "Library" }).click();
cy.contains("72402.jpg").click();
Expand All @@ -85,9 +104,10 @@ context("Attach Control", () => {
//Checking if clicking on the clear button clears the field of the doctype form and again displays the attach button
cy.get(".control-input > .btn-sm").should("contain", "Attach");

//Deleting the doc
//Deleting both docs
cy.go_to_list("Test Attach Control");
cy.get(".list-row-checkbox").eq(0).click();
cy.get(".list-row-checkbox").eq(1).click();
cy.get(".actions-btn-group > .btn").contains("Actions").click();
cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click();
cy.click_modal_primary_button("Yes");
Expand All @@ -106,7 +126,10 @@ context("Attach Control", () => {
};
},
});
cy.get("body").should("have.attr", "data-route", `Form/${doctype}/new-${dt_in_route}-1`);
cy.get("body").should(($body) => {
const dataRoute = $body.attr("data-route");
expect(dataRoute).to.match(new RegExp(`^Form/${doctype}/new-${dt_in_route}-`));
});
cy.get("body").should("have.attr", "data-ajax-state", "complete");

//Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype
Expand All @@ -126,7 +149,10 @@ context("Attach Control", () => {
delete win.navigator.mediaDevices;
},
});
cy.get("body").should("have.attr", "data-route", `Form/${doctype}/new-${dt_in_route}-1`);
cy.get("body").should(($body) => {
const dataRoute = $body.attr("data-route");
expect(dataRoute).to.match(new RegExp(`^Form/${doctype}/new-${dt_in_route}-`));
});
cy.get("body").should("have.attr", "data-ajax-state", "complete");

//Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype
Expand All @@ -136,3 +162,89 @@ context("Attach Control", () => {
cy.findByRole("button", { name: "Camera" }).should("not.exist");
});
});
context("Attach Control with Failed Document Save", () => {
before(() => {
cy.login();
cy.visit("/app/doctype");
return cy
.window()
.its("frappe")
.then((frappe) => {
return frappe.xcall("frappe.tests.ui_test_helpers.create_doctype", {
name: "Test Mandatory Attach Control",
fields: [
{
label: "Attach File or Image",
fieldname: "attach",
fieldtype: "Attach",
in_list_view: 1,
},
{
label: "Mandatory Text Field",
fieldname: "text_field",
fieldtype: "Text Editor",
in_list_view: 1,
reqd: 1,
},
],
});
});
});
let temp_name = "";
let docname = "";
it("Attaching a file on an unsaved document", () => {
//Navigating to the new form for the newly created doctype
cy.new_form("Test Mandatory Attach Control");
cy.get("body").should(($body) => {
temp_name = $body.attr("data-route").split("/")[2];
});

//Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype
cy.findByRole("button", { name: "Attach" }).click();

//Clicking on "Link" button to attach a file using the "Link" button
cy.findByRole("button", { name: "Link" }).click();
cy.findByPlaceholderText("Attach a web link").type(
"https://wallpaperplay.com/walls/full/8/2/b/72402.jpg",
{ force: true }
);

//Clicking on the Upload button to upload the file
cy.intercept("POST", "/api/method/upload_file").as("upload_image");
cy.get(".modal-footer").findByRole("button", { name: "Upload" }).click({ delay: 500 });
cy.wait("@upload_image");
cy.get(".msgprint-dialog .modal-title").contains("Missing Fields").should("be.visible");
cy.hide_dialog();
cy.fill_field("text_field", "Random value", "Text Editor").wait(500);
cy.findByRole("button", { name: "Save" }).click().wait(500);

//Checking if the URL of the attached image is getting displayed in the field of the newly created doctype
cy.get(".attached-file > .ellipsis > .attached-file-link")
.should("have.attr", "href")
.and("equal", "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg");

cy.get(".title-text").then(($value) => {
docname = $value.text();
});
});

it("Check if file was uploaded correctly", () => {
cy.go_to_list("File");
cy.open_list_filter();
cy.get(".fieldname-select-area .form-control")
.click()
.type("Attached To Name{enter}")
.blur()
.wait(500);
cy.get('input[data-fieldname="attached_to_name"]').click().type(docname).blur();
cy.get(".filter-popover .apply-filters").click({ force: true });
cy.get("header .level-right .list-count").should("contain.text", "1 of 1");
});

it("Check if file exists with temporary name", () => {
cy.open_list_filter();
cy.get('input[data-fieldname="attached_to_name"]').click().clear().type(temp_name).blur();
cy.get(".filter-popover .apply-filters").click({ force: true });
cy.get(".frappe-list > .no-result").should("be.visible");
});
});
7 changes: 5 additions & 2 deletions cypress/integration/control_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ context("Data Control", () => {
cy.new_form("Test Data Control");

//Checking the URL for the new form of the doctype
cy.location("pathname").should("eq", "/app/test-data-control/new-test-data-control-1");
cy.location("pathname").should("contains", "/app/test-data-control/new-test-data-control");
cy.get(".title-text").should("have.text", "New Test Data Control");
cy.get('.frappe-control[data-fieldname="name1"]')
.find("label")
Expand Down Expand Up @@ -128,7 +128,10 @@ context("Data Control", () => {
cy.fill_field("phone", "9432380001", "Data");
cy.findByRole("button", { name: "Save" }).click({ force: true });
//Checking if the fields contains the data which has been filled in
cy.location("pathname").should("not.be", "/app/test-data-control/new-test-data-control-1");
cy.location("pathname").should(
"not.contains",
"/app/test-data-control/new-test-data-control"
);
cy.get_field("name1").should("have.value", "Komal");
cy.get_field("email").should("have.value", "komal@test.com");
cy.get_field("phone").should("have.value", "9432380001");
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/dashboard_chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ context("Dashboard Chart", () => {
});

it("Check filter populate for child table doctype", () => {
cy.visit("/app/dashboard-chart/new-dashboard-chart-1");
cy.new_form("Dashboard Chart");
cy.get('[data-fieldname="parent_document_type"]').should("have.css", "display", "none");

cy.get_field("document_type", "Link");
Expand Down
17 changes: 12 additions & 5 deletions cypress/integration/grid_keyboard_shortcut.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
context("Grid Keyboard Shortcut", () => {
let total_count = 0;
let contact_email_name = null;
before(() => {
cy.login();
});
beforeEach(() => {
cy.reload();
cy.visit("/app/contact/new-contact-1");
cy.new_form("Contact");
cy.get('.frappe-control[data-fieldname="email_ids"]').find(".grid-add-row").click();
// as new names uses hash instead of numbers get row's data-name dynamically.
cy.get('.frappe-control[data-fieldname="email_ids"]')
.find(".grid-body .grid-row")
.should(($row) => {
contact_email_name = $row.attr("data-name");
});
});
it("Insert new row at the end", () => {
cy.add_new_row_in_grid(
"{ctrl}{shift}{downarrow}",
(cy, total_count) => {
cy.get('[data-name="new-contact-email-1"]').should(
cy.get(`[data-name="${contact_email_name}"]`).should(
"have.attr",
"data-idx",
`${total_count + 1}`
Expand All @@ -23,17 +30,17 @@ context("Grid Keyboard Shortcut", () => {
});
it("Insert new row at the top", () => {
cy.add_new_row_in_grid("{ctrl}{shift}{uparrow}", (cy) => {
cy.get('[data-name="new-contact-email-1"]').should("have.attr", "data-idx", "2");
cy.get(`[data-name="${contact_email_name}"]`).should("have.attr", "data-idx", "2");
});
});
it("Insert new row below", () => {
cy.add_new_row_in_grid("{ctrl}{downarrow}", (cy) => {
cy.get('[data-name="new-contact-email-1"]').should("have.attr", "data-idx", "1");
cy.get(`[data-name^="${contact_email_name}"]`).should("have.attr", "data-idx", "1");
});
});
it("Insert new row above", () => {
cy.add_new_row_in_grid("{ctrl}{uparrow}", (cy) => {
cy.get('[data-name="new-contact-email-1"]').should("have.attr", "data-idx", "2");
cy.get(`[data-name^="${contact_email_name}"]`).should("have.attr", "data-idx", "2");
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/number_card.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ context("Number Card", () => {
});

it("Check filter populate for child table doctype", () => {
cy.visit("/app/number-card/new-number-card-1");
cy.new_form("Number Card");
cy.get('[data-fieldname="parent_document_type"]').should("have.css", "display", "none");

cy.get_field("document_type", "Link");
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ context("Timeline", () => {

it("Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo", () => {
//Adding new ToDo
cy.visit("/app/todo/new-todo-1");
cy.new_form("ToDo");
cy.get('[data-fieldname="description"] .ql-editor.ql-blank')
.type("Test ToDo", { force: true })
.wait(200);
Expand Down
5 changes: 4 additions & 1 deletion cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ Cypress.Commands.add("awesomebar", (text) => {
Cypress.Commands.add("new_form", (doctype) => {
let dt_in_route = doctype.toLowerCase().replace(/ /g, "-");
cy.visit(`/app/${dt_in_route}/new`);
cy.get("body").should("have.attr", "data-route", `Form/${doctype}/new-${dt_in_route}-1`);
cy.get("body").should(($body) => {
const dataRoute = $body.attr("data-route");
expect(dataRoute).to.match(new RegExp(`^Form/${doctype}/new-${dt_in_route}-`));
});
cy.get("body").should("have.attr", "data-ajax-state", "complete");
});

Expand Down
48 changes: 47 additions & 1 deletion frappe/core/doctype/file/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import frappe
from frappe import _, safe_decode
from frappe.utils import cstr, encode, get_files_path, random_string, strip
from frappe.utils import cint, cstr, encode, get_files_path, random_string, strip
from frappe.utils.file_manager import safe_b64decode
from frappe.utils.image import optimize_image

Expand Down Expand Up @@ -337,6 +337,7 @@ def attach_files_to_document(doc: "Document", event) -> None:
"attached_to_name": doc.name,
"attached_to_doctype": doc.doctype,
"attached_to_field": df.fieldname,
"is_private": cint(value.startswith("/private")),
},
)
return
Expand All @@ -355,6 +356,51 @@ def attach_files_to_document(doc: "Document", event) -> None:
doc.log_error("Error Attaching File")


def relink_files(doc, fieldname, temp_doc_name):
if not temp_doc_name:
return
from frappe.utils.data import add_to_date, now_datetime

"""
Relink files attached to incorrect document name to the new document name
by check if file with temp name exists that was created in last 60 minutes
"""
mislinked_file = frappe.db.exists(
"File",
{
"file_url": doc.get(fieldname),
"attached_to_name": temp_doc_name,
"attached_to_doctype": doc.doctype,
"attached_to_field": fieldname,
"creation": (
"between",
[now_datetime() - add_to_date(date=now_datetime(), minutes=-60), now_datetime()],
),
},
)
"""If file exists, attach it to the new docname"""
if mislinked_file:
frappe.db.set_value(
"File",
mislinked_file,
field={
"attached_to_name": doc.name,
},
)
return


def relink_mismatched_files(doc: "Document") -> None:
if not doc.get("__temporary_name", None):
return
attach_fields = doc.meta.get("fields", {"fieldtype": ["in", ["Attach", "Attach Image"]]})
for df in attach_fields:
if doc.get(df.fieldname):
relink_files(doc, df.fieldname, doc.__temporary_name)
# delete temporary name after relinking is done
doc.delete_key("__temporary_name")


def decode_file_content(content: bytes) -> bytes:
if isinstance(content, str):
content = content.encode("utf-8")
Expand Down
3 changes: 3 additions & 0 deletions frappe/desk/form/save.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def savedocs(doc, action):
"""save / submit / update doclist"""
doc = frappe.get_doc(json.loads(doc))
capture_doc(doc, action)
if doc.get("__islocal") and doc.name.startswith("new-" + doc.doctype.lower().replace(" ", "-")):
# required to relink missing attachments if they exist.
doc.__temporary_name = doc.name
set_local_name(doc)

# action
Expand Down
11 changes: 11 additions & 0 deletions frappe/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ def upload_file():
optimize = frappe.form_dict.optimize
content = None

if frappe.form_dict.get("library_file_name", False):
doc = frappe.get_value(
"File",
frappe.form_dict.library_file_name,
["is_private", "file_url", "file_name"],
as_dict=True,
)
is_private = doc.is_private
file_url = doc.file_url
filename = doc.file_name

if "file" in files:
file = files["file"]
content = file.stream.read()
Expand Down
2 changes: 2 additions & 0 deletions frappe/model/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import frappe
from frappe import _, is_whitelisted, msgprint
from frappe.core.doctype.file.utils import relink_mismatched_files
from frappe.core.doctype.server_script.server_script_utils import run_server_script_for_doc_event
from frappe.desk.form.document_follow import follow_document
from frappe.integrations.doctype.webhook import run_webhooks
Expand Down Expand Up @@ -284,6 +285,7 @@ def insert(
# flag to prevent creation of event update log for create and update both
# during document creation
self.flags.update_log_for_doc_creation = True
relink_mismatched_files(self)
self.run_post_save_methods()
self.flags.in_insert = False

Expand Down
6 changes: 5 additions & 1 deletion frappe/public/js/frappe/file_uploader/FileUploader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ export default {
}
this.close_dialog = true;
return this.upload_file({
file_url: selected_file.file_url
library_file_name: selected_file.value,
});
},
upload_via_web_link() {
Expand Down Expand Up @@ -530,6 +530,10 @@ export default {
form_data.append('file_name', file.file_name);
}
if (file.library_file_name) {
form_data.append('library_file_name', file.library_file_name);
}
if (this.doctype && this.docname) {
form_data.append('doctype', this.doctype);
form_data.append('docname', this.docname);
Expand Down
Loading

0 comments on commit 480e560

Please sign in to comment.