Преглед изворни кода

Merge branch 'develop' of https://github.com/frappe/frappe into rebrand-ui

version-14
Suraj Shetty пре 4 година
родитељ
комит
b94d5778a8
100 измењених фајлова са 1993 додато и 926 уклоњено
  1. +56
    -1
      cypress/integration/depends_on.js
  2. +38
    -3
      cypress/support/commands.js
  3. +16
    -0
      frappe/__init__.py
  4. +51
    -15
      frappe/app.py
  5. +6
    -4
      frappe/automation/doctype/auto_repeat/auto_repeat.js
  6. +7
    -7
      frappe/automation/doctype/auto_repeat/auto_repeat.json
  7. +13
    -0
      frappe/cache_manager.py
  8. +1
    -1
      frappe/core/doctype/comment/comment.py
  9. +10
    -1
      frappe/core/doctype/custom_docperm/custom_docperm.json
  10. +1
    -1
      frappe/core/doctype/data_import/importer.py
  11. +59
    -605
      frappe/core/doctype/docperm/docperm.json
  12. +9
    -10
      frappe/core/doctype/doctype/doctype.py
  13. +11
    -0
      frappe/core/doctype/document_naming_rule/document_naming_rule.py
  14. +2
    -2
      frappe/core/doctype/server_script/server_script.json
  15. +1
    -0
      frappe/core/doctype/server_script/server_script_utils.py
  16. +2
    -2
      frappe/core/doctype/system_settings/system_settings.json
  17. +5
    -0
      frappe/core/doctype/user/user.json
  18. +3
    -3
      frappe/core/doctype/version/version_view.html
  19. +1
    -1
      frappe/core/page/permission_manager/permission_manager.js
  20. +13
    -1
      frappe/core/page/permission_manager/permission_manager.py
  21. +0
    -1
      frappe/desk/form/save.py
  22. +2
    -1
      frappe/desk/search.py
  23. +1
    -1
      frappe/email/doctype/email_account/email_account.py
  24. +12
    -2
      frappe/event_streaming/doctype/event_producer/event_producer.py
  25. +96
    -0
      frappe/geo/utils.py
  26. +1
    -1
      frappe/hooks.py
  27. +0
    -0
      frappe/integrations/doctype/connected_app/__init__.py
  28. +38
    -0
      frappe/integrations/doctype/connected_app/connected_app.js
  29. +166
    -0
      frappe/integrations/doctype/connected_app/connected_app.json
  30. +133
    -0
      frappe/integrations/doctype/connected_app/connected_app.py
  31. +162
    -0
      frappe/integrations/doctype/connected_app/test_connected_app.py
  32. +13
    -0
      frappe/integrations/doctype/connected_app/test_records.json
  33. +1
    -2
      frappe/integrations/doctype/oauth_client/test_records.json
  34. +0
    -0
      frappe/integrations/doctype/oauth_scope/__init__.py
  35. +30
    -0
      frappe/integrations/doctype/oauth_scope/oauth_scope.json
  36. +10
    -0
      frappe/integrations/doctype/oauth_scope/oauth_scope.py
  37. +0
    -0
      frappe/integrations/doctype/query_parameters/__init__.py
  38. +37
    -0
      frappe/integrations/doctype/query_parameters/query_parameters.json
  39. +10
    -0
      frappe/integrations/doctype/query_parameters/query_parameters.py
  40. +14
    -0
      frappe/integrations/doctype/social_login_key/test_social_login_key.py
  41. +0
    -0
      frappe/integrations/doctype/token_cache/__init__.py
  42. +18
    -0
      frappe/integrations/doctype/token_cache/test_records.json
  43. +37
    -0
      frappe/integrations/doctype/token_cache/test_token_cache.py
  44. +8
    -0
      frappe/integrations/doctype/token_cache/token_cache.js
  45. +110
    -0
      frappe/integrations/doctype/token_cache/token_cache.json
  46. +67
    -0
      frappe/integrations/doctype/token_cache/token_cache.py
  47. +1
    -1
      frappe/integrations/doctype/webhook/webhook.py
  48. +1
    -0
      frappe/integrations/oauth2.py
  49. +8
    -5
      frappe/model/base_document.py
  50. +9
    -3
      frappe/model/db_query.py
  51. +4
    -2
      frappe/model/document.py
  52. +3
    -1
      frappe/model/meta.py
  53. +10
    -2
      frappe/model/rename_doc.py
  54. +2
    -3
      frappe/model/workflow.py
  55. +1
    -0
      frappe/patches.txt
  56. +11
    -0
      frappe/patches/v13_0/delete_package_publish_tool.py
  57. +14
    -0
      frappe/patches/v13_0/website_theme_custom_scss.py
  58. +4
    -3
      frappe/permissions.py
  59. +1
    -0
      frappe/public/build.json
  60. +7
    -0
      frappe/public/css/list.css
  61. +22
    -5
      frappe/public/js/frappe/form/controls/base_control.js
  62. +1
    -1
      frappe/public/js/frappe/form/controls/base_input.js
  63. +6
    -4
      frappe/public/js/frappe/form/controls/geolocation.js
  64. +11
    -0
      frappe/public/js/frappe/form/controls/link.js
  65. +1
    -1
      frappe/public/js/frappe/form/controls/rating.js
  66. +2
    -1
      frappe/public/js/frappe/form/controls/table.js
  67. +4
    -1
      frappe/public/js/frappe/form/form.js
  68. +9
    -5
      frappe/public/js/frappe/form/formatters.js
  69. +2
    -0
      frappe/public/js/frappe/form/grid.js
  70. +3
    -0
      frappe/public/js/frappe/form/grid_row_form.js
  71. +61
    -58
      frappe/public/js/frappe/form/layout.js
  72. +17
    -4
      frappe/public/js/frappe/form/toolbar.js
  73. +2
    -11
      frappe/public/js/frappe/form/workflow.js
  74. +122
    -7
      frappe/public/js/frappe/list/list_sidebar.js
  75. +6
    -2
      frappe/public/js/frappe/model/model.js
  76. +1
    -2
      frappe/public/js/frappe/ui/filters/filter.js
  77. +1
    -1
      frappe/public/js/frappe/utils/common.js
  78. +2
    -0
      frappe/public/js/frappe/utils/utils.js
  79. +1
    -2
      frappe/public/js/frappe/views/communication.js
  80. +85
    -0
      frappe/public/js/frappe/views/map/map_view.js
  81. +27
    -0
      frappe/public/js/frappe/views/reports/report_view.js
  82. +7
    -7
      frappe/public/js/frappe/views/treeview.js
  83. +2
    -2
      frappe/public/js/frappe/web_form/webform_script.js
  84. +2
    -2
      frappe/public/scss/website/page_builder.scss
  85. +6
    -2
      frappe/templates/includes/breadcrumbs.html
  86. +7
    -5
      frappe/templates/print_formats/standard_macros.html
  87. +57
    -0
      frappe/tests/test_cors.py
  88. +11
    -11
      frappe/tests/test_hooks.py
  89. +19
    -1
      frappe/tests/test_permissions.py
  90. +42
    -0
      frappe/tests/tests_geo_utils.py
  91. +18
    -0
      frappe/tests/ui_test_helpers.py
  92. +1
    -1
      frappe/translate.py
  93. +1
    -1
      frappe/translations/de.csv
  94. +20
    -16
      frappe/utils/data.py
  95. +6
    -3
      frappe/utils/user.py
  96. +2
    -2
      frappe/website/doctype/blog_post/templates/blog_post_row.html
  97. +5
    -1
      frappe/website/js/bootstrap-4.js
  98. +1
    -3
      frappe/website/web_template/testimonial/testimonial.html
  99. +1
    -1
      package.json
  100. +52
    -78
      yarn.lock

+ 56
- 1
cypress/integration/depends_on.js Прегледај датотеку

@@ -3,7 +3,31 @@ context('Depends On', () => {
cy.login(); cy.login();
cy.visit('/app/website'); cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => { 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', name: 'Test Depends On',
fields: [ fields: [
{ {
@@ -24,6 +48,13 @@ context('Depends On', () => {
"fieldtype": "Data", "fieldtype": "Data",
'depends_on': "eval:doc.test_field=='Value'" '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('body').click();
cy.get('.control-input [data-fieldname="dependant_field"]').should('not.be.disabled'); 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', () => { it('should display the field depending on other fields value', () => {
cy.new_form('Test Depends On'); cy.new_form('Test Depends On');
cy.get('.control-input [data-fieldname="display_dependant_field"]').should('not.be.visible'); cy.get('.control-input [data-fieldname="display_dependant_field"]').should('not.be.visible');


+ 38
- 3
cypress/support/commands.js Прегледај датотеку

@@ -160,7 +160,7 @@ Cypress.Commands.add('remove_doc', (doctype, name) => {


Cypress.Commands.add('create_records', doc => { Cypress.Commands.add('create_records', doc => {
return cy 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); .then(r => r.message);
}); });


@@ -186,7 +186,7 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
if (fieldtype === 'Select') { if (fieldtype === 'Select') {
cy.get('@input').select(value); cy.get('@input').select(value);
} else { } else {
cy.get('@input').type(value, { waitForAnimations: false, force: true });
cy.get('@input').type(value, {waitForAnimations: false, force: true});
} }
return cy.get('@input'); return cy.get('@input');
}); });
@@ -204,8 +204,43 @@ Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => {
return cy.get(selector); 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 => { 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 => { Cypress.Commands.add('new_form', doctype => {


+ 16
- 0
frappe/__init__.py Прегледај датотеку

@@ -27,6 +27,7 @@ __version__ = '13.0.0-dev'
__title__ = "Frappe Framework" __title__ = "Frappe Framework"


local = Local() local = Local()
controllers = {}


class _dict(dict): class _dict(dict):
"""dict like object that exposes keys as attributes""" """dict like object that exposes keys as attributes"""
@@ -628,6 +629,21 @@ def clear_cache(user=None, doctype=None):


local.role_permissions = {} 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): def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=False, throw=False):
"""Raises `frappe.PermissionError` if not permitted. """Raises `frappe.PermissionError` if not permitted.




+ 51
- 15
frappe/app.py Прегледај датотеку

@@ -7,8 +7,8 @@ import os
from six import iteritems from six import iteritems
import logging import logging


from werkzeug.wrappers import Request
from werkzeug.local import LocalManager from werkzeug.local import LocalManager
from werkzeug.wrappers import Request, Response
from werkzeug.exceptions import HTTPException, NotFound from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.middleware.profiler import ProfilerMiddleware from werkzeug.middleware.profiler import ProfilerMiddleware
from werkzeug.middleware.shared_data import SharedDataMiddleware from werkzeug.middleware.shared_data import SharedDataMiddleware
@@ -57,19 +57,22 @@ def application(request):
frappe.monitor.start() frappe.monitor.start()
frappe.rate_limiter.apply() 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() response = frappe.handler.handle()


elif frappe.request.path.startswith("/api/"):
elif request.path.startswith("/api/"):
response = frappe.api.handle() response = frappe.api.handle()


elif frappe.request.path.startswith('/backups'):
elif request.path.startswith('/backups'):
response = frappe.utils.response.download_backup(request.path) 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) 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() response = frappe.website.render.render()


else: else:
@@ -88,13 +91,9 @@ def application(request):
rollback = after_request(rollback) rollback = after_request(rollback)


finally: 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() 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.rate_limiter.update()
frappe.monitor.stop(response) frappe.monitor.stop(response)
frappe.recorder.dump() frappe.recorder.dump()
@@ -110,9 +109,7 @@ def application(request):
"http_status_code": getattr(response, "status_code", "NOTFOUND") "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() frappe.destroy()


return response return response
@@ -134,7 +131,46 @@ def init_request(request):


make_form_dict(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): def make_form_dict(request):
import json import json


+ 6
- 4
frappe/automation/doctype/auto_repeat/auto_repeat.js Прегледај датотеку

@@ -54,10 +54,12 @@ frappe.ui.form.on('Auto Repeat', {


toggle_submit_on_creation: function(frm) { toggle_submit_on_creation: function(frm) {
// submit on creation checkbox // 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) { template: function(frm) {


+ 7
- 7
frappe/automation/doctype/auto_repeat/auto_repeat.json Прегледај датотеку

@@ -23,7 +23,7 @@
"repeat_on_last_day", "repeat_on_last_day",
"column_break_12", "column_break_12",
"next_schedule_date", "next_schedule_date",
"section_break_12",
"section_break_16",
"repeat_on_days", "repeat_on_days",
"notification", "notification",
"notify_by_email", "notify_by_email",
@@ -198,20 +198,20 @@
"label": "Repeat on Days", "label": "Repeat on Days",
"options": "Auto Repeat Day" "options": "Auto Repeat Day"
}, },
{
"depends_on": "eval:doc.frequency==='Weekly';",
"fieldname": "section_break_12",
"fieldtype": "Section Break"
},
{ {
"default": "0", "default": "0",
"fieldname": "submit_on_creation", "fieldname": "submit_on_creation",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Submit on Creation" "label": "Submit on Creation"
},
{
"depends_on": "eval:doc.frequency==='Weekly';",
"fieldname": "section_break_16",
"fieldtype": "Section Break"
} }
], ],
"links": [], "links": [],
"modified": "2020-12-10 10:43:13.449172",
"modified": "2021-01-12 09:24:49.719611",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Automation", "module": "Automation",
"name": "Auto Repeat", "name": "Auto Repeat",


+ 13
- 0
frappe/cache_manager.py Прегледај датотеку

@@ -68,6 +68,7 @@ def clear_defaults_cache(user=None):
frappe.cache().delete_key("defaults") frappe.cache().delete_key("defaults")


def clear_doctype_cache(doctype=None): def clear_doctype_cache(doctype=None):
clear_controller_cache(doctype)
cache = frappe.cache() cache = frappe.cache()


if getattr(frappe.local, 'meta_cache') and (doctype in frappe.local.meta_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: for name in doctype_cache_keys:
cache.delete_value(name) 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): def get_doctype_map(doctype, name, filters=None, order_by=None):
cache = frappe.cache() cache = frappe.cache()
cache_key = frappe.scrub(doctype) + '_map' cache_key = frappe.scrub(doctype) + '_map'


+ 1
- 1
frappe/core/doctype/comment/comment.py Прегледај датотеку

@@ -164,7 +164,7 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments):
try: try:
# use sql, so that we do not mess with the timestamp # 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 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: except Exception as e:
if frappe.db.is_column_missing(e) and getattr(frappe.local, 'request', None): if frappe.db.is_column_missing(e) and getattr(frappe.local, 'request', None):


+ 10
- 1
frappe/core/doctype/custom_docperm/custom_docperm.json Прегледај датотеку

@@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "hash", "autoname": "hash",
"creation": "2017-01-11 04:21:35.217943", "creation": "2017-01-11 04:21:35.217943",
@@ -13,6 +14,7 @@
"column_break_2", "column_break_2",
"permlevel", "permlevel",
"section_break_4", "section_break_4",
"select",
"read", "read",
"write", "write",
"create", "create",
@@ -211,9 +213,16 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Reference Document Type", "label": "Reference Document Type",
"read_only": 1 "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", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "Custom DocPerm", "name": "Custom DocPerm",


+ 1
- 1
frappe/core/doctype/data_import/importer.py Прегледај датотеку

@@ -751,7 +751,7 @@ class Row:
self.warnings.append( self.warnings.append(
{ {
"row": self.row_number, "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 return


+ 59
- 605
frappe/core/doctype/docperm/docperm.json Прегледај датотеку

@@ -1,775 +1,229 @@
{ {
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"actions": [],
"autoname": "hash", "autoname": "hash",
"beta": 0,
"creation": "2013-02-22 01:27:33", "creation": "2013-02-22 01:27:33",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"editable_grid": 1, "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": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "role_and_level", "fieldname": "role_and_level",
"fieldtype": "Section Break", "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", "fieldname": "role",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Role", "label": "Role",
"length": 0,
"no_copy": 0,
"oldfieldname": "role", "oldfieldname": "role",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Role", "options": "Role",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "150px", "print_width": "150px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "150px" "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", "description": "Apply this rule if the User is the Owner",
"fieldname": "if_owner", "fieldname": "if_owner",
"fieldtype": "Check", "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", "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", "default": "0",
"fieldname": "permlevel", "fieldname": "permlevel",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Level", "label": "Level",
"length": 0,
"no_copy": 0,
"oldfieldname": "permlevel", "oldfieldname": "permlevel",
"oldfieldtype": "Int", "oldfieldtype": "Int",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "40px", "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" "width": "40px"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_4", "fieldname": "section_break_4",
"fieldtype": "Section Break", "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", "default": "1",
"fieldname": "read", "fieldname": "read",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Read", "label": "Read",
"length": 0,
"no_copy": 0,
"oldfieldname": "read", "oldfieldname": "read",
"oldfieldtype": "Check", "oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "32px", "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" "width": "32px"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1", "default": "1",
"fieldname": "write", "fieldname": "write",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Write", "label": "Write",
"length": 0,
"no_copy": 0,
"oldfieldname": "write", "oldfieldname": "write",
"oldfieldtype": "Check", "oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "32px", "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" "width": "32px"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1", "default": "1",
"fieldname": "create", "fieldname": "create",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Create", "label": "Create",
"length": 0,
"no_copy": 0,
"oldfieldname": "create", "oldfieldname": "create",
"oldfieldtype": "Check", "oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "32px", "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" "width": "32px"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1", "default": "1",
"fieldname": "delete", "fieldname": "delete",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "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", "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", "fieldname": "submit",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Submit", "label": "Submit",
"length": 0,
"no_copy": 0,
"oldfieldname": "submit", "oldfieldname": "submit",
"oldfieldtype": "Check", "oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "32px", "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" "width": "32px"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "cancel", "fieldname": "cancel",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Cancel", "label": "Cancel",
"length": 0,
"no_copy": 0,
"oldfieldname": "cancel", "oldfieldname": "cancel",
"oldfieldtype": "Check", "oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "32px", "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" "width": "32px"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "amend", "fieldname": "amend",
"fieldtype": "Check", "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", "label": "Amend",
"length": 0,
"no_copy": 0,
"oldfieldname": "amend", "oldfieldname": "amend",
"oldfieldtype": "Check", "oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "32px", "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" "width": "32px"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "additional_permissions", "fieldname": "additional_permissions",
"fieldtype": "Section Break", "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", "default": "1",
"fieldname": "report", "fieldname": "report",
"fieldtype": "Check", "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", "label": "Report",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "32px", "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" "width": "32px"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1", "default": "1",
"fieldname": "export", "fieldname": "export",
"fieldtype": "Check", "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", "fieldname": "import",
"fieldtype": "Check", "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", "description": "This role update User Permissions for a user",
"fieldname": "set_user_permissions", "fieldname": "set_user_permissions",
"fieldtype": "Check", "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", "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", "default": "1",
"fieldname": "share", "fieldname": "share",
"fieldtype": "Check", "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", "default": "1",
"fieldname": "print", "fieldname": "print",
"fieldtype": "Check", "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", "default": "1",
"fieldname": "email", "fieldname": "email",
"fieldtype": "Check", "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, "idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "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", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "DocPerm", "name": "DocPerm",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "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"
} }

+ 9
- 10
frappe/core/doctype/doctype/doctype.py Прегледај датотеку

@@ -5,7 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import re, copy, os, shutil import re, copy, os, shutil
import json 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 # imports - third party imports
import six import six
@@ -395,13 +395,11 @@ class DocType(Document):
if not frappe.flags.in_patch: if not frappe.flags.in_patch:
self.rename_files_and_folders(old, new) 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): def after_delete(self):
if not self.custom: 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): def rename_files_and_folders(self, old, new):
# move files # move files
@@ -1004,10 +1002,10 @@ def validate_fields(meta):
check_sort_field(meta) check_sort_field(meta)
check_image_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.""" """Validates if permissions are set correctly."""
doctype = frappe.get_doc("DocType", doctype) doctype = frappe.get_doc("DocType", doctype)
validate_permissions(doctype, for_remove)
validate_permissions(doctype, for_remove, alert=alert)


# save permissions # save permissions
for perm in doctype.get("permissions"): for perm in doctype.get("permissions"):
@@ -1030,9 +1028,10 @@ def clear_permissions_cache(doctype):
""", doctype): """, doctype):
frappe.clear_cache(user=user) 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") 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') frappe.msgprint(_('No Permissions Specified'), alert=True, indicator='orange')
issingle = issubmittable = isimportable = False issingle = issubmittable = isimportable = False
if doctype: 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) 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): 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))) frappe.throw(_("{0}: No basic permissions set").format(get_txt(d)))


def check_double(d): def check_double(d):


+ 11
- 0
frappe/core/doctype/document_naming_rule/document_naming_rule.py Прегледај датотеку

@@ -6,8 +6,19 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils.data import evaluate_filters from frappe.utils.data import evaluate_filters
from frappe import _


class DocumentNamingRule(Document): 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): def apply(self, doc):
''' '''
Apply naming rules for the given document. Will set `name` if the rule is matched. Apply naming rules for the given document. Will set `name` if the rule is matched.


+ 2
- 2
frappe/core/doctype/server_script/server_script.json Прегледај датотеку

@@ -47,7 +47,7 @@
"fieldname": "doctype_event", "fieldname": "doctype_event",
"fieldtype": "Select", "fieldtype": "Select",
"label": "DocType Event", "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'", "depends_on": "eval:doc.script_type==='API'",
@@ -88,7 +88,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-12-03 22:42:02.708148",
"modified": "2021-01-03 18:50:14.767595",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "Server Script", "name": "Server Script",


+ 1
- 0
frappe/core/doctype/server_script/server_script_utils.py Прегледај датотеку

@@ -6,6 +6,7 @@ import frappe
EVENT_MAP = { EVENT_MAP = {
'before_insert': 'Before Insert', 'before_insert': 'Before Insert',
'after_insert': 'After Insert', 'after_insert': 'After Insert',
'before_validate': 'Before Validate',
'validate': 'Before Save', 'validate': 'Before Save',
'on_update': 'After Save', 'on_update': 'After Save',
'before_submit': 'Before Submit', 'before_submit': 'Before Submit',


+ 2
- 2
frappe/core/doctype/system_settings/system_settings.json Прегледај датотеку

@@ -358,7 +358,7 @@
"collapsible": 1, "collapsible": 1,
"fieldname": "email", "fieldname": "email",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "EMail"
"label": "Email"
}, },
{ {
"description": "Your organization name and address for the email footer.", "description": "Your organization name and address for the email footer.",
@@ -504,4 +504,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1 "track_changes": 1
}
}

+ 5
- 0
frappe/core/doctype/user/user.json Прегледај датотеку

@@ -654,6 +654,11 @@
"group": "Activity", "group": "Activity",
"link_doctype": "ToDo", "link_doctype": "ToDo",
"link_fieldname": "owner" "link_fieldname": "owner"
},
{
"group": "Integrations",
"link_doctype": "Token Cache",
"link_fieldname": "user"
} }
], ],
"max_attachments": 5, "max_attachments": 5,


+ 3
- 3
frappe/core/doctype/version/version_view.html Прегледај датотеку

@@ -21,7 +21,7 @@
<td class="danger">{{ item[1] }}</td> <td class="danger">{{ item[1] }}</td>
<td class="success">{{ item[2] }}</td> <td class="success">{{ item[2] }}</td>
</tr> </tr>
{% endif %}
{% endfor %}
</tbody> </tbody>
</table> </table>
{% endif %} {% endif %}
@@ -58,7 +58,7 @@
</table> </table>
</td> </td>
</tr> </tr>
{% endif %}
{% endfor %}
</tbody> </tbody>
</table> </table>


@@ -93,4 +93,4 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
{% endif %} {% endif %}
</div>
</div>

+ 1
- 1
frappe/core/page/permission_manager/permission_manager.js Прегледај датотеку

@@ -292,7 +292,7 @@ frappe.PermissionEngine = class PermissionEngine {
} }


get rights() { 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"] "print", "email", "report", "import", "export", "set_user_permissions", "share"]
} }




+ 13
- 1
frappe/core/page/permission_manager/permission_manager.py Прегледај датотеку

@@ -77,6 +77,18 @@ def add(parent, role, permlevel):


@frappe.whitelist() @frappe.whitelist()
def update(doctype, role, permlevel, ptype, value=None): 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") frappe.only_for("System Manager")
out = update_permission_property(doctype, role, permlevel, ptype, value) out = update_permission_property(doctype, role, permlevel, ptype, value)
return 'refresh' if out else None 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)): if not frappe.get_all('Custom DocPerm', dict(parent=doctype)):
frappe.throw(_('There must be atleast one permission rule.'), title=_('Cannot Remove')) 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() @frappe.whitelist()
def reset(doctype): def reset(doctype):


+ 0
- 1
frappe/desk/form/save.py Прегледај датотеку

@@ -42,7 +42,6 @@ def cancel(doctype=None, name=None, workflow_state_fieldname=None, workflow_stat


except Exception: except Exception:
frappe.errprint(frappe.utils.get_traceback()) frappe.errprint(frappe.utils.get_traceback())
frappe.msgprint(frappe._("Did not cancel"))
raise raise


def send_updated_docs(doc): def send_updated_docs(doc):


+ 2
- 1
frappe/desk/search.py Прегледај датотеку

@@ -150,7 +150,8 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
# 2 is the index of _relevance column # 2 is the index of _relevance column
order_by = "_relevance, {0}, `tab{1}`.idx desc".format(order_by_based_on_meta, doctype) 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: if doctype in UNTRANSLATED_DOCTYPES:
page_length = None page_length = None


+ 1
- 1
frappe/email/doctype/email_account/email_account.py Прегледај датотеку

@@ -210,7 +210,7 @@ class EmailAccount(Document):
elif not in_receive and any(map(lambda t: t in message, auth_error_codes)): elif not in_receive and any(map(lambda t: t in message, auth_error_codes)):
self.throw_invalid_credentials_exception() self.throw_invalid_credentials_exception()
else: else:
frappe.throw(e)
frappe.throw(cstr(e))


except socket.error: except socket.error:
if in_receive: if in_receive:


+ 12
- 2
frappe/event_streaming/doctype/event_producer/event_producer.py Прегледај датотеку

@@ -295,7 +295,7 @@ def set_update(update, producer_site):
if data.changed: if data.changed:
local_doc.update(data.changed) local_doc.update(data.changed)
if data.removed: if data.removed:
update_row_removed(local_doc, data.removed)
local_doc = update_row_removed(local_doc, data.removed)
if data.row_changed: if data.row_changed:
update_row_changed(local_doc, data.row_changed) update_row_changed(local_doc, data.row_changed)
if data.added: if data.added:
@@ -318,7 +318,17 @@ def update_row_removed(local_doc, removed):
for tablename, rownames in iteritems(removed): for tablename, rownames in iteritems(removed):
table = local_doc.get_table_field_doctype(tablename) table = local_doc.get_table_field_doctype(tablename)
for row in rownames: 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): def update_row_changed(local_doc, changed):


+ 96
- 0
frappe/geo/utils.py Прегледај датотеку

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

+ 1
- 1
frappe/hooks.py Прегледај датотеку

@@ -18,7 +18,7 @@ app_email = "info@frappe.io"


docs_app = "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" before_install = "frappe.utils.install.before_install"
after_install = "frappe.utils.install.after_install" after_install = "frappe.utils.install.after_install"


+ 0
- 0
frappe/integrations/doctype/connected_app/__init__.py Прегледај датотеку


+ 38
- 0
frappe/integrations/doctype/connected_app/connected_app.js Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/connected_app/connected_app.json Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/connected_app/connected_app.py Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/connected_app/test_connected_app.py Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/connected_app/test_records.json Прегледај датотеку

@@ -0,0 +1,13 @@
[
{
"doctype": "Connected App",
"provider_name": "frappe",
"client_id": "test_client_id",
"client_secret": "test_client_secret",
"scopes": [
{
"scope": "all"
}
]
}
]

+ 1
- 2
frappe/integrations/doctype/oauth_client/test_records.json Прегледај датотеку

@@ -1,7 +1,6 @@
[ [
{ {
"app_name": "_Test OAuth Client",
"client_id": "test_client_id",
"app_name": "_Test OAuth Client",
"client_secret": "test_client_secret", "client_secret": "test_client_secret",
"default_redirect_uri": "http://localhost", "default_redirect_uri": "http://localhost",
"docstatus": 0, "docstatus": 0,


+ 0
- 0
frappe/integrations/doctype/oauth_scope/__init__.py Прегледај датотеку


+ 30
- 0
frappe/integrations/doctype/oauth_scope/oauth_scope.json Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/oauth_scope/oauth_scope.py Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/query_parameters/__init__.py Прегледај датотеку


+ 37
- 0
frappe/integrations/doctype/query_parameters/query_parameters.json Прегледај датотеку

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

+ 10
- 0
frappe/integrations/doctype/query_parameters/query_parameters.py Прегледај датотеку

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

+ 14
- 0
frappe/integrations/doctype/social_login_key/test_social_login_key.py Прегледај датотеку

@@ -22,3 +22,17 @@ def make_social_login_key(**kwargs):
kwargs["provider_name"] = "Test OAuth2 Provider" kwargs["provider_name"] = "Test OAuth2 Provider"
doc = frappe.get_doc(kwargs) doc = frappe.get_doc(kwargs)
return doc 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
- 0
frappe/integrations/doctype/token_cache/__init__.py Прегледај датотеку


+ 18
- 0
frappe/integrations/doctype/token_cache/test_records.json Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/token_cache/test_token_cache.py Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/token_cache/token_cache.js Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/token_cache/token_cache.json Прегледај датотеку

@@ -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
- 0
frappe/integrations/doctype/token_cache/token_cache.py Прегледај датотеку

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

+ 1
- 1
frappe/integrations/doctype/webhook/webhook.py Прегледај датотеку

@@ -85,7 +85,7 @@ def enqueue_webhook(doc, webhook):


for i in range(3): for i in range(3):
try: 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() r.raise_for_status()
frappe.logger().debug({"webhook_success": r.text}) frappe.logger().debug({"webhook_success": r.text})
break break


+ 1
- 0
frappe/integrations/oauth2.py Прегледај датотеку

@@ -20,6 +20,7 @@ def get_oauth_server():
return frappe.local.oauth_server return frappe.local.oauth_server


def sanitize_kwargs(param_kwargs): def sanitize_kwargs(param_kwargs):
"""Remove 'data' and 'cmd' keys, if present."""
arguments = param_kwargs arguments = param_kwargs
arguments.pop('data', None) arguments.pop('data', None)
arguments.pop('cmd', None) arguments.pop('cmd', None)


+ 8
- 5
frappe/model/base_document.py Прегледај датотеку

@@ -48,7 +48,7 @@ def get_controller(doctype):
else: else:
class_overrides = frappe.get_hooks('override_doctype_class') class_overrides = frappe.get_hooks('override_doctype_class')
if class_overrides and class_overrides.get(doctype): 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_path, classname = import_path.rsplit('.', 1)
module = frappe.get_module(module_path) module = frappe.get_module(module_path)
if not hasattr(module, classname): if not hasattr(module, classname):
@@ -69,10 +69,13 @@ def get_controller(doctype):


if frappe.local.dev_server: if frappe.local.dev_server:
return _get_controller() 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): class BaseDocument(object):
ignore_in_getter = ("doctype", "_meta", "meta", "_table_fields", "_valid_columns") ignore_in_getter = ("doctype", "_meta", "meta", "_table_fields", "_valid_columns")




+ 9
- 3
frappe/model/db_query.py Прегледај датотеку

@@ -40,7 +40,10 @@ class DatabaseQuery(object):
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False, ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
update=None, add_total_row=None, user_settings=None, reference_doctype=None, update=None, add_total_row=None, user_settings=None, reference_doctype=None,
return_query=False, strict=True, pluck=None, ignore_ddl=False): 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)) frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
raise frappe.PermissionError(self.doctype) raise frappe.PermissionError(self.doctype)


@@ -315,7 +318,10 @@ class DatabaseQuery(object):
def append_table(self, table_name): def append_table(self, table_name):
self.tables.append(table_name) self.tables.append(table_name)
doctype = table_name[4:-1] 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)) frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(doctype))
raise frappe.PermissionError(doctype) raise frappe.PermissionError(doctype)


@@ -576,7 +582,7 @@ class DatabaseQuery(object):
self.shared = frappe.share.get_shared(self.doctype, self.user) self.shared = frappe.share.get_shared(self.doctype, self.user)


if (not meta.istable and 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 self.flags.ignore_permissions and
not has_any_user_permission_for_doctype(self.doctype, self.user, self.reference_doctype)): not has_any_user_permission_for_doctype(self.doctype, self.user, self.reference_doctype)):
only_if_shared = True only_if_shared = True


+ 4
- 2
frappe/model/document.py Прегледај датотеку

@@ -939,15 +939,17 @@ class Document(BaseDocument):
self.load_doc_before_save() self.load_doc_before_save()
self.reset_seen() 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: if self.flags.ignore_validate:
return return


if self._action=="save": if self._action=="save":
self.run_method("before_validate")
self.run_method("validate") self.run_method("validate")
self.run_method("before_save") self.run_method("before_save")
elif self._action=="submit": elif self._action=="submit":
self.run_method("before_validate")
self.run_method("validate") self.run_method("validate")
self.run_method("before_submit") self.run_method("before_submit")
elif self._action=="cancel": elif self._action=="cancel":


+ 3
- 1
frappe/model/meta.py Прегледај датотеку

@@ -68,7 +68,7 @@ def load_doctype_from_file(doctype):
class Meta(Document): class Meta(Document):
_metaclass = True _metaclass = True
default_fields = list(default_fields)[1:] 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): def __init__(self, doctype):
self._fields = {} self._fields = {}
@@ -484,6 +484,8 @@ class Meta(Document):
if not data.transactions: if not data.transactions:
# init groups # init groups
data.transactions = [] data.transactions = []

if not data.non_standard_fieldnames:
data.non_standard_fieldnames = {} data.non_standard_fieldnames = {}


for link in dashboard_links: for link in dashboard_links:


+ 10
- 2
frappe/model/rename_doc.py Прегледај датотеку

@@ -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) docname = rename_doc(doctype=doctype, old=docname, new=new_name, merge=merge)


if old_title and new_title and not old_title == new_title: 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 return docname




+ 2
- 3
frappe/model/workflow.py Прегледај датотеку

@@ -120,9 +120,8 @@ def apply_workflow(doc, action):
return doc return doc


@frappe.whitelist() @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: for state_doc in workflow.states:
if state_doc.doc_status == '2': if state_doc.doc_status == '2':
for transition in workflow.transitions: for transition in workflow.transitions:


+ 1
- 0
frappe/patches.txt Прегледај датотеку

@@ -326,3 +326,4 @@ execute:frappe.delete_doc_if_exists('Page', 'dashboard', force=1)
frappe.core.doctype.page.patches.drop_unused_pages frappe.core.doctype.page.patches.drop_unused_pages
execute:frappe.get_doc('Role', 'Guest').save() # remove desk access execute:frappe.get_doc('Role', 'Guest').save() # remove desk access
frappe.patches.v13_0.rename_desk_page_to_workspace frappe.patches.v13_0.rename_desk_page_to_workspace
frappe.patches.v13_0.delete_package_publish_tool

+ 11
- 0
frappe/patches/v13_0/delete_package_publish_tool.py Прегледај датотеку

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

+ 14
- 0
frappe/patches/v13_0/website_theme_custom_scss.py Прегледај датотеку

@@ -2,9 +2,23 @@ import frappe


def execute(): def execute():
frappe.reload_doctype('Website Theme') 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'): for theme in frappe.get_all('Website Theme'):
doc = frappe.get_doc('Website Theme', theme.name) doc = frappe.get_doc('Website Theme', theme.name)
if not doc.get('custom_scss') and doc.theme_scss: if not doc.get('custom_scss') and doc.theme_scss:
# move old theme to new theme # move old theme to new theme
doc.custom_scss = doc.theme_scss doc.custom_scss = doc.theme_scss

if doc.background_color:
setup_color_record(doc.background_color)

doc.save() doc.save()

def setup_color_record(color):
frappe.get_doc({
"doctype": "Color",
"__newname": color,
"color": color,
}).save()

+ 4
- 3
frappe/permissions.py Прегледај датотеку

@@ -7,7 +7,7 @@ import frappe, copy, json
from frappe import _, msgprint from frappe import _, msgprint
from frappe.utils import cint from frappe.utils import cint
import frappe.share 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") "print", "email", "report", "import", "export", "set_user_permissions", "share")


# TODO: # 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) role_permissions = get_role_permissions(meta, user=user)
perm = role_permissions.get(ptype) perm = role_permissions.get(ptype)

if not perm: 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))) 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'): and ptype != 'create'):
perms['if_owner'][ptype] = 1 perms['if_owner'][ptype] = 1
# has no access if not owner # 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) # (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 frappe.local.role_permissions[cache_key] = perms




+ 1
- 0
frappe/public/build.json Прегледај датотеку

@@ -261,6 +261,7 @@
"public/js/frappe/views/calendar/calendar.js", "public/js/frappe/views/calendar/calendar.js",
"public/js/frappe/views/dashboard/dashboard_view.js", "public/js/frappe/views/dashboard/dashboard_view.js",
"public/js/frappe/views/image/image_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/kanban/kanban_view.js",
"public/js/frappe/views/inbox/inbox_view.js", "public/js/frappe/views/inbox/inbox_view.js",
"public/js/frappe/views/file/file_view.js", "public/js/frappe/views/file/file_view.js",


+ 7
- 0
frappe/public/css/list.css Прегледај датотеку

@@ -401,6 +401,13 @@ input.list-row-checkbox {
.pswp__more-item img { .pswp__more-item img {
max-height: 100%; 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 { .list-paging-area .gantt-view-mode {
margin-left: 15px; margin-left: 15px;
margin-right: 15px; margin-right: 15px;


+ 22
- 5
frappe/public/js/frappe/form/controls/base_control.js Прегледај датотеку

@@ -40,23 +40,31 @@ frappe.ui.form.Control = Class.extend({
return this.df.get_status(this); 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 // like in case of a dialog box
if (cint(this.df.hidden)) { if (cint(this.df.hidden)) {
// eslint-disable-next-line // 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"; return "None";


} else if (cint(this.df.hidden_due_to_dependency)) { } else if (cint(this.df.hidden_due_to_dependency)) {
// eslint-disable-next-line // 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"; return "None";


} else if (cint(this.df.read_only)) { } else if (cint(this.df.read_only)) {
// eslint-disable-next-line // 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"; 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"; return "Write";
@@ -65,13 +73,22 @@ frappe.ui.form.Control = Class.extend({
var status = frappe.perm.get_field_display_status(this.df, 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); 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 // hide if no value
if (this.doctype && status==="Read" && !this.only_input if (this.doctype && status==="Read" && !this.only_input
&& is_null(frappe.model.get_value(this.doctype, this.docname, this.df.fieldname)) && is_null(frappe.model.get_value(this.doctype, this.docname, this.df.fieldname))
&& !in_list(["HTML", "Image", "Button"], this.df.fieldtype)) { && !in_list(["HTML", "Image", "Button"], this.df.fieldtype)) {


// eslint-disable-next-line // 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"; status = "None";
} }




+ 1
- 1
frappe/public/js/frappe/form/controls/base_input.js Прегледај датотеку

@@ -20,7 +20,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({
<div class="control-input-wrapper">\ <div class="control-input-wrapper">\
<div class="control-input"></div>\ <div class="control-input"></div>\
<div class="control-value like-disabled-input" style="display: none;"></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>\ </div>\
</div>').appendTo(this.parent); </div>').appendTo(this.parent);


+ 6
- 4
frappe/public/js/frappe/form/controls/geolocation.js Прегледај датотеку

@@ -1,3 +1,5 @@
frappe.provide('frappe.utils.utils');

frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({
horizontal: false, horizontal: false,


@@ -90,11 +92,11 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({
}); });


L.Icon.Default.imagePath = '/assets/frappe/images/leaflet/'; 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: '&copy; <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() { bind_leaflet_locate_control() {


+ 11
- 0
frappe/public/js/frappe/form/controls/link.js Прегледај датотеку

@@ -51,6 +51,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
this.translate_values = true; this.translate_values = true;
this.setup_buttons(); this.setup_buttons();
this.setup_awesomeplete(); this.setup_awesomeplete();
this.bind_change_event();
}, },
get_options: function() { get_options: function() {
return this.df.options; 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.$input.cache[doctype][term] = r.results;
me.awesomplete.list = me.$input.cache[doctype][term]; me.awesomplete.list = me.$input.cache[doctype][term];
me.toggle_href(doctype);
} }
}); });
}, 500)); }, 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'}] // 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) { get_filter_description(filters) {
let doctype = this.get_options(); let doctype = this.get_options();
let filter_array = []; let filter_array = [];


+ 1
- 1
frappe/public/js/frappe/form/controls/rating.js Прегледај датотеку

@@ -47,7 +47,7 @@ frappe.ui.form.ControlRating = frappe.ui.form.ControlInt.extend({
}); });
}, },
get_value() { get_value() {
return cint(this.value);
return cint(this.value, null);
}, },
set_formatted_input(value) { set_formatted_input(value) {
let el = $(this.input_area).find('i'); let el = $(this.input_area).find('i');


+ 2
- 1
frappe/public/js/frappe/form/controls/table.js Прегледај датотеку

@@ -9,7 +9,8 @@ frappe.ui.form.ControlTable = frappe.ui.form.Control.extend({
frm: this.frm, frm: this.frm,
df: this.df, df: this.df,
perm: this.perm || (this.frm && this.frm.perm) || this.df.perm, perm: this.perm || (this.frm && this.frm.perm) || this.df.perm,
parent: this.wrapper
parent: this.wrapper,
control: this
}); });
if(this.frm) { if(this.frm) {
this.frm.grids[this.frm.grids.length] = this; this.frm.grids[this.frm.grids.length] = this;


+ 4
- 1
frappe/public/js/frappe/form/form.js Прегледај датотеку

@@ -1264,7 +1264,10 @@ frappe.ui.form.Form = class FrappeForm {
} }
if (df && df[property] != value) { if (df && df[property] != value) {
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);
}
} }
} }




+ 9
- 5
frappe/public/js/frappe/form/formatters.js Прегледај датотеку

@@ -128,11 +128,15 @@ frappe.form.formatters = {
return repl('<a onclick="%(onclick)s">%(value)s</a>', return repl('<a onclick="%(onclick)s">%(value)s</a>',
{onclick: docfield.link_onclick.replace(/"/g, '&quot;'), value:value}); {onclick: docfield.link_onclick.replace(/"/g, '&quot;'), value:value});
} else if(docfield && doctype) { } 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 { } else {
return value; return value;
} }


+ 2
- 0
frappe/public/js/frappe/form/grid.js Прегледај датотеку

@@ -280,6 +280,8 @@ export default class Grid {
if (this.frm) { if (this.frm) {
this.display_status = frappe.perm.get_field_display_status(this.df, this.frm.doc, this.display_status = frappe.perm.get_field_display_status(this.df, this.frm.doc,
this.perm); this.perm);
} else if (this.df.is_web_form && this.control) {
this.display_status = this.control.get_status();
} else { } else {
// not in form // not in form
this.display_status = 'Write'; this.display_status = 'Write';


+ 3
- 0
frappe/public/js/frappe/form/grid_row_form.js Прегледај датотеку

@@ -16,6 +16,9 @@ export default class GridRowForm {
body: this.form_area, body: this.form_area,
no_submit_on_enter: true, no_submit_on_enter: true,
frm: this.row.frm, frm: this.row.frm,
grid: this.row.grid,
grid_row: this.row,
grid_row_form: this,
}); });
this.layout.make(); this.layout.make();




+ 61
- 58
frappe/public/js/frappe/form/layout.js Прегледај датотеку

@@ -1,7 +1,7 @@
import '../class'; import '../class';


frappe.ui.form.Layout = Class.extend({ frappe.ui.form.Layout = Class.extend({
init: function(opts) {
init: function (opts) {
this.views = {}; this.views = {};
this.pages = []; this.pages = [];
this.sections = []; this.sections = [];
@@ -87,7 +87,7 @@ frappe.ui.form.Layout = Class.extend({
this.message.empty().addClass('hidden'); this.message.empty().addClass('hidden');
} }
}, },
render: function(new_fields) {
render: function (new_fields) {
var me = this; var me = this;
var fields = new_fields || this.fields; var fields = new_fields || this.fields;


@@ -101,8 +101,8 @@ frappe.ui.form.Layout = Class.extend({
if (this.no_opening_section()) { if (this.no_opening_section()) {
this.make_section(); this.make_section();
} }
$.each(fields, function(i, df) {
switch(df.fieldtype) {
$.each(fields, function (i, df) {
switch (df.fieldtype) {
case "Fold": case "Fold":
me.make_page(df); me.make_page(df);
break; 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()) { if (this.no_opening_section()) {
this.fields.unshift({fieldtype: 'Section Break'}); 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 df.fieldname = fieldname; // change of fieldname is avoided
if (this.fields_dict[fieldname] && this.fields_dict[fieldname].df) { if (this.fields_dict[fieldname] && this.fields_dict[fieldname].df) {
const fieldobj = this.init_field(df, render); 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.section && this.make_section();
!this.column && this.make_column(); !this.column && this.make_column();


@@ -161,29 +161,30 @@ frappe.ui.form.Layout = Class.extend({
fieldobj.section = this.section; fieldobj.section = this.section;
}, },


init_field: function(df, render = false) {
init_field: function (df, render = false) {
const fieldobj = frappe.ui.form.make_control({ const fieldobj = frappe.ui.form.make_control({
df: df, df: df,
doctype: this.doctype, doctype: this.doctype,
parent: this.column.wrapper.get(0), parent: this.column.wrapper.get(0),
frm: this.frm, frm: this.frm,
render_input: render, render_input: render,
doc: this.doc
doc: this.doc,
layout: this
}); });


fieldobj.layout = this; fieldobj.layout = this;
return fieldobj; return fieldobj;
}, },


make_page: function(df) {
make_page: function (df) { // eslint-disable-line no-unused-vars
var me = this, var me = this,
head = $('<div class="form-clickable-section text-center">\ 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); </div>').appendTo(this.wrapper);


this.page = $('<div class="form-page second-page hide"></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(); var page = $(this).parent().next();
if (page.hasClass("hide")) { if (page.hasClass("hide")) {
$(this).removeClass("btn-fold").html(__("Hide details")); $(this).removeClass("btn-fold").html(__("Hide details"));
@@ -201,11 +202,11 @@ frappe.ui.form.Layout = Class.extend({
this.folded = true; this.folded = true;
}, },


unfold: function() {
unfold: function () {
this.fold_btn.trigger('click'); this.fold_btn.trigger('click');
}, },


make_section: function(df) {
make_section: function (df) {
this.section = new frappe.ui.form.Section(this, df); this.section = new frappe.ui.form.Section(this, df);


// append to layout fields // append to layout fields
@@ -217,14 +218,14 @@ frappe.ui.form.Layout = Class.extend({
this.column = null; this.column = null;
}, },


make_column: function(df) {
make_column: function (df) {
this.column = new frappe.ui.form.Column(this.section, df); this.column = new frappe.ui.form.Column(this.section, df);
if (df && df.fieldname) { if (df && df.fieldname) {
this.fields_list.push(this.column); this.fields_list.push(this.column);
} }
}, },


refresh: function(doc) {
refresh: function (doc) {
var me = this; var me = this;
if (doc) this.doc = doc; 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) => { let fieldnames = fields.map((field) => {
if (field.fieldname) return field.fieldname; 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.render(fields);
this.refresh_fields(fields); this.refresh_fields(fields);
}, },


refresh_section_collapse: function() {
refresh_section_collapse: function () {
if (!this.doc) return; 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 section = this.sections[i];
var df = section.df; var df = section.df;
if (df && df.collapsible) { 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; 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]; var fieldobj = this.fields_list[i];
if (me.doc) { if (me.doc) {
fieldobj.doc = 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; 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), var current = $(ev.target),
doctype = current.attr("data-doctype"), doctype = current.attr("data-doctype"),
fieldname = current.attr("data-fieldname"); 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, var me = this,
grid_row = null, grid_row = null,
prev = null, prev = null,
@@ -363,8 +364,8 @@ frappe.ui.form.Layout = Class.extend({
fields = grid_row.layout.fields_list; 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 (shift) {
if (prev) { if (prev) {
this.set_focus(prev); this.set_focus(prev);
@@ -373,7 +374,7 @@ frappe.ui.form.Layout = Class.extend({
} }
break; break;
} }
if (i < len-1) {
if (i < len - 1) {
focused = me.focus_on_next_field(i, fields); focused = me.focus_on_next_field(i, fields);
} }


@@ -389,9 +390,9 @@ frappe.ui.form.Layout = Class.extend({
// last field in this group // last field in this group
if (grid_row) { if (grid_row) {
// in grid // 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 // 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); grid_row.grid.frm.layout.handle_tab(grid_row.grid.df.parent, grid_row.grid.df.fieldname);
}); });
} else { } else {
@@ -405,12 +406,12 @@ frappe.ui.form.Layout = Class.extend({


return false; return false;
}, },
focus_on_next_field: function(start_idx, fields) {
focus_on_next_field: function (start_idx, fields) {
// loop to find next eligible 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]; var field = fields[i];
if (this.is_visible(field)) { if (this.is_visible(field)) {
if (field.df.fieldtype==="Table") {
if (field.df.fieldtype === "Table") {
// open table grid // open table grid
if (!(field.grid.grid_rows && field.grid.grid_rows.length)) { if (!(field.grid.grid_rows && field.grid.grid_rows.length)) {
// empty grid, add a new row // 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 // next is table, show the table
if (field.df.fieldtype=="Table") { if (field.df.fieldtype=="Table") {
if (!field.grid.grid_rows.length) { if (!field.grid.grid_rows.length) {
@@ -444,10 +445,10 @@ frappe.ui.form.Layout = Class.extend({
field.$input.focus(); field.$input.focus();
} }
}, },
get_open_grid_row: function() {
get_open_grid_row: function () {
return $(".grid-row-open").data("grid_row"); return $(".grid-row-open").data("grid_row");
}, },
refresh_dependency: function() {
refresh_dependency: function () {
// Resolve "depends_on" and show / hide accordingly // Resolve "depends_on" and show / hide accordingly
var me = this; var me = this;


@@ -465,7 +466,7 @@ frappe.ui.form.Layout = Class.extend({
if (!has_dep) return; if (!has_dep) return;


// show / hide based on values // 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]; var f = me.fields_list[i];
f.guardian_has_value = true; f.guardian_has_value = true;
if (f.df.depends_on) { if (f.df.depends_on) {
@@ -498,14 +499,14 @@ frappe.ui.form.Layout = Class.extend({


this.refresh_section_count(); 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 set_property = this.evaluate_depends_on_value(condition);
let value = set_property ? 1 : 0; let value = set_property ? 1 : 0;
let form_obj; let form_obj;


if (this.frm) { if (this.frm) {
form_obj = this.frm; form_obj = this.frm;
} else if (this.is_dialog) {
} else if (this.is_dialog || this.doctype === 'Web Form') {
form_obj = this; form_obj = this;
} }
if (form_obj) { if (form_obj) {
@@ -513,12 +514,14 @@ frappe.ui.form.Layout = Class.extend({
form_obj.setting_dependency = true; form_obj.setting_dependency = true;
form_obj.set_df_property(this.doc.parentfield, property, value, this.doc.parent, fieldname); form_obj.set_df_property(this.doc.parentfield, property, value, this.doc.parent, fieldname);
form_obj.setting_dependency = false; form_obj.setting_dependency = false;
// refresh child fields
this.fields_dict[fieldname] && this.fields_dict[fieldname].refresh();
} else { } else {
form_obj.set_df_property(fieldname, property, value); form_obj.set_df_property(fieldname, property, value);
} }
} }
}, },
evaluate_depends_on_value: function(expression) {
evaluate_depends_on_value: function (expression) {
var out = null; var out = null;
var doc = this.doc; var doc = this.doc;


@@ -544,7 +547,7 @@ frappe.ui.form.Layout = Class.extend({
if (parent && parent.istable && expression.includes('is_submittable')) { if (parent && parent.istable && expression.includes('is_submittable')) {
out = true; out = true;
} }
} catch(e) {
} catch (e) {
frappe.throw(__('Invalid "depends_on" expression')); frappe.throw(__('Invalid "depends_on" expression'));
} }


@@ -640,7 +643,7 @@ frappe.ui.form.Section = Class.extend({


this.wrapper.toggleClass("hide-control", !!hide); this.wrapper.toggleClass("hide-control", !!hide);
}, },
collapse: function(hide) {
collapse: function (hide) {
// unknown edge case // unknown edge case
if (!(this.head && this.body)) { if (!(this.head && this.body)) {
return; return;
@@ -659,7 +662,7 @@ frappe.ui.form.Section = Class.extend({


// refresh signature fields // refresh signature fields
this.fields_list.forEach((f) => { this.fields_list.forEach((f) => {
if (f.df.fieldtype=='Signature') {
if (f.df.fieldtype == 'Signature') {
f.refresh(); f.refresh();
} }
}); });
@@ -669,11 +672,11 @@ frappe.ui.form.Section = Class.extend({
return this.body.hasClass('hide'); return this.body.hasClass('hide');
}, },


has_missing_mandatory: function() {
has_missing_mandatory: function () {
var missing_mandatory = false; 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; 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; missing_mandatory = true;
break; break;
} }
@@ -691,13 +694,13 @@ frappe.ui.form.Column = Class.extend({
this.make(); this.make();
this.resize_all_columns(); this.resize_all_columns();
}, },
make: function() {
make: function () {
this.wrapper = $('<div class="form-column">\ this.wrapper = $('<div class="form-column">\
<form>\ <form>\
</form>\ </form>\
</div>').appendTo(this.section.body) </div>').appendTo(this.section.body)
.find("form") .find("form")
.on("submit", function() {
.on("submit", function () {
return false; return false;
}); });


@@ -706,7 +709,7 @@ frappe.ui.form.Column = Class.extend({
+ '</label>').appendTo(this.wrapper); + '</label>').appendTo(this.wrapper);
} }
}, },
resize_all_columns: function() {
resize_all_columns: function () {
// distribute all columns equally // distribute all columns equally
var colspan = cint(12 / this.section.wrapper.find(".form-column").length); 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); .addClass("col-sm-" + colspan);


}, },
refresh: function() {
refresh: function () {
this.section.refresh(); this.section.refresh();
} }
}); });

+ 17
- 4
frappe/public/js/frappe/form/toolbar.js Прегледај датотеку

@@ -226,7 +226,7 @@ frappe.ui.form.Toolbar = class Toolbar {
this.page.add_action_icon("right", ()=> { this.page.add_action_icon("right", ()=> {
this.frm.navigate_records(0); this.frm.navigate_records(0);
}, 'next-doc', __("Next")); }, 'next-doc', __("Next"));
}
}
} }


make_menu_items() { make_menu_items() {
@@ -470,9 +470,22 @@ frappe.ui.form.Toolbar = class Toolbar {
me.frm.page.set_view('main'); me.frm.page.set_view('main');
}, 'edit'); }, 'edit');
} else if(status === "Cancel") { } 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 { } else {
var click = { var click = {
"Save": function() { "Save": function() {


+ 2
- 11
frappe/public/js/frappe/form/workflow.js Прегледај датотеку

@@ -85,7 +85,7 @@ frappe.ui.form.States = Class.extend({
frappe.workflow.get_transitions(this.frm.doc).then(transitions => { frappe.workflow.get_transitions(this.frm.doc).then(transitions => {
this.frm.page.clear_actions_menu(); this.frm.page.clear_actions_menu();
transitions.forEach(d => { 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; added = true;
me.frm.page.add_action_item(__(d.action), function() { me.frm.page.add_action_item(__(d.action), function() {
// set the workflow_action for use in form scripts // 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);
}); });


}, },


+ 122
- 7
frappe/public/js/frappe/list/list_sidebar.js Прегледај датотеку

@@ -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() { 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() { get_stats() {
var me = this; var me = this;
frappe.call({ frappe.call({


+ 6
- 2
frappe/public/js/frappe/model/model.js Прегледај датотеку

@@ -135,8 +135,8 @@ $.extend(frappe.model, {
let cached_timestamp = null; let cached_timestamp = null;
let cached_doc = 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) { if (cached_docs) {
cached_doc = cached_docs.filter(doc => doc.name === doctype)[0]; cached_doc = cached_docs.filter(doc => doc.name === doctype)[0];
if(cached_doc) { if(cached_doc) {
@@ -252,6 +252,10 @@ $.extend(frappe.model, {
return frappe.boot.user.can_create.indexOf(doctype)!==-1; 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) { can_read: function(doctype) {
return frappe.boot.user.can_read.indexOf(doctype)!==-1; return frappe.boot.user.can_read.indexOf(doctype)!==-1;
}, },


+ 1
- 2
frappe/public/js/frappe/ui/filters/filter.js Прегледај датотеку

@@ -527,7 +527,7 @@ frappe.ui.filter_utils = {
['Date', 'Datetime', 'DateRange', 'Select'].includes(df.fieldtype) ['Date', 'Datetime', 'DateRange', 'Select'].includes(df.fieldtype)
) { ) {
df.fieldtype = 'Select'; 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') { if (condition === 'is') {
df.fieldtype = 'Select'; df.fieldtype = 'Select';
@@ -542,7 +542,6 @@ frappe.ui.filter_utils = {
get_timespan_options(periods) { get_timespan_options(periods) {
const period_map = { const period_map = {
Last: ['Week', 'Month', 'Quarter', '6 months', 'Year'], Last: ['Week', 'Month', 'Quarter', '6 months', 'Year'],
Today: null,
This: ['Week', 'Month', 'Quarter', 'Year'], This: ['Week', 'Month', 'Quarter', 'Year'],
Next: ['Week', 'Month', 'Quarter', '6 months', 'Year'], Next: ['Week', 'Month', 'Quarter', '6 months', 'Year'],
}; };


+ 1
- 1
frappe/public/js/frappe/utils/common.js Прегледај датотеку

@@ -176,7 +176,7 @@ window.replace_all = function (s, t1, t2) {
return s.split(t1).join(t2); return s.split(t1).join(t2);
} }


window.strip_html = function (txt) {
window.strip_html = function(txt) {
return cstr(txt).replace(/<[^>]*>/g, ""); return cstr(txt).replace(/<[^>]*>/g, "");
} }




+ 2
- 0
frappe/public/js/frappe/utils/utils.js Прегледај датотеку

@@ -1220,6 +1220,7 @@ Object.assign(frappe.utils, {
if (Math.floor(number) === number) return 0; if (Math.floor(number) === number) return 0;
return number.toString().split(".")[1].length || 0; return number.toString().split(".")[1].length || 0;
}, },

build_summary_item(summary) { build_summary_item(summary) {
if (summary.type == "separator") { if (summary.type == "separator") {
return $(`<div class="summary-separator"> return $(`<div class="summary-separator">
@@ -1242,6 +1243,7 @@ Object.assign(frappe.utils, {
<div class="summary-value ${color}">${value}</div> <div class="summary-value ${color}">${value}</div>
</div>`); </div>`);
}, },

get_names_for_mentions() { get_names_for_mentions() {
let names_for_mentions = Object.keys(frappe.boot.user_info || []) let names_for_mentions = Object.keys(frappe.boot.user_info || [])
.filter(user => { .filter(user => {


+ 1
- 2
frappe/public/js/frappe/views/communication.js Прегледај датотеку

@@ -112,7 +112,6 @@ frappe.views.CommunicationComposer = Class.extend({
{ {
label: __("Message"), label: __("Message"),
fieldtype: "Text Editor", fieldtype: "Text Editor",
reqd: 1,
fieldname: "content", fieldname: "content",
onchange: frappe.utils.debounce( onchange: frappe.utils.debounce(
this.save_as_draft.bind(this), this.save_as_draft.bind(this),
@@ -124,7 +123,7 @@ frappe.views.CommunicationComposer = Class.extend({
label: __("Send me a copy"), label: __("Send me a copy"),
fieldtype: "Check", fieldtype: "Check",
fieldname: "send_me_a_copy", fieldname: "send_me_a_copy",
default: 1
default: 1 // frappe.boot.user.send_me_a_copy
}, },
{ {
label: __("Send Read Receipt"), label: __("Send Read Receipt"),


+ 85
- 0
frappe/public/js/frappe/views/map/map_view.js Прегледај датотеку

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


};

+ 27
- 0
frappe/public/js/frappe/views/reports/report_view.js Прегледај датотеку

@@ -708,6 +708,32 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
super.build_fields(); 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() { get_fields() {
let fields = this.fields.map(f => { let fields = this.fields.map(f => {
let column_name = frappe.model.get_full_column_name(f[0], f[1]); 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.fields.map(f => this.add_currency_column(f[0], f[1]));


this.reorder_fields();
this.build_fields(); this.build_fields();
this.setup_columns(); this.setup_columns();




+ 7
- 7
frappe/public/js/frappe/views/treeview.js Прегледај датотеку

@@ -94,17 +94,17 @@ frappe.views.TreeView = Class.extend({
var me = this; var me = this;
this.opts.onload && this.opts.onload(me); this.opts.onload && this.opts.onload(me);
}, },
make_filters: function(){
make_filters: function() {
var me = this; var me = this;
frappe.treeview_settings.filters = [] frappe.treeview_settings.filters = []
$.each(this.opts.filters || [], function(i, filter) { $.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.change = function() {
filter.on_change && filter.on_change();
filter.onchange && filter.onchange();
var val = this.get_value(); var val = this.get_value();
me.args[filter.fieldname] = val; me.args[filter.fieldname] = val;
if (val) { if (val) {
@@ -114,7 +114,7 @@ frappe.views.TreeView = Class.extend({
} }
me.set_title(); me.set_title();
me.make_tree(); me.make_tree();
}
};
} }


me.page.add_field(filter); me.page.add_field(filter);
@@ -122,7 +122,7 @@ frappe.views.TreeView = Class.extend({
if (filter.default) { if (filter.default) {
$("[data-fieldname='"+filter.fieldname+"']").trigger("change"); $("[data-fieldname='"+filter.fieldname+"']").trigger("change");
} }
})
});
}, },
get_root: function() { get_root: function() {
var me = this; var me = this;


+ 2
- 2
frappe/public/js/frappe/web_form/webform_script.js Прегледај датотеку

@@ -85,6 +85,7 @@ frappe.ready(function() {


function setup_fields(form_data) { function setup_fields(form_data) {
form_data.web_form.web_form_fields.map(df => { form_data.web_form.web_form_fields.map(df => {
df.is_web_form = true;
if (df.fieldtype === "Table") { if (df.fieldtype === "Table") {
df.get_data = () => { df.get_data = () => {
let data = []; let data = [];
@@ -99,14 +100,13 @@ frappe.ready(function() {
if (field.fieldtype === "Link") { if (field.fieldtype === "Link") {
field.only_select = true; field.only_select = true;
} }
field.is_web_form = true;
}); });


if (df.fieldtype === "Attach") { if (df.fieldtype === "Attach") {
df.is_private = true; df.is_private = true;
} }


df.is_web_form = true;

delete df.parent; delete df.parent;
delete df.parentfield; delete df.parentfield;
delete df.parenttype; delete df.parenttype;


+ 2
- 2
frappe/public/scss/website/page_builder.scss Прегледај датотеку

@@ -29,11 +29,11 @@
} }


.hero.align-center { .hero.align-center {
h1, .hero-subtitle, .hero-buttons {
h1, .hero-title, .hero-subtitle, .hero-buttons {
text-align: center; text-align: center;
} }


.hero-subtitle {
.hero-title, .hero-subtitle {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }


+ 6
- 2
frappe/templates/includes/breadcrumbs.html Прегледај датотеку

@@ -3,6 +3,7 @@
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList"> <ol class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList">
{%- set parents = parents[-3:] %} {%- set parents = parents[-3:] %}
{% set count = (parents | length) + 1 %}
{% for parent in parents %} {% for parent in parents %}
<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem" class="breadcrumb-item"> <li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem" class="breadcrumb-item">
<a itemprop="item" href="{{ url_prefix }}{{ parent.route | abs_url }}" itemprop="url"> <a itemprop="item" href="{{ url_prefix }}{{ parent.route | abs_url }}" itemprop="url">
@@ -11,8 +12,11 @@
</a> </a>
</li> </li>
{% endfor %} {% 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> </li>
</ol> </ol>
</nav> </nav>


+ 7
- 5
frappe/templates/print_formats/standard_macros.html Прегледај датотеку

@@ -5,8 +5,11 @@
<div>{{ frappe.render_template(df.options, {"doc": doc}) or "" }}</div> <div>{{ frappe.render_template(df.options, {"doc": doc}) or "" }}</div>
{%- elif df.fieldtype in ("Text", "Text Editor", "Code", "Long Text") -%} {%- elif df.fieldtype in ("Text", "Text Editor", "Code", "Long Text") -%}
{{ render_text_field(df, doc) }} {{ 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) }} {{ render_image(df, doc) }}
{%- elif df.fieldtype=="Geolocation" -%} {%- elif df.fieldtype=="Geolocation" -%}
{{ render_geolocation(df, doc) }} {{ render_geolocation(df, doc) }}
@@ -137,15 +140,14 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
style="width: 12px; height: 12px; margin-top: 5px;"> 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"/> <path d="M2 9.66667L5.33333 13L14 3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
{% elif df.fieldtype=="Image" %}
{% elif df.fieldtype in ("Image", "Attach Image") %}
<img src="{{ doc[doc.meta.get_field(df.fieldname).options] }}" <img src="{{ doc[doc.meta.get_field(df.fieldname).options] }}"
class="img-responsive" class="img-responsive"
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}> {%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
{% elif df.fieldtype=="Signature" %} {% elif df.fieldtype=="Signature" %}
<img src="{{ doc[df.fieldname] }}" class="signature-img img-responsive" <img src="{{ doc[df.fieldname] }}" class="signature-img img-responsive"
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}> {%- 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" <img src="{{ doc[df.fieldname] }}" class="img-responsive"
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}> {%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
{% elif df.fieldtype=="HTML" %} {% elif df.fieldtype=="HTML" %}


+ 57
- 0
frappe/tests/test_cors.py Прегледај датотеку

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

+ 11
- 11
frappe/tests/test_hooks.py Прегледај датотеку

@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from frappe.desk.doctype.todo.todo import ToDo from frappe.desk.doctype.todo.todo import ToDo
from frappe.cache_manager import clear_controller_cache


class TestHooks(unittest.TestCase): class TestHooks(unittest.TestCase):
def test_hooks(self): def test_hooks(self):
@@ -17,21 +18,20 @@ class TestHooks(unittest.TestCase):
hooks.get("doc_events").get("*").get("on_update")) hooks.get("doc_events").get("*").get("on_update"))


def test_override_doctype_class(self): 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') todo = frappe.get_doc(doctype='ToDo', description='asdf')
self.assertTrue(isinstance(todo, CustomToDo)) self.assertTrue(isinstance(todo, CustomToDo))


# restore
frappe.get_hooks = original


class CustomToDo(ToDo): class CustomToDo(ToDo):
pass pass

+ 19
- 1
frappe/tests/test_permissions.py Прегледај датотеку

@@ -9,7 +9,7 @@ import frappe.defaults
import unittest import unittest
import frappe.model.meta import frappe.model.meta
from frappe.permissions import (add_user_permission, remove_user_permission, 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.core.page.permission_manager.permission_manager import update, reset
from frappe.test_runner import make_test_records_for_doctype from frappe.test_runner import make_test_records_for_doctype
from frappe.core.doctype.user_permission.user_permission import clear_user_permissions 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") post = frappe.get_doc("Blog Post", "-test-blog-post")
self.assertTrue(post.has_permission("read")) 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): def test_user_permissions_in_doc(self):
add_user_permission("Blog Category", "-test-blog-category-1", add_user_permission("Blog Category", "-test-blog-category-1",
"test2@example.com") "test2@example.com")


+ 42
- 0
frappe/tests/tests_geo_utils.py Прегледај датотеку

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

+ 18
- 0
frappe/tests/ui_test_helpers.py Прегледај датотеку

@@ -95,6 +95,24 @@ def create_doctype(name, fields):
"name": name "name": name
}).insert() }).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() @frappe.whitelist()
def create_contact_records(): def create_contact_records():
if frappe.db.get_all('Contact', {'first_name': 'Test Form Contact 1'}): if frappe.db.get_all('Contact', {'first_name': 'Test Form Contact 1'}):


+ 1
- 1
frappe/translate.py Прегледај датотеку

@@ -190,7 +190,7 @@ def get_full_dict(lang):
frappe.local.lang_full_dict = load_lang(lang) frappe.local.lang_full_dict = load_lang(lang)


try: try:
# get user specific transaltion data
# get user specific translation data
user_translations = get_user_translations(lang) user_translations = get_user_translations(lang)
frappe.local.lang_full_dict.update(user_translations) frappe.local.lang_full_dict.update(user_translations)
except Exception: except Exception:


+ 1
- 1
frappe/translations/de.csv Прегледај датотеку

@@ -1577,7 +1577,7 @@ Monospace,Monospace,
More articles on {0},Weitere Artikel zum {0}, 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., More content for the bottom of the page.,Zusätzlicher Inhalt für den unteren Teil der Seite.,
Most Used,Am Meisten verwendet, Most Used,Am Meisten verwendet,
Move To,Ziehen nach,
Move To,Bewegen nach,
Move To Trash,In den Papierkorb verschieben, Move To Trash,In den Papierkorb verschieben,
Move to Row Number,Gehe zu Zeilennummer, Move to Row Number,Gehe zu Zeilennummer,
Mr,Hr., Mr,Hr.,


+ 20
- 16
frappe/utils/data.py Прегледај датотеку

@@ -445,25 +445,29 @@ def get_weekday(datetime=None):
return weekdays[datetime.weekday()] return weekdays[datetime.weekday()]


def get_timespan_date_range(timespan): def get_timespan_date_range(timespan):
today = nowdate()
date_range_map = { 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"): def global_date_format(date, format="long"):
"""returns localized date in the form of January 1, 2012""" """returns localized date in the form of January 1, 2012"""


+ 6
- 3
frappe/utils/user.py Прегледај датотеку

@@ -22,6 +22,7 @@ class UserPermissions:


self.all_read = [] self.all_read = []
self.can_create = [] self.can_create = []
self.can_select = []
self.can_read = [] self.can_read = []
self.can_write = [] self.can_write = []
self.can_cancel = [] self.can_cancel = []
@@ -104,6 +105,9 @@ class UserPermissions:
if not p.get("read") and (dt in user_shared): if not p.get("read") and (dt in user_shared):
p["read"] = 1 p["read"] = 1


if p.get('select'):
self.can_select.append(dt)

if not dtp.get('istable'): if not dtp.get('istable'):
if p.get('create') and not dtp.get('issingle'): if p.get('create') and not dtp.get('issingle'):
if dtp.get('in_create'): if dtp.get('in_create'):
@@ -193,9 +197,8 @@ class UserPermissions:
d.name = self.name d.name = self.name
d.roles = self.get_roles() d.roles = self.get_roles()
d.defaults = self.get_defaults() 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", "in_create", "can_export", "can_import", "can_print", "can_email",
"can_set_user_permissions"): "can_set_user_permissions"):
d[key] = list(set(getattr(self, key))) d[key] = list(set(getattr(self, key)))


+ 2
- 2
frappe/website/doctype/blog_post/templates/blog_post_row.html Прегледај датотеку

@@ -21,7 +21,7 @@
{%- if post.featured -%} {%- if post.featured -%}
<h5 class="mt-1"><span class="text-dark">{{ post.title }}</span></h5> <h5 class="mt-1"><span class="text-dark">{{ post.title }}</span></h5>
{%- else -%} {%- 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 -%} {%- endif -%}
<p class="post-description text-muted">{{ post.intro }}</p> <p class="post-description text-muted">{{ post.intro }}</p>
</div> </div>
@@ -38,4 +38,4 @@
</div> </div>
<a class="stretched-link" href="/{{ post.route }}"></a> <a class="stretched-link" href="/{{ post.route }}"></a>
</div> </div>
</div>
</div>

+ 5
- 1
frappe/website/js/bootstrap-4.js Прегледај датотеку

@@ -18,7 +18,7 @@ $('.dropdown-menu a.dropdown-toggle').on('click', function (e) {
return false; return false;
}); });


frappe.get_modal = function(title, content) {
frappe.get_modal = function (title, content) {
return $( return $(
`<div class="modal" tabindex="-1" role="dialog"> `<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
@@ -33,6 +33,10 @@ frappe.get_modal = function(title, content) {
${content} ${content}
</div> </div>
<div class="modal-footer hidden"> <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> <button type="button" class="btn btn-sm btn-primary hidden"></button>
</div> </div>
</div> </div>


+ 1
- 3
frappe/website/web_template/testimonial/testimonial.html Прегледај датотеку

@@ -5,9 +5,7 @@
</div> </div>
{% endif %} {% endif %}
<div class="testimonial-content"> <div class="testimonial-content">
<span>“</span>
{{ content }}
<span>”</span>
“{{ content }}”
</div> </div>
<div class="testimonial-by"> <div class="testimonial-by">
{{ name }} {{ name }}


+ 1
- 1
package.json Прегледај датотеку

@@ -46,7 +46,7 @@
"redis": "^2.8.0", "redis": "^2.8.0",
"showdown": "^1.9.1", "showdown": "^1.9.1",
"snyk": "^1.425.4", "snyk": "^1.425.4",
"socket.io": "^2.3.0",
"socket.io": "^2.4.0",
"superagent": "^3.8.2", "superagent": "^3.8.2",
"touch": "^3.1.0", "touch": "^3.1.0",
"vue": "^2.6.11", "vue": "^2.6.11",


+ 52
- 78
yarn.lock Прегледај датотеку

@@ -693,13 +693,6 @@ bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
dependencies: dependencies:
tweetnacl "^0.14.3" 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: big.js@^3.1.3:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
@@ -940,11 +933,6 @@ caller-path@^2.0.0:
dependencies: dependencies:
caller-callsite "^2.0.0" 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: callsites@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" 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" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= 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: component-inherit@0.0.3:
version "0.0.3" version "0.0.3"
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" 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" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 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: cookie@0.4.0, cookie@^0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== 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: cookiejar@^2.1.0:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" 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" resolved "https://registry.yarnpkg.com/endian-reader/-/endian-reader-0.3.0.tgz#84eca436b80aed0d0639c47291338b932efe50a0"
integrity sha1-hOykNrgK7Q0GOcRykTOLky7+UKA= 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: dependencies:
component-emitter "1.2.1"
component-emitter "~1.3.0"
component-inherit "0.0.3" component-inherit "0.0.3"
debug "~4.1.0"
debug "~3.1.0"
engine.io-parser "~2.2.0" engine.io-parser "~2.2.0"
has-cors "1.1.0" has-cors "1.1.0"
indexof "0.0.1" 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" xmlhttprequest-ssl "~1.5.4"
yeast "0.1.2" yeast "0.1.2"


@@ -2010,17 +2003,17 @@ engine.io-parser@~2.2.0:
blob "0.0.5" blob "0.0.5"
has-binary2 "~1.0.2" 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: dependencies:
accepts "~1.3.4" accepts "~1.3.4"
base64id "2.0.0" base64id "2.0.0"
cookie "0.3.1"
cookie "~0.4.1"
debug "~4.1.0" debug "~4.1.0"
engine.io-parser "~2.2.0" engine.io-parser "~2.2.0"
ws "^7.1.2"
ws "~7.4.2"


entities@^1.1.1: entities@^1.1.1:
version "1.1.2" 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" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 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: object-copy@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" 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" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= 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: parseurl@~1.3.3:
version "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" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b"
integrity sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs= 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: dependencies:
backo2 "1.0.2" backo2 "1.0.2"
base64-arraybuffer "0.1.5"
component-bind "1.0.0" 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-binary2 "~1.0.2"
has-cors "1.1.0"
indexof "0.0.1" 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" socket.io-parser "~3.3.0"
to-array "0.1.4" to-array "0.1.4"


@@ -6846,16 +6827,16 @@ socket.io-parser@~3.4.0:
debug "~4.1.0" debug "~4.1.0"
isarray "2.0.1" 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: dependencies:
debug "~4.1.0" debug "~4.1.0"
engine.io "~3.4.0"
engine.io "~3.5.0"
has-binary2 "~1.0.2" has-binary2 "~1.0.2"
socket.io-adapter "~1.1.0" socket.io-adapter "~1.1.0"
socket.io-client "2.3.0"
socket.io-client "2.4.0"
socket.io-parser "~3.4.0" socket.io-parser "~3.4.0"


socks-proxy-agent@^4.0.1: socks-proxy-agent@^4.0.1:
@@ -7970,17 +7951,10 @@ write-file-atomic@^3.0.0:
signal-exit "^3.0.2" signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5" 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: xdg-basedir@^4.0.0:
version "4.0.0" version "4.0.0"


Loading…
Откажи
Сачувај