refactor: Webform (#17232)
This commit is contained in:
parent
cf7cb387f3
commit
a50e0ffa08
37 changed files with 1527 additions and 806 deletions
|
|
@ -3,24 +3,253 @@ context('Web Form', () => {
|
|||
cy.login();
|
||||
});
|
||||
|
||||
it('Create Web Form', () => {
|
||||
cy.visit('/app/web-form/new');
|
||||
|
||||
cy.intercept('POST', '/api/method/frappe.desk.form.save.savedocs').as('save_form');
|
||||
|
||||
cy.fill_field('title', 'Note');
|
||||
cy.fill_field('doc_type', 'Note', 'Link');
|
||||
cy.fill_field('module', 'Website', 'Link');
|
||||
cy.click_custom_action_button('Get Fields');
|
||||
cy.click_custom_action_button('Publish');
|
||||
|
||||
cy.wait('@save_form');
|
||||
|
||||
cy.get_field('route').should('have.value', 'note');
|
||||
cy.get('.title-area .indicator-pill').contains('Published');
|
||||
});
|
||||
|
||||
it('Open Web Form (Logged in User)', () => {
|
||||
cy.visit('/note');
|
||||
|
||||
cy.fill_field('title', 'Note 1');
|
||||
cy.get('.web-form-actions button').contains('Save').click();
|
||||
|
||||
cy.url().should('include', '/note/Note%201');
|
||||
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/Note%201');
|
||||
});
|
||||
|
||||
it('Open Web Form (Guest)', () => {
|
||||
cy.request('/api/method/logout');
|
||||
cy.visit('/note');
|
||||
|
||||
cy.url().should('include', '/note/new');
|
||||
|
||||
cy.fill_field('title', 'Guest Note 1');
|
||||
cy.get('.web-form-actions button').contains('Save').click();
|
||||
|
||||
cy.url().should('include', '/note/new');
|
||||
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/new');
|
||||
});
|
||||
|
||||
it('Login Required', () => {
|
||||
cy.login();
|
||||
cy.visit('/app/web-form/note');
|
||||
|
||||
cy.findByRole("tab", {name: "Form Settings"}).click();
|
||||
cy.get('input[data-fieldname="login_required"]').check({force: true});
|
||||
|
||||
cy.save();
|
||||
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/Note%201');
|
||||
|
||||
cy.call('logout');
|
||||
|
||||
cy.visit('/note');
|
||||
cy.get_open_dialog()
|
||||
.get('.modal-message')
|
||||
.contains('You are not permitted to access this page without login.');
|
||||
});
|
||||
|
||||
it('Show List', () => {
|
||||
cy.login();
|
||||
cy.visit('/app/web-form/note');
|
||||
|
||||
cy.findByRole("tab", {name: "List Settings"}).click();
|
||||
cy.get('input[data-fieldname="show_list"]').check();
|
||||
|
||||
cy.save();
|
||||
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/list');
|
||||
cy.get('.web-list-table').should('be.visible');
|
||||
});
|
||||
|
||||
it('Show Custom List Title', () => {
|
||||
cy.visit('/app/web-form/note');
|
||||
|
||||
cy.findByRole("tab", {name: "List Settings"}).click();
|
||||
cy.fill_field('list_title', 'Note List');
|
||||
|
||||
cy.save();
|
||||
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/list');
|
||||
cy.get('.web-list-header h1').should('contain.text', 'Note List');
|
||||
});
|
||||
|
||||
it('Show Custom List Columns', () => {
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/list');
|
||||
|
||||
cy.get('.web-list-table thead th').contains('Name');
|
||||
cy.get('.web-list-table thead th').contains('Title');
|
||||
|
||||
cy.visit('/app/web-form/note');
|
||||
|
||||
cy.findByRole("tab", {name: "List Settings"}).click();
|
||||
|
||||
cy.get('[data-fieldname="list_columns"] .grid-footer button').contains('Add Row').as('add-row');
|
||||
|
||||
cy.get('@add-row').click();
|
||||
cy.get('[data-fieldname="list_columns"] .grid-body .rows').as('grid-rows');
|
||||
cy.get('@grid-rows').find('.grid-row:first [data-fieldname="fieldname"]').click();
|
||||
cy.get('@grid-rows').find('.grid-row:first select[data-fieldname="fieldname"]').select('Title (Data)');
|
||||
|
||||
cy.get('@add-row').click();
|
||||
cy.get('@grid-rows').find('.grid-row[data-idx="2"] [data-fieldname="fieldname"]').click();
|
||||
cy.get('@grid-rows').find('.grid-row[data-idx="2"] select[data-fieldname="fieldname"]').select('Public (Check)');
|
||||
|
||||
cy.get('@add-row').click();
|
||||
cy.get('@grid-rows').find('.grid-row:last [data-fieldname="fieldname"]').click();
|
||||
cy.get('@grid-rows').find('.grid-row:last select[data-fieldname="fieldname"]').select('Content (Text Editor)');
|
||||
|
||||
cy.save();
|
||||
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/list');
|
||||
cy.get('.web-list-table thead th').contains('Title');
|
||||
cy.get('.web-list-table thead th').contains('Public');
|
||||
cy.get('.web-list-table thead th').contains('Content');
|
||||
});
|
||||
|
||||
it('Breadcrumbs', () => {
|
||||
cy.visit('/note/Note 1');
|
||||
cy.get('.breadcrumb-container .breadcrumb .breadcrumb-item:first a')
|
||||
.should('contain.text', 'Note').click();
|
||||
cy.url().should('include', '/note/list');
|
||||
});
|
||||
|
||||
it('Custom Breadcrumbs', () => {
|
||||
cy.visit('/app/web-form/note');
|
||||
|
||||
cy.findByRole("tab", {name: "Form Settings"}).click();
|
||||
cy.get('.form-section .section-head').contains('Customization').click();
|
||||
cy.fill_field('breadcrumbs', '[{"label": _("Notes"), "route":"note"}]', 'Code');
|
||||
cy.get('.form-section .section-head').contains('Customization').click();
|
||||
cy.save();
|
||||
|
||||
cy.visit('/note/Note 1');
|
||||
cy.get('.breadcrumb-container .breadcrumb .breadcrumb-item:first a')
|
||||
.should('contain.text', 'Notes');
|
||||
});
|
||||
|
||||
it('Read Only', () => {
|
||||
cy.login();
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/list');
|
||||
|
||||
// Read Only Field
|
||||
cy.get('.web-list-table tbody tr[id="Note 1"]').click();
|
||||
cy.get('.frappe-control[data-fieldname="title"] .control-input')
|
||||
.should('have.css', 'display', 'none');
|
||||
});
|
||||
|
||||
it('Edit Mode', () => {
|
||||
cy.visit('/app/web-form/note');
|
||||
|
||||
cy.findByRole("tab", {name: "Form Settings"}).click();
|
||||
cy.get('input[data-fieldname="allow_edit"]').check();
|
||||
|
||||
cy.save();
|
||||
|
||||
cy.visit('/note/Note 1');
|
||||
cy.url().should('include', '/note/Note%201');
|
||||
|
||||
cy.get('.web-form-actions a').contains('Edit').click();
|
||||
cy.url().should('include', '/note/Note%201/edit');
|
||||
|
||||
// Editable Field
|
||||
cy.get_field('title').should('have.value', 'Note 1');
|
||||
|
||||
cy.fill_field('title', ' Edited');
|
||||
cy.get('.web-form-actions button').contains('Save').click();
|
||||
cy.get_field('title').should('have.value', 'Note 1 Edited');
|
||||
});
|
||||
|
||||
it('Allow Multiple Response', () => {
|
||||
cy.visit('/app/web-form/note');
|
||||
|
||||
cy.findByRole("tab", {name: "Form Settings"}).click();
|
||||
cy.get('input[data-fieldname="allow_multiple"]').check();
|
||||
|
||||
cy.save();
|
||||
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/list');
|
||||
|
||||
cy.get('.web-list-actions a:visible').contains('New').click();
|
||||
cy.url().should('include', '/note/new');
|
||||
|
||||
cy.fill_field('title', 'Note 2');
|
||||
cy.get('.web-form-actions button').contains('Save').click();
|
||||
});
|
||||
|
||||
it('Allow Delete', () => {
|
||||
cy.visit('/app/web-form/note');
|
||||
|
||||
cy.findByRole("tab", {name: "Form Settings"}).click();
|
||||
cy.get('input[data-fieldname="allow_delete"]').check();
|
||||
|
||||
cy.save();
|
||||
|
||||
cy.visit('/note');
|
||||
cy.url().should('include', '/note/list');
|
||||
|
||||
cy.get('.web-list-table tbody tr[id="Note 1"] .list-col-checkbox').click();
|
||||
cy.get('.web-list-table tbody tr[id="Note 2"] .list-col-checkbox').click();
|
||||
cy.get('.web-list-actions button:visible').contains('Delete').click({force: true});
|
||||
|
||||
cy.get('.web-list-actions button').contains('Delete').should('not.be.visible');
|
||||
|
||||
cy.visit('/note');
|
||||
cy.get('.web-list-table tbody tr[id="Note 1"]').should('not.exist');
|
||||
cy.get('.web-list-table tbody tr[id="Note 2"]').should('not.exist');
|
||||
cy.get('.web-list-table tbody tr[id="Guest Note 1"]').should('exist');
|
||||
});
|
||||
|
||||
it('Navigate and Submit a WebForm', () => {
|
||||
cy.visit('/update-profile');
|
||||
cy.get_field('last_name', 'Data').type('_Test User', {force: true}).wait(200);
|
||||
|
||||
cy.get('.web-form-actions a').contains('Edit').click();
|
||||
|
||||
cy.fill_field('last_name', '_Test User');
|
||||
|
||||
cy.get('.web-form-actions .btn-primary').click();
|
||||
cy.wait(5000);
|
||||
cy.url().should('include', '/me');
|
||||
});
|
||||
|
||||
it('Navigate and Submit a MultiStep WebForm', () => {
|
||||
cy.call('frappe.tests.ui_test_helpers.update_webform_to_multistep').then(() => {
|
||||
cy.visit('/update-profile-duplicate');
|
||||
cy.get_field('last_name', 'Data').type('_Test User', {force: true}).wait(200);
|
||||
|
||||
cy.get('.web-form-actions a').contains('Edit').click();
|
||||
|
||||
cy.fill_field('last_name', '_Test User');
|
||||
|
||||
cy.get('.btn-next').should('be.visible');
|
||||
cy.get('.btn-next').click();
|
||||
|
||||
cy.get('.btn-previous').should('be.visible');
|
||||
cy.get('.btn-next').should('not.be.visible');
|
||||
|
||||
cy.get('.web-form-actions .btn-primary').click();
|
||||
cy.wait(5000);
|
||||
cy.url().should('include', '/me');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -162,7 +162,12 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
|
|||
if (fieldtype === 'Select') {
|
||||
cy.get('@input').select(value);
|
||||
} else {
|
||||
cy.get('@input').type(value, {waitForAnimations: false, force: true, delay: 100});
|
||||
cy.get('@input').type(value, {
|
||||
waitForAnimations: false,
|
||||
parseSpecialCharSequences: false,
|
||||
force: true,
|
||||
delay: 100
|
||||
});
|
||||
}
|
||||
return cy.get('@input');
|
||||
});
|
||||
|
|
@ -358,6 +363,10 @@ Cypress.Commands.add('open_list_filter', () => {
|
|||
cy.get('.filter-popover').should('exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('click_custom_action_button', (name) => {
|
||||
cy.get(`.custom-actions [data-label="${encodeURIComponent(name)}"]`).click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('click_action_button', (name) => {
|
||||
cy.findByRole('button', {name: 'Actions'}).click();
|
||||
cy.get(`.actions-btn-group [data-label="${encodeURIComponent(name)}"]`).click();
|
||||
|
|
|
|||
|
|
@ -1796,6 +1796,14 @@ def respond_as_web_page(
|
|||
local.response["context"] = context
|
||||
|
||||
|
||||
def redirect(url):
|
||||
"""Raise a 301 redirect to url"""
|
||||
from frappe.exceptions import Redirect
|
||||
|
||||
flags.redirect_location = url
|
||||
raise Redirect
|
||||
|
||||
|
||||
def redirect_to_message(title, html, http_status_code=None, context=None, indicator_color=None):
|
||||
"""Redirects to /message?id=random
|
||||
Similar to respond_as_web_page, but used to 'redirect' and show message pages like success, failure, etc. with a detailed message
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@
|
|||
"introduction_text": "",
|
||||
"is_multi_step_form": 0,
|
||||
"is_standard": 1,
|
||||
"list_columns": [],
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2022-03-22 15:00:43.456738",
|
||||
"modified": "2022-07-18 16:51:19.796411",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "edit-profile",
|
||||
|
|
@ -29,9 +30,8 @@
|
|||
"route": "update-profile",
|
||||
"route_to_success_link": 0,
|
||||
"show_attachments": 0,
|
||||
"show_in_grid": 0,
|
||||
"show_list": 0,
|
||||
"show_sidebar": 0,
|
||||
"sidebar_items": [],
|
||||
"success_message": "Profile updated successfully.",
|
||||
"success_url": "/me",
|
||||
"title": "Update Profile",
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ frappe.patches.v14_0.remove_is_first_startup
|
|||
frappe.patches.v14_0.clear_long_pending_stale_logs
|
||||
frappe.patches.v14_0.log_settings_migration
|
||||
frappe.patches.v14_0.setup_likes_from_feedback
|
||||
frappe.patches.v14_0.update_webforms
|
||||
|
||||
[post_model_sync]
|
||||
frappe.patches.v14_0.drop_data_import_legacy
|
||||
|
|
|
|||
14
frappe/patches/v14_0/update_webforms.py
Normal file
14
frappe/patches/v14_0/update_webforms.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("website", "doctype", "web_form_list_column")
|
||||
frappe.reload_doctype("Web Form")
|
||||
|
||||
for web_form in frappe.db.get_all("Web Form", fields=["*"]):
|
||||
if web_form.allow_multiple and not web_form.show_list:
|
||||
frappe.db.set_value("Web Form", web_form.name, "show_list", True)
|
||||
|
|
@ -3,6 +3,7 @@ import "./frappe/class.js";
|
|||
import "./frappe/polyfill.js";
|
||||
import "./lib/moment.js";
|
||||
import "./frappe/provide.js";
|
||||
import "./frappe/form/formatters.js";
|
||||
import "./frappe/format.js";
|
||||
import "./frappe/utils/number_format.js";
|
||||
import "./frappe/utils/utils.js";
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co
|
|||
}
|
||||
|
||||
get_start_date() {
|
||||
this.value = this.value == null ? undefined : this.value;
|
||||
let value = frappe.datetime.convert_to_user_tz(this.value);
|
||||
return frappe.datetime.str_to_obj(value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ frappe.form.formatters = {
|
|||
Datetime: function(value) {
|
||||
if(value) {
|
||||
return moment(frappe.datetime.convert_to_user_tz(value))
|
||||
.format(frappe.boot.sysdefaults.date_format.toUpperCase() + ' ' + frappe.boot.sysdefaults.time_format || 'HH:mm:ss');
|
||||
.format(frappe.boot.sysdefaults.date_format.toUpperCase() + ' ' + (frappe.boot.sysdefaults.time_format || 'HH:mm:ss'));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,14 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
this.set_sections();
|
||||
this.set_field_values();
|
||||
this.setup_listeners();
|
||||
if (this.introduction_text) this.set_form_description(this.introduction_text);
|
||||
if (this.allow_print && !this.is_new) this.setup_print_button();
|
||||
if (this.is_new) this.setup_cancel_button();
|
||||
this.setup_primary_action();
|
||||
|
||||
if (this.is_new || this.is_form_editable) {
|
||||
this.setup_primary_action();
|
||||
}
|
||||
|
||||
this.setup_footer_actions();
|
||||
this.setup_previous_next_button();
|
||||
this.toggle_section();
|
||||
$(".link-btn").remove();
|
||||
|
||||
// webform client script
|
||||
frappe.init_client_script && frappe.init_client_script();
|
||||
|
|
@ -70,6 +71,14 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
this.sections = $(`.form-section`);
|
||||
}
|
||||
|
||||
setup_footer_actions() {
|
||||
if (this.is_multi_step_form) return;
|
||||
|
||||
if ($('.web-form-container').height() > 600) {
|
||||
$(".web-form-footer").removeClass("hide");
|
||||
}
|
||||
}
|
||||
|
||||
setup_previous_next_button() {
|
||||
let me = this;
|
||||
|
||||
|
|
@ -87,7 +96,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
$('.btn-previous').on('click', function () {
|
||||
let is_validated = me.validate_section();
|
||||
|
||||
if (!is_validated) return;
|
||||
if (!is_validated) return false;
|
||||
|
||||
/**
|
||||
The eslint utility cannot figure out if this is an infinite loop in backwards and
|
||||
|
|
@ -107,12 +116,13 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
}
|
||||
/* eslint-enable for-direction */
|
||||
me.toggle_section();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.btn-next').on('click', function () {
|
||||
let is_validated = me.validate_section();
|
||||
|
||||
if (!is_validated) return;
|
||||
if (!is_validated) return false;
|
||||
|
||||
for (let idx = me.current_section; idx < me.sections.length; idx++) {
|
||||
let is_empty = me.is_next_section_empty(idx);
|
||||
|
|
@ -123,6 +133,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
}
|
||||
}
|
||||
me.toggle_section();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -132,56 +143,20 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
}
|
||||
|
||||
set_default_values() {
|
||||
let defaults = {};
|
||||
for (let df of this.fields) {
|
||||
if (df.default) {
|
||||
defaults[df.fieldname] = df.default;
|
||||
}
|
||||
}
|
||||
let values = frappe.utils.get_query_params();
|
||||
delete values.new;
|
||||
Object.assign(defaults, values);
|
||||
this.set_values(values);
|
||||
}
|
||||
|
||||
set_form_description(intro) {
|
||||
let intro_wrapper = document.getElementById('introduction');
|
||||
intro_wrapper.innerHTML = intro;
|
||||
intro_wrapper.classList.remove('hidden');
|
||||
}
|
||||
|
||||
add_button(name, type, action, wrapper_class=".web-form-actions") {
|
||||
const button = document.createElement("button");
|
||||
button.classList.add("btn", "btn-" + type, "btn-sm", "ml-2");
|
||||
button.innerHTML = name;
|
||||
button.onclick = action;
|
||||
document.querySelector(wrapper_class).appendChild(button);
|
||||
}
|
||||
|
||||
add_button_to_footer(name, type, action) {
|
||||
this.add_button(name, type, action, '.web-form-footer');
|
||||
}
|
||||
|
||||
add_button_to_header(name, type, action) {
|
||||
this.add_button(name, type, action, '.web-form-actions');
|
||||
}
|
||||
|
||||
setup_primary_action() {
|
||||
this.add_button_to_header(this.button_label || __("Save", null, "Button in web form"), "primary", () =>
|
||||
this.save()
|
||||
);
|
||||
|
||||
if (!this.is_multi_step_form && $('.frappe-card').height() > 600) {
|
||||
// add button on footer if page is long
|
||||
this.add_button_to_footer(this.button_label || __("Save", null, "Button in web form"), "primary", () =>
|
||||
this.save()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setup_cancel_button() {
|
||||
this.add_button_to_header(__("Cancel", null, "Button in web form"), "light", () => this.cancel());
|
||||
}
|
||||
|
||||
setup_print_button() {
|
||||
this.add_button_to_header(
|
||||
frappe.utils.icon('print'),
|
||||
"light",
|
||||
() => this.print()
|
||||
);
|
||||
$(".web-form-container").on("submit", () => this.save());
|
||||
}
|
||||
|
||||
validate_section() {
|
||||
|
|
@ -349,18 +324,21 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
window.saving = false;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
print() {
|
||||
window.open(`/printview?
|
||||
doctype=${this.doc_type}
|
||||
&name=${this.doc.name}
|
||||
&format=${this.print_format || "Standard"}`, '_blank');
|
||||
edit() {
|
||||
window.location.href = window.location.pathname + "/edit";
|
||||
}
|
||||
|
||||
cancel() {
|
||||
window.location.href = window.location.pathname;
|
||||
let path = window.location.pathname;
|
||||
if (this.is_new) {
|
||||
path = path.replace('/new', '');
|
||||
} else {
|
||||
path = path.replace('/edit', '');
|
||||
}
|
||||
window.location.href = path;
|
||||
}
|
||||
|
||||
handle_success(data) {
|
||||
|
|
@ -375,12 +353,19 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
|
||||
// redirect
|
||||
setTimeout(() => {
|
||||
let path = window.location.pathname;
|
||||
|
||||
if (this.success_url) {
|
||||
window.location.href = this.success_url;
|
||||
} else if(this.login_required) {
|
||||
window.location.href =
|
||||
window.location.pathname + "?name=" + data.name;
|
||||
path = this.success_url;
|
||||
} else if (this.login_required) {
|
||||
if (this.is_new && data.name) {
|
||||
path = path.replace("/new", "");
|
||||
path = path + "/" + data.name;
|
||||
} else if (this.is_form_editable) {
|
||||
path = path.replace("/edit", "");
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
window.location.href = path;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,63 +6,74 @@ export default class WebFormList {
|
|||
constructor(opts) {
|
||||
Object.assign(this, opts);
|
||||
frappe.web_form_list = this;
|
||||
this.wrapper = document.getElementById("list-table");
|
||||
this.wrapper = $(".web-list-table");
|
||||
this.make_actions();
|
||||
this.make_filters();
|
||||
$('.link-btn').remove();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
if (this.table) {
|
||||
Array.from(this.table.tBodies).forEach(tbody => tbody.remove());
|
||||
let check = document.getElementById('select-all');
|
||||
if (check)
|
||||
check.checked = false;
|
||||
}
|
||||
this.rows = [];
|
||||
this.page_length = 20;
|
||||
this.web_list_start = 0;
|
||||
this.page_length = 10;
|
||||
|
||||
frappe.run_serially([
|
||||
() => this.get_list_view_fields(),
|
||||
() => this.get_data(),
|
||||
() => this.remove_more(),
|
||||
() => this.make_table(),
|
||||
() => this.create_more()
|
||||
]);
|
||||
}
|
||||
|
||||
remove_more() {
|
||||
$('.more').remove();
|
||||
}
|
||||
|
||||
make_filters() {
|
||||
this.filters = {};
|
||||
this.filter_input = [];
|
||||
const filter_area = document.getElementById('list-filters');
|
||||
let filter_area = $('.web-list-filters');
|
||||
|
||||
frappe.call('frappe.website.doctype.web_form.web_form.get_web_form_filters', {
|
||||
web_form_name: this.web_form_name
|
||||
}).then(response => {
|
||||
let fields = response.message;
|
||||
fields.length && filter_area.removeClass('hide');
|
||||
fields.forEach(field => {
|
||||
let col = document.createElement('div.col-sm-4');
|
||||
col.classList.add('col', 'col-sm-3');
|
||||
filter_area.appendChild(col);
|
||||
if (field.default) this.add_filter(field.fieldname, field.default, field.fieldtype);
|
||||
if (["Text Editor", "Text", "Small Text"].includes(field.fieldtype)) {
|
||||
field.fieldtype = "Data";
|
||||
}
|
||||
|
||||
if (["Table", "Signature"].includes(field.fieldtype)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let input = frappe.ui.form.make_control({
|
||||
df: {
|
||||
fieldtype: field.fieldtype,
|
||||
fieldname: field.fieldname,
|
||||
options: field.options,
|
||||
input_class: 'input-xs',
|
||||
only_select: true,
|
||||
label: __(field.label),
|
||||
onchange: (event) => {
|
||||
$('#more').remove();
|
||||
this.add_filter(field.fieldname, input.value, field.fieldtype);
|
||||
this.refresh();
|
||||
}
|
||||
},
|
||||
parent: col,
|
||||
value: field.default,
|
||||
parent: filter_area,
|
||||
render_input: 1,
|
||||
only_input: field.fieldtype == "Check" ? false : true,
|
||||
});
|
||||
|
||||
$(input.wrapper)
|
||||
.addClass('col-md-2')
|
||||
.attr("title", __(field.label)).tooltip({
|
||||
delay: { "show": 600, "hide": 100},
|
||||
trigger: "hover"
|
||||
});
|
||||
|
||||
input.$input.attr("placeholder", __(field.label));
|
||||
this.filter_input.push(input);
|
||||
});
|
||||
this.refresh();
|
||||
|
|
@ -73,37 +84,65 @@ export default class WebFormList {
|
|||
if (!value) {
|
||||
delete this.filters[field];
|
||||
} else {
|
||||
if (fieldtype === 'Data') value = ['like', value + '%'];
|
||||
if (["Data", "Currency", "Float", "Int"].includes(fieldtype)) {
|
||||
value = ['like', '%' + value + '%'];
|
||||
}
|
||||
Object.assign(this.filters, Object.fromEntries([[field, value]]));
|
||||
}
|
||||
}
|
||||
|
||||
get_list_view_fields() {
|
||||
return frappe
|
||||
.call({
|
||||
method:
|
||||
"frappe.website.doctype.web_form.web_form.get_in_list_view_fields",
|
||||
args: { doctype: this.doctype }
|
||||
})
|
||||
.then(response => (this.fields_list = response.message));
|
||||
if (this.columns) return this.columns;
|
||||
|
||||
if (this.list_columns) {
|
||||
this.columns = this.list_columns.map(df => {
|
||||
return {
|
||||
label: df.label,
|
||||
fieldname: df.fieldname,
|
||||
fieldtype: df.fieldtype
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetch_data() {
|
||||
return frappe.call({
|
||||
let args = {
|
||||
method: "frappe.www.list.get_list_data",
|
||||
args: {
|
||||
doctype: this.doctype,
|
||||
fields: this.fields_list.map(df => df.fieldname),
|
||||
limit_start: this.web_list_start,
|
||||
limit: this.page_length,
|
||||
web_form_name: this.web_form_name,
|
||||
...this.filters
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (this.no_change(args)) {
|
||||
// console.log('throttled');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return frappe.call(args);
|
||||
}
|
||||
|
||||
no_change(args) {
|
||||
// returns true if arguments are same for the last 3 seconds
|
||||
// this helps in throttling if called from various sources
|
||||
if (this.last_args && JSON.stringify(args) === this.last_args) {
|
||||
return true;
|
||||
}
|
||||
this.last_args = JSON.stringify(args);
|
||||
setTimeout(() => {
|
||||
this.last_args = null;
|
||||
}, 3000);
|
||||
return false;
|
||||
}
|
||||
|
||||
async get_data() {
|
||||
let response = await this.fetch_data();
|
||||
this.data = await response.message;
|
||||
if (response) {
|
||||
this.data = await response.message;
|
||||
}
|
||||
}
|
||||
|
||||
more() {
|
||||
|
|
@ -118,159 +157,145 @@ export default class WebFormList {
|
|||
}
|
||||
|
||||
make_table() {
|
||||
this.columns = this.fields_list.map(df => {
|
||||
return {
|
||||
label: df.label,
|
||||
fieldname: df.fieldname,
|
||||
fieldtype: df.fieldtype
|
||||
};
|
||||
this.table = $(`<table class="table"></table>`);
|
||||
|
||||
this.make_table_head();
|
||||
this.make_table_body();
|
||||
}
|
||||
|
||||
make_table_head() {
|
||||
let $thead = $(`
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" class="select-all">
|
||||
</th>
|
||||
<th>${__("Sr")}.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
`);
|
||||
|
||||
this.check_all = $thead.find('input.select-all');
|
||||
this.check_all.on("click", event => {
|
||||
this.toggle_select_all(event.target.checked);
|
||||
});
|
||||
|
||||
if (!this.table) {
|
||||
this.table = document.createElement("table");
|
||||
this.table.classList.add("table");
|
||||
this.make_table_head();
|
||||
}
|
||||
this.columns.forEach(col => {
|
||||
let $tr = $thead.find("tr");
|
||||
let $th = $(`<th>${__(col.label)}</th>`);
|
||||
$th.appendTo($tr);
|
||||
});
|
||||
|
||||
$thead.appendTo(this.table);
|
||||
}
|
||||
|
||||
make_table_body() {
|
||||
if (this.data.length) {
|
||||
this.wrapper.empty();
|
||||
|
||||
if (this.table) {
|
||||
this.table.find('tbody').remove();
|
||||
|
||||
if (this.check_all.length) {
|
||||
this.check_all.prop("checked", false);
|
||||
}
|
||||
}
|
||||
|
||||
this.append_rows(this.data);
|
||||
this.wrapper.appendChild(this.table);
|
||||
this.table.appendTo(this.wrapper);
|
||||
} else {
|
||||
let new_button = "";
|
||||
let empty_state = document.createElement("div");
|
||||
empty_state.classList.add("no-result", "text-muted", "flex", "justify-center", "align-center");
|
||||
if (this.wrapper.find('.no-result').length) return;
|
||||
|
||||
this.wrapper.empty();
|
||||
frappe.has_permission(this.doctype, "", "create", () => {
|
||||
new_button = `
|
||||
<a
|
||||
class="btn btn-primary btn-sm btn-new-doc hidden-xs"
|
||||
href="${window.location.pathname}?new=1">
|
||||
${__("Create a new {0}", [__(this.doctype)])}
|
||||
</a>
|
||||
`;
|
||||
|
||||
empty_state.innerHTML = `
|
||||
<div class="text-center">
|
||||
<div>
|
||||
<img
|
||||
src="/assets/frappe/images/ui-states/list-empty-state.svg"
|
||||
alt="Generic Empty State"
|
||||
class="null-state">
|
||||
</div>
|
||||
<p class="small mb-2">${__("No {0} found", [__(this.doctype)])}</p>
|
||||
${new_button}
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.wrapper.appendChild(empty_state);
|
||||
this.setup_empty_state();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
make_table_head() {
|
||||
// Create Heading
|
||||
let thead = this.table.createTHead();
|
||||
let row = thead.insertRow();
|
||||
setup_empty_state() {
|
||||
let new_button = `
|
||||
<a
|
||||
class="btn btn-primary btn-sm btn-new-doc hidden-xs"
|
||||
href="${location.pathname.replace('/list', '')}/new">
|
||||
${__("Create a new {0}", [__(this.doctype)])}
|
||||
</a>
|
||||
`;
|
||||
|
||||
let th = document.createElement("th");
|
||||
let empty_state = $(`
|
||||
<div class="no-result text-muted flex justify-center align-center">
|
||||
<div class="text-center">
|
||||
<div>
|
||||
<img
|
||||
src="/assets/frappe/images/ui-states/list-empty-state.svg"
|
||||
alt="Generic Empty State"
|
||||
class="null-state">
|
||||
</div>
|
||||
<p class="small mb-2">${__("No {0} found", [__(this.doctype)])}</p>
|
||||
${new_button}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
let checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.id = "select-all";
|
||||
checkbox.onclick = event =>
|
||||
this.toggle_select_all(event.target.checked);
|
||||
|
||||
th.appendChild(checkbox);
|
||||
row.appendChild(th);
|
||||
|
||||
add_heading(row, __("Sr"));
|
||||
this.columns.forEach(col => {
|
||||
add_heading(row, __(col.label));
|
||||
});
|
||||
|
||||
function add_heading(row, label) {
|
||||
let th = document.createElement("th");
|
||||
th.innerText = label;
|
||||
row.appendChild(th);
|
||||
}
|
||||
empty_state.appendTo(this.wrapper);
|
||||
}
|
||||
|
||||
append_rows(row_data) {
|
||||
const tbody = this.table.childNodes[1] || this.table.createTBody();
|
||||
let $tbody = this.table.find('tbody');
|
||||
|
||||
if (!$tbody.length) {
|
||||
$tbody = $(`<tbody></tbody>`);
|
||||
$tbody.appendTo(this.table);
|
||||
}
|
||||
|
||||
row_data.forEach((data_item) => {
|
||||
let row_element = tbody.insertRow();
|
||||
row_element.setAttribute("id", data_item.name);
|
||||
let $row_element = $(`<tr id="${data_item.name}"></tr>`);
|
||||
|
||||
let row = new frappe.ui.WebFormListRow({
|
||||
row: row_element,
|
||||
row: $row_element,
|
||||
doc: data_item,
|
||||
columns: this.columns,
|
||||
serial_number: this.rows.length + 1,
|
||||
events: {
|
||||
onEdit: () => this.open_form(data_item.name),
|
||||
onSelect: () => this.toggle_delete()
|
||||
on_edit: () => this.open_form(data_item.name),
|
||||
on_select: () => {
|
||||
this.toggle_new();
|
||||
this.toggle_delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.rows.push(row);
|
||||
$row_element.appendTo($tbody);
|
||||
});
|
||||
}
|
||||
|
||||
make_actions() {
|
||||
const actions = document.querySelector(".list-view-actions");
|
||||
const actions = $(".web-list-actions");
|
||||
|
||||
frappe.has_permission(this.doctype, "", "delete", () => {
|
||||
this.addButton(actions, "delete-rows", "danger", true, "Delete", () =>
|
||||
this.delete_rows()
|
||||
);
|
||||
this.add_button(actions, "delete-rows", "danger", true, "Delete", () => this.delete_rows());
|
||||
});
|
||||
|
||||
this.addButton(
|
||||
actions,
|
||||
"new",
|
||||
"primary",
|
||||
false,
|
||||
"New",
|
||||
() => (window.location.href = window.location.pathname + "?new=1")
|
||||
);
|
||||
}
|
||||
|
||||
addButton(wrapper, id, type, hidden, name, action) {
|
||||
if (document.getElementById(id)) return;
|
||||
const button = document.createElement("button");
|
||||
if (type == "secondary") {
|
||||
button.classList.add(
|
||||
"btn",
|
||||
"btn-secondary",
|
||||
"btn-sm",
|
||||
"ml-2"
|
||||
);
|
||||
}
|
||||
else if (type == "danger") {
|
||||
button.classList.add(
|
||||
"btn",
|
||||
"btn-danger",
|
||||
"button-delete",
|
||||
"btn-sm",
|
||||
"ml-2"
|
||||
);
|
||||
}
|
||||
else {
|
||||
button.classList.add("btn", "btn-primary", "btn-sm", "ml-2");
|
||||
}
|
||||
add_button(wrapper, name, type, hidden, text, action) {
|
||||
if ($(`.${name}`).length) return;
|
||||
|
||||
button.id = id;
|
||||
button.innerText = name;
|
||||
button.hidden = hidden;
|
||||
hidden = hidden ? "hide" : "";
|
||||
type = type == "danger" ? "danger button-delete" : type;
|
||||
|
||||
button.onclick = action;
|
||||
wrapper.appendChild(button);
|
||||
let button = $(`
|
||||
<button class="${name} btn btn-${type} btn-sm ml-2 ${hidden}">${text}</button>
|
||||
`);
|
||||
|
||||
button.on("click", () => action());
|
||||
button.appendTo(wrapper);
|
||||
}
|
||||
|
||||
create_more() {
|
||||
if (this.rows.length >= this.page_length) {
|
||||
const footer = document.querySelector(".list-view-footer");
|
||||
this.addButton(footer, "more", "secondary", false, "More", () => this.more());
|
||||
const footer = $(".web-list-footer");
|
||||
this.add_button(footer, "more", "secondary", false, "Load More", () => this.more());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +304,12 @@ export default class WebFormList {
|
|||
}
|
||||
|
||||
open_form(name) {
|
||||
window.location.href = window.location.pathname + "?name=" + name;
|
||||
let path = window.location.pathname;
|
||||
if (path.includes('/list')) {
|
||||
path = path.replace('/list', '');
|
||||
}
|
||||
|
||||
window.location.href = path + "/" + name;
|
||||
}
|
||||
|
||||
get_selected() {
|
||||
|
|
@ -287,9 +317,15 @@ export default class WebFormList {
|
|||
}
|
||||
|
||||
toggle_delete() {
|
||||
if (!this.settings.allow_delete) return
|
||||
let btn = document.getElementById("delete-rows");
|
||||
btn.hidden = !this.get_selected().length;
|
||||
if (!this.settings.allow_delete) return;
|
||||
let btn = $(".delete-rows");
|
||||
!this.get_selected().length ? btn.addClass('hide') : btn.removeClass('hide');
|
||||
}
|
||||
|
||||
toggle_new() {
|
||||
if (!this.settings.allow_delete) return;
|
||||
let btn = $(".button-new");
|
||||
this.get_selected().length ? btn.addClass('hide') : btn.removeClass('hide');
|
||||
}
|
||||
|
||||
delete_rows() {
|
||||
|
|
@ -305,8 +341,9 @@ export default class WebFormList {
|
|||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.refresh()
|
||||
this.toggle_delete()
|
||||
this.refresh();
|
||||
this.toggle_delete();
|
||||
this.toggle_new();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -319,40 +356,37 @@ frappe.ui.WebFormListRow = class WebFormListRow {
|
|||
|
||||
make_row() {
|
||||
// Add Checkboxes
|
||||
let cell = this.row.insertCell();
|
||||
cell.classList.add('list-col-checkbox');
|
||||
let $cell = $(`<td class="list-col-checkbox"></td>`);
|
||||
|
||||
this.checkbox = document.createElement("input");
|
||||
this.checkbox.type = "checkbox";
|
||||
this.checkbox.onclick = event => {
|
||||
this.checkbox = $(`<input type="checkbox">`);
|
||||
this.checkbox.on("click", event => {
|
||||
this.toggle_select(event.target.checked);
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
cell.appendChild(this.checkbox);
|
||||
});
|
||||
this.checkbox.appendTo($cell);
|
||||
$cell.appendTo(this.row);
|
||||
|
||||
// Add Serial Number
|
||||
let serialNo = this.row.insertCell();
|
||||
serialNo.classList.add('list-col-serial');
|
||||
serialNo.innerText = this.serial_number;
|
||||
let serialNo = $(`<td class="list-col-serial">${__(this.serial_number)}</td>`);
|
||||
serialNo.appendTo(this.row);
|
||||
|
||||
this.columns.forEach(field => {
|
||||
let cell = this.row.insertCell();
|
||||
let formatter = frappe.form.get_formatter(field.fieldtype);
|
||||
cell.innerHTML = this.doc[field.fieldname] &&
|
||||
let value = this.doc[field.fieldname] &&
|
||||
__(formatter(this.doc[field.fieldname], field, {only_value: 1}, this.doc)) || "";
|
||||
let cell = $(`<td>${value}</td>`);
|
||||
cell.appendTo(this.row);
|
||||
});
|
||||
|
||||
this.row.onclick = () => this.events.onEdit();
|
||||
this.row.style.cursor = "pointer";
|
||||
this.row.on("click", () => this.events.on_edit());
|
||||
}
|
||||
|
||||
toggle_select(checked) {
|
||||
this.checkbox.checked = checked;
|
||||
this.events.onSelect(checked);
|
||||
this.checkbox.prop("checked", checked);
|
||||
this.events.on_select(checked);
|
||||
}
|
||||
|
||||
is_selected() {
|
||||
return this.checkbox.checked;
|
||||
return this.checkbox.prop("checked");
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,23 +2,15 @@ import WebFormList from './web_form_list'
|
|||
import WebForm from './web_form'
|
||||
|
||||
frappe.ready(function() {
|
||||
let query_params = frappe.utils.get_query_params();
|
||||
let wrapper = $(".web-form-wrapper");
|
||||
let is_list = parseInt(wrapper.data('is-list')) || query_params.is_list;
|
||||
let webform_doctype = wrapper.data('web-form-doctype');
|
||||
let webform_name = wrapper.data('web-form');
|
||||
let login_required = parseInt(wrapper.data('login-required'));
|
||||
let allow_delete = parseInt(wrapper.data('allow-delete'));
|
||||
let doc_name = query_params.name || '';
|
||||
let is_new = query_params.new;
|
||||
let web_form_doc = frappe.web_form_doc;
|
||||
let reference_doc = frappe.reference_doc;
|
||||
|
||||
if (login_required) show_login_prompt();
|
||||
else if (is_list) show_grid();
|
||||
else show_form(webform_doctype, webform_name, is_new);
|
||||
show_login_prompt();
|
||||
|
||||
document.querySelector("body").style.display = "block";
|
||||
web_form_doc.is_list ? show_list() : show_form();
|
||||
|
||||
function show_login_prompt() {
|
||||
if (frappe.session.user != "Guest" || !web_form_doc.login_required) return;
|
||||
const login_required = new frappe.ui.Dialog({
|
||||
title: __("Not Permitted"),
|
||||
primary_action_label: __("Login"),
|
||||
|
|
@ -30,102 +22,79 @@ frappe.ready(function() {
|
|||
login_required.set_message(__("You are not permitted to access this page without login."));
|
||||
}
|
||||
|
||||
function show_grid() {
|
||||
function show_list() {
|
||||
new WebFormList({
|
||||
parent: wrapper,
|
||||
doctype: webform_doctype,
|
||||
web_form_name: webform_name,
|
||||
doctype: web_form_doc.doc_type,
|
||||
web_form_name: web_form_doc.name,
|
||||
list_columns: web_form_doc.list_columns,
|
||||
settings: {
|
||||
allow_delete
|
||||
allow_delete: web_form_doc.allow_delete
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function show_form() {
|
||||
let web_form = new WebForm({
|
||||
parent: wrapper,
|
||||
is_new,
|
||||
web_form_name: webform_name,
|
||||
parent: $(".web-form-wrapper"),
|
||||
is_new: web_form_doc.is_new,
|
||||
is_form_editable: web_form_doc.is_form_editable,
|
||||
web_form_name: web_form_doc.name,
|
||||
});
|
||||
let doc = reference_doc || {};
|
||||
setup_fields(web_form_doc, doc);
|
||||
|
||||
get_data().then(r => {
|
||||
const data = setup_fields(r.message);
|
||||
let web_form_doc = data.web_form;
|
||||
web_form.prepare(web_form_doc, doc);
|
||||
web_form.make();
|
||||
|
||||
// if (web_form_doc.name && web_form_doc.allow_edit === 0) {
|
||||
// if (!window.location.href.includes("?new=1")) {
|
||||
// window.location.replace(window.location.pathname + "?new=1");
|
||||
// }
|
||||
// }
|
||||
let doc = r.message.doc || build_doc(r.message);
|
||||
web_form.prepare(web_form_doc, r.message.doc && web_form_doc.allow_edit === 1 ? r.message.doc : {});
|
||||
web_form.make();
|
||||
if (web_form_doc.is_new) {
|
||||
web_form.set_default_values();
|
||||
})
|
||||
|
||||
function build_doc(form_data) {
|
||||
let doc = {};
|
||||
form_data.web_form.web_form_fields.forEach(df => {
|
||||
if (df.default) return doc[df.fieldname] = df.default;
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
|
||||
function get_data() {
|
||||
return frappe.call({
|
||||
method: "frappe.website.doctype.web_form.web_form.get_form_data",
|
||||
args: {
|
||||
doctype: webform_doctype,
|
||||
docname: doc_name,
|
||||
web_form_name: webform_name
|
||||
},
|
||||
freeze: true
|
||||
});
|
||||
}
|
||||
$(".file-size").each(function () {
|
||||
$(this).text(frappe.form.formatters.FileSize($(this).text()));
|
||||
});
|
||||
}
|
||||
|
||||
function setup_fields(form_data) {
|
||||
form_data.web_form.web_form_fields.map(df => {
|
||||
df.is_web_form = true;
|
||||
if (df.fieldtype === "Table") {
|
||||
df.get_data = () => {
|
||||
let data = [];
|
||||
if (form_data.doc) {
|
||||
data = form_data.doc[df.fieldname];
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
df.fields = form_data[df.fieldname];
|
||||
$.each(df.fields || [], function(_i, field) {
|
||||
if (field.fieldtype === "Link") {
|
||||
field.only_select = true;
|
||||
}
|
||||
field.is_web_form = true;
|
||||
});
|
||||
|
||||
if (df.fieldtype === "Attach") {
|
||||
df.is_private = true;
|
||||
function setup_fields(web_form_doc, doc_data) {
|
||||
web_form_doc.web_form_fields.forEach(df => {
|
||||
df.is_web_form = true;
|
||||
df.read_only = !web_form_doc.is_new && !web_form_doc.is_form_editable;
|
||||
if (df.fieldtype === "Table") {
|
||||
df.get_data = () => {
|
||||
let data = [];
|
||||
if (doc_data && doc_data[df.fieldname]) {
|
||||
return doc_data[df.fieldname];
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
delete df.parent;
|
||||
delete df.parentfield;
|
||||
delete df.parenttype;
|
||||
delete df.doctype;
|
||||
|
||||
return df;
|
||||
}
|
||||
if (df.fieldtype === "Link") {
|
||||
df.only_select = true;
|
||||
}
|
||||
if (["Attach", "Attach Image"].includes(df.fieldtype)) {
|
||||
if (typeof df.options !== "object") {
|
||||
df.options = {};
|
||||
$.each(df.fields || [], function(_i, field) {
|
||||
if (field.fieldtype === "Link") {
|
||||
field.only_select = true;
|
||||
}
|
||||
df.options.disable_file_browser = true;
|
||||
}
|
||||
});
|
||||
field.is_web_form = true;
|
||||
});
|
||||
|
||||
return form_data;
|
||||
}
|
||||
if (df.fieldtype === "Attach") {
|
||||
df.is_private = true;
|
||||
}
|
||||
|
||||
delete df.parent;
|
||||
delete df.parentfield;
|
||||
delete df.parenttype;
|
||||
delete df.doctype;
|
||||
|
||||
return df;
|
||||
}
|
||||
if (df.fieldtype === "Link") {
|
||||
df.only_select = true;
|
||||
}
|
||||
if (["Attach", "Attach Image"].includes(df.fieldtype)) {
|
||||
if (typeof df.options !== "object") {
|
||||
df.options = {};
|
||||
}
|
||||
df.options.disable_file_browser = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ a.badge-hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,37 +5,212 @@
|
|||
max-width: 800px;
|
||||
margin: auto;
|
||||
|
||||
.frappe-card {
|
||||
padding: 1rem;
|
||||
h1 {
|
||||
font-size: 1.9rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.9rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.web-form-container {
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: 2rem;
|
||||
|
||||
.web-form-head {
|
||||
margin: 0 -1rem;
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
.web-form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 -2rem 1rem;
|
||||
padding: 0 2rem 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
.web-form-actions {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
#introduction {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
#introduction p {
|
||||
.web-form-introduction {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 2rem;
|
||||
|
||||
p {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
.web-form-actions button {
|
||||
margin-top: 0.1rem;
|
||||
.web-form-wrapper {
|
||||
.form-control {
|
||||
color: var(--text-color);
|
||||
background-color: var(--control-bg);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
.section-head {
|
||||
font-weight: bold;
|
||||
font-size: var(--text-xl);
|
||||
padding: var(--padding-md) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-column {
|
||||
padding: 0 var(--padding-md);
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.web-form-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.attachments {
|
||||
margin: 1rem -2rem 0;
|
||||
padding: 1rem 2rem 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
|
||||
.attachment {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
max-width: 300px;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-md);
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
.file-name span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.frappe-card.list-card {
|
||||
min-height: 400px;
|
||||
.web-list-container {
|
||||
min-height: 470px;
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: 2rem;
|
||||
|
||||
.web-list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.web-list-actions {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 1rem -2rem 0;
|
||||
padding: 1rem 2rem 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
gap: 10px;
|
||||
|
||||
.form-group.frappe-control {
|
||||
min-width: 145px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
align-self: center;
|
||||
|
||||
.checkbox {
|
||||
.input-xs {
|
||||
height: var(--checkbox-size);
|
||||
}
|
||||
|
||||
.help-box {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input-xs {
|
||||
height: 28px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-table {
|
||||
overflow: auto;
|
||||
margin: 1rem -2rem 0;
|
||||
|
||||
.table {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-top: 1px solid var(--border-color);
|
||||
|
||||
thead tr {
|
||||
th {
|
||||
border: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
color: var(--text-muted);
|
||||
|
||||
&:first-child {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
|
||||
td {
|
||||
font-size: 13px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
|
||||
&:first-child {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-left: 0.5rem;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.list-col-checkbox {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.list-col-serial {
|
||||
width: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.no-result {
|
||||
min-height: 330px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-footer {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container.container {
|
||||
|
|
@ -45,76 +220,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.web-form-wrapper {
|
||||
.form-control {
|
||||
color: var(--text-color);
|
||||
background-color: var(--control-bg);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
.section-head {
|
||||
font-weight: bold;
|
||||
font-size: var(--text-xl);
|
||||
padding: var(--padding-md) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-column {
|
||||
padding: 0 var(--padding-md);
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-table {
|
||||
margin-left: -1rem;
|
||||
margin-right: -1rem;
|
||||
|
||||
.table {
|
||||
thead {
|
||||
th {
|
||||
border: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
color: var(--text-muted);
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
color: var(--text-color);
|
||||
|
||||
td {
|
||||
font-size: 13px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-left: 0.5rem;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.list-col-checkbox {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.list-col-serial {
|
||||
width: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,12 +96,7 @@
|
|||
{% block base_scripts %}
|
||||
<!-- js should be loaded in body! -->
|
||||
<script>
|
||||
frappe.boot = {
|
||||
sysdefaults: {
|
||||
float_precision: parseInt("{{ frappe.get_system_settings('float_precision') or 3 }}"),
|
||||
date_format: "{{ frappe.get_system_settings('date_format') or 'yyyy-mm-dd' }}",
|
||||
}
|
||||
};
|
||||
frappe.boot = {{ boot }}
|
||||
// for backward compatibility of some libs
|
||||
frappe.sys_defaults = frappe.boot.sysdefaults;
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -8,17 +8,17 @@ from frappe.www.list import get_list_context
|
|||
|
||||
class TestWebform(unittest.TestCase):
|
||||
def test_webform_publish_functionality(self):
|
||||
edit_profile = frappe.get_doc("Web Form", "edit-profile")
|
||||
request_data = frappe.get_doc("Web Form", "request-data")
|
||||
# publish webform
|
||||
edit_profile.published = True
|
||||
edit_profile.save()
|
||||
set_request(method="GET", path="update-profile")
|
||||
request_data.published = True
|
||||
request_data.save()
|
||||
set_request(method="GET", path="request-data/new")
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# un-publish webform
|
||||
edit_profile.published = False
|
||||
edit_profile.save()
|
||||
request_data.published = False
|
||||
request_data.save()
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
|
|
|||
|
|
@ -1951,6 +1951,15 @@ def generate_hash(*args, **kwargs) -> str:
|
|||
return frappe.generate_hash(*args, **kwargs)
|
||||
|
||||
|
||||
def dict_with_keys(dict, keys):
|
||||
"""Returns a new dict with a subset of keys"""
|
||||
out = {}
|
||||
for key in dict:
|
||||
if key in keys:
|
||||
out[key] = dict[key]
|
||||
return out
|
||||
|
||||
|
||||
def guess_date_format(date_string: str) -> str:
|
||||
DATE_FORMATS = [
|
||||
r"%d/%b/%y",
|
||||
|
|
|
|||
|
|
@ -1,149 +1,129 @@
|
|||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %}{{ _(title) }}{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{% endblock %}
|
||||
|
||||
{% macro container_attributes() %}
|
||||
data-web-form="{{ name }}" data-web-form-doctype="{{ doc_type }}" data-login-required="{{ frappe.utils.cint(login_required and frappe.session.user=='Guest') }}" data-is-list="{{ frappe.utils.cint(is_list) }}" data-allow-delete="{{ allow_delete }}"
|
||||
{% macro action_buttons() %}
|
||||
{% if allow_print and not is_new %}
|
||||
{% set print_format_url = "/printview?doctype=" + doc_type + "&name=" + doc_name + "&format=" + print_format %}
|
||||
<!-- print button -->
|
||||
<a href="{{ print_format_url }}" target="_blank" class="btn btn-light btn-sm ml-2">
|
||||
<svg class="icon icon-sm"><use href="#icon-printer"></use></svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if allow_edit and doc_name and not is_form_editable %}
|
||||
<!-- edit button -->
|
||||
<a href="/{{ route }}/{{ doc_name }}/edit" class="btn btn-primary btn-sm ml-2">{{ _("Edit", null, "Button in web form") }}</a>
|
||||
{% endif %}
|
||||
|
||||
{% if is_new or is_form_editable %}
|
||||
<!-- cancel button -->
|
||||
<a href="/{{ route }}/{% if doc_name %}{{ doc_name }}{% endif %}" class="btn btn-light btn-sm ml-2">{{ _("Cancel", null, "Button in web form") }}</a>
|
||||
<!-- submit button -->
|
||||
<button type="submit" class="btn btn-primary btn-sm ml-2">{{ button_label or _("Save", null, "Button in web form") }}</button>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% block page_content %}
|
||||
|
||||
{% if has_header and login_required and allow_multiple %}
|
||||
<!-- breadcrumb -->
|
||||
{% include "templates/includes/breadcrumbs.html" %}
|
||||
{% else %}
|
||||
<div style="height: 3rem"></div>
|
||||
{% endif %}
|
||||
|
||||
<!-- main card -->
|
||||
<div class="frappe-card {{ frappe.utils.cint(is_list) and 'list-card' or '' }}">
|
||||
{% if is_list %}
|
||||
<!-- list -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<h3 class="mt-0">{{ _(title) }}</h3>
|
||||
<div class="list-view-actions"></div>
|
||||
</div>
|
||||
|
||||
<div class="web-form-wrapper" {{ container_attributes() }}></div>
|
||||
<div id="list-filters" class="mt-4 row"></div>
|
||||
<div id="list-table" class="pt-4 overflow-auto list-table"></div>
|
||||
<div class="text-right list-view-footer"></div>
|
||||
{% else %}
|
||||
<!-- web form -->
|
||||
<div class="d-flex justify-content-between web-form-head">
|
||||
<h1>{{ _(title) }}</h1>
|
||||
<div class="web-form-actions"></div>
|
||||
</div>
|
||||
<div role="form">
|
||||
<div id="introduction" class="hidden text-muted"></div>
|
||||
<div class="web-form-wrapper" {{ container_attributes() }}></div>
|
||||
<div class="text-right web-form-footer"></div>
|
||||
</div>
|
||||
|
||||
{% if show_attachments and not frappe.form_dict.new and attachments %}
|
||||
<div class="attachments">
|
||||
<h5>{{ _("Attachments") }}</h5>
|
||||
{% for attachment in attachments %}
|
||||
<div class="attachment">
|
||||
<a class="no-underline attachment-link" href="{{ attachment.file_url }}" target="blank">
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<span class="file-name">{{ attachment.file_name }}</span>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<span class="pull-right file-size">{{ attachment.file_size }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %} {# attachments #}
|
||||
|
||||
<!-- breadcrumb -->
|
||||
{% if has_header and login_required and show_list %}
|
||||
{% include "templates/includes/breadcrumbs.html" %}
|
||||
{% else %}
|
||||
<div style="height: 3rem"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if allow_comments and not frappe.form_dict.new and not is_list -%}
|
||||
<!-- comments -->
|
||||
<div class="comments" style="margin-top: 3rem;">
|
||||
{% include 'templates/includes/comments/comments.html' %}
|
||||
</div>
|
||||
{%- else -%}
|
||||
<div style="height: 3rem"></div>
|
||||
{%- endif %} {# comments #}
|
||||
<!-- main card -->
|
||||
<form role="form" class="web-form-container">
|
||||
<div class="web-form-header">
|
||||
<h1>{{ _(title) }}</h1>
|
||||
<div class="web-form-actions">
|
||||
{{ action_buttons() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="web-form-body">
|
||||
{% if introduction_text %}
|
||||
<div class="web-form-introduction">{{ introduction_text }}</div>
|
||||
{% endif %}
|
||||
<div class="web-form-wrapper"></div>
|
||||
<div class="web-form-footer hide">
|
||||
{{ action_buttons() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- attachments -->
|
||||
{% if show_attachments and not is_new and attachments %}
|
||||
<div class="attachments">
|
||||
<h5 class="mb-3">{{ _("Attachments") }}</h5>
|
||||
{% for attachment in attachments %}
|
||||
<a class="attachment attachment-link" href="{{ attachment.file_url }}" target="blank">
|
||||
<div class="file-name ellipsis">
|
||||
<svg class="icon icon-sm"><use href="#icon-attachment"></use></svg>
|
||||
<span>{{ attachment.file_name }}</span>
|
||||
</div>
|
||||
<div class="file-size">{{ attachment.file_size }}</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %} {# attachments #}
|
||||
</form>
|
||||
|
||||
<!-- comments -->
|
||||
{% if allow_comments and not is_new and not is_list -%}
|
||||
<div class="comments">
|
||||
<h3>{{ _("Comments") }}</h3>
|
||||
{% include 'templates/includes/comments/comments.html' %}
|
||||
</div>
|
||||
{%- else -%}
|
||||
<div style="height: 3rem"></div>
|
||||
{%- endif %} {# comments #}
|
||||
|
||||
{% endblock page_content %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
frappe.boot = {
|
||||
sysdefaults: {
|
||||
float_precision: parseInt("{{ frappe.get_system_settings('float_precision') or 3 }}"),
|
||||
date_format: "{{ frappe.get_system_settings('date_format') or 'yyyy-mm-dd' }}",
|
||||
},
|
||||
time_zone: {
|
||||
system: "{{ frappe.utils.get_time_zone() }}",
|
||||
user: "{{ frappe.db.get_value('User', frappe.session.user, 'time_zone') or frappe.utils.get_time_zone() }}"
|
||||
},
|
||||
link_title_doctypes: {{ link_title_doctypes }}
|
||||
};
|
||||
// for backward compatibility of some libs
|
||||
frappe.sys_defaults = frappe.boot.sysdefaults;
|
||||
frappe._messages = {{ translated_messages }};
|
||||
$(".file-size").each(function() {
|
||||
$(this).text(frappe.form.formatters.FileSize($(this).text()));
|
||||
});
|
||||
</script>
|
||||
{{ include_script("controls.bundle.js") }}
|
||||
{% if is_list %} <!-- web form list -->
|
||||
{{ include_script("dialog.bundle.js") }}
|
||||
{{ include_script("web_form.bundle.js") }}
|
||||
{{ include_script("bootstrap-4-web.bundle.js") }}
|
||||
{% else %} <!-- web form -->
|
||||
{{ include_script("dialog.bundle.js") }}
|
||||
<script type="text/javascript" src="/assets/frappe/node_modules/vue/dist/vue.js"></script>
|
||||
<script>
|
||||
Vue.prototype.__ = window.__;
|
||||
Vue.prototype.frappe = window.frappe;
|
||||
</script>
|
||||
{{ include_script("web_form.bundle.js") }}
|
||||
{{ include_script("bootstrap-4-web.bundle.js") }}
|
||||
<script>
|
||||
<script>
|
||||
frappe.boot = {{ boot }};
|
||||
frappe._messages = {{ translated_messages }};
|
||||
frappe.web_form_doc = {{ web_form_doc | json }};
|
||||
frappe.reference_doc = {{ reference_doc | json }};
|
||||
</script>
|
||||
|
||||
{% if client_script %}
|
||||
frappe.init_client_script = () => {
|
||||
try {
|
||||
{{ client_script }}
|
||||
} catch(e) {
|
||||
console.error('Error in web form client script');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
<script type="text/javascript" src="/assets/frappe/node_modules/vue/dist/vue.js"></script>
|
||||
<script>
|
||||
Vue.prototype.__ = window.__;
|
||||
Vue.prototype.frappe = window.frappe;
|
||||
</script>
|
||||
|
||||
{% if script is defined %}
|
||||
{{ script }}
|
||||
{% endif %}
|
||||
</script>
|
||||
{% endif %}
|
||||
{{ include_script("controls.bundle.js") }}
|
||||
{{ include_script("dialog.bundle.js") }}
|
||||
{{ include_script("web_form.bundle.js") }}
|
||||
{{ include_script("bootstrap-4-web.bundle.js") }}
|
||||
|
||||
<script>
|
||||
{% if client_script %}
|
||||
frappe.init_client_script = () => {
|
||||
try {
|
||||
{{ client_script }}
|
||||
} catch(e) {
|
||||
console.error('Error in web form client script');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if script is defined %}
|
||||
{{ script }}
|
||||
{% endif %}
|
||||
</script>
|
||||
{% endblock script %}
|
||||
|
||||
{% block style %}
|
||||
{% if not is_list %}
|
||||
{{ include_style('web_form.bundle.css') }}
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
{% if style is defined %}
|
||||
{{ style }}
|
||||
{% endif %}
|
||||
{% if custom_css %}
|
||||
{{ custom_css }}
|
||||
{% endif %}
|
||||
</style>
|
||||
<style>
|
||||
{% if style is defined %}
|
||||
{{ style }}
|
||||
{% endif %}
|
||||
{% if custom_css %}
|
||||
{{ custom_css }}
|
||||
{% endif %}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
<div>
|
||||
<a href={{ route }}>{{ title }}</a>
|
||||
</div>
|
||||
<!-- this is a sample default list template -->
|
||||
45
frappe/website/doctype/web_form/templates/web_list.html
Normal file
45
frappe/website/doctype/web_form/templates/web_list.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block breadcrumbs %}{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<!-- main card -->
|
||||
<div class="web-list-container">
|
||||
<!-- list -->
|
||||
<div class="web-list-header">
|
||||
<h1>{{ _(list_title or title) }}</h1>
|
||||
<div class="web-list-actions">
|
||||
{%- if allow_multiple -%}
|
||||
<a class="btn btn-primary btn-sm button-new" href="/{{ route }}/new">New</a>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
</div>
|
||||
<div class="web-list-filters hide"></div>
|
||||
<div class="web-list-table"></div>
|
||||
<div class="web-list-footer"></div>
|
||||
</div>
|
||||
{% endblock page_content %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
frappe.boot = {{ boot }};
|
||||
frappe._messages = {{ translated_messages }};
|
||||
frappe.web_form_doc = {{ web_form_doc | json }};
|
||||
</script>
|
||||
|
||||
{{ include_script("controls.bundle.js") }}
|
||||
{{ include_script("dialog.bundle.js") }}
|
||||
{{ include_script("web_form.bundle.js") }}
|
||||
{{ include_script("bootstrap-4-web.bundle.js") }}
|
||||
{% endblock script %}
|
||||
|
||||
{% block style %}
|
||||
<style>
|
||||
{% if style is defined %}
|
||||
{{ style }}
|
||||
{% endif %}
|
||||
{% if custom_css %}
|
||||
{{ custom_css }}
|
||||
{% endif %}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
|
@ -4,6 +4,7 @@ import json
|
|||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import set_request
|
||||
from frappe.website.doctype.web_form.web_form import accept
|
||||
from frappe.website.serve import get_response_content
|
||||
|
||||
|
|
@ -68,8 +69,9 @@ class TestWebForm(unittest.TestCase):
|
|||
)
|
||||
|
||||
def test_webform_render(self):
|
||||
content = get_response_content("request-data")
|
||||
self.assertIn("<h1>Request Data</h1>", content)
|
||||
set_request(method="GET", path="manage-events/new")
|
||||
content = get_response_content("manage-events/new")
|
||||
self.assertIn("<h1>New Manage Events</h1>", content)
|
||||
self.assertIn('data-doctype="Web Form"', content)
|
||||
self.assertIn('data-path="request-data"', content)
|
||||
self.assertIn('data-path="manage-events/new"', content)
|
||||
self.assertIn('source-type="Generator"', content)
|
||||
|
|
|
|||
|
|
@ -1,89 +1,149 @@
|
|||
frappe.web_form = {
|
||||
set_fieldname_select: function(frm) {
|
||||
return new Promise(resolve => {
|
||||
var me = this,
|
||||
doc = frm.doc;
|
||||
if (doc.doc_type) {
|
||||
frappe.model.with_doctype(doc.doc_type, function() {
|
||||
var fields = $.map(frappe.get_doc("DocType", frm.doc.doc_type).fields, function(d) {
|
||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
||||
d.fieldtype === 'Table') {
|
||||
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
var currency_fields = $.map(frappe.get_doc("DocType", frm.doc.doc_type).fields, function(d) {
|
||||
if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') {
|
||||
return { label: d.label, value: d.fieldname };
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
frm.fields_dict.web_form_fields.grid.update_docfield_property(
|
||||
'fieldname', 'options', fields
|
||||
);
|
||||
frappe.meta.get_docfield("Web Form", "amount_field", frm.doc.name).options = [""].concat(currency_fields);
|
||||
frm.refresh_field("amount_field");
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
frappe.ui.form.on("Web Form", {
|
||||
refresh: function(frm) {
|
||||
// show is-standard only if developer mode
|
||||
frm.get_field("is_standard").toggle(frappe.boot.developer_mode);
|
||||
|
||||
frappe.web_form.set_fieldname_select(frm);
|
||||
|
||||
if (frm.doc.is_standard && !frappe.boot.developer_mode) {
|
||||
frm.set_read_only();
|
||||
frm.disable_save();
|
||||
}
|
||||
render_list_settings_message(frm);
|
||||
|
||||
frm.add_custom_button(__('Get Fields'), () => {
|
||||
let webform_fieldtypes = frappe.meta.get_field('Web Form Field', 'fieldtype').options.split('\n');
|
||||
let fieldnames = (frm.doc.web_form_fields || []).map(d => d.fieldname);
|
||||
frappe.model.with_doctype(frm.doc.doc_type, () => {
|
||||
let meta = frappe.get_meta(frm.doc.doc_type);
|
||||
for (let field of meta.fields) {
|
||||
if (webform_fieldtypes.includes(field.fieldtype)
|
||||
&& !fieldnames.includes(field.fieldname)) {
|
||||
frm.add_child('web_form_fields', {
|
||||
fieldname: field.fieldname,
|
||||
label: field.label,
|
||||
fieldtype: field.fieldtype,
|
||||
options: field.options,
|
||||
reqd: field.reqd,
|
||||
default: field.default,
|
||||
read_only: field.read_only || field.is_virtual,
|
||||
depends_on: field.depends_on,
|
||||
mandatory_depends_on: field.mandatory_depends_on,
|
||||
read_only_depends_on: field.read_only_depends_on,
|
||||
hidden: field.hidden,
|
||||
description: field.description
|
||||
frm.trigger('set_fields');
|
||||
frm.trigger('add_get_fields_button');
|
||||
frm.trigger('add_publish_button');
|
||||
},
|
||||
|
||||
login_required: function(frm) {
|
||||
render_list_settings_message(frm);
|
||||
},
|
||||
|
||||
validate: function(frm) {
|
||||
if (!frm.doc.login_required) {
|
||||
frm.set_value("allow_multiple", 0);
|
||||
frm.set_value("allow_edit", 0);
|
||||
frm.set_value("show_list", 0);
|
||||
}
|
||||
|
||||
!frm.doc.allow_multiple && frm.set_value("allow_delete", 0);
|
||||
frm.doc.allow_multiple && frm.set_value("show_list", 1);
|
||||
|
||||
if (!frm.doc.web_form_fields) {
|
||||
frm.scroll_to_field('web_form_fields');
|
||||
frappe.throw(__("Atleast one field is required in Web Form Fields Table"));
|
||||
}
|
||||
},
|
||||
|
||||
add_publish_button(frm) {
|
||||
frm.add_custom_button(frm.doc.published ? __("Unpublish") : __("Publish"), () => {
|
||||
frm.set_value("published", !frm.doc.published);
|
||||
frm.save();
|
||||
});
|
||||
},
|
||||
|
||||
add_get_fields_button(frm) {
|
||||
frm.add_custom_button(__("Get Fields"), () => {
|
||||
let webform_fieldtypes = frappe.meta
|
||||
.get_field("Web Form Field", "fieldtype")
|
||||
.options.split("\n");
|
||||
|
||||
let added_fields = (frm.doc.fields || []).map(d => d.fieldname);
|
||||
|
||||
get_fields_for_doctype(frm.doc.doc_type).then(fields => {
|
||||
for (let df of fields) {
|
||||
if (
|
||||
webform_fieldtypes.includes(df.fieldtype) &&
|
||||
!added_fields.includes(df.fieldname) &&
|
||||
!df.hidden
|
||||
) {
|
||||
frm.add_child("web_form_fields", {
|
||||
fieldname: df.fieldname,
|
||||
label: df.label,
|
||||
fieldtype: df.fieldtype,
|
||||
options: df.options,
|
||||
reqd: df.reqd,
|
||||
default: df.default,
|
||||
read_only: df.read_only,
|
||||
depends_on: df.depends_on,
|
||||
mandatory_depends_on: df.mandatory_depends_on,
|
||||
read_only_depends_on: df.read_only_depends_on,
|
||||
});
|
||||
}
|
||||
}
|
||||
frm.refresh();
|
||||
frm.refresh_field('web_form_fields');
|
||||
frm.scroll_to_field('web_form_fields');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
set_fields(frm) {
|
||||
let doc = frm.doc;
|
||||
|
||||
let update_options = options => {
|
||||
[
|
||||
frm.fields_dict.web_form_fields.grid,
|
||||
frm.fields_dict.list_columns.grid
|
||||
].forEach(obj => {
|
||||
obj.update_docfield_property("fieldname", "options", options);
|
||||
});
|
||||
};
|
||||
|
||||
if (!doc.doc_type) {
|
||||
update_options([]);
|
||||
frm.set_df_property("amount_field", "options", []);
|
||||
return;
|
||||
}
|
||||
|
||||
update_options([`Fetching fields from ${doc.doc_type}...`]);
|
||||
|
||||
get_fields_for_doctype(doc.doc_type).then(fields => {
|
||||
let as_select_option = df => ({
|
||||
label: df.label + " (" + df.fieldtype + ")",
|
||||
value: df.fieldname
|
||||
});
|
||||
update_options(fields.map(as_select_option));
|
||||
|
||||
let currency_fields = fields
|
||||
.filter(df => ["Currency", "Float"].includes(df.fieldtype))
|
||||
.map(as_select_option);
|
||||
if (!currency_fields.length) {
|
||||
currency_fields = [
|
||||
{
|
||||
label: `No currency fields in ${doc.doc_type}`,
|
||||
value: "",
|
||||
disabled: true
|
||||
}
|
||||
];
|
||||
}
|
||||
frm.set_df_property("amount_field", "options", currency_fields);
|
||||
});
|
||||
},
|
||||
|
||||
title: function(frm) {
|
||||
if (frm.doc.__islocal) {
|
||||
var page_name = frm.doc.title.toLowerCase().replace(/ /g, "-");
|
||||
frm.set_value("route", page_name);
|
||||
frm.set_value("success_url", "/" + page_name);
|
||||
}
|
||||
},
|
||||
|
||||
doc_type: function(frm) {
|
||||
frappe.web_form.set_fieldname_select(frm);
|
||||
frm.trigger('set_fields');
|
||||
},
|
||||
|
||||
allow_multiple: function(frm) {
|
||||
frm.doc.allow_multiple && frm.set_value("show_list", 1);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
frappe.ui.form.on("Web Form List Column", {
|
||||
fieldname: function(frm, doctype, name) {
|
||||
let doc = frappe.get_doc(doctype, name);
|
||||
let df = frappe.meta.get_docfield(frm.doc.doc_type, doc.fieldname);
|
||||
if (!df) return;
|
||||
doc.fieldtype = df.fieldtype;
|
||||
doc.label = df.label;
|
||||
frm.refresh_field("list_columns");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -93,22 +153,61 @@ frappe.ui.form.on("Web Form Field", {
|
|||
var doc = frappe.get_doc(doctype, name);
|
||||
if (['Section Break', 'Column Break', 'Page Break'].includes(doc.fieldtype)) {
|
||||
doc.fieldname = '';
|
||||
doc.options = "";
|
||||
frm.refresh_field("web_form_fields");
|
||||
}
|
||||
},
|
||||
fieldname: function(frm, doctype, name) {
|
||||
var doc = frappe.get_doc(doctype, name);
|
||||
var df = $.map(frappe.get_doc("DocType", frm.doc.doc_type).fields, function(d) {
|
||||
return doc.fieldname == d.fieldname ? d : null;
|
||||
})[0];
|
||||
let doc = frappe.get_doc(doctype, name);
|
||||
let df = frappe.meta.get_docfield(frm.doc.doc_type, doc.fieldname);
|
||||
if (!df) return;
|
||||
|
||||
doc.label = df.label;
|
||||
doc.reqd = df.reqd;
|
||||
doc.fieldtype = df.fieldtype;
|
||||
doc.options = df.options;
|
||||
doc.fieldtype = frappe.meta.get_docfield("Web Form Field", "fieldtype")
|
||||
.options.split("\n").indexOf(df.fieldtype) === -1 ? "Data" : df.fieldtype;
|
||||
doc.description = df.description;
|
||||
doc["default"] = df["default"];
|
||||
doc.reqd = df.reqd;
|
||||
doc.default = df.default;
|
||||
doc.read_only = df.read_only;
|
||||
doc.depends_on = df.depends_on;
|
||||
doc.mandatory_depends_on = df.mandatory_depends_on;
|
||||
doc.read_only_depends_on = df.read_only_depends_on;
|
||||
|
||||
frm.refresh_field("web_form_fields");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function get_fields_for_doctype(doctype) {
|
||||
return new Promise(resolve =>
|
||||
frappe.model.with_doctype(doctype, resolve)
|
||||
).then(() => {
|
||||
return frappe.meta.get_docfields(doctype).filter(df => {
|
||||
return (
|
||||
(frappe.model.is_value_type(df.fieldtype) &&
|
||||
!["lft", "rgt"].includes(df.fieldname)) ||
|
||||
["Table", "Table Multiselect"].includes(df.fieldtype)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function render_list_settings_message(frm) {
|
||||
// render list setting message
|
||||
if (frm.fields_dict['list_setting_message'] && !frm.doc.login_required) {
|
||||
const switch_to_form_settings_tab = `
|
||||
<span class="bold pointer" title="${__("Switch to Form Settings Tab")}">
|
||||
${__("Form Settings Tab")}
|
||||
</span>
|
||||
`;
|
||||
$(frm.fields_dict['list_setting_message'].wrapper)
|
||||
.html($(
|
||||
`<div class="form-message blue">
|
||||
${__("Login is required to see web form list view. Enable <code>login_required</code> from {0} to see list settings", [switch_to_form_settings_tab])}
|
||||
</div>`
|
||||
))
|
||||
.find('span')
|
||||
.click(() => frm.scroll_to_field('login_required'));
|
||||
} else {
|
||||
$(frm.fields_dict['list_setting_message'].wrapper).empty();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,43 +5,51 @@
|
|||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title_and_route_tab",
|
||||
"title",
|
||||
"route",
|
||||
"published",
|
||||
"column_break_4",
|
||||
"doc_type",
|
||||
"module",
|
||||
"column_break_4",
|
||||
"is_standard",
|
||||
"is_multi_step_form",
|
||||
"published",
|
||||
"introduction",
|
||||
"introduction_text",
|
||||
"form_settings_tab",
|
||||
"login_required",
|
||||
"route_to_success_link",
|
||||
"allow_edit",
|
||||
"is_multi_step_form",
|
||||
"allow_multiple",
|
||||
"apply_document_permissions",
|
||||
"show_in_grid",
|
||||
"allow_edit",
|
||||
"allow_delete",
|
||||
"column_break_18",
|
||||
"apply_document_permissions",
|
||||
"allow_print",
|
||||
"print_format",
|
||||
"allow_comments",
|
||||
"show_attachments",
|
||||
"allow_incomplete",
|
||||
"introduction",
|
||||
"introduction_text",
|
||||
"fields",
|
||||
"form_fields",
|
||||
"web_form_fields",
|
||||
"max_attachment_size",
|
||||
"client_script_section",
|
||||
"client_script",
|
||||
"custom_css_section",
|
||||
"custom_css",
|
||||
"actions",
|
||||
"breadcrumbs",
|
||||
"button_label",
|
||||
"column_break_29",
|
||||
"success_message",
|
||||
"route_to_success_link",
|
||||
"success_url",
|
||||
"sidebar_settings",
|
||||
"list_settings_tab",
|
||||
"list_setting_message",
|
||||
"show_list",
|
||||
"list_title",
|
||||
"list_columns",
|
||||
"sidebar_settings_tab",
|
||||
"show_sidebar",
|
||||
"sidebar_items",
|
||||
"payments",
|
||||
"website_sidebar",
|
||||
"scripting_style_tab",
|
||||
"client_script",
|
||||
"custom_css",
|
||||
"payments_tab",
|
||||
"accept_payment",
|
||||
"payment_gateway",
|
||||
"payment_button_label",
|
||||
|
|
@ -50,10 +58,7 @@
|
|||
"amount_based_on_field",
|
||||
"amount_field",
|
||||
"amount",
|
||||
"currency",
|
||||
"advanced",
|
||||
"web_page_link_text",
|
||||
"breadcrumbs"
|
||||
"currency"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -118,25 +123,18 @@
|
|||
"depends_on": "login_required",
|
||||
"fieldname": "allow_edit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Edit"
|
||||
"label": "Allow Editing After Submit"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "login_required",
|
||||
"fieldname": "allow_multiple",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Multiple"
|
||||
"label": "Allow Multiple Responses"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "allow_multiple",
|
||||
"fieldname": "show_in_grid",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show as Grid"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "allow_multiple",
|
||||
"depends_on": "eval: doc.allow_multiple && doc.login_required",
|
||||
"fieldname": "allow_delete",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Delete"
|
||||
|
|
@ -187,11 +185,6 @@
|
|||
"ignore_xss_filter": 1,
|
||||
"label": "Introduction"
|
||||
},
|
||||
{
|
||||
"fieldname": "fields",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Fields"
|
||||
},
|
||||
{
|
||||
"fieldname": "web_form_fields",
|
||||
"fieldtype": "Table",
|
||||
|
|
@ -203,13 +196,6 @@
|
|||
"fieldtype": "Int",
|
||||
"label": "Max Attachment Size (in MB)"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "client_script",
|
||||
"fieldname": "client_script_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Client Script"
|
||||
},
|
||||
{
|
||||
"description": "For help see <a href=\"https://frappeframework.com/docs/user/en/guides/portal-development/web-forms\" target=\"_blank\">Client Script API and Examples</a>",
|
||||
"fieldname": "client_script",
|
||||
|
|
@ -220,13 +206,13 @@
|
|||
"collapsible": 1,
|
||||
"fieldname": "actions",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Actions"
|
||||
"label": "Customization"
|
||||
},
|
||||
{
|
||||
"default": "Save",
|
||||
"fieldname": "button_label",
|
||||
"fieldtype": "Data",
|
||||
"label": "Button Label"
|
||||
"label": "Submit Button Label"
|
||||
},
|
||||
{
|
||||
"description": "Message to be displayed on successful completion (only for Guest users)",
|
||||
|
|
@ -235,36 +221,18 @@
|
|||
"label": "Success Message"
|
||||
},
|
||||
{
|
||||
"depends_on": "route_to_success_link",
|
||||
"description": "Go to this URL after completing the form",
|
||||
"fieldname": "success_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Success URL"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sidebar_settings",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Sidebar Settings"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_sidebar",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Sidebar"
|
||||
},
|
||||
{
|
||||
"fieldname": "sidebar_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Sidebar Items",
|
||||
"options": "Portal Menu Item"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "accept_payment",
|
||||
"fieldname": "payments",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Payments"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "accept_payment",
|
||||
|
|
@ -321,18 +289,6 @@
|
|||
"label": "Currency",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "advanced",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Advanced"
|
||||
},
|
||||
{
|
||||
"description": "Text to be displayed for Link to Web Page if this form has a web page. Link route will be automatically generated based on `page_name` and `parent_website_route`",
|
||||
"fieldname": "web_page_link_text",
|
||||
"fieldtype": "Data",
|
||||
"label": "Web Page Link Text"
|
||||
},
|
||||
{
|
||||
"description": "List as [{\"label\": _(\"Jobs\"), \"route\":\"jobs\"}]",
|
||||
"fieldname": "breadcrumbs",
|
||||
|
|
@ -345,13 +301,6 @@
|
|||
"label": "Custom CSS",
|
||||
"options": "CSS"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "custom_css",
|
||||
"fieldname": "custom_css_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Custom CSS"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "apply_document_permissions",
|
||||
|
|
@ -363,13 +312,93 @@
|
|||
"fieldname": "is_multi_step_form",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Multi Step Form"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "login_required",
|
||||
"fieldname": "show_list",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show List"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.login_required && doc.show_list",
|
||||
"fieldname": "list_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.login_required && doc.show_list",
|
||||
"fieldname": "list_columns",
|
||||
"fieldtype": "Table",
|
||||
"label": "List Columns",
|
||||
"options": "Web Form List Column"
|
||||
},
|
||||
{
|
||||
"fieldname": "title_and_route_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Title & Route"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "form_fields",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Form Fields"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "website_sidebar",
|
||||
"fieldtype": "Link",
|
||||
"label": "Website Sidebar",
|
||||
"options": "Website Sidebar"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_29",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "list_setting_message",
|
||||
"fieldtype": "HTML",
|
||||
"label": "List Setting Message"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Form Settings"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "show_list",
|
||||
"fieldname": "list_settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "List Settings"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sidebar_settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Sidebar Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "scripting_style_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Scripting / Style"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "accept_payment",
|
||||
"fieldname": "payments_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Payments"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"icon": "icon-edit",
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"modified": "2022-03-23 15:44:41.385001",
|
||||
"modified": "2022-07-18 15:51:15.288860",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Form",
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ from frappe.desk.form.meta import get_code_files_via_hooks
|
|||
from frappe.integrations.utils import get_payment_gateway_controller
|
||||
from frappe.modules.utils import export_module_json, get_doc_module
|
||||
from frappe.rate_limiter import rate_limit
|
||||
from frappe.utils import cstr
|
||||
from frappe.website.utils import get_comment_list
|
||||
from frappe.utils import cstr, dict_with_keys, strip_html
|
||||
from frappe.website.utils import get_boot_data, get_comment_list, get_sidebar_items
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
|
||||
|
||||
|
|
@ -32,17 +32,20 @@ class WebForm(WebsiteGenerator):
|
|||
if not self.module:
|
||||
self.module = frappe.db.get_value("DocType", self.doc_type, "module")
|
||||
|
||||
if (
|
||||
not (
|
||||
frappe.flags.in_install
|
||||
or frappe.flags.in_patch
|
||||
or frappe.flags.in_test
|
||||
or frappe.flags.in_fixtures
|
||||
)
|
||||
and self.is_standard
|
||||
and not frappe.conf.developer_mode
|
||||
):
|
||||
frappe.throw(_("You need to be in developer mode to edit a Standard Web Form"))
|
||||
in_user_env = not (
|
||||
frappe.flags.in_install
|
||||
or frappe.flags.in_patch
|
||||
or frappe.flags.in_test
|
||||
or frappe.flags.in_fixtures
|
||||
)
|
||||
if in_user_env and self.is_standard and not frappe.conf.developer_mode:
|
||||
# only published can be changed for standard web forms
|
||||
if self.has_value_changed("published"):
|
||||
published_value = self.published
|
||||
self.reload()
|
||||
self.published = published_value
|
||||
else:
|
||||
frappe.throw(_("You need to be in developer mode to edit a Standard Web Form"))
|
||||
|
||||
if not frappe.flags.in_import:
|
||||
self.validate_fields()
|
||||
|
|
@ -131,60 +134,131 @@ def get_context(context):
|
|||
|
||||
def get_context(self, context):
|
||||
"""Build context to render the `web_form.html` template"""
|
||||
context.is_form_editable = False
|
||||
self.set_web_form_module()
|
||||
|
||||
doc, delimeter = make_route_string(frappe.form_dict)
|
||||
context.doc = doc
|
||||
context.delimeter = delimeter
|
||||
if frappe.form_dict.is_list:
|
||||
context.template = "website/doctype/web_form/templates/web_list.html"
|
||||
else:
|
||||
context.template = "website/doctype/web_form/templates/web_form.html"
|
||||
|
||||
# check permissions
|
||||
if frappe.session.user == "Guest" and frappe.form_dict.name:
|
||||
frappe.throw(
|
||||
_("You need to be logged in to access this {0}.").format(self.doc_type), frappe.PermissionError
|
||||
)
|
||||
if frappe.form_dict.name:
|
||||
if frappe.session.user == "Guest":
|
||||
frappe.throw(
|
||||
_("You need to be logged in to access this {0}.").format(self.doc_type),
|
||||
frappe.PermissionError,
|
||||
)
|
||||
|
||||
if frappe.form_dict.name and not self.has_web_form_permission(
|
||||
self.doc_type, frappe.form_dict.name
|
||||
if not frappe.db.exists(self.doc_type, frappe.form_dict.name):
|
||||
raise frappe.PageDoesNotExistError()
|
||||
|
||||
if not self.has_web_form_permission(self.doc_type, frappe.form_dict.name):
|
||||
frappe.throw(
|
||||
_("You don't have the permissions to access this document"), frappe.PermissionError
|
||||
)
|
||||
|
||||
if frappe.local.path == self.route:
|
||||
path = f"/{self.route}/list" if self.show_list else f"/{self.route}/new"
|
||||
frappe.redirect(path)
|
||||
|
||||
if frappe.form_dict.is_list and not self.show_list:
|
||||
frappe.redirect(f"/{self.route}/new")
|
||||
|
||||
if frappe.form_dict.is_edit and not self.allow_edit:
|
||||
frappe.redirect(f"/{self.route}/{frappe.form_dict.name}")
|
||||
|
||||
if frappe.form_dict.is_edit:
|
||||
context.is_form_editable = True
|
||||
|
||||
if (
|
||||
not frappe.form_dict.is_edit
|
||||
and not frappe.form_dict.is_read
|
||||
and self.allow_edit
|
||||
and frappe.form_dict.name
|
||||
):
|
||||
frappe.throw(
|
||||
_("You don't have the permissions to access this document"), frappe.PermissionError
|
||||
)
|
||||
context.is_form_editable = True
|
||||
frappe.redirect(f"/{frappe.local.path}/edit")
|
||||
|
||||
if (
|
||||
frappe.session.user != "Guest"
|
||||
and not self.allow_multiple
|
||||
and not frappe.form_dict.name
|
||||
and not frappe.form_dict.is_list
|
||||
):
|
||||
name = frappe.db.get_value(self.doc_type, {"owner": frappe.session.user}, "name")
|
||||
if name:
|
||||
frappe.redirect(f"/{self.route}/{name}")
|
||||
|
||||
# Show new form when
|
||||
# - User is Guest
|
||||
# - Login not required
|
||||
route_to_new = frappe.session.user == "Guest" and not self.login_required
|
||||
if not frappe.form_dict.is_new and route_to_new:
|
||||
frappe.redirect(f"/{self.route}/new")
|
||||
|
||||
self.reset_field_parent()
|
||||
|
||||
if self.is_standard:
|
||||
self.use_meta_fields()
|
||||
|
||||
if not frappe.session.user == "Guest":
|
||||
if self.allow_edit:
|
||||
if self.allow_multiple:
|
||||
if not frappe.form_dict.name and not frappe.form_dict.new:
|
||||
# list data is queried via JS
|
||||
context.is_list = True
|
||||
else:
|
||||
if frappe.session.user != "Guest" and not frappe.form_dict.name:
|
||||
frappe.form_dict.name = frappe.db.get_value(
|
||||
self.doc_type, {"owner": frappe.session.user}, "name"
|
||||
)
|
||||
# add keys from form_dict to context
|
||||
context.update(dict_with_keys(frappe.form_dict, ["is_list", "is_new", "is_edit", "is_read"]))
|
||||
|
||||
if not frappe.form_dict.name:
|
||||
# only a single doc allowed and no existing doc, hence new
|
||||
frappe.form_dict.new = 1
|
||||
for df in self.web_form_fields:
|
||||
if df.fieldtype == "Column Break":
|
||||
context.has_column_break = True
|
||||
break
|
||||
|
||||
# load web form doc
|
||||
context.web_form_doc = self.as_dict(no_nulls=True)
|
||||
context.web_form_doc.update(dict_with_keys(context, ["is_list", "is_new", "is_form_editable"]))
|
||||
|
||||
if self.show_sidebar and self.website_sidebar:
|
||||
context.sidebar_items = get_sidebar_items(self.website_sidebar)
|
||||
|
||||
if frappe.form_dict.is_list:
|
||||
context.is_list = True
|
||||
self.load_list_data(context)
|
||||
else:
|
||||
self.load_form_data(context)
|
||||
|
||||
# always render new form if login is not required or doesn't allow editing existing ones
|
||||
if not self.login_required or not self.allow_edit:
|
||||
frappe.form_dict.new = 1
|
||||
self.add_custom_context_and_script(context)
|
||||
self.load_translations(context)
|
||||
|
||||
context.boot = get_boot_data()
|
||||
context.boot["link_title_doctypes"] = frappe.boot.get_link_title_doctypes()
|
||||
|
||||
def load_translations(self, context):
|
||||
translated_messages = frappe.translate.get_dict("doctype", self.doc_type)
|
||||
# Sr is not added by default, had to be added manually
|
||||
translated_messages["Sr"] = _("Sr")
|
||||
context.translated_messages = frappe.as_json(translated_messages)
|
||||
|
||||
def load_list_data(self, context):
|
||||
if not self.list_columns:
|
||||
self.list_columns = get_in_list_view_fields(self.doc_type)
|
||||
context.web_form_doc.list_columns = self.list_columns
|
||||
|
||||
def load_form_data(self, context):
|
||||
"""Load document `doc` and `layout` properties for template"""
|
||||
context.parents = []
|
||||
if self.show_list:
|
||||
context.parents.append(
|
||||
{
|
||||
"label": _(self.title),
|
||||
"route": f"{self.route}/list",
|
||||
}
|
||||
)
|
||||
|
||||
self.load_document(context)
|
||||
context.parents = self.get_parents(context)
|
||||
|
||||
if self.breadcrumbs:
|
||||
context.parents = frappe.safe_eval(self.breadcrumbs, {"_": _})
|
||||
|
||||
context.has_header = (frappe.form_dict.name or frappe.form_dict.new) and (
|
||||
if frappe.form_dict.is_new:
|
||||
context.title = _("New {0}").format(context.title)
|
||||
|
||||
context.has_header = (frappe.form_dict.name or frappe.form_dict.is_new) and (
|
||||
frappe.session.user != "Guest" or not self.login_required
|
||||
)
|
||||
|
||||
|
|
@ -193,33 +267,40 @@ def get_context(context):
|
|||
"'"
|
||||
)
|
||||
|
||||
self.add_custom_context_and_script(context)
|
||||
if not context.max_attachment_size:
|
||||
context.max_attachment_size = get_max_file_size() / 1024 / 1024
|
||||
|
||||
context.show_in_grid = self.show_in_grid
|
||||
self.load_translations(context)
|
||||
context.link_title_doctypes = frappe.boot.get_link_title_doctypes()
|
||||
# For Table fields, server-side processing for meta
|
||||
for field in context.web_form_doc.web_form_fields:
|
||||
if field.fieldtype == "Table":
|
||||
field.fields = get_in_list_view_fields(field.options)
|
||||
|
||||
def load_translations(self, context):
|
||||
translated_messages = frappe.translate.get_dict("doctype", self.doc_type)
|
||||
# Sr is not added by default, had to be added manually
|
||||
translated_messages["Sr"] = _("Sr")
|
||||
context.translated_messages = frappe.as_json(translated_messages)
|
||||
if field.fieldtype == "Link":
|
||||
field.fieldtype = "Autocomplete"
|
||||
field.options = get_link_options(
|
||||
self.name, field.options, field.allow_read_on_all_link_options
|
||||
)
|
||||
|
||||
def load_document(self, context):
|
||||
"""Load document `doc` and `layout` properties for template"""
|
||||
if frappe.form_dict.name or frappe.form_dict.new:
|
||||
context.layout = self.get_layout()
|
||||
context.parents = [{"route": self.route, "label": _(self.title)}]
|
||||
context.reference_doc = {}
|
||||
|
||||
# load reference doc
|
||||
if frappe.form_dict.name:
|
||||
context.doc = frappe.get_doc(self.doc_type, frappe.form_dict.name)
|
||||
context.title = context.doc.get(context.doc.meta.get_title_field())
|
||||
context.doc.add_seen()
|
||||
|
||||
context.reference_doctype = context.doc.doctype
|
||||
context.reference_name = context.doc.name
|
||||
context.doc_name = frappe.form_dict.name
|
||||
context.reference_doc = frappe.get_doc(self.doc_type, context.doc_name)
|
||||
context.title = strip_html(
|
||||
context.reference_doc.get(context.reference_doc.meta.get_title_field())
|
||||
)
|
||||
if context.is_form_editable:
|
||||
context.parents.append(
|
||||
{
|
||||
"label": _(context.title),
|
||||
"route": f"{self.route}/{context.doc_name}",
|
||||
}
|
||||
)
|
||||
context.title = _("Edit")
|
||||
context.reference_doc.add_seen()
|
||||
context.reference_doctype = context.reference_doc.doctype
|
||||
context.reference_name = context.reference_doc.name
|
||||
|
||||
if self.show_attachments:
|
||||
context.attachments = frappe.get_all(
|
||||
|
|
@ -233,7 +314,11 @@ def get_context(context):
|
|||
)
|
||||
|
||||
if self.allow_comments:
|
||||
context.comment_list = get_comment_list(context.doc.doctype, context.doc.name)
|
||||
context.comment_list = get_comment_list(
|
||||
context.reference_doc.doctype, context.reference_doc.name
|
||||
)
|
||||
|
||||
context.reference_doc = json.loads(context.reference_doc.as_json())
|
||||
|
||||
def get_payment_gateway_url(self, doc):
|
||||
if self.accept_payment:
|
||||
|
|
@ -594,7 +679,7 @@ def get_form_data(doctype, docname=None, web_form_name=None):
|
|||
# For Table fields, server-side processing for meta
|
||||
for field in out.web_form.web_form_fields:
|
||||
if field.fieldtype == "Table":
|
||||
field.fields = frappe.get_meta(field.options).fields
|
||||
field.fields = get_in_list_view_fields(field.options)
|
||||
out.update({field.fieldname: field.fields})
|
||||
|
||||
if field.fieldtype == "Link":
|
||||
|
|
|
|||
10
frappe/website/doctype/web_form/web_form_list.js
Normal file
10
frappe/website/doctype/web_form/web_form_list.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
frappe.listview_settings['Web Form'] = {
|
||||
add_fields: ["title", "published"],
|
||||
get_indicator: function(doc) {
|
||||
if (doc.published) {
|
||||
return [__("Published"), "green", "published,=,1"];
|
||||
} else {
|
||||
return [__("Not Published"), "gray", "published,=,0"];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
"label",
|
||||
"allow_read_on_all_link_options",
|
||||
"reqd",
|
||||
"depends_on",
|
||||
"read_only",
|
||||
"show_in_filter",
|
||||
"hidden",
|
||||
|
|
@ -19,6 +18,7 @@
|
|||
"max_length",
|
||||
"max_value",
|
||||
"property_depends_on_section",
|
||||
"depends_on",
|
||||
"mandatory_depends_on",
|
||||
"column_break_16",
|
||||
"read_only_depends_on",
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
{
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Depends On"
|
||||
"label": "Display Depends On"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -146,12 +146,13 @@
|
|||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-28 10:41:25.422345",
|
||||
"modified": "2022-06-06 16:00:55.627950",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Form Field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
0
frappe/website/doctype/web_form_list_column/__init__.py
Normal file
0
frappe/website/doctype/web_form_list_column/__init__.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "autoincrement",
|
||||
"creation": "2022-06-20 20:02:12.132569",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"fieldname",
|
||||
"fieldtype",
|
||||
"label"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldtype",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-21 17:22:14.978947",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Form List Column",
|
||||
"naming_rule": "Autoincrement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) 2022, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class WebFormListColumn(Document):
|
||||
pass
|
||||
|
|
@ -16,6 +16,7 @@ from frappe.website.utils import (
|
|||
find_first_image,
|
||||
get_comment_list,
|
||||
get_html_content_based_on_type,
|
||||
get_sidebar_items,
|
||||
)
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
|
||||
|
|
@ -70,6 +71,9 @@ class WebPage(WebsiteGenerator):
|
|||
if not self.show_title:
|
||||
context["no_header"] = 1
|
||||
|
||||
if self.show_sidebar:
|
||||
context.sidebar_items = get_sidebar_items(self.website_sidebar)
|
||||
|
||||
self.set_metatags(context)
|
||||
self.set_breadcrumbs(context)
|
||||
self.set_title_and_header(context)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from frappe import _
|
|||
from frappe.integrations.google_oauth import GoogleOAuth
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import encode, get_request_site_address
|
||||
from frappe.website.utils import get_boot_data
|
||||
|
||||
|
||||
class WebsiteSettings(Document):
|
||||
|
|
@ -190,6 +191,8 @@ def get_website_settings(context=None):
|
|||
if settings.splash_image:
|
||||
context["splash_image"] = settings.splash_image
|
||||
|
||||
context.boot = get_boot_data()
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import frappe
|
||||
from frappe.website.page_renderers.document_page import DocumentPage
|
||||
from frappe.website.router import get_page_info_from_web_form
|
||||
|
||||
|
||||
class WebFormPage(DocumentPage):
|
||||
def can_render(self):
|
||||
webform_name = frappe.db.exists("Web Form", {"route": self.path, "published": 1}, cache=True)
|
||||
if webform_name:
|
||||
web_form = get_page_info_from_web_form(self.path)
|
||||
if web_form:
|
||||
self.doctype = "Web Form"
|
||||
self.docname = webform_name
|
||||
return bool(webform_name)
|
||||
self.docname = web_form.name
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -30,6 +30,32 @@ def get_page_info_from_web_page_with_dynamic_routes(path):
|
|||
return page_info[end_point]
|
||||
|
||||
|
||||
def get_page_info_from_web_form(path):
|
||||
"""Query published web forms and evaluate if the route matches"""
|
||||
rules, page_info = [], {}
|
||||
web_forms = frappe.db.get_all("Web Form", ["name", "route", "modified"], {"published": 1})
|
||||
for d in web_forms:
|
||||
rules.append(Rule(f"/{d.route}", endpoint=d.name))
|
||||
rules.append(Rule(f"/{d.route}/list", endpoint=d.name))
|
||||
rules.append(Rule(f"/{d.route}/new", endpoint=d.name))
|
||||
rules.append(Rule(f"/{d.route}/<name>", endpoint=d.name))
|
||||
rules.append(Rule(f"/{d.route}/<name>/edit", endpoint=d.name))
|
||||
d.doctype = "Web Form"
|
||||
page_info[d.name] = d
|
||||
|
||||
end_point = evaluate_dynamic_routes(rules, path)
|
||||
if end_point:
|
||||
if path.endswith("/list"):
|
||||
frappe.form_dict.is_list = True
|
||||
elif path.endswith("/new"):
|
||||
frappe.form_dict.is_new = True
|
||||
elif path.endswith("/edit"):
|
||||
frappe.form_dict.is_edit = True
|
||||
else:
|
||||
frappe.form_dict.is_read = True
|
||||
return page_info[end_point]
|
||||
|
||||
|
||||
def evaluate_dynamic_routes(rules, path):
|
||||
"""
|
||||
Use Werkzeug routing to evaluate dynamic routes like /project/<name>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import frappe
|
||||
from frappe.website.page_renderers.error_page import ErrorPage
|
||||
from frappe.website.page_renderers.not_found_page import NotFoundPage
|
||||
from frappe.website.page_renderers.not_permitted_page import NotPermittedPage
|
||||
from frappe.website.page_renderers.redirect_page import RedirectPage
|
||||
from frappe.website.path_resolver import PathResolver
|
||||
|
|
@ -19,6 +20,8 @@ def get_response(path=None, http_status_code=200):
|
|||
return RedirectPage(endpoint or path, http_status_code).render()
|
||||
except frappe.PermissionError as e:
|
||||
response = NotPermittedPage(endpoint, http_status_code, exception=e).render()
|
||||
except frappe.PageDoesNotExistError:
|
||||
response = NotFoundPage(endpoint, http_status_code).render()
|
||||
except Exception as e:
|
||||
frappe.log_error(f"{path} failed")
|
||||
response = ErrorPage(exception=e).render()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from werkzeug.wrappers import Response
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import md_to_html
|
||||
from frappe.utils import cint, get_time_zone, md_to_html
|
||||
|
||||
FRONTMATTER_PATTERN = re.compile(r"^\s*(?:---|\+\+\+)(.*?)(?:---|\+\+\+)\s*(.+)$", re.S | re.M)
|
||||
H1_TAG_PATTERN = re.compile("<h1>([^<]*)")
|
||||
|
|
@ -158,6 +158,20 @@ def get_home_page_via_hooks():
|
|||
return home_page
|
||||
|
||||
|
||||
def get_boot_data():
|
||||
return {
|
||||
"sysdefaults": {
|
||||
"float_precision": cint(frappe.get_system_settings("float_precision")) or 3,
|
||||
"date_format": frappe.get_system_settings("date_format") or "yyyy-mm-dd",
|
||||
"time_format": frappe.get_system_settings("time_format") or "HH:mm:ss",
|
||||
},
|
||||
"time_zone": {
|
||||
"system": get_time_zone(),
|
||||
"user": frappe.db.get_value("User", frappe.session.user, "time_zone") or get_time_zone(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def is_signup_disabled():
|
||||
return frappe.db.get_single_value("Website Settings", "disable_signup", True)
|
||||
|
||||
|
|
@ -393,7 +407,7 @@ def get_frontmatter(string):
|
|||
}
|
||||
|
||||
|
||||
def get_sidebar_items(parent_sidebar, basepath):
|
||||
def get_sidebar_items(parent_sidebar, basepath=None):
|
||||
import frappe.www.list
|
||||
|
||||
sidebar_items = []
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"apply_document_permissions": 0,
|
||||
"breadcrumbs": "",
|
||||
"button_label": "Request Data",
|
||||
"client_script": "",
|
||||
"creation": "2019-01-24 16:19:26.886096",
|
||||
"currency": "INR",
|
||||
"doc_type": "Personal Data Download Request",
|
||||
|
|
@ -18,10 +19,12 @@
|
|||
"doctype": "Web Form",
|
||||
"idx": 0,
|
||||
"introduction_text": "<div class=\"ql-editor read-mode\"><p>Request a file containing your personally identifiable information (PII) that is saved on our system. The file will be in JSON format and is sent to you by email. If you would like to have your PII deleted from our system, please make a <a href=\"/request-to-delete-data\" rel=\"noopener noreferrer\">request to delete data</a>.</p></div>",
|
||||
"is_multi_step_form": 0,
|
||||
"is_standard": 1,
|
||||
"list_columns": [],
|
||||
"login_required": 0,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2021-03-25 10:52:13.149538",
|
||||
"modified": "2022-07-18 16:51:07.281527",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "request-data",
|
||||
|
|
@ -31,9 +34,8 @@
|
|||
"route": "request-data",
|
||||
"route_to_success_link": 1,
|
||||
"show_attachments": 0,
|
||||
"show_in_grid": 0,
|
||||
"show_list": 0,
|
||||
"show_sidebar": 0,
|
||||
"sidebar_items": [],
|
||||
"success_message": "A download link with your data will be sent to the email address associated with your account.",
|
||||
"success_url": "/desk",
|
||||
"title": "Request Data",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"amount": 0.0,
|
||||
"amount_based_on_field": 0,
|
||||
"apply_document_permissions": 0,
|
||||
"breadcrumbs": "",
|
||||
"button_label": "Submit",
|
||||
"client_script": "",
|
||||
"creation": "2019-01-25 14:24:12.588810",
|
||||
|
|
@ -19,10 +20,12 @@
|
|||
"doctype": "Web Form",
|
||||
"idx": 0,
|
||||
"introduction_text": "<div class=\"ql-editor read-mode\"><p>Send a request to delete your account and personally identifiable information (PII) that is stored on our system. You will receive an email to verify your request. Once the request is verified we will take care of deleting your PII. If you just want to check what PII we have stored, you can <a href=\"/request-data\" rel=\"noopener noreferrer\">request your data</a>.</p></div>",
|
||||
"is_multi_step_form": 0,
|
||||
"is_standard": 1,
|
||||
"list_columns": [],
|
||||
"login_required": 0,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2021-11-30 17:56:03.099870",
|
||||
"modified": "2022-07-18 16:51:30.949738",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "request-to-delete-data",
|
||||
|
|
@ -32,9 +35,8 @@
|
|||
"route": "request-for-account-deletion",
|
||||
"route_to_success_link": 0,
|
||||
"show_attachments": 0,
|
||||
"show_in_grid": 0,
|
||||
"show_list": 0,
|
||||
"show_sidebar": 0,
|
||||
"sidebar_items": [],
|
||||
"success_message": "An email to verify your request has been sent to your email address. Please verify your request to complete the process.",
|
||||
"success_url": "/",
|
||||
"title": "Request for Account Deletion",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue