Merge branch 'develop' of https://github.com/frappe/frappe into rebrand-ui
This commit is contained in:
commit
b94d5778a8
100 changed files with 1993 additions and 926 deletions
|
|
@ -3,7 +3,31 @@ context('Depends On', () => {
|
|||
cy.login();
|
||||
cy.visit('/app/website');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.call('frappe.tests.ui_test_helpers.create_doctype', {
|
||||
return frappe.xcall('frappe.tests.ui_test_helpers.create_child_doctype', {
|
||||
name: 'Child Test Depends On',
|
||||
fields: [
|
||||
{
|
||||
"label": "Child Test Field",
|
||||
"fieldname": "child_test_field",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
},
|
||||
{
|
||||
"label": "Child Dependant Field",
|
||||
"fieldname": "child_dependant_field",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
},
|
||||
{
|
||||
"label": "Child Display Dependant Field",
|
||||
"fieldname": "child_display_dependant_field",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
},
|
||||
]
|
||||
});
|
||||
}).then(frappe => {
|
||||
return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
|
||||
name: 'Test Depends On',
|
||||
fields: [
|
||||
{
|
||||
|
|
@ -24,6 +48,13 @@ context('Depends On', () => {
|
|||
"fieldtype": "Data",
|
||||
'depends_on': "eval:doc.test_field=='Value'"
|
||||
},
|
||||
{
|
||||
"label": "Child Test Depends On Field",
|
||||
"fieldname": "child_test_depends_on_field",
|
||||
"fieldtype": "Table",
|
||||
'read_only_depends_on': "eval:doc.test_field=='Some Other Value'",
|
||||
'options': "Child Test Depends On"
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
|
@ -48,6 +79,30 @@ context('Depends On', () => {
|
|||
cy.get('body').click();
|
||||
cy.get('.control-input [data-fieldname="dependant_field"]').should('not.be.disabled');
|
||||
});
|
||||
it('should set the table and its fields as read only depending on other fields value', () => {
|
||||
cy.new_form('Test Depends On');
|
||||
cy.fill_field('dependant_field', 'Some Value');
|
||||
//cy.fill_field('test_field', 'Some Other Value');
|
||||
cy.get('.frappe-control[data-fieldname="child_test_depends_on_field"]').as('table');
|
||||
cy.get('@table').find('button.grid-add-row').click();
|
||||
cy.get('@table').find('[data-idx="1"]').as('row1');
|
||||
cy.get('@row1').find('.btn-open-row').click();
|
||||
cy.get('@row1').find('.form-in-grid').as('row1-form_in_grid');
|
||||
//cy.get('@row1-form_in_grid').find('')
|
||||
cy.fill_table_field('child_test_depends_on_field', '1', 'child_test_field', 'Some Value');
|
||||
cy.fill_table_field('child_test_depends_on_field', '1', 'child_dependant_field', 'Some Other Value');
|
||||
|
||||
cy.get('@row1-form_in_grid').find('.octicon-triangle-up').click();
|
||||
|
||||
// set the table to read-only
|
||||
cy.fill_field('test_field', 'Some Other Value');
|
||||
|
||||
// grid row form fields should be read-only
|
||||
cy.get('@row1').find('.btn-open-row').click();
|
||||
|
||||
cy.get('@row1-form_in_grid').find('.control-input [data-fieldname="child_test_field"]').should('be.disabled');
|
||||
cy.get('@row1-form_in_grid').find('.control-input [data-fieldname="child_dependant_field"]').should('be.disabled');
|
||||
});
|
||||
it('should display the field depending on other fields value', () => {
|
||||
cy.new_form('Test Depends On');
|
||||
cy.get('.control-input [data-fieldname="display_dependant_field"]').should('not.be.visible');
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ Cypress.Commands.add('remove_doc', (doctype, name) => {
|
|||
|
||||
Cypress.Commands.add('create_records', doc => {
|
||||
return cy
|
||||
.call('frappe.tests.ui_test_helpers.create_if_not_exists', { doc })
|
||||
.call('frappe.tests.ui_test_helpers.create_if_not_exists', {doc})
|
||||
.then(r => r.message);
|
||||
});
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
|
|||
if (fieldtype === 'Select') {
|
||||
cy.get('@input').select(value);
|
||||
} else {
|
||||
cy.get('@input').type(value, { waitForAnimations: false, force: true });
|
||||
cy.get('@input').type(value, {waitForAnimations: false, force: true});
|
||||
}
|
||||
return cy.get('@input');
|
||||
});
|
||||
|
|
@ -204,8 +204,43 @@ Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => {
|
|||
return cy.get(selector);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('fill_table_field', (tablefieldname, row_idx, fieldname, value, fieldtype = 'Data') => {
|
||||
cy.get_table_field(tablefieldname, row_idx, fieldname, fieldtype).as('input');
|
||||
|
||||
if (['Date', 'Time', 'Datetime'].includes(fieldtype)) {
|
||||
cy.get('@input').click().wait(200);
|
||||
cy.get('.datepickers-container .datepicker.active').should('exist');
|
||||
}
|
||||
if (fieldtype === 'Time') {
|
||||
cy.get('@input').clear().wait(200);
|
||||
}
|
||||
|
||||
if (fieldtype === 'Select') {
|
||||
cy.get('@input').select(value);
|
||||
} else {
|
||||
cy.get('@input').type(value, {waitForAnimations: false, force: true});
|
||||
}
|
||||
return cy.get('@input');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_table_field', (tablefieldname, row_idx, fieldname, fieldtype = 'Data') => {
|
||||
let selector = `.frappe-control[data-fieldname="${tablefieldname}"]`;
|
||||
selector += ` [data-idx="${row_idx}"]`;
|
||||
selector += ` .form-in-grid`;
|
||||
|
||||
if (fieldtype === 'Text Editor') {
|
||||
selector += ` [data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`;
|
||||
} else if (fieldtype === 'Code') {
|
||||
selector += ` [data-fieldname="${fieldname}"] .ace_text-input`;
|
||||
} else {
|
||||
selector += ` .form-control[data-fieldname="${fieldname}"]`;
|
||||
}
|
||||
|
||||
return cy.get(selector);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('awesomebar', text => {
|
||||
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, { delay: 100 });
|
||||
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, {delay: 100});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('new_form', doctype => {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ __version__ = '13.0.0-dev'
|
|||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
controllers = {}
|
||||
|
||||
class _dict(dict):
|
||||
"""dict like object that exposes keys as attributes"""
|
||||
|
|
@ -628,6 +629,21 @@ def clear_cache(user=None, doctype=None):
|
|||
|
||||
local.role_permissions = {}
|
||||
|
||||
def only_has_select_perm(doctype, user=None, ignore_permissions=False):
|
||||
if ignore_permissions:
|
||||
return False
|
||||
|
||||
if not user:
|
||||
user = local.session.user
|
||||
|
||||
import frappe.permissions
|
||||
permissions = frappe.permissions.get_role_permissions(doctype, user=user)
|
||||
|
||||
if permissions.get('select') and not permissions.get('read'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=False, throw=False):
|
||||
"""Raises `frappe.PermissionError` if not permitted.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import os
|
|||
from six import iteritems
|
||||
import logging
|
||||
|
||||
from werkzeug.wrappers import Request
|
||||
from werkzeug.local import LocalManager
|
||||
from werkzeug.wrappers import Request, Response
|
||||
from werkzeug.exceptions import HTTPException, NotFound
|
||||
from werkzeug.middleware.profiler import ProfilerMiddleware
|
||||
from werkzeug.middleware.shared_data import SharedDataMiddleware
|
||||
|
|
@ -57,19 +57,22 @@ def application(request):
|
|||
frappe.monitor.start()
|
||||
frappe.rate_limiter.apply()
|
||||
|
||||
if frappe.local.form_dict.cmd:
|
||||
if request.method == "OPTIONS":
|
||||
response = Response()
|
||||
|
||||
elif frappe.form_dict.cmd:
|
||||
response = frappe.handler.handle()
|
||||
|
||||
elif frappe.request.path.startswith("/api/"):
|
||||
elif request.path.startswith("/api/"):
|
||||
response = frappe.api.handle()
|
||||
|
||||
elif frappe.request.path.startswith('/backups'):
|
||||
elif request.path.startswith('/backups'):
|
||||
response = frappe.utils.response.download_backup(request.path)
|
||||
|
||||
elif frappe.request.path.startswith('/private/files/'):
|
||||
elif request.path.startswith('/private/files/'):
|
||||
response = frappe.utils.response.download_private_file(request.path)
|
||||
|
||||
elif frappe.local.request.method in ('GET', 'HEAD', 'POST'):
|
||||
elif request.method in ('GET', 'HEAD', 'POST'):
|
||||
response = frappe.website.render.render()
|
||||
|
||||
else:
|
||||
|
|
@ -88,13 +91,9 @@ def application(request):
|
|||
rollback = after_request(rollback)
|
||||
|
||||
finally:
|
||||
if frappe.local.request.method in ("POST", "PUT") and frappe.db and rollback:
|
||||
if request.method in ("POST", "PUT") and frappe.db and rollback:
|
||||
frappe.db.rollback()
|
||||
|
||||
# set cookies
|
||||
if response and hasattr(frappe.local, 'cookie_manager'):
|
||||
frappe.local.cookie_manager.flush_cookies(response=response)
|
||||
|
||||
frappe.rate_limiter.update()
|
||||
frappe.monitor.stop(response)
|
||||
frappe.recorder.dump()
|
||||
|
|
@ -110,9 +109,7 @@ def application(request):
|
|||
"http_status_code": getattr(response, "status_code", "NOTFOUND")
|
||||
})
|
||||
|
||||
if response and hasattr(frappe.local, 'rate_limiter'):
|
||||
response.headers.extend(frappe.local.rate_limiter.headers())
|
||||
|
||||
process_response(response)
|
||||
frappe.destroy()
|
||||
|
||||
return response
|
||||
|
|
@ -134,7 +131,46 @@ def init_request(request):
|
|||
|
||||
make_form_dict(request)
|
||||
|
||||
frappe.local.http_request = frappe.auth.HTTPRequest()
|
||||
if request.method != "OPTIONS":
|
||||
frappe.local.http_request = frappe.auth.HTTPRequest()
|
||||
|
||||
def process_response(response):
|
||||
if not response:
|
||||
return
|
||||
|
||||
# set cookies
|
||||
if hasattr(frappe.local, 'cookie_manager'):
|
||||
frappe.local.cookie_manager.flush_cookies(response=response)
|
||||
|
||||
# rate limiter headers
|
||||
if hasattr(frappe.local, 'rate_limiter'):
|
||||
response.headers.extend(frappe.local.rate_limiter.headers())
|
||||
|
||||
# CORS headers
|
||||
if hasattr(frappe.local, 'conf') and frappe.conf.allow_cors:
|
||||
set_cors_headers(response)
|
||||
|
||||
def set_cors_headers(response):
|
||||
origin = frappe.request.headers.get('Origin')
|
||||
if not origin:
|
||||
return
|
||||
|
||||
allow_cors = frappe.conf.allow_cors
|
||||
if allow_cors != "*":
|
||||
if not isinstance(allow_cors, list):
|
||||
allow_cors = [allow_cors]
|
||||
|
||||
if origin not in allow_cors:
|
||||
return
|
||||
|
||||
response.headers.extend({
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': ('Authorization,DNT,X-Mx-ReqToken,'
|
||||
'Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,'
|
||||
'Cache-Control,Content-Type')
|
||||
})
|
||||
|
||||
def make_form_dict(request):
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -54,10 +54,12 @@ frappe.ui.form.on('Auto Repeat', {
|
|||
|
||||
toggle_submit_on_creation: function(frm) {
|
||||
// submit on creation checkbox
|
||||
frappe.model.with_doctype(frm.doc.reference_doctype, () => {
|
||||
let meta = frappe.get_meta(frm.doc.reference_doctype);
|
||||
frm.toggle_display('submit_on_creation', meta.is_submittable);
|
||||
});
|
||||
if (frm.doc.reference_doctype) {
|
||||
frappe.model.with_doctype(frm.doc.reference_doctype, () => {
|
||||
let meta = frappe.get_meta(frm.doc.reference_doctype);
|
||||
frm.toggle_display('submit_on_creation', meta.is_submittable);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
template: function(frm) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
"repeat_on_last_day",
|
||||
"column_break_12",
|
||||
"next_schedule_date",
|
||||
"section_break_12",
|
||||
"section_break_16",
|
||||
"repeat_on_days",
|
||||
"notification",
|
||||
"notify_by_email",
|
||||
|
|
@ -198,20 +198,20 @@
|
|||
"label": "Repeat on Days",
|
||||
"options": "Auto Repeat Day"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.frequency==='Weekly';",
|
||||
"fieldname": "section_break_12",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "submit_on_creation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Submit on Creation"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.frequency==='Weekly';",
|
||||
"fieldname": "section_break_16",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-12-10 10:43:13.449172",
|
||||
"modified": "2021-01-12 09:24:49.719611",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Auto Repeat",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ def clear_defaults_cache(user=None):
|
|||
frappe.cache().delete_key("defaults")
|
||||
|
||||
def clear_doctype_cache(doctype=None):
|
||||
clear_controller_cache(doctype)
|
||||
cache = frappe.cache()
|
||||
|
||||
if getattr(frappe.local, 'meta_cache') and (doctype in frappe.local.meta_cache):
|
||||
|
|
@ -99,6 +100,18 @@ def clear_doctype_cache(doctype=None):
|
|||
for name in doctype_cache_keys:
|
||||
cache.delete_value(name)
|
||||
|
||||
# Clear all document's cache. To clear documents of a specific DocType document_cache should be restructured
|
||||
clear_document_cache()
|
||||
|
||||
def clear_controller_cache(doctype=None):
|
||||
if not doctype:
|
||||
del frappe.controllers
|
||||
frappe.controllers = {}
|
||||
return
|
||||
|
||||
for site_controllers in frappe.controllers.values():
|
||||
site_controllers.pop(doctype, None)
|
||||
|
||||
def get_doctype_map(doctype, name, filters=None, order_by=None):
|
||||
cache = frappe.cache()
|
||||
cache_key = frappe.scrub(doctype) + '_map'
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments):
|
|||
try:
|
||||
# use sql, so that we do not mess with the timestamp
|
||||
frappe.db.sql("""update `tab{0}` set `_comments`=%s where name=%s""".format(reference_doctype), # nosec
|
||||
(json.dumps(_comments[-50:]), reference_name))
|
||||
(json.dumps(_comments[-100:]), reference_name))
|
||||
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e) and getattr(frappe.local, 'request', None):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"autoname": "hash",
|
||||
"creation": "2017-01-11 04:21:35.217943",
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
"column_break_2",
|
||||
"permlevel",
|
||||
"section_break_4",
|
||||
"select",
|
||||
"read",
|
||||
"write",
|
||||
"create",
|
||||
|
|
@ -211,9 +213,16 @@
|
|||
"fieldtype": "Data",
|
||||
"label": "Reference Document Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "select",
|
||||
"fieldtype": "Check",
|
||||
"label": "Select"
|
||||
}
|
||||
],
|
||||
"modified": "2019-10-31 16:58:16.157079",
|
||||
"links": [],
|
||||
"modified": "2020-12-03 15:20:48.296730",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Custom DocPerm",
|
||||
|
|
|
|||
|
|
@ -751,7 +751,7 @@ class Row:
|
|||
self.warnings.append(
|
||||
{
|
||||
"row": self.row_number,
|
||||
"message": _("{0} is a mandatory field asdadsf").format(id_field.label),
|
||||
"message": _("{0} is a mandatory field").format(id_field.label),
|
||||
}
|
||||
)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,775 +1,229 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:33",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"role_and_level",
|
||||
"role",
|
||||
"if_owner",
|
||||
"column_break_2",
|
||||
"permlevel",
|
||||
"section_break_4",
|
||||
"select",
|
||||
"read",
|
||||
"write",
|
||||
"create",
|
||||
"delete",
|
||||
"column_break_8",
|
||||
"submit",
|
||||
"cancel",
|
||||
"amend",
|
||||
"additional_permissions",
|
||||
"report",
|
||||
"export",
|
||||
"import",
|
||||
"set_user_permissions",
|
||||
"column_break_19",
|
||||
"share",
|
||||
"print",
|
||||
"email"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "role_and_level",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Role and Level",
|
||||
"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": "Role and Level"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "role",
|
||||
"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": "Role",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "role",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Role",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "150px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "150px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"description": "Apply this rule if the User is the Owner",
|
||||
"fieldname": "if_owner",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "If user is the owner",
|
||||
"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
|
||||
"label": "If user is the owner"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "permlevel",
|
||||
"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": "Level",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "permlevel",
|
||||
"oldfieldtype": "Int",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "40px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "40px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Permissions",
|
||||
"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": "Permissions"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "read",
|
||||
"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": "Read",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "read",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "write",
|
||||
"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": "Write",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "write",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "create",
|
||||
"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": "Create",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "create",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "delete",
|
||||
"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": "Delete",
|
||||
"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": "Delete"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "submit",
|
||||
"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": "Submit",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "submit",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "cancel",
|
||||
"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": "Cancel",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "cancel",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "amend",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amend",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "amend",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "additional_permissions",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Additional Permissions",
|
||||
"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": "Additional Permissions"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "report",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Report",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "export",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Export",
|
||||
"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": "Export"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "import",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Import",
|
||||
"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": "Import"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"description": "This role update User Permissions for a user",
|
||||
"fieldname": "set_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Set User Permissions",
|
||||
"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": "Set User Permissions"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "share",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Share",
|
||||
"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
|
||||
"label": "Share"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "print",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Print",
|
||||
"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": "Print"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "email",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Email",
|
||||
"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": "Email"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "select",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Select"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-05-29 11:54:38.613936",
|
||||
"links": [],
|
||||
"modified": "2020-12-03 15:15:30.488212",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocPerm",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import re, copy, os, shutil
|
||||
import json
|
||||
from frappe.cache_manager import clear_user_cache
|
||||
from frappe.cache_manager import clear_user_cache, clear_controller_cache
|
||||
|
||||
# imports - third party imports
|
||||
import six
|
||||
|
|
@ -395,13 +395,11 @@ class DocType(Document):
|
|||
if not frappe.flags.in_patch:
|
||||
self.rename_files_and_folders(old, new)
|
||||
|
||||
for site in frappe.utils.get_sites():
|
||||
frappe.cache().delete(f"{site}:doctype_classes", old)
|
||||
clear_controller_cache(old)
|
||||
|
||||
def after_delete(self):
|
||||
if not self.custom:
|
||||
for site in frappe.utils.get_sites():
|
||||
frappe.cache().delete(f"{site}:doctype_classes", self.name)
|
||||
clear_controller_cache(self.name)
|
||||
|
||||
def rename_files_and_folders(self, old, new):
|
||||
# move files
|
||||
|
|
@ -1004,10 +1002,10 @@ def validate_fields(meta):
|
|||
check_sort_field(meta)
|
||||
check_image_field(meta)
|
||||
|
||||
def validate_permissions_for_doctype(doctype, for_remove=False):
|
||||
def validate_permissions_for_doctype(doctype, for_remove=False, alert=False):
|
||||
"""Validates if permissions are set correctly."""
|
||||
doctype = frappe.get_doc("DocType", doctype)
|
||||
validate_permissions(doctype, for_remove)
|
||||
validate_permissions(doctype, for_remove, alert=alert)
|
||||
|
||||
# save permissions
|
||||
for perm in doctype.get("permissions"):
|
||||
|
|
@ -1030,9 +1028,10 @@ def clear_permissions_cache(doctype):
|
|||
""", doctype):
|
||||
frappe.clear_cache(user=user)
|
||||
|
||||
def validate_permissions(doctype, for_remove=False):
|
||||
def validate_permissions(doctype, for_remove=False, alert=False):
|
||||
permissions = doctype.get("permissions")
|
||||
if not permissions:
|
||||
# Some DocTypes may not have permissions by default, don't show alert for them
|
||||
if not permissions and alert:
|
||||
frappe.msgprint(_('No Permissions Specified'), alert=True, indicator='orange')
|
||||
issingle = issubmittable = isimportable = False
|
||||
if doctype:
|
||||
|
|
@ -1044,7 +1043,7 @@ def validate_permissions(doctype, for_remove=False):
|
|||
return _("For {0} at level {1} in {2} in row {3}").format(d.role, d.permlevel, d.parent, d.idx)
|
||||
|
||||
def check_atleast_one_set(d):
|
||||
if not d.read and not d.write and not d.submit and not d.cancel and not d.create:
|
||||
if not d.select and not d.read and not d.write and not d.submit and not d.cancel and not d.create:
|
||||
frappe.throw(_("{0}: No basic permissions set").format(get_txt(d)))
|
||||
|
||||
def check_double(d):
|
||||
|
|
|
|||
|
|
@ -6,8 +6,19 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.data import evaluate_filters
|
||||
from frappe import _
|
||||
|
||||
class DocumentNamingRule(Document):
|
||||
def validate(self):
|
||||
self.validate_fields_in_conditions()
|
||||
|
||||
def validate_fields_in_conditions(self):
|
||||
if self.has_value_changed("document_type"):
|
||||
docfields = [x.fieldname for x in frappe.get_meta(self.document_type).fields]
|
||||
for condition in self.conditions:
|
||||
if condition.field not in docfields:
|
||||
frappe.throw(_("{0} is not a field of doctype {1}").format(frappe.bold(condition.field), frappe.bold(self.document_type)))
|
||||
|
||||
def apply(self, doc):
|
||||
'''
|
||||
Apply naming rules for the given document. Will set `name` if the rule is matched.
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
"fieldname": "doctype_event",
|
||||
"fieldtype": "Select",
|
||||
"label": "DocType Event",
|
||||
"options": "Before Insert\nBefore Save\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)"
|
||||
"options": "Before Insert\nBefore Validate\nBefore Save\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.script_type==='API'",
|
||||
|
|
@ -88,7 +88,7 @@
|
|||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-03 22:42:02.708148",
|
||||
"modified": "2021-01-03 18:50:14.767595",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Server Script",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import frappe
|
|||
EVENT_MAP = {
|
||||
'before_insert': 'Before Insert',
|
||||
'after_insert': 'After Insert',
|
||||
'before_validate': 'Before Validate',
|
||||
'validate': 'Before Save',
|
||||
'on_update': 'After Save',
|
||||
'before_submit': 'Before Submit',
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@
|
|||
"collapsible": 1,
|
||||
"fieldname": "email",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "EMail"
|
||||
"label": "Email"
|
||||
},
|
||||
{
|
||||
"description": "Your organization name and address for the email footer.",
|
||||
|
|
@ -504,4 +504,4 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -654,6 +654,11 @@
|
|||
"group": "Activity",
|
||||
"link_doctype": "ToDo",
|
||||
"link_fieldname": "owner"
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"link_doctype": "Token Cache",
|
||||
"link_fieldname": "user"
|
||||
}
|
||||
],
|
||||
"max_attachments": 5,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<td class="danger">{{ item[1] }}</td>
|
||||
<td class="success">{{ item[2] }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
@ -93,4 +93,4 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
}
|
||||
|
||||
get rights() {
|
||||
return ["read", "write", "create", "delete", "submit", "cancel", "amend",
|
||||
return ["select", "read", "write", "create", "delete", "submit", "cancel", "amend",
|
||||
"print", "email", "report", "import", "export", "set_user_permissions", "share"]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,18 @@ def add(parent, role, permlevel):
|
|||
|
||||
@frappe.whitelist()
|
||||
def update(doctype, role, permlevel, ptype, value=None):
|
||||
"""Update role permission params
|
||||
|
||||
Args:
|
||||
doctype (str): Name of the DocType to update params for
|
||||
role (str): Role to be updated for, eg "Website Manager".
|
||||
permlevel (int): perm level the provided rule applies to
|
||||
ptype (str): permission type, example "read", "delete", etc.
|
||||
value (None, optional): value for ptype, None indicates False
|
||||
|
||||
Returns:
|
||||
str: Refresh flag is permission is updated successfully
|
||||
"""
|
||||
frappe.only_for("System Manager")
|
||||
out = update_permission_property(doctype, role, permlevel, ptype, value)
|
||||
return 'refresh' if out else None
|
||||
|
|
@ -92,7 +104,7 @@ def remove(doctype, role, permlevel):
|
|||
if not frappe.get_all('Custom DocPerm', dict(parent=doctype)):
|
||||
frappe.throw(_('There must be atleast one permission rule.'), title=_('Cannot Remove'))
|
||||
|
||||
validate_permissions_for_doctype(doctype, for_remove=True)
|
||||
validate_permissions_for_doctype(doctype, for_remove=True, alert=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def reset(doctype):
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ def cancel(doctype=None, name=None, workflow_state_fieldname=None, workflow_stat
|
|||
|
||||
except Exception:
|
||||
frappe.errprint(frappe.utils.get_traceback())
|
||||
frappe.msgprint(frappe._("Did not cancel"))
|
||||
raise
|
||||
|
||||
def send_updated_docs(doc):
|
||||
|
|
|
|||
|
|
@ -150,7 +150,8 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
|
|||
# 2 is the index of _relevance column
|
||||
order_by = "_relevance, {0}, `tab{1}`.idx desc".format(order_by_based_on_meta, doctype)
|
||||
|
||||
ignore_permissions = True if doctype == "DocType" else (cint(ignore_user_permissions) and has_permission(doctype))
|
||||
ptype = 'select' if frappe.only_has_select_perm(doctype) else 'read'
|
||||
ignore_permissions = True if doctype == "DocType" else (cint(ignore_user_permissions) and has_permission(doctype, ptype=ptype))
|
||||
|
||||
if doctype in UNTRANSLATED_DOCTYPES:
|
||||
page_length = None
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ class EmailAccount(Document):
|
|||
elif not in_receive and any(map(lambda t: t in message, auth_error_codes)):
|
||||
self.throw_invalid_credentials_exception()
|
||||
else:
|
||||
frappe.throw(e)
|
||||
frappe.throw(cstr(e))
|
||||
|
||||
except socket.error:
|
||||
if in_receive:
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ def set_update(update, producer_site):
|
|||
if data.changed:
|
||||
local_doc.update(data.changed)
|
||||
if data.removed:
|
||||
update_row_removed(local_doc, data.removed)
|
||||
local_doc = update_row_removed(local_doc, data.removed)
|
||||
if data.row_changed:
|
||||
update_row_changed(local_doc, data.row_changed)
|
||||
if data.added:
|
||||
|
|
@ -318,7 +318,17 @@ def update_row_removed(local_doc, removed):
|
|||
for tablename, rownames in iteritems(removed):
|
||||
table = local_doc.get_table_field_doctype(tablename)
|
||||
for row in rownames:
|
||||
frappe.db.delete(table, row)
|
||||
table_rows = local_doc.get(tablename)
|
||||
child_table_row = get_child_table_row(table_rows, row)
|
||||
table_rows.remove(child_table_row)
|
||||
local_doc.set(tablename, table_rows)
|
||||
return local_doc
|
||||
|
||||
|
||||
def get_child_table_row(table_rows, row):
|
||||
for entry in table_rows:
|
||||
if entry.get('name') == row:
|
||||
return entry
|
||||
|
||||
|
||||
def update_row_changed(local_doc, changed):
|
||||
|
|
|
|||
96
frappe/geo/utils.py
Normal file
96
frappe/geo/utils.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
from pymysql import InternalError
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_coords(doctype, filters, type):
|
||||
'''Get a geojson dict representing a doctype.'''
|
||||
filters_sql = get_coords_conditions(doctype, filters)[4:]
|
||||
|
||||
coords = None
|
||||
if type == 'location_field':
|
||||
coords = return_location(doctype, filters_sql)
|
||||
elif type == 'coordinates':
|
||||
coords = return_coordinates(doctype, filters_sql)
|
||||
|
||||
out = convert_to_geojson(type, coords)
|
||||
return out
|
||||
|
||||
def convert_to_geojson(type, coords):
|
||||
'''Converts GPS coordinates to geoJSON string.'''
|
||||
geojson = {"type": "FeatureCollection", "features": None}
|
||||
|
||||
if type == 'location_field':
|
||||
geojson['features'] = merge_location_features_in_one(coords)
|
||||
elif type == 'coordinates':
|
||||
geojson['features'] = create_gps_markers(coords)
|
||||
|
||||
return geojson
|
||||
|
||||
|
||||
def merge_location_features_in_one(coords):
|
||||
'''Merging all features from location field.'''
|
||||
geojson_dict = []
|
||||
for element in coords:
|
||||
geojson_loc = frappe.parse_json(element['location'])
|
||||
if not geojson_loc:
|
||||
continue
|
||||
for coord in geojson_loc['features']:
|
||||
coord['properties']['name'] = element['name']
|
||||
geojson_dict.append(coord.copy())
|
||||
|
||||
return geojson_dict
|
||||
|
||||
|
||||
def create_gps_markers(coords):
|
||||
'''Build Marker based on latitude and longitude.'''
|
||||
geojson_dict = []
|
||||
for i in coords:
|
||||
node = {"type": "Feature", "properties": {}, "geometry": {"type": "Point", "coordinates": None}}
|
||||
node['properties']['name'] = i.name
|
||||
node['geometry']['coordinates'] = [i.latitude, i.longitude]
|
||||
geojson_dict.append(node.copy())
|
||||
|
||||
return geojson_dict
|
||||
|
||||
|
||||
def return_location(doctype, filters_sql):
|
||||
'''Get name and location fields for Doctype.'''
|
||||
if filters_sql:
|
||||
try:
|
||||
coords = frappe.db.sql('''SELECT name, location FROM `tab{}` WHERE {}'''.format(doctype, filters_sql), as_dict=True)
|
||||
except InternalError:
|
||||
frappe.msgprint(frappe._('This Doctype does not contain location fields'), raise_exception=True)
|
||||
return
|
||||
else:
|
||||
coords = frappe.get_all(doctype, fields=['name', 'location'])
|
||||
return coords
|
||||
|
||||
|
||||
def return_coordinates(doctype, filters_sql):
|
||||
'''Get name, latitude and longitude fields for Doctype.'''
|
||||
if filters_sql:
|
||||
try:
|
||||
coords = frappe.db.sql('''SELECT name, latitude, longitude FROM `tab{}` WHERE {}'''.format(doctype, filters_sql), as_dict=True)
|
||||
except InternalError:
|
||||
frappe.msgprint(frappe._('This Doctype does not contain latitude and longitude fields'), raise_exception=True)
|
||||
return
|
||||
else:
|
||||
coords = frappe.get_all(doctype, fields=['name', 'latitude', 'longitude'])
|
||||
return coords
|
||||
|
||||
|
||||
def get_coords_conditions(doctype, filters=None):
|
||||
'''Returns SQL conditions with user permissions and filters for event queries.'''
|
||||
from frappe.desk.reportview import get_filters_cond
|
||||
if not frappe.has_permission(doctype):
|
||||
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)
|
||||
|
||||
return get_filters_cond(doctype, filters, [], with_match_conditions=True)
|
||||
|
|
@ -18,7 +18,7 @@ app_email = "info@frappe.io"
|
|||
|
||||
docs_app = "frappe_io"
|
||||
|
||||
translator_url = "https://translatev2.erpnext.com"
|
||||
translator_url = "https://translate.erpnext.com"
|
||||
|
||||
before_install = "frappe.utils.install.before_install"
|
||||
after_install = "frappe.utils.install.after_install"
|
||||
|
|
|
|||
0
frappe/integrations/doctype/connected_app/__init__.py
Normal file
0
frappe/integrations/doctype/connected_app/__init__.py
Normal file
38
frappe/integrations/doctype/connected_app/connected_app.js
Normal file
38
frappe/integrations/doctype/connected_app/connected_app.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Connected App', {
|
||||
refresh: frm => {
|
||||
frm.add_custom_button(__('Get OpenID Configuration'), async () => {
|
||||
if (!frm.doc.openid_configuration) {
|
||||
frappe.msgprint(__('Please enter OpenID Configuration URL'));
|
||||
} else {
|
||||
try {
|
||||
const response = await fetch(frm.doc.openid_configuration);
|
||||
const oidc = await response.json();
|
||||
frm.set_value('authorization_uri', oidc.authorization_endpoint);
|
||||
frm.set_value('token_uri', oidc.token_endpoint);
|
||||
frm.set_value('userinfo_uri', oidc.userinfo_endpoint);
|
||||
frm.set_value('introspection_uri', oidc.introspection_endpoint);
|
||||
frm.set_value('revocation_uri', oidc.revocation_endpoint);
|
||||
} catch (error) {
|
||||
frappe.msgprint(__('Please check OpenID Configuration URL'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!frm.is_new()) {
|
||||
frm.add_custom_button(__('Connect to {}', [frm.doc.provider_name]), async () => {
|
||||
frappe.call({
|
||||
method: 'initiate_web_application_flow',
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
window.open(r.message, '_blank');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
frm.toggle_display('sb_client_credentials_section', !frm.is_new());
|
||||
}
|
||||
});
|
||||
166
frappe/integrations/doctype/connected_app/connected_app.json
Normal file
166
frappe/integrations/doctype/connected_app/connected_app.json
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
{
|
||||
"actions": [],
|
||||
"beta": 1,
|
||||
"creation": "2019-01-24 15:51:06.362222",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"provider_name",
|
||||
"cb_00",
|
||||
"openid_configuration",
|
||||
"sb_client_credentials_section",
|
||||
"client_id",
|
||||
"redirect_uri",
|
||||
"cb_01",
|
||||
"client_secret",
|
||||
"sb_scope_section",
|
||||
"scopes",
|
||||
"sb_endpoints_section",
|
||||
"authorization_uri",
|
||||
"token_uri",
|
||||
"revocation_uri",
|
||||
"cb_02",
|
||||
"userinfo_uri",
|
||||
"introspection_uri",
|
||||
"section_break_18",
|
||||
"query_parameters"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "provider_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Provider Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cb_00",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "openid_configuration",
|
||||
"fieldtype": "Data",
|
||||
"label": "OpenID Configuration"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sb_client_credentials_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Client Credentials"
|
||||
},
|
||||
{
|
||||
"fieldname": "client_id",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Client Id"
|
||||
},
|
||||
{
|
||||
"fieldname": "redirect_uri",
|
||||
"fieldtype": "Data",
|
||||
"label": "Redirect URI",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cb_01",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "client_secret",
|
||||
"fieldtype": "Password",
|
||||
"label": "Client Secret"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sb_scope_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Scopes"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sb_endpoints_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Endpoints"
|
||||
},
|
||||
{
|
||||
"fieldname": "cb_02",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "scopes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Scopes",
|
||||
"options": "OAuth Scope"
|
||||
},
|
||||
{
|
||||
"fieldname": "authorization_uri",
|
||||
"fieldtype": "Data",
|
||||
"label": "Authorization URI"
|
||||
},
|
||||
{
|
||||
"fieldname": "token_uri",
|
||||
"fieldtype": "Data",
|
||||
"label": "Token URI"
|
||||
},
|
||||
{
|
||||
"fieldname": "revocation_uri",
|
||||
"fieldtype": "Data",
|
||||
"label": "Revocation URI"
|
||||
},
|
||||
{
|
||||
"fieldname": "userinfo_uri",
|
||||
"fieldtype": "Data",
|
||||
"label": "Userinfo URI"
|
||||
},
|
||||
{
|
||||
"fieldname": "introspection_uri",
|
||||
"fieldtype": "Data",
|
||||
"label": "Introspection URI"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_18",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Extra Parameters"
|
||||
},
|
||||
{
|
||||
"fieldname": "query_parameters",
|
||||
"fieldtype": "Table",
|
||||
"label": "Query Parameters",
|
||||
"options": "Query Parameters"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Token Cache",
|
||||
"link_fieldname": "connected_app"
|
||||
}
|
||||
],
|
||||
"modified": "2020-11-16 16:29:50.277405",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Connected App",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "provider_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
133
frappe/integrations/doctype/connected_app/connected_app.py
Normal file
133
frappe/integrations/doctype/connected_app/connected_app.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import os
|
||||
from urllib.parse import urljoin
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from requests_oauthlib import OAuth2Session
|
||||
|
||||
if any((os.getenv('CI'), frappe.conf.developer_mode, frappe.conf.allow_tests)):
|
||||
# Disable mandatory TLS in developer mode and tests
|
||||
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
|
||||
|
||||
class ConnectedApp(Document):
|
||||
"""Connect to a remote oAuth Server. Retrieve and store user's access token
|
||||
in a Token Cache.
|
||||
"""
|
||||
|
||||
def validate(self):
|
||||
base_url = frappe.utils.get_url()
|
||||
callback_path = '/api/method/frappe.integrations.doctype.connected_app.connected_app.callback/' + self.name
|
||||
self.redirect_uri = urljoin(base_url, callback_path)
|
||||
|
||||
def get_oauth2_session(self, user=None, init=False):
|
||||
token = None
|
||||
token_updater = None
|
||||
|
||||
if not init:
|
||||
user = user or frappe.session.user
|
||||
token_cache = self.get_user_token(user)
|
||||
token = token_cache.get_json()
|
||||
token_updater = token_cache.update_data
|
||||
|
||||
return OAuth2Session(
|
||||
client_id=self.client_id,
|
||||
token=token,
|
||||
token_updater=token_updater,
|
||||
auto_refresh_url=self.token_uri,
|
||||
redirect_uri=self.redirect_uri,
|
||||
scope=self.get_scopes()
|
||||
)
|
||||
|
||||
def initiate_web_application_flow(self, user=None, success_uri=None):
|
||||
"""Return an authorization URL for the user. Save state in Token Cache."""
|
||||
user = user or frappe.session.user
|
||||
oauth = self.get_oauth2_session(init=True)
|
||||
query_params = self.get_query_params()
|
||||
authorization_url, state = oauth.authorization_url(self.authorization_uri, **query_params)
|
||||
token_cache = self.get_token_cache(user)
|
||||
|
||||
if not token_cache:
|
||||
token_cache = frappe.new_doc('Token Cache')
|
||||
token_cache.user = user
|
||||
token_cache.connected_app = self.name
|
||||
|
||||
token_cache.success_uri = success_uri
|
||||
token_cache.state = state
|
||||
token_cache.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
return authorization_url
|
||||
|
||||
def get_user_token(self, user=None, success_uri=None):
|
||||
"""Return an existing user token or initiate a Web Application Flow."""
|
||||
user = user or frappe.session.user
|
||||
token_cache = self.get_token_cache(user)
|
||||
|
||||
if token_cache:
|
||||
return token_cache
|
||||
|
||||
redirect = self.initiate_web_application_flow(user, success_uri)
|
||||
frappe.local.response['type'] = 'redirect'
|
||||
frappe.local.response['location'] = redirect
|
||||
return redirect
|
||||
|
||||
def get_token_cache(self, user):
|
||||
token_cache = None
|
||||
token_cache_name = self.name + '-' + user
|
||||
|
||||
if frappe.db.exists('Token Cache', token_cache_name):
|
||||
token_cache = frappe.get_doc('Token Cache', token_cache_name)
|
||||
|
||||
return token_cache
|
||||
|
||||
def get_scopes(self):
|
||||
return [row.scope for row in self.scopes]
|
||||
|
||||
def get_query_params(self):
|
||||
return {param.key: param.value for param in self.query_parameters}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def callback(code=None, state=None):
|
||||
"""Handle client's code.
|
||||
|
||||
Called during the oauthorization flow by the remote oAuth2 server to
|
||||
transmit a code that can be used by the local server to obtain an access
|
||||
token.
|
||||
"""
|
||||
if frappe.request.method != 'GET':
|
||||
frappe.throw(_('Invalid request method: {}').format(frappe.request.method))
|
||||
|
||||
if frappe.session.user == 'Guest':
|
||||
frappe.local.response['type'] = 'redirect'
|
||||
frappe.local.response['location'] = '/login?' + urlencode({'redirect-to': frappe.request.url})
|
||||
return
|
||||
|
||||
path = frappe.request.path[1:].split('/')
|
||||
if len(path) != 4 or not path[3]:
|
||||
frappe.throw(_('Invalid Parameters.'))
|
||||
|
||||
connected_app = frappe.get_doc('Connected App', path[3])
|
||||
token_cache = frappe.get_doc('Token Cache', connected_app.name + '-' + frappe.session.user)
|
||||
|
||||
if state != token_cache.state:
|
||||
frappe.throw(_('Invalid state.'))
|
||||
|
||||
oauth_session = connected_app.get_oauth2_session(init=True)
|
||||
query_params = connected_app.get_query_params()
|
||||
token = oauth_session.fetch_token(connected_app.token_uri,
|
||||
code=code,
|
||||
client_secret=connected_app.get_password('client_secret'),
|
||||
include_client_id=True,
|
||||
**query_params
|
||||
)
|
||||
token_cache.update_data(token)
|
||||
|
||||
frappe.local.response['type'] = 'redirect'
|
||||
frappe.local.response['location'] = token_cache.get('success_uri') or connected_app.get_url()
|
||||
162
frappe/integrations/doctype/connected_app/test_connected_app.py
Normal file
162
frappe/integrations/doctype/connected_app/test_connected_app.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import requests
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import frappe
|
||||
from frappe.integrations.doctype.social_login_key.test_social_login_key import create_or_update_social_login_key
|
||||
|
||||
|
||||
def get_user(usr, pwd):
|
||||
user = frappe.new_doc('User')
|
||||
user.email = usr
|
||||
user.enabled = 1
|
||||
user.first_name = "_Test"
|
||||
user.new_password = pwd
|
||||
user.roles = []
|
||||
user.append('roles', {
|
||||
'doctype': 'Has Role',
|
||||
'parentfield': 'roles',
|
||||
'role': 'System Manager'
|
||||
})
|
||||
user.insert()
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def get_connected_app():
|
||||
doctype = 'Connected App'
|
||||
connected_app = frappe.new_doc(doctype)
|
||||
connected_app.provider_name = 'frappe'
|
||||
connected_app.scopes = []
|
||||
connected_app.append('scopes', {'scope': 'all'})
|
||||
connected_app.insert()
|
||||
|
||||
return connected_app
|
||||
|
||||
|
||||
def get_oauth_client():
|
||||
oauth_client = frappe.new_doc('OAuth Client')
|
||||
oauth_client.app_name = '_Test Connected App'
|
||||
oauth_client.redirect_uris = 'to be replaced'
|
||||
oauth_client.default_redirect_uri = 'to be replaced'
|
||||
oauth_client.grant_type = 'Authorization Code'
|
||||
oauth_client.response_type = 'Code'
|
||||
oauth_client.skip_authorization = 1
|
||||
oauth_client.insert()
|
||||
|
||||
return oauth_client
|
||||
|
||||
|
||||
class TestConnectedApp(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Set up a Connected App that connects to our own oAuth provider.
|
||||
|
||||
Frappe comes with it's own oAuth2 provider that we can test against. The
|
||||
client credentials can be obtained from an "OAuth Client". All depends
|
||||
on "Social Login Key" so we create one as well.
|
||||
|
||||
The redirect URIs from "Connected App" and "OAuth Client" have to match.
|
||||
Frappe's "Authorization URL" and "Access Token URL" (actually they're
|
||||
just endpoints) are stored in "Social Login Key" so we get them from
|
||||
there.
|
||||
"""
|
||||
self.user_name = 'test-connected-app@example.com'
|
||||
self.user_password = 'Eastern_43A1W'
|
||||
|
||||
self.user = get_user(self.user_name, self.user_password)
|
||||
self.connected_app = get_connected_app()
|
||||
self.oauth_client = get_oauth_client()
|
||||
social_login_key = create_or_update_social_login_key()
|
||||
self.base_url = social_login_key.get('base_url')
|
||||
|
||||
frappe.db.commit()
|
||||
self.connected_app.reload()
|
||||
self.oauth_client.reload()
|
||||
|
||||
redirect_uri = self.connected_app.get('redirect_uri')
|
||||
self.oauth_client.update({
|
||||
'redirect_uris': redirect_uri,
|
||||
'default_redirect_uri': redirect_uri
|
||||
})
|
||||
self.oauth_client.save()
|
||||
|
||||
self.connected_app.update({
|
||||
'authorization_uri': urljoin(self.base_url, social_login_key.get('authorize_url')),
|
||||
'client_id': self.oauth_client.get('client_id'),
|
||||
'client_secret': self.oauth_client.get('client_secret'),
|
||||
'token_uri': urljoin(self.base_url, social_login_key.get('access_token_url'))
|
||||
})
|
||||
self.connected_app.save()
|
||||
|
||||
frappe.db.commit()
|
||||
self.connected_app.reload()
|
||||
self.oauth_client.reload()
|
||||
|
||||
def test_web_application_flow(self):
|
||||
"""Simulate a logged in user who opens the authorization URL."""
|
||||
def login():
|
||||
return session.get(urljoin(self.base_url, '/api/method/login'), params={
|
||||
'usr': self.user_name,
|
||||
'pwd': self.user_password
|
||||
})
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
# first login of a new user on a new site fails with "401 UNAUTHORIZED"
|
||||
# when anybody fixes that, the two lines below can be removed
|
||||
first_login = login()
|
||||
self.assertEqual(first_login.status_code, 401)
|
||||
|
||||
second_login = login()
|
||||
self.assertEqual(second_login.status_code, 200)
|
||||
|
||||
authorization_url = self.connected_app.initiate_web_application_flow(user=self.user_name)
|
||||
|
||||
auth_response = session.get(authorization_url)
|
||||
self.assertEqual(auth_response.status_code, 200)
|
||||
|
||||
callback_response = session.get(auth_response.url)
|
||||
self.assertEqual(callback_response.status_code, 200)
|
||||
|
||||
self.token_cache = self.connected_app.get_token_cache(self.user_name)
|
||||
token = self.token_cache.get_password('access_token')
|
||||
self.assertNotEqual(token, None)
|
||||
|
||||
oauth2_session = self.connected_app.get_oauth2_session(self.user_name)
|
||||
resp = oauth2_session.get(urljoin(self.base_url, '/api/method/frappe.auth.get_logged_user'))
|
||||
self.assertEqual(resp.json().get('message'), self.user_name)
|
||||
|
||||
def tearDown(self):
|
||||
def delete_if_exists(attribute):
|
||||
doc = getattr(self, attribute, None)
|
||||
if doc:
|
||||
doc.delete()
|
||||
|
||||
delete_if_exists('token_cache')
|
||||
delete_if_exists('connected_app')
|
||||
|
||||
if getattr(self, 'oauth_client', None):
|
||||
tokens = frappe.get_all('OAuth Bearer Token', filters={
|
||||
'client': self.oauth_client.name
|
||||
})
|
||||
for token in tokens:
|
||||
doc = frappe.get_doc('OAuth Bearer Token', token.name)
|
||||
doc.delete()
|
||||
|
||||
codes = frappe.get_all('OAuth Authorization Code', filters={
|
||||
'client': self.oauth_client.name
|
||||
})
|
||||
for code in codes:
|
||||
doc = frappe.get_doc('OAuth Authorization Code', code.name)
|
||||
doc.delete()
|
||||
|
||||
delete_if_exists('user')
|
||||
delete_if_exists('oauth_client')
|
||||
|
||||
frappe.db.commit()
|
||||
13
frappe/integrations/doctype/connected_app/test_records.json
Normal file
13
frappe/integrations/doctype/connected_app/test_records.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[
|
||||
{
|
||||
"doctype": "Connected App",
|
||||
"provider_name": "frappe",
|
||||
"client_id": "test_client_id",
|
||||
"client_secret": "test_client_secret",
|
||||
"scopes": [
|
||||
{
|
||||
"scope": "all"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
[
|
||||
{
|
||||
"app_name": "_Test OAuth Client",
|
||||
"client_id": "test_client_id",
|
||||
"app_name": "_Test OAuth Client",
|
||||
"client_secret": "test_client_secret",
|
||||
"default_redirect_uri": "http://localhost",
|
||||
"docstatus": 0,
|
||||
|
|
|
|||
0
frappe/integrations/doctype/oauth_scope/__init__.py
Normal file
0
frappe/integrations/doctype/oauth_scope/__init__.py
Normal file
30
frappe/integrations/doctype/oauth_scope/oauth_scope.json
Normal file
30
frappe/integrations/doctype/oauth_scope/oauth_scope.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-07-15 22:08:14.616585",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"scope"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "scope",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Scope"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-15 22:15:18.930632",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "OAuth Scope",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/integrations/doctype/oauth_scope/oauth_scope.py
Normal file
10
frappe/integrations/doctype/oauth_scope/oauth_scope.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class OAuthScope(Document):
|
||||
pass
|
||||
0
frappe/integrations/doctype/query_parameters/__init__.py
Normal file
0
frappe/integrations/doctype/query_parameters/__init__.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-16 14:54:37.226914",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"key",
|
||||
"value"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "key",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Key",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "value",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Value",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-16 15:18:35.887149",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Query Parameters",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class QueryParameters(Document):
|
||||
pass
|
||||
|
|
@ -22,3 +22,17 @@ def make_social_login_key(**kwargs):
|
|||
kwargs["provider_name"] = "Test OAuth2 Provider"
|
||||
doc = frappe.get_doc(kwargs)
|
||||
return doc
|
||||
|
||||
def create_or_update_social_login_key():
|
||||
# used in other tests (connected app, oauth20)
|
||||
try:
|
||||
social_login_key = frappe.get_doc("Social Login Key", "frappe")
|
||||
except frappe.DoesNotExistError:
|
||||
social_login_key = frappe.new_doc("Social Login Key")
|
||||
social_login_key.get_social_login_provider("Frappe", initialize=True)
|
||||
social_login_key.base_url = frappe.utils.get_url()
|
||||
social_login_key.enable_social_login = 0
|
||||
social_login_key.save()
|
||||
frappe.db.commit()
|
||||
|
||||
return social_login_key
|
||||
|
|
|
|||
0
frappe/integrations/doctype/token_cache/__init__.py
Normal file
0
frappe/integrations/doctype/token_cache/__init__.py
Normal file
18
frappe/integrations/doctype/token_cache/test_records.json
Normal file
18
frappe/integrations/doctype/token_cache/test_records.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
{
|
||||
"doctype": "Token Cache",
|
||||
"user": "test@example.com",
|
||||
"access_token": "test-access-token",
|
||||
"refresh_token": "test-refresh-token",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 1000,
|
||||
"scopes": [
|
||||
{
|
||||
"scope": "all"
|
||||
},
|
||||
{
|
||||
"scope": "openid"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
37
frappe/integrations/doctype/token_cache/test_token_cache.py
Normal file
37
frappe/integrations/doctype/token_cache/test_token_cache.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import frappe
|
||||
|
||||
test_dependencies = ['User', 'Connected App', 'Token Cache']
|
||||
|
||||
class TestTokenCache(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.token_cache = frappe.get_last_doc('Token Cache')
|
||||
self.token_cache.update({'connected_app': frappe.get_last_doc('Connected App').name})
|
||||
self.token_cache.save()
|
||||
|
||||
def test_get_auth_header(self):
|
||||
self.token_cache.get_auth_header()
|
||||
|
||||
def test_update_data(self):
|
||||
self.token_cache.update_data({
|
||||
'access_token': 'new-access-token',
|
||||
'refresh_token': 'new-refresh-token',
|
||||
'token_type': 'bearer',
|
||||
'expires_in': 2000,
|
||||
'scope': 'new scope'
|
||||
})
|
||||
|
||||
def test_get_expires_in(self):
|
||||
self.token_cache.get_expires_in()
|
||||
|
||||
def test_is_expired(self):
|
||||
self.token_cache.is_expired()
|
||||
|
||||
def get_json(self):
|
||||
self.token_cache.get_json()
|
||||
8
frappe/integrations/doctype/token_cache/token_cache.js
Normal file
8
frappe/integrations/doctype/token_cache/token_cache.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Token Cache', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
110
frappe/integrations/doctype/token_cache/token_cache.json
Normal file
110
frappe/integrations/doctype/token_cache/token_cache.json
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "format:{connected_app}-{user}",
|
||||
"beta": 1,
|
||||
"creation": "2019-01-24 16:56:55.631096",
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"user",
|
||||
"connected_app",
|
||||
"provider_name",
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
"expires_in",
|
||||
"state",
|
||||
"scopes",
|
||||
"success_uri",
|
||||
"token_type"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "connected_app",
|
||||
"fieldtype": "Link",
|
||||
"label": "Connected App",
|
||||
"options": "Connected App",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "access_token",
|
||||
"fieldtype": "Password",
|
||||
"label": "Access Token",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "refresh_token",
|
||||
"fieldtype": "Password",
|
||||
"label": "Refresh Token",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "expires_in",
|
||||
"fieldtype": "Int",
|
||||
"label": "Expires In",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "state",
|
||||
"fieldtype": "Data",
|
||||
"label": "State",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "scopes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Scopes",
|
||||
"options": "OAuth Scope",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "success_uri",
|
||||
"fieldtype": "Data",
|
||||
"label": "Success URI",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "token_type",
|
||||
"fieldtype": "Data",
|
||||
"label": "Token Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "connected_app.provider_name",
|
||||
"fieldname": "provider_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Provider Name",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-11-13 13:35:53.714352",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Token Cache",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"delete": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"delete": 1,
|
||||
"if_owner": 1,
|
||||
"read": 1,
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
67
frappe/integrations/doctype/token_cache/token_cache.py
Normal file
67
frappe/integrations/doctype/token_cache/token_cache.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, cint
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TokenCache(Document):
|
||||
|
||||
def get_auth_header(self):
|
||||
if self.access_token:
|
||||
headers = {'Authorization': 'Bearer ' + self.get_password('access_token')}
|
||||
return headers
|
||||
|
||||
raise frappe.exceptions.DoesNotExistError
|
||||
|
||||
def update_data(self, data):
|
||||
"""
|
||||
Store data returned by authorization flow.
|
||||
|
||||
Params:
|
||||
data - Dict with access_token, refresh_token, expires_in and scope.
|
||||
"""
|
||||
token_type = cstr(data.get('token_type', '')).lower()
|
||||
if token_type not in ['bearer', 'mac']:
|
||||
frappe.throw(_('Received an invalid token type.'))
|
||||
# 'Bearer' or 'MAC'
|
||||
token_type = token_type.title() if token_type == 'bearer' else token_type.upper()
|
||||
|
||||
self.token_type = token_type
|
||||
self.access_token = cstr(data.get('access_token', ''))
|
||||
self.refresh_token = cstr(data.get('refresh_token', ''))
|
||||
self.expires_in = cint(data.get('expires_in', 0))
|
||||
|
||||
new_scopes = data.get('scope')
|
||||
if new_scopes:
|
||||
if isinstance(new_scopes, str):
|
||||
new_scopes = new_scopes.split(' ')
|
||||
if isinstance(new_scopes, list):
|
||||
self.scopes = None
|
||||
for scope in new_scopes:
|
||||
self.append('scopes', {'scope': scope})
|
||||
|
||||
self.state = None
|
||||
self.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
return self
|
||||
|
||||
def get_expires_in(self):
|
||||
expiry_time = frappe.utils.get_datetime(self.modified) + timedelta(self.expires_in)
|
||||
return (datetime.now() - expiry_time).total_seconds()
|
||||
|
||||
def is_expired(self):
|
||||
return self.get_expires_in() < 0
|
||||
|
||||
def get_json(self):
|
||||
return {
|
||||
'access_token': self.get_password('access_token', ''),
|
||||
'refresh_token': self.get_password('refresh_token', ''),
|
||||
'expires_in': self.get_expires_in(),
|
||||
'token_type': self.token_type
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ def enqueue_webhook(doc, webhook):
|
|||
|
||||
for i in range(3):
|
||||
try:
|
||||
r = requests.post(webhook.request_url, data=json.dumps(data), headers=headers, timeout=5)
|
||||
r = requests.post(webhook.request_url, data=json.dumps(data, default=str), headers=headers, timeout=5)
|
||||
r.raise_for_status()
|
||||
frappe.logger().debug({"webhook_success": r.text})
|
||||
break
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ def get_oauth_server():
|
|||
return frappe.local.oauth_server
|
||||
|
||||
def sanitize_kwargs(param_kwargs):
|
||||
"""Remove 'data' and 'cmd' keys, if present."""
|
||||
arguments = param_kwargs
|
||||
arguments.pop('data', None)
|
||||
arguments.pop('cmd', None)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ def get_controller(doctype):
|
|||
else:
|
||||
class_overrides = frappe.get_hooks('override_doctype_class')
|
||||
if class_overrides and class_overrides.get(doctype):
|
||||
import_path = frappe.get_hooks('override_doctype_class').get(doctype)[-1]
|
||||
import_path = class_overrides[doctype][-1]
|
||||
module_path, classname = import_path.rsplit('.', 1)
|
||||
module = frappe.get_module(module_path)
|
||||
if not hasattr(module, classname):
|
||||
|
|
@ -69,10 +69,13 @@ def get_controller(doctype):
|
|||
|
||||
if frappe.local.dev_server:
|
||||
return _get_controller()
|
||||
|
||||
key = '{}:doctype_classes'.format(frappe.local.site)
|
||||
return frappe.cache().hget(key, doctype, generator=_get_controller, shared=True)
|
||||
|
||||
|
||||
site_controllers = frappe.controllers.setdefault(frappe.local.site, {})
|
||||
if doctype not in site_controllers:
|
||||
site_controllers[doctype] = _get_controller()
|
||||
|
||||
return site_controllers[doctype]
|
||||
|
||||
class BaseDocument(object):
|
||||
ignore_in_getter = ("doctype", "_meta", "meta", "_table_fields", "_valid_columns")
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,10 @@ class DatabaseQuery(object):
|
|||
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, ignore_ddl=False):
|
||||
if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user):
|
||||
if not ignore_permissions and \
|
||||
not frappe.has_permission(self.doctype, "select", user=user) 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)
|
||||
|
||||
|
|
@ -315,7 +318,10 @@ class DatabaseQuery(object):
|
|||
def append_table(self, table_name):
|
||||
self.tables.append(table_name)
|
||||
doctype = table_name[4:-1]
|
||||
if (not self.flags.ignore_permissions) and (not frappe.has_permission(doctype)):
|
||||
ptype = 'select' if frappe.only_has_select_perm(doctype) else 'read'
|
||||
|
||||
if (not self.flags.ignore_permissions) and\
|
||||
(not frappe.has_permission(doctype, ptype=ptype)):
|
||||
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(doctype))
|
||||
raise frappe.PermissionError(doctype)
|
||||
|
||||
|
|
@ -576,7 +582,7 @@ class DatabaseQuery(object):
|
|||
self.shared = frappe.share.get_shared(self.doctype, self.user)
|
||||
|
||||
if (not meta.istable and
|
||||
not role_permissions.get("read") and
|
||||
not (role_permissions.get("select") or role_permissions.get("read")) and
|
||||
not self.flags.ignore_permissions and
|
||||
not has_any_user_permission_for_doctype(self.doctype, self.user, self.reference_doctype)):
|
||||
only_if_shared = True
|
||||
|
|
|
|||
|
|
@ -939,15 +939,17 @@ class Document(BaseDocument):
|
|||
self.load_doc_before_save()
|
||||
self.reset_seen()
|
||||
|
||||
# before_validate method should be executed before ignoring validations
|
||||
if self._action in ("save", "submit"):
|
||||
self.run_method("before_validate")
|
||||
|
||||
if self.flags.ignore_validate:
|
||||
return
|
||||
|
||||
if self._action=="save":
|
||||
self.run_method("before_validate")
|
||||
self.run_method("validate")
|
||||
self.run_method("before_save")
|
||||
elif self._action=="submit":
|
||||
self.run_method("before_validate")
|
||||
self.run_method("validate")
|
||||
self.run_method("before_submit")
|
||||
elif self._action=="cancel":
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ def load_doctype_from_file(doctype):
|
|||
class Meta(Document):
|
||||
_metaclass = True
|
||||
default_fields = list(default_fields)[1:]
|
||||
special_doctypes = ("DocField", "DocPerm", "Role", "DocType", "Module Def", 'DocType Action', 'DocType Link')
|
||||
special_doctypes = ("DocField", "DocPerm", "DocType", "Module Def", 'DocType Action', 'DocType Link')
|
||||
|
||||
def __init__(self, doctype):
|
||||
self._fields = {}
|
||||
|
|
@ -484,6 +484,8 @@ class Meta(Document):
|
|||
if not data.transactions:
|
||||
# init groups
|
||||
data.transactions = []
|
||||
|
||||
if not data.non_standard_fieldnames:
|
||||
data.non_standard_fieldnames = {}
|
||||
|
||||
for link in dashboard_links:
|
||||
|
|
|
|||
|
|
@ -21,8 +21,16 @@ def update_document_title(doctype, docname, title_field=None, old_title=None, ne
|
|||
docname = rename_doc(doctype=doctype, old=docname, new=new_name, merge=merge)
|
||||
|
||||
if old_title and new_title and not old_title == new_title:
|
||||
frappe.db.set_value(doctype, docname, title_field, new_title)
|
||||
frappe.msgprint(_('Saved'), alert=True, indicator='green')
|
||||
try:
|
||||
frappe.db.set_value(doctype, docname, title_field, new_title)
|
||||
frappe.msgprint(_('Saved'), alert=True, indicator='green')
|
||||
except Exception as e:
|
||||
if frappe.db.is_duplicate_entry(e):
|
||||
frappe.throw(
|
||||
_("{0} {1} already exists").format(doctype, frappe.bold(docname)),
|
||||
title=_("Duplicate Name"),
|
||||
exc=frappe.DuplicateEntryError
|
||||
)
|
||||
|
||||
return docname
|
||||
|
||||
|
|
|
|||
|
|
@ -120,9 +120,8 @@ def apply_workflow(doc, action):
|
|||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def can_cancel_document(doc):
|
||||
doc = frappe.get_doc(frappe.parse_json(doc))
|
||||
workflow = get_workflow(doc.doctype)
|
||||
def can_cancel_document(doctype):
|
||||
workflow = get_workflow(doctype)
|
||||
for state_doc in workflow.states:
|
||||
if state_doc.doc_status == '2':
|
||||
for transition in workflow.transitions:
|
||||
|
|
|
|||
|
|
@ -326,3 +326,4 @@ execute:frappe.delete_doc_if_exists('Page', 'dashboard', force=1)
|
|||
frappe.core.doctype.page.patches.drop_unused_pages
|
||||
execute:frappe.get_doc('Role', 'Guest').save() # remove desk access
|
||||
frappe.patches.v13_0.rename_desk_page_to_workspace
|
||||
frappe.patches.v13_0.delete_package_publish_tool
|
||||
|
|
|
|||
11
frappe/patches/v13_0/delete_package_publish_tool.py
Normal file
11
frappe/patches/v13_0/delete_package_publish_tool.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.delete_doc("DocType", "Package Publish Tool", ignore_missing=True)
|
||||
frappe.delete_doc("DocType", "Package Document Type", ignore_missing=True)
|
||||
frappe.delete_doc("DocType", "Package Publish Target", ignore_missing=True)
|
||||
|
|
@ -2,9 +2,23 @@ import frappe
|
|||
|
||||
def execute():
|
||||
frappe.reload_doctype('Website Theme')
|
||||
frappe.reload_doc('website', 'doctype', 'website_theme_ignore_app')
|
||||
frappe.reload_doc('website', 'doctype', 'color')
|
||||
|
||||
for theme in frappe.get_all('Website Theme'):
|
||||
doc = frappe.get_doc('Website Theme', theme.name)
|
||||
if not doc.get('custom_scss') and doc.theme_scss:
|
||||
# move old theme to new theme
|
||||
doc.custom_scss = doc.theme_scss
|
||||
|
||||
if doc.background_color:
|
||||
setup_color_record(doc.background_color)
|
||||
|
||||
doc.save()
|
||||
|
||||
def setup_color_record(color):
|
||||
frappe.get_doc({
|
||||
"doctype": "Color",
|
||||
"__newname": color,
|
||||
"color": color,
|
||||
}).save()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import frappe, copy, json
|
|||
from frappe import _, msgprint
|
||||
from frappe.utils import cint
|
||||
import frappe.share
|
||||
rights = ("read", "write", "create", "delete", "submit", "cancel", "amend",
|
||||
rights = ("select", "read", "write", "create", "delete", "submit", "cancel", "amend",
|
||||
"print", "email", "report", "import", "export", "set_user_permissions", "share")
|
||||
|
||||
# TODO:
|
||||
|
|
@ -73,6 +73,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, ra
|
|||
|
||||
role_permissions = get_role_permissions(meta, user=user)
|
||||
perm = role_permissions.get(ptype)
|
||||
|
||||
if not perm:
|
||||
push_perm_check_log(_('User {0} does not have doctype access via role permission for document {1}').format(frappe.bold(user), frappe.bold(doctype)))
|
||||
|
||||
|
|
@ -192,9 +193,9 @@ def get_role_permissions(doctype_meta, user=None):
|
|||
and ptype != 'create'):
|
||||
perms['if_owner'][ptype] = 1
|
||||
# has no access if not owner
|
||||
# only provide read access so that user is able to at-least access list
|
||||
# only provide select or read access so that user is able to at-least access list
|
||||
# (and the documents will be filtered based on owner sin further checks)
|
||||
perms[ptype] = 1 if ptype == 'read' else 0
|
||||
perms[ptype] = 1 if ptype in ['select', 'read'] else 0
|
||||
|
||||
frappe.local.role_permissions[cache_key] = perms
|
||||
|
||||
|
|
|
|||
|
|
@ -261,6 +261,7 @@
|
|||
"public/js/frappe/views/calendar/calendar.js",
|
||||
"public/js/frappe/views/dashboard/dashboard_view.js",
|
||||
"public/js/frappe/views/image/image_view.js",
|
||||
"public/js/frappe/views/map/map_view.js",
|
||||
"public/js/frappe/views/kanban/kanban_view.js",
|
||||
"public/js/frappe/views/inbox/inbox_view.js",
|
||||
"public/js/frappe/views/file/file_view.js",
|
||||
|
|
|
|||
|
|
@ -401,6 +401,13 @@ input.list-row-checkbox {
|
|||
.pswp__more-item img {
|
||||
max-height: 100%;
|
||||
}
|
||||
.map-view-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
height: calc(100vh - 284px);
|
||||
z-index: 0;
|
||||
}
|
||||
.list-paging-area .gantt-view-mode {
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
|
|
|
|||
|
|
@ -40,23 +40,31 @@ frappe.ui.form.Control = Class.extend({
|
|||
return this.df.get_status(this);
|
||||
}
|
||||
|
||||
if((!this.doctype && !this.docname) || this.df.parenttype === 'Web Form') {
|
||||
if ((!this.doctype && !this.docname) || this.df.parenttype === 'Web Form' || this.df.is_web_form) {
|
||||
// like in case of a dialog box
|
||||
if (cint(this.df.hidden)) {
|
||||
// eslint-disable-next-line
|
||||
if(explain) console.log("By Hidden: None");
|
||||
if (explain) console.log("By Hidden: None"); // eslint-disable-line no-console
|
||||
return "None";
|
||||
|
||||
} else if (cint(this.df.hidden_due_to_dependency)) {
|
||||
// eslint-disable-next-line
|
||||
if(explain) console.log("By Hidden Dependency: None");
|
||||
if(explain) console.log("By Hidden Dependency: None"); // eslint-disable-line no-console
|
||||
return "None";
|
||||
|
||||
} else if (cint(this.df.read_only)) {
|
||||
// eslint-disable-next-line
|
||||
if(explain) console.log("By Read Only: Read");
|
||||
if (explain) console.log("By Read Only: Read"); // eslint-disable-line no-console
|
||||
return "Read";
|
||||
|
||||
} else if ((this.grid &&
|
||||
this.grid.display_status == 'Read') ||
|
||||
(this.layout &&
|
||||
this.layout.grid &&
|
||||
this.layout.grid.display_status == 'Read')) {
|
||||
// parent grid is read
|
||||
if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
|
||||
return "Read";
|
||||
}
|
||||
|
||||
return "Write";
|
||||
|
|
@ -65,13 +73,22 @@ frappe.ui.form.Control = Class.extend({
|
|||
var status = frappe.perm.get_field_display_status(this.df,
|
||||
frappe.model.get_doc(this.doctype, this.docname), this.perm || (this.frm && this.frm.perm), explain);
|
||||
|
||||
// Match parent grid controls read only status
|
||||
if (status === 'Write' && (this.grid || (this.layout && this.layout.grid))) {
|
||||
var grid = this.grid || this.layout.grid;
|
||||
if (grid.display_status == 'Read') {
|
||||
status = 'Read';
|
||||
if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
// hide if no value
|
||||
if (this.doctype && status==="Read" && !this.only_input
|
||||
&& is_null(frappe.model.get_value(this.doctype, this.docname, this.df.fieldname))
|
||||
&& !in_list(["HTML", "Image", "Button"], this.df.fieldtype)) {
|
||||
|
||||
// eslint-disable-next-line
|
||||
if(explain) console.log("By Hide Read-only, null fields: None");
|
||||
if (explain) console.log("By Hide Read-only, null fields: None"); // eslint-disable-line no-console
|
||||
status = "None";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({
|
|||
<div class="control-input-wrapper">\
|
||||
<div class="control-input"></div>\
|
||||
<div class="control-value like-disabled-input" style="display: none;"></div>\
|
||||
<p class="help-box small text-muted hidden-xs"></p>\
|
||||
<p class="help-box small text-muted"></p>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>').appendTo(this.parent);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
frappe.provide('frappe.utils.utils');
|
||||
|
||||
frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({
|
||||
horizontal: false,
|
||||
|
||||
|
|
@ -90,11 +92,11 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({
|
|||
});
|
||||
|
||||
L.Icon.Default.imagePath = '/assets/frappe/images/leaflet/';
|
||||
this.map = L.map(this.map_id).setView([19.0800, 72.8961], 13);
|
||||
this.map = L.map(this.map_id).setView(frappe.utils.map_defaults.center,
|
||||
frappe.utils.map_defaults.zoom);
|
||||
|
||||
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(this.map);
|
||||
L.tileLayer(frappe.utils.map_defaults.tiles,
|
||||
frappe.utils.map_defaults.options).addTo(this.map);
|
||||
},
|
||||
|
||||
bind_leaflet_locate_control() {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
|
|||
this.translate_values = true;
|
||||
this.setup_buttons();
|
||||
this.setup_awesomeplete();
|
||||
this.bind_change_event();
|
||||
},
|
||||
get_options: function() {
|
||||
return this.df.options;
|
||||
|
|
@ -217,6 +218,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
|
|||
}
|
||||
me.$input.cache[doctype][term] = r.results;
|
||||
me.awesomplete.list = me.$input.cache[doctype][term];
|
||||
me.toggle_href(doctype);
|
||||
}
|
||||
});
|
||||
}, 500));
|
||||
|
|
@ -303,6 +305,15 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
|
|||
// returns [{value: 'Manufacturer 1', 'description': 'mobile part 1, mobile part 2'}]
|
||||
},
|
||||
|
||||
toggle_href(doctype) {
|
||||
if (frappe.model.can_select(doctype) && !frappe.model.can_read(doctype)) {
|
||||
// remove href from link field as user has only select perm
|
||||
this.$input_area.find(".link-btn").addClass('hide');
|
||||
} else {
|
||||
this.$input_area.find(".link-btn").removeClass('hide');
|
||||
}
|
||||
},
|
||||
|
||||
get_filter_description(filters) {
|
||||
let doctype = this.get_options();
|
||||
let filter_array = [];
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ frappe.ui.form.ControlRating = frappe.ui.form.ControlInt.extend({
|
|||
});
|
||||
},
|
||||
get_value() {
|
||||
return cint(this.value);
|
||||
return cint(this.value, null);
|
||||
},
|
||||
set_formatted_input(value) {
|
||||
let el = $(this.input_area).find('i');
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ frappe.ui.form.ControlTable = frappe.ui.form.Control.extend({
|
|||
frm: this.frm,
|
||||
df: this.df,
|
||||
perm: this.perm || (this.frm && this.frm.perm) || this.df.perm,
|
||||
parent: this.wrapper
|
||||
parent: this.wrapper,
|
||||
control: this
|
||||
});
|
||||
if(this.frm) {
|
||||
this.frm.grids[this.frm.grids.length] = this;
|
||||
|
|
|
|||
|
|
@ -1264,7 +1264,10 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
}
|
||||
if (df && df[property] != value) {
|
||||
df[property] = value;
|
||||
this.refresh_field(fieldname);
|
||||
if (!docname || !table_field) {
|
||||
// do not refresh childtable fields since `this.fields_dict` doesn't have child table fields
|
||||
this.refresh_field(fieldname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,11 +128,15 @@ frappe.form.formatters = {
|
|||
return repl('<a onclick="%(onclick)s">%(value)s</a>',
|
||||
{onclick: docfield.link_onclick.replace(/"/g, '"'), value:value});
|
||||
} else if(docfield && doctype) {
|
||||
return `<a
|
||||
href="/app/${encodeURIComponent(frappe.router.slug(doctype))}/${encodeURIComponent(original_value)}"
|
||||
data-doctype="${doctype}"
|
||||
data-name="${original_value}">
|
||||
${__(options && options.label || value)}</a>`
|
||||
if (!frappe.model.can_select(doctype) && frappe.model.can_read(doctype)) {
|
||||
return `<a
|
||||
href="/app/${encodeURIComponent(frappe.router.slug(doctype))}/${encodeURIComponent(original_value)}"
|
||||
data-doctype="${doctype}"
|
||||
data-name="${original_value}">
|
||||
${__(options && options.label || value)}</a>`
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,6 +280,8 @@ export default class Grid {
|
|||
if (this.frm) {
|
||||
this.display_status = frappe.perm.get_field_display_status(this.df, this.frm.doc,
|
||||
this.perm);
|
||||
} else if (this.df.is_web_form && this.control) {
|
||||
this.display_status = this.control.get_status();
|
||||
} else {
|
||||
// not in form
|
||||
this.display_status = 'Write';
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ export default class GridRowForm {
|
|||
body: this.form_area,
|
||||
no_submit_on_enter: true,
|
||||
frm: this.row.frm,
|
||||
grid: this.row.grid,
|
||||
grid_row: this.row,
|
||||
grid_row_form: this,
|
||||
});
|
||||
this.layout.make();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import '../class';
|
||||
|
||||
frappe.ui.form.Layout = Class.extend({
|
||||
init: function(opts) {
|
||||
init: function (opts) {
|
||||
this.views = {};
|
||||
this.pages = [];
|
||||
this.sections = [];
|
||||
|
|
@ -87,7 +87,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
this.message.empty().addClass('hidden');
|
||||
}
|
||||
},
|
||||
render: function(new_fields) {
|
||||
render: function (new_fields) {
|
||||
var me = this;
|
||||
var fields = new_fields || this.fields;
|
||||
|
||||
|
|
@ -101,8 +101,8 @@ frappe.ui.form.Layout = Class.extend({
|
|||
if (this.no_opening_section()) {
|
||||
this.make_section();
|
||||
}
|
||||
$.each(fields, function(i, df) {
|
||||
switch(df.fieldtype) {
|
||||
$.each(fields, function (i, df) {
|
||||
switch (df.fieldtype) {
|
||||
case "Fold":
|
||||
me.make_page(df);
|
||||
break;
|
||||
|
|
@ -119,17 +119,17 @@ frappe.ui.form.Layout = Class.extend({
|
|||
|
||||
},
|
||||
|
||||
no_opening_section: function() {
|
||||
return (this.fields[0] && this.fields[0].fieldtype!="Section Break") || !this.fields.length;
|
||||
no_opening_section: function () {
|
||||
return (this.fields[0] && this.fields[0].fieldtype != "Section Break") || !this.fields.length;
|
||||
},
|
||||
|
||||
setup_dashboard_section: function() {
|
||||
setup_dashboard_section: function () {
|
||||
if (this.no_opening_section()) {
|
||||
this.fields.unshift({fieldtype: 'Section Break'});
|
||||
}
|
||||
},
|
||||
|
||||
replace_field: function(fieldname, df, render) {
|
||||
replace_field: function (fieldname, df, render) {
|
||||
df.fieldname = fieldname; // change of fieldname is avoided
|
||||
if (this.fields_dict[fieldname] && this.fields_dict[fieldname].df) {
|
||||
const fieldobj = this.init_field(df, render);
|
||||
|
|
@ -145,7 +145,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
}
|
||||
},
|
||||
|
||||
make_field: function(df, colspan, render) {
|
||||
make_field: function (df, colspan, render) {
|
||||
!this.section && this.make_section();
|
||||
!this.column && this.make_column();
|
||||
|
||||
|
|
@ -161,29 +161,30 @@ frappe.ui.form.Layout = Class.extend({
|
|||
fieldobj.section = this.section;
|
||||
},
|
||||
|
||||
init_field: function(df, render = false) {
|
||||
init_field: function (df, render = false) {
|
||||
const fieldobj = frappe.ui.form.make_control({
|
||||
df: df,
|
||||
doctype: this.doctype,
|
||||
parent: this.column.wrapper.get(0),
|
||||
frm: this.frm,
|
||||
render_input: render,
|
||||
doc: this.doc
|
||||
doc: this.doc,
|
||||
layout: this
|
||||
});
|
||||
|
||||
fieldobj.layout = this;
|
||||
return fieldobj;
|
||||
},
|
||||
|
||||
make_page: function(df) {
|
||||
make_page: function (df) { // eslint-disable-line no-unused-vars
|
||||
var me = this,
|
||||
head = $('<div class="form-clickable-section text-center">\
|
||||
<a class="btn-fold h6 text-muted">'+__("Show more details")+'</a>\
|
||||
<a class="btn-fold h6 text-muted">' + __("Show more details") + '</a>\
|
||||
</div>').appendTo(this.wrapper);
|
||||
|
||||
this.page = $('<div class="form-page second-page hide"></div>').appendTo(this.wrapper);
|
||||
|
||||
this.fold_btn = head.find(".btn-fold").on("click", function() {
|
||||
this.fold_btn = head.find(".btn-fold").on("click", function () {
|
||||
var page = $(this).parent().next();
|
||||
if (page.hasClass("hide")) {
|
||||
$(this).removeClass("btn-fold").html(__("Hide details"));
|
||||
|
|
@ -201,11 +202,11 @@ frappe.ui.form.Layout = Class.extend({
|
|||
this.folded = true;
|
||||
},
|
||||
|
||||
unfold: function() {
|
||||
unfold: function () {
|
||||
this.fold_btn.trigger('click');
|
||||
},
|
||||
|
||||
make_section: function(df) {
|
||||
make_section: function (df) {
|
||||
this.section = new frappe.ui.form.Section(this, df);
|
||||
|
||||
// append to layout fields
|
||||
|
|
@ -217,14 +218,14 @@ frappe.ui.form.Layout = Class.extend({
|
|||
this.column = null;
|
||||
},
|
||||
|
||||
make_column: function(df) {
|
||||
make_column: function (df) {
|
||||
this.column = new frappe.ui.form.Column(this.section, df);
|
||||
if (df && df.fieldname) {
|
||||
this.fields_list.push(this.column);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(doc) {
|
||||
refresh: function (doc) {
|
||||
var me = this;
|
||||
if (doc) this.doc = doc;
|
||||
|
||||
|
|
@ -267,7 +268,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
|
||||
},
|
||||
|
||||
refresh_fields: function(fields) {
|
||||
refresh_fields: function (fields) {
|
||||
let fieldnames = fields.map((field) => {
|
||||
if (field.fieldname) return field.fieldname;
|
||||
});
|
||||
|
|
@ -282,15 +283,15 @@ frappe.ui.form.Layout = Class.extend({
|
|||
});
|
||||
},
|
||||
|
||||
add_fields: function(fields) {
|
||||
add_fields: function (fields) {
|
||||
this.render(fields);
|
||||
this.refresh_fields(fields);
|
||||
},
|
||||
|
||||
refresh_section_collapse: function() {
|
||||
refresh_section_collapse: function () {
|
||||
if (!this.doc) return;
|
||||
|
||||
for (var i=0; i<this.sections.length; i++) {
|
||||
for (var i = 0; i < this.sections.length; i++) {
|
||||
var section = this.sections[i];
|
||||
var df = section.df;
|
||||
if (df && df.collapsible) {
|
||||
|
|
@ -309,9 +310,9 @@ frappe.ui.form.Layout = Class.extend({
|
|||
}
|
||||
},
|
||||
|
||||
attach_doc_and_docfields: function(refresh) {
|
||||
attach_doc_and_docfields: function (refresh) {
|
||||
var me = this;
|
||||
for (var i=0, l=this.fields_list.length; i<l; i++) {
|
||||
for (var i = 0, l = this.fields_list.length; i < l; i++) {
|
||||
var fieldobj = this.fields_list[i];
|
||||
if (me.doc) {
|
||||
fieldobj.doc = me.doc;
|
||||
|
|
@ -329,15 +330,15 @@ frappe.ui.form.Layout = Class.extend({
|
|||
}
|
||||
},
|
||||
|
||||
refresh_section_count: function() {
|
||||
this.wrapper.find(".section-count-label:visible").each(function(i) {
|
||||
$(this).html(i+1);
|
||||
refresh_section_count: function () {
|
||||
this.wrapper.find(".section-count-label:visible").each(function (i) {
|
||||
$(this).html(i + 1);
|
||||
});
|
||||
},
|
||||
setup_tabbing: function() {
|
||||
setup_tabbing: function () {
|
||||
var me = this;
|
||||
this.wrapper.on("keydown", function(ev) {
|
||||
if (ev.which==9) {
|
||||
this.wrapper.on("keydown", function (ev) {
|
||||
if (ev.which == 9) {
|
||||
var current = $(ev.target),
|
||||
doctype = current.attr("data-doctype"),
|
||||
fieldname = current.attr("data-fieldname");
|
||||
|
|
@ -346,7 +347,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
}
|
||||
});
|
||||
},
|
||||
handle_tab: function(doctype, fieldname, shift) {
|
||||
handle_tab: function (doctype, fieldname, shift) {
|
||||
var me = this,
|
||||
grid_row = null,
|
||||
prev = null,
|
||||
|
|
@ -363,8 +364,8 @@ frappe.ui.form.Layout = Class.extend({
|
|||
fields = grid_row.layout.fields_list;
|
||||
}
|
||||
|
||||
for (var i=0, len=fields.length; i < len; i++) {
|
||||
if (fields[i].df.fieldname==fieldname) {
|
||||
for (var i = 0, len = fields.length; i < len; i++) {
|
||||
if (fields[i].df.fieldname == fieldname) {
|
||||
if (shift) {
|
||||
if (prev) {
|
||||
this.set_focus(prev);
|
||||
|
|
@ -373,7 +374,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
}
|
||||
break;
|
||||
}
|
||||
if (i < len-1) {
|
||||
if (i < len - 1) {
|
||||
focused = me.focus_on_next_field(i, fields);
|
||||
}
|
||||
|
||||
|
|
@ -389,9 +390,9 @@ frappe.ui.form.Layout = Class.extend({
|
|||
// last field in this group
|
||||
if (grid_row) {
|
||||
// in grid
|
||||
if (grid_row.doc.idx==grid_row.grid.grid_rows.length) {
|
||||
if (grid_row.doc.idx == grid_row.grid.grid_rows.length) {
|
||||
// last row, close it and find next field
|
||||
grid_row.toggle_view(false, function() {
|
||||
grid_row.toggle_view(false, function () {
|
||||
grid_row.grid.frm.layout.handle_tab(grid_row.grid.df.parent, grid_row.grid.df.fieldname);
|
||||
});
|
||||
} else {
|
||||
|
|
@ -405,12 +406,12 @@ frappe.ui.form.Layout = Class.extend({
|
|||
|
||||
return false;
|
||||
},
|
||||
focus_on_next_field: function(start_idx, fields) {
|
||||
focus_on_next_field: function (start_idx, fields) {
|
||||
// loop to find next eligible fields
|
||||
for (var i= start_idx + 1, len = fields.length; i < len; i++) {
|
||||
for (var i = start_idx + 1, len = fields.length; i < len; i++) {
|
||||
var field = fields[i];
|
||||
if (this.is_visible(field)) {
|
||||
if (field.df.fieldtype==="Table") {
|
||||
if (field.df.fieldtype === "Table") {
|
||||
// open table grid
|
||||
if (!(field.grid.grid_rows && field.grid.grid_rows.length)) {
|
||||
// empty grid, add a new row
|
||||
|
|
@ -427,10 +428,10 @@ frappe.ui.form.Layout = Class.extend({
|
|||
}
|
||||
}
|
||||
},
|
||||
is_visible: function(field) {
|
||||
return field.disp_status==="Write" && (field.$wrapper && field.$wrapper.is(":visible"));
|
||||
is_visible: function (field) {
|
||||
return field.disp_status === "Write" && (field.$wrapper && field.$wrapper.is(":visible"));
|
||||
},
|
||||
set_focus: function(field) {
|
||||
set_focus: function (field) {
|
||||
// next is table, show the table
|
||||
if (field.df.fieldtype=="Table") {
|
||||
if (!field.grid.grid_rows.length) {
|
||||
|
|
@ -444,10 +445,10 @@ frappe.ui.form.Layout = Class.extend({
|
|||
field.$input.focus();
|
||||
}
|
||||
},
|
||||
get_open_grid_row: function() {
|
||||
get_open_grid_row: function () {
|
||||
return $(".grid-row-open").data("grid_row");
|
||||
},
|
||||
refresh_dependency: function() {
|
||||
refresh_dependency: function () {
|
||||
// Resolve "depends_on" and show / hide accordingly
|
||||
var me = this;
|
||||
|
||||
|
|
@ -465,7 +466,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
if (!has_dep) return;
|
||||
|
||||
// show / hide based on values
|
||||
for (var i=me.fields_list.length-1;i>=0;i--) {
|
||||
for (var i = me.fields_list.length - 1; i >= 0; i--) {
|
||||
var f = me.fields_list[i];
|
||||
f.guardian_has_value = true;
|
||||
if (f.df.depends_on) {
|
||||
|
|
@ -498,14 +499,14 @@ frappe.ui.form.Layout = Class.extend({
|
|||
|
||||
this.refresh_section_count();
|
||||
},
|
||||
set_dependant_property: function(condition, fieldname, property) {
|
||||
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) {
|
||||
form_obj = this.frm;
|
||||
} else if (this.is_dialog) {
|
||||
} else if (this.is_dialog || this.doctype === 'Web Form') {
|
||||
form_obj = this;
|
||||
}
|
||||
if (form_obj) {
|
||||
|
|
@ -513,12 +514,14 @@ frappe.ui.form.Layout = Class.extend({
|
|||
form_obj.setting_dependency = true;
|
||||
form_obj.set_df_property(this.doc.parentfield, property, value, this.doc.parent, fieldname);
|
||||
form_obj.setting_dependency = false;
|
||||
// refresh child fields
|
||||
this.fields_dict[fieldname] && this.fields_dict[fieldname].refresh();
|
||||
} else {
|
||||
form_obj.set_df_property(fieldname, property, value);
|
||||
}
|
||||
}
|
||||
},
|
||||
evaluate_depends_on_value: function(expression) {
|
||||
evaluate_depends_on_value: function (expression) {
|
||||
var out = null;
|
||||
var doc = this.doc;
|
||||
|
||||
|
|
@ -544,7 +547,7 @@ frappe.ui.form.Layout = Class.extend({
|
|||
if (parent && parent.istable && expression.includes('is_submittable')) {
|
||||
out = true;
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
frappe.throw(__('Invalid "depends_on" expression'));
|
||||
}
|
||||
|
||||
|
|
@ -640,7 +643,7 @@ frappe.ui.form.Section = Class.extend({
|
|||
|
||||
this.wrapper.toggleClass("hide-control", !!hide);
|
||||
},
|
||||
collapse: function(hide) {
|
||||
collapse: function (hide) {
|
||||
// unknown edge case
|
||||
if (!(this.head && this.body)) {
|
||||
return;
|
||||
|
|
@ -659,7 +662,7 @@ frappe.ui.form.Section = Class.extend({
|
|||
|
||||
// refresh signature fields
|
||||
this.fields_list.forEach((f) => {
|
||||
if (f.df.fieldtype=='Signature') {
|
||||
if (f.df.fieldtype == 'Signature') {
|
||||
f.refresh();
|
||||
}
|
||||
});
|
||||
|
|
@ -669,11 +672,11 @@ frappe.ui.form.Section = Class.extend({
|
|||
return this.body.hasClass('hide');
|
||||
},
|
||||
|
||||
has_missing_mandatory: function() {
|
||||
has_missing_mandatory: function () {
|
||||
var missing_mandatory = false;
|
||||
for (var j=0, l=this.fields_list.length; j < l; j++) {
|
||||
for (var j = 0, l = this.fields_list.length; j < l; j++) {
|
||||
var section_df = this.fields_list[j].df;
|
||||
if (section_df.reqd && this.layout.doc[section_df.fieldname]==null) {
|
||||
if (section_df.reqd && this.layout.doc[section_df.fieldname] == null) {
|
||||
missing_mandatory = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -691,13 +694,13 @@ frappe.ui.form.Column = Class.extend({
|
|||
this.make();
|
||||
this.resize_all_columns();
|
||||
},
|
||||
make: function() {
|
||||
make: function () {
|
||||
this.wrapper = $('<div class="form-column">\
|
||||
<form>\
|
||||
</form>\
|
||||
</div>').appendTo(this.section.body)
|
||||
.find("form")
|
||||
.on("submit", function() {
|
||||
.on("submit", function () {
|
||||
return false;
|
||||
});
|
||||
|
||||
|
|
@ -706,7 +709,7 @@ frappe.ui.form.Column = Class.extend({
|
|||
+ '</label>').appendTo(this.wrapper);
|
||||
}
|
||||
},
|
||||
resize_all_columns: function() {
|
||||
resize_all_columns: function () {
|
||||
// distribute all columns equally
|
||||
var colspan = cint(12 / this.section.wrapper.find(".form-column").length);
|
||||
|
||||
|
|
@ -715,7 +718,7 @@ frappe.ui.form.Column = Class.extend({
|
|||
.addClass("col-sm-" + colspan);
|
||||
|
||||
},
|
||||
refresh: function() {
|
||||
refresh: function () {
|
||||
this.section.refresh();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ frappe.ui.form.Toolbar = class Toolbar {
|
|||
this.page.add_action_icon("right", ()=> {
|
||||
this.frm.navigate_records(0);
|
||||
}, 'next-doc', __("Next"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
make_menu_items() {
|
||||
|
|
@ -470,9 +470,22 @@ frappe.ui.form.Toolbar = class Toolbar {
|
|||
me.frm.page.set_view('main');
|
||||
}, 'edit');
|
||||
} else if(status === "Cancel") {
|
||||
this.page.set_secondary_action(__(status), function() {
|
||||
me.frm.savecancel(this);
|
||||
});
|
||||
let add_cancel_button = () => {
|
||||
this.page.set_secondary_action(__(status), function() {
|
||||
me.frm.savecancel(this);
|
||||
});
|
||||
};
|
||||
if (this.has_workflow()) {
|
||||
frappe.xcall('frappe.model.workflow.can_cancel_document', {
|
||||
'doctype': this.frm.doc.doctype,
|
||||
}).then((can_cancel) => {
|
||||
if (can_cancel) {
|
||||
add_cancel_button();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
add_cancel_button();
|
||||
}
|
||||
} else {
|
||||
var click = {
|
||||
"Save": function() {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ frappe.ui.form.States = Class.extend({
|
|||
frappe.workflow.get_transitions(this.frm.doc).then(transitions => {
|
||||
this.frm.page.clear_actions_menu();
|
||||
transitions.forEach(d => {
|
||||
if(frappe.user_roles.includes(d.allowed) && has_approval_access(d)) {
|
||||
if (frappe.user_roles.includes(d.allowed) && has_approval_access(d)) {
|
||||
added = true;
|
||||
me.frm.page.add_action_item(__(d.action), function() {
|
||||
// set the workflow_action for use in form scripts
|
||||
|
|
@ -103,17 +103,8 @@ frappe.ui.form.States = Class.extend({
|
|||
});
|
||||
}
|
||||
});
|
||||
if (!added) {
|
||||
//call function and clear cancel button if Cancel doc state is defined in the workfloe
|
||||
frappe.xcall('frappe.model.workflow.can_cancel_document', {doc: this.frm.doc}).then((can_cancel) => {
|
||||
if (!can_cancel) {
|
||||
this.frm.page.clear_secondary_action();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setup_btn(added);
|
||||
}
|
||||
|
||||
this.setup_btn(added);
|
||||
});
|
||||
|
||||
},
|
||||
|
|
|
|||
|
|
@ -39,13 +39,105 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
|
||||
}
|
||||
|
||||
setup_list_group_by() {
|
||||
this.list_group_by = new frappe.views.ListGroupBy({
|
||||
doctype: this.doctype,
|
||||
sidebar: this,
|
||||
list_view: this.list_view,
|
||||
page: this.page
|
||||
});
|
||||
setup_views() {
|
||||
var show_list_link = false;
|
||||
|
||||
if (frappe.views.calendar[this.doctype]) {
|
||||
this.sidebar.find('.list-link[data-view="Calendar"]').removeClass("hide");
|
||||
this.sidebar.find('.list-link[data-view="Gantt"]').removeClass('hide');
|
||||
show_list_link = true;
|
||||
}
|
||||
//show link for kanban view
|
||||
this.sidebar.find('.list-link[data-view="Kanban"]').removeClass('hide');
|
||||
if (this.doctype === "Communication" && frappe.boot.email_accounts.length) {
|
||||
this.sidebar.find('.list-link[data-view="Inbox"]').removeClass('hide');
|
||||
show_list_link = true;
|
||||
}
|
||||
|
||||
if (frappe.treeview_settings[this.doctype] || frappe.get_meta(this.doctype).is_tree) {
|
||||
this.sidebar.find(".tree-link").removeClass("hide");
|
||||
}
|
||||
|
||||
this.current_view = 'List';
|
||||
var route = frappe.get_route();
|
||||
if (route.length > 2 && frappe.views.view_modes.includes(route[2])) {
|
||||
this.current_view = route[2];
|
||||
|
||||
if (this.current_view === 'Kanban') {
|
||||
this.kanban_board = route[3];
|
||||
} else if (this.current_view === 'Inbox') {
|
||||
this.email_account = route[3];
|
||||
}
|
||||
}
|
||||
|
||||
// disable link for current view
|
||||
this.sidebar.find('.list-link[data-view="' + this.current_view + '"] a')
|
||||
.attr('disabled', 'disabled').addClass('disabled');
|
||||
|
||||
//enable link for Kanban view
|
||||
this.sidebar.find('.list-link[data-view="Kanban"] a, .list-link[data-view="Inbox"] a')
|
||||
.attr('disabled', null).removeClass('disabled');
|
||||
|
||||
// show image link if image_view
|
||||
if (this.list_view.meta.image_field) {
|
||||
this.sidebar.find('.list-link[data-view="Image"]').removeClass('hide');
|
||||
show_list_link = true;
|
||||
}
|
||||
|
||||
if (this.list_view.settings.get_coords_method ||
|
||||
(this.list_view.meta.fields.find(i => i.fieldname === "latitude") &&
|
||||
this.list_view.meta.fields.find(i => i.fieldname === "longitude")) ||
|
||||
(this.list_view.meta.fields.find(i => i.fieldname === 'location' && i.fieldtype == 'Geolocation'))) {
|
||||
this.sidebar.find('.list-link[data-view="Map"]').removeClass('hide');
|
||||
show_list_link = true;
|
||||
}
|
||||
|
||||
if (show_list_link) {
|
||||
this.sidebar.find('.list-link[data-view="List"]').removeClass('hide');
|
||||
}
|
||||
}
|
||||
|
||||
setup_reports() {
|
||||
// add reports linked to this doctype to the dropdown
|
||||
var me = this;
|
||||
var added = [];
|
||||
var dropdown = this.page.sidebar.find('.reports-dropdown');
|
||||
var divider = false;
|
||||
|
||||
var add_reports = function(reports) {
|
||||
$.each(reports, function(name, r) {
|
||||
if (!r.ref_doctype || r.ref_doctype == me.doctype) {
|
||||
var report_type = r.report_type === 'Report Builder' ?
|
||||
`List/${r.ref_doctype}/Report` : 'query-report';
|
||||
|
||||
var route = r.route || report_type + '/' + (r.title || r.name);
|
||||
|
||||
if (added.indexOf(route) === -1) {
|
||||
// don't repeat
|
||||
added.push(route);
|
||||
|
||||
if (!divider) {
|
||||
me.get_divider().appendTo(dropdown);
|
||||
divider = true;
|
||||
}
|
||||
|
||||
$('<li><a href="#' + route + '">' +
|
||||
__(r.title || r.name) + '</a></li>').appendTo(dropdown);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// from reference doctype
|
||||
if (this.list_view.settings.reports) {
|
||||
add_reports(this.list_view.settings.reports);
|
||||
}
|
||||
|
||||
// Sort reports alphabetically
|
||||
var reports = Object.values(frappe.boot.user.all_reports).sort((a,b) => a.title.localeCompare(b.title)) || [];
|
||||
|
||||
// from specially tagged reports
|
||||
add_reports(reports);
|
||||
}
|
||||
|
||||
setup_list_filter() {
|
||||
|
|
@ -56,6 +148,29 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
});
|
||||
}
|
||||
|
||||
setup_kanban_boards() {
|
||||
const $dropdown = this.page.sidebar.find('.kanban-dropdown');
|
||||
frappe.views.KanbanView.setup_dropdown_in_sidebar(this.doctype, $dropdown);
|
||||
}
|
||||
|
||||
|
||||
setup_keyboard_shortcuts() {
|
||||
this.sidebar.find('.list-link > a, .list-link > .btn-group > a').each((i, el) => {
|
||||
frappe.ui.keys
|
||||
.get_shortcut_group(this.page)
|
||||
.add($(el));
|
||||
});
|
||||
}
|
||||
|
||||
setup_list_group_by() {
|
||||
this.list_group_by = new frappe.views.ListGroupBy({
|
||||
doctype: this.doctype,
|
||||
sidebar: this,
|
||||
list_view: this.list_view,
|
||||
page: this.page
|
||||
});
|
||||
}
|
||||
|
||||
get_stats() {
|
||||
var me = this;
|
||||
frappe.call({
|
||||
|
|
|
|||
|
|
@ -135,8 +135,8 @@ $.extend(frappe.model, {
|
|||
let cached_timestamp = null;
|
||||
let cached_doc = null;
|
||||
|
||||
let cached_docs = frappe.model.get_from_localstorage(doctype)
|
||||
|
||||
let cached_docs = frappe.model.get_from_localstorage(doctype);
|
||||
|
||||
if (cached_docs) {
|
||||
cached_doc = cached_docs.filter(doc => doc.name === doctype)[0];
|
||||
if(cached_doc) {
|
||||
|
|
@ -252,6 +252,10 @@ $.extend(frappe.model, {
|
|||
return frappe.boot.user.can_create.indexOf(doctype)!==-1;
|
||||
},
|
||||
|
||||
can_select: function(doctype) {
|
||||
return frappe.boot.user.can_select.indexOf(doctype)!==-1;
|
||||
},
|
||||
|
||||
can_read: function(doctype) {
|
||||
return frappe.boot.user.can_read.indexOf(doctype)!==-1;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -527,7 +527,7 @@ frappe.ui.filter_utils = {
|
|||
['Date', 'Datetime', 'DateRange', 'Select'].includes(df.fieldtype)
|
||||
) {
|
||||
df.fieldtype = 'Select';
|
||||
df.options = this.get_timespan_options(['Last', 'Today', 'This', 'Next']);
|
||||
df.options = this.get_timespan_options(['Last', 'Yesterday', 'Today', 'Tomorrow', 'This', 'Next']);
|
||||
}
|
||||
if (condition === 'is') {
|
||||
df.fieldtype = 'Select';
|
||||
|
|
@ -542,7 +542,6 @@ frappe.ui.filter_utils = {
|
|||
get_timespan_options(periods) {
|
||||
const period_map = {
|
||||
Last: ['Week', 'Month', 'Quarter', '6 months', 'Year'],
|
||||
Today: null,
|
||||
This: ['Week', 'Month', 'Quarter', 'Year'],
|
||||
Next: ['Week', 'Month', 'Quarter', '6 months', 'Year'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ window.replace_all = function (s, t1, t2) {
|
|||
return s.split(t1).join(t2);
|
||||
}
|
||||
|
||||
window.strip_html = function (txt) {
|
||||
window.strip_html = function(txt) {
|
||||
return cstr(txt).replace(/<[^>]*>/g, "");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1220,6 +1220,7 @@ Object.assign(frappe.utils, {
|
|||
if (Math.floor(number) === number) return 0;
|
||||
return number.toString().split(".")[1].length || 0;
|
||||
},
|
||||
|
||||
build_summary_item(summary) {
|
||||
if (summary.type == "separator") {
|
||||
return $(`<div class="summary-separator">
|
||||
|
|
@ -1242,6 +1243,7 @@ Object.assign(frappe.utils, {
|
|||
<div class="summary-value ${color}">${value}</div>
|
||||
</div>`);
|
||||
},
|
||||
|
||||
get_names_for_mentions() {
|
||||
let names_for_mentions = Object.keys(frappe.boot.user_info || [])
|
||||
.filter(user => {
|
||||
|
|
|
|||
|
|
@ -112,7 +112,6 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
{
|
||||
label: __("Message"),
|
||||
fieldtype: "Text Editor",
|
||||
reqd: 1,
|
||||
fieldname: "content",
|
||||
onchange: frappe.utils.debounce(
|
||||
this.save_as_draft.bind(this),
|
||||
|
|
@ -124,7 +123,7 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
label: __("Send me a copy"),
|
||||
fieldtype: "Check",
|
||||
fieldname: "send_me_a_copy",
|
||||
default: 1
|
||||
default: 1 // frappe.boot.user.send_me_a_copy
|
||||
},
|
||||
{
|
||||
label: __("Send Read Receipt"),
|
||||
|
|
|
|||
85
frappe/public/js/frappe/views/map/map_view.js
Normal file
85
frappe/public/js/frappe/views/map/map_view.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* frappe.views.MapView
|
||||
*/
|
||||
frappe.provide('frappe.utils.utils');
|
||||
frappe.provide("frappe.views");
|
||||
|
||||
frappe.views.MapView = class MapView extends frappe.views.ListView {
|
||||
get view_name() {
|
||||
return 'Map';
|
||||
}
|
||||
|
||||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
this.page_title = __('{0} Map', [this.page_title]);
|
||||
}
|
||||
|
||||
setup_view() {
|
||||
}
|
||||
|
||||
on_filter_change() {
|
||||
this.get_coords();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.get_coords()
|
||||
.then(() => {
|
||||
this.render_map_view();
|
||||
});
|
||||
this.$paging_area.find('.level-left').append('<div></div>');
|
||||
}
|
||||
|
||||
render_map_view() {
|
||||
this.map_id = frappe.dom.get_unique_id();
|
||||
|
||||
this.$result.html(`<div id="${this.map_id}" class="map-view-container"></div>`);
|
||||
|
||||
L.Icon.Default.imagePath = '/assets/frappe/images/leaflet/';
|
||||
this.map = L.map(this.map_id).setView(frappe.utils.map_defaults.center,
|
||||
frappe.utils.map_defaults.zoom);
|
||||
|
||||
L.tileLayer(frappe.utils.map_defaults.tiles,
|
||||
frappe.utils.map_defaults.options).addTo(this.map);
|
||||
|
||||
L.control.scale().addTo(this.map);
|
||||
if (this.coords.features && this.coords.features.length) {
|
||||
this.coords.features.forEach(
|
||||
coords => L.geoJSON(coords).bindPopup(coords.properties.name).addTo(this.map)
|
||||
);
|
||||
let lastCoords = this.coords.features[0].geometry.coordinates.reverse();
|
||||
this.map.panTo(lastCoords, 8);
|
||||
}
|
||||
}
|
||||
|
||||
get_coords() {
|
||||
let get_coords_method = this.settings && this.settings.get_coords_method || 'frappe.geo.utils.get_coords';
|
||||
|
||||
if (cur_list.meta.fields.find(i => i.fieldname === 'location' && i.fieldtype === 'Geolocation')) {
|
||||
this.type = 'location_field';
|
||||
} else if (cur_list.meta.fields.find(i => i.fieldname === "latitude") &&
|
||||
cur_list.meta.fields.find(i => i.fieldname === "longitude")) {
|
||||
this.type = 'coordinates';
|
||||
}
|
||||
return frappe.call({
|
||||
method: get_coords_method,
|
||||
args: {
|
||||
doctype: this.doctype,
|
||||
filters: cur_list.filter_area.get(),
|
||||
type: this.type
|
||||
}
|
||||
}).then(r => {
|
||||
this.coords = r.message;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
get required_libs() {
|
||||
return [
|
||||
"assets/frappe/js/lib/leaflet/leaflet.css",
|
||||
"assets/frappe/js/lib/leaflet/leaflet.js"
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
|
@ -708,6 +708,32 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
super.build_fields();
|
||||
}
|
||||
|
||||
reorder_fields() {
|
||||
// generate table fields in the required format ["name", "DocType"]
|
||||
// these are fields in the column before adding new fields
|
||||
let table_fields = this.columns.map(df => [df.field, df.docfield.parent]);
|
||||
|
||||
// filter fields that are already in table
|
||||
// iterate over table_fields to preserve the existing order of fields
|
||||
// The filter will ensure the unchecked fields are removed
|
||||
let fields_already_in_table = table_fields.filter(df => {
|
||||
return this.fields.find((field) => {
|
||||
return df[0] == field[0] && df[1] == field[1]
|
||||
})
|
||||
})
|
||||
|
||||
// find new fields that didn't already exists
|
||||
// This will be appended to the end of the table
|
||||
let fields_to_add = this.fields.filter(df => {
|
||||
return !table_fields.find((field) => {
|
||||
return df[0] == field[0] && df[1] == field[1]
|
||||
})
|
||||
})
|
||||
|
||||
// rebuild fields
|
||||
this.fields = [...fields_already_in_table, ...fields_to_add];
|
||||
}
|
||||
|
||||
get_fields() {
|
||||
let fields = this.fields.map(f => {
|
||||
let column_name = frappe.model.get_full_column_name(f[0], f[1]);
|
||||
|
|
@ -1329,6 +1355,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
|
||||
this.fields.map(f => this.add_currency_column(f[0], f[1]));
|
||||
|
||||
this.reorder_fields();
|
||||
this.build_fields();
|
||||
this.setup_columns();
|
||||
|
||||
|
|
|
|||
|
|
@ -94,17 +94,17 @@ frappe.views.TreeView = Class.extend({
|
|||
var me = this;
|
||||
this.opts.onload && this.opts.onload(me);
|
||||
},
|
||||
make_filters: function(){
|
||||
make_filters: function() {
|
||||
var me = this;
|
||||
frappe.treeview_settings.filters = []
|
||||
$.each(this.opts.filters || [], function(i, filter) {
|
||||
if(frappe.route_options && frappe.route_options[filter.fieldname]) {
|
||||
filter.default = frappe.route_options[filter.fieldname]
|
||||
if (frappe.route_options && frappe.route_options[filter.fieldname]) {
|
||||
filter.default = frappe.route_options[filter.fieldname];
|
||||
}
|
||||
|
||||
if(!filter.disable_onchange) {
|
||||
if (!filter.disable_onchange) {
|
||||
filter.change = function() {
|
||||
filter.on_change && filter.on_change();
|
||||
filter.onchange && filter.onchange();
|
||||
var val = this.get_value();
|
||||
me.args[filter.fieldname] = val;
|
||||
if (val) {
|
||||
|
|
@ -114,7 +114,7 @@ frappe.views.TreeView = Class.extend({
|
|||
}
|
||||
me.set_title();
|
||||
me.make_tree();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
me.page.add_field(filter);
|
||||
|
|
@ -122,7 +122,7 @@ frappe.views.TreeView = Class.extend({
|
|||
if (filter.default) {
|
||||
$("[data-fieldname='"+filter.fieldname+"']").trigger("change");
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
get_root: function() {
|
||||
var me = this;
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ frappe.ready(function() {
|
|||
|
||||
function setup_fields(form_data) {
|
||||
form_data.web_form.web_form_fields.map(df => {
|
||||
df.is_web_form = true;
|
||||
if (df.fieldtype === "Table") {
|
||||
df.get_data = () => {
|
||||
let data = [];
|
||||
|
|
@ -99,14 +100,13 @@ frappe.ready(function() {
|
|||
if (field.fieldtype === "Link") {
|
||||
field.only_select = true;
|
||||
}
|
||||
field.is_web_form = true;
|
||||
});
|
||||
|
||||
if (df.fieldtype === "Attach") {
|
||||
df.is_private = true;
|
||||
}
|
||||
|
||||
df.is_web_form = true;
|
||||
|
||||
delete df.parent;
|
||||
delete df.parentfield;
|
||||
delete df.parenttype;
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@
|
|||
}
|
||||
|
||||
.hero.align-center {
|
||||
h1, .hero-subtitle, .hero-buttons {
|
||||
h1, .hero-title, .hero-subtitle, .hero-buttons {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
.hero-title, .hero-subtitle {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList">
|
||||
{%- set parents = parents[-3:] %}
|
||||
{% set count = (parents | length) + 1 %}
|
||||
{% for parent in parents %}
|
||||
<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem" class="breadcrumb-item">
|
||||
<a itemprop="item" href="{{ url_prefix }}{{ parent.route | abs_url }}" itemprop="url">
|
||||
|
|
@ -11,8 +12,11 @@
|
|||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
{{ title or "" }}
|
||||
<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem" class="breadcrumb-item active" aria-current="page">
|
||||
<span itemprop="item">
|
||||
<span itemprop="name">{{ title }}</span>
|
||||
<meta itemprop="position" content="{{ count }}"/>
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@
|
|||
<div>{{ frappe.render_template(df.options, {"doc": doc}) or "" }}</div>
|
||||
{%- elif df.fieldtype in ("Text", "Text Editor", "Code", "Long Text") -%}
|
||||
{{ render_text_field(df, doc) }}
|
||||
{%- elif df.fieldtype in ("Image", "Attach Image", "Attach")
|
||||
and (guess_mimetype(doc[df.fieldname])[0] or "").startswith("image/") -%}
|
||||
{%- elif df.fieldtype in ("Image", "Attach Image")
|
||||
and (
|
||||
(guess_mimetype(doc[df.fieldname])[0] or "").startswith("image/")
|
||||
or doc[df.fieldname].startswith("http")
|
||||
) -%}
|
||||
{{ render_image(df, doc) }}
|
||||
{%- elif df.fieldtype=="Geolocation" -%}
|
||||
{{ render_geolocation(df, doc) }}
|
||||
|
|
@ -137,15 +140,14 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
|
|||
style="width: 12px; height: 12px; margin-top: 5px;">
|
||||
<path d="M2 9.66667L5.33333 13L14 3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{% elif df.fieldtype=="Image" %}
|
||||
{% elif df.fieldtype in ("Image", "Attach Image") %}
|
||||
<img src="{{ doc[doc.meta.get_field(df.fieldname).options] }}"
|
||||
class="img-responsive"
|
||||
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
|
||||
{% elif df.fieldtype=="Signature" %}
|
||||
<img src="{{ doc[df.fieldname] }}" class="signature-img img-responsive"
|
||||
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
|
||||
{% elif df.fieldtype in ("Attach", "Attach Image") and doc[df.fieldname]
|
||||
and frappe.utils.is_image(doc[df.fieldname]) %}
|
||||
{% elif df.fieldtype == "Attach" and doc[df.fieldname] and frappe.utils.is_image(doc[df.fieldname]) %}
|
||||
<img src="{{ doc[df.fieldname] }}" class="img-responsive"
|
||||
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
|
||||
{% elif df.fieldtype=="HTML" %}
|
||||
|
|
|
|||
57
frappe/tests/test_cors.py
Normal file
57
frappe/tests/test_cors.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, unittest
|
||||
from werkzeug.wrappers import Response
|
||||
from frappe.app import process_response
|
||||
|
||||
HEADERS = ('Access-Control-Allow-Origin', 'Access-Control-Allow-Credentials',
|
||||
'Access-Control-Allow-Methods', 'Access-Control-Allow-Headers')
|
||||
|
||||
class TestCORS(unittest.TestCase):
|
||||
def make_request_and_test(self, origin='http://example.com', absent=False):
|
||||
self.origin = origin
|
||||
|
||||
headers = {}
|
||||
if origin:
|
||||
headers = {'Origin': origin}
|
||||
|
||||
frappe.utils.set_request(headers=headers)
|
||||
|
||||
self.response = Response()
|
||||
process_response(self.response)
|
||||
|
||||
for header in HEADERS:
|
||||
if absent:
|
||||
self.assertNotIn(header, self.response.headers)
|
||||
else:
|
||||
if header == 'Access-Control-Allow-Origin':
|
||||
self.assertEqual(self.response.headers.get(header), self.origin)
|
||||
else:
|
||||
self.assertIn(header, self.response.headers)
|
||||
|
||||
def test_cors_disabled(self):
|
||||
frappe.conf.allow_cors = None
|
||||
self.make_request_and_test('http://example.com', True)
|
||||
|
||||
def test_request_without_origin(self):
|
||||
frappe.conf.allow_cors = 'http://example.com'
|
||||
self.make_request_and_test(None, True)
|
||||
|
||||
def test_valid_origin(self):
|
||||
frappe.conf.allow_cors = 'http://example.com'
|
||||
self.make_request_and_test()
|
||||
|
||||
frappe.conf.allow_cors = "*"
|
||||
self.make_request_and_test()
|
||||
|
||||
frappe.conf.allow_cors = ['http://example.com', 'https://example.com']
|
||||
self.make_request_and_test()
|
||||
|
||||
def test_invalid_origin(self):
|
||||
frappe.conf.allow_cors = 'http://example1.com'
|
||||
self.make_request_and_test(absent=True)
|
||||
|
||||
frappe.conf.allow_cors = ['http://example1.com', 'https://example.com']
|
||||
self.make_request_and_test(absent=True)
|
||||
|
|
@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
|||
import unittest
|
||||
import frappe
|
||||
from frappe.desk.doctype.todo.todo import ToDo
|
||||
from frappe.cache_manager import clear_controller_cache
|
||||
|
||||
class TestHooks(unittest.TestCase):
|
||||
def test_hooks(self):
|
||||
|
|
@ -17,21 +18,20 @@ class TestHooks(unittest.TestCase):
|
|||
hooks.get("doc_events").get("*").get("on_update"))
|
||||
|
||||
def test_override_doctype_class(self):
|
||||
# mock get_hooks
|
||||
original = frappe.get_hooks
|
||||
def get_hooks(hook=None, default=None, app_name=None):
|
||||
if hook == 'override_doctype_class':
|
||||
return {
|
||||
'ToDo': ['frappe.tests.test_hooks.CustomToDo']
|
||||
}
|
||||
return original(hook, default, app_name)
|
||||
frappe.get_hooks = get_hooks
|
||||
from frappe import hooks
|
||||
|
||||
# Set hook
|
||||
hooks.override_doctype_class = {
|
||||
'ToDo': ['frappe.tests.test_hooks.CustomToDo']
|
||||
}
|
||||
|
||||
# Clear cache
|
||||
frappe.cache().delete_value('app_hooks')
|
||||
clear_controller_cache('ToDo')
|
||||
|
||||
todo = frappe.get_doc(doctype='ToDo', description='asdf')
|
||||
self.assertTrue(isinstance(todo, CustomToDo))
|
||||
|
||||
# restore
|
||||
frappe.get_hooks = original
|
||||
|
||||
class CustomToDo(ToDo):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import frappe.defaults
|
|||
import unittest
|
||||
import frappe.model.meta
|
||||
from frappe.permissions import (add_user_permission, remove_user_permission,
|
||||
clear_user_permissions_for_doctype, get_doc_permissions, add_permission)
|
||||
clear_user_permissions_for_doctype, get_doc_permissions, add_permission, update_permission_property)
|
||||
from frappe.core.page.permission_manager.permission_manager import update, reset
|
||||
from frappe.test_runner import make_test_records_for_doctype
|
||||
from frappe.core.doctype.user_permission.user_permission import clear_user_permissions
|
||||
|
|
@ -58,6 +58,24 @@ class TestPermissions(unittest.TestCase):
|
|||
post = frappe.get_doc("Blog Post", "-test-blog-post")
|
||||
self.assertTrue(post.has_permission("read"))
|
||||
|
||||
def test_select_permission(self):
|
||||
# grant only select perm to blog post
|
||||
add_permission('Blog Post', 'Sales User', 0)
|
||||
update_permission_property('Blog Post', 'Sales User', 0, 'select', 1)
|
||||
update_permission_property('Blog Post', 'Sales User', 0, 'read', 0)
|
||||
update_permission_property('Blog Post', 'Sales User', 0, 'write', 0)
|
||||
|
||||
frappe.clear_cache(doctype="Blog Post")
|
||||
frappe.set_user("test3@example.com")
|
||||
|
||||
# validate select perm
|
||||
post = frappe.get_doc("Blog Post", "-test-blog-post")
|
||||
self.assertTrue(post.has_permission("select"))
|
||||
|
||||
# validate does not have read and write perm
|
||||
self.assertFalse(post.has_permission("read"))
|
||||
self.assertRaises(frappe.PermissionError, post.save)
|
||||
|
||||
def test_user_permissions_in_doc(self):
|
||||
add_user_permission("Blog Category", "-test-blog-category-1",
|
||||
"test2@example.com")
|
||||
|
|
|
|||
42
frappe/tests/tests_geo_utils.py
Normal file
42
frappe/tests/tests_geo_utils.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.geo.utils import get_coords
|
||||
|
||||
|
||||
class TestGeoUtils(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.todo = frappe.get_doc(
|
||||
dict(doctype='ToDo', description='Test description', assigned_by='Administrator')).insert()
|
||||
|
||||
self.test_location_dict = {'type': 'FeatureCollection', 'features': [
|
||||
{'type': 'Feature', 'properties': {}, "geometry": {'type': 'Point', 'coordinates': [49.20433, 55.753395]}}]}
|
||||
self.test_location = frappe.get_doc({'name': 'Test Location', 'doctype': 'Location',
|
||||
'location': str(self.test_location_dict)})
|
||||
|
||||
self.test_filter_exists = [['Location', 'name', 'like', '%Test Location%']]
|
||||
self.test_filter_not_exists = [['Location', 'name', 'like', '%Test Location Not exists%']]
|
||||
self.test_filter_todo = [['ToDo', 'description', 'like', '%Test description%']]
|
||||
|
||||
def test_get_coords_location_with_filter_exists(self):
|
||||
coords = get_coords('Location', self.test_filter_exists, 'location_field')
|
||||
self.assertEqual(self.test_location_dict['features'][0]['geometry'], coords['features'][0]['geometry'])
|
||||
|
||||
def test_get_coords_location_with_filter_not_exists(self):
|
||||
coords = get_coords('Location', self.test_filter_not_exists, 'location_field')
|
||||
self.assertEqual(coords, {'type': 'FeatureCollection', 'features': []})
|
||||
|
||||
def test_get_coords_from_not_existable_location(self):
|
||||
self.assertRaises(frappe.ValidationError, get_coords, 'ToDo', self.test_filter_todo, 'location_field')
|
||||
|
||||
def test_get_coords_from_not_existable_coords(self):
|
||||
self.assertRaises(frappe.ValidationError, get_coords, 'ToDo', self.test_filter_todo, 'coordinates')
|
||||
|
||||
def tearDown(self):
|
||||
self.todo.delete()
|
||||
|
|
@ -95,6 +95,24 @@ def create_doctype(name, fields):
|
|||
"name": name
|
||||
}).insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_child_doctype(name, fields):
|
||||
fields = frappe.parse_json(fields)
|
||||
if frappe.db.exists('DocType', name):
|
||||
return
|
||||
frappe.get_doc({
|
||||
"doctype": "DocType",
|
||||
"module": "Core",
|
||||
"istable": 1,
|
||||
"custom": 1,
|
||||
"fields": fields,
|
||||
"permissions": [{
|
||||
"role": "System Manager",
|
||||
"read": 1
|
||||
}],
|
||||
"name": name
|
||||
}).insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_contact_records():
|
||||
if frappe.db.get_all('Contact', {'first_name': 'Test Form Contact 1'}):
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ def get_full_dict(lang):
|
|||
frappe.local.lang_full_dict = load_lang(lang)
|
||||
|
||||
try:
|
||||
# get user specific transaltion data
|
||||
# get user specific translation data
|
||||
user_translations = get_user_translations(lang)
|
||||
frappe.local.lang_full_dict.update(user_translations)
|
||||
except Exception:
|
||||
|
|
|
|||
|
|
@ -1577,7 +1577,7 @@ Monospace,Monospace,
|
|||
More articles on {0},Weitere Artikel zum {0},
|
||||
More content for the bottom of the page.,Zusätzlicher Inhalt für den unteren Teil der Seite.,
|
||||
Most Used,Am Meisten verwendet,
|
||||
Move To,Ziehen nach,
|
||||
Move To,Bewegen nach,
|
||||
Move To Trash,In den Papierkorb verschieben,
|
||||
Move to Row Number,Gehe zu Zeilennummer,
|
||||
Mr,Hr.,
|
||||
|
|
|
|||
|
|
|
@ -445,25 +445,29 @@ def get_weekday(datetime=None):
|
|||
return weekdays[datetime.weekday()]
|
||||
|
||||
def get_timespan_date_range(timespan):
|
||||
today = nowdate()
|
||||
date_range_map = {
|
||||
"last week": [add_to_date(nowdate(), days=-7), nowdate()],
|
||||
"last month": [add_to_date(nowdate(), months=-1), nowdate()],
|
||||
"last quarter": [add_to_date(nowdate(), months=-3), nowdate()],
|
||||
"last 6 months": [add_to_date(nowdate(), months=-6), nowdate()],
|
||||
"last year": [add_to_date(nowdate(), years=-1), nowdate()],
|
||||
"today": [nowdate(), nowdate()],
|
||||
"this week": [get_first_day_of_week(nowdate(), as_str=True), nowdate()],
|
||||
"this month": [get_first_day(nowdate(), as_str=True), nowdate()],
|
||||
"this quarter": [get_quarter_start(nowdate(), as_str=True), nowdate()],
|
||||
"this year": [get_year_start(nowdate(), as_str=True), nowdate()],
|
||||
"next week": [nowdate(), add_to_date(nowdate(), days=7)],
|
||||
"next month": [nowdate(), add_to_date(nowdate(), months=1)],
|
||||
"next quarter": [nowdate(), add_to_date(nowdate(), months=3)],
|
||||
"next 6 months": [nowdate(), add_to_date(nowdate(), months=6)],
|
||||
"next year": [nowdate(), add_to_date(nowdate(), years=1)],
|
||||
"last week": lambda: (add_to_date(today, days=-7), today),
|
||||
"last month": lambda: (add_to_date(today, months=-1), today),
|
||||
"last quarter": lambda: (add_to_date(today, months=-3), today),
|
||||
"last 6 months": lambda: (add_to_date(today, months=-6), today),
|
||||
"last year": lambda: (add_to_date(today, years=-1), today),
|
||||
"yesterday": lambda: (add_to_date(today, days=-1),) * 2,
|
||||
"today": lambda: (today, today),
|
||||
"tomorrow": lambda: (add_to_date(today, days=1),) * 2,
|
||||
"this week": lambda: (get_first_day_of_week(today, as_str=True), today),
|
||||
"this month": lambda: (get_first_day(today, as_str=True), today),
|
||||
"this quarter": lambda: (get_quarter_start(today, as_str=True), today),
|
||||
"this year": lambda: (get_year_start(today, as_str=True), today),
|
||||
"next week": lambda: (today, add_to_date(today, days=7)),
|
||||
"next month": lambda: (today, add_to_date(today, months=1)),
|
||||
"next quarter": lambda: (today, add_to_date(today, months=3)),
|
||||
"next 6 months": lambda: (today, add_to_date(today, months=6)),
|
||||
"next year": lambda: (today, add_to_date(today, years=1)),
|
||||
}
|
||||
|
||||
return date_range_map.get(timespan)
|
||||
if timespan in date_range_map:
|
||||
return date_range_map[timespan]()
|
||||
|
||||
def global_date_format(date, format="long"):
|
||||
"""returns localized date in the form of January 1, 2012"""
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class UserPermissions:
|
|||
|
||||
self.all_read = []
|
||||
self.can_create = []
|
||||
self.can_select = []
|
||||
self.can_read = []
|
||||
self.can_write = []
|
||||
self.can_cancel = []
|
||||
|
|
@ -104,6 +105,9 @@ class UserPermissions:
|
|||
if not p.get("read") and (dt in user_shared):
|
||||
p["read"] = 1
|
||||
|
||||
if p.get('select'):
|
||||
self.can_select.append(dt)
|
||||
|
||||
if not dtp.get('istable'):
|
||||
if p.get('create') and not dtp.get('issingle'):
|
||||
if dtp.get('in_create'):
|
||||
|
|
@ -193,9 +197,8 @@ class UserPermissions:
|
|||
d.name = self.name
|
||||
d.roles = self.get_roles()
|
||||
d.defaults = self.get_defaults()
|
||||
|
||||
for key in ("can_create", "can_write", "can_read", "can_cancel", "can_delete",
|
||||
"can_get_report", "allow_modules", "all_read", "can_search",
|
||||
for key in ("can_select", "can_create", "can_write", "can_read", "can_cancel",
|
||||
"can_delete", "can_get_report", "allow_modules", "all_read", "can_search",
|
||||
"in_create", "can_export", "can_import", "can_print", "can_email",
|
||||
"can_set_user_permissions"):
|
||||
d[key] = list(set(getattr(self, key)))
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
{%- if post.featured -%}
|
||||
<h5 class="mt-1"><span class="text-dark">{{ post.title }}</span></h5>
|
||||
{%- else -%}
|
||||
<h5 class="mt-1"><span class="text-dark">{{ post.title }}</span></h3>
|
||||
<h5 class="mt-1"><span class="text-dark">{{ post.title }}</span></h5>
|
||||
{%- endif -%}
|
||||
<p class="post-description text-muted">{{ post.intro }}</p>
|
||||
</div>
|
||||
|
|
@ -38,4 +38,4 @@
|
|||
</div>
|
||||
<a class="stretched-link" href="/{{ post.route }}"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
6
frappe/website/js/bootstrap-4.js
vendored
6
frappe/website/js/bootstrap-4.js
vendored
|
|
@ -18,7 +18,7 @@ $('.dropdown-menu a.dropdown-toggle').on('click', function (e) {
|
|||
return false;
|
||||
});
|
||||
|
||||
frappe.get_modal = function(title, content) {
|
||||
frappe.get_modal = function (title, content) {
|
||||
return $(
|
||||
`<div class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
|
|
@ -33,6 +33,10 @@ frappe.get_modal = function(title, content) {
|
|||
${content}
|
||||
</div>
|
||||
<div class="modal-footer hidden">
|
||||
<button type="button" class="btn btn-default btn-sm btn-modal-close" data-dismiss="modal">
|
||||
<i class="octicon octicon-x visible-xs" style="padding: 1px 0px;"></i>
|
||||
<span class="hidden-xs">${__("Close")}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary hidden"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<div class="testimonial-content">
|
||||
<span>“</span>
|
||||
{{ content }}
|
||||
<span>”</span>
|
||||
“{{ content }}”
|
||||
</div>
|
||||
<div class="testimonial-by">
|
||||
{{ name }}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
"redis": "^2.8.0",
|
||||
"showdown": "^1.9.1",
|
||||
"snyk": "^1.425.4",
|
||||
"socket.io": "^2.3.0",
|
||||
"socket.io": "^2.4.0",
|
||||
"superagent": "^3.8.2",
|
||||
"touch": "^3.1.0",
|
||||
"vue": "^2.6.11",
|
||||
|
|
|
|||
130
yarn.lock
130
yarn.lock
|
|
@ -693,13 +693,6 @@ bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
|
|||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
better-assert@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
|
||||
integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=
|
||||
dependencies:
|
||||
callsite "1.0.0"
|
||||
|
||||
big.js@^3.1.3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
|
||||
|
|
@ -940,11 +933,6 @@ caller-path@^2.0.0:
|
|||
dependencies:
|
||||
caller-callsite "^2.0.0"
|
||||
|
||||
callsite@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
|
||||
integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
|
||||
|
||||
callsites@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
|
||||
|
|
@ -1252,6 +1240,11 @@ component-emitter@1.2.1, component-emitter@^1.2.0, component-emitter@^1.2.1:
|
|||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
|
||||
integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
|
||||
|
||||
component-emitter@~1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
||||
|
||||
component-inherit@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
|
||||
|
|
@ -1320,16 +1313,16 @@ cookie-signature@1.0.6:
|
|||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||
|
||||
cookie@0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||
|
||||
cookie@0.4.0, cookie@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||
|
||||
cookie@~0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
|
||||
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
|
||||
|
||||
cookiejar@^2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
|
||||
|
|
@ -1982,20 +1975,20 @@ endian-reader@^0.3.0:
|
|||
resolved "https://registry.yarnpkg.com/endian-reader/-/endian-reader-0.3.0.tgz#84eca436b80aed0d0639c47291338b932efe50a0"
|
||||
integrity sha1-hOykNrgK7Q0GOcRykTOLky7+UKA=
|
||||
|
||||
engine.io-client@~3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.0.tgz#82a642b42862a9b3f7a188f41776b2deab643700"
|
||||
integrity sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==
|
||||
engine.io-client@~3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.5.0.tgz#fc1b4d9616288ce4f2daf06dcf612413dec941c7"
|
||||
integrity sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==
|
||||
dependencies:
|
||||
component-emitter "1.2.1"
|
||||
component-emitter "~1.3.0"
|
||||
component-inherit "0.0.3"
|
||||
debug "~4.1.0"
|
||||
debug "~3.1.0"
|
||||
engine.io-parser "~2.2.0"
|
||||
has-cors "1.1.0"
|
||||
indexof "0.0.1"
|
||||
parseqs "0.0.5"
|
||||
parseuri "0.0.5"
|
||||
ws "~6.1.0"
|
||||
parseqs "0.0.6"
|
||||
parseuri "0.0.6"
|
||||
ws "~7.4.2"
|
||||
xmlhttprequest-ssl "~1.5.4"
|
||||
yeast "0.1.2"
|
||||
|
||||
|
|
@ -2010,17 +2003,17 @@ engine.io-parser@~2.2.0:
|
|||
blob "0.0.5"
|
||||
has-binary2 "~1.0.2"
|
||||
|
||||
engine.io@~3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.4.0.tgz#3a962cc4535928c252759a00f98519cb46c53ff3"
|
||||
integrity sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==
|
||||
engine.io@~3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.5.0.tgz#9d6b985c8a39b1fe87cd91eb014de0552259821b"
|
||||
integrity sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==
|
||||
dependencies:
|
||||
accepts "~1.3.4"
|
||||
base64id "2.0.0"
|
||||
cookie "0.3.1"
|
||||
cookie "~0.4.1"
|
||||
debug "~4.1.0"
|
||||
engine.io-parser "~2.2.0"
|
||||
ws "^7.1.2"
|
||||
ws "~7.4.2"
|
||||
|
||||
entities@^1.1.1:
|
||||
version "1.1.2"
|
||||
|
|
@ -4623,11 +4616,6 @@ object-assign@^4.0.1, object-assign@^4.1.0:
|
|||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
||||
object-component@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
|
||||
integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=
|
||||
|
||||
object-copy@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
|
||||
|
|
@ -4938,19 +4926,15 @@ parse-passwd@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
|
||||
integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
|
||||
|
||||
parseqs@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
|
||||
integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=
|
||||
dependencies:
|
||||
better-assert "~1.0.0"
|
||||
parseqs@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5"
|
||||
integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==
|
||||
|
||||
parseuri@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
|
||||
integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=
|
||||
dependencies:
|
||||
better-assert "~1.0.0"
|
||||
parseuri@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a"
|
||||
integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==
|
||||
|
||||
parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
|
|
@ -6808,23 +6792,20 @@ socket.io-adapter@~1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b"
|
||||
integrity sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=
|
||||
|
||||
socket.io-client@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4"
|
||||
integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==
|
||||
socket.io-client@2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.4.0.tgz#aafb5d594a3c55a34355562fc8aea22ed9119a35"
|
||||
integrity sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==
|
||||
dependencies:
|
||||
backo2 "1.0.2"
|
||||
base64-arraybuffer "0.1.5"
|
||||
component-bind "1.0.0"
|
||||
component-emitter "1.2.1"
|
||||
debug "~4.1.0"
|
||||
engine.io-client "~3.4.0"
|
||||
component-emitter "~1.3.0"
|
||||
debug "~3.1.0"
|
||||
engine.io-client "~3.5.0"
|
||||
has-binary2 "~1.0.2"
|
||||
has-cors "1.1.0"
|
||||
indexof "0.0.1"
|
||||
object-component "0.0.3"
|
||||
parseqs "0.0.5"
|
||||
parseuri "0.0.5"
|
||||
parseqs "0.0.6"
|
||||
parseuri "0.0.6"
|
||||
socket.io-parser "~3.3.0"
|
||||
to-array "0.1.4"
|
||||
|
||||
|
|
@ -6846,16 +6827,16 @@ socket.io-parser@~3.4.0:
|
|||
debug "~4.1.0"
|
||||
isarray "2.0.1"
|
||||
|
||||
socket.io@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.3.0.tgz#cd762ed6a4faeca59bc1f3e243c0969311eb73fb"
|
||||
integrity sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==
|
||||
socket.io@^2.4.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.4.1.tgz#95ad861c9a52369d7f1a68acf0d4a1b16da451d2"
|
||||
integrity sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==
|
||||
dependencies:
|
||||
debug "~4.1.0"
|
||||
engine.io "~3.4.0"
|
||||
engine.io "~3.5.0"
|
||||
has-binary2 "~1.0.2"
|
||||
socket.io-adapter "~1.1.0"
|
||||
socket.io-client "2.3.0"
|
||||
socket.io-client "2.4.0"
|
||||
socket.io-parser "~3.4.0"
|
||||
|
||||
socks-proxy-agent@^4.0.1:
|
||||
|
|
@ -7970,17 +7951,10 @@ write-file-atomic@^3.0.0:
|
|||
signal-exit "^3.0.2"
|
||||
typedarray-to-buffer "^3.1.5"
|
||||
|
||||
ws@^7.1.2:
|
||||
version "7.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e"
|
||||
integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==
|
||||
|
||||
ws@~6.1.0:
|
||||
version "6.1.4"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
|
||||
integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
ws@~7.4.2:
|
||||
version "7.4.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.2.tgz#782100048e54eb36fe9843363ab1c68672b261dd"
|
||||
integrity sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==
|
||||
|
||||
xdg-basedir@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue