Merge branch 'develop' of github.com:frappe/frappe into bg-rename_doc

This commit is contained in:
Gavin D'souza 2022-04-20 16:04:27 +05:30
commit 892e05fdc1
144 changed files with 1496 additions and 836 deletions

32
.github/workflows/release.yml vendored Normal file
View 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

View file

@ -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
View 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"
]
}

View file

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

View file

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

View file

@ -0,0 +1,85 @@
context('Kanban Board', () => {
before(() => {
cy.login();
cy.visit('/app');
});
it('Create ToDo Kanban', () => {
cy.visit('/app/todo');
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');
cy.get('.title-text').should('contain', 'ToDo Kanban');
});
it('Create ToDo from kanban', () => {
cy.intercept({
method: 'POST',
url: 'api/method/frappe.client.save'
}).as('save-todo');
cy.click_listview_primary_button('Add ToDo');
cy.fill_field('description', 'Test Kanban ToDo', 'Text Editor');
cy.get('.modal-footer .btn-primary').last().click();
cy.wait('@save-todo');
});
it('Add and Remove fields', () => {
cy.visit('/app/todo/view/kanban/ToDo Kanban');
cy.intercept('POST', '/api/method/frappe.desk.doctype.kanban_board.kanban_board.save_settings').as('save-kanban');
cy.intercept('POST', '/api/method/frappe.desk.doctype.kanban_board.kanban_board.update_order').as('update-order');
cy.get('.page-actions .menu-btn-group > .btn').click();
cy.get('.page-actions .menu-btn-group .dropdown-menu li').contains('Kanban Settings').click();
cy.get('.add-new-fields').click();
cy.get('.checkbox-options .checkbox').contains('ID').click();
cy.get('.checkbox-options .checkbox').contains('Status').first().click();
cy.get('.checkbox-options .checkbox').contains('Priority').click();
cy.get('.modal-footer .btn-primary').last().click();
cy.get('.frappe-control .label-area').contains('Show Labels').click();
cy.click_modal_primary_button('Save');
cy.wait('@save-kanban');
cy.get('.kanban-column[data-column-value="Open"] .kanban-cards').as('open-cards');
cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('contain', 'ID:');
cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('contain', 'Status:');
cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('contain', 'Priority:');
cy.get('.page-actions .menu-btn-group > .btn').click();
cy.get('.page-actions .menu-btn-group .dropdown-menu li').contains('Kanban Settings').click();
cy.get_open_dialog().find('.frappe-control[data-fieldname="fields_html"] div[data-label="ID"] .remove-field').click();
cy.wait('@update-order');
cy.get_open_dialog().find('.frappe-control .label-area').contains('Show Labels').click();
cy.get('.modal-footer .btn-primary').last().click();
cy.wait('@save-kanban');
cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('not.contain', 'ID:');
});
it('Drag todo', () => {
cy.intercept({
method: 'POST',
url: 'api/method/frappe.desk.doctype.kanban_board.kanban_board.update_order_for_single_card'
}).as('drag-completed');
cy.get('.kanban-card-body:first').drag('[data-column-value="Closed"] .kanban-cards', {force: true});
cy.wait('@drag-completed');
});
});

View file

@ -1,5 +1,6 @@
import 'cypress-file-upload';
import '@testing-library/cypress/add-commands';
import '@4tw/cypress-drag-drop';
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
@ -240,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;
});
@ -257,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()

View file

@ -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
@ -2069,25 +2068,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):
@ -2140,9 +2150,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():

View file

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

View file

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

View file

@ -75,7 +75,7 @@ def get(doctype, name=None, filters=None, parent=None):
check_parent_permission(parent, doctype)
if filters and not name:
name = frappe.db.get_value(doctype, json.loads(filters))
name = frappe.db.get_value(doctype, frappe.parse_json(filters))
if not name:
frappe.throw(_("No document found for given filters"))

View file

@ -870,7 +870,7 @@ def run_ui_tests(
# install cypress
click.secho("Installing Cypress...", fg="yellow")
frappe.commands.popen(
"yarn add cypress@^6 cypress-file-upload@^5 @testing-library/cypress@^8 @cypress/code-coverage@^3 --no-lockfile"
"yarn add cypress@^6 cypress-file-upload@^5 @4tw/cypress-drag-drop@^2 @testing-library/cypress@^8 @cypress/code-coverage@^3 --no-lockfile"
)
# run for headless mode
@ -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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1043,7 +1043,7 @@ def attach_files_to_document(doc, event):
):
return
frappe.get_doc(
file_doc = frappe.get_doc(
doctype="File",
file_url=value,
attached_to_name=doc.name,
@ -1052,4 +1052,4 @@ def attach_files_to_document(doc, event):
folder="Home/Attachments",
).insert()
except Exception:
frappe.log_error(title=_("Error Attaching File"))
file_doc.log_error("Error Attaching File")

View file

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

View file

@ -72,6 +72,16 @@ frappe.method_that_doesnt_exist("do some magic")
disabled=1,
script="""
frappe.db.commit()
""",
),
dict(
name="test_add_index",
script_type="DocType Event",
doctype_event="Before Save",
reference_doctype="ToDo",
disabled=1,
script="""
frappe.db.add_index("Todo", ["color", "date"])
""",
),
]
@ -153,6 +163,18 @@ class TestServerScript(unittest.TestCase):
server_script.disabled = 1
server_script.save()
def test_add_index_in_doctype_event(self):
server_script = frappe.get_doc("Server Script", "test_add_index")
server_script.disabled = 0
server_script.save()
self.assertRaises(
AttributeError, frappe.get_doc(dict(doctype="ToDo", description="test me")).insert
)
server_script.disabled = 1
server_script.save()
def test_restricted_qb(self):
todo = frappe.get_doc(doctype="ToDo", description="QbScriptTestNote")
todo.insert()

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
{
"actions": [],
"allow_import": 1,
"autoname": "Prompt",
"creation": "2013-01-10 16:34:01",
"description": "Adds a custom client script to a DocType",
"doctype": "DocType",
@ -52,6 +53,7 @@
"default": "Form",
"fieldname": "view",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Apply To",
"options": "List\nForm",
"set_only_once": 1
@ -75,10 +77,11 @@
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-02-18 00:43:33.941466",
"modified": "2022-04-12 12:48:15.717985",
"modified_by": "Administrator",
"module": "Custom",
"name": "Client Script",
"naming_rule": "Set by user",
"owner": "Administrator",
"permissions": [
{

View file

@ -6,20 +6,6 @@ from frappe.model.document import Document
class ClientScript(Document):
def autoname(self):
self.name = f"{self.dt}-{self.view}"
def validate(self):
if not self.is_new():
return
exists = frappe.db.exists("Client Script", {"dt": self.dt, "view": self.view})
if exists:
frappe.throw(
_("Client Script for {0} {1} already exists").format(frappe.bold(self.dt), self.view),
frappe.DuplicateEntryError,
)
def on_update(self):
frappe.clear_cache(doctype=self.dt)

View file

@ -0,0 +1,101 @@
context("Client Script", () => {
before(() => {
cy.login();
cy.visit("/app");
});
it("should run form script in doctype form", () => {
cy.insert_doc(
"Client Script",
{
name: "Todo form script",
dt: "ToDo",
view: "Form",
enabled: 1,
script: `console.log('todo form script')`
},
true
);
cy.visit("/app/todo/new", {
onBeforeLoad(win) {
cy.spy(win.console, "log").as("consoleLog");
}
});
cy.get("@consoleLog").should("be.calledWith", "todo form script");
});
it("should run list script in doctype list view", () => {
cy.insert_doc(
"Client Script",
{
name: "Todo list script",
dt: "ToDo",
view: "List",
enabled: 1,
script: `console.log('todo list script')`
},
true
);
cy.visit("/app/todo", {
onBeforeLoad(win) {
cy.spy(win.console, "log").as("consoleLog");
}
});
cy.get("@consoleLog").should("be.calledWith", "todo list script");
});
it("should not run disabled scripts", () => {
cy.insert_doc(
"Client Script",
{
name: "Todo disabled list",
dt: "ToDo",
view: "List",
enabled: 0,
script: `console.log('todo disabled script')`
},
true
);
cy.visit("/app/todo", {
onBeforeLoad(win) {
cy.spy(win.console, "log").as("consoleLog");
}
});
cy.get("@consoleLog").should(
"not.be.calledWith",
"todo disabled script"
);
});
it("should run multiple scripts", () => {
cy.insert_doc(
"Client Script",
{
name: "Todo form script 1",
dt: "ToDo",
view: "Form",
enabled: 1,
script: `console.log('todo form script 1')`
},
true
);
cy.insert_doc(
"Client Script",
{
name: "Todo form script 2",
dt: "ToDo",
view: "Form",
enabled: 1,
script: `console.log('todo form script 2')`
},
true
);
cy.visit("/app/todo/new", {
onBeforeLoad(win) {
cy.spy(win.console, "log").as("consoleLog");
}
});
cy.get("@consoleLog").should("be.calledWith", "todo form script 1");
cy.get("@consoleLog").should("be.calledWith", "todo form script 2");
});
});

View file

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

View file

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

View file

@ -1228,7 +1228,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
@ -1236,22 +1236,19 @@ class Database(object):
:param fields: list of fields
:params values: list of list of values
"""
insert_list = []
fields = ", ".join("`" + field + "`" for field in fields)
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 = []
table = frappe.qb.DocType(doctype)
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():

View file

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

View file

@ -46,7 +46,7 @@ def submit_cancel_or_update_docs(doctype, docnames, action="submit", data=None):
message = ""
if action == "submit" and doc.docstatus.is_draft():
doc.submit()
message = _("Submiting {0}").format(doctype)
message = _("Submitting {0}").format(doctype)
elif action == "cancel" and doc.docstatus.is_submitted():
doc.cancel()
message = _("Cancelling {0}").format(doctype)

View file

@ -1,267 +1,124 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"actions": [],
"allow_rename": 1,
"autoname": "field:kanban_board_name",
"beta": 0,
"creation": "2016-10-19 12:26:04.809812",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"kanban_board_name",
"reference_doctype",
"field_name",
"column_break_4",
"private",
"show_labels",
"section_break_3",
"columns",
"filters",
"fields"
],
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "kanban_board_name",
"fieldtype": "Data",
"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": "Kanban Board Name",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
"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": "Reference Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"reqd": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "field_name",
"fieldtype": "Select",
"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": "Field Name",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"reqd": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"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,
"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
"fieldtype": "Section Break"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "columns",
"fieldtype": "Table",
"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": "Columns",
"length": 0,
"no_copy": 0,
"options": "Kanban Board Column",
"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
"options": "Kanban Board Column"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "filters",
"fieldtype": "Text",
"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,
"fieldtype": "Code",
"label": "Filters",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"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
"options": "JSON",
"read_only": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "private",
"fieldtype": "Check",
"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": "Private",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"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
"read_only": 1
},
{
"fieldname": "fields",
"fieldtype": "Code",
"label": "Fields",
"options": "JSON",
"read_only": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "show_labels",
"fieldtype": "Check",
"label": "Show Labels",
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-09-05 14:22:27.664645",
"links": [],
"modified": "2022-04-13 12:10:20.284367",
"modified_by": "Administrator",
"module": "Desk",
"name": "Kanban Board",
"name_case": "",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"read": 1,
"role": "All"
},
{
"create": 1,
"delete": 1,
"if_owner": 1,
"read": 1,
"role": "All",
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"set_user_permissions": 0,
"role": "System Manager",
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
"states": [],
"track_changes": 1
}

View file

@ -24,7 +24,7 @@ class KanbanBoard(Document):
def validate_column_name(self):
for column in self.columns:
if not column.column_name:
frappe.msgprint(frappe._("Column Name cannot be empty"), raise_exception=True)
frappe.msgprint(_("Column Name cannot be empty"), raise_exception=True)
def get_permission_query_conditions(user):
@ -92,7 +92,6 @@ def update_order(board_name, order):
updated_cards = []
for col_name, cards in order_dict.items():
order_list = []
for card in cards:
column = frappe.get_value(doctype, {"name": card}, fieldname)
if column != col_name:
@ -246,3 +245,22 @@ def set_indicator(board_name, column_name, indicator):
board.save()
return board
@frappe.whitelist()
def save_settings(board_name: str, settings: str) -> Document:
settings = json.loads(settings)
doc = frappe.get_doc("Kanban Board", board_name)
fields = settings["fields"]
if not isinstance(fields, str):
fields = json.dumps(fields)
doc.fields = fields
doc.show_labels = settings["show_labels"]
doc.save()
resp = doc.as_dict()
resp["fields"] = frappe.parse_json(resp["fields"])
return resp

View file

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

View file

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

View file

@ -155,7 +155,7 @@ class FormMeta(Meta):
frappe.db.get_all(
"Client Script",
filters={"dt": self.name, "enabled": 1},
fields=["script", "view"],
fields=["name", "script", "view"],
order_by="creation asc",
)
or ""
@ -165,10 +165,18 @@ class FormMeta(Meta):
form_script = ""
for script in client_scripts:
if script.view == "List":
list_script += script.script
list_script += f"""
// {script.name}
{script.script}
"""
if script.view == "Form":
form_script += script.script
form_script += f"""
// {script.name}
{script.script}
"""
file = scrub(self.name)
form_script += f"\n\n//# sourceURL={file}__custom_js"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -123,7 +123,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."))
@ -306,7 +306,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()

View file

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

View file

@ -1567,12 +1567,13 @@
},
"Malta": {
"code": "mt",
"currency": "MTL",
"currency": "EUR",
"currency_fraction": "Cent",
"currency_fraction_units": 100,
"currency_name": "Maltese Lira",
"smallest_currency_fraction_value": 0.01,
"currency_symbol": "\u20ac",
"number_format": "#,###.##",
"date_format": "dd/mm/yyyy",
"timezones": [
"Europe/Malta"
]

View file

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

View file

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

View file

@ -869,7 +869,7 @@ class BaseDocument(object):
autoname = self.meta.autoname or ""
_empty, _field_specifier, fieldname = autoname.partition("field:")
if fieldname and self.name and self.name != self.get("fieldname"):
if fieldname and self.name and self.name != self.get(fieldname):
self.set(fieldname, self.name)
def throw_length_exceeded_error(self, df, max_length, value):

View file

@ -528,6 +528,7 @@ class Document(BaseDocument):
d._validate_non_negative()
d._validate_length()
d._validate_code_fields()
d._sync_autoname_field()
d._extract_images_from_text_editor()
d._sanitize_content()
d._save_passwords()
@ -1378,6 +1379,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()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1580,15 +1580,22 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
if (frappe.user.has_role("System Manager")) {
items.push({
label: __("List Settings", null, "Button in list view menu"),
action: () => this.show_list_settings(),
standard: true,
});
if (this.get_view_settings) {
items.push(this.get_view_settings());
}
}
return items;
}
get_view_settings() {
return {
label: __("List Settings", null, "Button in list view menu"),
action: () => this.show_list_settings(),
standard: true,
};
}
show_list_settings() {
frappe.model.with_doctype(this.doctype, () => {
new ListSettings({

View file

@ -630,8 +630,6 @@ frappe.provide("frappe.views");
if(!card) return;
make_dom();
render_card_meta();
add_task_link();
// edit_card_title();
}
function make_dom() {
@ -640,12 +638,35 @@ frappe.provide("frappe.views");
title: frappe.utils.html2text(card.title),
disable_click: card._disable_click ? 'disable-click' : '',
creation: card.creation,
doc_content: get_doc_content(card),
image_url: cur_list.get_image_url(card),
form_link: frappe.utils.get_form_link(card.doctype, card.name)
};
self.$card = $(frappe.render_template('kanban_card', opts))
.appendTo(wrapper);
}
function get_doc_content(card) {
let fields = [];
for (let field_name of cur_list.board.fields) {
let field = (
frappe.meta.get_docfield(card.doctype, field_name, card.name)
|| frappe.model.get_std_field(field_name)
);
let label = cur_list.board.show_labels ? `<span>${__(field.label)}: </span>` : '';
let value = frappe.format(card.doc[field_name], field);
fields.push(`
<div class="text-muted text-truncate">
${label}
<span>${value}</span>
</div>
`);
}
return fields.join("");
}
function get_tags_html(card) {
return card.tags
? `<div class="kanban-tags">
@ -688,12 +709,6 @@ frappe.provide("frappe.views");
.find('.kanban-assignments').append($assignees_group);
}
function add_task_link() {
let task_link = frappe.utils.get_form_link(card.doctype, card.name);
self.$card.find('.kanban-card-redirect')
.attr('href', task_link);
}
function get_assignees_group() {
return frappe.avatar_group(card.assigned_list, 3, {
css_class: 'avatar avatar-small',
@ -744,7 +759,7 @@ frappe.provide("frappe.views");
assigned_list: card.assigned_list || assigned_list,
comment_count: card.comment_count || comment_count,
color: card.color || null,
doc: doc
doc: doc || card
};
}

View file

@ -1,23 +1,24 @@
<div class="kanban-card-wrapper {{ disable_click }}" data-name="{{encodeURIComponent(name)}}">
<a class="kanban-card-redirect" href="#">
<div class="kanban-card content">
{% if(image_url) { %}
<div class="kanban-image">
<img src="{{image_url}}" alt="{{title}}">
</div>
{% } %}
<div class="kanban-card-body">
<div class="kanban-title-area">
<div class="kanban-card content">
{% if(image_url) { %}
<div class="kanban-image">
<img src="{{image_url}}" alt="{{title}}">
</div>
{% } %}
<div class="kanban-card-body">
<div class="kanban-title-area">
<a href="{{ form_link }}">
<div class="kanban-card-title ellipsis" title="{{title}}">
{{ title }}
</div>
<div class="kanban-card-creation">
{{ creation }}
</div>
</div>
<div class="kanban-card-meta">
</a>
<br>
<div class="kanban-card-doc text-muted">
{{ doc_content }}
</div>
</div>
<div class="kanban-card-meta">
</div>
</div>
</a>
</div>
</div>
</div>

View file

@ -0,0 +1,264 @@
export default class KanbanSettings {
constructor({ kanbanview, doctype, meta, settings }) {
if (!doctype) {
frappe.throw(__("DocType required"));
}
this.kanbanview = kanbanview;
this.doctype = doctype;
this.meta = meta;
this.settings = settings;
this.dialog = null;
this.fields = this.settings && this.settings.fields;
frappe.model.with_doctype("List View Settings", () => {
this.make();
this.get_fields();
this.setup_fields();
this.setup_remove_fields();
this.add_new_fields();
this.show_dialog();
});
}
make() {
this.dialog = new frappe.ui.Dialog({
title: __("{0} Settings", [__(this.doctype)]),
fields: [
{
fieldname: "show_labels",
label: __("Show Labels"),
fieldtype: "Check",
},
{
fieldname: "fields_html",
fieldtype: "HTML"
},
{
fieldname: "fields",
fieldtype: "Code",
hidden: 1
}
]
});
this.dialog.set_values(this.settings);
this.dialog.set_primary_action(__("Save"), () => {
frappe.show_alert({
message: __("Saving"),
indicator: "green"
});
frappe.call({
method:
"frappe.desk.doctype.kanban_board.kanban_board.save_settings",
args: {
board_name: this.settings.name,
settings: this.dialog.get_values()
},
callback: r => {
this.kanbanview.board = r.message;
this.kanbanview.render();
this.dialog.hide();
}
});
});
}
refresh() {
this.setup_fields();
this.add_new_fields();
this.setup_remove_fields();
}
show_dialog() {
if (!this.settings.fields) {
this.update_fields();
}
this.dialog.show();
}
setup_fields() {
const fields_html = this.dialog.get_field("fields_html");
const wrapper = fields_html.$wrapper[0];
let fields = "";
for (let fieldname of this.fields) {
let field = this.get_docfield(fieldname);
fields += `
<div class="control-input flex align-center form-control fields_order sortable"
style="display: block; margin-bottom: 5px;"
data-fieldname="${field.fieldname}"
data-label="${field.label}"
data-type="${field.type}">
<div class="row">
<div class="col-md-1">
${frappe.utils.icon("drag", "xs", "", "", "sortable-handle")}
</div>
<div class="col-md-10" style="padding-left:0px;">
${__(field.label)}
</div>
<div class="col-md-1">
<a class="text-muted remove-field" data-fieldname="${field.fieldname}">
${frappe.utils.icon("delete", "xs")}
</a>
</div>
</div>
</div>`;
}
fields_html.html(`
<div class="form-group">
<div class="clearfix">
<label class="control-label" style="padding-right: 0px;">Fields</label>
</div>
<div class="control-input-wrapper">
${fields}
</div>
<p class="help-box small text-muted">
<a class="add-new-fields text-muted">
+ Add / Remove Fields
</a>
</p>
</div>
`);
new Sortable(
wrapper.getElementsByClassName("control-input-wrapper")[0],
{
handle: ".sortable-handle",
draggable: ".sortable",
onUpdate: params => {
this.fields.splice(
params.newIndex,
0,
this.fields.splice(params.oldIndex, 1)[0]
);
this.dialog.set_value(
"fields",
JSON.stringify(this.fields)
);
this.refresh();
}
}
);
}
add_new_fields() {
let add_new_fields = this.get_dialog_fields_wrapper().getElementsByClassName(
"add-new-fields"
)[0];
add_new_fields.onclick = () => this.show_column_selector();
}
setup_remove_fields() {
let remove_fields = this.get_dialog_fields_wrapper().getElementsByClassName(
"remove-field"
);
for (let idx = 0; idx < remove_fields.length; idx++) {
remove_fields.item(idx).onclick = () =>
this.remove_fields(
remove_fields.item(idx).getAttribute("data-fieldname")
);
}
}
get_dialog_fields_wrapper() {
return this.dialog.get_field("fields_html").$wrapper[0];
}
remove_fields(fieldname) {
this.fields = this.fields.filter(field => field !== fieldname);
this.dialog.set_value("fields", JSON.stringify(this.fields));
this.refresh();
}
update_fields() {
const wrapper = this.dialog.get_field("fields_html").$wrapper[0];
let fields_order = wrapper.getElementsByClassName("fields_order");
this.fields = [];
for (let idx = 0; idx < fields_order.length; idx++) {
this.fields.push(
fields_order.item(idx).getAttribute("data-fieldname")
);
}
this.dialog.set_value("fields", JSON.stringify(this.fields));
}
show_column_selector() {
let dialog = new frappe.ui.Dialog({
title: __("{0} Fields", [__(this.doctype)]),
fields: [
{
label: __("Select Fields"),
fieldtype: "MultiCheck",
fieldname: "fields",
options: this.get_multiselect_fields(),
columns: 2
}
]
});
dialog.set_primary_action(__("Save"), () => {
this.fields = dialog.get_values().fields || [];
this.dialog.set_value("fields", JSON.stringify(this.fields));
this.refresh();
dialog.hide();
});
dialog.show();
}
get_fields() {
this.fields = this.settings.fields;
this.fields.uniqBy(f => f.fieldname);
}
get_docfield(field_name) {
return (
frappe.meta.get_docfield(this.doctype, field_name) ||
frappe.model.get_std_field(field_name)
);
}
get_multiselect_fields() {
const ignore_fields = [
"idx",
"lft",
"rgt",
"old_parent",
"_user_tags",
"_liked_by",
"_comments",
"_assign",
this.meta.title_field || "name"
];
const ignore_fieldtypes = [
"Attach Image",
"Text Editor",
"HTML Editor",
"Code",
"Color",
...frappe.model.no_value_type
];
return frappe.model.std_fields
.concat(this.kanbanview.get_fields_in_list_view())
.filter(
field =>
!ignore_fields.includes(field.fieldname) &&
!ignore_fieldtypes.includes(field.fieldtype)
)
.map(field => {
return {
label: __(field.label),
value: field.fieldname,
checked: this.fields.includes(field.fieldname)
};
});
}
}

View file

@ -1,3 +1,5 @@
import KanbanSettings from "./kanban_settings";
frappe.provide('frappe.views');
frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
@ -57,6 +59,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
.then(board => {
this.board = board;
this.board.filters_array = JSON.parse(this.board.filters || '[]');
this.board.fields = JSON.parse(this.board.fields || '[]');
this.filters = this.board.filters_array;
});
}
@ -187,6 +190,25 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
};
}
get_view_settings() {
return {
label: __("Kanban Settings", null, "Button in kanban view menu"),
action: () => this.show_kanban_settings(),
standard: true,
};
}
show_kanban_settings() {
frappe.model.with_doctype(this.doctype, () => {
new KanbanSettings({
kanbanview: this,
doctype: this.doctype,
settings: this.board,
meta: frappe.get_meta(this.doctype)
});
});
}
get required_libs() {
return [
'assets/frappe/js/lib/fluxify.min.js',

View file

@ -1,3 +1,6 @@
@import "../element/checkbox";
@import "../element/radio";
html, body {
height: 100%;
}
@ -20,100 +23,6 @@ html, body {
}
}
$check-icon: url("data:image/svg+xml, <svg viewBox='0 0 8 7' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M1 4.00001L2.66667 5.80001L7 1.20001' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
input[type="radio"] {
position: relative;
width: var(--checkbox-size) !important;
height: var(--checkbox-size);
margin-right: 8px !important;
font-size: calc(var(--checkbox-size) - 1px);
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
// Reset browser behavior
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
-webkit-print-color-adjust: exact;
color-adjust: exact;
&:focus {
outline: none;
}
&:before {
width: var(--checkbox-size);
height: var(--checkbox-size);
position: absolute;
content: ' ';
border: 1px solid var(--gray-400);
border-radius: 16px;
}
&:checked::before {
background-color: var(--primary);
border-radius: 16px;
box-shadow: inset 0 0 0 2px white;
}
}
input[type="checkbox"] {
position: relative;
width: var(--checkbox-size) !important;
height: var(--checkbox-size);
margin-right: var(--checkbox-right-margin) !important;
background-repeat: no-repeat;
background-position: center;
border: 1px solid var(--gray-400);
box-sizing: border-box;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 4px;
// Reset browser behavior
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
-webkit-print-color-adjust: exact;
color-adjust: exact;
.grid-static-col & {
margin-right: 0 !important;
}
&:checked {
background-color: var(--primary);
background-image: $check-icon, var(--checkbox-gradient);
background-size: 57%, 100%;
box-shadow: none;
border: none;
}
&:focus {
outline: none; // Prevent browser behavior
box-shadow: var(--checkbox-focus-shadow);
}
&.disabled-deselected, &:disabled {
background-color: var(--disabled-control-bg);
box-shadow: inset 0px 1px 7px rgba(0, 0, 0, 0.1);
border: 0.5px solid var(--gray-300);
pointer-events: none;
}
&.disabled-selected, &:disabled:checked {
background-color: var(--gray-500);
background-image: $check-icon;
background-size: 57%;
box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.1);
border: none;
pointer-events: none;
}
}
.frappe-card {
@include card();
}

View file

@ -2,8 +2,6 @@ html {
background-color: var(--bg-color);
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

View file

@ -139,6 +139,7 @@
}
.kanban-cards {
height: 100%;
max-height: calc(100vh - 250px);
margin: -5px;
padding: 5px;
@ -149,39 +150,120 @@
&::-webkit-scrollbar {
display: none;
}
}
.kanban-card {
@include flex(flex, space-between, null, column);
margin-top: var(--margin-sm);
min-height: 100px;
@include card(
$padding: 0,
$background-color: var(--kanban-card-bg)
);
.kanban-card-body {
padding: var(--padding-sm);
.kanban-card-wrapper {
position: relative;
display: block;
&:last-child .kanban-card {
margin-bottom: var(--margin-xl);
}
.kanban-card {
@include flex(flex, space-between, null, column);
margin-top: var(--margin-sm);
min-height: 100px;
@include card(
$padding: 0,
$background-color: var(--kanban-card-bg)
);
.kanban-image {
height: 125px;
img {
border-radius: var(--border-radius) var(--border-radius) 0 0;
object-position: top;
object-fit: cover;
margin: 0 auto;
height: 100%;
width: 100%;
min-width: 100%;
color: transparent;
position: relative;
}
@include broken-img(
$height: 125px,
$top: -4px,
)
}
.kanban-card-body {
cursor: grab;
padding: var(--padding-sm);
.kanban-title-area {
margin-bottom: 12px;
max-width: 90%;
font-size: var(--text-md);
font-weight: 500;
.kanban-card-doc {
.text-muted div {
display: inline;
}
}
.kanban-card-creation {
font-size: var(--text-md);
color: var(--text-muted);
margin-top: var(--margin-xs);
}
}
.kanban-card-meta {
.list-comment-count {
width: 30px;
}
.like-action:not(.liked) {
.icon use {
stroke: var(--text-muted);
}
}
.kanban-tags {
font-size: var(--text-sm);
margin-bottom: 8px;
.tag-pill {
border-radius: 100px;
height: 22px;
width: auto;
padding: 2px 8px;
margin-bottom: var(--margin-xs);
margin-right: var(--margin-xs);
}
}
.kanban-assignments {
display: flex;
float: right;
.avatar {
cursor: default;
width: 22px;
height: 22px;
}
.avatar-action {
width: 22px;
height: 22px;
.icon {
width: 12px;
height: 12px;
}
}
}
}
}
}
}
}
}
.kanban-card-wrapper {
position: relative;
.kanban-card-redirect {
display: block;
&:hover,
&:focus {
text-decoration: none;
}
}
&:last-child .kanban-card {
margin-bottom: var(--margin-xl);
}
}
.kanban-card:hover,
.new-card-area,
.edit-card-area {
@ -189,7 +271,6 @@
}
.kanban-card-wrapper:hover {
cursor: pointer;
text-decoration: none;
.kanban-card-edit {
@ -197,43 +278,6 @@
}
}
.kanban-title-area {
margin-bottom: 12px;
.kanban-card-title {
max-width: 90%;
font-size: var(--text-md);
font-weight: 500;
}
.kanban-card-creation {
font-size: var(--text-md);
color: var(--text-muted);
margin-top: var(--margin-xs);
}
}
.kanban-image {
height: 125px;
img {
border-radius: var(--border-radius) var(--border-radius) 0 0;
object-position: top;
object-fit: cover;
margin: 0 auto;
height: 100%;
width: 100%;
min-width: 100%;
color: transparent;
position: relative;
}
@include broken-img(
$height: 125px,
$top: -4px,
)
}
.kanban-card-edit {
position: absolute;
right: 10px;
@ -291,54 +335,6 @@
}
}
.kanban-card-meta {
.list-comment-count {
width: 30px;
}
.like-action:not(.liked) {
.icon use {
stroke: var(--text-muted);
}
}
.kanban-tags {
font-size: var(--text-sm);
margin-bottom: 8px;
.tag-pill {
border-radius: 100px;
height: 22px;
width: auto;
padding: 2px 8px;
margin-bottom: var(--margin-xs);
margin-right: var(--margin-xs);
}
}
.kanban-assignments {
display: flex;
float: right;
.avatar {
cursor: default;
width: 22px;
height: 22px;
}
.avatar-action {
width: 22px;
height: 22px;
.icon {
width: 12px;
height: 12px;
}
}
}
}
.kanban-empty-state {
width: 100%;
line-height: 400px;

View file

@ -0,0 +1,55 @@
$check-icon: url("data:image/svg+xml, <svg viewBox='0 0 8 7' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M1 4.00001L2.66667 5.80001L7 1.20001' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
input[type="checkbox"] {
position: relative;
width: var(--checkbox-size) !important;
height: var(--checkbox-size);
margin-right: var(--checkbox-right-margin) !important;
background-repeat: no-repeat;
background-position: center;
border: 1px solid var(--gray-400);
box-sizing: border-box;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 4px;
// Reset browser behavior
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
-webkit-print-color-adjust: exact;
color-adjust: exact;
.grid-static-col & {
margin-right: 0 !important;
}
&:checked {
background-color: var(--primary);
background-image: $check-icon, var(--checkbox-gradient);
background-size: 57%, 100%;
box-shadow: none;
border: none;
}
&:focus {
outline: none; // Prevent browser behavior
box-shadow: var(--checkbox-focus-shadow);
}
&.disabled-deselected, &:disabled {
background-color: var(--disabled-control-bg);
box-shadow: inset 0px 1px 7px rgba(0, 0, 0, 0.1);
border: 0.5px solid var(--gray-300);
pointer-events: none;
}
&.disabled-selected, &:disabled:checked {
background-color: var(--gray-500);
background-image: $check-icon;
background-size: 57%;
box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.1);
border: none;
pointer-events: none;
}
}

View file

@ -0,0 +1,37 @@
input[type="radio"] {
position: relative;
width: var(--checkbox-size) !important;
height: var(--checkbox-size);
margin-right: 8px !important;
font-size: calc(var(--checkbox-size) - 1px);
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
// Reset browser behavior
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
-webkit-print-color-adjust: exact;
color-adjust: exact;
&:focus {
outline: none;
}
&:before {
width: var(--checkbox-size);
height: var(--checkbox-size);
position: absolute;
content: ' ';
border: 1px solid var(--gray-400);
border-radius: 16px;
}
&:checked::before {
background-color: var(--primary);
border-radius: 16px;
box-shadow: inset 0 0 0 2px white;
}
}

View file

@ -1,6 +1,7 @@
@import "frappe/public/css/bootstrap.css";
@import "./common/quill";
@import "./desk/css_variables";
@import "./element/checkbox";
// !! PDF Barcode hack !!
// Workaround for rendering barcodes prior to https://github.com/frappe/frappe/pull/15307

View file

@ -30,6 +30,7 @@
.my-account-container {
max-width: 800px;
margin: auto;
margin-bottom: 4rem;
}
.account-info {

View file

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

View file

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

View file

@ -129,3 +129,15 @@ class TestClient(unittest.TestCase):
self.assertTrue("name" in first_item)
self.assertTrue("modified" in first_item)
frappe.local.login_manager.logout()
def test_client_get(self):
from frappe.client import get
todo = frappe.get_doc(doctype="ToDo", description="test").insert()
filters = {"name": todo.name}
filters_json = frappe.as_json(filters)
self.assertEqual(get("ToDo", filters=filters).description, "test")
self.assertEqual(get("ToDo", filters=filters_json).description, "test")
todo.delete()

View file

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

View file

@ -4,11 +4,11 @@
import unittest
import frappe
from frappe.core.doctype.doctype.test_doctype import new_doctype
from frappe.model.naming import (
append_number_if_name_exists,
determine_consecutive_week_number,
getseries,
parse_naming_series,
revert_series_if_last,
)
from frappe.utils import now_datetime
@ -51,6 +51,40 @@ class TestNaming(unittest.TestCase):
self.assertEqual(country.name, original_name)
self.assertEqual(country.name, country.country_name)
def test_child_table_naming(self):
child_dt_with_naming = new_doctype(
"childtable_with_autonaming", istable=1, autoname="field:some_fieldname"
).insert()
dt_with_child_autoname = new_doctype(
"dt_with_childtable_naming",
fields=[
{
"label": "table with naming",
"fieldname": "table_with_naming",
"options": "childtable_with_autonaming",
"fieldtype": "Table",
}
],
).insert()
name = frappe.generate_hash(length=10)
doc = frappe.new_doc("dt_with_childtable_naming")
doc.append("table_with_naming", {"some_fieldname": name})
doc.save()
self.assertEqual(doc.table_with_naming[0].name, name)
# change autoname field
doc.table_with_naming[0].some_fieldname = "Something else"
doc.save()
self.assertEqual(doc.table_with_naming[0].name, name)
self.assertEqual(doc.table_with_naming[0].some_fieldname, name)
doc.delete()
dt_with_child_autoname.delete()
child_dt_with_naming.delete()
def test_format_autoname(self):
"""
Test if braced params are replaced in format autoname

View file

@ -354,8 +354,8 @@ class TestPythonExpressions(unittest.TestCase):
class TestDiffUtils(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.doc = frappe.get_doc(doctype="Client Script", dt="Client Script")
cls.doc.save(ignore_version=False)
cls.doc = frappe.get_doc(doctype="Client Script", dt="Client Script", name="test_client_script")
cls.doc.insert()
cls.doc.script = "2;"
cls.doc.save(ignore_version=False)
cls.doc.script = "42;"

View file

@ -2398,7 +2398,7 @@ Submit after importing,Dien na invoer in,
Submit an Issue,Dien &#39;n uitgawe in,
Submit this document to confirm,Dien hierdie dokument in om te bevestig,
Submit {0} documents?,Dien {0} dokumente in,
Submiting {0},Inhandiging {0},
Submitting {0},Inhandiging {0},
Submitted Document cannot be converted back to draft. Transition row {0},"Dokument wat ingedien is, kan nie weer na konsep omgeskakel word nie. Oorgangsreël {0}",
Submitting,indiening,
Subscription Notification,Inskrywing kennisgewing,

1 A4 A4
2398 Submit an Issue Dien &#39;n uitgawe in
2399 Submit this document to confirm Dien hierdie dokument in om te bevestig
2400 Submit {0} documents? Dien {0} dokumente in
2401 Submiting {0} Submitting {0} Inhandiging {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Dokument wat ingedien is, kan nie weer na konsep omgeskakel word nie. Oorgangsreël {0}
2403 Submitting indiening
2404 Subscription Notification Inskrywing kennisgewing

View file

@ -2398,7 +2398,7 @@ Submit after importing,ማስመጣት አስገባ,
Submit an Issue,አንድ ችግር አስገባ,
Submit this document to confirm,ለማረጋገጥ ይህን ሰነድ ማስገባት,
Submit {0} documents?,የ {0} ሰነዶች አስገባ?,
Submiting {0},በማስገባት ላይ {0},
Submitting {0},በማስገባት ላይ {0},
Submitted Document cannot be converted back to draft. Transition row {0},ገብቷል ሰነድ ረቂቅ ተመልሶ ሊቀየር አይችልም. የሽግግር ረድፍ {0},
Submitting,በማስገባት ላይ,
Subscription Notification,የደንበኝነት ምዝገባ ማሳወቂያ,

1 A4 A4
2398 Submit an Issue አንድ ችግር አስገባ
2399 Submit this document to confirm ለማረጋገጥ ይህን ሰነድ ማስገባት
2400 Submit {0} documents? የ {0} ሰነዶች አስገባ?
2401 Submiting {0} Submitting {0} በማስገባት ላይ {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} ገብቷል ሰነድ ረቂቅ ተመልሶ ሊቀየር አይችልም. የሽግግር ረድፍ {0}
2403 Submitting በማስገባት ላይ
2404 Subscription Notification የደንበኝነት ምዝገባ ማሳወቂያ

View file

@ -2398,7 +2398,7 @@ Submit after importing,تأكيد بعد الاستيراد,
Submit an Issue,يقدم العدد,
Submit this document to confirm,إرسال هذه الوثيقة إلى تأكيد,
Submit {0} documents?,إرسال {0} وثائق؟,
Submiting {0},تقديم {0},
Submitting {0},تقديم {0},
Submitted Document cannot be converted back to draft. Transition row {0},الوثيقة المسجلة لا يمكن تحويلها إلى مسودة row {0},
Submitting,تقديم,
Subscription Notification,إشعار الاشتراك,

1 A4 A4
2398 Submit an Issue يقدم العدد
2399 Submit this document to confirm إرسال هذه الوثيقة إلى تأكيد
2400 Submit {0} documents? إرسال {0} وثائق؟
2401 Submiting {0} Submitting {0} تقديم {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} الوثيقة المسجلة لا يمكن تحويلها إلى مسودة row {0}
2403 Submitting تقديم
2404 Subscription Notification إشعار الاشتراك

View file

@ -2398,7 +2398,7 @@ Submit after importing,Изпратете след импортирането,
Submit an Issue,Пуснете Issue,
Submit this document to confirm,"Изпратете този документ, за да потвърдите",
Submit {0} documents?,Изпратете {0} документи?,
Submiting {0},Изпращане на {0},
Submitting {0},Изпращане на {0},
Submitted Document cannot be converted back to draft. Transition row {0},Изпратено Документът не може да се превръща отново да изготви проект. Поредни Transition {0},
Submitting,Изпращане,
Subscription Notification,Нотификация за абонамент,

1 A4 A4
2398 Submit an Issue Пуснете Issue
2399 Submit this document to confirm Изпратете този документ, за да потвърдите
2400 Submit {0} documents? Изпратете {0} документи?
2401 Submiting {0} Submitting {0} Изпращане на {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Изпратено Документът не може да се превръща отново да изготви проект. Поредни Transition {0}
2403 Submitting Изпращане
2404 Subscription Notification Нотификация за абонамент

View file

@ -2398,7 +2398,7 @@ Submit after importing,আমদানি করার পরে জমা দ
Submit an Issue,একটি সমস্যার জমা,
Submit this document to confirm,নিশ্চিত করার জন্য এই দস্তাবেজ জমা দিন,
Submit {0} documents?,{0} নথি জমা দিন?,
Submiting {0},জমা দেওয়া {0},
Submitting {0},জমা দেওয়া {0},
Submitted Document cannot be converted back to draft. Transition row {0},লগইন ডকুমেন্ট খসড়া ফিরে রূপান্তরিত করা যাবে না. স্থানান্তরণ সারিতে {0},
Submitting,জমা দেওয়ার,
Subscription Notification,সাবস্ক্রিপশন বিজ্ঞপ্তি,

1 A4 A4
2398 Submit an Issue একটি সমস্যার জমা
2399 Submit this document to confirm নিশ্চিত করার জন্য এই দস্তাবেজ জমা দিন
2400 Submit {0} documents? {0} নথি জমা দিন?
2401 Submiting {0} Submitting {0} জমা দেওয়া {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} লগইন ডকুমেন্ট খসড়া ফিরে রূপান্তরিত করা যাবে না. স্থানান্তরণ সারিতে {0}
2403 Submitting জমা দেওয়ার
2404 Subscription Notification সাবস্ক্রিপশন বিজ্ঞপ্তি

View file

@ -2398,7 +2398,7 @@ Submit after importing,Pošaljite nakon uvoza,
Submit an Issue,Slanje problem,
Submit this document to confirm,Dostavi taj dokument da potvrdi,
Submit {0} documents?,Pošalji {0} dokumente?,
Submiting {0},Prijavljivanje {0},
Submitting {0},Prijavljivanje {0},
Submitted Document cannot be converted back to draft. Transition row {0},Postavio Dokument se ne može pretvoriti natrag u nacrtu .,
Submitting,Podnošenje,
Subscription Notification,Obaveštenje o pretplati,

1 A4 A4
2398 Submit an Issue Slanje problem
2399 Submit this document to confirm Dostavi taj dokument da potvrdi
2400 Submit {0} documents? Pošalji {0} dokumente?
2401 Submiting {0} Submitting {0} Prijavljivanje {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Postavio Dokument se ne može pretvoriti natrag u nacrtu .
2403 Submitting Podnošenje
2404 Subscription Notification Obaveštenje o pretplati

View file

@ -2398,7 +2398,7 @@ Submit after importing,Enviar després d&#39;importar,
Submit an Issue,Presentar un problema,
Submit this document to confirm,Presentar aquest document per confirmar,
Submit {0} documents?,Enviar documents {0}?,
Submiting {0},Enviant {0},
Submitting {0},Enviant {0},
Submitted Document cannot be converted back to draft. Transition row {0},Document presentat no es pot convertir de nou a redactar. Fila Transició {0},
Submitting,Presentar,
Subscription Notification,Notificació de subscripció,

1 A4 A4
2398 Submit an Issue Presentar un problema
2399 Submit this document to confirm Presentar aquest document per confirmar
2400 Submit {0} documents? Enviar documents {0}?
2401 Submiting {0} Submitting {0} Enviant {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Document presentat no es pot convertir de nou a redactar. Fila Transició {0}
2403 Submitting Presentar
2404 Subscription Notification Notificació de subscripció

View file

@ -2398,7 +2398,7 @@ Submit after importing,Odeslat po importu,
Submit an Issue,Vložit případ podpory,
Submit this document to confirm,Předloží tento dokument potvrdit,
Submit {0} documents?,Odeslat {0} dokumenty?,
Submiting {0},Odeslání {0},
Submitting {0},Odeslání {0},
Submitted Document cannot be converted back to draft. Transition row {0},Vložený dokument nemůže být konvertován na stav rozpracováno. řádek transkace {0},
Submitting,Odeslání,
Subscription Notification,Oznámení předplatného,

1 A4 A4
2398 Submit an Issue Vložit případ podpory
2399 Submit this document to confirm Předloží tento dokument potvrdit
2400 Submit {0} documents? Odeslat {0} dokumenty?
2401 Submiting {0} Submitting {0} Odeslání {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Vložený dokument nemůže být konvertován na stav rozpracováno. řádek transkace {0}
2403 Submitting Odeslání
2404 Subscription Notification Oznámení předplatného

View file

@ -2398,7 +2398,7 @@ Submit after importing,Indsend efter import,
Submit an Issue,Indberet et problem,
Submit this document to confirm,Godkend dette dokument for at bekræfte,
Submit {0} documents?,Indsend {0} dokumenter?,
Submiting {0},Indsender {0},
Submitting {0},Indsender {0},
Submitted Document cannot be converted back to draft. Transition row {0},Indsendt Dokument kan ikke konverteres tilbage til at udarbejde. Overgang rækken {0},
Submitting,Godkender,
Subscription Notification,Abonnementsmeddelelse,

1 A4 A4
2398 Submit an Issue Indberet et problem
2399 Submit this document to confirm Godkend dette dokument for at bekræfte
2400 Submit {0} documents? Indsend {0} dokumenter?
2401 Submiting {0} Submitting {0} Indsender {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Indsendt Dokument kan ikke konverteres tilbage til at udarbejde. Overgang rækken {0}
2403 Submitting Godkender
2404 Subscription Notification Abonnementsmeddelelse

View file

@ -2410,7 +2410,7 @@ Submit after importing,Nach dem Importieren buchen,
Submit an Issue,Eine Anfrage übertragen.,
Submit this document to confirm,"Buchen Sie dieses Dokument, um zu bestätigen",
Submit {0} documents?,{0} Dokumente einreichen?,
Submiting {0},Übermitteln von {0},
Submitting {0},Übermitteln von {0},
Submitted Document cannot be converted back to draft. Transition row {0},Buchung kann nicht in Entwurf umgewandelt werden. Zeile {0},
Submitting,Buche,
Subscription Notification,Abonnementbenachrichtigung,

1 A4 A4
2410 Submit an Issue Eine Anfrage übertragen.
2411 Submit this document to confirm Buchen Sie dieses Dokument, um zu bestätigen
2412 Submit {0} documents? {0} Dokumente einreichen?
2413 Submiting {0} Submitting {0} Übermitteln von {0}
2414 Submitted Document cannot be converted back to draft. Transition row {0} Buchung kann nicht in Entwurf umgewandelt werden. Zeile {0}
2415 Submitting Buche
2416 Subscription Notification Abonnementbenachrichtigung

View file

@ -2398,7 +2398,7 @@ Submit after importing,Υποβολή μετά την εισαγωγή,
Submit an Issue,Υποβολή Θέματος,
Submit this document to confirm,Υποβολή αυτό το έγγραφο για να επιβεβαιώσετε,
Submit {0} documents?,Υποβάλετε {0} έγγραφα;,
Submiting {0},Υποβολή {0},
Submitting {0},Υποβολή {0},
Submitted Document cannot be converted back to draft. Transition row {0},Το υποβλημένο έγγραφο δεν μπορεί να μετατραπεί ξανά σε προσχέδιο. Γραμμή μετάβασης {0},
Submitting,Υποβολή,
Subscription Notification,Ειδοποίηση συνδρομής,

1 A4 A4
2398 Submit an Issue Υποβολή Θέματος
2399 Submit this document to confirm Υποβολή αυτό το έγγραφο για να επιβεβαιώσετε
2400 Submit {0} documents? Υποβάλετε {0} έγγραφα;
2401 Submiting {0} Submitting {0} Υποβολή {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Το υποβλημένο έγγραφο δεν μπορεί να μετατραπεί ξανά σε προσχέδιο. Γραμμή μετάβασης {0}
2403 Submitting Υποβολή
2404 Subscription Notification Ειδοποίηση συνδρομής

View file

@ -2398,7 +2398,7 @@ Submit after importing,Enviar Después de Importar,
Submit an Issue,Validar una incidencia,
Submit this document to confirm,Presentar este documento para confirmar,
Submit {0} documents?,¿Presentar {0} documentos?,
Submiting {0},Envío de {0},
Submitting {0},Envío de {0},
Submitted Document cannot be converted back to draft. Transition row {0},"El documento validado no se puede revertir a borrador, en la línea de transición {0}.",
Submitting,Validando,
Subscription Notification,Notificación de Suscripción,

1 A4 A4
2398 Submit an Issue Validar una incidencia
2399 Submit this document to confirm Presentar este documento para confirmar
2400 Submit {0} documents? ¿Presentar {0} documentos?
2401 Submiting {0} Submitting {0} Envío de {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} El documento validado no se puede revertir a borrador, en la línea de transición {0}.
2403 Submitting Validando
2404 Subscription Notification Notificación de Suscripción

View file

@ -2398,7 +2398,7 @@ Submit after importing,Esita pärast importimist,
Submit an Issue,Esita probleemist,
Submit this document to confirm,Esita selle dokumendi kinnitamiseks,
Submit {0} documents?,Esitada {0} dokumente?,
Submiting {0},{0} esitamine,
Submitting {0},{0} esitamine,
Submitted Document cannot be converted back to draft. Transition row {0},Esitatud dokument ei saa muuta tagasi koostada. Üleminek rida {0},
Submitting,Esitades,
Subscription Notification,Tellimuse teatis,

1 A4 A4
2398 Submit an Issue Esita probleemist
2399 Submit this document to confirm Esita selle dokumendi kinnitamiseks
2400 Submit {0} documents? Esitada {0} dokumente?
2401 Submiting {0} Submitting {0} {0} esitamine
2402 Submitted Document cannot be converted back to draft. Transition row {0} Esitatud dokument ei saa muuta tagasi koostada. Üleminek rida {0}
2403 Submitting Esitades
2404 Subscription Notification Tellimuse teatis

View file

@ -2398,7 +2398,7 @@ Submit after importing,ارسال پس از وارد کردن,
Submit an Issue,ثبت کردن شماره,
Submit this document to confirm,ارسال این سند را به اعلام,
Submit {0} documents?,ارسال اسناد {0}؟,
Submiting {0},ارسال {0},
Submitting {0},ارسال {0},
Submitted Document cannot be converted back to draft. Transition row {0},سند فرستاده ممکن نیست تبدیل به پیش نویس است. ردیف گذار {0},
Submitting,ارسال,
Subscription Notification,اعلان اشتراک,

1 A4 A4
2398 Submit an Issue ثبت کردن شماره
2399 Submit this document to confirm ارسال این سند را به اعلام
2400 Submit {0} documents? ارسال اسناد {0}؟
2401 Submiting {0} Submitting {0} ارسال {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} سند فرستاده ممکن نیست تبدیل به پیش نویس است. ردیف گذار {0}
2403 Submitting ارسال
2404 Subscription Notification اعلان اشتراک

View file

@ -2398,7 +2398,7 @@ Submit after importing,Lähetä tuonnin jälkeen,
Submit an Issue,Vahvista aihe,
Submit this document to confirm,Vahvista hyväksyväsi tämän dokumentin,
Submit {0} documents?,Lähetä {0} asiakirjoja?,
Submiting {0},Lähettäminen {0},
Submitting {0},Lähettäminen {0},
Submitted Document cannot be converted back to draft. Transition row {0},Vahvistettua dokumenttia ei voi muuttaa takaisin luonnokseksi. Tapahtuman rivi {0},
Submitting,Vahvistetaan,
Subscription Notification,Tilausilmoitus,

1 A4 A4
2398 Submit an Issue Vahvista aihe
2399 Submit this document to confirm Vahvista hyväksyväsi tämän dokumentin
2400 Submit {0} documents? Lähetä {0} asiakirjoja?
2401 Submiting {0} Submitting {0} Lähettäminen {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Vahvistettua dokumenttia ei voi muuttaa takaisin luonnokseksi. Tapahtuman rivi {0}
2403 Submitting Vahvistetaan
2404 Subscription Notification Tilausilmoitus

View file

@ -418,8 +418,8 @@ Also adding the dependent currency field {0},Ajout également du champ de devise
"Always add ""Draft"" Heading for printing draft documents","Toujours ajouter ""Brouillon"" pour l'impression des documents brouillons",
Always use Account's Email Address as Sender,Toujours utiliser l'adresse Email du compte comme Expéditeur,
Always use Account's Name as Sender's Name,Toujours utiliser le nom du compte comme nom de l&#39;expéditeur,
Amend,Modifier,
Amending,Modification,
Amend,Nouv. version
Amending,Nouv. version en cours,
Amount Based On Field,Montant Basé sur le Champ,
Amount Field,Champ du Montant,
Amount must be greater than 0.,Le montant doit être supérieur à 0.,
@ -1100,7 +1100,7 @@ File not attached,Fichier joint manquant,
File size exceeded the maximum allowed size of {0} MB,La taille du fichier a dépassé la taille maximale autorisée de {0} Mo,
File too big,Fichier trop grand,
File {0} does not exist,Fichier {0} n'existe pas,
Files,Des dossiers,
Files,Fichiers,
Filter,Filtre,
Filter Data,Filtrer les Données,
Filter List,Liste de filtres,
@ -1145,7 +1145,7 @@ For Document Type,Pour le type de document,
For User,Pour l\'Utilisateur,
For Value,Pour la Valeur,
"For currency {0}, the minimum transaction amount should be {1}","Pour la devise {0}, le montant minimum de la transaction doit être {1}",
For example if you cancel and amend INV004 it will become a new document INV004-1. This helps you to keep track of each amendment.,"Par exemple, si vous annulez et modifiez le document INV004, il deviendra un nouveau document INV004-1. Cela vous aide à garder une trace de chaque modification.",
For example if you cancel and amend INV004 it will become a new document INV004-1. This helps you to keep track of each amendment.,"Par exemple, si vous annulez et creer une nouvelle version le document INV004, il deviendra un nouveau document INV004-1. Cela vous aide à garder une trace de chaque modification.",
"For example: If you want to include the document ID, use {0}","Par exemple: Si vous voulez inclure l'ID du document, utilisez {0}",
"For updating, you can update only selective columns.","Pour la mise à jour, vous pouvez mettre à jour uniquement une sélection colonnes.",
For {0} at level {1} in {2} in row {3},Pour {0} au niveau {1} dans {2} à la ligne {3},
@ -1541,7 +1541,7 @@ Max Value,Valeur Max,
Max width for type Currency is 100px in row {0},Largeur max pour le type Devise est 100px dans la ligne {0},
Maximum Attachment Limit for this record reached.,Taille maximale des Pièces Jointes pour cet enregistrement est atteint.,
Maximum {0} rows allowed,Maximum {0} lignes autorisés,
"Meaning of Submit, Cancel, Amend","Signification de Valider, Annuler, Modifier",
"Meaning of Submit, Cancel, Amend","Signification de Valider, Annuler, Nouv. version",
Mention transaction completion page URL,Mentionnez la page URL de fin de transaction,
Mentions,Mentions,
Menu,Menu,
@ -1737,7 +1737,7 @@ Old Password,Ancien Mot De Passe,
Old Password Required.,Ancien Mot de Passe Requis.,
Older backups will be automatically deleted,Les anciennes sauvegardes seront automatiquement supprimées,
"On {0}, {1} wrote:","Sur {0}, {1} a écrit :",
"Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.","Une fois validé, les documents à valider ne peuvent plus être modifiés. Ils ne peuvent être annulés et amendés.",
"Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.","Une fois validé, les documents à valider ne peuvent plus être modifiés. Ils ne peuvent être annulés et modifiés (Nouv. version).",
"Once you have set this, the users will only be able access documents (eg. Blog Post) where the link exists (eg. Blogger).","Une fois que vous avez défini ceci, les utilisateurs ne pourront accèder qu'aux documents (e.g. Article de Blog) où le lien existe (e.g. Blogger) .",
One Last Step,Une Dernière Étape,
One Time Password (OTP) Registration Code from {},Code de Mot de Passe Unique (OTP) à partir de {},
@ -1837,7 +1837,7 @@ Permission Levels,Niveaux d'Autorisation,
Permission Rules,Règles d'Autorisation,
Permissions,Autorisations,
Permissions are automatically applied to Standard Reports and searches.,Les autorisations sont automatiquement appliquées aux rapports standard et aux recherches.,
"Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Set User Permissions.","Les Autorisations sont définies sur les Rôles et les Types de Documents (appelés DocTypes) en définissant des droits , tels que Lire, Écrire, Créer, Supprimer, Valider, Annuler, Modifier, Rapporter, Importer, Exporter, Imprimer, Envoyer un Email et Définir les Autorisations de l'Utilisateur .",
"Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Set User Permissions.","Les Autorisations sont définies sur les Rôles et les Types de Documents (appelés DocTypes) en définissant des droits , tels que Lire, Écrire, Créer, Supprimer, Valider, Annuler, Nouv. version, Rapporter, Importer, Exporter, Imprimer, Envoyer un Email et Définir les Autorisations de l'Utilisateur .",
Permissions at higher levels are Field Level permissions. All Fields have a Permission Level set against them and the rules defined at that permissions apply to the field. This is useful in case you want to hide or make certain field read-only for certain Roles.,Les Autorisations aux niveaux supérieurs sont des permissions de Niveau Champ. Un Niveau d'Autorisation est défini pour chaque Champ et les règles définies pour ces Autorisations sappliquent au Champ. Ceci est utile si vous voulez cacher ou mettre certains champs en lecture seule pour certains Rôles.,
"Permissions at level 0 are Document Level permissions, i.e. they are primary for access to the document.","Les Autorisations au niveau 0 sont les autorisations de Niveau Document, cest à dire qu'elles sont nécessaires pour accéder au document.",
Permissions get applied on Users based on what Roles they are assigned.,Autorisations sont appliqués aux utilisateurs en fonction des Rôles qui leurs sont affectés.,
@ -2399,7 +2399,7 @@ Submit after importing,Valider après l'import,
Submit an Issue,Valider un ticket,
Submit this document to confirm,Valider ce document pour confirmer,
Submit {0} documents?,Valider {0} documents ?,
Submiting {0},Validation de {0},
Submitting {0},Validation de {0},
Submitted Document cannot be converted back to draft. Transition row {0},Document Valider ne peut pas être reconvertis en Brouillon. Ligne de transition {0},
Submitting,Validation,
Subscription Notification,Notification d&#39;abonnement,
@ -2745,7 +2745,7 @@ Website Theme Image,Image du Thème du Site Web,
Website Theme Image Link,Lien de l'Image du Thème du Site Web,
Website User,Utilisateur du Site Web,
Welcome Message,Message de Bienvenue,
"When you Amend a document after Cancel and save it, it will get a new number that is a version of the old number.","Lorsque vous Modifiez un document après l'avoir Annulé et que vous l'enregistrez, il va obtenir un nouveau numéro qui est une version de l'ancien numéro.",
"When you Amend a document after Cancel and save it, it will get a new number that is a version of the old number.","Lorsque vous faite une nouvelle version d&#39;un document après l&#39;avoir Annulé et que vous l&#39;enregistrez, il va obtenir un nouveau numéro qui est une version de l&#39;ancien numéro.",
Width,Largeur,
Widths can be set in px or %.,Les largeurs peuvent être réglées en px ou %.,
Will be used in url (usually first name).,Sera utilisé dans l'url (généralement le prénom).,
@ -2784,7 +2784,7 @@ You are not permitted to view the newsletter.,Vous n&#39;êtes pas autorisé à
You are now following this document. You will receive daily updates via email. You can change this in User Settings.,Vous suivez maintenant ce document. Vous recevrez des mises à jour quotidiennes par courrier électronique. Vous pouvez modifier cela dans les paramètres de l&#39;utilisateur.,
You can add dynamic properties from the document by using Jinja templating.,Vous pouvez ajouter des propriétés dynamiques au document à l'aide des modèles Jinja.,
You can also copy-paste this ,Vous pouvez également copier-coller cette,
"You can change Submitted documents by cancelling them and then, amending them.","Vous pouvez modifier les documents Validés en les annulant et ensuite, en les modifiant.",
"You can change Submitted documents by cancelling them and then, amending them.","Vous pouvez modifier les documents Validés en les annulant et ensuite, en créant une nouvelle version.",
You can find things by asking 'find orange in customers',Vous pouvez trouver des choses en demandant 'trouver orange dans clients',
You can only upload upto 5000 records in one go. (may be less in some cases),Vous pouvez seulement charger jusqu'à 5000 enregistrement en une seule fois. (peut-être moins dans certains cas),
You can use Customize Form to set levels on fields.,Vous pouvez utiliser Personaliser le Formulaire pour définir les niveaux de champs.,
@ -3093,12 +3093,12 @@ zoom-out,Réduire,
{0} {1} to {2},{0} {1} à {2},
"{0}, Row {1}","{0}, Ligne {1}",
"{0}: '{1}' ({3}) will get truncated, as max characters allowed is {2}",{0} : {1} '({3}) sera tronqué car le nombre de caractères max est {2},
{0}: Cannot set Amend without Cancel,{0} : Impossible de choisir Modifier sans Annuler,
{0}: Cannot set Assign Amend if not Submittable,{0} : Impossible de définir Assigner Modifier si non Validable,
{0}: Cannot set Amend without Cancel,{0} : Impossible de choisir Nouv. version sans Annuler,
{0}: Cannot set Assign Amend if not Submittable,{0} : Impossible de définir Assigner Nouv. version si non Validable,
{0}: Cannot set Assign Submit if not Submittable,{0} : Impossible de définir Assigner Valider si non Validable,
{0}: Cannot set Cancel without Submit,{0} : Impossible de choisir Annuler sans Valider,
{0}: Cannot set Import without Create,{0} : Impossible de choisir Import sans Créer,
"{0}: Cannot set Submit, Cancel, Amend without Write","{0} : Vous ne pouvez pas choisir Valider, Annuler, Modifier sans Écrire",
"{0}: Cannot set Submit, Cancel, Amend without Write","{0} : Vous ne pouvez pas choisir Valider, Annuler, Nouv. version sans Écrire",
{0}: Cannot set import as {1} is not importable,{0} : Impossible de choisir import car {1} n'est pas importable,
{0}: No basic permissions set,{0} : Aucune autorisation de base définie,
"{0}: Only one rule allowed with the same Role, Level and {1}","{0} : Une seule règle est permise avec le même Rôle, Niveau et {1}",
@ -3132,7 +3132,7 @@ No values to show,Aucune valeur à afficher,
View Ref,Voir la référence,
Workflow Action is not created for optional states,L&#39;action de flux de travail n&#39;est pas créée pour les états facultatifs,
{0} values selected,{0} valeurs sélectionnées,
"""amended_from"" field must be present to do an amendment.",Le champ &quot;modified_from&quot; doit être présent pour effectuer un amendement.,
"""amended_from"" field must be present to do an amendment.",Le champ &quot;modified_from&quot; doit être présent pour effectuer une Nouv. version,
(Mandatory),(Obligatoire),
1 Google Calendar Event synced.,1 événement Google Agenda synchronisé.,
1 record will be exported,1 enregistrement sera exporté,
@ -4139,6 +4139,7 @@ by Role,par rôle,
Document is only editable by users with role,Le document n&#39;est modifiable que par les utilisateurs avec un rôle,
{0}: Other permission rules may also apply,{0}: d&#39;autres règles d&#39;autorisation peuvent également s&#39;appliquer,
{0} Page Views,{0} pages vues,
{0} Page Views,{0} pages vues,
Expand,Développer,
Collapse,Réduire,
"Invalid Bearer token, please provide a valid access token with prefix 'Bearer'.","Jeton de porteur non valide, veuillez fournir un jeton d&#39;accès valide avec le préfixe «porteur».",
@ -4530,7 +4531,7 @@ Delete and Generate New,Supprimer et générer un nouveau,
{0} ({1}) (1 row mandatory),{0} ({1}) (1 ligne obligatoire),
Select Fields To Insert,Sélectionnez les champs à insérer,
Select Fields To Update,Sélectionnez les champs à mettre à jour,
"This document is already amended, you cannot ammend it again","Ce document est déjà modifié, vous ne pouvez plus le modifier",
"This document is already amended, you cannot ammend it again","Ce document est déjà obsoléte (une nouvelle version existe), vous ne pouvez plus le modifier",
Add to ToDo,Ajouter à ToDo,
{0} is currently {1},{0} est actuellement {1},
{0} are currently {1},{0} sont actuellement {1},
@ -4702,16 +4703,17 @@ Negative Value,Valeur négative,
Authentication failed while receiving emails from Email Account: {0}.,L&#39;authentification a échoué lors de la réception des e-mails du compte de messagerie: {0}.,
Message from server: {0},Message du serveur: {0},
{0} edited this {1},{0} a édité {1},
{0} created this {1}, {0} a créé {1}
Report an Issue, Signaler une anomalie
About, A Propos
My Profile, Mon profil
My Settings, Mes paramètres
Toggle Full Width, Changer l&#39;affichage en pleine largeur
Toggle Theme, Basculer le thème
Theme Changed, Thème changé
Amend, Nouv. version
Document has been submitted, Document validé
Document has been cancelled, Document annulé
Document is in draft state, Document au statut brouillon
{0} created this {1},{0} a créé {1}
Report an Issue,Signaler une anomalie
User Forum,Forum utilisateur
About,A Propos
My Profile,Mon profil
My Settings,Mes paramètres
Toggle Full Width,Changer l&#39;affichage en pleine largeur
Toggle Theme,Basculer le thème
Theme Changed,Thème changé
Document has been submitted,Document validé
Document has been cancelled,Document annulé
Document is in draft state,Document au statut brouillon
Copy to Clipboard,Copier vers le presse-papiers
Don't have an account?,Vous n&#39;avez pas de compte?

Can't render this file because it has a wrong number of fields in line 421.

View file

@ -2398,7 +2398,7 @@ Submit after importing,આયાત કર્યા પછી સબમિટ
Submit an Issue,એક મુદ્દો સબમિટ,
Submit this document to confirm,તેની ખાતરી કરવા માટે આ દસ્તાવેજ સબમિટ,
Submit {0} documents?,{0} દસ્તાવેજો સબમિટ કરીએ?,
Submiting {0},સબમિટ કરવું {0},
Submitting {0},સબમિટ કરવું {0},
Submitted Document cannot be converted back to draft. Transition row {0},સબમિટ દસ્તાવેજ મુસદ્દો તૈયાર કરવા માટે પાછા રૂપાંતરિત કરી શકાતા નથી. ટ્રાન્ઝિશન પંક્તિ {0},
Submitting,સબમિટ,
Subscription Notification,સબ્સ્ક્રિપ્શન સૂચના,

1 A4 A4
2398 Submit an Issue એક મુદ્દો સબમિટ
2399 Submit this document to confirm તેની ખાતરી કરવા માટે આ દસ્તાવેજ સબમિટ
2400 Submit {0} documents? {0} દસ્તાવેજો સબમિટ કરીએ?
2401 Submiting {0} Submitting {0} સબમિટ કરવું {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} સબમિટ દસ્તાવેજ મુસદ્દો તૈયાર કરવા માટે પાછા રૂપાંતરિત કરી શકાતા નથી. ટ્રાન્ઝિશન પંક્તિ {0}
2403 Submitting સબમિટ
2404 Subscription Notification સબ્સ્ક્રિપ્શન સૂચના

View file

@ -2398,7 +2398,7 @@ Submit after importing,הגש לאחר הייבוא,
Submit an Issue,שלח גיליון,
Submit this document to confirm,שלח את המסמך הזה כדי לאשר,
Submit {0} documents?,להגיש {0} מסמכים?,
Submiting {0},מגיש {0},
Submitting {0},מגיש {0},
Submitted Document cannot be converted back to draft. Transition row {0},מסמך שהוגש לא ניתן להמיר חזרה לטיוטה. שורת מעבר {0},
Submitting,הגשה,
Subscription Notification,הודעת מנוי,

1 A4 A4
2398 Submit an Issue שלח גיליון
2399 Submit this document to confirm שלח את המסמך הזה כדי לאשר
2400 Submit {0} documents? להגיש {0} מסמכים?
2401 Submiting {0} Submitting {0} מגיש {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} מסמך שהוגש לא ניתן להמיר חזרה לטיוטה. שורת מעבר {0}
2403 Submitting הגשה
2404 Subscription Notification הודעת מנוי

View file

@ -2398,7 +2398,7 @@ Submit after importing,आयात करने के बाद सबमि
Submit an Issue,किसी समस्या सबमिट,
Submit this document to confirm,इस बात की पुष्टि करने के लिए इस दस्तावेज जमा करें,
Submit {0} documents?,{0} दस्तावेज़ सबमिट करें?,
Submiting {0},जमा करना {0},
Submitting {0},जमा करना {0},
Submitted Document cannot be converted back to draft. Transition row {0},प्रस्तुत दस्तावेज का मसौदा तैयार करने के लिए वापस नहीं बदला जा सकता .,
Submitting,भेजने से,
Subscription Notification,सदस्यता अधिसूचना,

1 A4 A4
2398 Submit an Issue किसी समस्या सबमिट
2399 Submit this document to confirm इस बात की पुष्टि करने के लिए इस दस्तावेज जमा करें
2400 Submit {0} documents? {0} दस्तावेज़ सबमिट करें?
2401 Submiting {0} Submitting {0} जमा करना {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} प्रस्तुत दस्तावेज का मसौदा तैयार करने के लिए वापस नहीं बदला जा सकता .
2403 Submitting भेजने से
2404 Subscription Notification सदस्यता अधिसूचना

View file

@ -2398,7 +2398,7 @@ Submit after importing,Slanje nakon uvoza,
Submit an Issue,Slanje problem,
Submit this document to confirm,Pošaljite ovaj dokument za potvrdu,
Submit {0} documents?,Pošaljite {0} dokumente?,
Submiting {0},Slanje {0},
Submitting {0},Slanje {0},
Submitted Document cannot be converted back to draft. Transition row {0},Postavio Dokument se ne može pretvoriti natrag u nacrtu .,
Submitting,Slanje,
Subscription Notification,Obavijest o pretplati,

1 A4 A4
2398 Submit an Issue Slanje problem
2399 Submit this document to confirm Pošaljite ovaj dokument za potvrdu
2400 Submit {0} documents? Pošaljite {0} dokumente?
2401 Submiting {0} Submitting {0} Slanje {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Postavio Dokument se ne može pretvoriti natrag u nacrtu .
2403 Submitting Slanje
2404 Subscription Notification Obavijest o pretplati

View file

@ -2398,7 +2398,7 @@ Submit after importing,Elküldés az importálás után,
Submit an Issue,Jelentsen egy problémát,
Submit this document to confirm,Küldje el ezt a dokumentumot megerősítésre,
Submit {0} documents?,Küldje el a {0} dokumentumokat?,
Submiting {0},Benyúj {0},
Submitting {0},Benyúj {0},
Submitted Document cannot be converted back to draft. Transition row {0},Benyújtott dokumentumot nem lehet visszaalakítani tervezetté. Átvezetési sor {0},
Submitting,Benyújtása,
Subscription Notification,Előfizetési értesítés,

1 A4 A4
2398 Submit an Issue Jelentsen egy problémát
2399 Submit this document to confirm Küldje el ezt a dokumentumot megerősítésre
2400 Submit {0} documents? Küldje el a {0} dokumentumokat?
2401 Submiting {0} Submitting {0} Benyúj {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Benyújtott dokumentumot nem lehet visszaalakítani tervezetté. Átvezetési sor {0}
2403 Submitting Benyújtása
2404 Subscription Notification Előfizetési értesítés

View file

@ -2398,7 +2398,7 @@ Submit after importing,Kirim setelah mengimpor,
Submit an Issue,Menyerahkan Masalah,
Submit this document to confirm,Menyerahkan dokumen ini untuk mengkonfirmasi,
Submit {0} documents?,Kirim {0} dokumen?,
Submiting {0},Mengajukan {0},
Submitting {0},Mengajukan {0},
Submitted Document cannot be converted back to draft. Transition row {0},Dikirim Dokumen tidak dapat dikonversi kembali untuk menyusun. Transisi baris {0},
Submitting,Mengirimkan,
Subscription Notification,Pemberitahuan Berlangganan,

1 A4 A4
2398 Submit an Issue Menyerahkan Masalah
2399 Submit this document to confirm Menyerahkan dokumen ini untuk mengkonfirmasi
2400 Submit {0} documents? Kirim {0} dokumen?
2401 Submiting {0} Submitting {0} Mengajukan {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Dikirim Dokumen tidak dapat dikonversi kembali untuk menyusun. Transisi baris {0}
2403 Submitting Mengirimkan
2404 Subscription Notification Pemberitahuan Berlangganan

View file

@ -2398,7 +2398,7 @@ Submit after importing,Sendu inn eftir innflutning,
Submit an Issue,Leggja mál,
Submit this document to confirm,Senda þessu skjali til að staðfesta,
Submit {0} documents?,Sendu inn {0} skjöl?,
Submiting {0},Sending {0},
Submitting {0},Sending {0},
Submitted Document cannot be converted back to draft. Transition row {0},Lögð Document ekki hægt að breyta aftur í drög. Umskipti róður {0},
Submitting,Sendi,
Subscription Notification,Tilkynning um áskrift,

1 A4 A4
2398 Submit an Issue Leggja mál
2399 Submit this document to confirm Senda þessu skjali til að staðfesta
2400 Submit {0} documents? Sendu inn {0} skjöl?
2401 Submiting {0} Submitting {0} Sending {0}
2402 Submitted Document cannot be converted back to draft. Transition row {0} Lögð Document ekki hægt að breyta aftur í drög. Umskipti róður {0}
2403 Submitting Sendi
2404 Subscription Notification Tilkynning um áskrift

Some files were not shown because too many files have changed in this diff Show more