Merge branch 'develop' of https://github.com/frappe/frappe into rebrand-ui
This commit is contained in:
commit
b5d9c1e816
219 changed files with 3803 additions and 2311 deletions
3
.github/workflows/docker-release.yml
vendored
3
.github/workflows/docker-release.yml
vendored
|
|
@ -1,9 +1,10 @@
|
|||
name: Trigger Docker build on release
|
||||
name: 'Trigger Docker build on release'
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
jobs:
|
||||
curl:
|
||||
name: 'Trigger Docker build on release'
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: alpine:latest
|
||||
|
|
|
|||
5
.github/workflows/docs-checker.yml
vendored
5
.github/workflows/docs-checker.yml
vendored
|
|
@ -1,10 +1,11 @@
|
|||
name: 'Documentation Required'
|
||||
name: 'Documentation Check'
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize, reopened, edited ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docs-required:
|
||||
name: 'Documentation Required'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
|
|
|||
5
.github/workflows/publish-assets-develop.yml
vendored
5
.github/workflows/publish-assets-develop.yml
vendored
|
|
@ -1,11 +1,12 @@
|
|||
name: Build and Publish Assets for Development
|
||||
name: 'Frappe Assets'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-dev-and-publish:
|
||||
name: 'Build and Publish Assets for Development'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
name: Build and Publish Assets built for Releases
|
||||
name: 'Frappe Assets'
|
||||
|
||||
on:
|
||||
release:
|
||||
|
|
@ -8,7 +8,8 @@ env:
|
|||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-release-and-publish:
|
||||
name: 'Build and Publish Assets built for Releases'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
|
@ -44,4 +45,3 @@ jobs:
|
|||
asset_path: build/assets.tar.gz
|
||||
asset_name: assets.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ pull_request_rules:
|
|||
- status-success=Semantic Pull Request
|
||||
- status-success=Travis CI - Pull Request
|
||||
- status-success=security/snyk (frappe)
|
||||
- label!=don't-merge
|
||||
- label!=dont-merge
|
||||
- label!=squash
|
||||
- "#approved-reviews-by>=1"
|
||||
actions:
|
||||
|
|
@ -17,7 +17,7 @@ pull_request_rules:
|
|||
- status-success=Semantic Pull Request
|
||||
- status-success=Travis CI - Pull Request
|
||||
- status-success=security/snyk (frappe)
|
||||
- label!=don't-merge
|
||||
- label!=dont-merge
|
||||
- label=squash
|
||||
- "#approved-reviews-by>=1"
|
||||
actions:
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ matrix:
|
|||
- name: "Python 3.7 MariaDB"
|
||||
python: 3.7
|
||||
env: DB=mariadb TYPE=server
|
||||
script: bench --verbose --site test_site run-tests --coverage
|
||||
script: bench --site test_site run-tests --coverage
|
||||
|
||||
- name: "Python 3.7 PostgreSQL"
|
||||
python: 3.7
|
||||
env: DB=postgres TYPE=server
|
||||
script: bench --verbose --site test_site run-tests --coverage
|
||||
script: bench --site test_site run-tests --coverage
|
||||
|
||||
- name: "Cypress"
|
||||
python: 3.7
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ website/ @scmmishra
|
|||
web_form/ @scmmishra
|
||||
templates/ @scmmishra
|
||||
www/ @scmmishra
|
||||
integrations/ @Mangesh-Khairnar
|
||||
integrations/ @nextchamp-saqib
|
||||
patches/ @sahil28297
|
||||
dashboard/ @prssanna
|
||||
email/ @Thunderbottom
|
||||
email/ @saurabh6790
|
||||
event_streaming/ @ruchamahabal
|
||||
data_import* @netchampfaris
|
||||
core/ @surajshetty3416
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ def log(msg):
|
|||
|
||||
debug_log.append(as_unicode(msg))
|
||||
|
||||
def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False, primary_action=None, is_minimizable=None, wide=None):
|
||||
def msgprint(msg, title=None, raise_exception=0, as_table=False, as_list=False, indicator=None, alert=False, primary_action=None, is_minimizable=None, wide=None):
|
||||
"""Print a message to the user (via HTTP response).
|
||||
Messages are sent in the `__server_messages` property in the
|
||||
response JSON and shown in a pop-up / modal.
|
||||
|
|
@ -321,6 +321,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
|
|||
:param title: [optional] Message title.
|
||||
:param raise_exception: [optional] Raise given exception and show message.
|
||||
:param as_table: [optional] If `msg` is a list of lists, render as HTML table.
|
||||
:param as_list: [optional] If `msg` is a list, render as un-ordered list.
|
||||
:param primary_action: [optional] Bind a primary server/client side action.
|
||||
:param is_minimizable: [optional] Allow users to minimize the modal
|
||||
:param wide: [optional] Show wide modal
|
||||
|
|
@ -346,16 +347,10 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
|
|||
return
|
||||
|
||||
if as_table and type(msg) in (list, tuple):
|
||||
|
||||
table_rows = ''
|
||||
for row in msg:
|
||||
table_row_data = ''
|
||||
for data in row:
|
||||
table_row_data += '<td>{}</td>'.format(data)
|
||||
table_rows += '<tr>{}</tr>'.format(table_row_data)
|
||||
|
||||
out.message = '''<table class="table table-bordered"
|
||||
style="margin: 0;">{}</table>'''.format(table_rows)
|
||||
out.as_table = 1
|
||||
|
||||
if as_list and type(msg) in (list, tuple) and len(msg) > 1:
|
||||
out.as_list = 1
|
||||
|
||||
if flags.print_messages and out.message:
|
||||
print(f"Message: {repr(out.message).encode('utf-8')}")
|
||||
|
|
@ -405,12 +400,12 @@ def clear_last_message():
|
|||
if len(local.message_log) > 0:
|
||||
local.message_log = local.message_log[:-1]
|
||||
|
||||
def throw(msg, exc=ValidationError, title=None, is_minimizable=None, wide=None):
|
||||
def throw(msg, exc=ValidationError, title=None, is_minimizable=None, wide=None, as_list=False):
|
||||
"""Throw execption and show message (`msgprint`).
|
||||
|
||||
:param msg: Message.
|
||||
:param exc: Exception class. Default `frappe.ValidationError`"""
|
||||
msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable, wide=wide)
|
||||
msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable, wide=wide, as_list=as_list)
|
||||
|
||||
def emit_js(js, user=False, **kwargs):
|
||||
if user == False:
|
||||
|
|
@ -1159,6 +1154,7 @@ def make_property_setter(args, ignore_validate=False, validate_fields_for_doctyp
|
|||
'doctype_or_field': args.doctype_or_field,
|
||||
'doc_type': doctype,
|
||||
'field_name': args.fieldname,
|
||||
'row_name': args.row_name,
|
||||
'property': args.property,
|
||||
'value': args.value,
|
||||
'property_type': args.property_type or "Data",
|
||||
|
|
|
|||
|
|
@ -160,6 +160,10 @@ def handle_exception(e):
|
|||
http_status_code = getattr(e, "http_status_code", 500)
|
||||
return_as_message = False
|
||||
|
||||
if frappe.conf.get('developer_mode'):
|
||||
# don't fail silently
|
||||
print(frappe.get_traceback())
|
||||
|
||||
if frappe.get_request_header('Accept') and (frappe.local.is_ajax or 'application/json' in frappe.get_request_header('Accept')):
|
||||
# handle ajax responses first
|
||||
# if the request is ajax, send back the trace or error message
|
||||
|
|
|
|||
|
|
@ -2,38 +2,70 @@
|
|||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Assignment Rule', {
|
||||
onload: (frm) => {
|
||||
frm.trigger('set_due_date_field_options');
|
||||
},
|
||||
refresh: function(frm) {
|
||||
frm.trigger('setup_assignment_days_buttons');
|
||||
frm.trigger('set_options');
|
||||
// refresh description
|
||||
frm.events.rule(frm);
|
||||
},
|
||||
|
||||
document_type: function(frm) {
|
||||
frm.trigger('set_options');
|
||||
},
|
||||
|
||||
setup_assignment_days_buttons: function(frm) {
|
||||
const labels = ['Weekends', 'Weekdays', 'All Days'];
|
||||
let get_days = (label) => {
|
||||
const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
|
||||
const weekends = ['Saturday', 'Sunday'];
|
||||
return {
|
||||
'All Days': weekdays.concat(weekends),
|
||||
'Weekdays': weekdays,
|
||||
'Weekends': weekends,
|
||||
}[label];
|
||||
};
|
||||
|
||||
let set_days = (e) => {
|
||||
frm.clear_table('assignment_days');
|
||||
const label = $(e.currentTarget).text();
|
||||
get_days(label).forEach((day) =>
|
||||
frm.add_child('assignment_days', { day: day })
|
||||
);
|
||||
frm.refresh_field('assignment_days');
|
||||
};
|
||||
|
||||
labels.forEach(label =>
|
||||
frm.fields_dict['assignment_days'].grid.add_custom_button(
|
||||
label,
|
||||
set_days,
|
||||
'top'
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
rule: function(frm) {
|
||||
if (frm.doc.rule === 'Round Robin') {
|
||||
frm.get_field('rule').set_description(__('Assign one by one, in sequence'));
|
||||
} else {
|
||||
frm.get_field('rule').set_description(__('Assign to the one who has the least assignments'));
|
||||
}
|
||||
const description_map = {
|
||||
'Round Robin': __('Assign one by one, in sequence'),
|
||||
'Load Balancing': __('Assign to the one who has the least assignments'),
|
||||
'Based on Field': __('Assign to the user set in this field'),
|
||||
};
|
||||
frm.get_field('rule').set_description(description_map[frm.doc.rule]);
|
||||
},
|
||||
document_type: (frm) => {
|
||||
frm.trigger('set_due_date_field_options');
|
||||
},
|
||||
set_due_date_field_options: (frm) => {
|
||||
let doctype = frm.doc.document_type;
|
||||
let datetime_fields = [];
|
||||
|
||||
set_options(frm) {
|
||||
const doctype = frm.doc.document_type;
|
||||
frm.set_fields_as_options(
|
||||
'field',
|
||||
doctype,
|
||||
(df) => df.fieldtype == 'Link' && df.options == 'User',
|
||||
[{ label: 'Owner', value: 'owner' }]
|
||||
);
|
||||
if (doctype) {
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
frappe.get_meta(doctype).fields.map((df) => {
|
||||
if (['Date', 'Datetime'].includes(df.fieldtype)) {
|
||||
datetime_fields.push({ label: df.label, value: df.fieldname });
|
||||
}
|
||||
});
|
||||
if (datetime_fields) {
|
||||
frm.set_df_property('due_date_based_on', 'options', datetime_fields);
|
||||
}
|
||||
frm.set_df_property('due_date_based_on', 'hidden', !datetime_fields.length);
|
||||
});
|
||||
frm.set_fields_as_options(
|
||||
'due_date_based_on',
|
||||
doctype,
|
||||
(df) => ['Date', 'Datetime'].includes(df.fieldtype)
|
||||
).then(options => frm.set_df_property('due_date_based_on', 'hidden', !options.length));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"assignment_days",
|
||||
"assign_to_users_section",
|
||||
"rule",
|
||||
"field",
|
||||
"users",
|
||||
"last_user"
|
||||
],
|
||||
|
|
@ -93,15 +94,16 @@
|
|||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Rule",
|
||||
"options": "Round Robin\nLoad Balancing",
|
||||
"options": "Round Robin\nLoad Balancing\nBased on Field",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.rule !== 'Based on Field'",
|
||||
"fieldname": "users",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Users",
|
||||
"options": "Assignment Rule User",
|
||||
"reqd": 1
|
||||
"mandatory_depends_on": "eval: doc.rule !== 'Based on Field'",
|
||||
"options": "Assignment Rule User"
|
||||
},
|
||||
{
|
||||
"fieldname": "last_user",
|
||||
|
|
@ -134,15 +136,22 @@
|
|||
},
|
||||
{
|
||||
"depends_on": "document_type",
|
||||
"description": "Value from this field will be set as the due date in the ToDo",
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Due Date Based On",
|
||||
"description": "Value from this field will be set as the due date in the ToDo"
|
||||
"label": "Due Date Based On"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.rule == 'Based on Field'",
|
||||
"fieldname": "field",
|
||||
"fieldtype": "Select",
|
||||
"label": "Field",
|
||||
"mandatory_depends_on": "eval: doc.rule == 'Based on Field'"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-13 06:48:54.019735",
|
||||
"modified": "2020-10-20 14:47:20.662954",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Assignment Rule",
|
||||
|
|
|
|||
|
|
@ -38,27 +38,30 @@ class AssignmentRule(Document):
|
|||
|
||||
def apply_assign(self, doc):
|
||||
if self.safe_eval('assign_condition', doc):
|
||||
self.do_assignment(doc)
|
||||
return True
|
||||
return self.do_assignment(doc)
|
||||
|
||||
def do_assignment(self, doc):
|
||||
# clear existing assignment, to reassign
|
||||
assign_to.clear(doc.get('doctype'), doc.get('name'))
|
||||
|
||||
user = self.get_user()
|
||||
user = self.get_user(doc)
|
||||
|
||||
assign_to.add(dict(
|
||||
assign_to = [user],
|
||||
doctype = doc.get('doctype'),
|
||||
name = doc.get('name'),
|
||||
description = frappe.render_template(self.description, doc),
|
||||
assignment_rule = self.name,
|
||||
notify = True,
|
||||
date = doc.get(self.due_date_based_on) if self.due_date_based_on else None
|
||||
))
|
||||
if user:
|
||||
assign_to.add(dict(
|
||||
assign_to = [user],
|
||||
doctype = doc.get('doctype'),
|
||||
name = doc.get('name'),
|
||||
description = frappe.render_template(self.description, doc),
|
||||
assignment_rule = self.name,
|
||||
notify = True,
|
||||
date = doc.get(self.due_date_based_on) if self.due_date_based_on else None
|
||||
))
|
||||
|
||||
# set for reference in round robin
|
||||
self.db_set('last_user', user)
|
||||
# set for reference in round robin
|
||||
self.db_set('last_user', user)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def clear_assignment(self, doc):
|
||||
'''Clear assignments'''
|
||||
|
|
@ -70,7 +73,7 @@ class AssignmentRule(Document):
|
|||
if self.safe_eval('close_condition', doc):
|
||||
return assign_to.close_all_assignments(doc.get('doctype'), doc.get('name'))
|
||||
|
||||
def get_user(self):
|
||||
def get_user(self, doc):
|
||||
'''
|
||||
Get the next user for assignment
|
||||
'''
|
||||
|
|
@ -78,6 +81,8 @@ class AssignmentRule(Document):
|
|||
return self.get_user_round_robin()
|
||||
elif self.rule == 'Load Balancing':
|
||||
return self.get_user_load_balancing()
|
||||
elif self.rule == 'Based on Field':
|
||||
return doc.get(self.field)
|
||||
|
||||
def get_user_round_robin(self):
|
||||
'''
|
||||
|
|
|
|||
|
|
@ -88,6 +88,30 @@ class TestAutoAssign(unittest.TestCase):
|
|||
for user in ('test@example.com', 'test1@example.com', 'test2@example.com'):
|
||||
self.assertEqual(len(frappe.get_all('ToDo', dict(owner = user, reference_type = 'Note'))), 10)
|
||||
|
||||
def test_based_on_field(self):
|
||||
self.assignment_rule.rule = 'Based on Field'
|
||||
self.assignment_rule.field = 'owner'
|
||||
self.assignment_rule.save()
|
||||
|
||||
frappe.set_user('test1@example.com')
|
||||
note = make_note(dict(public=1))
|
||||
# check if auto assigned to doc owner, test1@example.com
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), 'test1@example.com')
|
||||
|
||||
frappe.set_user('test2@example.com')
|
||||
note = make_note(dict(public=1))
|
||||
# check if auto assigned to doc owner, test2@example.com
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), 'test2@example.com')
|
||||
|
||||
frappe.set_user('Administrator')
|
||||
|
||||
def test_assign_condition(self):
|
||||
# check condition
|
||||
|
|
@ -287,4 +311,4 @@ def make_note(values=None):
|
|||
|
||||
note.insert()
|
||||
|
||||
return note
|
||||
return note
|
||||
|
|
|
|||
|
|
@ -1,76 +1,34 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"allow_read": 1,
|
||||
"creation": "2019-02-27 11:41:46.602400",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"user"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"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": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-27 17:16:41.399261",
|
||||
"links": [],
|
||||
"modified": "2020-09-29 20:12:14.456785",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Assignment Rule User",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -615,8 +615,10 @@ def browse(context, site):
|
|||
@click.command('start-recording')
|
||||
@pass_context
|
||||
def start_recording(context):
|
||||
import frappe.recorder
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
frappe.set_user("Administrator")
|
||||
frappe.recorder.start()
|
||||
if not context.sites:
|
||||
raise SiteNotSpecifiedError
|
||||
|
|
@ -625,8 +627,10 @@ def start_recording(context):
|
|||
@click.command('stop-recording')
|
||||
@pass_context
|
||||
def stop_recording(context):
|
||||
import frappe.recorder
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
frappe.set_user("Administrator")
|
||||
frappe.recorder.stop()
|
||||
if not context.sites:
|
||||
raise SiteNotSpecifiedError
|
||||
|
|
|
|||
|
|
@ -460,11 +460,21 @@ def console(context):
|
|||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.local.lang = frappe.db.get_default("lang")
|
||||
|
||||
import IPython
|
||||
all_apps = frappe.get_installed_apps()
|
||||
failed_to_import = []
|
||||
|
||||
for app in all_apps:
|
||||
locals()[app] = __import__(app)
|
||||
try:
|
||||
locals()[app] = __import__(app)
|
||||
except ModuleNotFoundError:
|
||||
failed_to_import.append(app)
|
||||
|
||||
print("Apps in this namespace:\n{}".format(", ".join(all_apps)))
|
||||
if failed_to_import:
|
||||
print("\nFailed to import:\n{}".format(", ".join(failed_to_import)))
|
||||
|
||||
IPython.embed(display_banner="", header="", colors="neutral")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
{
|
||||
"fieldname": "state",
|
||||
"fieldtype": "Data",
|
||||
"label": "State"
|
||||
"label": "State/Province"
|
||||
},
|
||||
{
|
||||
"fieldname": "country",
|
||||
|
|
@ -148,7 +148,7 @@
|
|||
"icon": "fa fa-map-marker",
|
||||
"idx": 5,
|
||||
"links": [],
|
||||
"modified": "2020-10-14 17:38:08.971776",
|
||||
"modified": "2020-10-21 16:14:37.284830",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Contacts",
|
||||
"name": "Address",
|
||||
|
|
|
|||
|
|
@ -259,10 +259,8 @@ class Communication(Document):
|
|||
# Timeline Links
|
||||
def set_timeline_links(self):
|
||||
contacts = []
|
||||
if (self.email_account and frappe.db.get_value("Email Account", self.email_account, "create_contact")) or \
|
||||
frappe.flags.in_test:
|
||||
|
||||
contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc])
|
||||
create_contact_enabled = self.email_account and frappe.db.get_value("Email Account", self.email_account, "create_contact")
|
||||
contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc], auto_create_contact=create_contact_enabled)
|
||||
|
||||
for contact_name in contacts:
|
||||
self.add_link('Contact', contact_name)
|
||||
|
|
@ -341,7 +339,7 @@ def get_permission_query_conditions_for_communication(user):
|
|||
return """`tabCommunication`.email_account in ({email_accounts})"""\
|
||||
.format(email_accounts=','.join(email_accounts))
|
||||
|
||||
def get_contacts(email_strings):
|
||||
def get_contacts(email_strings, auto_create_contact=False):
|
||||
email_addrs = []
|
||||
|
||||
for email_string in email_strings:
|
||||
|
|
@ -356,7 +354,7 @@ def get_contacts(email_strings):
|
|||
email = get_email_without_link(email)
|
||||
contact_name = get_contact_name(email)
|
||||
|
||||
if not contact_name and email:
|
||||
if not contact_name and email and auto_create_contact:
|
||||
email_parts = email.split("@")
|
||||
first_name = frappe.unscrub(email_parts[0])
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
comm = frappe.get_doc({
|
||||
"doctype":"Communication",
|
||||
"subject": subject,
|
||||
"content": frappe.utils.sanitize_html(content),
|
||||
"content": content,
|
||||
"sender": sender,
|
||||
"sender_full_name":sender_full_name,
|
||||
"recipients": recipients,
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
Title ,Description ,Number ,another_number ,ID (Table Field 1) ,Child Title (Table Field 1) ,Child Description (Table Field 1) ,Child 2 Title (Table Field 2) ,Child 2 Date (Table Field 2) ,Child 2 Number (Table Field 2) ,Child Title (Table Field 1 Again) ,Child Date (Table Field 1 Again) ,Child Number (Table Field 1 Again) ,table_field_1_again.child_another_number
|
||||
Test 26 ,test description ,1 ,2 ,"" ,child title ,child description ,child title ,14-08-2019 ,4 ,child title again ,22-09-2020 ,5 , 7
|
||||
Title,Description,Number,another_number,ID (Table Field 1),Child Title (Table Field 1),Child Description (Table Field 1),Child 2 Title (Table Field 2),Child 2 Date (Table Field 2),Child 2 Number (Table Field 2),Child Title (Table Field 1 Again),Child Date (Table Field 1 Again),Child Number (Table Field 1 Again),table_field_1_again.child_another_number
|
||||
Test 26,test description,1,2,"",child title,child description,child title,14-08-2019,4,child title again,22-09-2020,5,7
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 56.
|
|
|
@ -616,7 +616,9 @@ class Row:
|
|||
id_field = get_id_field(doctype)
|
||||
id_value = doc.get(id_field.fieldname)
|
||||
if id_value and frappe.db.exists(doctype, id_value):
|
||||
doc = frappe.get_doc(doctype, id_value)
|
||||
existing_doc = frappe.get_doc(doctype, id_value)
|
||||
existing_doc.update(doc)
|
||||
doc = existing_doc
|
||||
else:
|
||||
# for table rows being inserted in update
|
||||
# create a new doc with defaults set
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ from __future__ import unicode_literals
|
|||
|
||||
import unittest
|
||||
import frappe
|
||||
from frappe.core.doctype.data_import.importer import Importer
|
||||
from frappe.utils import getdate, format_duration
|
||||
|
||||
doctype_name = 'DocType for Import'
|
||||
|
||||
class TestImporter(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
create_doctype_if_not_exists(doctype_name)
|
||||
|
||||
def test_data_import_from_file(self):
|
||||
|
|
@ -71,19 +73,28 @@ class TestImporter(unittest.TestCase):
|
|||
self.assertEqual(warnings[2]['message'], "<b>Title</b> is a mandatory field")
|
||||
|
||||
def test_data_import_update(self):
|
||||
if not frappe.db.exists(doctype_name, 'Test 26'):
|
||||
frappe.get_doc(
|
||||
doctype=doctype_name,
|
||||
title='Test 26'
|
||||
).insert()
|
||||
existing_doc = frappe.get_doc(
|
||||
doctype=doctype_name,
|
||||
title=frappe.generate_hash(doctype_name, 8),
|
||||
table_field_1=[{'child_title': 'child title to update'}]
|
||||
)
|
||||
existing_doc.save()
|
||||
frappe.db.commit()
|
||||
|
||||
import_file = get_import_file('sample_import_file_for_update')
|
||||
data_import = self.get_importer(doctype_name, import_file, update=True)
|
||||
data_import.start_import()
|
||||
i = Importer(data_import.reference_doctype, data_import=data_import)
|
||||
|
||||
updated_doc = frappe.get_doc(doctype_name, 'Test 26')
|
||||
# update child table id in template date
|
||||
i.import_file.raw_data[1][4] = existing_doc.table_field_1[0].name
|
||||
i.import_file.raw_data[1][0] = existing_doc.name
|
||||
i.import_file.parse_data_from_template()
|
||||
i.import_data()
|
||||
|
||||
updated_doc = frappe.get_doc(doctype_name, existing_doc.name)
|
||||
self.assertEqual(updated_doc.description, 'test description')
|
||||
self.assertEqual(updated_doc.table_field_1[0].child_title, 'child title')
|
||||
self.assertEqual(updated_doc.table_field_1[0].name, existing_doc.table_field_1[0].name)
|
||||
self.assertEqual(updated_doc.table_field_1[0].child_description, 'child description')
|
||||
self.assertEqual(updated_doc.table_field_1_again[0].child_title, 'child title again')
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"fieldname",
|
||||
"precision",
|
||||
"length",
|
||||
"non_negative",
|
||||
"hide_days",
|
||||
"hide_seconds",
|
||||
"reqd",
|
||||
|
|
@ -473,13 +474,20 @@
|
|||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)",
|
||||
"fieldname": "non_negative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Non Negative"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-28 11:28:21.252853",
|
||||
"modified": "2020-10-29 06:09:26.454990",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import re, copy, os, shutil
|
||||
import json
|
||||
from frappe.cache_manager import clear_user_cache
|
||||
|
||||
# imports - third party imports
|
||||
import six
|
||||
|
|
@ -103,6 +104,10 @@ class DocType(Document):
|
|||
self.owner = 'Administrator'
|
||||
self.modified_by = 'Administrator'
|
||||
|
||||
def after_insert(self):
|
||||
# clear user cache so that on the next reload this doctype is included in boot
|
||||
clear_user_cache(frappe.session.user)
|
||||
|
||||
def set_default_in_list_view(self):
|
||||
'''Set default in-list-view for first 4 mandatory fields'''
|
||||
if not [d.fieldname for d in self.fields if d.in_list_view]:
|
||||
|
|
@ -747,8 +752,8 @@ def validate_fields(meta):
|
|||
def check_illegal_default(d):
|
||||
if d.fieldtype == "Check" and not d.default:
|
||||
d.default = '0'
|
||||
if d.fieldtype == "Check" and d.default not in ('0', '1'):
|
||||
frappe.throw(_("Default for 'Check' type of field must be either '0' or '1'"))
|
||||
if d.fieldtype == "Check" and cint(d.default) not in (0, 1):
|
||||
frappe.throw(_("Default for 'Check' type of field {0} must be either '0' or '1'").format(frappe.bold(d.fieldname)))
|
||||
if d.fieldtype == "Select" and d.default:
|
||||
if not d.options:
|
||||
frappe.throw(_("Options for {0} must be set before setting the default value.").format(frappe.bold(d.fieldname)))
|
||||
|
|
|
|||
|
|
@ -12,41 +12,22 @@ from frappe.core.doctype.doctype.doctype import UniqueFieldnameError, IllegalMan
|
|||
|
||||
|
||||
class TestDocType(unittest.TestCase):
|
||||
def new_doctype(self, name, unique=0, depends_on=''):
|
||||
return frappe.get_doc({
|
||||
"doctype": "DocType",
|
||||
"module": "Core",
|
||||
"custom": 1,
|
||||
"fields": [{
|
||||
"label": "Some Field",
|
||||
"fieldname": "some_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"unique": unique,
|
||||
"depends_on": depends_on,
|
||||
}],
|
||||
"permissions": [{
|
||||
"role": "System Manager",
|
||||
"read": 1,
|
||||
}],
|
||||
"name": name
|
||||
})
|
||||
|
||||
def test_validate_name(self):
|
||||
self.assertRaises(frappe.NameError, self.new_doctype("_Some DocType").insert)
|
||||
self.assertRaises(frappe.NameError, self.new_doctype("8Some DocType").insert)
|
||||
self.assertRaises(frappe.NameError, self.new_doctype("Some (DocType)").insert)
|
||||
self.assertRaises(frappe.NameError, new_doctype("_Some DocType").insert)
|
||||
self.assertRaises(frappe.NameError, new_doctype("8Some DocType").insert)
|
||||
self.assertRaises(frappe.NameError, new_doctype("Some (DocType)").insert)
|
||||
for name in ("Some DocType", "Some_DocType"):
|
||||
if frappe.db.exists("DocType", name):
|
||||
frappe.delete_doc("DocType", name)
|
||||
|
||||
doc = self.new_doctype(name).insert()
|
||||
doc = new_doctype(name).insert()
|
||||
doc.delete()
|
||||
|
||||
def test_doctype_unique_constraint_dropped(self):
|
||||
if frappe.db.exists("DocType", "With_Unique"):
|
||||
frappe.delete_doc("DocType", "With_Unique")
|
||||
|
||||
dt = self.new_doctype("With_Unique", unique=1)
|
||||
dt = new_doctype("With_Unique", unique=1)
|
||||
dt.insert()
|
||||
|
||||
doc1 = frappe.new_doc("With_Unique")
|
||||
|
|
@ -67,7 +48,7 @@ class TestDocType(unittest.TestCase):
|
|||
doc2.delete()
|
||||
|
||||
def test_validate_search_fields(self):
|
||||
doc = self.new_doctype("Test Search Fields")
|
||||
doc = new_doctype("Test Search Fields")
|
||||
doc.search_fields = "some_fieldname"
|
||||
doc.insert()
|
||||
self.assertEqual(doc.name, "Test Search Fields")
|
||||
|
|
@ -85,7 +66,7 @@ class TestDocType(unittest.TestCase):
|
|||
self.assertRaises(frappe.ValidationError, doc.save)
|
||||
|
||||
def test_depends_on_fields(self):
|
||||
doc = self.new_doctype("Test Depends On", depends_on="eval:doc.__islocal == 0")
|
||||
doc = new_doctype("Test Depends On", depends_on="eval:doc.__islocal == 0")
|
||||
doc.insert()
|
||||
|
||||
# check if the assignment operation is allowed in depends_on
|
||||
|
|
@ -261,7 +242,7 @@ class TestDocType(unittest.TestCase):
|
|||
frappe.flags.allow_doctype_export = 0
|
||||
|
||||
def test_unique_field_name_for_two_fields(self):
|
||||
doc = self.new_doctype('Test Unique Field')
|
||||
doc = new_doctype('Test Unique Field')
|
||||
field_1 = doc.append('fields', {})
|
||||
field_1.fieldname = 'some_fieldname_1'
|
||||
field_1.fieldtype = 'Data'
|
||||
|
|
@ -273,7 +254,7 @@ class TestDocType(unittest.TestCase):
|
|||
self.assertRaises(UniqueFieldnameError, doc.insert)
|
||||
|
||||
def test_fieldname_is_not_name(self):
|
||||
doc = self.new_doctype('Test Name Field')
|
||||
doc = new_doctype('Test Name Field')
|
||||
field_1 = doc.append('fields', {})
|
||||
field_1.label = 'Name'
|
||||
field_1.fieldtype = 'Data'
|
||||
|
|
@ -283,7 +264,7 @@ class TestDocType(unittest.TestCase):
|
|||
self.assertRaises(InvalidFieldNameError, doc.save)
|
||||
|
||||
def test_illegal_mandatory_validation(self):
|
||||
doc = self.new_doctype('Test Illegal mandatory')
|
||||
doc = new_doctype('Test Illegal mandatory')
|
||||
field_1 = doc.append('fields', {})
|
||||
field_1.fieldname = 'some_fieldname_1'
|
||||
field_1.fieldtype = 'Section Break'
|
||||
|
|
@ -292,7 +273,7 @@ class TestDocType(unittest.TestCase):
|
|||
self.assertRaises(IllegalMandatoryError, doc.insert)
|
||||
|
||||
def test_link_with_wrong_and_no_options(self):
|
||||
doc = self.new_doctype('Test link')
|
||||
doc = new_doctype('Test link')
|
||||
field_1 = doc.append('fields', {})
|
||||
field_1.fieldname = 'some_fieldname_1'
|
||||
field_1.fieldtype = 'Link'
|
||||
|
|
@ -304,7 +285,7 @@ class TestDocType(unittest.TestCase):
|
|||
self.assertRaises(WrongOptionsDoctypeLinkError, doc.insert)
|
||||
|
||||
def test_hidden_and_mandatory_without_default(self):
|
||||
doc = self.new_doctype('Test hidden and mandatory')
|
||||
doc = new_doctype('Test hidden and mandatory')
|
||||
field_1 = doc.append('fields', {})
|
||||
field_1.fieldname = 'some_fieldname_1'
|
||||
field_1.fieldtype = 'Data'
|
||||
|
|
@ -314,7 +295,7 @@ class TestDocType(unittest.TestCase):
|
|||
self.assertRaises(HiddenAndMandatoryWithoutDefaultError, doc.insert)
|
||||
|
||||
def test_field_can_not_be_indexed_validation(self):
|
||||
doc = self.new_doctype('Test index')
|
||||
doc = new_doctype('Test index')
|
||||
field_1 = doc.append('fields', {})
|
||||
field_1.fieldname = 'some_fieldname_1'
|
||||
field_1.fieldtype = 'Long Text'
|
||||
|
|
@ -327,14 +308,14 @@ class TestDocType(unittest.TestCase):
|
|||
from frappe.desk.form.linked_with import get_submitted_linked_docs, cancel_all_linked_docs
|
||||
|
||||
#create doctype
|
||||
link_doc = self.new_doctype('Test Linked Doctype')
|
||||
link_doc = new_doctype('Test Linked Doctype')
|
||||
link_doc.is_submittable = 1
|
||||
for data in link_doc.get('permissions'):
|
||||
data.submit = 1
|
||||
data.cancel = 1
|
||||
link_doc.insert()
|
||||
|
||||
doc = self.new_doctype('Test Doctype')
|
||||
doc = new_doctype('Test Doctype')
|
||||
doc.is_submittable = 1
|
||||
field_2 = doc.append('fields', {})
|
||||
field_2.label = 'Test Linked Doctype'
|
||||
|
|
@ -377,12 +358,12 @@ class TestDocType(unittest.TestCase):
|
|||
doc.delete()
|
||||
frappe.db.commit()
|
||||
|
||||
def test_ignore_cancelation_of_linked_doctype_during_cancell(self):
|
||||
def test_ignore_cancelation_of_linked_doctype_during_cancel(self):
|
||||
import json
|
||||
from frappe.desk.form.linked_with import get_submitted_linked_docs, cancel_all_linked_docs
|
||||
|
||||
#create linked doctype
|
||||
link_doc = self.new_doctype('Test Linked Doctype 1')
|
||||
link_doc = new_doctype('Test Linked Doctype 1')
|
||||
link_doc.is_submittable = 1
|
||||
for data in link_doc.get('permissions'):
|
||||
data.submit = 1
|
||||
|
|
@ -390,7 +371,7 @@ class TestDocType(unittest.TestCase):
|
|||
link_doc.insert()
|
||||
|
||||
#create first parent doctype
|
||||
test_doc_1 = self.new_doctype('Test Doctype 1')
|
||||
test_doc_1 = new_doctype('Test Doctype 1')
|
||||
test_doc_1.is_submittable = 1
|
||||
|
||||
field_2 = test_doc_1.append('fields', {})
|
||||
|
|
@ -405,7 +386,7 @@ class TestDocType(unittest.TestCase):
|
|||
test_doc_1.insert()
|
||||
|
||||
#crete second parent doctype
|
||||
doc = self.new_doctype('Test Doctype 2')
|
||||
doc = new_doctype('Test Doctype 2')
|
||||
doc.is_submittable = 1
|
||||
|
||||
field_2 = doc.append('fields', {})
|
||||
|
|
@ -469,3 +450,28 @@ class TestDocType(unittest.TestCase):
|
|||
doc.delete()
|
||||
test_doc_1.delete()
|
||||
frappe.db.commit()
|
||||
|
||||
def new_doctype(name, unique=0, depends_on='', fields=None):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "DocType",
|
||||
"module": "Core",
|
||||
"custom": 1,
|
||||
"fields": [{
|
||||
"label": "Some Field",
|
||||
"fieldname": "some_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"unique": unique,
|
||||
"depends_on": depends_on,
|
||||
}],
|
||||
"permissions": [{
|
||||
"role": "System Manager",
|
||||
"read": 1,
|
||||
}],
|
||||
"name": name
|
||||
})
|
||||
|
||||
if fields:
|
||||
for f in fields:
|
||||
doc.append('fields', f)
|
||||
|
||||
return doc
|
||||
|
|
@ -9,7 +9,8 @@
|
|||
"action_type",
|
||||
"action",
|
||||
"group",
|
||||
"hidden"
|
||||
"hidden",
|
||||
"custom"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -48,12 +49,19 @@
|
|||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "custom",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Custom"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-21 14:44:03.845315",
|
||||
"modified": "2020-09-24 14:19:05.549835",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType Action",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
"field_order": [
|
||||
"link_doctype",
|
||||
"link_fieldname",
|
||||
"group"
|
||||
"group",
|
||||
"hidden",
|
||||
"custom"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -30,10 +32,25 @@
|
|||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Group"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "custom",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Custom"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-09-24 11:41:25.291377",
|
||||
"links": [],
|
||||
"modified": "2020-09-24 14:19:25.189511",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType Link",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@
|
|||
"fieldname": "prefix",
|
||||
"fieldtype": "Data",
|
||||
"label": "Prefix",
|
||||
"mandatory_depends_on": "eval:doc.naming_by===\"Numbered\""
|
||||
"mandatory_depends_on": "eval:doc.naming_by===\"Numbered\"",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "counter",
|
||||
|
|
@ -48,7 +49,8 @@
|
|||
"fieldname": "prefix_digits",
|
||||
"fieldtype": "Int",
|
||||
"label": "Digits",
|
||||
"mandatory_depends_on": "eval:doc.naming_by===\"Numbered\""
|
||||
"mandatory_depends_on": "eval:doc.naming_by===\"Numbered\"",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_section",
|
||||
|
|
@ -69,7 +71,7 @@
|
|||
"options": "Document Naming Rule Condition"
|
||||
},
|
||||
{
|
||||
"description": "Rules with higher priority will be applied first.",
|
||||
"description": "Rules with higher priority number will be applied first.",
|
||||
"fieldname": "priority",
|
||||
"fieldtype": "Int",
|
||||
"label": "Priority"
|
||||
|
|
@ -77,7 +79,7 @@
|
|||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-21 10:23:34.401539",
|
||||
"modified": "2020-11-04 14:38:14.836056",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Document Naming Rule",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class DocumentNamingRule(Document):
|
|||
Apply naming rules for the given document. Will set `name` if the rule is matched.
|
||||
'''
|
||||
if self.conditions:
|
||||
if not evaluate_filters(doc, [(d.field, d.condition, d.value) for d in self.conditions]):
|
||||
if not evaluate_filters(doc, [(self.document_type, d.field, d.condition, d.value) for d in self.conditions]):
|
||||
return
|
||||
|
||||
counter = frappe.db.get_value(self.doctype, self.name, 'counter', for_update=True) or 0
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
"actions": [],
|
||||
"creation": "2020-08-01 23:38:41.783206",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_label",
|
||||
|
|
@ -30,6 +29,7 @@
|
|||
"in_list_view": 1,
|
||||
"label": "Item Type",
|
||||
"options": "Route\nAction\nSeparator",
|
||||
"read_only_depends_on": "eval:doc.is_standard",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
|
|
@ -59,6 +59,7 @@
|
|||
"in_list_view": 1,
|
||||
"label": "Route",
|
||||
"mandatory_depends_on": "eval:doc.item_type == 'Route'",
|
||||
"read_only_depends_on": "eval:doc.is_standard",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
|
|
@ -68,13 +69,14 @@
|
|||
"fieldtype": "Data",
|
||||
"label": "Action",
|
||||
"mandatory_depends_on": "eval:doc.item_type == 'Action'",
|
||||
"read_only_depends_on": "eval:doc.is_standard",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-06 16:32:49.597060",
|
||||
"modified": "2020-11-02 10:57:37.709262",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Navbar Item",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ frappe.ui.form.on('Report', {
|
|||
refresh: function(frm) {
|
||||
if (frm.doc.is_standard === "Yes" && !frappe.boot.developer_mode) {
|
||||
// make the document read-only
|
||||
frm.set_read_only();
|
||||
frm.disable_form();
|
||||
} else {
|
||||
frm.enable_save();
|
||||
}
|
||||
|
||||
let doc = frm.doc;
|
||||
|
|
@ -32,8 +34,6 @@ frappe.ui.form.on('Report', {
|
|||
});
|
||||
}, doc.disabled ? "fa fa-check" : "fa fa-off");
|
||||
}
|
||||
|
||||
frm.events.report_type(frm);
|
||||
},
|
||||
|
||||
ref_doctype: function(frm) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,9 @@ class Report(Document):
|
|||
self.export_doc()
|
||||
|
||||
def on_trash(self):
|
||||
if self.is_standard == 'Yes' and not cint(getattr(frappe.local.conf, 'developer_mode',0)):
|
||||
if (self.is_standard == 'Yes'
|
||||
and not cint(getattr(frappe.local.conf, 'developer_mode', 0))
|
||||
and not frappe.flags.in_patch):
|
||||
frappe.throw(_("You are not allowed to delete Standard Report"))
|
||||
delete_custom_role('report', self.name)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe, json, os
|
||||
import unittest
|
||||
from frappe.desk.query_report import run, save_report
|
||||
from frappe.custom.doctype.customize_form.customize_form import reset_customization
|
||||
|
||||
test_records = frappe.get_test_records('Report')
|
||||
test_dependencies = ['User']
|
||||
|
|
@ -27,7 +29,57 @@ class TestReport(unittest.TestCase):
|
|||
columns, data = report.get_data(filters={'user': 'Administrator', 'doctype': 'DocType'})
|
||||
self.assertEqual(columns[0].get('label'), 'Name')
|
||||
self.assertEqual(columns[1].get('label'), 'Module')
|
||||
self.assertTrue('User' in [d[0] for d in data])
|
||||
self.assertTrue('User' in [d.get('name') for d in data])
|
||||
|
||||
def test_custom_report(self):
|
||||
reset_customization('User')
|
||||
custom_report_name = save_report(
|
||||
'Permitted Documents For User',
|
||||
'Permitted Documents For User Custom',
|
||||
json.dumps([{
|
||||
'fieldname': 'email',
|
||||
'fieldtype': 'Data',
|
||||
'label': 'Email',
|
||||
'insert_after_index': 0,
|
||||
'link_field': 'name',
|
||||
'doctype': 'User',
|
||||
'options': 'Email',
|
||||
'width': 100,
|
||||
'id':'email',
|
||||
'name': 'Email'
|
||||
}]))
|
||||
custom_report = frappe.get_doc('Report', custom_report_name)
|
||||
columns, result = custom_report.run_query_report(
|
||||
filters={
|
||||
'user': 'Administrator',
|
||||
'doctype': 'User'
|
||||
}, user=frappe.session.user)
|
||||
|
||||
self.assertListEqual(['email'], [column.get('fieldname') for column in columns])
|
||||
admin_dict = frappe.core.utils.find(result, lambda d: d['name'] == 'Administrator')
|
||||
self.assertDictEqual({'name': 'Administrator', 'user_type': 'System User', 'email': 'admin@example.com'}, admin_dict)
|
||||
|
||||
def test_report_with_custom_column(self):
|
||||
reset_customization('User')
|
||||
response = run('Permitted Documents For User',
|
||||
filters={'user': 'Administrator', 'doctype': 'User'},
|
||||
custom_columns=[{
|
||||
'fieldname': 'email',
|
||||
'fieldtype': 'Data',
|
||||
'label': 'Email',
|
||||
'insert_after_index': 0,
|
||||
'link_field': 'name',
|
||||
'doctype': 'User',
|
||||
'options': 'Email',
|
||||
'width': 100,
|
||||
'id':'email',
|
||||
'name': 'Email'
|
||||
}])
|
||||
result = response.get('result')
|
||||
columns = response.get('columns')
|
||||
self.assertListEqual(['name', 'email', 'user_type'], [column.get('fieldname') for column in columns])
|
||||
admin_dict = frappe.core.utils.find(result, lambda d: d['name'] == 'Administrator')
|
||||
self.assertDictEqual({'name': 'Administrator', 'user_type': 'System User', 'email': 'admin@example.com'}, admin_dict)
|
||||
|
||||
def test_report_permissions(self):
|
||||
frappe.set_user('test@example.com')
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.queue==='All'",
|
||||
"depends_on": "eval:doc.frequency==='All'",
|
||||
"fieldname": "create_log",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create Log"
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"depends_on": "eval:doc.queue==='Cron'",
|
||||
"depends_on": "eval:doc.frequency==='Cron'",
|
||||
"fieldname": "cron_format",
|
||||
"fieldtype": "Data",
|
||||
"label": "Cron Format",
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
"link_fieldname": "scheduled_job_type"
|
||||
}
|
||||
],
|
||||
"modified": "2020-04-05 17:27:33.480562",
|
||||
"modified": "2020-10-07 10:39:24.519460",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Scheduled Job Type",
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ class ScheduledJobType(Document):
|
|||
# force logging for all events other than continuous ones (ALL)
|
||||
self.create_log = 1
|
||||
|
||||
def enqueue(self):
|
||||
def enqueue(self, force=False):
|
||||
# enqueue event if last execution is done
|
||||
if self.is_event_due():
|
||||
if self.is_event_due() or force:
|
||||
if frappe.flags.enqueued_jobs:
|
||||
frappe.flags.enqueued_jobs.append(self.method)
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ class ScheduledJobType(Document):
|
|||
def execute_event(doc):
|
||||
frappe.only_for('System Manager')
|
||||
doc = json.loads(doc)
|
||||
frappe.get_doc('Scheduled Job Type', doc.get('name')).enqueue()
|
||||
frappe.get_doc('Scheduled Job Type', doc.get('name')).enqueue(force=True)
|
||||
|
||||
|
||||
def run_scheduled_job(job_type):
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ def query_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
|||
user = filters.get("user")
|
||||
user_perms = frappe.utils.user.UserPermissions(user)
|
||||
user_perms.build_permissions()
|
||||
can_read = user_perms.can_read
|
||||
can_read = user_perms.can_read # Does not include child tables
|
||||
|
||||
single_doctypes = [d[0] for d in frappe.db.get_values("DocType", {"issingle": 1})]
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
"mandatory_depends_on",
|
||||
"read_only_depends_on",
|
||||
"properties",
|
||||
"non_negative",
|
||||
"reqd",
|
||||
"unique",
|
||||
"read_only",
|
||||
|
|
@ -403,13 +404,20 @@
|
|||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)",
|
||||
"fieldname": "non_negative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Non Negative"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-28 11:28:44.377753",
|
||||
"modified": "2020-10-29 06:14:43.073329",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Field",
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Custom Link', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query("document_type", function () {
|
||||
return {
|
||||
filters: {
|
||||
custom: 0,
|
||||
istable: 0,
|
||||
module: ['not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]]
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.add_custom_button(__('Go to {0} List', [frm.doc.document_type]), function() {
|
||||
frappe.set_route('List', frm.doc.document_type);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -5,6 +5,7 @@ frappe.provide("frappe.customize_form");
|
|||
|
||||
frappe.ui.form.on("Customize Form", {
|
||||
onload: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.set_query("doc_type", function() {
|
||||
return {
|
||||
translate_values: false,
|
||||
|
|
@ -27,7 +28,7 @@ frappe.ui.form.on("Customize Form", {
|
|||
});
|
||||
|
||||
$(frm.wrapper).on("grid-row-render", function(e, grid_row) {
|
||||
if(grid_row.doc && grid_row.doc.fieldtype=="Section Break") {
|
||||
if (grid_row.doc && grid_row.doc.fieldtype=="Section Break") {
|
||||
$(grid_row.row).css({"font-weight": "bold"});
|
||||
}
|
||||
});
|
||||
|
|
@ -40,19 +41,25 @@ frappe.ui.form.on("Customize Form", {
|
|||
frm.trigger("setup_sortable");
|
||||
});
|
||||
|
||||
if (localStorage['customize_doctype']) {
|
||||
// set default value from customize form
|
||||
frm.set_value('doc_type', localStorage['customize_doctype']);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
doc_type: function(frm) {
|
||||
if(frm.doc.doc_type) {
|
||||
if (frm.doc.doc_type) {
|
||||
return frm.call({
|
||||
method: "fetch_to_customize",
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if(r) {
|
||||
if(r._server_messages && r._server_messages.length) {
|
||||
if (r) {
|
||||
if (r._server_messages && r._server_messages.length) {
|
||||
frm.set_value("doc_type", "");
|
||||
} else {
|
||||
localStorage['customize_doctype'] = frm.doc.doc_type;
|
||||
frm.refresh();
|
||||
frm.trigger("setup_sortable");
|
||||
}
|
||||
|
|
@ -69,7 +76,7 @@ frappe.ui.form.on("Customize Form", {
|
|||
frm.doc.fields.forEach(function(f, i) {
|
||||
var data_row = frm.page.body.find('[data-fieldname="fields"] [data-idx="'+ f.idx +'"] .data-row');
|
||||
|
||||
if(f.is_custom_field) {
|
||||
if (f.is_custom_field) {
|
||||
data_row.addClass("highlight");
|
||||
} else {
|
||||
f._sortable = false;
|
||||
|
|
@ -82,26 +89,26 @@ frappe.ui.form.on("Customize Form", {
|
|||
frm.disable_save();
|
||||
frm.page.clear_icons();
|
||||
|
||||
if(frm.doc.doc_type) {
|
||||
if (frm.doc.doc_type) {
|
||||
frappe.customize_form.set_primary_action(frm);
|
||||
|
||||
frm.add_custom_button(__('Go to {0} List', [frm.doc.doc_type]), function() {
|
||||
frappe.set_route('List', frm.doc.doc_type);
|
||||
});
|
||||
}, __('Actions'));
|
||||
|
||||
frm.add_custom_button(__('Refresh Form'), function() {
|
||||
frm.add_custom_button(__('Reload'), function() {
|
||||
frm.script_manager.trigger("doc_type");
|
||||
}, "fa fa-refresh", "btn-default");
|
||||
}, __('Actions'));
|
||||
|
||||
frm.add_custom_button(__('Reset to defaults'), function() {
|
||||
frappe.customize_form.confirm(__('Remove all customizations?'), frm);
|
||||
}, "fa fa-eraser", "btn-default");
|
||||
}, __('Actions'));
|
||||
|
||||
frm.add_custom_button(__('Set Permissions'), function() {
|
||||
frappe.set_route('permission-manager', frm.doc.doc_type);
|
||||
}, "fa fa-lock", "btn-default");
|
||||
}, __('Actions'));
|
||||
|
||||
if(frappe.boot.developer_mode) {
|
||||
if (frappe.boot.developer_mode) {
|
||||
frm.add_custom_button(__('Export Customizations'), function() {
|
||||
frappe.prompt(
|
||||
[
|
||||
|
|
@ -124,34 +131,36 @@ frappe.ui.form.on("Customize Form", {
|
|||
});
|
||||
},
|
||||
__("Select Module"));
|
||||
});
|
||||
}, __('Actions'));
|
||||
}
|
||||
}
|
||||
|
||||
// sort order select
|
||||
if(frm.doc.doc_type) {
|
||||
if (frm.doc.doc_type) {
|
||||
var fields = $.map(frm.doc.fields,
|
||||
function(df) { return frappe.model.is_value_type(df.fieldtype) ? df.fieldname : null; });
|
||||
function(df) {
|
||||
return frappe.model.is_value_type(df.fieldtype) ? df.fieldname : null;
|
||||
});
|
||||
fields = ["", "name", "modified"].concat(fields);
|
||||
frm.set_df_property("sort_field", "options", fields);
|
||||
}
|
||||
|
||||
if(frappe.route_options && frappe.route_options.doc_type) {
|
||||
if (frappe.route_options && frappe.route_options.doc_type) {
|
||||
setTimeout(function() {
|
||||
frm.set_value("doc_type", frappe.route_options.doc_type);
|
||||
frappe.route_options = null;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// can't delete standard fields
|
||||
frappe.ui.form.on("Customize Form Field", {
|
||||
before_fields_remove: function(frm, doctype, name) {
|
||||
var row = frappe.get_doc(doctype, name);
|
||||
if(!(row.is_custom_field || row.__islocal)) {
|
||||
if (!(row.is_custom_field || row.__islocal)) {
|
||||
frappe.msgprint(__("Cannot delete standard field. You can hide it if you want"));
|
||||
throw "cannot delete custom field";
|
||||
throw "cannot delete standard field";
|
||||
}
|
||||
},
|
||||
fields_add: function(frm, cdt, cdn) {
|
||||
|
|
@ -160,16 +169,46 @@ frappe.ui.form.on("Customize Form Field", {
|
|||
}
|
||||
});
|
||||
|
||||
// can't delete standard links
|
||||
frappe.ui.form.on("DocType Link", {
|
||||
before_links_remove: function(frm, doctype, name) {
|
||||
let row = frappe.get_doc(doctype, name);
|
||||
if (!(row.custom || row.__islocal)) {
|
||||
frappe.msgprint(__("Cannot delete standard link. You can hide it if you want"));
|
||||
throw "cannot delete standard link";
|
||||
}
|
||||
},
|
||||
links_add: function(frm, cdt, cdn) {
|
||||
let f = frappe.model.get_doc(cdt, cdn);
|
||||
f.custom = 1;
|
||||
}
|
||||
});
|
||||
|
||||
// can't delete standard actions
|
||||
frappe.ui.form.on("DocType Action", {
|
||||
before_actions_remove: function(frm, doctype, name) {
|
||||
let row = frappe.get_doc(doctype, name);
|
||||
if (!(row.custom || row.__islocal)) {
|
||||
frappe.msgprint(__("Cannot delete standard action. You can hide it if you want"));
|
||||
throw "cannot delete standard action";
|
||||
}
|
||||
},
|
||||
actions_add: function(frm, cdt, cdn) {
|
||||
let f = frappe.model.get_doc(cdt, cdn);
|
||||
f.custom = 1;
|
||||
}
|
||||
});
|
||||
|
||||
frappe.customize_form.set_primary_action = function(frm) {
|
||||
frm.page.set_primary_action(__("Update"), function() {
|
||||
if(frm.doc.doc_type) {
|
||||
if (frm.doc.doc_type) {
|
||||
return frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
btn: frm.page.btn_primary,
|
||||
method: "save_customization",
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
if (!r.exc) {
|
||||
frappe.customize_form.clear_locals_and_refresh(frm);
|
||||
frm.script_manager.trigger("doc_type");
|
||||
}
|
||||
|
|
@ -180,7 +219,7 @@ frappe.customize_form.set_primary_action = function(frm) {
|
|||
};
|
||||
|
||||
frappe.customize_form.confirm = function(msg, frm) {
|
||||
if(!frm.doc.doc_type) return;
|
||||
if (!frm.doc.doc_type) return;
|
||||
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: 'Reset To Defaults',
|
||||
|
|
@ -192,7 +231,7 @@ frappe.customize_form.confirm = function(msg, frm) {
|
|||
doc: frm.doc,
|
||||
method: "reset_to_defaults",
|
||||
callback: function(r) {
|
||||
if(r.exc) {
|
||||
if (r.exc) {
|
||||
frappe.msgprint(r.exc);
|
||||
} else {
|
||||
d.hide();
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@
|
|||
"doc_type",
|
||||
"properties",
|
||||
"label",
|
||||
"default_print_format",
|
||||
"max_attachments",
|
||||
"search_fields",
|
||||
"column_break_5",
|
||||
"allow_copy",
|
||||
"istable",
|
||||
"editable_grid",
|
||||
|
|
@ -20,22 +21,27 @@
|
|||
"track_views",
|
||||
"allow_auto_repeat",
|
||||
"allow_import",
|
||||
"show_preview_popup",
|
||||
"image_view",
|
||||
"column_break_5",
|
||||
"fields_section_break",
|
||||
"fields",
|
||||
"view_settings_section",
|
||||
"title_field",
|
||||
"image_field",
|
||||
"search_fields",
|
||||
"section_break_8",
|
||||
"sort_field",
|
||||
"column_break_10",
|
||||
"sort_order",
|
||||
"section_break_23",
|
||||
"default_print_format",
|
||||
"column_break_29",
|
||||
"show_preview_popup",
|
||||
"image_view",
|
||||
"email_settings_section",
|
||||
"email_append_to",
|
||||
"sender_field",
|
||||
"subject_field",
|
||||
"fields_section_break",
|
||||
"fields"
|
||||
"document_actions_section",
|
||||
"actions",
|
||||
"document_links_section",
|
||||
"links",
|
||||
"section_break_8",
|
||||
"sort_field",
|
||||
"column_break_10",
|
||||
"sort_order"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -130,9 +136,11 @@
|
|||
"label": "Search Fields"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "doc_type",
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "List Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "sort_field",
|
||||
|
|
@ -161,7 +169,8 @@
|
|||
"fieldname": "fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "Fields",
|
||||
"options": "Customize Form Field"
|
||||
"options": "Customize Form Field",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -200,24 +209,67 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Allow document creation via Email"
|
||||
},
|
||||
{
|
||||
"depends_on": "doc_type",
|
||||
"fieldname": "section_break_23",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_preview_popup",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Preview Popup"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "doc_type",
|
||||
"fieldname": "view_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "View Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_29",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "email_append_to",
|
||||
"depends_on": "doc_type",
|
||||
"fieldname": "email_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Email Settings"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "links",
|
||||
"depends_on": "doc_type",
|
||||
"fieldname": "document_links_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Document Links"
|
||||
},
|
||||
{
|
||||
"fieldname": "links",
|
||||
"fieldtype": "Table",
|
||||
"label": "Links",
|
||||
"options": "DocType Link"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "actions",
|
||||
"depends_on": "doc_type",
|
||||
"fieldname": "document_actions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Document Actions"
|
||||
},
|
||||
{
|
||||
"fieldname": "actions",
|
||||
"fieldtype": "Table",
|
||||
"label": "Actions",
|
||||
"options": "DocType Action"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-10 12:16:01.320411",
|
||||
"modified": "2020-09-24 14:16:49.594012",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
|||
Customize Form is a Single DocType used to mask the Property Setter
|
||||
Thus providing a better UI from user perspective
|
||||
"""
|
||||
import json
|
||||
import frappe
|
||||
import frappe.translate
|
||||
from frappe import _
|
||||
|
|
@ -14,80 +15,9 @@ from frappe.model.document import Document
|
|||
from frappe.model import no_value_fields, core_doctypes_list
|
||||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype, check_email_append_to
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.custom.doctype.property_setter.property_setter import delete_property_setter
|
||||
from frappe.model.docfield import supports_translation
|
||||
|
||||
doctype_properties = {
|
||||
'search_fields': 'Data',
|
||||
'title_field': 'Data',
|
||||
'image_field': 'Data',
|
||||
'sort_field': 'Data',
|
||||
'sort_order': 'Data',
|
||||
'default_print_format': 'Data',
|
||||
'allow_copy': 'Check',
|
||||
'istable': 'Check',
|
||||
'quick_entry': 'Check',
|
||||
'editable_grid': 'Check',
|
||||
'max_attachments': 'Int',
|
||||
'track_changes': 'Check',
|
||||
'track_views': 'Check',
|
||||
'allow_auto_repeat': 'Check',
|
||||
'allow_import': 'Check',
|
||||
'show_preview_popup': 'Check',
|
||||
'email_append_to': 'Check',
|
||||
'subject_field': 'Data',
|
||||
'sender_field': 'Data'
|
||||
}
|
||||
|
||||
docfield_properties = {
|
||||
'idx': 'Int',
|
||||
'label': 'Data',
|
||||
'fieldtype': 'Select',
|
||||
'options': 'Text',
|
||||
'fetch_from': 'Small Text',
|
||||
'fetch_if_empty': 'Check',
|
||||
'permlevel': 'Int',
|
||||
'width': 'Data',
|
||||
'print_width': 'Data',
|
||||
'reqd': 'Check',
|
||||
'unique': 'Check',
|
||||
'ignore_user_permissions': 'Check',
|
||||
'in_list_view': 'Check',
|
||||
'in_standard_filter': 'Check',
|
||||
'in_global_search': 'Check',
|
||||
'in_preview': 'Check',
|
||||
'bold': 'Check',
|
||||
'hidden': 'Check',
|
||||
'collapsible': 'Check',
|
||||
'collapsible_depends_on': 'Data',
|
||||
'print_hide': 'Check',
|
||||
'print_hide_if_no_value': 'Check',
|
||||
'report_hide': 'Check',
|
||||
'allow_on_submit': 'Check',
|
||||
'translatable': 'Check',
|
||||
'mandatory_depends_on': 'Data',
|
||||
'read_only_depends_on': 'Data',
|
||||
'depends_on': 'Data',
|
||||
'description': 'Text',
|
||||
'default': 'Text',
|
||||
'precision': 'Select',
|
||||
'read_only': 'Check',
|
||||
'length': 'Int',
|
||||
'columns': 'Int',
|
||||
'remember_last_selected_value': 'Check',
|
||||
'allow_bulk_edit': 'Check',
|
||||
'auto_repeat': 'Link',
|
||||
'allow_in_quick_entry': 'Check',
|
||||
'hide_border': 'Check',
|
||||
'hide_days': 'Check',
|
||||
'hide_seconds': 'Check'
|
||||
}
|
||||
|
||||
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
|
||||
('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature', 'HTML Editor'), ('Data', 'Select'),
|
||||
('Text', 'Small Text'), ('Text', 'Data', 'Barcode'), ('Code', 'Geolocation'), ('Table', 'Table MultiSelect'))
|
||||
|
||||
allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select', 'Data')
|
||||
|
||||
class CustomizeForm(Document):
|
||||
def on_update(self):
|
||||
frappe.db.sql("delete from tabSingles where doctype='Customize Form'")
|
||||
|
|
@ -100,37 +30,64 @@ class CustomizeForm(Document):
|
|||
|
||||
meta = frappe.get_meta(self.doc_type)
|
||||
|
||||
if self.doc_type in core_doctypes_list:
|
||||
return frappe.msgprint(_("Core DocTypes cannot be customized."))
|
||||
self.validate_doctype(meta)
|
||||
|
||||
if meta.issingle:
|
||||
return frappe.msgprint(_("Single DocTypes cannot be customized."))
|
||||
|
||||
if meta.custom:
|
||||
return frappe.msgprint(_("Only standard DocTypes are allowed to be customized from Customize Form."))
|
||||
|
||||
# doctype properties
|
||||
for property in doctype_properties:
|
||||
self.set(property, meta.get(property))
|
||||
|
||||
for d in meta.get("fields"):
|
||||
new_d = {"fieldname": d.fieldname, "is_custom_field": d.get("is_custom_field"), "name": d.name}
|
||||
for property in docfield_properties:
|
||||
new_d[property] = d.get(property)
|
||||
self.append("fields", new_d)
|
||||
# load the meta properties on the customize (self) object
|
||||
self.load_properties(meta)
|
||||
|
||||
# load custom translation
|
||||
translation = self.get_name_translation()
|
||||
self.label = translation.translated_text if translation else ''
|
||||
|
||||
#If allow_auto_repeat is set, add auto_repeat custom field.
|
||||
self.create_auto_repeat_custom_field_if_requried(meta)
|
||||
|
||||
# NOTE doc (self) is sent to clientside by run_method
|
||||
|
||||
def validate_doctype(self, meta):
|
||||
'''
|
||||
Check if the doctype is allowed to be customized.
|
||||
'''
|
||||
if self.doc_type in core_doctypes_list:
|
||||
frappe.throw(_("Core DocTypes cannot be customized."))
|
||||
|
||||
if meta.issingle:
|
||||
frappe.throw(_("Single DocTypes cannot be customized."))
|
||||
|
||||
if meta.custom:
|
||||
frappe.throw(_("Only standard DocTypes are allowed to be customized from Customize Form."))
|
||||
|
||||
def load_properties(self, meta):
|
||||
'''
|
||||
Load the customize object (this) with the metadata properties
|
||||
'''
|
||||
# doctype properties
|
||||
for prop in doctype_properties:
|
||||
self.set(prop, meta.get(prop))
|
||||
|
||||
for d in meta.get("fields"):
|
||||
new_d = {"fieldname": d.fieldname, "is_custom_field": d.get("is_custom_field"), "name": d.name}
|
||||
for prop in docfield_properties:
|
||||
new_d[prop] = d.get(prop)
|
||||
self.append("fields", new_d)
|
||||
|
||||
for fieldname in ('links', 'actions'):
|
||||
for d in meta.get(fieldname):
|
||||
self.append(fieldname, d)
|
||||
|
||||
def create_auto_repeat_custom_field_if_requried(self, meta):
|
||||
if self.allow_auto_repeat:
|
||||
if not frappe.db.exists('Custom Field', {'fieldname': 'auto_repeat', 'dt': self.doc_type}):
|
||||
if not frappe.db.exists('Custom Field', {'fieldname': 'auto_repeat',
|
||||
'dt': self.doc_type}):
|
||||
insert_after = self.fields[len(self.fields) - 1].fieldname
|
||||
df = dict(fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', options='Auto Repeat', insert_after=insert_after, read_only=1, no_copy=1, print_hide=1)
|
||||
df = dict(
|
||||
fieldname='auto_repeat',
|
||||
label='Auto Repeat',
|
||||
fieldtype='Link',
|
||||
options='Auto Repeat',
|
||||
insert_after=insert_after,
|
||||
read_only=1, no_copy=1, print_hide=1)
|
||||
create_custom_field(self.doc_type, df)
|
||||
|
||||
# NOTE doc is sent to clientside by run_method
|
||||
|
||||
def get_name_translation(self):
|
||||
'''Get translation object if exists of current doctype name in the default language'''
|
||||
|
|
@ -195,72 +152,142 @@ class CustomizeForm(Document):
|
|||
|
||||
def set_property_setters(self):
|
||||
meta = frappe.get_meta(self.doc_type)
|
||||
# doctype property setters
|
||||
|
||||
for property in doctype_properties:
|
||||
if self.get(property) != meta.get(property):
|
||||
self.make_property_setter(property=property, value=self.get(property),
|
||||
property_type=doctype_properties[property])
|
||||
# doctype
|
||||
self.set_property_setters_for_doctype(meta)
|
||||
|
||||
# docfield
|
||||
for df in self.get("fields"):
|
||||
meta_df = meta.get("fields", {"fieldname": df.fieldname})
|
||||
|
||||
if not meta_df or meta_df[0].get("is_custom_field"):
|
||||
continue
|
||||
self.set_property_setters_for_docfield(meta, df, meta_df)
|
||||
|
||||
for property in docfield_properties:
|
||||
if property != "idx" and (df.get(property) or '') != (meta_df[0].get(property) or ''):
|
||||
if property == "fieldtype":
|
||||
self.validate_fieldtype_change(df, meta_df[0].get(property), df.get(property))
|
||||
# action and links
|
||||
self.set_property_setters_for_actions_and_links(meta)
|
||||
|
||||
elif property == "allow_on_submit" and df.get(property):
|
||||
if not frappe.db.get_value("DocField",
|
||||
{"parent": self.doc_type, "fieldname": df.fieldname}, "allow_on_submit"):
|
||||
frappe.msgprint(_("Row {0}: Not allowed to enable Allow on Submit for standard fields")\
|
||||
.format(df.idx))
|
||||
continue
|
||||
def set_property_setters_for_doctype(self, meta):
|
||||
for prop, prop_type in doctype_properties.items():
|
||||
if self.get(prop) != meta.get(prop):
|
||||
self.make_property_setter(prop, self.get(prop), prop_type)
|
||||
|
||||
elif property == "reqd" and \
|
||||
((frappe.db.get_value("DocField",
|
||||
{"parent":self.doc_type,"fieldname":df.fieldname}, "reqd") == 1) \
|
||||
and (df.get(property) == 0)):
|
||||
frappe.msgprint(_("Row {0}: Not allowed to disable Mandatory for standard fields")\
|
||||
.format(df.idx))
|
||||
continue
|
||||
def set_property_setters_for_docfield(self, meta, df, meta_df):
|
||||
for prop, prop_type in docfield_properties.items():
|
||||
if prop != "idx" and (df.get(prop) or '') != (meta_df[0].get(prop) or ''):
|
||||
if not self.allow_property_change(prop, meta_df, df):
|
||||
continue
|
||||
|
||||
elif property == "in_list_view" and df.get(property) \
|
||||
and df.fieldtype!="Attach Image" and df.fieldtype in no_value_fields:
|
||||
frappe.msgprint(_("'In List View' not allowed for type {0} in row {1}")
|
||||
.format(df.fieldtype, df.idx))
|
||||
continue
|
||||
self.make_property_setter(prop, df.get(prop), prop_type,
|
||||
fieldname=df.fieldname)
|
||||
|
||||
elif property == "precision" and cint(df.get("precision")) > 6 \
|
||||
and cint(df.get("precision")) > cint(meta_df[0].get("precision")):
|
||||
self.flags.update_db = True
|
||||
def allow_property_change(self, prop, meta_df, df):
|
||||
if prop == "fieldtype":
|
||||
self.validate_fieldtype_change(df, meta_df[0].get(prop), df.get(prop))
|
||||
|
||||
elif property == "unique":
|
||||
self.flags.update_db = True
|
||||
elif prop == "allow_on_submit" and df.get(prop):
|
||||
if not frappe.db.get_value("DocField",
|
||||
{"parent": self.doc_type, "fieldname": df.fieldname}, "allow_on_submit"):
|
||||
frappe.msgprint(_("Row {0}: Not allowed to enable Allow on Submit for standard fields")\
|
||||
.format(df.idx))
|
||||
return False
|
||||
|
||||
elif (property == "read_only" and cint(df.get("read_only"))==0
|
||||
and frappe.db.get_value("DocField", {"parent": self.doc_type, "fieldname": df.fieldname}, "read_only")==1):
|
||||
# if docfield has read_only checked and user is trying to make it editable, don't allow it
|
||||
frappe.msgprint(_("You cannot unset 'Read Only' for field {0}").format(df.label))
|
||||
continue
|
||||
elif prop == "reqd" and \
|
||||
((frappe.db.get_value("DocField",
|
||||
{"parent":self.doc_type,"fieldname":df.fieldname}, "reqd") == 1) \
|
||||
and (df.get(prop) == 0)):
|
||||
frappe.msgprint(_("Row {0}: Not allowed to disable Mandatory for standard fields")\
|
||||
.format(df.idx))
|
||||
return False
|
||||
|
||||
elif property == "options" and df.get("fieldtype") not in allowed_fieldtype_for_options_change:
|
||||
frappe.msgprint(_("You can't set 'Options' for field {0}").format(df.label))
|
||||
continue
|
||||
elif prop == "in_list_view" and df.get(prop) \
|
||||
and df.fieldtype!="Attach Image" and df.fieldtype in no_value_fields:
|
||||
frappe.msgprint(_("'In List View' not allowed for type {0} in row {1}")
|
||||
.format(df.fieldtype, df.idx))
|
||||
return False
|
||||
|
||||
elif property == 'translatable' and not supports_translation(df.get('fieldtype')):
|
||||
frappe.msgprint(_("You can't set 'Translatable' for field {0}").format(df.label))
|
||||
continue
|
||||
elif prop == "precision" and cint(df.get("precision")) > 6 \
|
||||
and cint(df.get("precision")) > cint(meta_df[0].get("precision")):
|
||||
self.flags.update_db = True
|
||||
|
||||
elif (property == 'in_global_search' and
|
||||
df.in_global_search != meta_df[0].get("in_global_search")):
|
||||
self.flags.rebuild_doctype_for_global_search = True
|
||||
elif prop == "unique":
|
||||
self.flags.update_db = True
|
||||
|
||||
self.make_property_setter(property=property, value=df.get(property),
|
||||
property_type=docfield_properties[property], fieldname=df.fieldname)
|
||||
elif (prop == "read_only" and cint(df.get("read_only"))==0
|
||||
and frappe.db.get_value("DocField", {"parent": self.doc_type,
|
||||
"fieldname": df.fieldname}, "read_only")==1):
|
||||
# if docfield has read_only checked and user is trying to make it editable, don't allow it
|
||||
frappe.msgprint(_("You cannot unset 'Read Only' for field {0}").format(df.label))
|
||||
return False
|
||||
|
||||
elif prop == "options" and df.get("fieldtype") not in ALLOWED_OPTIONS_CHANGE:
|
||||
frappe.msgprint(_("You can't set 'Options' for field {0}").format(df.label))
|
||||
return False
|
||||
|
||||
elif prop == 'translatable' and not supports_translation(df.get('fieldtype')):
|
||||
frappe.msgprint(_("You can't set 'Translatable' for field {0}").format(df.label))
|
||||
return False
|
||||
|
||||
elif (prop == 'in_global_search' and
|
||||
df.in_global_search != meta_df[0].get("in_global_search")):
|
||||
self.flags.rebuild_doctype_for_global_search = True
|
||||
|
||||
return True
|
||||
|
||||
def set_property_setters_for_actions_and_links(self, meta):
|
||||
'''
|
||||
Apply property setters or create custom records for DocType Action and DocType Link
|
||||
'''
|
||||
for doctype, fieldname, field_map in (
|
||||
('DocType Link', 'links', doctype_link_properties),
|
||||
('DocType Action', 'actions', doctype_action_properties)
|
||||
):
|
||||
has_custom = False
|
||||
items = []
|
||||
for i, d in enumerate(self.get(fieldname) or []):
|
||||
d.idx = i
|
||||
if frappe.db.exists(doctype, d.name) and not d.custom:
|
||||
# check property and apply property setter
|
||||
original = frappe.get_doc(doctype, d.name)
|
||||
for prop, prop_type in field_map.items():
|
||||
if d.get(prop) != original.get(prop):
|
||||
self.make_property_setter(prop, d.get(prop), prop_type,
|
||||
apply_on=doctype, row_name=d.name)
|
||||
items.append(d.name)
|
||||
else:
|
||||
# custom - just insert/update
|
||||
d.parent = self.doc_type
|
||||
d.custom = 1
|
||||
d.save(ignore_permissions=True)
|
||||
has_custom = True
|
||||
items.append(d.name)
|
||||
|
||||
self.update_order_property_setter(has_custom, fieldname)
|
||||
self.clear_removed_items(doctype, items)
|
||||
|
||||
def update_order_property_setter(self, has_custom, fieldname):
|
||||
'''
|
||||
We need to maintain the order of the link/actions if the user has shuffled them.
|
||||
So we create a new property (ex `links_order`) to keep a list of items.
|
||||
'''
|
||||
property_name = '{}_order'.format(fieldname)
|
||||
if has_custom:
|
||||
# save the order of the actions and links
|
||||
self.make_property_setter(property_name,
|
||||
json.dumps([d.name for d in self.get(fieldname)]), 'Small Text')
|
||||
else:
|
||||
frappe.db.delete('Property Setter', dict(property=property_name,
|
||||
doc_type=self.doc_type))
|
||||
|
||||
|
||||
def clear_removed_items(self, doctype, items):
|
||||
'''
|
||||
Clear rows that do not appear in `items`. These have been removed by the user.
|
||||
'''
|
||||
if items:
|
||||
frappe.db.delete(doctype, dict(parent=self.doc_type, custom=1,
|
||||
name=('not in', items)))
|
||||
else:
|
||||
frappe.db.delete(doctype, dict(parent=self.doc_type, custom=1))
|
||||
|
||||
def update_custom_fields(self):
|
||||
for i, df in enumerate(self.get("fields")):
|
||||
|
|
@ -278,8 +305,8 @@ class CustomizeForm(Document):
|
|||
|
||||
d.dt = self.doc_type
|
||||
|
||||
for property in docfield_properties:
|
||||
d.set(property, df.get(property))
|
||||
for prop in docfield_properties:
|
||||
d.set(prop, df.get(prop))
|
||||
|
||||
if i!=0:
|
||||
d.insert_after = self.fields[i-1].fieldname
|
||||
|
|
@ -297,12 +324,12 @@ class CustomizeForm(Document):
|
|||
|
||||
custom_field = frappe.get_doc("Custom Field", meta_df[0].name)
|
||||
changed = False
|
||||
for property in docfield_properties:
|
||||
if df.get(property) != custom_field.get(property):
|
||||
if property == "fieldtype":
|
||||
self.validate_fieldtype_change(df, meta_df[0].get(property), df.get(property))
|
||||
for prop in docfield_properties:
|
||||
if df.get(prop) != custom_field.get(prop):
|
||||
if prop == "fieldtype":
|
||||
self.validate_fieldtype_change(df, meta_df[0].get(prop), df.get(prop))
|
||||
|
||||
custom_field.set(property, df.get(property))
|
||||
custom_field.set(prop, df.get(prop))
|
||||
changed = True
|
||||
|
||||
# check and update `insert_after` property
|
||||
|
|
@ -328,32 +355,28 @@ class CustomizeForm(Document):
|
|||
if df.get("is_custom_field"):
|
||||
frappe.delete_doc("Custom Field", df.name)
|
||||
|
||||
def make_property_setter(self, property, value, property_type, fieldname=None):
|
||||
self.delete_existing_property_setter(property, fieldname)
|
||||
def make_property_setter(self, prop, value, property_type, fieldname=None,
|
||||
apply_on=None, row_name = None):
|
||||
delete_property_setter(self.doc_type, prop, fieldname)
|
||||
|
||||
property_value = self.get_existing_property_value(property, fieldname)
|
||||
property_value = self.get_existing_property_value(prop, fieldname)
|
||||
|
||||
if property_value==value:
|
||||
return
|
||||
|
||||
if not apply_on:
|
||||
apply_on = "DocField" if fieldname else "DocType"
|
||||
|
||||
# create a new property setter
|
||||
# ignore validation becuase it will be done at end
|
||||
frappe.make_property_setter({
|
||||
"doctype": self.doc_type,
|
||||
"doctype_or_field": "DocField" if fieldname else "DocType",
|
||||
"doctype_or_field": apply_on,
|
||||
"fieldname": fieldname,
|
||||
"property": property,
|
||||
"row_name": row_name,
|
||||
"property": prop,
|
||||
"value": value,
|
||||
"property_type": property_type
|
||||
}, ignore_validate=True)
|
||||
|
||||
def delete_existing_property_setter(self, property, fieldname=None):
|
||||
# first delete existing property setter
|
||||
existing_property_setter = frappe.db.get_value("Property Setter", {"doc_type": self.doc_type,
|
||||
"property": property, "field_name['']": fieldname or ''})
|
||||
|
||||
if existing_property_setter:
|
||||
frappe.db.sql("delete from `tabProperty Setter` where name=%s", existing_property_setter)
|
||||
})
|
||||
|
||||
def get_existing_property_value(self, property_name, fieldname=None):
|
||||
# check if there is any need to make property setter!
|
||||
|
|
@ -361,20 +384,17 @@ class CustomizeForm(Document):
|
|||
property_value = frappe.db.get_value("DocField", {"parent": self.doc_type,
|
||||
"fieldname": fieldname}, property_name)
|
||||
else:
|
||||
try:
|
||||
if frappe.db.has_column("DocType", property_name):
|
||||
property_value = frappe.db.get_value("DocType", self.doc_type, property_name)
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e):
|
||||
property_value = None
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
property_value = None
|
||||
|
||||
return property_value
|
||||
|
||||
def validate_fieldtype_change(self, df, old_value, new_value):
|
||||
allowed = False
|
||||
self.check_length_for_fieldtypes = []
|
||||
for allowed_changes in allowed_fieldtype_change:
|
||||
for allowed_changes in ALLOWED_FIELDTYPE_CHANGE:
|
||||
if (old_value in allowed_changes and new_value in allowed_changes):
|
||||
allowed = True
|
||||
old_value_length = cint(frappe.db.type_map.get(old_value)[1])
|
||||
|
|
@ -425,8 +445,109 @@ class CustomizeForm(Document):
|
|||
if not self.doc_type:
|
||||
return
|
||||
|
||||
frappe.db.sql("""DELETE FROM `tabProperty Setter` WHERE doc_type=%s
|
||||
and `field_name`!='naming_series'
|
||||
and `property`!='options'""", self.doc_type)
|
||||
frappe.clear_cache(doctype=self.doc_type)
|
||||
reset_customization(self.doc_type)
|
||||
self.fetch_to_customize()
|
||||
|
||||
def reset_customization(doctype):
|
||||
frappe.db.sql("""
|
||||
DELETE FROM `tabProperty Setter` WHERE doc_type=%s
|
||||
and `field_name`!='naming_series'
|
||||
and `property`!='options'
|
||||
""", doctype)
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
|
||||
doctype_properties = {
|
||||
'search_fields': 'Data',
|
||||
'title_field': 'Data',
|
||||
'image_field': 'Data',
|
||||
'sort_field': 'Data',
|
||||
'sort_order': 'Data',
|
||||
'default_print_format': 'Data',
|
||||
'allow_copy': 'Check',
|
||||
'istable': 'Check',
|
||||
'quick_entry': 'Check',
|
||||
'editable_grid': 'Check',
|
||||
'max_attachments': 'Int',
|
||||
'track_changes': 'Check',
|
||||
'track_views': 'Check',
|
||||
'allow_auto_repeat': 'Check',
|
||||
'allow_import': 'Check',
|
||||
'show_preview_popup': 'Check',
|
||||
'email_append_to': 'Check',
|
||||
'subject_field': 'Data',
|
||||
'sender_field': 'Data'
|
||||
}
|
||||
|
||||
docfield_properties = {
|
||||
'idx': 'Int',
|
||||
'label': 'Data',
|
||||
'fieldtype': 'Select',
|
||||
'options': 'Text',
|
||||
'fetch_from': 'Small Text',
|
||||
'fetch_if_empty': 'Check',
|
||||
'permlevel': 'Int',
|
||||
'width': 'Data',
|
||||
'print_width': 'Data',
|
||||
'non_negative': 'Check',
|
||||
'reqd': 'Check',
|
||||
'unique': 'Check',
|
||||
'ignore_user_permissions': 'Check',
|
||||
'in_list_view': 'Check',
|
||||
'in_standard_filter': 'Check',
|
||||
'in_global_search': 'Check',
|
||||
'in_preview': 'Check',
|
||||
'bold': 'Check',
|
||||
'hidden': 'Check',
|
||||
'collapsible': 'Check',
|
||||
'collapsible_depends_on': 'Data',
|
||||
'print_hide': 'Check',
|
||||
'print_hide_if_no_value': 'Check',
|
||||
'report_hide': 'Check',
|
||||
'allow_on_submit': 'Check',
|
||||
'translatable': 'Check',
|
||||
'mandatory_depends_on': 'Data',
|
||||
'read_only_depends_on': 'Data',
|
||||
'depends_on': 'Data',
|
||||
'description': 'Text',
|
||||
'default': 'Text',
|
||||
'precision': 'Select',
|
||||
'read_only': 'Check',
|
||||
'length': 'Int',
|
||||
'columns': 'Int',
|
||||
'remember_last_selected_value': 'Check',
|
||||
'allow_bulk_edit': 'Check',
|
||||
'auto_repeat': 'Link',
|
||||
'allow_in_quick_entry': 'Check',
|
||||
'hide_border': 'Check',
|
||||
'hide_days': 'Check',
|
||||
'hide_seconds': 'Check'
|
||||
}
|
||||
|
||||
doctype_link_properties = {
|
||||
'link_doctype': 'Link',
|
||||
'link_fieldname': 'Data',
|
||||
'group': 'Data',
|
||||
'hidden': 'Check'
|
||||
}
|
||||
|
||||
doctype_action_properties = {
|
||||
'label': 'Link',
|
||||
'action_type': 'Select',
|
||||
'action': 'Small Text',
|
||||
'group': 'Data',
|
||||
'hidden': 'Check'
|
||||
}
|
||||
|
||||
|
||||
ALLOWED_FIELDTYPE_CHANGE = (
|
||||
('Currency', 'Float', 'Percent'),
|
||||
('Small Text', 'Data'),
|
||||
('Text', 'Data'),
|
||||
('Text', 'Text Editor', 'Code', 'Signature', 'HTML Editor'),
|
||||
('Data', 'Select'),
|
||||
('Text', 'Small Text'),
|
||||
('Text', 'Data', 'Barcode'),
|
||||
('Code', 'Geolocation'),
|
||||
('Table', 'Table MultiSelect'))
|
||||
|
||||
ALLOWED_OPTIONS_CHANGE = ('Read Only', 'HTML', 'Select', 'Data')
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
|||
import frappe, unittest, json
|
||||
from frappe.test_runner import make_test_records_for_doctype
|
||||
from frappe.core.doctype.doctype.doctype import InvalidFieldNameError
|
||||
from frappe.core.doctype.doctype.test_doctype import new_doctype
|
||||
|
||||
test_dependencies = ["Custom Field", "Property Setter"]
|
||||
class TestCustomizeForm(unittest.TestCase):
|
||||
|
|
@ -24,6 +25,7 @@ class TestCustomizeForm(unittest.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.insert_custom_field()
|
||||
frappe.db.delete('Property Setter', dict(doc_type='Event'))
|
||||
frappe.db.commit()
|
||||
frappe.clear_cache(doctype="Event")
|
||||
|
||||
|
|
@ -185,9 +187,75 @@ class TestCustomizeForm(unittest.TestCase):
|
|||
d.run_method("save_customization")
|
||||
|
||||
def test_core_doctype_customization(self):
|
||||
d = self.get_customize_form('User')
|
||||
e = self.get_customize_form('Custom Field')
|
||||
self.assertRaises(frappe.ValidationError, self.get_customize_form, 'User')
|
||||
|
||||
# core doctype is invalid, hence no attributes are set
|
||||
self.assertEquals(d.get("fields"), [])
|
||||
self.assertEquals(e.get("fields"), [])
|
||||
def test_custom_link(self):
|
||||
try:
|
||||
# create a dummy doctype linked to Event
|
||||
testdt_name = 'Test Link for Event'
|
||||
testdt = new_doctype(testdt_name, fields=[
|
||||
dict(fieldtype='Link', fieldname='event', options='Event')
|
||||
]).insert()
|
||||
|
||||
testdt_name1 = 'Test Link for Event 1'
|
||||
testdt1 = new_doctype(testdt_name1, fields=[
|
||||
dict(fieldtype='Link', fieldname='event', options='Event')
|
||||
]).insert()
|
||||
|
||||
# add a custom link
|
||||
d = self.get_customize_form("Event")
|
||||
|
||||
d.append('links', dict(link_doctype=testdt_name, link_fieldname='event', group='Tests'))
|
||||
d.append('links', dict(link_doctype=testdt_name1, link_fieldname='event', group='Tests'))
|
||||
|
||||
d.run_method("save_customization")
|
||||
|
||||
frappe.clear_cache()
|
||||
event = frappe.get_meta('Event')
|
||||
|
||||
# check links exist
|
||||
self.assertTrue([d.name for d in event.links if d.link_doctype == testdt_name])
|
||||
self.assertTrue([d.name for d in event.links if d.link_doctype == testdt_name1])
|
||||
|
||||
# check order
|
||||
order = json.loads(event.links_order)
|
||||
self.assertListEqual(order, [d.name for d in event.links])
|
||||
|
||||
# remove the link
|
||||
d = self.get_customize_form("Event")
|
||||
d.links = []
|
||||
d.run_method("save_customization")
|
||||
|
||||
frappe.clear_cache()
|
||||
event = frappe.get_meta('Event')
|
||||
self.assertFalse([d.name for d in (event.links or []) if d.link_doctype == testdt_name])
|
||||
finally:
|
||||
testdt.delete()
|
||||
testdt1.delete()
|
||||
|
||||
def test_custom_action(self):
|
||||
test_route = '#List/DocType'
|
||||
|
||||
# create a dummy action (route)
|
||||
d = self.get_customize_form("Event")
|
||||
d.append('actions', dict(label='Test Action', action_type='Route', action=test_route))
|
||||
d.run_method("save_customization")
|
||||
|
||||
frappe.clear_cache()
|
||||
event = frappe.get_meta('Event')
|
||||
|
||||
# check if added to meta
|
||||
action = [d for d in event.actions if d.label=='Test Action']
|
||||
self.assertEqual(len(action), 1)
|
||||
self.assertEqual(action[0].action, test_route)
|
||||
|
||||
# clear the action
|
||||
d = self.get_customize_form("Event")
|
||||
d.actions = []
|
||||
d.run_method("save_customization")
|
||||
|
||||
frappe.clear_cache()
|
||||
event = frappe.get_meta('Event')
|
||||
|
||||
action = [d for d in event.actions if d.label=='Test Action']
|
||||
self.assertEqual(len(action), 0)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@
|
|||
"label",
|
||||
"fieldtype",
|
||||
"fieldname",
|
||||
"hide_seconds",
|
||||
"hide_days",
|
||||
"non_negative",
|
||||
"reqd",
|
||||
"unique",
|
||||
"in_list_view",
|
||||
|
|
@ -23,6 +22,7 @@
|
|||
"allow_in_quick_entry",
|
||||
"translatable",
|
||||
"column_break_7",
|
||||
"default",
|
||||
"precision",
|
||||
"length",
|
||||
"options",
|
||||
|
|
@ -47,8 +47,9 @@
|
|||
"column_break_33",
|
||||
"read_only_depends_on",
|
||||
"display",
|
||||
"default",
|
||||
"in_filter",
|
||||
"hide_seconds",
|
||||
"hide_days",
|
||||
"column_break_21",
|
||||
"description",
|
||||
"print_hide",
|
||||
|
|
@ -100,6 +101,7 @@
|
|||
"depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Mandatory",
|
||||
"oldfieldname": "reqd",
|
||||
"oldfieldtype": "Check",
|
||||
|
|
@ -283,7 +285,7 @@
|
|||
},
|
||||
{
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Text",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Default",
|
||||
"oldfieldname": "default",
|
||||
"oldfieldtype": "Text"
|
||||
|
|
@ -413,13 +415,20 @@
|
|||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)",
|
||||
"fieldname": "non_negative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Non Negative"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-28 11:28:59.084060",
|
||||
"modified": "2020-10-29 06:11:57.661039",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -1,358 +1,133 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-01-10 16:34:04",
|
||||
"custom": 0,
|
||||
"description": "Property Setter overrides a standard DocType or Field property",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2013-01-10 16:34:04",
|
||||
"description": "Property Setter overrides a standard DocType or Field property",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"help",
|
||||
"sb0",
|
||||
"doctype_or_field",
|
||||
"doc_type",
|
||||
"field_name",
|
||||
"row_name",
|
||||
"column_break0",
|
||||
"property",
|
||||
"property_type",
|
||||
"value",
|
||||
"default_value"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "help",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Help",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "<div class=\"alert\">Please don't update it as it can mess up your form. Use the Customize Form View and Custom Fields to set properties!</div>",
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Help",
|
||||
"options": "<div class=\"alert\">Please don't update it as it can mess up your form. Use the Customize Form View and Custom Fields to set properties!</div>"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "sb0",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "sb0",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.__islocal",
|
||||
"fieldname": "doctype_or_field",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "DocType or Field",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nDocField\nDocType",
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "doctype_or_field",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Applied On",
|
||||
"options": "\nDocField\nDocType\nDocType Link\nDocType Action",
|
||||
"read_only_depends_on": "eval:!doc.__islocal",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "New value to be set",
|
||||
"fieldname": "value",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Set Value",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"description": "New value to be set",
|
||||
"fieldname": "value",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Set Value"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break0",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "column_break0",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "doc_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "DocType",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "doc_type",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.doctype_or_field=='DocField'",
|
||||
"description": "ID (name) of the entity whose property is to be set",
|
||||
"fieldname": "field_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Field Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"depends_on": "eval:doc.doctype_or_field=='DocField'",
|
||||
"description": "ID (name) of the entity whose property is to be set",
|
||||
"fieldname": "field_name",
|
||||
"fieldtype": "Data",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Field Name",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "property",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Property",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "property",
|
||||
"fieldtype": "Data",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Property",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "property_type",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Property Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "property_type",
|
||||
"fieldtype": "Data",
|
||||
"label": "Property Type"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "default_value",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Default Value",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
"fieldname": "default_value",
|
||||
"fieldtype": "Data",
|
||||
"label": "Default Value"
|
||||
},
|
||||
{
|
||||
"description": "For DocType Link / DocType Action",
|
||||
"fieldname": "row_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Row Name"
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-12-29 14:39:50.172883",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Property Setter",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-24 14:42:38.599684",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Property Setter",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 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": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "doc_type,property",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"search_fields": "doc_type,property",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -11,13 +11,16 @@ not_allowed_fieldtype_change = ['naming_series']
|
|||
|
||||
class PropertySetter(Document):
|
||||
def autoname(self):
|
||||
self.name = self.doc_type + "-" \
|
||||
+ (self.field_name and (self.field_name + "-") or "") \
|
||||
+ self.property
|
||||
self.name = '{doctype}-{field}-{property}'.format(
|
||||
doctype = self.doc_type,
|
||||
field = self.field_name or self.row_name or 'main',
|
||||
property = self.property
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.validate_fieldtype_change()
|
||||
self.delete_property_setter()
|
||||
if self.is_new():
|
||||
delete_property_setter(self.doc_type, self.property, self.field_name)
|
||||
|
||||
# clear cache
|
||||
frappe.clear_cache(doctype = self.doc_type)
|
||||
|
|
@ -27,15 +30,6 @@ class PropertySetter(Document):
|
|||
self.property == 'fieldtype':
|
||||
frappe.throw(_("Field type cannot be changed for {0}").format(self.field_name))
|
||||
|
||||
def delete_property_setter(self):
|
||||
"""delete other property setters on this, if this is new"""
|
||||
if self.get('__islocal'):
|
||||
frappe.db.sql("""delete from `tabProperty Setter` where
|
||||
doctype_or_field = %(doctype_or_field)s
|
||||
and doc_type = %(doc_type)s
|
||||
and coalesce(field_name,'') = coalesce(%(field_name)s, '')
|
||||
and property = %(property)s""", self.get_valid_dict())
|
||||
|
||||
def get_property_list(self, dt):
|
||||
return frappe.db.get_all('DocField',
|
||||
fields=['fieldname', 'label', 'fieldtype'],
|
||||
|
|
@ -89,3 +83,12 @@ def make_property_setter(doctype, fieldname, property, value, property_type, for
|
|||
property_setter.flags.validate_fields_for_doctype = validate_fields_for_doctype
|
||||
property_setter.insert()
|
||||
return property_setter
|
||||
|
||||
def delete_property_setter(doc_type, property, field_name=None):
|
||||
"""delete other property setters on this, if this is new"""
|
||||
filters = dict(doc_type = doc_type, property=property)
|
||||
if field_name:
|
||||
filters['field_name'] = field_name
|
||||
|
||||
frappe.db.delete('Property Setter', filters)
|
||||
|
||||
|
|
|
|||
|
|
@ -319,8 +319,7 @@ class Database(object):
|
|||
nres.append(nr)
|
||||
return nres
|
||||
|
||||
@staticmethod
|
||||
def build_conditions(filters):
|
||||
def build_conditions(self, filters):
|
||||
"""Convert filters sent as dict, lists to SQL conditions. filter's key
|
||||
is passed by map function, build conditions like:
|
||||
|
||||
|
|
@ -341,18 +340,12 @@ class Database(object):
|
|||
value = filters.get(key)
|
||||
values[key] = value
|
||||
if isinstance(value, (list, tuple)):
|
||||
# value is a tuble like ("!=", 0)
|
||||
# value is a tuple like ("!=", 0)
|
||||
_operator = value[0]
|
||||
values[key] = value[1]
|
||||
if isinstance(value[1], (tuple, list)):
|
||||
# value is a list in tuple ("in", ("A", "B"))
|
||||
inner_list = []
|
||||
for i, v in enumerate(value[1]):
|
||||
inner_key = "{0}_{1}".format(key, i)
|
||||
values[inner_key] = v
|
||||
inner_list.append("%({0})s".format(inner_key))
|
||||
|
||||
_rhs = " ({0})".format(", ".join(inner_list))
|
||||
_rhs = " ({0})".format(", ".join([self.escape(v) for v in value[1]]))
|
||||
del values[key]
|
||||
|
||||
if _operator not in ["=", "!=", ">", ">=", "<", "<=", "like", "in", "not in", "not like"]:
|
||||
|
|
@ -787,6 +780,9 @@ class Database(object):
|
|||
"""Returns True if table for given doctype exists."""
|
||||
return ("tab" + doctype) in self.get_tables()
|
||||
|
||||
def has_table(self, doctype):
|
||||
return self.table_exists(doctype)
|
||||
|
||||
def get_tables(self):
|
||||
tables = frappe.cache().get_value('db_tables')
|
||||
if not tables:
|
||||
|
|
@ -959,13 +955,13 @@ class Database(object):
|
|||
query = sql_dict.get(current_dialect)
|
||||
return self.sql(query, values, **kwargs)
|
||||
|
||||
def delete(self, doctype, conditions):
|
||||
def delete(self, doctype, conditions, debug=False):
|
||||
if conditions:
|
||||
conditions, values = self.build_conditions(conditions)
|
||||
return self.sql("DELETE FROM `tab{doctype}` where {conditions}".format(
|
||||
doctype=doctype,
|
||||
conditions=conditions
|
||||
), values)
|
||||
), values, debug=debug)
|
||||
else:
|
||||
frappe.throw(_('No conditions provided'))
|
||||
|
||||
|
|
|
|||
|
|
@ -140,11 +140,11 @@ class PostgresDatabase(Database):
|
|||
|
||||
@staticmethod
|
||||
def is_table_missing(e):
|
||||
return e.pgcode == '42P01'
|
||||
return getattr(e, 'pgcode', None) == '42P01'
|
||||
|
||||
@staticmethod
|
||||
def is_missing_column(e):
|
||||
return e.pgcode == '42703'
|
||||
return getattr(e, 'pgcode', None) == '42703'
|
||||
|
||||
@staticmethod
|
||||
def is_access_denied(e):
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ class DbColumn:
|
|||
column_def += ' not null default {0}'.format(default_value)
|
||||
|
||||
elif self.default and (self.default not in frappe.db.DEFAULT_SHORTCUTS) \
|
||||
and not self.default.startswith(":") and column_def not in ('text', 'longtext'):
|
||||
and not cstr(self.default).startswith(":") and column_def not in ('text', 'longtext'):
|
||||
column_def += " default {}".format(frappe.db.escape(self.default))
|
||||
|
||||
if self.unique and (column_def not in ('text', 'longtext')):
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ def get_desk_sidebar_items(flatten=False, cache=True):
|
|||
|
||||
# pages sorted based on pinned to top and then by name
|
||||
order_by = "pin_to_top desc, pin_to_bottom asc, name asc"
|
||||
all_pages = frappe.get_all("Desk Page", fields=["name", "category", "icon"], filters=filters, order_by=order_by, ignore_permissions=True)
|
||||
all_pages = frappe.get_all("Desk Page", fields=["name", "category", "icon", "module"]], filters=filters, order_by=order_by, ignore_permissions=True)
|
||||
pages = []
|
||||
|
||||
# Filter Page based on Permission
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
|
||||
refresh: function(frm) {
|
||||
frm.chart_filters = null;
|
||||
frm.is_disabled = !frappe.boot.developer_mode && frm.doc.is_standard;
|
||||
|
||||
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||
if (frm.is_disabled) {
|
||||
!frm.doc.custom_options && frm.set_df_property('chart_options_section', 'hidden', 1);
|
||||
frm.disable_form();
|
||||
}
|
||||
|
||||
|
|
@ -333,6 +335,7 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
}
|
||||
|
||||
table.on('click', () => {
|
||||
frm.is_disabled && frappe.throw(__('Cannot edit filters for standard charts'));
|
||||
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Set Filters'),
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ def make_notification_logs(doc, users):
|
|||
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
|
||||
|
||||
for user in users:
|
||||
if frappe.db.exists('User', user):
|
||||
if frappe.db.exists('User', {"name": user, "enabled": 1}):
|
||||
if is_notifications_enabled(user):
|
||||
if doc.type == 'Energy Point' and not is_energy_point_enabled():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2,12 +2,19 @@
|
|||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Notification Settings', {
|
||||
onload: () => {
|
||||
onload: (frm) => {
|
||||
frappe.breadcrumbs.add({
|
||||
label: __('Settings'),
|
||||
route: '#modules/Settings',
|
||||
type: 'Custom'
|
||||
});
|
||||
frm.set_query('subscribed_documents', () => {
|
||||
return {
|
||||
filters: {
|
||||
istable: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: (frm) => {
|
||||
|
|
|
|||
|
|
@ -22,68 +22,52 @@
|
|||
"default": "1",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "subscribed_documents",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Subscribed Documents",
|
||||
"options": "Notification Subscribed Document",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Open Documents",
|
||||
"options": "Notification Subscribed Document"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Email Settings",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Email Settings"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enable_email_notifications",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Email Notifications",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Enable Email Notifications"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "enable_email_notifications",
|
||||
"fieldname": "enable_email_mention",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mentions",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Mentions"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "enable_email_notifications",
|
||||
"fieldname": "enable_email_assignment",
|
||||
"fieldtype": "Check",
|
||||
"label": "Assignments",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Assignments"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "enable_email_notifications",
|
||||
"fieldname": "enable_email_energy_point",
|
||||
"fieldtype": "Check",
|
||||
"label": "Energy Points",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Energy Points"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "enable_email_notifications",
|
||||
"fieldname": "enable_email_share",
|
||||
"fieldtype": "Check",
|
||||
"label": "Document Share",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Document Share"
|
||||
},
|
||||
{
|
||||
"default": "__user",
|
||||
|
|
@ -92,23 +76,20 @@
|
|||
"hidden": 1,
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "seen",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Seen",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Seen"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-31 22:16:40.798019",
|
||||
"modified": "2020-11-04 12:54:57.989317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Notification Settings",
|
||||
|
|
|
|||
|
|
@ -168,8 +168,8 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
|
|||
"""
|
||||
if not (assigned_by and owner and doc_type and doc_name): return
|
||||
|
||||
# self assignment / closing - no message
|
||||
if assigned_by==owner:
|
||||
# return if self assigned or user disabled
|
||||
if assigned_by == owner or not frappe.db.get_value('User', owner, 'enabled'):
|
||||
return
|
||||
|
||||
# Search for email address in description -- i.e. assignee
|
||||
|
|
@ -177,7 +177,7 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
|
|||
title = get_title(doc_type, doc_name)
|
||||
description_html = "<div>{0}</div>".format(description) if description else None
|
||||
|
||||
if action=='CLOSE':
|
||||
if action == 'CLOSE':
|
||||
subject = _('Your assignment on {0} {1} has been removed by {2}')\
|
||||
.format(frappe.bold(doc_type), get_title_html(title), frappe.bold(user_name))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
import frappe
|
||||
from datetime import datetime
|
||||
from frappe.utils import getdate
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_energy_points_heatmap_data(user, date):
|
||||
try:
|
||||
date = getdate(date)
|
||||
except Exception:
|
||||
date = getdate()
|
||||
|
||||
return dict(frappe.db.sql("""select unix_timestamp(date(creation)), sum(points)
|
||||
from `tabEnergy Point Log`
|
||||
where
|
||||
date(creation) > subdate('{date}', interval 1 year) and
|
||||
date(creation) < subdate('{date}', interval -1 year) and
|
||||
user = '{user}' and
|
||||
user = %s and
|
||||
type != 'Review'
|
||||
group by date(creation)
|
||||
order by creation asc""".format(user = user, date = date)))
|
||||
order by creation asc""".format(date = date), user))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from frappe.modules import scrub, get_module_path
|
|||
from frappe.utils import (
|
||||
flt,
|
||||
cint,
|
||||
cstr,
|
||||
get_html_format,
|
||||
get_url_to_form,
|
||||
gzip_decompress,
|
||||
|
|
@ -74,23 +75,27 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None)
|
|||
res = report.execute_script_report(filters)
|
||||
|
||||
columns, result, message, chart, report_summary, skip_total_row = ljust_list(res, 6)
|
||||
columns = [get_column_as_dict(col) for col in columns]
|
||||
report_column_names = [col["fieldname"] for col in columns]
|
||||
|
||||
# convert to list of dicts
|
||||
result = normalize_result(result, columns)
|
||||
|
||||
if report.custom_columns:
|
||||
# Original query columns, needed to reorder data as per custom columns
|
||||
query_columns = columns
|
||||
# Reordered columns
|
||||
# saved columns (with custom columns / with different column order)
|
||||
columns = json.loads(report.custom_columns)
|
||||
|
||||
result = reorder_data_for_custom_columns(columns, query_columns, result)
|
||||
|
||||
result = add_data_to_custom_columns(columns, result)
|
||||
|
||||
# unsaved custom_columns
|
||||
if custom_columns:
|
||||
result = add_data_to_custom_columns(custom_columns, result)
|
||||
|
||||
for custom_column in custom_columns:
|
||||
columns.insert(custom_column["insert_after_index"] + 1, custom_column)
|
||||
|
||||
# all columns which are not in original report
|
||||
report_custom_columns = [column for column in columns if column["fieldname"] not in report_column_names]
|
||||
|
||||
if report_custom_columns:
|
||||
result = add_custom_column_data(report_custom_columns, result)
|
||||
|
||||
if result:
|
||||
result = get_filtered_data(report.ref_doctype, columns, result, user)
|
||||
|
||||
|
|
@ -109,6 +114,20 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None)
|
|||
or 0,
|
||||
}
|
||||
|
||||
def normalize_result(result, columns):
|
||||
# Converts to list of dicts from list of lists/tuples
|
||||
data = []
|
||||
column_names = [column["fieldname"] for column in columns]
|
||||
if result and isinstance(result[0], (list, tuple)):
|
||||
for row in result:
|
||||
row_obj = {}
|
||||
for idx, column_name in enumerate(column_names):
|
||||
row_obj[column_name] = row[idx]
|
||||
data.append(row_obj)
|
||||
else:
|
||||
data = result
|
||||
|
||||
return data
|
||||
|
||||
@frappe.whitelist()
|
||||
def background_enqueue_run(report_name, filters=None, user=None):
|
||||
|
|
@ -177,14 +196,7 @@ def get_script(report_name):
|
|||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def run(
|
||||
report_name,
|
||||
filters=None,
|
||||
user=None,
|
||||
ignore_prepared_report=False,
|
||||
custom_columns=None,
|
||||
):
|
||||
|
||||
def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None):
|
||||
report = get_report_doc(report_name)
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
|
|
@ -221,69 +233,20 @@ def run(
|
|||
return result
|
||||
|
||||
|
||||
def add_data_to_custom_columns(columns, result):
|
||||
custom_fields_data = get_data_for_custom_report(columns)
|
||||
def add_custom_column_data(custom_columns, result):
|
||||
custom_column_data = get_data_for_custom_report(custom_columns)
|
||||
|
||||
data = []
|
||||
for row in result:
|
||||
row_obj = {}
|
||||
if isinstance(row, tuple):
|
||||
row = list(row)
|
||||
for column in custom_columns:
|
||||
key = (column.get('doctype'), column.get('fieldname'))
|
||||
if key in custom_column_data:
|
||||
for row in result:
|
||||
row_reference = row.get(column.get('link_field'))
|
||||
# possible if the row is empty
|
||||
if not row_reference:
|
||||
continue
|
||||
row[column.get('fieldname')] = custom_column_data.get(key).get(row_reference)
|
||||
|
||||
if isinstance(row, list):
|
||||
for idx, column in enumerate(columns):
|
||||
if column.get("link_field"):
|
||||
row_obj[column["fieldname"]] = None
|
||||
row.insert(idx, None)
|
||||
else:
|
||||
row_obj[column["fieldname"]] = row[idx]
|
||||
data.append(row_obj)
|
||||
else:
|
||||
data.append(row)
|
||||
|
||||
for row in data:
|
||||
for column in columns:
|
||||
if column.get("link_field"):
|
||||
fieldname = column["fieldname"]
|
||||
key = (column["doctype"], fieldname)
|
||||
link_field = column["link_field"]
|
||||
row[fieldname] = custom_fields_data.get(key, {}).get(
|
||||
row.get(link_field)
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def reorder_data_for_custom_columns(custom_columns, columns, result):
|
||||
if not result:
|
||||
return []
|
||||
|
||||
columns = [get_column_as_dict(col) for col in columns]
|
||||
if isinstance(result[0], list) or isinstance(result[0], tuple):
|
||||
# If the result is a list of lists
|
||||
custom_column_names = [col["label"] for col in custom_columns]
|
||||
original_column_names = [col["label"] for col in columns]
|
||||
return get_columns_from_list(custom_column_names, original_column_names, result)
|
||||
else:
|
||||
# columns do not need to be reordered if result is a list of dicts
|
||||
return result
|
||||
|
||||
|
||||
def get_columns_from_list(columns, target_columns, result):
|
||||
reordered_result = []
|
||||
|
||||
for res in result:
|
||||
r = []
|
||||
for col_name in columns:
|
||||
try:
|
||||
idx = target_columns.index(col_name)
|
||||
r.append(res[idx])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
reordered_result.append(r)
|
||||
|
||||
return reordered_result
|
||||
return result
|
||||
|
||||
|
||||
def get_prepared_report_result(report, filters, dn="", user=None):
|
||||
|
|
@ -343,31 +306,27 @@ def get_prepared_report_result(report, filters, dn="", user=None):
|
|||
@frappe.whitelist()
|
||||
def export_query():
|
||||
"""export from query reports"""
|
||||
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
|
||||
del data["cmd"]
|
||||
if "csrf_token" in data:
|
||||
del data["csrf_token"]
|
||||
data.pop("cmd", None)
|
||||
data.pop("csrf_token", None)
|
||||
|
||||
if isinstance(data.get("filters"), string_types):
|
||||
filters = json.loads(data["filters"])
|
||||
if isinstance(data.get("report_name"), string_types):
|
||||
|
||||
if data.get("report_name"):
|
||||
report_name = data["report_name"]
|
||||
frappe.permissions.can_export(
|
||||
frappe.get_cached_value("Report", report_name, "ref_doctype"),
|
||||
raise_exception=True,
|
||||
)
|
||||
if isinstance(data.get("file_format_type"), string_types):
|
||||
file_format_type = data["file_format_type"]
|
||||
|
||||
custom_columns = frappe.parse_json(data["custom_columns"])
|
||||
file_format_type = data.get("file_format_type")
|
||||
custom_columns = frappe.parse_json(data.get("custom_columns", "[]"))
|
||||
include_indentation = data.get("include_indentation")
|
||||
visible_idx = data.get("visible_idx")
|
||||
|
||||
include_indentation = data["include_indentation"]
|
||||
if isinstance(data.get("visible_idx"), string_types):
|
||||
visible_idx = json.loads(data.get("visible_idx"))
|
||||
else:
|
||||
visible_idx = None
|
||||
if isinstance(visible_idx, string_types):
|
||||
visible_idx = json.loads(visible_idx)
|
||||
|
||||
if file_format_type == "Excel":
|
||||
data = run(report_name, filters, custom_columns=custom_columns)
|
||||
|
|
@ -386,8 +345,8 @@ def export_query():
|
|||
data["result"] = handle_duration_fieldtype_values(
|
||||
data.get("result"), data.get("columns")
|
||||
)
|
||||
xlsx_data = build_xlsx_data(columns, data, visible_idx, include_indentation)
|
||||
xlsx_file = make_xlsx(xlsx_data, "Query Report")
|
||||
xlsx_data, column_widths = build_xlsx_data(columns, data, visible_idx, include_indentation)
|
||||
xlsx_file = make_xlsx(xlsx_data, "Query Report", column_widths=column_widths)
|
||||
|
||||
frappe.response["filename"] = report_name + ".xlsx"
|
||||
frappe.response["filecontent"] = xlsx_file.getvalue()
|
||||
|
|
@ -421,34 +380,38 @@ def handle_duration_fieldtype_values(result, columns):
|
|||
|
||||
def build_xlsx_data(columns, data, visible_idx, include_indentation):
|
||||
result = [[]]
|
||||
column_widths = []
|
||||
|
||||
# add column headings
|
||||
for idx in range(len(data.columns)):
|
||||
if not columns[idx].get("hidden"):
|
||||
result[0].append(columns[idx]["label"])
|
||||
for column in data.columns:
|
||||
if column.get("hidden"):
|
||||
continue
|
||||
result[0].append(column["label"])
|
||||
column_width = cint(column.get('width', 0))
|
||||
# to convert into scale accepted by openpyxl
|
||||
column_width /= 10
|
||||
column_widths.append(column_width)
|
||||
|
||||
# build table from result
|
||||
for i, row in enumerate(data.result):
|
||||
for row_idx, row in enumerate(data.result):
|
||||
# only pick up rows that are visible in the report
|
||||
if i in visible_idx:
|
||||
if row_idx in visible_idx:
|
||||
row_data = []
|
||||
|
||||
if isinstance(row, dict) and row:
|
||||
for idx in range(len(data.columns)):
|
||||
# check if column is not hidden
|
||||
if not columns[idx].get("hidden"):
|
||||
label = columns[idx]["label"]
|
||||
fieldname = columns[idx]["fieldname"]
|
||||
cell_value = row.get(fieldname, row.get(label, ""))
|
||||
if cint(include_indentation) and "indent" in row and idx == 0:
|
||||
cell_value = (" " * cint(row["indent"])) + cell_value
|
||||
row_data.append(cell_value)
|
||||
else:
|
||||
if isinstance(row, dict):
|
||||
for col_idx, column in enumerate(data.columns):
|
||||
if column.get("hidden"):
|
||||
continue
|
||||
label = column.get("label")
|
||||
fieldname = column.get("fieldname")
|
||||
cell_value = row.get(fieldname, row.get(label, ""))
|
||||
if cint(include_indentation) and "indent" in row and col_idx == 0:
|
||||
cell_value = (" " * cint(row["indent"])) + cstr(cell_value)
|
||||
row_data.append(cell_value)
|
||||
elif row:
|
||||
row_data = row
|
||||
|
||||
result.append(row_data)
|
||||
|
||||
return result
|
||||
return result, column_widths
|
||||
|
||||
|
||||
def add_total_row(result, columns, meta=None):
|
||||
|
|
@ -755,6 +718,8 @@ def get_column_as_dict(col):
|
|||
col_dict["fieldtype"], col_dict["options"] = col[1].split("/")
|
||||
else:
|
||||
col_dict["fieldtype"] = col[1]
|
||||
if len(col) == 3:
|
||||
col_dict["width"] = col[2]
|
||||
|
||||
col_dict["label"] = col[0]
|
||||
col_dict["fieldname"] = frappe.scrub(col[0])
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
|
|||
|
||||
# find relevance as location of search term from the beginning of string `name`. used for sorting results.
|
||||
formatted_fields.append("""locate({_txt}, `tab{doctype}`.`name`) as `_relevance`""".format(
|
||||
_txt=frappe.db.escape((txt or "").replace("%", "")), doctype=doctype))
|
||||
_txt=frappe.db.escape((txt or "").replace("%", "").replace("@", "")), doctype=doctype))
|
||||
|
||||
|
||||
# In order_by, `idx` gets second priority, because it stores link count
|
||||
|
|
|
|||
|
|
@ -25,7 +25,11 @@ from frappe.core.doctype.communication.email import set_incoming_outgoing_accoun
|
|||
from frappe.utils.html_utils import clean_email_html
|
||||
from frappe.email.utils import get_port
|
||||
|
||||
class SentEmailInInbox(Exception): pass
|
||||
class SentEmailInInbox(Exception):
|
||||
pass
|
||||
|
||||
class InvalidEmailCredentials(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
class EmailAccount(Document):
|
||||
def autoname(self):
|
||||
|
|
@ -148,7 +152,7 @@ class EmailAccount(Document):
|
|||
return None
|
||||
|
||||
args = frappe._dict({
|
||||
"email_account":self.name,
|
||||
"email_account": self.name,
|
||||
"host": self.email_server,
|
||||
"use_ssl": self.use_ssl,
|
||||
"username": getattr(self, "login_id", None) or self.email_id,
|
||||
|
|
@ -166,21 +170,45 @@ class EmailAccount(Document):
|
|||
frappe.throw(_("{0} is required").format("Email Server"))
|
||||
|
||||
email_server = EmailServer(frappe._dict(args))
|
||||
self.check_email_server_connection(email_server, in_receive)
|
||||
|
||||
if not in_receive and self.use_imap:
|
||||
email_server.imap.logout()
|
||||
|
||||
# reset failed attempts count
|
||||
self.set_failed_attempts_count(0)
|
||||
|
||||
return email_server
|
||||
|
||||
def check_email_server_connection(self, email_server, in_receive):
|
||||
# tries to connect to email server and handles failure
|
||||
try:
|
||||
email_server.connect()
|
||||
except (error_proto, imaplib.IMAP4.error) as e:
|
||||
e = cstr(e)
|
||||
message = e.lower().replace(" ","")
|
||||
if in_receive and any(map(lambda t: t in message, ['authenticationfailed', 'loginviayourwebbrowser', #abbreviated to work with both failure and failed
|
||||
'loginfailed', 'err[auth]', 'errtemporaryerror'])): #temporary error to deal with godaddy
|
||||
# if called via self.receive and it leads to authentication error, disable incoming
|
||||
# and send email to system manager
|
||||
self.handle_incoming_connect_error(
|
||||
description=_('Authentication failed while receiving emails from Email Account {0}. Message from server: {1}').format(self.name, e)
|
||||
)
|
||||
message = cstr(e).lower().replace(" ","")
|
||||
auth_error_codes = [
|
||||
'authenticationfailed',
|
||||
'loginfailed',
|
||||
]
|
||||
|
||||
other_error_codes = [
|
||||
'err[auth]',
|
||||
'errtemporaryerror',
|
||||
'loginviayourwebbrowser'
|
||||
]
|
||||
|
||||
all_error_codes = auth_error_codes + other_error_codes
|
||||
|
||||
if in_receive and any(map(lambda t: t in message, all_error_codes)):
|
||||
# if called via self.receive and it leads to authentication error,
|
||||
# disable incoming and send email to System Manager
|
||||
error_message = _("Authentication failed while receiving emails from Email Account: {0}.").format(self.name)
|
||||
error_message += "<br>" + _("Message from server: {0}").format(cstr(e))
|
||||
self.handle_incoming_connect_error(description=error_message)
|
||||
return None
|
||||
|
||||
elif not in_receive and any(map(lambda t: t in message, auth_error_codes)):
|
||||
self.throw_invalid_credentials_exception()
|
||||
else:
|
||||
frappe.throw(e)
|
||||
|
||||
|
|
@ -195,16 +223,16 @@ class EmailAccount(Document):
|
|||
else:
|
||||
frappe.cache().set_value("workers:no-internet", True)
|
||||
return None
|
||||
|
||||
else:
|
||||
raise
|
||||
if not in_receive:
|
||||
if self.use_imap:
|
||||
email_server.imap.logout()
|
||||
# reset failed attempts count
|
||||
self.set_failed_attempts_count(0)
|
||||
|
||||
return email_server
|
||||
@classmethod
|
||||
def throw_invalid_credentials_exception(cls):
|
||||
frappe.throw(
|
||||
_("Incorrect email or password. Please check your login credentials."),
|
||||
exc=InvalidEmailCredentials,
|
||||
title=_("Invalid Credentials")
|
||||
)
|
||||
|
||||
def handle_incoming_connect_error(self, description):
|
||||
if test_internet():
|
||||
|
|
|
|||
|
|
@ -3,11 +3,6 @@
|
|||
|
||||
frappe.ui.form.on("Email Group", "refresh", function(frm) {
|
||||
if(!frm.is_new()) {
|
||||
frm.add_custom_button(__("View Subscribers"), function() {
|
||||
frappe.route_options = {"email_group": frm.doc.name};
|
||||
frappe.set_route("List", "Email Group Member");
|
||||
}, __("View"));
|
||||
|
||||
frm.add_custom_button(__("Import Subscribers"), function() {
|
||||
frappe.prompt({fieldtype:"Select", options: frm.doc.__onload.import_types,
|
||||
label:__("Import Email From"), fieldname:"doctype", reqd:1},
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"creation": "2015-03-18 06:08:32.729800",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"total_subscribers",
|
||||
|
|
@ -41,8 +42,15 @@
|
|||
"options": "Email Template"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-02-21 14:12:48.884738",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [
|
||||
{
|
||||
"group": "Members",
|
||||
"link_doctype": "Email Group Member",
|
||||
"link_fieldname": "email_group"
|
||||
}
|
||||
],
|
||||
"modified": "2020-09-24 16:41:55.286377",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Group",
|
||||
|
|
|
|||
|
|
@ -198,12 +198,15 @@ class EMail:
|
|||
|
||||
def set_message_id(self, message_id, is_notification=False):
|
||||
if message_id:
|
||||
self.msg_root["Message-Id"] = '<' + message_id + '>'
|
||||
message_id = '<' + message_id + '>'
|
||||
else:
|
||||
self.msg_root["Message-Id"] = get_message_id()
|
||||
self.msg_root["isnotification"] = '<notification>'
|
||||
message_id = get_message_id()
|
||||
self.set_header('isnotification', '<notification>')
|
||||
|
||||
if is_notification:
|
||||
self.msg_root["isnotification"] = '<notification>'
|
||||
self.set_header('isnotification', '<notification>')
|
||||
|
||||
self.set_header('Message-Id', message_id)
|
||||
|
||||
def set_in_reply_to(self, in_reply_to):
|
||||
"""Used to send the Message-Id of a received email back as In-Reply-To"""
|
||||
|
|
|
|||
|
|
@ -59,10 +59,6 @@ class EmailServer:
|
|||
frappe.msgprint(_('Invalid Mail Server. Please rectify and try again.'))
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
frappe.msgprint(_('Cannot connect: {0}').format(str(e)))
|
||||
raise
|
||||
|
||||
def connect_pop(self):
|
||||
#this method return pop connection
|
||||
try:
|
||||
|
|
@ -540,6 +536,8 @@ class Email:
|
|||
except MaxFileSizeReachedError:
|
||||
# WARNING: bypass max file size exception
|
||||
pass
|
||||
except frappe.FileAlreadyAttachedException:
|
||||
pass
|
||||
except frappe.DuplicateEntryError:
|
||||
# same file attached twice??
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from six import reraise as raise_
|
||||
import frappe
|
||||
import smtplib
|
||||
import email.utils
|
||||
|
|
@ -242,16 +241,17 @@ class SMTPServer:
|
|||
|
||||
return self._sess
|
||||
|
||||
except smtplib.SMTPAuthenticationError as e:
|
||||
from frappe.email.doctype.email_account.email_account import EmailAccount
|
||||
EmailAccount.throw_invalid_credentials_exception()
|
||||
|
||||
except _socket.error as e:
|
||||
# Invalid mail server -- due to refusing connection
|
||||
frappe.msgprint(_('Invalid Outgoing Mail Server or Port'))
|
||||
traceback = sys.exc_info()[2]
|
||||
raise_(frappe.ValidationError, e, traceback)
|
||||
|
||||
except smtplib.SMTPAuthenticationError as e:
|
||||
frappe.msgprint(_("Invalid login or password"))
|
||||
traceback = sys.exc_info()[2]
|
||||
raise_(frappe.ValidationError, e, traceback)
|
||||
frappe.throw(
|
||||
_("Invalid Outgoing Mail Server or Port"),
|
||||
exc=frappe.ValidationError,
|
||||
title=_("Incorrect Configuration")
|
||||
)
|
||||
|
||||
except smtplib.SMTPException:
|
||||
frappe.msgprint(_('Unable to send emails at this time'))
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
"api_secret",
|
||||
"column_break_6",
|
||||
"user",
|
||||
"last_update",
|
||||
"incoming_change"
|
||||
],
|
||||
"fields": [
|
||||
|
|
@ -25,12 +24,6 @@
|
|||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "last_update",
|
||||
"fieldtype": "Data",
|
||||
"label": "Last Update",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "API Key of the user(Event Subscriber) on the producer site",
|
||||
"fieldname": "api_key",
|
||||
|
|
@ -77,7 +70,7 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-09-08 18:50:57.687979",
|
||||
"modified": "2020-10-26 13:00:15.361316",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Event Streaming",
|
||||
"name": "Event Producer",
|
||||
|
|
|
|||
|
|
@ -79,10 +79,24 @@ class EventProducer(Document):
|
|||
)
|
||||
if response:
|
||||
response = json.loads(response)
|
||||
self.last_update = response['last_update']
|
||||
self.set_last_update(response['last_update'])
|
||||
else:
|
||||
frappe.throw(_('Failed to create an Event Consumer or an Event Consumer for the current site is already registered.'))
|
||||
|
||||
def set_last_update(self, last_update):
|
||||
last_update_doc_name = frappe.db.get_value('Event Producer Last Update', dict(event_producer=self.name))
|
||||
if not last_update_doc_name:
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Event Producer Last Update',
|
||||
event_producer = self.producer_url,
|
||||
last_update = last_update
|
||||
)).insert(ignore_permissions=True)
|
||||
else:
|
||||
frappe.db.set_value('Event Producer Last Update', last_update_doc_name, 'last_update', last_update)
|
||||
|
||||
def get_last_update(self):
|
||||
return frappe.db.get_value('Event Producer Last Update', dict(event_producer=self.name), 'last_update')
|
||||
|
||||
def get_request_data(self):
|
||||
consumer_doctypes = []
|
||||
for entry in self.producer_doctypes:
|
||||
|
|
@ -184,7 +198,7 @@ def pull_from_node(event_producer):
|
|||
"""pull all updates after the last update timestamp from event producer site"""
|
||||
event_producer = frappe.get_doc('Event Producer', event_producer)
|
||||
producer_site = get_producer_site(event_producer.producer_url)
|
||||
last_update = event_producer.last_update
|
||||
last_update = event_producer.get_last_update()
|
||||
|
||||
(doctypes, mapping_config, naming_config) = get_config(event_producer.producer_doctypes)
|
||||
|
||||
|
|
@ -239,7 +253,7 @@ def sync(update, producer_site, event_producer, in_retry=False):
|
|||
return 'Failed'
|
||||
log_event_sync(update, event_producer.name, 'Failed', frappe.get_traceback())
|
||||
|
||||
event_producer.db_set('last_update', update.creation)
|
||||
event_producer.set_last_update(update.creation)
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Event Producer Last Update', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
|
@ -1,36 +1,36 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "field:document_type",
|
||||
"creation": "2020-04-08 15:16:44.342509",
|
||||
"autoname": "field:event_producer",
|
||||
"creation": "2020-10-26 12:53:11.940177",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"links"
|
||||
"event_producer",
|
||||
"last_update"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "event_producer",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"label": "Event Producer",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "links",
|
||||
"fieldtype": "Table",
|
||||
"label": "Links",
|
||||
"options": "DocType Link"
|
||||
"fieldname": "last_update",
|
||||
"fieldtype": "Data",
|
||||
"label": "Last Update"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-08 16:42:59.402671",
|
||||
"modified": "2020-10-26 13:22:27.056599",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Link",
|
||||
"module": "Event Streaming",
|
||||
"name": "Event Producer Last Update",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
|
@ -46,6 +46,7 @@
|
|||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class CustomLink(Document):
|
||||
class EventProducerLastUpdate(Document):
|
||||
pass
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestCustomLink(unittest.TestCase):
|
||||
class TestEventProducerLastUpdate(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -76,6 +76,7 @@ class UnknownDomainError(Exception): pass
|
|||
class MappingMismatchError(ValidationError): pass
|
||||
class InvalidStatusError(ValidationError): pass
|
||||
class MandatoryError(ValidationError): pass
|
||||
class NonNegativeError(ValidationError): pass
|
||||
class InvalidSignatureError(ValidationError): pass
|
||||
class RateLimitExceededError(ValidationError): pass
|
||||
class CannotChangeConstantError(ValidationError): pass
|
||||
|
|
|
|||
|
|
@ -1,345 +1,113 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:currency_name",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-28 10:06:02",
|
||||
"custom": 0,
|
||||
"description": "**Currency** Master",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"currency_name",
|
||||
"enabled",
|
||||
"fraction",
|
||||
"fraction_units",
|
||||
"smallest_currency_fraction_value",
|
||||
"symbol",
|
||||
"number_format"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "currency_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": "Currency Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "currency_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"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": "Enabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Sub-currency. For e.g. \"Cent\"",
|
||||
"fieldname": "fraction",
|
||||
"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": "Fraction",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Fraction"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "1 Currency = [?] Fraction\nFor e.g. 1 USD = 100 Cent",
|
||||
"fieldname": "fraction_units",
|
||||
"fieldtype": "Int",
|
||||
"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": "Fraction Units",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Fraction Units"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Smallest circulating fraction unit (coin). For e.g. 1 cent for USD and it should be entered as 0.01",
|
||||
"fieldname": "smallest_currency_fraction_value",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Smallest Currency Fraction Value",
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "A symbol for this currency. For e.g. $",
|
||||
"fieldname": "symbol",
|
||||
"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": "Symbol",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Symbol"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "How should this currency be formatted? If not set, will use system defaults",
|
||||
"fieldname": "number_format",
|
||||
"fieldtype": "Select",
|
||||
"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": "Number Format",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\n#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###",
|
||||
"permlevel": 0,
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "\n#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-bitcoin",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-29 06:37:19.908254",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-29 06:33:12.879978",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Geo",
|
||||
"name": "Currency",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Sales User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Purchase User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"role": "Purchase User"
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -138,7 +138,6 @@ doc_events = {
|
|||
"frappe.core.doctype.activity_log.feed.update_feed",
|
||||
"frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions",
|
||||
"frappe.automation.doctype.assignment_rule.assignment_rule.apply",
|
||||
"frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone",
|
||||
"frappe.core.doctype.file.file.attach_files_to_document",
|
||||
"frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers",
|
||||
"frappe.automation.doctype.assignment_rule.assignment_rule.update_due_date",
|
||||
|
|
@ -154,7 +153,8 @@ doc_events = {
|
|||
"frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers"
|
||||
],
|
||||
"on_change": [
|
||||
"frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points"
|
||||
"frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points",
|
||||
"frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone"
|
||||
]
|
||||
},
|
||||
"Event": {
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False)
|
|||
|
||||
for doctype in set(drop_doctypes):
|
||||
print("* dropping Table for '{0}'...".format(doctype))
|
||||
frappe.db.sql("drop table `tab{0}`".format(doctype))
|
||||
frappe.db.sql_ddl("drop table `tab{0}`".format(doctype))
|
||||
|
||||
frappe.db.commit()
|
||||
click.secho("Uninstalled App {0} from Site {1}".format(app_name, frappe.local.site), fg="green")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import copy
|
|||
import frappe
|
||||
import frappe.defaults
|
||||
from frappe.model import data_fieldtypes
|
||||
from frappe.utils import nowdate, nowtime, now_datetime
|
||||
from frappe.utils import nowdate, nowtime, now_datetime, cstr
|
||||
from frappe.core.doctype.user_permission.user_permission import get_user_permissions
|
||||
from frappe.permissions import filter_allowed_docs_for_doctype
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ def get_static_default_value(df, doctype_user_permissions, allowed_records):
|
|||
elif df.default == "Today":
|
||||
return nowdate()
|
||||
|
||||
elif not df.default.startswith(":"):
|
||||
elif not cstr(df.default).startswith(":"):
|
||||
# a simple default value
|
||||
is_allowed_default_value = (not user_permissions_exist(df, doctype_user_permissions)
|
||||
or (df.default in allowed_records))
|
||||
|
|
@ -116,7 +116,7 @@ def set_dynamic_default_values(doc, parent_doc, parentfield):
|
|||
|
||||
for df in frappe.get_meta(doc["doctype"]).get("fields"):
|
||||
if df.get("default"):
|
||||
if df.default.startswith(":"):
|
||||
if cstr(df.default).startswith(":"):
|
||||
default_value = get_default_based_on_another_field(df, user_permissions, parent_doc)
|
||||
if default_value is not None and not doc.get(df.fieldname):
|
||||
doc[df.fieldname] = default_value
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class DatabaseQuery(object):
|
|||
join='left join', distinct=False, start=None, page_length=None, limit=None,
|
||||
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
|
||||
update=None, add_total_row=None, user_settings=None, reference_doctype=None,
|
||||
return_query=False, strict=True, pluck=None):
|
||||
return_query=False, strict=True, pluck=None, ignore_ddl=False):
|
||||
if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user):
|
||||
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
|
||||
raise frappe.PermissionError(self.doctype)
|
||||
|
|
@ -86,6 +86,7 @@ class DatabaseQuery(object):
|
|||
self.user_settings_fields = copy.deepcopy(self.fields)
|
||||
self.return_query = return_query
|
||||
self.strict = strict
|
||||
self.ignore_ddl = ignore_ddl
|
||||
|
||||
# for contextual user permission check
|
||||
# to determine which user permission is applicable on link field of specific doctype
|
||||
|
|
@ -94,6 +95,11 @@ class DatabaseQuery(object):
|
|||
if user_settings:
|
||||
self.user_settings = json.loads(user_settings)
|
||||
|
||||
self.columns = self.get_table_columns()
|
||||
|
||||
# no table & ignore_ddl, return
|
||||
if not self.columns: return []
|
||||
|
||||
if query:
|
||||
result = self.run_custom_query(query)
|
||||
else:
|
||||
|
|
@ -134,7 +140,8 @@ class DatabaseQuery(object):
|
|||
if self.return_query:
|
||||
return query
|
||||
else:
|
||||
return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug, update=self.update)
|
||||
return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug,
|
||||
update=self.update, ignore_ddl=self.ignore_ddl)
|
||||
|
||||
def prepare_args(self):
|
||||
self.parse_args()
|
||||
|
|
@ -323,15 +330,22 @@ class DatabaseQuery(object):
|
|||
if '.' not in field and not _in_standard_sql_methods(field):
|
||||
self.fields[idx] = '{0}.{1}'.format(self.tables[0], field)
|
||||
|
||||
def get_table_columns(self):
|
||||
try:
|
||||
return get_table_columns(self.doctype)
|
||||
except frappe.db.TableMissingError:
|
||||
if self.ignore_ddl:
|
||||
return None
|
||||
else:
|
||||
raise
|
||||
|
||||
def set_optional_columns(self):
|
||||
"""Removes optional columns like `_user_tags`, `_comments` etc. if not in table"""
|
||||
columns = get_table_columns(self.doctype)
|
||||
|
||||
# remove from fields
|
||||
to_remove = []
|
||||
for fld in self.fields:
|
||||
for f in optional_fields:
|
||||
if f in fld and not f in columns:
|
||||
if f in fld and not f in self.columns:
|
||||
to_remove.append(fld)
|
||||
|
||||
for fld in to_remove:
|
||||
|
|
@ -344,7 +358,7 @@ class DatabaseQuery(object):
|
|||
each = [each]
|
||||
|
||||
for element in each:
|
||||
if element in optional_fields and element not in columns:
|
||||
if element in optional_fields and element not in self.columns:
|
||||
to_remove.append(each)
|
||||
|
||||
for each in to_remove:
|
||||
|
|
|
|||
|
|
@ -493,6 +493,7 @@ class Document(BaseDocument):
|
|||
self._validate_mandatory()
|
||||
self._validate_data_fields()
|
||||
self._validate_selects()
|
||||
self._validate_non_negative()
|
||||
self._validate_length()
|
||||
self._extract_images_from_text_editor()
|
||||
self._sanitize_content()
|
||||
|
|
@ -503,6 +504,7 @@ class Document(BaseDocument):
|
|||
for d in children:
|
||||
d._validate_data_fields()
|
||||
d._validate_selects()
|
||||
d._validate_non_negative()
|
||||
d._validate_length()
|
||||
d._extract_images_from_text_editor()
|
||||
d._sanitize_content()
|
||||
|
|
@ -514,6 +516,21 @@ class Document(BaseDocument):
|
|||
else:
|
||||
self.validate_set_only_once()
|
||||
|
||||
def _validate_non_negative(self):
|
||||
def get_msg(df):
|
||||
if self.parentfield:
|
||||
return "{} {} #{}: {} {}".format(frappe.bold(_(self.doctype)),
|
||||
_("Row"), self.idx, _("Value cannot be negative for"), frappe.bold(_(df.label)))
|
||||
else:
|
||||
return _("Value cannot be negative for {0}: {1}").format(_(df.parent), frappe.bold(_(df.label)))
|
||||
|
||||
for df in self.meta.get('fields', {'non_negative': ('=', 1),
|
||||
'fieldtype': ('in', ['Int', 'Float', 'Currency'])}):
|
||||
|
||||
if flt(self.get(df.fieldname)) < 0:
|
||||
msg = get_msg(df)
|
||||
frappe.throw(msg, frappe.NonNegativeError, title=_("Negative Value"))
|
||||
|
||||
def validate_workflow(self):
|
||||
"""Validate if the workflow transition is valid"""
|
||||
if frappe.flags.in_install == 'frappe': return
|
||||
|
|
|
|||
|
|
@ -42,9 +42,12 @@ def get_dynamic_link_map(for_delete=False):
|
|||
# always check in Single DocTypes
|
||||
dynamic_link_map.setdefault(meta.name, []).append(df)
|
||||
else:
|
||||
links = frappe.db.sql_list("""select distinct {options} from `tab{parent}`""".format(**df))
|
||||
for doctype in links:
|
||||
dynamic_link_map.setdefault(doctype, []).append(df)
|
||||
try:
|
||||
links = frappe.db.sql_list("""select distinct {options} from `tab{parent}`""".format(**df))
|
||||
for doctype in links:
|
||||
dynamic_link_map.setdefault(doctype, []).append(df)
|
||||
except frappe.db.TableMissingError: # noqa: E722
|
||||
pass
|
||||
|
||||
frappe.local.dynamic_link_map = dynamic_link_map
|
||||
return frappe.local.dynamic_link_map
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from __future__ import unicode_literals, print_function
|
|||
from datetime import datetime
|
||||
from six.moves import range
|
||||
import frappe, json, os
|
||||
from frappe.utils import cstr, cint
|
||||
from frappe.utils import cstr, cint, cast_fieldtype
|
||||
from frappe.model import default_fields, no_value_fields, optional_fields, data_fieldtypes, table_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.base_document import BaseDocument
|
||||
|
|
@ -103,6 +103,7 @@ class Meta(Document):
|
|||
self.sort_fields()
|
||||
self.get_valid_columns()
|
||||
self.set_custom_permissions()
|
||||
self.add_custom_links_and_actions()
|
||||
|
||||
def as_dict(self, no_nulls = False):
|
||||
def serialize(doc):
|
||||
|
|
@ -305,6 +306,11 @@ class Meta(Document):
|
|||
self.extend("fields", custom_fields)
|
||||
|
||||
def apply_property_setters(self):
|
||||
"""
|
||||
Property Setters are set via Customize Form. They override standard properties
|
||||
of the doctype or its child properties like fields, links etc. This method
|
||||
applies the customized properties over the standard meta object
|
||||
"""
|
||||
if not frappe.db.table_exists('Property Setter'):
|
||||
return
|
||||
|
||||
|
|
@ -313,26 +319,52 @@ class Meta(Document):
|
|||
|
||||
if not property_setters: return
|
||||
|
||||
integer_docfield_properties = [d.fieldname for d in frappe.get_meta('DocField').fields
|
||||
if d.fieldtype in ('Int', 'Check')]
|
||||
|
||||
for ps in property_setters:
|
||||
if ps.doctype_or_field=='DocType':
|
||||
if ps.property_type in ('Int', 'Check'):
|
||||
ps.value = cint(ps.value)
|
||||
self.set(ps.property, cast_fieldtype(ps.property_type, ps.value))
|
||||
|
||||
self.set(ps.property, ps.value)
|
||||
else:
|
||||
docfield = self.get("fields", {"fieldname":ps.field_name}, limit=1)
|
||||
if docfield:
|
||||
docfield = docfield[0]
|
||||
else:
|
||||
continue
|
||||
elif ps.doctype_or_field=='DocField':
|
||||
for d in self.fields:
|
||||
if d.fieldname == ps.field_name:
|
||||
d.set(ps.property, cast_fieldtype(ps.property_type, ps.value))
|
||||
break
|
||||
|
||||
if ps.property in integer_docfield_properties:
|
||||
ps.value = cint(ps.value)
|
||||
elif ps.doctype_or_field=='DocType Link':
|
||||
for d in self.links:
|
||||
if d.name == ps.row_name:
|
||||
d.set(ps.property, cast_fieldtype(ps.property_type, ps.value))
|
||||
break
|
||||
|
||||
docfield.set(ps.property, ps.value)
|
||||
elif ps.doctype_or_field=='DocType Action':
|
||||
for d in self.actions:
|
||||
if d.name == ps.row_name:
|
||||
d.set(ps.property, cast_fieldtype(ps.property_type, ps.value))
|
||||
break
|
||||
|
||||
def add_custom_links_and_actions(self):
|
||||
for doctype, fieldname in (('DocType Link', 'links'), ('DocType Action', 'actions')):
|
||||
# ignore_ddl because the `custom` column was added later via a patch
|
||||
for d in frappe.get_all(doctype, fields='*', filters=dict(parent=self.name, custom=1), ignore_ddl=True):
|
||||
self.append(fieldname, d)
|
||||
|
||||
# set the fields in order if specified
|
||||
# order is saved as `links_order`
|
||||
order = json.loads(self.get('{}_order'.format(fieldname)) or '[]')
|
||||
if order:
|
||||
name_map = {d.name:d for d in self.get(fieldname)}
|
||||
new_list = []
|
||||
for name in order:
|
||||
if name in name_map:
|
||||
new_list.append(name_map[name])
|
||||
|
||||
# add the missing items that have not be added
|
||||
# maybe these items were added to the standard product
|
||||
# after the customization was done
|
||||
for d in self.get(fieldname):
|
||||
if d not in new_list:
|
||||
new_list.append(d)
|
||||
|
||||
self.set(fieldname, new_list)
|
||||
|
||||
def sort_fields(self):
|
||||
"""sort on basis of insert_after"""
|
||||
|
|
@ -448,9 +480,6 @@ class Meta(Document):
|
|||
if hasattr(self, 'links') and self.links:
|
||||
dashboard_links.extend(self.links)
|
||||
|
||||
if frappe.get_all("Custom Link", {"document_type": self.name}):
|
||||
dashboard_links.extend(frappe.get_doc("Custom Link", self.name).links)
|
||||
|
||||
if not data.transactions:
|
||||
# init groups
|
||||
data.transactions = []
|
||||
|
|
@ -458,6 +487,9 @@ class Meta(Document):
|
|||
|
||||
for link in dashboard_links:
|
||||
link.added = False
|
||||
if link.hidden:
|
||||
continue
|
||||
|
||||
for group in data.transactions:
|
||||
group = frappe._dict(group)
|
||||
# group found
|
||||
|
|
|
|||
|
|
@ -93,15 +93,12 @@ def set_naming_from_document_naming_rule(doc):
|
|||
if doc.doctype in log_types:
|
||||
return
|
||||
|
||||
try:
|
||||
for d in frappe.get_all('Document Naming Rule',
|
||||
dict(document_type=doc.doctype, disabled=0), order_by='priority desc'):
|
||||
frappe.get_cached_doc('Document Naming Rule', d.name).apply(doc)
|
||||
if doc.name:
|
||||
break
|
||||
except frappe.db.TableMissingError: # noqa: E722
|
||||
# not yet bootstrapped
|
||||
pass
|
||||
# ignore_ddl if naming is not yet bootstrapped
|
||||
for d in frappe.get_all('Document Naming Rule',
|
||||
dict(document_type=doc.doctype, disabled=0), order_by='priority desc', ignore_ddl=True):
|
||||
frappe.get_cached_doc('Document Naming Rule', d.name).apply(doc)
|
||||
if doc.name:
|
||||
break
|
||||
|
||||
def set_name_by_naming_series(doc):
|
||||
"""Sets name by the `naming_series` property"""
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ frappe.patches.v7_0.update_auth
|
|||
frappe.patches.v8_0.drop_in_dialog #2017-09-22
|
||||
frappe.patches.v7_2.remove_in_filter
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype_action', force=True) #2019-09-23
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype_link', force=True) #2019-09-23
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype_link', force=True) #2020-10-17
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22
|
||||
execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2018-02-20
|
||||
frappe.patches.v11_0.drop_column_apply_user_permissions
|
||||
|
|
@ -315,3 +315,6 @@ frappe.patches.v13_0.update_newsletter_content_type
|
|||
execute:frappe.db.set_value('Website Settings', 'Website Settings', {'navbar_template': 'Standard Navbar', 'footer_template': 'Standard Footer'})
|
||||
frappe.patches.v13_0.delete_event_producer_and_consumer_keys
|
||||
frappe.patches.v13_0.web_template_set_module #2020-10-05
|
||||
frappe.patches.v13_0.remove_custom_link
|
||||
execute:frappe.delete_doc("DocType", "Footer Item")
|
||||
frappe.patches.v13_0.replace_field_target_with_open_in_new_tab
|
||||
|
|
|
|||
15
frappe/patches/v13_0/remove_custom_link.py
Normal file
15
frappe/patches/v13_0/remove_custom_link.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
'''
|
||||
Remove the doctype "Custom Link" that was used to add Custom Links to the
|
||||
Dashboard since this is now managed by Customize Form.
|
||||
Update `parent` property to the DocType and delte the doctype
|
||||
'''
|
||||
frappe.reload_doctype('DocType Link')
|
||||
if frappe.db.has_table('Custom Link'):
|
||||
for custom_link in frappe.get_all('Custom Link', ['name', 'document_type']):
|
||||
frappe.db.sql('update `tabDocType Link` set custom=1, parent=%s where parent=%s',
|
||||
(custom_link.document_type, custom_link.name))
|
||||
|
||||
frappe.delete_doc('DocType', 'Custom Link')
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
doctype = "Top Bar Item"
|
||||
if not frappe.db.table_exists(doctype) \
|
||||
or not frappe.db.has_column(doctype, "target"):
|
||||
return
|
||||
|
||||
frappe.reload_doc("website", "doctype", "top_bar_item")
|
||||
frappe.db.set_value(doctype, {"target": 'target = "_blank"'}, 'open_in_new_tab', 1)
|
||||
|
|
@ -8,10 +8,11 @@
|
|||
"field_order": [
|
||||
"doc_type",
|
||||
"module",
|
||||
"disabled",
|
||||
"default_print_language",
|
||||
"column_break_3",
|
||||
"standard",
|
||||
"custom_format",
|
||||
"disabled",
|
||||
"section_break_6",
|
||||
"print_format_type",
|
||||
"raw_printing",
|
||||
|
|
@ -22,7 +23,6 @@
|
|||
"show_section_headings",
|
||||
"line_breaks",
|
||||
"column_break_11",
|
||||
"default_print_language",
|
||||
"font",
|
||||
"css_section",
|
||||
"css",
|
||||
|
|
@ -202,7 +202,7 @@
|
|||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-22 14:58:03.261063",
|
||||
"modified": "2020-10-27 18:27:58.307070",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Printing",
|
||||
"name": "Print Format",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ body {
|
|||
html,
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
.intro-area,
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ frappe.Application = Class.extend({
|
|||
'reqd': 1
|
||||
},
|
||||
{
|
||||
"fieldname": "submit",
|
||||
"fieldtype": "Button",
|
||||
"label": __("Submit")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
cur_frm = this;
|
||||
|
||||
if(this.docname) { // document to show
|
||||
|
||||
this.save_disabled = false;
|
||||
// set the doc
|
||||
this.doc = frappe.get_doc(this.doctype, this.docname);
|
||||
|
||||
|
|
@ -1270,17 +1270,17 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
|
||||
set_df_property(fieldname, property, value, docname, table_field) {
|
||||
var df;
|
||||
if (!docname && !table_field) {
|
||||
if (!docname || !table_field) {
|
||||
df = this.get_docfield(fieldname);
|
||||
} else {
|
||||
var grid = this.fields_dict[table_field].grid,
|
||||
fname = frappe.utils.filter_dict(grid.docfields, {'fieldname': fieldname});
|
||||
var grid = this.fields_dict[fieldname].grid,
|
||||
fname = frappe.utils.filter_dict(grid.docfields, {'fieldname': table_field});
|
||||
if (fname && fname.length)
|
||||
df = frappe.meta.get_docfield(fname[0].parent, fieldname, docname);
|
||||
df = frappe.meta.get_docfield(fname[0].parent, table_field, docname);
|
||||
}
|
||||
if (df && df[property] != value) {
|
||||
df[property] = value;
|
||||
refresh_field(fieldname, table_field);
|
||||
this.refresh_field(fieldname);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1690,6 +1690,21 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
this.timeline && this.timeline.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
// Filters fields from the reference doctype and sets them as options for a Select field
|
||||
set_fields_as_options(fieldname, reference_doctype, filter_function, default_options=[], table_fieldname) {
|
||||
if (!reference_doctype) return;
|
||||
let options = default_options;
|
||||
return new Promise(resolve => {
|
||||
frappe.model.with_doctype(reference_doctype, () => {
|
||||
frappe.get_meta(reference_doctype).fields.map(df => {
|
||||
filter_function(df) && options.push({ label: df.label, value: df.fieldname });
|
||||
});
|
||||
options && this.set_df_property(fieldname, 'options', options, this.doc.name, table_fieldname);
|
||||
resolve(options);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
frappe.validated = 0;
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ frappe.form.formatters = {
|
|||
if(frappe.form.link_formatters[doctype]) {
|
||||
// don't apply formatters in case of composite (parent field of same type)
|
||||
if (doc && doctype !== doc.doctype) {
|
||||
value = frappe.form.link_formatters[doctype](value, doc);
|
||||
value = frappe.form.link_formatters[doctype](value, doc, docfield);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -305,7 +305,7 @@ frappe.format = function(value, df, options, doc) {
|
|||
formatted = frappe.dom.remove_script_and_style(formatted);
|
||||
|
||||
return formatted;
|
||||
}
|
||||
};
|
||||
|
||||
frappe.get_format_helper = function(doc) {
|
||||
var helper = {
|
||||
|
|
@ -317,4 +317,9 @@ frappe.get_format_helper = function(doc) {
|
|||
};
|
||||
$.extend(helper, doc);
|
||||
return helper;
|
||||
}
|
||||
};
|
||||
|
||||
frappe.form.link_formatters['User'] = function(value, doc, docfield) {
|
||||
let full_name = doc && (doc.full_name || (docfield && doc[`${docfield.fieldname}_full_name`]));
|
||||
return full_name || value;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export default class Grid {
|
|||
let template = `
|
||||
<label class="control-label">${__(this.df.label || '')}</label>
|
||||
<p class="text-muted small grid-description"></p>
|
||||
<div class="grid-custom-buttons grid-field"></div>
|
||||
<div class="form-grid">
|
||||
<div class="grid-heading-row"></div>
|
||||
<div class="grid-body">
|
||||
|
|
@ -117,6 +118,7 @@ export default class Grid {
|
|||
|
||||
this.custom_buttons = {};
|
||||
this.grid_buttons = this.wrapper.find('.grid-buttons');
|
||||
this.grid_custom_buttons = this.wrapper.find('.grid-custom-buttons');
|
||||
this.remove_rows_button = this.grid_buttons.find('.grid-remove-rows');
|
||||
this.remove_all_rows_button = this.grid_buttons.find('.grid-remove-all-rows');
|
||||
|
||||
|
|
@ -873,18 +875,19 @@ export default class Grid {
|
|||
});
|
||||
}
|
||||
|
||||
add_custom_button(label, click) {
|
||||
add_custom_button(label, click, position='bottom') {
|
||||
// add / unhide a custom button
|
||||
var btn = this.custom_buttons[label];
|
||||
if (!btn) {
|
||||
btn = $('<button class="btn btn-secondary btn-xs btn-custom">' + label + '</button>')
|
||||
.css('margin-right', '4px')
|
||||
.prependTo(this.grid_buttons)
|
||||
const $wrapper = position === 'top' ? this.grid_custom_buttons : this.grid_buttons;
|
||||
let $btn = this.custom_buttons[label];
|
||||
if (!$btn) {
|
||||
$btn = $(`<button class="btn btn-default btn-xs btn-custom">${__(label)}</button>`)
|
||||
.prependTo($wrapper)
|
||||
.on('click', click);
|
||||
this.custom_buttons[label] = btn;
|
||||
this.custom_buttons[label] = $btn;
|
||||
} else {
|
||||
btn.removeClass('hidden');
|
||||
$btn.removeClass('hidden');
|
||||
}
|
||||
return $btn;
|
||||
}
|
||||
|
||||
clear_custom_buttons() {
|
||||
|
|
|
|||
|
|
@ -491,6 +491,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
},
|
||||
set_dependant_property: function(condition, fieldname, property) {
|
||||
let set_property = this.evaluate_depends_on_value(condition);
|
||||
let value = set_property ? 1 : 0;
|
||||
let form_obj;
|
||||
|
||||
if (this.frm) {
|
||||
|
|
@ -499,10 +500,10 @@ frappe.ui.form.Layout = Class.extend({
|
|||
form_obj = this;
|
||||
}
|
||||
if (form_obj) {
|
||||
if (set_property) {
|
||||
form_obj.set_df_property(fieldname, property, 1);
|
||||
if (this.doc && this.doc.parent) {
|
||||
form_obj.set_df_property(this.doc.parentfield, property, value, this.doc.parent, fieldname);
|
||||
} else {
|
||||
form_obj.set_df_property(fieldname, property, 0);
|
||||
form_obj.set_df_property(fieldname, property, value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -334,16 +334,12 @@ frappe.views.BaseList = class BaseList {
|
|||
`<div class="list-paging-area level">
|
||||
<div class="level-left">
|
||||
<div class="btn-group">
|
||||
${paging_values
|
||||
.map(
|
||||
(value) => `
|
||||
${paging_values.map((value) => `
|
||||
<button type="button" class="btn btn-default btn-sm btn-paging"
|
||||
data-value="${value}">
|
||||
${value}
|
||||
</button>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
|
|
@ -373,8 +369,8 @@ frappe.views.BaseList = class BaseList {
|
|||
this.refresh();
|
||||
} else if ($this.is(".btn-more")) {
|
||||
this.start = this.start + this.page_length;
|
||||
this.refresh();
|
||||
}
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
constructor(opts) {
|
||||
$.extend(this, opts);
|
||||
this.make();
|
||||
this.cat_tags = [];
|
||||
}
|
||||
|
||||
make() {
|
||||
|
|
@ -95,32 +94,6 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
this.sidebar.find(".list-stats-dropdown .stat-result").html(tag_list);
|
||||
}
|
||||
|
||||
set_fieldtype(df) {
|
||||
// scrub
|
||||
if (df.fieldname == "docstatus") {
|
||||
df.fieldtype = "Select",
|
||||
df.options = [
|
||||
{ value: 0, label: "Draft" },
|
||||
{ value: 1, label: "Submitted" },
|
||||
{ value: 2, label: "Cancelled" },
|
||||
];
|
||||
} else if (df.fieldtype == 'Check') {
|
||||
df.fieldtype = 'Select';
|
||||
df.options = [{ value: 0, label: 'No' },
|
||||
{ value: 1, label: 'Yes' }
|
||||
];
|
||||
} else if (['Text', 'Small Text', 'Text Editor', 'Code', 'Tag', 'Comments',
|
||||
'Dynamic Link', 'Read Only', 'Assign'
|
||||
].indexOf(df.fieldtype) != -1) {
|
||||
df.fieldtype = 'Data';
|
||||
} else if (df.fieldtype == 'Link' && this.$w.find('.condition').val() != "=") {
|
||||
df.fieldtype = 'Data';
|
||||
}
|
||||
if (df.fieldtype === "Data" && (df.options || "").toLowerCase() === "email") {
|
||||
df.options = null;
|
||||
}
|
||||
}
|
||||
|
||||
reload_stats() {
|
||||
this.sidebar.find(".stat-link").remove();
|
||||
this.sidebar.find(".stat-no-records").remove();
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
});
|
||||
}
|
||||
|
||||
if (this.view_name == 'List') this.toggle_paging = true;
|
||||
|
||||
this.patch_refresh_and_load_lib();
|
||||
return this.get_list_view_settings();
|
||||
}
|
||||
|
|
@ -526,6 +528,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
sort_by: this.sort_selector.sort_by,
|
||||
sort_order: this.sort_selector.sort_order,
|
||||
});
|
||||
this.toggle_paging && this.$paging_area.toggle(false);
|
||||
}
|
||||
|
||||
after_render() {
|
||||
|
|
@ -536,6 +539,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
`);
|
||||
this.setup_new_doc_event();
|
||||
this.list_sidebar && this.list_sidebar.reload_stats();
|
||||
this.toggle_paging && this.$paging_area.toggle(true);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -575,9 +579,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
const subject_field = this.columns[0].df;
|
||||
let subject_html = `
|
||||
<input class="level-item list-check-all hidden-xs" type="checkbox" title="${__(
|
||||
"Select All"
|
||||
)}">
|
||||
<input class="level-item list-check-all hidden-xs" type="checkbox"
|
||||
title="${__("Select All")}">
|
||||
<span class="level-item list-liked-by-me">
|
||||
<span title="${__("Likes")}">${frappe.utils.icon('heart', 'sm', 'like-icon')}</span>
|
||||
</span>
|
||||
|
|
@ -594,12 +597,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
|
||||
return `
|
||||
<div class="${classes}">
|
||||
${
|
||||
col.type === "Subject"
|
||||
? subject_html
|
||||
: `
|
||||
<span>${__((col.df && col.df.label) || col.type)}</span>`
|
||||
}
|
||||
${col.type === "Subject" ? subject_html : `
|
||||
<span>${__((col.df && col.df.label) || col.type)}</span>`}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
|
|
@ -619,9 +618,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
</div>
|
||||
<div class="level-left checkbox-actions">
|
||||
<div class="level list-subject">
|
||||
<input class="level-item list-check-all hidden-xs" type="checkbox" title="${__(
|
||||
"Select All"
|
||||
)}">
|
||||
<input class="level-item list-check-all hidden-xs" type="checkbox"
|
||||
title="${__("Select All")}">
|
||||
<span class="level-item list-header-meta"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -728,10 +726,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
if (df.fieldtype === "Image") {
|
||||
html = df.options
|
||||
? `<img src="${
|
||||
doc[df.options]
|
||||
}" style="max-height: 30px; max-width: 100%;">`
|
||||
html = df.options ? `<img src="${doc[df.options]}"
|
||||
style="max-height: 30px; max-width: 100%;">`
|
||||
: `<div class="missing-image small">
|
||||
<span class="octicon octicon-circle-slash"></span>
|
||||
</div>`;
|
||||
|
|
@ -796,7 +792,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
if (tag) {
|
||||
return `<div class="tag-pill ellipsis" title="${tag}">${tag}</div>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
return user_tags.split(',').slice(1, 3).map(get_tag_html).join('');
|
||||
}
|
||||
|
||||
|
|
@ -871,7 +867,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
return this.settings.get_form_link(doc);
|
||||
}
|
||||
|
||||
const docname = doc.name.match(/[%'"]/)
|
||||
const docname = doc.name.match(/[%'"\s]/)
|
||||
? encodeURIComponent(doc.name)
|
||||
: doc.name;
|
||||
|
||||
|
|
@ -903,7 +899,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
get_subject_html(doc) {
|
||||
let user = frappe.session.user;
|
||||
let subject_field = this.columns[0].df;
|
||||
let value = doc[subject_field.fieldname] || doc.name;
|
||||
let subject = strip_html(value.toString());
|
||||
|
|
@ -912,19 +907,18 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
const seen = this.get_seen_class(doc);
|
||||
|
||||
let subject_html = `
|
||||
<input class="level-item list-row-checkbox hidden-xs" type="checkbox" data-name="${escape(
|
||||
doc.name
|
||||
)}">
|
||||
<input class="level-item list-row-checkbox hidden-xs" type="checkbox"
|
||||
data-name="${escape(doc.name)}">
|
||||
<span class="level-item" style="margin-bottom: 1px;">
|
||||
${this.get_like_html(doc)}
|
||||
</span>
|
||||
<span class="level-item ${seen} ellipsis" title="${escaped_subject}">
|
||||
<a class="ellipsis" href="${this.get_form_link(
|
||||
doc
|
||||
)}" title="${escaped_subject}" data-doctype="${
|
||||
this.doctype
|
||||
}" data-name="${doc.name}">
|
||||
${subject}
|
||||
<a class="ellipsis"
|
||||
href="${this.get_form_link(doc)}"
|
||||
title="${escaped_subject}"
|
||||
data-doctype="${this.doctype}"
|
||||
data-name="${doc.name}">
|
||||
${subject}
|
||||
</a>
|
||||
</span>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ frappe.render_template = function(name, data) {
|
|||
if(data===undefined) {
|
||||
data = {};
|
||||
}
|
||||
if (!template) {
|
||||
frappe.throw(`Template <b>${name}</b> not found.`);
|
||||
}
|
||||
return frappe.render(template, data, name);
|
||||
}
|
||||
frappe.render_grid = function(opts) {
|
||||
|
|
@ -160,4 +163,4 @@ frappe.render_pdf = function(html, opts = {}) {
|
|||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -444,7 +444,7 @@ frappe.request.report_error = function(xhr, request_opts) {
|
|||
var communication_composer = new frappe.views.CommunicationComposer({
|
||||
subject: 'Error Report [' + frappe.datetime.nowdate() + ']',
|
||||
recipients: error_report_email,
|
||||
message: frappe.utils.xss_sanitise(error_report_message),
|
||||
message: error_report_message,
|
||||
doc: {
|
||||
doctype: "User",
|
||||
name: frappe.session.user
|
||||
|
|
|
|||
|
|
@ -118,6 +118,19 @@ frappe.msgprint = function(msg, title, is_minimizable) {
|
|||
data.indicator = 'blue';
|
||||
}
|
||||
|
||||
if (data.as_list) {
|
||||
const list_rows = data.message.map(m => `<li>${m}</li>`).join('');
|
||||
data.message = `<ul style="padding-left: 20px">${list_rows}</ul>`;
|
||||
}
|
||||
|
||||
if (data.as_table) {
|
||||
const rows = data.message.map(row => {
|
||||
const cols = row.map(col => `<td>${col}</td>`).join('');
|
||||
return `<tr>${cols}</tr>`;
|
||||
}).join('');
|
||||
data.message = `<table class="table table-bordered" style="margin: 0;">${rows}</table>`;
|
||||
}
|
||||
|
||||
if(data.message instanceof Array) {
|
||||
data.message.forEach(function(m) {
|
||||
frappe.msgprint(m);
|
||||
|
|
|
|||
|
|
@ -252,33 +252,38 @@ Object.assign(frappe.utils, {
|
|||
</a></p>');
|
||||
return content.html();
|
||||
},
|
||||
scroll_to: function(element, animate, additional_offset, element_to_be_scrolled) {
|
||||
scroll_to: function(element, animate=true, additional_offset, element_to_be_scrolled) {
|
||||
element_to_be_scrolled = element_to_be_scrolled || $("html, body");
|
||||
|
||||
var y = 0;
|
||||
if (element && typeof element==="number") {
|
||||
y = element;
|
||||
} else if (element) {
|
||||
var header_offset = $(".navbar").height() + $(".page-head").height();
|
||||
var y = $(element).offset().top - header_offset - cint(additional_offset);
|
||||
let scroll_top = 0;
|
||||
if (element) {
|
||||
// If a number is passed, just subtract the offset,
|
||||
// otherwise calculate scroll position from element
|
||||
scroll_top = typeof element == "number"
|
||||
? element - cint(additional_offset)
|
||||
: this.get_scroll_position(element, additional_offset);
|
||||
}
|
||||
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
if (scroll_top < 0) {
|
||||
scroll_top = 0;
|
||||
}
|
||||
|
||||
// already there
|
||||
if (y == element_to_be_scrolled.scrollTop()) {
|
||||
if (scroll_top == element_to_be_scrolled.scrollTop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (animate !== false) {
|
||||
element_to_be_scrolled.animate({ scrollTop: y });
|
||||
if (animate) {
|
||||
element_to_be_scrolled.animate({ scrollTop: scroll_top });
|
||||
} else {
|
||||
element_to_be_scrolled.scrollTop(y);
|
||||
element_to_be_scrolled.scrollTop(scroll_top);
|
||||
}
|
||||
|
||||
},
|
||||
get_scroll_position: function(element, additional_offset) {
|
||||
let header_offset = $(".navbar").height() + $(".page-head").height();
|
||||
let scroll_top = $(element).offset().top - header_offset - cint(additional_offset);
|
||||
return scroll_top;
|
||||
},
|
||||
filter_dict: function(dict, filters) {
|
||||
var ret = [];
|
||||
if (typeof filters=='string') {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@ frappe.views.GanttView = class GanttView extends frappe.views.ListView {
|
|||
.then(() => {
|
||||
this.page_title = this.page_title + ' ' + __('Gantt');
|
||||
this.calendar_settings = frappe.views.calendar[this.doctype] || {};
|
||||
if(this.calendar_settings.order_by) {
|
||||
|
||||
if (typeof this.calendar_settings.gantt == 'object') {
|
||||
Object.assign(this.calendar_settings, this.calendar_settings.gantt);
|
||||
}
|
||||
|
||||
if (this.calendar_settings.order_by) {
|
||||
this.sort_by = this.calendar_settings.order_by;
|
||||
this.sort_order = 'asc';
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
|
|||
this.save_kanban_board_filters();
|
||||
}
|
||||
});
|
||||
|
||||
this.toggle_paging = true;
|
||||
return this.get_board();
|
||||
});
|
||||
}
|
||||
|
|
@ -78,6 +78,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
|
|||
this.save_view_user_settings({
|
||||
last_kanban_board: this.board_name
|
||||
});
|
||||
this.toggle_paging && this.$paging_area.toggle(false);
|
||||
}
|
||||
|
||||
render_list() {
|
||||
|
|
|
|||
|
|
@ -1061,7 +1061,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
// applied to Float, Currency fields, needed only for currency formatting.
|
||||
// make first data column have value 'Total'
|
||||
let index = 1;
|
||||
if (this.datatable && this.datatable.options.checkboxColumn) index = 2;
|
||||
|
||||
if (this.report_settings.get_datatable_options) {
|
||||
let datatable = this.report_settings.get_datatable_options({});
|
||||
if (datatable && datatable.checkboxColumn) index = 2;
|
||||
}
|
||||
|
||||
if (column.colIndex === index && !value) {
|
||||
value = "Total";
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue