Merge branch 'develop' of github.com:frappe/frappe into refactor-file
This commit is contained in:
commit
1d84483289
55 changed files with 506 additions and 274 deletions
32
.github/workflows/release.yml
vendored
Normal file
32
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
name: Generate Semantic Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- version-13
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Entire Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js v14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
npm install @semantic-release/git @semantic-release/exec --no-save
|
||||
- name: Create Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GIT_AUTHOR_NAME: "Frappe PR Bot"
|
||||
GIT_AUTHOR_EMAIL: "developers@frappe.io"
|
||||
GIT_COMMITTER_NAME: "Frappe PR Bot"
|
||||
GIT_COMMITTER_EMAIL: "developers@frappe.io"
|
||||
run: npx semantic-release
|
||||
40
.mergify.yml
40
.mergify.yml
|
|
@ -53,3 +53,43 @@ pull_request_rules:
|
|||
{{ title }} (#{{ number }})
|
||||
|
||||
{{ body }}
|
||||
|
||||
- name: backport to develop
|
||||
conditions:
|
||||
- label="backport develop"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- develop
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-13-hotfix
|
||||
conditions:
|
||||
- label="backport version-13-hotfix"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-13-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-13-pre-release
|
||||
conditions:
|
||||
- label="backport version-13-pre-release"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-13-pre-release
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-12-hotfix
|
||||
conditions:
|
||||
- label="backport version-12-hotfix"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-12-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
24
.releaserc
Normal file
24
.releaserc
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"branches": ["version-13"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"preset": "angular",
|
||||
"releaseRules": [
|
||||
{"breaking": true, "release": false}
|
||||
]
|
||||
},
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/exec", {
|
||||
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" frappe/__init__.py'
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git", {
|
||||
"assets": ["frappe/__init__.py"],
|
||||
"message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ context('Control Barcode', () => {
|
|||
it('should generate barcode on setting a value', () => {
|
||||
get_dialog_with_barcode().as('dialog');
|
||||
|
||||
cy.focused().blur();
|
||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
|
||||
.type('123456789')
|
||||
.blur();
|
||||
|
|
@ -36,6 +37,7 @@ context('Control Barcode', () => {
|
|||
it('should reset when input is cleared', () => {
|
||||
get_dialog_with_barcode().as('dialog');
|
||||
|
||||
cy.focused().blur();
|
||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
|
||||
.type('123456789')
|
||||
.blur();
|
||||
|
|
|
|||
|
|
@ -1,23 +1,27 @@
|
|||
context('Date Control', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/doctype');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
|
||||
name: 'Test Date Control',
|
||||
fields: [
|
||||
{
|
||||
"label": "Date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
cy.visit('/app');
|
||||
});
|
||||
|
||||
function get_dialog(date_field_options) {
|
||||
return cy.dialog({
|
||||
title: 'Date',
|
||||
fields: [{
|
||||
"label": "Date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
...date_field_options
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
it('Selecting a date from the datepicker', () => {
|
||||
cy.new_form('Test Date Control');
|
||||
cy.clear_dialogs();
|
||||
cy.clear_datepickers();
|
||||
|
||||
get_dialog().as('dialog');
|
||||
cy.get_field('date', 'Date').click();
|
||||
cy.get('.datepicker--nav-title').click();
|
||||
cy.get('.datepicker--nav-title').click({force: true});
|
||||
|
|
@ -28,12 +32,16 @@ context('Date Control', () => {
|
|||
cy.get('.datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]').click();
|
||||
cy.get('.datepicker--days > .datepicker--cells > .datepicker--cell[data-date=15]').click();
|
||||
|
||||
//Verifying if the selected date is displayed in the date field
|
||||
cy.get_field('date', 'Date').should('have.value', '01-15-2020');
|
||||
// Verify if the selected date is set the date field
|
||||
cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-01-15');
|
||||
});
|
||||
|
||||
it('Checking next and previous button', () => {
|
||||
cy.get_field('date', 'Date').click();
|
||||
cy.clear_dialogs();
|
||||
cy.clear_datepickers();
|
||||
|
||||
get_dialog({ default: '2020-01-15' }).as('dialog');
|
||||
cy.get_field('date', 'Date').click();
|
||||
|
||||
//Clicking on the next button in the datepicker
|
||||
cy.get('.datepicker--nav-action[data-action=next]').click();
|
||||
|
|
@ -42,7 +50,7 @@ context('Date Control', () => {
|
|||
cy.get('.datepicker--cell[data-date=15]').click({force: true});
|
||||
|
||||
//Verifying if the selected date has been displayed in the date field
|
||||
cy.get_field('date', 'Date').should('have.value', '02-15-2020');
|
||||
cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-02-15');
|
||||
cy.wait(500);
|
||||
cy.get_field('date', 'Date').click();
|
||||
|
||||
|
|
@ -53,19 +61,22 @@ context('Date Control', () => {
|
|||
cy.get('.datepicker--cell[data-date=15]').click({force: true});
|
||||
|
||||
//Verifying if the selected date has been displayed in the date field
|
||||
cy.get_field('date', 'Date').should('have.value', '01-15-2020');
|
||||
cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-01-15');
|
||||
});
|
||||
|
||||
it('Clicking on "Today" button gives todays date', () => {
|
||||
cy.get_field('date', 'Date').click();
|
||||
cy.clear_dialogs();
|
||||
cy.clear_datepickers();
|
||||
|
||||
get_dialog().as('dialog');
|
||||
cy.get_field('date', 'Date').click();
|
||||
|
||||
//Clicking on "Today" button
|
||||
cy.get('.datepicker--button').click();
|
||||
|
||||
//Picking up the todays date
|
||||
const todays_date = Cypress.moment().format('MM-DD-YYYY');
|
||||
|
||||
//Verifying if clicking on "Today" button matches today's date
|
||||
cy.get_field('date', 'Date').should('have.value', todays_date);
|
||||
cy.window().then(win => {
|
||||
expect(win.cur_dialog.fields_dict.date.value).to.be.equal(win.frappe.datetime.get_today());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -10,6 +10,7 @@ context('Kanban Board', () => {
|
|||
cy.get('.page-actions .custom-btn-group button').click();
|
||||
cy.get('.page-actions .custom-btn-group ul.dropdown-menu li').contains('Kanban').click();
|
||||
|
||||
cy.focused().blur();
|
||||
cy.fill_field('board_name', 'ToDo Kanban', 'Data');
|
||||
cy.fill_field('field_name', 'Status', 'Select');
|
||||
cy.click_modal_primary_button('Save');
|
||||
|
|
|
|||
|
|
@ -241,8 +241,20 @@ Cypress.Commands.add('clear_cache', () => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('dialog', opts => {
|
||||
return cy.window().then(win => {
|
||||
var d = new win.frappe.ui.Dialog(opts);
|
||||
return cy.window({ log: false }).its('frappe', { log: false }).then(frappe => {
|
||||
Cypress.log({
|
||||
name: "dialog",
|
||||
displayName: "dialog",
|
||||
message: 'frappe.ui.Dialog',
|
||||
consoleProps: () => {
|
||||
return {
|
||||
options: opts,
|
||||
dialog: d
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var d = new frappe.ui.Dialog(opts);
|
||||
d.show();
|
||||
return d;
|
||||
});
|
||||
|
|
@ -258,6 +270,20 @@ Cypress.Commands.add('hide_dialog', () => {
|
|||
cy.get('.modal:visible').should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('clear_dialogs', () => {
|
||||
cy.window().then((win) => {
|
||||
win.$('.modal, .modal-backdrop').remove();
|
||||
});
|
||||
cy.get('.modal').should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('clear_datepickers', () => {
|
||||
cy.window().then((win) => {
|
||||
win.$('.datepicker').remove();
|
||||
});
|
||||
cy.get('.datepicker').should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('insert_doc', (doctype, args, ignore_duplicate) => {
|
||||
return cy
|
||||
.window()
|
||||
|
|
|
|||
|
|
@ -218,7 +218,6 @@ def init(site, sites_path=None, new_site=False):
|
|||
|
||||
local.module_app = None
|
||||
local.app_modules = None
|
||||
local.system_settings = _dict()
|
||||
|
||||
local.user = None
|
||||
local.user_perms = None
|
||||
|
|
@ -2077,25 +2076,36 @@ def logger(
|
|||
)
|
||||
|
||||
|
||||
def log_error(message=None, title=_("Error")):
|
||||
def log_error(title=None, message=None, reference_doctype=None, reference_name=None):
|
||||
"""Log error to Error Log"""
|
||||
|
||||
# AI ALERT:
|
||||
# Parameter ALERT:
|
||||
# the title and message may be swapped
|
||||
# the better API for this is log_error(title, message), and used in many cases this way
|
||||
# this hack tries to be smart about whats a title (single line ;-)) and fixes it
|
||||
|
||||
traceback = None
|
||||
if message:
|
||||
if "\n" in title:
|
||||
error, title = title, message
|
||||
if "\n" in title: # traceback sent as title
|
||||
traceback, title = title, message
|
||||
else:
|
||||
error = message
|
||||
else:
|
||||
error = get_traceback()
|
||||
traceback = message
|
||||
|
||||
return get_doc(dict(doctype="Error Log", error=as_unicode(error), method=title)).insert(
|
||||
ignore_permissions=True
|
||||
)
|
||||
if not traceback:
|
||||
traceback = get_traceback()
|
||||
|
||||
if not title:
|
||||
title = "Error"
|
||||
|
||||
return get_doc(
|
||||
dict(
|
||||
doctype="Error Log",
|
||||
error=as_unicode(traceback),
|
||||
method=title,
|
||||
reference_doctype=reference_doctype,
|
||||
reference_name=reference_name,
|
||||
)
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
|
||||
def get_desk_link(doctype, name):
|
||||
|
|
@ -2148,9 +2158,7 @@ def safe_eval(code, eval_globals=None, eval_locals=None):
|
|||
|
||||
|
||||
def get_system_settings(key):
|
||||
if key not in local.system_settings:
|
||||
local.system_settings.update({key: db.get_single_value("System Settings", key)})
|
||||
return local.system_settings.get(key)
|
||||
return db.get_single_value("System Settings", key, cache=True)
|
||||
|
||||
|
||||
def get_active_domains():
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ class AutoRepeat(Document):
|
|||
if self.notify_by_email and self.recipients:
|
||||
self.send_notification(new_doc)
|
||||
except Exception:
|
||||
error_log = frappe.log_error(frappe.get_traceback(), _("Auto Repeat Document Creation Failure"))
|
||||
error_log = self.log_error("Auto repeat failed")
|
||||
|
||||
self.disable_auto_repeat()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import json
|
|||
|
||||
import frappe
|
||||
from frappe.desk.notifications import clear_notifications, delete_notification_count_for
|
||||
from frappe.model.document import Document
|
||||
|
||||
common_default_keys = ["__default", "__global"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1024,6 +1024,7 @@ def set_config(context, key, value, global_=False, parse=False, as_dict=False):
|
|||
def get_version(output):
|
||||
"""Show the versions of all the installed apps."""
|
||||
from git import Repo
|
||||
from git.exc import InvalidGitRepositoryError
|
||||
|
||||
from frappe.utils.change_log import get_app_branch
|
||||
from frappe.utils.commands import render_table
|
||||
|
|
@ -1034,12 +1035,16 @@ def get_version(output):
|
|||
for app in sorted(frappe.get_all_apps()):
|
||||
module = frappe.get_module(app)
|
||||
app_hooks = frappe.get_module(app + ".hooks")
|
||||
repo = Repo(frappe.get_app_path(app, ".."))
|
||||
|
||||
app_info = frappe._dict()
|
||||
|
||||
try:
|
||||
app_info.commit = Repo(frappe.get_app_path(app, "..")).head.object.hexsha[:7]
|
||||
except InvalidGitRepositoryError:
|
||||
app_info.commit = ""
|
||||
|
||||
app_info.app = app
|
||||
app_info.branch = get_app_branch(app)
|
||||
app_info.commit = repo.head.object.hexsha[:7]
|
||||
app_info.version = getattr(app_hooks, f"{app_info.branch}_version", None) or module.__version__
|
||||
|
||||
data.append(app_info)
|
||||
|
|
|
|||
|
|
@ -450,8 +450,7 @@ def get_contacts(email_strings: List[str], auto_create_contact=False) -> List[st
|
|||
contact.insert(ignore_permissions=True)
|
||||
contact_name = contact.name
|
||||
except Exception:
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(traceback)
|
||||
contact.log_error("Unable to add contact")
|
||||
|
||||
if contact_name:
|
||||
contacts.append(contact_name)
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ def mark_email_as_seen(name: str = None):
|
|||
frappe.db.commit() # nosemgrep: this will be called in a GET request
|
||||
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
frappe.log_error("Unable to mark as seen", None, "Communication", name)
|
||||
|
||||
finally:
|
||||
frappe.response.update(
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ def start_import(data_import):
|
|||
except Exception:
|
||||
frappe.db.rollback()
|
||||
data_import.db_set("status", "Error")
|
||||
frappe.log_error(title=data_import.name)
|
||||
data_import.log_error("Data import failed")
|
||||
finally:
|
||||
frappe.flags.in_import = False
|
||||
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"description": "Don't encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
|
|
@ -547,7 +547,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-02 17:07:32.117897",
|
||||
"modified": "2022-04-19 12:27:28.641580",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -1,148 +1,76 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-16 13:09:40",
|
||||
"custom": 0,
|
||||
"description": "Log of Scheduler Errors",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 0,
|
||||
"engine": "MyISAM",
|
||||
"actions": [],
|
||||
"creation": "2013-01-16 13:09:40",
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"engine": "MyISAM",
|
||||
"field_order": [
|
||||
"seen",
|
||||
"method",
|
||||
"error",
|
||||
"reference_doctype",
|
||||
"reference_name"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "seen",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Seen",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "seen",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Seen"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "method",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Title",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "method",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "error",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Error",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "error",
|
||||
"fieldtype": "Code",
|
||||
"label": "Error",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Reference DocType",
|
||||
"options": "DocType",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference Name"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-warning-sign",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2021-10-25 12:21:44.292471",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Error Log",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-warning-sign",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-18 17:25:47.406873",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Error Log",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_seen": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"title_field": "method"
|
||||
}
|
||||
|
|
@ -9,4 +9,8 @@ import frappe
|
|||
|
||||
|
||||
class TestErrorLog(unittest.TestCase):
|
||||
pass
|
||||
def test_error_log(self):
|
||||
"""let's do an error log on error log?"""
|
||||
doc = frappe.new_doc("Error Log")
|
||||
error = doc.log_error("This is an error")
|
||||
self.assertEqual(error.doctype, "Error Log")
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ def run_background(prepared_report):
|
|||
instance.save(ignore_permissions=True)
|
||||
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
report.log_error("Prepared report failed")
|
||||
instance = frappe.get_doc("Prepared Report", prepared_report)
|
||||
instance.status = "Error"
|
||||
instance.error_message = frappe.get_traceback()
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ class SystemSettings(Document):
|
|||
|
||||
frappe.cache().delete_value("system_settings")
|
||||
frappe.cache().delete_value("time_zone")
|
||||
frappe.local.system_settings = {}
|
||||
|
||||
if frappe.flags.update_last_reset_password_date:
|
||||
update_last_reset_password_date()
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ class User(Document):
|
|||
|
||||
except frappe.OutgoingEmailError:
|
||||
# email server not set, don't send email
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
self.log_error("Unable to send new password notification")
|
||||
|
||||
@Document.hook
|
||||
def validate_reset_password(self):
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@ JOB_COLORS = {"queued": "orange", "failed": "red", "started": "blue", "finished"
|
|||
def get_info(view=None, queue_timeout=None, job_status=None) -> List[Dict]:
|
||||
jobs = []
|
||||
|
||||
def add_job(job: "Job", name: str) -> None:
|
||||
if job_status != "all" and job.get_status() != job_status:
|
||||
return
|
||||
if queue_timeout != "all" and not name.endswith(f":{queue_timeout}"):
|
||||
return
|
||||
def add_job(job: "Job", queue: str) -> None:
|
||||
|
||||
if job.kwargs.get("site") == frappe.local.site:
|
||||
job_info = {
|
||||
|
|
@ -34,7 +30,7 @@ def get_info(view=None, queue_timeout=None, job_status=None) -> List[Dict]:
|
|||
or job.kwargs.get("kwargs", {}).get("job_type")
|
||||
or str(job.kwargs.get("job_name")),
|
||||
"status": job.get_status(),
|
||||
"queue": name,
|
||||
"queue": queue,
|
||||
"creation": convert_utc_to_user_timezone(job.created_at),
|
||||
"color": JOB_COLORS[job.get_status()],
|
||||
}
|
||||
|
|
@ -48,14 +44,21 @@ def get_info(view=None, queue_timeout=None, job_status=None) -> List[Dict]:
|
|||
queues = get_queues()
|
||||
for queue in queues:
|
||||
for job in queue.jobs:
|
||||
if job_status != "all" and job.get_status() != job_status:
|
||||
return
|
||||
if queue_timeout != "all" and not queue.name.endswith(f":{queue_timeout}"):
|
||||
return
|
||||
add_job(job, queue.name)
|
||||
|
||||
elif view == "Workers":
|
||||
workers = get_workers()
|
||||
for worker in workers:
|
||||
current_job = worker.get_current_job()
|
||||
if current_job and current_job.kwargs.get("site") == frappe.local.site:
|
||||
add_job(current_job, job.origin)
|
||||
if current_job:
|
||||
if hasattr(current_job, "kwargs") and current_job.kwargs.get("site") == frappe.local.site:
|
||||
add_job(current_job, current_job.origin)
|
||||
else:
|
||||
jobs.append({"queue": worker.name, "job_name": "busy", "status": "", "creation": ""})
|
||||
else:
|
||||
jobs.append({"queue": worker.name, "job_name": "idle", "status": "", "creation": ""})
|
||||
|
||||
|
|
|
|||
|
|
@ -596,6 +596,7 @@ docfield_properties = {
|
|||
"in_preview": "Check",
|
||||
"bold": "Check",
|
||||
"no_copy": "Check",
|
||||
"ignore_xss_filter": "Check",
|
||||
"hidden": "Check",
|
||||
"collapsible": "Check",
|
||||
"collapsible_depends_on": "Data",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
"report_hide",
|
||||
"remember_last_selected_value",
|
||||
"hide_border",
|
||||
"ignore_xss_filter",
|
||||
"property_depends_on_section",
|
||||
"mandatory_depends_on",
|
||||
"column_break_33",
|
||||
|
|
@ -453,13 +454,20 @@
|
|||
"hidden": 1,
|
||||
"label": "Is System Generated",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Don't encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-31 12:05:11.799654",
|
||||
"modified": "2022-04-13 22:31:14.162661",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -1226,7 +1226,7 @@ class Database(object):
|
|||
frappe.flags.touched_tables = set()
|
||||
frappe.flags.touched_tables.update(tables)
|
||||
|
||||
def bulk_insert(self, doctype, fields, values, ignore_duplicates=False):
|
||||
def bulk_insert(self, doctype, fields, values, ignore_duplicates=False, *, chunk_size=10_000):
|
||||
"""
|
||||
Insert multiple records at a time
|
||||
|
||||
|
|
@ -1234,22 +1234,20 @@ class Database(object):
|
|||
:param fields: list of fields
|
||||
:params values: list of list of values
|
||||
"""
|
||||
insert_list = []
|
||||
fields = ", ".join("`" + field + "`" for field in fields)
|
||||
values = list(values)
|
||||
table = frappe.qb.DocType(doctype)
|
||||
|
||||
for idx, value in enumerate(values):
|
||||
insert_list.append(tuple(value))
|
||||
if idx and (idx % 10000 == 0 or idx < len(values) - 1):
|
||||
self.sql(
|
||||
"""INSERT {ignore_duplicates} INTO `tab{doctype}` ({fields}) VALUES {values}""".format(
|
||||
ignore_duplicates="IGNORE" if ignore_duplicates else "",
|
||||
doctype=doctype,
|
||||
fields=fields,
|
||||
values=", ".join(["%s"] * len(insert_list)),
|
||||
),
|
||||
tuple(insert_list),
|
||||
)
|
||||
insert_list = []
|
||||
for start_index in range(0, len(values), chunk_size):
|
||||
query = frappe.qb.into(table)
|
||||
if ignore_duplicates:
|
||||
# Pypika does not have same api for ignoring duplicates
|
||||
if frappe.conf.db_type == "mariadb":
|
||||
query = query.ignore()
|
||||
elif frappe.conf.db_type == "postgres":
|
||||
query = query.on_conflict().do_nothing()
|
||||
|
||||
values_to_insert = values[start_index : start_index + chunk_size]
|
||||
query.columns(fields).insert(*values_to_insert).run()
|
||||
|
||||
|
||||
def enqueue_jobs_after_commit():
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ def get_desktop_page(page):
|
|||
"onboardings": workspace.onboardings,
|
||||
}
|
||||
except DoesNotExistError:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
frappe.log_error("Workspace Missing")
|
||||
return {}
|
||||
|
||||
|
||||
|
|
@ -472,7 +472,7 @@ def save_new_widget(doc, page, blocks, new_widgets):
|
|||
""".format(
|
||||
page, json_config, e
|
||||
)
|
||||
frappe.log_error(log, _("Could not save customization"))
|
||||
doc.log_error("Could not save customization", log)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class NotificationLog(Document):
|
|||
try:
|
||||
send_notification_email(self)
|
||||
except frappe.OutgoingEmailError:
|
||||
frappe.log_error(message=frappe.get_traceback(), title=_("Failed to send notification email"))
|
||||
self.log_error(_("Failed to send notification email"))
|
||||
|
||||
|
||||
def get_permission_query_conditions(for_user):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"actions": [
|
||||
{
|
||||
"action": "app/console-log",
|
||||
"action": "/app/console-log",
|
||||
"action_type": "Route",
|
||||
"label": "Logs"
|
||||
},
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-09 16:35:32.345542",
|
||||
"modified": "2022-04-15 14:15:58.398590",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "System Console",
|
||||
|
|
@ -106,4 +106,4 @@
|
|||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -314,7 +314,7 @@ def get_prepared_report_result(report, filters, dn="", user=None):
|
|||
|
||||
latest_report_data = {"columns": columns, "result": data}
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
doc.log_error("Prepared report failed")
|
||||
frappe.delete_doc("Prepared Report", doc.name)
|
||||
frappe.db.commit()
|
||||
doc = None
|
||||
|
|
|
|||
|
|
@ -255,7 +255,9 @@ def send_daily():
|
|||
try:
|
||||
auto_email_report.send()
|
||||
except Exception as e:
|
||||
frappe.log_error(e, _("Failed to send {0} Auto Email Report").format(auto_email_report.name))
|
||||
auto_email_report.log_error(
|
||||
"Failed to send {0} Auto Email Report".format(auto_email_report.name)
|
||||
)
|
||||
|
||||
|
||||
def send_monthly():
|
||||
|
|
|
|||
|
|
@ -473,7 +473,7 @@ class EmailAccount(Document):
|
|||
frappe.db.rollback()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.log_error(title="EmailAccount.receive")
|
||||
self.log_error(title="EmailAccount.receive")
|
||||
if self.use_imap:
|
||||
self.handle_bad_emails(mail.uid, mail.raw_message, frappe.get_traceback())
|
||||
exceptions.append(frappe.get_traceback())
|
||||
|
|
@ -521,7 +521,7 @@ class EmailAccount(Document):
|
|||
# close connection to mailserver
|
||||
email_server.logout()
|
||||
except Exception:
|
||||
frappe.log_error(title=_("Error while connecting to email account {0}").format(self.name))
|
||||
self.log_error(title=_("Error while connecting to email account {0}").format(self.name))
|
||||
return []
|
||||
return mails
|
||||
|
||||
|
|
@ -667,7 +667,7 @@ class EmailAccount(Document):
|
|||
try:
|
||||
email_server = self.get_incoming_server(in_receive=True)
|
||||
except Exception:
|
||||
frappe.log_error(title=_("Error while connecting to email account {0}").format(self.name))
|
||||
self.log_error("Email Connection Error")
|
||||
|
||||
if not email_server:
|
||||
return
|
||||
|
|
@ -679,7 +679,7 @@ class EmailAccount(Document):
|
|||
message = safe_encode(message)
|
||||
email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message)
|
||||
except Exception:
|
||||
frappe.log_error(title="EmailAccount.append_email_to_sent_folder")
|
||||
self.log_error("Unable to add to Sent folder")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -198,10 +198,7 @@ class SendMailContext:
|
|||
traceback_string = "".join(traceback.format_tb(exc_tb))
|
||||
traceback_string += f"\n Queue Name: {self.queue_doc.name}"
|
||||
|
||||
if self.is_background_task:
|
||||
frappe.log_error(title="frappe.email.queue.flush", message=traceback_string)
|
||||
else:
|
||||
frappe.log_error(message=traceback_string)
|
||||
self.queue_doc.log_error("Email sending failed", traceback_string)
|
||||
|
||||
@property
|
||||
def smtp_session(self):
|
||||
|
|
@ -625,11 +622,11 @@ class QueueBuilder:
|
|||
mail_to_string = cstr(mail.as_string())
|
||||
except frappe.InvalidEmailAddressError:
|
||||
# bad Email Address - don't add to queue
|
||||
frappe.log_error(
|
||||
"Invalid Email ID Sender: {0}, Recipients: {1}, \nTraceback: {2} ".format(
|
||||
self.log_error(
|
||||
title="Invalid email address",
|
||||
message="Invalid email address Sender: {0}, Recipients: {1}, \nTraceback: {2} ".format(
|
||||
self.sender, ", ".join(self.final_recipients()), traceback.format_exc()
|
||||
),
|
||||
"Email Not Sent",
|
||||
)
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -329,19 +329,17 @@ def send_scheduled_email():
|
|||
pluck="name",
|
||||
)
|
||||
|
||||
for newsletter in scheduled_newsletter:
|
||||
for newsletter_name in scheduled_newsletter:
|
||||
try:
|
||||
frappe.get_doc("Newsletter", newsletter).queue_all()
|
||||
newsletter = frappe.get_doc("Newsletter", newsletter_name)
|
||||
newsletter.queue_all()
|
||||
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
|
||||
# wasn't able to send emails :(
|
||||
frappe.db.set_value("Newsletter", newsletter, "email_sent", 0)
|
||||
message = (
|
||||
f"Newsletter {newsletter} failed to send" "\n\n" f"Traceback: {frappe.get_traceback()}"
|
||||
)
|
||||
frappe.log_error(title="Send Newsletter", message=message)
|
||||
frappe.db.set_value("Newsletter", newsletter_name, "email_sent", 0)
|
||||
newsletter.log_error("Failed to send newsletter")
|
||||
|
||||
if not frappe.flags.in_test:
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ def get_context(context):
|
|||
self.create_system_notification(doc, context)
|
||||
|
||||
except:
|
||||
frappe.log_error(title="Failed to send notification", message=frappe.get_traceback())
|
||||
self.log_error("Failed to send Notification")
|
||||
|
||||
if self.set_property_after_alert:
|
||||
allow_update = True
|
||||
|
|
@ -168,7 +168,7 @@ def get_context(context):
|
|||
doc.save(ignore_permissions=True)
|
||||
doc.flags.in_notification_update = False
|
||||
except Exception:
|
||||
frappe.log_error(title="Document update failed", message=frappe.get_traceback())
|
||||
self.log_error("Document update failed")
|
||||
|
||||
def create_system_notification(self, doc, context):
|
||||
subject = self.subject
|
||||
|
|
@ -433,7 +433,7 @@ def evaluate_alert(doc, alert, event):
|
|||
if event == "Value Change" and not doc.is_new():
|
||||
if not frappe.db.has_column(doc.doctype, alert.value_changed):
|
||||
alert.db_set("enabled", 0)
|
||||
frappe.log_error("Notification {0} has been disabled due to missing field".format(alert.name))
|
||||
alert.log_error("Notification {0} has been disabled due to missing field".format(alert.name))
|
||||
return
|
||||
|
||||
doc_before_save = doc.get_doc_before_save()
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ def flush(from_test=False):
|
|||
is_background_task = not from_test
|
||||
func(email_queue_name=row.name, is_background_task=is_background_task)
|
||||
except Exception:
|
||||
frappe.log_error()
|
||||
frappe.get_doc("Email Queue", row.name).log_error()
|
||||
|
||||
|
||||
def get_queue():
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class EmailServer:
|
|||
|
||||
except _socket.error:
|
||||
# log performs rollback and logs error in Error Log
|
||||
frappe.log_error("receive.connect_pop")
|
||||
self.log_error("POP: Unable to connect")
|
||||
|
||||
# Invalid mail server -- due to refusing connection
|
||||
frappe.msgprint(_("Invalid Mail Server. Please rectify and try again."))
|
||||
|
|
@ -307,7 +307,7 @@ class EmailServer:
|
|||
|
||||
else:
|
||||
# log performs rollback and logs error in Error Log
|
||||
frappe.log_error("receive.get_messages", self.make_error_msg(msg_num, incoming_mail))
|
||||
self.log_error("Unable to fetch email", self.make_error_msg(msg_num, incoming_mail))
|
||||
self.errors = True
|
||||
frappe.db.rollback()
|
||||
|
||||
|
|
|
|||
|
|
@ -213,5 +213,5 @@ def has_consumer_access(consumer, update_log):
|
|||
else:
|
||||
return frappe.safe_eval(condition, frappe._dict(doc=doc))
|
||||
except Exception as e:
|
||||
frappe.log_error(title="has_consumer_access error", message=e)
|
||||
consumer.log_error("has_consumer_access error")
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ def enqueue_webhook(doc, webhook):
|
|||
if i != 2:
|
||||
continue
|
||||
else:
|
||||
raise e
|
||||
webhook.log_error("Webhook failed")
|
||||
|
||||
|
||||
def log_request(url, headers, data, res):
|
||||
|
|
|
|||
|
|
@ -159,13 +159,13 @@ class SiteMigration:
|
|||
"""Run Migrate operation on site specified. This method initializes
|
||||
and destroys connections to the site database.
|
||||
"""
|
||||
if not self.required_services_running():
|
||||
raise SystemExit(1)
|
||||
|
||||
if site:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
|
||||
if not self.required_services_running():
|
||||
raise SystemExit(1)
|
||||
|
||||
self.setUp()
|
||||
try:
|
||||
self.pre_schema_updates()
|
||||
|
|
|
|||
|
|
@ -1362,6 +1362,12 @@ class Document(BaseDocument):
|
|||
).insert(ignore_permissions=True)
|
||||
frappe.local.flags.commit = True
|
||||
|
||||
def log_error(self, title=None, message=None):
|
||||
"""Helper function to create an Error Log"""
|
||||
return frappe.log_error(
|
||||
message=message, title=title, reference_doctype=self.doctype, reference_name=self.name
|
||||
)
|
||||
|
||||
def get_signature(self):
|
||||
"""Returns signature (hash) for private URL."""
|
||||
return hashlib.sha224(get_datetime_str(self.creation).encode()).hexdigest()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Optional, Union
|
|||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.database.sequence import get_next_val, set_next_val
|
||||
from frappe.model import log_types
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.utils import cint, cstr, now_datetime
|
||||
|
|
@ -36,6 +35,8 @@ def set_new_name(doc):
|
|||
doc.name = None
|
||||
|
||||
if is_autoincremented(doc.doctype, meta):
|
||||
from frappe.database.sequence import get_next_val
|
||||
|
||||
doc.name = get_next_val(doc.doctype)
|
||||
return
|
||||
|
||||
|
|
@ -322,11 +323,14 @@ def get_default_naming_series(doctype):
|
|||
|
||||
|
||||
def validate_name(doctype: str, name: Union[int, str], case: Optional[str] = None):
|
||||
|
||||
if not name:
|
||||
frappe.throw(_("No Name Specified for {0}").format(doctype))
|
||||
|
||||
if isinstance(name, int):
|
||||
if is_autoincremented(doctype):
|
||||
from frappe.database.sequence import set_next_val
|
||||
|
||||
# this will set the sequence val to be the provided name and set it to be used
|
||||
# so that the sequence will start from the next val of the setted val(name)
|
||||
set_next_val(doctype, name, is_val_used=True)
|
||||
|
|
|
|||
|
|
@ -254,8 +254,9 @@ def bulk_workflow_approval(docnames, doctype, action):
|
|||
|
||||
frappe.db.rollback()
|
||||
frappe.log_error(
|
||||
frappe.get_traceback(),
|
||||
"Workflow {0} threw an error for {1} {2}".format(action, doctype, docname),
|
||||
title="Workflow {0} threw an error for {1} {2}".format(action, doctype, docname),
|
||||
reference_doctype="Workflow",
|
||||
reference_name=action,
|
||||
)
|
||||
finally:
|
||||
if not message_dict:
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ class ParallelTestRunner:
|
|||
|
||||
class ParallelTestResult(unittest.TextTestResult):
|
||||
def startTest(self, test):
|
||||
self.tb_locals = True
|
||||
self._started_at = time.time()
|
||||
super(unittest.TextTestResult, self).startTest(test)
|
||||
test_class = unittest.util.strclass(test.__class__)
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ frappe.patches.v12_0.set_default_incoming_email_port
|
|||
frappe.patches.v12_0.update_global_search
|
||||
frappe.patches.v12_0.setup_tags
|
||||
frappe.patches.v12_0.update_auto_repeat_status_and_not_submittable
|
||||
frappe.patches.v12_0.copy_to_parent_for_tags
|
||||
frappe.patches.v12_0.create_notification_settings_for_user
|
||||
frappe.patches.v11_0.make_all_prepared_report_attachments_private #2019-11-26
|
||||
frappe.patches.v12_0.setup_email_linking
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
|
||||
frappe.db.sql("UPDATE `tabTag Link` SET parenttype=document_type")
|
||||
frappe.db.sql("UPDATE `tabTag Link` SET parent=document_name")
|
||||
|
|
@ -595,6 +595,8 @@ export default class GridRow {
|
|||
// to get update df for the row
|
||||
let df = this.docfields.find(field => field.fieldname === col[0].fieldname);
|
||||
|
||||
this.set_dependant_property(df);
|
||||
|
||||
let colsize = col[1];
|
||||
let txt = this.doc ?
|
||||
frappe.format(this.doc[df.fieldname], df, null, this.doc) :
|
||||
|
|
@ -633,6 +635,56 @@ export default class GridRow {
|
|||
}
|
||||
}
|
||||
|
||||
set_dependant_property(df) {
|
||||
if (!df.reqd && df.mandatory_depends_on &&
|
||||
this.evaluate_depends_on_value(df.mandatory_depends_on)) {
|
||||
df.reqd = 1;
|
||||
}
|
||||
|
||||
if (!df.read_only && df.read_only_depends_on &&
|
||||
this.evaluate_depends_on_value(df.read_only_depends_on)) {
|
||||
df.read_only = 1;
|
||||
}
|
||||
}
|
||||
|
||||
evaluate_depends_on_value(expression) {
|
||||
let out = null;
|
||||
let doc = this.doc;
|
||||
|
||||
if (!doc) return;
|
||||
|
||||
let parent = this.frm ? this.frm.doc : this.doc || null;
|
||||
|
||||
if (typeof (expression) === 'boolean') {
|
||||
out = expression;
|
||||
|
||||
} else if (typeof (expression) === 'function') {
|
||||
out = expression(doc);
|
||||
|
||||
} else if (expression.substr(0, 5)=='eval:') {
|
||||
try {
|
||||
out = frappe.utils.eval(expression.substr(5), { doc, parent });
|
||||
if (parent && parent.istable && expression.includes('is_submittable')) {
|
||||
out = true;
|
||||
}
|
||||
} catch (e) {
|
||||
frappe.throw(__('Invalid "depends_on" expression'));
|
||||
}
|
||||
|
||||
} else if (expression.substr(0, 3)=='fn:' && this.frm) {
|
||||
out = this.frm.script_manager.trigger(expression.substr(3), this.doctype, this.docname);
|
||||
} else {
|
||||
var value = doc[expression];
|
||||
if ($.isArray(value)) {
|
||||
out = !!value.length;
|
||||
} else {
|
||||
out = !!value;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
show_search_row() {
|
||||
// show or remove search columns based on grid rows
|
||||
this.show_search = this.frm && this.frm.doc &&
|
||||
|
|
|
|||
|
|
@ -130,7 +130,8 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
folded = frm.layout.folded;
|
||||
}
|
||||
|
||||
if (df.reqd && !frappe.model.has_value(doc.doctype, doc.name, df.fieldname)) {
|
||||
if (is_docfield_mandatory(doc, df) &&
|
||||
!frappe.model.has_value(doc.doctype, doc.name, df.fieldname)) {
|
||||
has_errors = true;
|
||||
error_fields[error_fields.length] = __(df.label);
|
||||
// scroll to field
|
||||
|
|
@ -173,6 +174,42 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
return !has_errors;
|
||||
};
|
||||
|
||||
let is_docfield_mandatory = function(doc, df) {
|
||||
if (df.reqd) return true;
|
||||
if (!df.mandatory_depends_on || !doc) return;
|
||||
|
||||
let out = null;
|
||||
let expression = df.mandatory_depends_on;
|
||||
let parent = frappe.get_meta(df.parent);
|
||||
|
||||
if (typeof (expression) === 'boolean') {
|
||||
out = expression;
|
||||
|
||||
} else if (typeof (expression) === 'function') {
|
||||
out = expression(doc);
|
||||
|
||||
} else if (expression.substr(0, 5) == 'eval:') {
|
||||
try {
|
||||
out = frappe.utils.eval(expression.substr(5), { doc, parent });
|
||||
if (parent && parent.istable && expression.includes('is_submittable')) {
|
||||
out = true;
|
||||
}
|
||||
} catch (e) {
|
||||
frappe.throw(__('Invalid "mandatory_depends_on" expression'));
|
||||
}
|
||||
|
||||
} else {
|
||||
var value = doc[expression];
|
||||
if ($.isArray(value)) {
|
||||
out = !!value.length;
|
||||
} else {
|
||||
out = !!value;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
const scroll_to = (fieldname) => {
|
||||
frm.scroll_to_field(fieldname);
|
||||
frm.scroll_set = true;
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ frappe.views.BaseList = class BaseList {
|
|||
// df is passed
|
||||
const df = fieldname;
|
||||
fieldname = df.fieldname;
|
||||
doctype = df.parent;
|
||||
doctype = df.parent || doctype;
|
||||
}
|
||||
|
||||
if (!this.fields) this.fields = [];
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
font-family: inherit;
|
||||
}
|
||||
|
||||
/*rtl:begin:ignore*/
|
||||
.ql-editor {
|
||||
font-family: var(--font-stack);
|
||||
color: var(--text-color);
|
||||
|
|
@ -22,7 +23,15 @@
|
|||
a[href] {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ql-direction-rtl {
|
||||
direction: rtl;
|
||||
+ .table {
|
||||
direction: ltr;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*rtl:end:ignore*/
|
||||
|
||||
|
||||
.ql-toolbar.ql-snow {
|
||||
border-top-left-radius: var(--border-radius);
|
||||
|
|
@ -70,6 +79,7 @@
|
|||
min-height: 0;
|
||||
max-height: none;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
.my-account-container {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class EnergyPointRule(Document):
|
|||
self.apply_only_once,
|
||||
)
|
||||
except Exception as e:
|
||||
frappe.log_error(frappe.get_traceback(), "apply_energy_point")
|
||||
self.log_error("Energy points failed")
|
||||
|
||||
def rule_condition_satisfied(self, doc):
|
||||
if self.for_doc_event == "New":
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import frappe
|
|||
import frappe.utils.scheduler
|
||||
from frappe.model.naming import revert_series_if_last
|
||||
from frappe.modules import get_module_name, load_doctype_module
|
||||
from frappe.utils import cint
|
||||
|
||||
unittest_runner = unittest.TextTestRunner
|
||||
SLOW_TEST_THRESHOLD = 2
|
||||
|
|
@ -177,10 +178,13 @@ def run_all_tests(
|
|||
_add_test(app, path, filename, verbose, test_suite, ui_tests)
|
||||
|
||||
if junit_xml_output:
|
||||
runner = unittest_runner(verbosity=1 + (verbose and 1 or 0), failfast=failfast)
|
||||
runner = unittest_runner(verbosity=1 + cint(verbose), failfast=failfast)
|
||||
else:
|
||||
runner = unittest_runner(
|
||||
resultclass=TimeLoggingTestResult, verbosity=1 + (verbose and 1 or 0), failfast=failfast
|
||||
resultclass=TimeLoggingTestResult,
|
||||
verbosity=1 + cint(verbose),
|
||||
failfast=failfast,
|
||||
tb_locals=verbose,
|
||||
)
|
||||
|
||||
if profile:
|
||||
|
|
@ -279,10 +283,13 @@ def _run_unittest(
|
|||
test_suite.addTest(module_test_cases)
|
||||
|
||||
if junit_xml_output:
|
||||
runner = unittest_runner(verbosity=1 + (verbose and 1 or 0), failfast=failfast)
|
||||
runner = unittest_runner(verbosity=1 + cint(verbose), failfast=failfast)
|
||||
else:
|
||||
runner = unittest_runner(
|
||||
resultclass=TimeLoggingTestResult, verbosity=1 + (verbose and 1 or 0), failfast=failfast
|
||||
resultclass=TimeLoggingTestResult,
|
||||
verbosity=1 + cint(verbose),
|
||||
failfast=failfast,
|
||||
tb_locals=verbose,
|
||||
)
|
||||
|
||||
if profile:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import datetime
|
||||
import inspect
|
||||
import unittest
|
||||
from math import ceil
|
||||
from random import choice
|
||||
from unittest.mock import patch
|
||||
|
||||
|
|
@ -445,6 +446,33 @@ class TestDB(unittest.TestCase):
|
|||
|
||||
self.assertEqual(frappe.db.exists(dt, [["name", "=", dn]]), dn)
|
||||
|
||||
def test_bulk_insert(self):
|
||||
current_count = frappe.db.count("ToDo")
|
||||
test_body = f"test_bulk_insert - {random_string(10)}"
|
||||
chunk_size = 10
|
||||
|
||||
for number_of_values in (1, 2, 5, 27):
|
||||
current_transaction_writes = frappe.db.transaction_writes
|
||||
|
||||
frappe.db.bulk_insert(
|
||||
"ToDo",
|
||||
["name", "description"],
|
||||
[[f"ToDo Test Bulk Insert {i}", test_body] for i in range(number_of_values)],
|
||||
ignore_duplicates=True,
|
||||
chunk_size=chunk_size,
|
||||
)
|
||||
|
||||
# check that all records were inserted
|
||||
self.assertEqual(number_of_values, frappe.db.count("ToDo") - current_count)
|
||||
|
||||
# check if inserts were done in chunks
|
||||
expected_number_of_writes = ceil(number_of_values / chunk_size)
|
||||
self.assertEqual(
|
||||
expected_number_of_writes, frappe.db.transaction_writes - current_transaction_writes
|
||||
)
|
||||
|
||||
frappe.db.delete("ToDo", {"description": test_body})
|
||||
|
||||
|
||||
@run_only_if(db_type_is.MARIADB)
|
||||
class TestDDLCommandsMaria(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -92,7 +92,12 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options=
|
|||
pdf_options=options,
|
||||
)
|
||||
except Exception:
|
||||
frappe.log_error("Permission Error on doc {} of doctype {}".format(doc_name, doctype_name))
|
||||
frappe.log_error(
|
||||
title="Error in Multi PDF download",
|
||||
message="Permission Error on doc {} of doctype {}".format(doc_name, doctype_name),
|
||||
reference_doctype=doctype_name,
|
||||
reference_name=doc_name,
|
||||
)
|
||||
frappe.local.response.filename = "{}.pdf".format(name)
|
||||
|
||||
frappe.local.response.filecontent = read_multi_pdf(output)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ data-web-form="{{ name }}" data-web-form-doctype="{{ doc_type }}" data-login-req
|
|||
<div class="comments" style="margin-top: 3rem;">
|
||||
{% include 'templates/includes/comments/comments.html' %}
|
||||
</div>
|
||||
{%- else -%}
|
||||
<div style="height: 3rem"></div>
|
||||
{%- endif %} {# comments #}
|
||||
|
||||
{% endblock page_content %}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ def get_response(path=None, http_status_code=200):
|
|||
except frappe.PermissionError as e:
|
||||
response = NotPermittedPage(endpoint, http_status_code, exception=e).render()
|
||||
except Exception as e:
|
||||
frappe.log_error(f"{path} failed")
|
||||
response = ErrorPage(exception=e).render()
|
||||
|
||||
return response
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue