Merge branch 'develop' of github.com:frappe/frappe into bg-rename_doc
This commit is contained in:
commit
892e05fdc1
144 changed files with 1496 additions and 836 deletions
32
.github/workflows/release.yml
vendored
Normal file
32
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
name: Generate Semantic Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- version-13
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Entire Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js v14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
npm install @semantic-release/git @semantic-release/exec --no-save
|
||||
- name: Create Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GIT_AUTHOR_NAME: "Frappe PR Bot"
|
||||
GIT_AUTHOR_EMAIL: "developers@frappe.io"
|
||||
GIT_COMMITTER_NAME: "Frappe PR Bot"
|
||||
GIT_COMMITTER_EMAIL: "developers@frappe.io"
|
||||
run: npx semantic-release
|
||||
40
.mergify.yml
40
.mergify.yml
|
|
@ -53,3 +53,43 @@ pull_request_rules:
|
|||
{{ title }} (#{{ number }})
|
||||
|
||||
{{ body }}
|
||||
|
||||
- name: backport to develop
|
||||
conditions:
|
||||
- label="backport develop"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- develop
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-13-hotfix
|
||||
conditions:
|
||||
- label="backport version-13-hotfix"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-13-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-13-pre-release
|
||||
conditions:
|
||||
- label="backport version-13-pre-release"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-13-pre-release
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-12-hotfix
|
||||
conditions:
|
||||
- label="backport version-12-hotfix"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-12-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
24
.releaserc
Normal file
24
.releaserc
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"branches": ["version-13"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"preset": "angular",
|
||||
"releaseRules": [
|
||||
{"breaking": true, "release": false}
|
||||
]
|
||||
},
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/exec", {
|
||||
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" frappe/__init__.py'
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git", {
|
||||
"assets": ["frappe/__init__.py"],
|
||||
"message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ context('Control Barcode', () => {
|
|||
it('should generate barcode on setting a value', () => {
|
||||
get_dialog_with_barcode().as('dialog');
|
||||
|
||||
cy.focused().blur();
|
||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
|
||||
.type('123456789')
|
||||
.blur();
|
||||
|
|
@ -36,6 +37,7 @@ context('Control Barcode', () => {
|
|||
it('should reset when input is cleared', () => {
|
||||
get_dialog_with_barcode().as('dialog');
|
||||
|
||||
cy.focused().blur();
|
||||
cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
|
||||
.type('123456789')
|
||||
.blur();
|
||||
|
|
|
|||
|
|
@ -1,23 +1,27 @@
|
|||
context('Date Control', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/doctype');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
|
||||
name: 'Test Date Control',
|
||||
fields: [
|
||||
{
|
||||
"label": "Date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
cy.visit('/app');
|
||||
});
|
||||
|
||||
function get_dialog(date_field_options) {
|
||||
return cy.dialog({
|
||||
title: 'Date',
|
||||
fields: [{
|
||||
"label": "Date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
...date_field_options
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
it('Selecting a date from the datepicker', () => {
|
||||
cy.new_form('Test Date Control');
|
||||
cy.clear_dialogs();
|
||||
cy.clear_datepickers();
|
||||
|
||||
get_dialog().as('dialog');
|
||||
cy.get_field('date', 'Date').click();
|
||||
cy.get('.datepicker--nav-title').click();
|
||||
cy.get('.datepicker--nav-title').click({force: true});
|
||||
|
|
@ -28,12 +32,16 @@ context('Date Control', () => {
|
|||
cy.get('.datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]').click();
|
||||
cy.get('.datepicker--days > .datepicker--cells > .datepicker--cell[data-date=15]').click();
|
||||
|
||||
//Verifying if the selected date is displayed in the date field
|
||||
cy.get_field('date', 'Date').should('have.value', '01-15-2020');
|
||||
// Verify if the selected date is set the date field
|
||||
cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-01-15');
|
||||
});
|
||||
|
||||
it('Checking next and previous button', () => {
|
||||
cy.get_field('date', 'Date').click();
|
||||
cy.clear_dialogs();
|
||||
cy.clear_datepickers();
|
||||
|
||||
get_dialog({ default: '2020-01-15' }).as('dialog');
|
||||
cy.get_field('date', 'Date').click();
|
||||
|
||||
//Clicking on the next button in the datepicker
|
||||
cy.get('.datepicker--nav-action[data-action=next]').click();
|
||||
|
|
@ -42,7 +50,7 @@ context('Date Control', () => {
|
|||
cy.get('.datepicker--cell[data-date=15]').click({force: true});
|
||||
|
||||
//Verifying if the selected date has been displayed in the date field
|
||||
cy.get_field('date', 'Date').should('have.value', '02-15-2020');
|
||||
cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-02-15');
|
||||
cy.wait(500);
|
||||
cy.get_field('date', 'Date').click();
|
||||
|
||||
|
|
@ -53,19 +61,22 @@ context('Date Control', () => {
|
|||
cy.get('.datepicker--cell[data-date=15]').click({force: true});
|
||||
|
||||
//Verifying if the selected date has been displayed in the date field
|
||||
cy.get_field('date', 'Date').should('have.value', '01-15-2020');
|
||||
cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-01-15');
|
||||
});
|
||||
|
||||
it('Clicking on "Today" button gives todays date', () => {
|
||||
cy.get_field('date', 'Date').click();
|
||||
cy.clear_dialogs();
|
||||
cy.clear_datepickers();
|
||||
|
||||
get_dialog().as('dialog');
|
||||
cy.get_field('date', 'Date').click();
|
||||
|
||||
//Clicking on "Today" button
|
||||
cy.get('.datepicker--button').click();
|
||||
|
||||
//Picking up the todays date
|
||||
const todays_date = Cypress.moment().format('MM-DD-YYYY');
|
||||
|
||||
//Verifying if clicking on "Today" button matches today's date
|
||||
cy.get_field('date', 'Date').should('have.value', todays_date);
|
||||
cy.window().then(win => {
|
||||
expect(win.cur_dialog.fields_dict.date.value).to.be.equal(win.frappe.datetime.get_today());
|
||||
});
|
||||
});
|
||||
});
|
||||
85
cypress/integration/kanban.js
Normal file
85
cypress/integration/kanban.js
Normal 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');
|
||||
});
|
||||
});
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ class AutoRepeat(Document):
|
|||
if self.notify_by_email and self.recipients:
|
||||
self.send_notification(new_doc)
|
||||
except Exception:
|
||||
error_log = frappe.log_error(frappe.get_traceback(), _("Auto Repeat Document Creation Failure"))
|
||||
error_log = self.log_error("Auto repeat failed")
|
||||
|
||||
self.disable_auto_repeat()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import json
|
|||
|
||||
import frappe
|
||||
from frappe.desk.notifications import clear_notifications, delete_notification_count_for
|
||||
from frappe.model.document import Document
|
||||
|
||||
common_default_keys = ["__default", "__global"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -450,8 +450,7 @@ def get_contacts(email_strings: List[str], auto_create_contact=False) -> List[st
|
|||
contact.insert(ignore_permissions=True)
|
||||
contact_name = contact.name
|
||||
except Exception:
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(traceback)
|
||||
contact.log_error("Unable to add contact")
|
||||
|
||||
if contact_name:
|
||||
contacts.append(contact_name)
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ def mark_email_as_seen(name: str = None):
|
|||
frappe.db.commit() # nosemgrep: this will be called in a GET request
|
||||
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
frappe.log_error("Unable to mark as seen", None, "Communication", name)
|
||||
|
||||
finally:
|
||||
frappe.response.update(
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ def start_import(data_import):
|
|||
except Exception:
|
||||
frappe.db.rollback()
|
||||
data_import.db_set("status", "Error")
|
||||
frappe.log_error(title=data_import.name)
|
||||
data_import.log_error("Data import failed")
|
||||
finally:
|
||||
frappe.flags.in_import = False
|
||||
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"description": "Don't encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
|
|
@ -547,7 +547,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-02 17:07:32.117897",
|
||||
"modified": "2022-04-19 12:27:28.641580",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -1,148 +1,76 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-16 13:09:40",
|
||||
"custom": 0,
|
||||
"description": "Log of Scheduler Errors",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 0,
|
||||
"engine": "MyISAM",
|
||||
"actions": [],
|
||||
"creation": "2013-01-16 13:09:40",
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"engine": "MyISAM",
|
||||
"field_order": [
|
||||
"seen",
|
||||
"method",
|
||||
"error",
|
||||
"reference_doctype",
|
||||
"reference_name"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "seen",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Seen",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "seen",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Seen"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "method",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Title",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "method",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "error",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Error",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "error",
|
||||
"fieldtype": "Code",
|
||||
"label": "Error",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Reference DocType",
|
||||
"options": "DocType",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference Name"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-warning-sign",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2021-10-25 12:21:44.292471",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Error Log",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-warning-sign",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-18 17:25:47.406873",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Error Log",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_seen": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"title_field": "method"
|
||||
}
|
||||
|
|
@ -9,4 +9,8 @@ import frappe
|
|||
|
||||
|
||||
class TestErrorLog(unittest.TestCase):
|
||||
pass
|
||||
def test_error_log(self):
|
||||
"""let's do an error log on error log?"""
|
||||
doc = frappe.new_doc("Error Log")
|
||||
error = doc.log_error("This is an error")
|
||||
self.assertEqual(error.doctype, "Error Log")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ class SystemSettings(Document):
|
|||
|
||||
frappe.cache().delete_value("system_settings")
|
||||
frappe.cache().delete_value("time_zone")
|
||||
frappe.local.system_settings = {}
|
||||
|
||||
if frappe.flags.update_last_reset_password_date:
|
||||
update_last_reset_password_date()
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ class User(Document):
|
|||
|
||||
except frappe.OutgoingEmailError:
|
||||
# email server not set, don't send email
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
self.log_error("Unable to send new password notification")
|
||||
|
||||
@Document.hook
|
||||
def validate_reset_password(self):
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@ JOB_COLORS = {"queued": "orange", "failed": "red", "started": "blue", "finished"
|
|||
def get_info(view=None, queue_timeout=None, job_status=None) -> List[Dict]:
|
||||
jobs = []
|
||||
|
||||
def add_job(job: "Job", name: str) -> None:
|
||||
if job_status != "all" and job.get_status() != job_status:
|
||||
return
|
||||
if queue_timeout != "all" and not name.endswith(f":{queue_timeout}"):
|
||||
return
|
||||
def add_job(job: "Job", queue: str) -> None:
|
||||
|
||||
if job.kwargs.get("site") == frappe.local.site:
|
||||
job_info = {
|
||||
|
|
@ -34,7 +30,7 @@ def get_info(view=None, queue_timeout=None, job_status=None) -> List[Dict]:
|
|||
or job.kwargs.get("kwargs", {}).get("job_type")
|
||||
or str(job.kwargs.get("job_name")),
|
||||
"status": job.get_status(),
|
||||
"queue": name,
|
||||
"queue": queue,
|
||||
"creation": convert_utc_to_user_timezone(job.created_at),
|
||||
"color": JOB_COLORS[job.get_status()],
|
||||
}
|
||||
|
|
@ -48,14 +44,21 @@ def get_info(view=None, queue_timeout=None, job_status=None) -> List[Dict]:
|
|||
queues = get_queues()
|
||||
for queue in queues:
|
||||
for job in queue.jobs:
|
||||
if job_status != "all" and job.get_status() != job_status:
|
||||
return
|
||||
if queue_timeout != "all" and not queue.name.endswith(f":{queue_timeout}"):
|
||||
return
|
||||
add_job(job, queue.name)
|
||||
|
||||
elif view == "Workers":
|
||||
workers = get_workers()
|
||||
for worker in workers:
|
||||
current_job = worker.get_current_job()
|
||||
if current_job and current_job.kwargs.get("site") == frappe.local.site:
|
||||
add_job(current_job, job.origin)
|
||||
if current_job:
|
||||
if hasattr(current_job, "kwargs") and current_job.kwargs.get("site") == frappe.local.site:
|
||||
add_job(current_job, current_job.origin)
|
||||
else:
|
||||
jobs.append({"queue": worker.name, "job_name": "busy", "status": "", "creation": ""})
|
||||
else:
|
||||
jobs.append({"queue": worker.name, "job_name": "idle", "status": "", "creation": ""})
|
||||
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
101
frappe/custom/doctype/client_script/ui_test_client_script.js
Normal file
101
frappe/custom/doctype/client_script/ui_test_client_script.js
Normal 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");
|
||||
});
|
||||
});
|
||||
|
|
@ -596,6 +596,7 @@ docfield_properties = {
|
|||
"in_preview": "Check",
|
||||
"bold": "Check",
|
||||
"no_copy": "Check",
|
||||
"ignore_xss_filter": "Check",
|
||||
"hidden": "Check",
|
||||
"collapsible": "Check",
|
||||
"collapsible_depends_on": "Data",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
"report_hide",
|
||||
"remember_last_selected_value",
|
||||
"hide_border",
|
||||
"ignore_xss_filter",
|
||||
"property_depends_on_section",
|
||||
"mandatory_depends_on",
|
||||
"column_break_33",
|
||||
|
|
@ -453,13 +454,20 @@
|
|||
"hidden": 1,
|
||||
"label": "Is System Generated",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Don't encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-31 12:05:11.799654",
|
||||
"modified": "2022-04-13 22:31:14.162661",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class NotificationLog(Document):
|
|||
try:
|
||||
send_notification_email(self)
|
||||
except frappe.OutgoingEmailError:
|
||||
frappe.log_error(message=frappe.get_traceback(), title=_("Failed to send notification email"))
|
||||
self.log_error(_("Failed to send notification email"))
|
||||
|
||||
|
||||
def get_permission_query_conditions(for_user):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"actions": [
|
||||
{
|
||||
"action": "app/console-log",
|
||||
"action": "/app/console-log",
|
||||
"action_type": "Route",
|
||||
"label": "Logs"
|
||||
},
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-09 16:35:32.345542",
|
||||
"modified": "2022-04-15 14:15:58.398590",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "System Console",
|
||||
|
|
@ -106,4 +106,4 @@
|
|||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -314,7 +314,7 @@ def get_prepared_report_result(report, filters, dn="", user=None):
|
|||
|
||||
latest_report_data = {"columns": columns, "result": data}
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
doc.log_error("Prepared report failed")
|
||||
frappe.delete_doc("Prepared Report", doc.name)
|
||||
frappe.db.commit()
|
||||
doc = None
|
||||
|
|
|
|||
|
|
@ -255,7 +255,9 @@ def send_daily():
|
|||
try:
|
||||
auto_email_report.send()
|
||||
except Exception as e:
|
||||
frappe.log_error(e, _("Failed to send {0} Auto Email Report").format(auto_email_report.name))
|
||||
auto_email_report.log_error(
|
||||
"Failed to send {0} Auto Email Report".format(auto_email_report.name)
|
||||
)
|
||||
|
||||
|
||||
def send_monthly():
|
||||
|
|
|
|||
|
|
@ -473,7 +473,7 @@ class EmailAccount(Document):
|
|||
frappe.db.rollback()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.log_error(title="EmailAccount.receive")
|
||||
self.log_error(title="EmailAccount.receive")
|
||||
if self.use_imap:
|
||||
self.handle_bad_emails(mail.uid, mail.raw_message, frappe.get_traceback())
|
||||
exceptions.append(frappe.get_traceback())
|
||||
|
|
@ -521,7 +521,7 @@ class EmailAccount(Document):
|
|||
# close connection to mailserver
|
||||
email_server.logout()
|
||||
except Exception:
|
||||
frappe.log_error(title=_("Error while connecting to email account {0}").format(self.name))
|
||||
self.log_error(title=_("Error while connecting to email account {0}").format(self.name))
|
||||
return []
|
||||
return mails
|
||||
|
||||
|
|
@ -667,7 +667,7 @@ class EmailAccount(Document):
|
|||
try:
|
||||
email_server = self.get_incoming_server(in_receive=True)
|
||||
except Exception:
|
||||
frappe.log_error(title=_("Error while connecting to email account {0}").format(self.name))
|
||||
self.log_error("Email Connection Error")
|
||||
|
||||
if not email_server:
|
||||
return
|
||||
|
|
@ -679,7 +679,7 @@ class EmailAccount(Document):
|
|||
message = safe_encode(message)
|
||||
email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message)
|
||||
except Exception:
|
||||
frappe.log_error(title="EmailAccount.append_email_to_sent_folder")
|
||||
self.log_error("Unable to add to Sent folder")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -198,10 +198,7 @@ class SendMailContext:
|
|||
traceback_string = "".join(traceback.format_tb(exc_tb))
|
||||
traceback_string += f"\n Queue Name: {self.queue_doc.name}"
|
||||
|
||||
if self.is_background_task:
|
||||
frappe.log_error(title="frappe.email.queue.flush", message=traceback_string)
|
||||
else:
|
||||
frappe.log_error(message=traceback_string)
|
||||
self.queue_doc.log_error("Email sending failed", traceback_string)
|
||||
|
||||
@property
|
||||
def smtp_session(self):
|
||||
|
|
@ -625,11 +622,11 @@ class QueueBuilder:
|
|||
mail_to_string = cstr(mail.as_string())
|
||||
except frappe.InvalidEmailAddressError:
|
||||
# bad Email Address - don't add to queue
|
||||
frappe.log_error(
|
||||
"Invalid Email ID Sender: {0}, Recipients: {1}, \nTraceback: {2} ".format(
|
||||
self.log_error(
|
||||
title="Invalid email address",
|
||||
message="Invalid email address Sender: {0}, Recipients: {1}, \nTraceback: {2} ".format(
|
||||
self.sender, ", ".join(self.final_recipients()), traceback.format_exc()
|
||||
),
|
||||
"Email Not Sent",
|
||||
)
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -329,19 +329,17 @@ def send_scheduled_email():
|
|||
pluck="name",
|
||||
)
|
||||
|
||||
for newsletter in scheduled_newsletter:
|
||||
for newsletter_name in scheduled_newsletter:
|
||||
try:
|
||||
frappe.get_doc("Newsletter", newsletter).queue_all()
|
||||
newsletter = frappe.get_doc("Newsletter", newsletter_name)
|
||||
newsletter.queue_all()
|
||||
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
|
||||
# wasn't able to send emails :(
|
||||
frappe.db.set_value("Newsletter", newsletter, "email_sent", 0)
|
||||
message = (
|
||||
f"Newsletter {newsletter} failed to send" "\n\n" f"Traceback: {frappe.get_traceback()}"
|
||||
)
|
||||
frappe.log_error(title="Send Newsletter", message=message)
|
||||
frappe.db.set_value("Newsletter", newsletter_name, "email_sent", 0)
|
||||
newsletter.log_error("Failed to send newsletter")
|
||||
|
||||
if not frappe.flags.in_test:
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ def get_context(context):
|
|||
self.create_system_notification(doc, context)
|
||||
|
||||
except:
|
||||
frappe.log_error(title="Failed to send notification", message=frappe.get_traceback())
|
||||
self.log_error("Failed to send Notification")
|
||||
|
||||
if self.set_property_after_alert:
|
||||
allow_update = True
|
||||
|
|
@ -168,7 +168,7 @@ def get_context(context):
|
|||
doc.save(ignore_permissions=True)
|
||||
doc.flags.in_notification_update = False
|
||||
except Exception:
|
||||
frappe.log_error(title="Document update failed", message=frappe.get_traceback())
|
||||
self.log_error("Document update failed")
|
||||
|
||||
def create_system_notification(self, doc, context):
|
||||
subject = self.subject
|
||||
|
|
@ -433,7 +433,7 @@ def evaluate_alert(doc, alert, event):
|
|||
if event == "Value Change" and not doc.is_new():
|
||||
if not frappe.db.has_column(doc.doctype, alert.value_changed):
|
||||
alert.db_set("enabled", 0)
|
||||
frappe.log_error("Notification {0} has been disabled due to missing field".format(alert.name))
|
||||
alert.log_error("Notification {0} has been disabled due to missing field".format(alert.name))
|
||||
return
|
||||
|
||||
doc_before_save = doc.get_doc_before_save()
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ def flush(from_test=False):
|
|||
is_background_task = not from_test
|
||||
func(email_queue_name=row.name, is_background_task=is_background_task)
|
||||
except Exception:
|
||||
frappe.log_error()
|
||||
frappe.get_doc("Email Queue", row.name).log_error()
|
||||
|
||||
|
||||
def get_queue():
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ def enqueue_webhook(doc, webhook):
|
|||
if i != 2:
|
||||
continue
|
||||
else:
|
||||
raise e
|
||||
webhook.log_error("Webhook failed")
|
||||
|
||||
|
||||
def log_request(url, headers, data, res):
|
||||
|
|
|
|||
|
|
@ -159,13 +159,13 @@ class SiteMigration:
|
|||
"""Run Migrate operation on site specified. This method initializes
|
||||
and destroys connections to the site database.
|
||||
"""
|
||||
if not self.required_services_running():
|
||||
raise SystemExit(1)
|
||||
|
||||
if site:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
|
||||
if not self.required_services_running():
|
||||
raise SystemExit(1)
|
||||
|
||||
self.setUp()
|
||||
try:
|
||||
self.pre_schema_updates()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Optional, Union
|
|||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.database.sequence import get_next_val, set_next_val
|
||||
from frappe.model import log_types
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.utils import cint, cstr, now_datetime
|
||||
|
|
@ -36,6 +35,8 @@ def set_new_name(doc):
|
|||
doc.name = None
|
||||
|
||||
if is_autoincremented(doc.doctype, meta):
|
||||
from frappe.database.sequence import get_next_val
|
||||
|
||||
doc.name = get_next_val(doc.doctype)
|
||||
return
|
||||
|
||||
|
|
@ -322,11 +323,14 @@ def get_default_naming_series(doctype):
|
|||
|
||||
|
||||
def validate_name(doctype: str, name: Union[int, str], case: Optional[str] = None):
|
||||
|
||||
if not name:
|
||||
frappe.throw(_("No Name Specified for {0}").format(doctype))
|
||||
|
||||
if isinstance(name, int):
|
||||
if is_autoincremented(doctype):
|
||||
from frappe.database.sequence import set_next_val
|
||||
|
||||
# this will set the sequence val to be the provided name and set it to be used
|
||||
# so that the sequence will start from the next val of the setted val(name)
|
||||
set_next_val(doctype, name, is_val_used=True)
|
||||
|
|
|
|||
|
|
@ -254,8 +254,9 @@ def bulk_workflow_approval(docnames, doctype, action):
|
|||
|
||||
frappe.db.rollback()
|
||||
frappe.log_error(
|
||||
frappe.get_traceback(),
|
||||
"Workflow {0} threw an error for {1} {2}".format(action, doctype, docname),
|
||||
title="Workflow {0} threw an error for {1} {2}".format(action, doctype, docname),
|
||||
reference_doctype="Workflow",
|
||||
reference_name=action,
|
||||
)
|
||||
finally:
|
||||
if not message_dict:
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ class ParallelTestRunner:
|
|||
|
||||
class ParallelTestResult(unittest.TextTestResult):
|
||||
def startTest(self, test):
|
||||
self.tb_locals = True
|
||||
self._started_at = time.time()
|
||||
super(unittest.TextTestResult, self).startTest(test)
|
||||
test_class = unittest.util.strclass(test.__class__)
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ frappe.patches.v12_0.set_default_incoming_email_port
|
|||
frappe.patches.v12_0.update_global_search
|
||||
frappe.patches.v12_0.setup_tags
|
||||
frappe.patches.v12_0.update_auto_repeat_status_and_not_submittable
|
||||
frappe.patches.v12_0.copy_to_parent_for_tags
|
||||
frappe.patches.v12_0.create_notification_settings_for_user
|
||||
frappe.patches.v11_0.make_all_prepared_report_attachments_private #2019-11-26
|
||||
frappe.patches.v12_0.setup_email_linking
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
|
||||
frappe.db.sql("UPDATE `tabTag Link` SET parenttype=document_type")
|
||||
frappe.db.sql("UPDATE `tabTag Link` SET parent=document_name")
|
||||
|
|
@ -595,6 +595,8 @@ export default class GridRow {
|
|||
// to get update df for the row
|
||||
let df = this.docfields.find(field => field.fieldname === col[0].fieldname);
|
||||
|
||||
this.set_dependant_property(df);
|
||||
|
||||
let colsize = col[1];
|
||||
let txt = this.doc ?
|
||||
frappe.format(this.doc[df.fieldname], df, null, this.doc) :
|
||||
|
|
@ -633,6 +635,56 @@ export default class GridRow {
|
|||
}
|
||||
}
|
||||
|
||||
set_dependant_property(df) {
|
||||
if (!df.reqd && df.mandatory_depends_on &&
|
||||
this.evaluate_depends_on_value(df.mandatory_depends_on)) {
|
||||
df.reqd = 1;
|
||||
}
|
||||
|
||||
if (!df.read_only && df.read_only_depends_on &&
|
||||
this.evaluate_depends_on_value(df.read_only_depends_on)) {
|
||||
df.read_only = 1;
|
||||
}
|
||||
}
|
||||
|
||||
evaluate_depends_on_value(expression) {
|
||||
let out = null;
|
||||
let doc = this.doc;
|
||||
|
||||
if (!doc) return;
|
||||
|
||||
let parent = this.frm ? this.frm.doc : this.doc || null;
|
||||
|
||||
if (typeof (expression) === 'boolean') {
|
||||
out = expression;
|
||||
|
||||
} else if (typeof (expression) === 'function') {
|
||||
out = expression(doc);
|
||||
|
||||
} else if (expression.substr(0, 5)=='eval:') {
|
||||
try {
|
||||
out = frappe.utils.eval(expression.substr(5), { doc, parent });
|
||||
if (parent && parent.istable && expression.includes('is_submittable')) {
|
||||
out = true;
|
||||
}
|
||||
} catch (e) {
|
||||
frappe.throw(__('Invalid "depends_on" expression'));
|
||||
}
|
||||
|
||||
} else if (expression.substr(0, 3)=='fn:' && this.frm) {
|
||||
out = this.frm.script_manager.trigger(expression.substr(3), this.doctype, this.docname);
|
||||
} else {
|
||||
var value = doc[expression];
|
||||
if ($.isArray(value)) {
|
||||
out = !!value.length;
|
||||
} else {
|
||||
out = !!value;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
show_search_row() {
|
||||
// show or remove search columns based on grid rows
|
||||
this.show_search = this.frm && this.frm.doc &&
|
||||
|
|
|
|||
|
|
@ -130,7 +130,8 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
folded = frm.layout.folded;
|
||||
}
|
||||
|
||||
if (df.reqd && !frappe.model.has_value(doc.doctype, doc.name, df.fieldname)) {
|
||||
if (is_docfield_mandatory(doc, df) &&
|
||||
!frappe.model.has_value(doc.doctype, doc.name, df.fieldname)) {
|
||||
has_errors = true;
|
||||
error_fields[error_fields.length] = __(df.label);
|
||||
// scroll to field
|
||||
|
|
@ -173,6 +174,42 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
return !has_errors;
|
||||
};
|
||||
|
||||
let is_docfield_mandatory = function(doc, df) {
|
||||
if (df.reqd) return true;
|
||||
if (!df.mandatory_depends_on || !doc) return;
|
||||
|
||||
let out = null;
|
||||
let expression = df.mandatory_depends_on;
|
||||
let parent = frappe.get_meta(df.parent);
|
||||
|
||||
if (typeof (expression) === 'boolean') {
|
||||
out = expression;
|
||||
|
||||
} else if (typeof (expression) === 'function') {
|
||||
out = expression(doc);
|
||||
|
||||
} else if (expression.substr(0, 5) == 'eval:') {
|
||||
try {
|
||||
out = frappe.utils.eval(expression.substr(5), { doc, parent });
|
||||
if (parent && parent.istable && expression.includes('is_submittable')) {
|
||||
out = true;
|
||||
}
|
||||
} catch (e) {
|
||||
frappe.throw(__('Invalid "mandatory_depends_on" expression'));
|
||||
}
|
||||
|
||||
} else {
|
||||
var value = doc[expression];
|
||||
if ($.isArray(value)) {
|
||||
out = !!value.length;
|
||||
} else {
|
||||
out = !!value;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
const scroll_to = (fieldname) => {
|
||||
frm.scroll_to_field(fieldname);
|
||||
frm.scroll_set = true;
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ frappe.views.BaseList = class BaseList {
|
|||
// df is passed
|
||||
const df = fieldname;
|
||||
fieldname = df.fieldname;
|
||||
doctype = df.parent;
|
||||
doctype = df.parent || doctype;
|
||||
}
|
||||
|
||||
if (!this.fields) this.fields = [];
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
264
frappe/public/js/frappe/views/kanban/kanban_settings.js
Normal file
264
frappe/public/js/frappe/views/kanban/kanban_settings.js
Normal 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)
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ html {
|
|||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
55
frappe/public/scss/element/checkbox.scss
Normal file
55
frappe/public/scss/element/checkbox.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
37
frappe/public/scss/element/radio.scss
Normal file
37
frappe/public/scss/element/radio.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
.my-account-container {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class EnergyPointRule(Document):
|
|||
self.apply_only_once,
|
||||
)
|
||||
except Exception as e:
|
||||
frappe.log_error(frappe.get_traceback(), "apply_energy_point")
|
||||
self.log_error("Energy points failed")
|
||||
|
||||
def rule_condition_satisfied(self, doc):
|
||||
if self.for_doc_event == "New":
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import frappe
|
|||
import frappe.utils.scheduler
|
||||
from frappe.model.naming import revert_series_if_last
|
||||
from frappe.modules import get_module_name, load_doctype_module
|
||||
from frappe.utils import cint
|
||||
|
||||
unittest_runner = unittest.TextTestRunner
|
||||
SLOW_TEST_THRESHOLD = 2
|
||||
|
|
@ -177,10 +178,13 @@ def run_all_tests(
|
|||
_add_test(app, path, filename, verbose, test_suite, ui_tests)
|
||||
|
||||
if junit_xml_output:
|
||||
runner = unittest_runner(verbosity=1 + (verbose and 1 or 0), failfast=failfast)
|
||||
runner = unittest_runner(verbosity=1 + cint(verbose), failfast=failfast)
|
||||
else:
|
||||
runner = unittest_runner(
|
||||
resultclass=TimeLoggingTestResult, verbosity=1 + (verbose and 1 or 0), failfast=failfast
|
||||
resultclass=TimeLoggingTestResult,
|
||||
verbosity=1 + cint(verbose),
|
||||
failfast=failfast,
|
||||
tb_locals=verbose,
|
||||
)
|
||||
|
||||
if profile:
|
||||
|
|
@ -279,10 +283,13 @@ def _run_unittest(
|
|||
test_suite.addTest(module_test_cases)
|
||||
|
||||
if junit_xml_output:
|
||||
runner = unittest_runner(verbosity=1 + (verbose and 1 or 0), failfast=failfast)
|
||||
runner = unittest_runner(verbosity=1 + cint(verbose), failfast=failfast)
|
||||
else:
|
||||
runner = unittest_runner(
|
||||
resultclass=TimeLoggingTestResult, verbosity=1 + (verbose and 1 or 0), failfast=failfast
|
||||
resultclass=TimeLoggingTestResult,
|
||||
verbosity=1 + cint(verbose),
|
||||
failfast=failfast,
|
||||
tb_locals=verbose,
|
||||
)
|
||||
|
||||
if profile:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;"
|
||||
|
|
|
|||
|
|
@ -2398,7 +2398,7 @@ Submit after importing,Dien na invoer in,
|
|||
Submit an Issue,Dien '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,
|
||||
|
|
|
|||
|
|
|
@ -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,የደንበኝነት ምዝገባ ማሳወቂያ,
|
||||
|
|
|
|||
|
|
|
@ -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,إشعار الاشتراك,
|
||||
|
|
|
|||
|
|
|
@ -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,Нотификация за абонамент,
|
||||
|
|
|
|||
|
|
|
@ -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,সাবস্ক্রিপশন বিজ্ঞপ্তি,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -2398,7 +2398,7 @@ Submit after importing,Enviar després d'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ó,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -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,Ειδοποίηση συνδρομής,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -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,اعلان اشتراک,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -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'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 s’appliquent 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, c’est à 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'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'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.",
|
||||
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'ê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'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'action de flux de travail n'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 "modified_from" doit être présent pour effectuer un amendement.,
|
||||
"""amended_from"" field must be present to do an amendment.",Le champ "modified_from" 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'est modifiable que par les utilisateurs avec un rôle,
|
||||
{0}: Other permission rules may also apply,{0}: d'autres règles d'autorisation peuvent également s'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'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'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'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'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'avez pas de compte?
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 421.
|
|
|
@ -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,સબ્સ્ક્રિપ્શન સૂચના,
|
||||
|
|
|
|||
|
|
|
@ -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,הודעת מנוי,
|
||||
|
|
|
|||
|
|
|
@ -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,सदस्यता अधिसूचना,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
|
|
@ -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,
|
||||
|
|
|
|||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue