Merge branch 'develop' of git://github.com/frappe/frappe into social-improvements
This commit is contained in:
commit
deacd63991
310 changed files with 12759 additions and 8685 deletions
|
|
@ -19,10 +19,6 @@
|
|||
"error",
|
||||
"1tbs"
|
||||
],
|
||||
"space-before-function-paren": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"space-unary-ops": [
|
||||
"error",
|
||||
{ "words": true }
|
||||
|
|
@ -121,6 +117,7 @@
|
|||
"md5": true,
|
||||
"$": true,
|
||||
"jQuery": true,
|
||||
"Vue": true,
|
||||
"moment": true,
|
||||
"hljs": true,
|
||||
"Awesomplete": true,
|
||||
|
|
|
|||
9
.snyk
Normal file
9
.snyk
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.13.3
|
||||
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
|
||||
ignore:
|
||||
'npm:mem:20180117':
|
||||
- showdown > yargs > os-locale > mem:
|
||||
reason: None given
|
||||
expires: '2019-04-01T10:08:52.588Z'
|
||||
patch: {}
|
||||
|
|
@ -1 +1 @@
|
|||
skips: ['B605', 'B404', 'B603', 'B607']
|
||||
skips: ['E0203', 'B605', 'B404', 'B603', 'B607']
|
||||
|
|
@ -8,13 +8,6 @@ context('Awesome Bar', () => {
|
|||
cy.get('.navbar-home').click();
|
||||
});
|
||||
|
||||
it('navigates to modules', () => {
|
||||
cy.get('#navbar-search')
|
||||
.type('modules{downarrow}{enter}', { delay: 100 });
|
||||
|
||||
cy.location('hash').should('eq', '#modules');
|
||||
});
|
||||
|
||||
it('navigates to doctype list', () => {
|
||||
cy.get('#navbar-search')
|
||||
.type('todo{downarrow}{enter}', { delay: 100 });
|
||||
|
|
|
|||
|
|
@ -19,15 +19,15 @@ context('Relative Timeframe', () => {
|
|||
cy.get('select.condition.form-control').select("Previous");
|
||||
cy.get('.filter-field select.input-with-feedback.form-control').select("1 week");
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/'
|
||||
}).as('applyFilter');
|
||||
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.get('.filter-box .btn:contains("Apply")').click();
|
||||
cy.wait('@applyFilter');
|
||||
cy.wait('@list_refresh');
|
||||
cy.get('.list-row-container').its('length').should('eq', 1);
|
||||
cy.get('.list-row-container').should('contain', 'this is second todo');
|
||||
cy.route('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
.as('save_user_settings');
|
||||
cy.get('.remove-filter.btn').click();
|
||||
cy.wait('@save_user_settings');
|
||||
});
|
||||
it('set relative filter for Next and check list', () => {
|
||||
cy.visit('/desk#List/ToDo/List');
|
||||
|
|
@ -37,14 +37,14 @@ context('Relative Timeframe', () => {
|
|||
cy.get('select.condition.form-control').select("Next");
|
||||
cy.get('.filter-field select.input-with-feedback.form-control').select("1 week");
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/'
|
||||
}).as('applyFilter');
|
||||
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.get('.filter-box .btn:contains("Apply")').click();
|
||||
cy.wait('@applyFilter');
|
||||
cy.wait('@list_refresh');
|
||||
cy.get('.list-row-container').its('length').should('eq', 1);
|
||||
cy.get('.list-row').should('contain', 'this is first todo');
|
||||
cy.route('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
.as('save_user_settings');
|
||||
cy.get('.remove-filter.btn').click();
|
||||
cy.wait('@save_user_settings');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ context('Table MultiSelect', () => {
|
|||
cy.login('Administrator', 'qwe');
|
||||
});
|
||||
|
||||
let todo_description = 'table multiselect' + Math.random().toString().slice(2, 8);
|
||||
|
||||
it('select value from multiselect dropdown', () => {
|
||||
cy.visit('/desk#Form/ToDo/New ToDo 1');
|
||||
cy.fill_field('description', 'asdf', 'Text Editor').blur();
|
||||
cy.fill_field('description', todo_description, 'Text Editor').blur();
|
||||
cy.get('input[data-fieldname="assign_to"]').focus().as('input');
|
||||
cy.get('input[data-fieldname="assign_to"] + ul').should('be.visible');
|
||||
cy.get('@input').type('faris{enter}', { delay: 100 });
|
||||
|
|
@ -14,7 +16,7 @@ context('Table MultiSelect', () => {
|
|||
cy.get('@selected-value').should('contain', 'faris@erpnext.com');
|
||||
|
||||
cy.server();
|
||||
cy.route('POST', '/').as('save_form');
|
||||
cy.route('POST', '/api/method/frappe.desk.form.save.savedocs').as('save_form');
|
||||
// trigger save
|
||||
cy.get('.primary-action').click();
|
||||
cy.wait('@save_form').its('status').should('eq', 200);
|
||||
|
|
@ -23,8 +25,7 @@ context('Table MultiSelect', () => {
|
|||
|
||||
it('delete value using backspace', () => {
|
||||
cy.visit('/desk#List/ToDo/List');
|
||||
cy.get('.list-row a').should('exist');
|
||||
cy.get('.list-subject').last().find('a').click();
|
||||
cy.get(`.list-subject:contains("table multiselect")`).last().find('a').click();
|
||||
cy.get('input[data-fieldname="assign_to"]').focus().type('{backspace}');
|
||||
cy.get('.frappe-control[data-fieldname="assign_to"] .form-control .tb-selected-value')
|
||||
.should('not.exist');
|
||||
|
|
@ -32,8 +33,7 @@ context('Table MultiSelect', () => {
|
|||
|
||||
it('delete value using x', () => {
|
||||
cy.visit('/desk#List/ToDo/List');
|
||||
cy.get('.list-row a').should('exist');
|
||||
cy.get('.list-subject').last().find('a').click();
|
||||
cy.get(`.list-subject:contains("table multiselect")`).last().find('a').click();
|
||||
cy.get('.frappe-control[data-fieldname="assign_to"] .form-control .tb-selected-value').as('existing_value');
|
||||
cy.get('@existing_value').find('.btn-remove').click();
|
||||
cy.get('@existing_value').should('not.exist');
|
||||
|
|
@ -41,8 +41,7 @@ context('Table MultiSelect', () => {
|
|||
|
||||
it('navigate to selected value', () => {
|
||||
cy.visit('/desk#List/ToDo/List');
|
||||
cy.get('.list-row a').should('exist');
|
||||
cy.get('.list-subject').last().find('a').click();
|
||||
cy.get(`.list-subject:contains("table multiselect")`).last().find('a').click();
|
||||
cy.get('.frappe-control[data-fieldname="assign_to"] .form-control .tb-selected-value').as('existing_value');
|
||||
cy.get('@existing_value').find('.btn-link-to-form').click();
|
||||
cy.location('hash').should('contain', 'Form/User/faris@erpnext.com');
|
||||
|
|
|
|||
|
|
@ -49,4 +49,8 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => {
|
|||
} else {
|
||||
return cy.get('@input').type(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('awesomebar', (text) => {
|
||||
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, { delay: 100 });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from faker import Faker
|
|||
# public
|
||||
from .exceptions import *
|
||||
from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader)
|
||||
from .utils.error import get_frame_locals
|
||||
|
||||
# Hamless for Python 3
|
||||
# For Python 2 set default encoding to utf-8
|
||||
|
|
@ -23,7 +24,7 @@ if sys.version[0] == '2':
|
|||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
__version__ = '11.1.4'
|
||||
__version__ = '11.1.13'
|
||||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
|
|
@ -273,7 +274,8 @@ def errprint(msg):
|
|||
if not request or (not "cmd" in local.form_dict) or conf.developer_mode:
|
||||
print(msg)
|
||||
|
||||
error_log.append(msg)
|
||||
from .utils import escape_html
|
||||
error_log.append({"exc": escape_html(msg), "locals": get_frame_locals()})
|
||||
|
||||
def log(msg):
|
||||
"""Add to `debug_log`.
|
||||
|
|
@ -501,6 +503,7 @@ def read_only():
|
|||
retval = fn(*args, **get_newargs(fn, kwargs))
|
||||
|
||||
if local and hasattr(local, 'master_db'):
|
||||
local.db.close()
|
||||
local.db = local.master_db
|
||||
|
||||
return retval
|
||||
|
|
@ -916,11 +919,15 @@ def get_hooks(hook=None, default=None, app_name=None):
|
|||
append_hook(hooks, key, getattr(app_hooks, key))
|
||||
return hooks
|
||||
|
||||
no_cache = conf.developer_mode or False
|
||||
|
||||
if app_name:
|
||||
hooks = _dict(load_app_hooks(app_name))
|
||||
else:
|
||||
hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
|
||||
if no_cache:
|
||||
hooks = _dict(load_app_hooks())
|
||||
else:
|
||||
hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
|
||||
|
||||
if hook:
|
||||
return hooks.get(hook) or (default if default is not None else [])
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ import frappe.website.render
|
|||
from frappe.utils import get_site_name
|
||||
from frappe.middlewares import StaticDataMiddleware
|
||||
from frappe.utils.error import make_error_snapshot
|
||||
from frappe.core.doctype.communication.comment import update_comments_in_parent_after_request
|
||||
from frappe.core.doctype.comment.comment import update_comments_in_parent_after_request
|
||||
from frappe import _
|
||||
import frappe.recorder
|
||||
|
||||
local_manager = LocalManager([frappe.local])
|
||||
|
||||
|
|
@ -41,7 +42,6 @@ class RequestContext(object):
|
|||
def __exit__(self, type, value, traceback):
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
@Request.application
|
||||
def application(request):
|
||||
response = None
|
||||
|
|
@ -51,6 +51,8 @@ def application(request):
|
|||
|
||||
init_request(request)
|
||||
|
||||
frappe.recorder.record()
|
||||
|
||||
if frappe.local.form_dict.cmd:
|
||||
response = frappe.handler.handle()
|
||||
|
||||
|
|
@ -91,6 +93,8 @@ def application(request):
|
|||
if response and hasattr(frappe.local, 'cookie_manager'):
|
||||
frappe.local.cookie_manager.flush_cookies(response=response)
|
||||
|
||||
frappe.recorder.dump()
|
||||
|
||||
frappe.destroy()
|
||||
|
||||
return response
|
||||
|
|
|
|||
0
frappe/automation/doctype/assignment_rule/__init__.py
Normal file
0
frappe/automation/doctype/assignment_rule/__init__.py
Normal file
16
frappe/automation/doctype/assignment_rule/assignment_rule.js
Normal file
16
frappe/automation/doctype/assignment_rule/assignment_rule.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Assignment Rule', {
|
||||
refresh: function(frm) {
|
||||
// refresh description
|
||||
frm.events.rule(frm);
|
||||
},
|
||||
rule: function(frm) {
|
||||
if (frm.doc.rule === 'Round Robin') {
|
||||
frm.get_field('rule').set_description(__('Assign one by one, in sequence'));
|
||||
} else {
|
||||
frm.get_field('rule').set_description(__('Assign to the one who has the least assignments'));
|
||||
}
|
||||
}
|
||||
});
|
||||
488
frappe/automation/doctype/assignment_rule/assignment_rule.json
Normal file
488
frappe/automation/doctype/assignment_rule/assignment_rule.json
Normal file
|
|
@ -0,0 +1,488 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"beta": 0,
|
||||
"creation": "2019-02-28 17:12:18.815830",
|
||||
"custom": 0,
|
||||
"description": "Automatically Assign Documents to Users",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Higher priority rule will be applied first",
|
||||
"fieldname": "priority",
|
||||
"fieldtype": "Int",
|
||||
"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": "Priority",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disabled",
|
||||
"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": "Disabled",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"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,
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Automatic Assignment",
|
||||
"description": "Example: {{ subject }}",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"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": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "assignment_rules_section",
|
||||
"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": "Assignment Rules",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Simple Python Expression, Example: status == 'Open' and type == 'Bug'",
|
||||
"fieldname": "assign_condition",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Assign Condition",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_6",
|
||||
"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,
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Simple Python Expression, Example: Status in (\"Closed\", \"Cancelled\")",
|
||||
"fieldname": "unassign_condition",
|
||||
"fieldtype": "Small Text",
|
||||
"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": "Unassign Condition",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "assign_to_users_section",
|
||||
"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": "Assign To Users",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rule",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Rule",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Round Robin\nLoad Balancing",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "users",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Users",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Assignment Rule User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "last_user",
|
||||
"fieldtype": "Link",
|
||||
"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": "Last User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-28 17:12:44.413782",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Assignment Rule",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
115
frappe/automation/doctype/assignment_rule/assignment_rule.py
Normal file
115
frappe/automation/doctype/assignment_rule/assignment_rule.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.desk.form import assign_to
|
||||
|
||||
class AssignmentRule(Document):
|
||||
def on_update(self): # pylint: disable=no-self-use
|
||||
frappe.cache().delete_value('assignment_rule')
|
||||
|
||||
def after_rename(self): # pylint: disable=no-self-use
|
||||
frappe.cache().delete_value('assignment_rule')
|
||||
|
||||
def apply(self, doc):
|
||||
assignments = assign_to.get(doc)
|
||||
if not assignments and self.safe_eval('assign_condition', doc):
|
||||
self.do_assignment(doc)
|
||||
return True
|
||||
|
||||
# try clearing
|
||||
if assignments and self.unassign_condition:
|
||||
return self.clear_assignment(doc)
|
||||
|
||||
return False
|
||||
|
||||
def do_assignment(self, doc):
|
||||
# clear existing assignment, to reassign
|
||||
assign_to.clear(doc.get('doctype'), doc.get('name'))
|
||||
|
||||
user = self.get_user()
|
||||
|
||||
assign_to.add(dict(
|
||||
assign_to = user,
|
||||
doctype = doc.get('doctype'),
|
||||
name = doc.get('name'),
|
||||
description = frappe.render_template(self.description, doc)
|
||||
))
|
||||
|
||||
# set for reference in round robin
|
||||
self.db_set('last_user', user)
|
||||
|
||||
def clear_assignment(self, doc):
|
||||
'''Clear assignments'''
|
||||
if self.safe_eval('unassign_condition', doc):
|
||||
return assign_to.clear(doc.get('doctype'), doc.get('name'))
|
||||
|
||||
def get_user(self):
|
||||
'''
|
||||
Get the next user for assignment
|
||||
'''
|
||||
if self.rule == 'Round Robin':
|
||||
return self.get_user_round_robin()
|
||||
elif self.rule == 'Load Balancing':
|
||||
return self.get_user_load_balancing()
|
||||
|
||||
def get_user_round_robin(self):
|
||||
'''
|
||||
Get next user based on round robin
|
||||
'''
|
||||
|
||||
# first time, or last in list, pick the first
|
||||
if not self.last_user or self.last_user == self.users[-1].user:
|
||||
return self.users[0].user
|
||||
|
||||
# find out the next user in the list
|
||||
for i, d in enumerate(self.users):
|
||||
if self.last_user == d.user:
|
||||
return self.users[i+1].user
|
||||
|
||||
# bad last user, assign to the first one
|
||||
return self.users[0].user
|
||||
|
||||
def get_user_load_balancing(self):
|
||||
'''Assign to the user with least number of open assignments'''
|
||||
counts = []
|
||||
for d in self.users:
|
||||
counts.append(dict(
|
||||
user = d.user,
|
||||
count = frappe.db.count('ToDo', dict(
|
||||
reference_type = self.document_type,
|
||||
owner = d.user,
|
||||
status = "Open"))
|
||||
))
|
||||
|
||||
# sort by dict value
|
||||
sorted_counts = sorted(counts, key = lambda k: k['count'])
|
||||
|
||||
# pick the first user
|
||||
return sorted_counts[0].get('user')
|
||||
|
||||
def safe_eval(self, fieldname, doc):
|
||||
try:
|
||||
return frappe.safe_eval(self.get(fieldname), None, doc)
|
||||
except Exception:
|
||||
# when assignment fails, don't block the document as it may be
|
||||
# a part of the email pulling
|
||||
frappe.msgprint(frappe._('Auto assignment failed'), indicator = 'orange')
|
||||
|
||||
def apply(doc, method):
|
||||
if frappe.flags.in_patch or frappe.flags.in_install:
|
||||
return
|
||||
|
||||
assignment_rules = frappe.cache().get_value('assignment_rule', get_assignment_rules)
|
||||
if doc.doctype in assignment_rules:
|
||||
# multiple auto assigns
|
||||
for d in frappe.db.get_all('Assignment Rule', dict(document_type=doc.doctype, disabled = 0), order_by = 'priority desc'):
|
||||
if frappe.get_doc('Assignment Rule', d.name).apply(doc.as_dict()):
|
||||
break
|
||||
|
||||
def get_assignment_rules():
|
||||
return [d.document_type for d in frappe.db.get_all('Assignment Rule', fields=['document_type'], filters=dict(disabled = 0))]
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import random_string
|
||||
|
||||
class TestAutoAssign(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.assignment_rule = get_assignment_rule()
|
||||
clear_assignments()
|
||||
|
||||
def test_round_robin(self):
|
||||
note = make_note(dict(public=1))
|
||||
|
||||
# check if auto assigned to first user
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), 'test@example.com')
|
||||
|
||||
note = make_note(dict(public=1))
|
||||
|
||||
# check if auto assigned to second user
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), 'test1@example.com')
|
||||
|
||||
clear_assignments()
|
||||
|
||||
note = make_note(dict(public=1))
|
||||
|
||||
# check if auto assigned to third user, even if
|
||||
# previous assignments where closed
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), 'test2@example.com')
|
||||
|
||||
# check loop back to first user
|
||||
note = make_note(dict(public=1))
|
||||
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), 'test@example.com')
|
||||
|
||||
def test_load_balancing(self):
|
||||
self.assignment_rule.rule = 'Load Balancing'
|
||||
self.assignment_rule.save()
|
||||
|
||||
for _ in range(30):
|
||||
note = make_note(dict(public=1))
|
||||
|
||||
# check if each user has 10 assignments (?)
|
||||
for user in ('test@example.com', 'test1@example.com', 'test2@example.com'):
|
||||
self.assertEqual(len(frappe.get_all('ToDo', dict(owner = user, reference_type = 'Note'))), 10)
|
||||
|
||||
# clear 5 assignments for first user
|
||||
# can't do a limit in "delete" since postgres does not support it
|
||||
for d in frappe.get_all('ToDo', dict(reference_type = 'Note', owner = 'test@example.com'), limit=5):
|
||||
frappe.db.sql("delete from tabToDo where name = %s", d.name)
|
||||
|
||||
# add 5 more assignments
|
||||
for i in range(5):
|
||||
make_note(dict(public=1))
|
||||
|
||||
# check if each user still has 10 assignments
|
||||
for user in ('test@example.com', 'test1@example.com', 'test2@example.com'):
|
||||
self.assertEqual(len(frappe.get_all('ToDo', dict(owner = user, reference_type = 'Note'))), 10)
|
||||
|
||||
|
||||
def test_assign_condition(self):
|
||||
# check condition
|
||||
note = make_note(dict(public=0))
|
||||
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), None)
|
||||
|
||||
def test_clear_assignment(self):
|
||||
note = make_note(dict(public=1))
|
||||
|
||||
# check if auto assigned to first user
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), 'test@example.com')
|
||||
|
||||
# test auto unassign
|
||||
note.public = 0
|
||||
note.save()
|
||||
|
||||
# check if cleared
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), None)
|
||||
|
||||
def check_multiple_rules(self):
|
||||
note = make_note(dict(public=1, notify_on_login=1))
|
||||
|
||||
# check if auto assigned to test3 (2nd rule is applied, as it has higher priority)
|
||||
self.assertEqual(frappe.db.get_value('ToDo', dict(
|
||||
reference_type = 'Note',
|
||||
reference_name = note.name,
|
||||
status = 'Open'
|
||||
), 'owner'), 'test@example.com')
|
||||
|
||||
def clear_assignments():
|
||||
frappe.db.sql("delete from tabToDo where reference_type = 'Note'")
|
||||
|
||||
def get_assignment_rule():
|
||||
frappe.delete_doc_if_exists('Assignment Rule', 'For Note 1')
|
||||
|
||||
assignment_rule = frappe.get_doc(dict(
|
||||
name = 'For Note 1',
|
||||
doctype = 'Assignment Rule',
|
||||
priority = 0,
|
||||
document_type = 'Note',
|
||||
assign_condition = 'public == 1',
|
||||
unassign_condition = 'pubic == 0 or notify_on_login == 1',
|
||||
rule = 'Round Robin',
|
||||
users = [
|
||||
dict(user = 'test@example.com'),
|
||||
dict(user = 'test1@example.com'),
|
||||
dict(user = 'test2@example.com'),
|
||||
]
|
||||
)).insert()
|
||||
|
||||
frappe.delete_doc_if_exists('Assignment Rule', 'For Note 2')
|
||||
|
||||
# 2nd rule
|
||||
frappe.get_doc(dict(
|
||||
name = 'For Note 2',
|
||||
doctype = 'Assignment Rule',
|
||||
priority = 1,
|
||||
document_type = 'Note',
|
||||
assign_condition = 'notify_on_login == 1',
|
||||
unassign_condition = 'notify_on_login == 0',
|
||||
rule = 'Round Robin',
|
||||
users = [
|
||||
dict(user = 'test3@example.com')
|
||||
]
|
||||
)).insert()
|
||||
|
||||
|
||||
return assignment_rule
|
||||
|
||||
def make_note(values=None):
|
||||
note = frappe.get_doc(dict(
|
||||
doctype = 'Note',
|
||||
title = random_string(10),
|
||||
content = random_string(20)
|
||||
))
|
||||
|
||||
if values:
|
||||
note.update(values)
|
||||
|
||||
note.insert()
|
||||
|
||||
return note
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2019-02-27 11:41:46.602400",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-27 17:16:41.399261",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Assignment Rule User",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, 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 AssignmentRuleUser(Document):
|
||||
pass
|
||||
|
|
@ -96,8 +96,8 @@ def load_conf_settings(bootinfo):
|
|||
if key in conf: bootinfo[key] = conf.get(key)
|
||||
|
||||
def load_desktop_icons(bootinfo):
|
||||
from frappe.desk.doctype.desktop_icon.desktop_icon import get_desktop_icons
|
||||
bootinfo.desktop_icons = get_desktop_icons()
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
bootinfo.allowed_modules = get_modules_from_all_apps_for_user()
|
||||
|
||||
def get_allowed_pages():
|
||||
return get_user_pages_or_reports('Page')
|
||||
|
|
|
|||
|
|
@ -2,19 +2,16 @@
|
|||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals, print_function
|
||||
from frappe.utils.minify import JavascriptMinify
|
||||
import warnings
|
||||
|
||||
from six import iteritems, text_type
|
||||
import subprocess
|
||||
import os, frappe, json, shutil, re, warnings
|
||||
from os.path import exists as path_exists, join as join_path, abspath, isdir
|
||||
from distutils.spawn import find_executable
|
||||
from six import iteritems, text_type
|
||||
from frappe.utils.minify import JavascriptMinify
|
||||
|
||||
"""
|
||||
Build the `public` folders and setup languages
|
||||
"""
|
||||
|
||||
import os, frappe, json, shutil, re
|
||||
|
||||
app_paths = None
|
||||
def setup():
|
||||
global app_paths
|
||||
|
|
@ -45,7 +42,7 @@ def bundle(no_compress, app=None, make_copy=False, restore=False, verbose=False)
|
|||
if app:
|
||||
command += ' --app {app}'.format(app=app)
|
||||
|
||||
frappe_app_path = os.path.abspath(os.path.join(app_paths[0], '..'))
|
||||
frappe_app_path = abspath(join_path(app_paths[0], '..'))
|
||||
check_yarn()
|
||||
frappe.commands.popen(command, cwd=frappe_app_path)
|
||||
|
||||
|
|
@ -55,7 +52,7 @@ def watch(no_compress):
|
|||
|
||||
pacman = get_node_pacman()
|
||||
|
||||
frappe_app_path = os.path.abspath(os.path.join(app_paths[0], '..'))
|
||||
frappe_app_path = abspath(join_path(app_paths[0], '..'))
|
||||
check_yarn()
|
||||
frappe_app_path = frappe.get_app_path('frappe', '..')
|
||||
frappe.commands.popen('{pacman} run watch'.format(pacman=pacman), cwd = frappe_app_path)
|
||||
|
|
@ -69,51 +66,53 @@ def check_yarn():
|
|||
|
||||
def make_asset_dirs(make_copy=False, restore=False):
|
||||
# don't even think of making assets_path absolute - rm -rf ahead.
|
||||
assets_path = os.path.join(frappe.local.sites_path, "assets")
|
||||
assets_path = join_path(frappe.local.sites_path, "assets")
|
||||
for dir_path in [
|
||||
os.path.join(assets_path, 'js'),
|
||||
os.path.join(assets_path, 'css')]:
|
||||
join_path(assets_path, 'js'),
|
||||
join_path(assets_path, 'css')]:
|
||||
|
||||
if not os.path.exists(dir_path):
|
||||
if not path_exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
|
||||
for app_name in frappe.get_all_apps(True):
|
||||
pymodule = frappe.get_module(app_name)
|
||||
app_base_path = os.path.abspath(os.path.dirname(pymodule.__file__))
|
||||
app_base_path = abspath(os.path.dirname(pymodule.__file__))
|
||||
|
||||
symlinks = []
|
||||
app_public_path = join_path(app_base_path, 'public')
|
||||
# app/public > assets/app
|
||||
symlinks.append([os.path.join(app_base_path, 'public'), os.path.join(assets_path, app_name)])
|
||||
symlinks.append([app_public_path, join_path(assets_path, app_name)])
|
||||
# app/node_modules > assets/app/node_modules
|
||||
symlinks.append([os.path.join(app_base_path, '..', 'node_modules'), os.path.join(assets_path, app_name, 'node_modules')])
|
||||
if path_exists(abspath(app_public_path)):
|
||||
symlinks.append([join_path(app_base_path, '..', 'node_modules'), join_path(assets_path, app_name, 'node_modules')])
|
||||
|
||||
app_doc_path = None
|
||||
if os.path.isdir(os.path.join(app_base_path, 'docs')):
|
||||
app_doc_path = os.path.join(app_base_path, 'docs')
|
||||
if isdir(join_path(app_base_path, 'docs')):
|
||||
app_doc_path = join_path(app_base_path, 'docs')
|
||||
|
||||
elif os.path.isdir(os.path.join(app_base_path, 'www', 'docs')):
|
||||
app_doc_path = os.path.join(app_base_path, 'www', 'docs')
|
||||
elif isdir(join_path(app_base_path, 'www', 'docs')):
|
||||
app_doc_path = join_path(app_base_path, 'www', 'docs')
|
||||
|
||||
if app_doc_path:
|
||||
symlinks.append([app_doc_path, os.path.join(assets_path, app_name + '_docs')])
|
||||
symlinks.append([app_doc_path, join_path(assets_path, app_name + '_docs')])
|
||||
|
||||
for source, target in symlinks:
|
||||
source = os.path.abspath(source)
|
||||
if os.path.exists(source):
|
||||
source = abspath(source)
|
||||
if path_exists(source):
|
||||
if restore:
|
||||
if os.path.exists(target):
|
||||
if path_exists(target):
|
||||
if os.path.islink(target):
|
||||
os.unlink(target)
|
||||
else:
|
||||
shutil.rmtree(target)
|
||||
shutil.copytree(source, target)
|
||||
elif make_copy:
|
||||
if os.path.exists(target):
|
||||
if path_exists(target):
|
||||
warnings.warn('Target {target} already exists.'.format(target = target))
|
||||
else:
|
||||
shutil.copytree(source, target)
|
||||
else:
|
||||
if os.path.exists(target):
|
||||
if path_exists(target):
|
||||
if os.path.islink(target):
|
||||
os.unlink(target)
|
||||
else:
|
||||
|
|
@ -124,10 +123,10 @@ def make_asset_dirs(make_copy=False, restore=False):
|
|||
pass
|
||||
|
||||
def build(no_compress=False, verbose=False):
|
||||
assets_path = os.path.join(frappe.local.sites_path, "assets")
|
||||
assets_path = join_path(frappe.local.sites_path, "assets")
|
||||
|
||||
for target, sources in iteritems(get_build_maps()):
|
||||
pack(os.path.join(assets_path, target), sources, no_compress, verbose)
|
||||
pack(join_path(assets_path, target), sources, no_compress, verbose)
|
||||
|
||||
def get_build_maps():
|
||||
"""get all build.jsons with absolute paths"""
|
||||
|
|
@ -135,8 +134,8 @@ def get_build_maps():
|
|||
|
||||
build_maps = {}
|
||||
for app_path in app_paths:
|
||||
path = os.path.join(app_path, 'public', 'build.json')
|
||||
if os.path.exists(path):
|
||||
path = join_path(app_path, 'public', 'build.json')
|
||||
if path_exists(path):
|
||||
with open(path) as f:
|
||||
try:
|
||||
for target, sources in iteritems(json.loads(f.read())):
|
||||
|
|
@ -146,7 +145,7 @@ def get_build_maps():
|
|||
if isinstance(source, list):
|
||||
s = frappe.get_pymodule_path(source[0], *source[1].split("/"))
|
||||
else:
|
||||
s = os.path.join(app_path, source)
|
||||
s = join_path(app_path, source)
|
||||
source_paths.append(s)
|
||||
|
||||
build_maps[target] = source_paths
|
||||
|
|
@ -166,7 +165,7 @@ def pack(target, sources, no_compress, verbose):
|
|||
for f in sources:
|
||||
suffix = None
|
||||
if ':' in f: f, suffix = f.split(':')
|
||||
if not os.path.exists(f) or os.path.isdir(f):
|
||||
if not path_exists(f) or isdir(f):
|
||||
print("did not find " + f)
|
||||
continue
|
||||
timestamps[f] = os.path.getmtime(f)
|
||||
|
|
@ -220,7 +219,7 @@ def files_dirty():
|
|||
for target, sources in iteritems(get_build_maps()):
|
||||
for f in sources:
|
||||
if ':' in f: f, suffix = f.split(':')
|
||||
if not os.path.exists(f) or os.path.isdir(f): continue
|
||||
if not path_exists(f) or isdir(f): continue
|
||||
if os.path.getmtime(f) != timestamps.get(f):
|
||||
print(f + ' dirty')
|
||||
return True
|
||||
|
|
@ -233,11 +232,11 @@ def compile_less():
|
|||
return
|
||||
|
||||
for path in app_paths:
|
||||
less_path = os.path.join(path, "public", "less")
|
||||
if os.path.exists(less_path):
|
||||
less_path = join_path(path, "public", "less")
|
||||
if path_exists(less_path):
|
||||
for fname in os.listdir(less_path):
|
||||
if fname.endswith(".less") and fname != "variables.less":
|
||||
fpath = os.path.join(less_path, fname)
|
||||
fpath = join_path(less_path, fname)
|
||||
mtime = os.path.getmtime(fpath)
|
||||
if fpath in timestamps and mtime == timestamps[fpath]:
|
||||
continue
|
||||
|
|
@ -246,5 +245,5 @@ def compile_less():
|
|||
|
||||
print("compiling {0}".format(fpath))
|
||||
|
||||
css_path = os.path.join(path, "public", "css", fname.rsplit(".", 1)[0] + ".css")
|
||||
css_path = join_path(path, "public", "css", fname.rsplit(".", 1)[0] + ".css")
|
||||
os.system("lessc {0} > {1}".format(fpath, css_path))
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ def clear_global_cache():
|
|||
clear_website_cache()
|
||||
frappe.cache().delete_value(["app_hooks", "installed_apps",
|
||||
"app_modules", "module_app", "notification_config", 'system_settings',
|
||||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains', 'active_modules'])
|
||||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
|
||||
'active_modules', 'assignment_rule'])
|
||||
frappe.setup_module_map()
|
||||
|
||||
def clear_defaults_cache(user=None):
|
||||
|
|
|
|||
|
|
@ -141,13 +141,16 @@ def send(user, room, content, type = "Content"):
|
|||
def seen(message, user = None):
|
||||
authenticate(user)
|
||||
|
||||
mess = frappe.get_doc('Chat Message', message)
|
||||
mess.add_seen(user)
|
||||
has_message = frappe.db.exists('Chat Message', message)
|
||||
|
||||
room = mess.room
|
||||
resp = dict(message = message, data = dict(seen = json.loads(mess._seen)))
|
||||
if has_message:
|
||||
mess = frappe.get_doc('Chat Message', message)
|
||||
mess.add_seen(user)
|
||||
|
||||
frappe.publish_realtime('frappe.chat.message:update', resp, room = room, after_commit = True)
|
||||
room = mess.room
|
||||
resp = dict(message = message, data = dict(seen = json.loads(mess._seen)))
|
||||
|
||||
frappe.publish_realtime('frappe.chat.message:update', resp, room = room, after_commit = True)
|
||||
|
||||
def history(room, fields = None, limit = 10, start = None, end = None):
|
||||
room = frappe.get_doc('Chat Room', room)
|
||||
|
|
@ -194,18 +197,21 @@ def mark_messages_as_seen(message_names, user):
|
|||
def get(name, rooms = None, fields = None):
|
||||
rooms, fields = safe_json_loads(rooms, fields)
|
||||
|
||||
dmess = frappe.get_doc('Chat Message', name)
|
||||
data = dict(
|
||||
name = dmess.name,
|
||||
user = dmess.user,
|
||||
room = dmess.room,
|
||||
room_type = dmess.room_type,
|
||||
content = json.loads(dmess.content) if dmess.type in ["File"] else dmess.content,
|
||||
type = dmess.type,
|
||||
urls = dmess.urls,
|
||||
mentions = dmess.mentions,
|
||||
creation = dmess.creation,
|
||||
seen = get_if_empty(dmess._seen, [ ])
|
||||
)
|
||||
has_message = frappe.db.exists('Chat Message', name)
|
||||
|
||||
return data
|
||||
if has_message:
|
||||
dmess = frappe.get_doc('Chat Message', name)
|
||||
data = dict(
|
||||
name = dmess.name,
|
||||
user = dmess.user,
|
||||
room = dmess.room,
|
||||
room_type = dmess.room_type,
|
||||
content = json.loads(dmess.content) if dmess.type in ["File"] else dmess.content,
|
||||
type = dmess.type,
|
||||
urls = dmess.urls,
|
||||
mentions = dmess.mentions,
|
||||
creation = dmess.creation,
|
||||
seen = get_if_empty(dmess._seen, [ ])
|
||||
)
|
||||
|
||||
return data
|
||||
|
|
@ -9,99 +9,94 @@ import frappe
|
|||
from frappe.core.doctype.version.version import get_diff
|
||||
from frappe.chat.doctype.chat_room import chat_room
|
||||
from frappe.chat.util import (
|
||||
safe_json_loads,
|
||||
filter_dict,
|
||||
dictify
|
||||
safe_json_loads,
|
||||
filter_dict,
|
||||
dictify
|
||||
)
|
||||
|
||||
session = frappe.session
|
||||
|
||||
class ChatProfile(Document):
|
||||
def before_save(self):
|
||||
if not self.is_new():
|
||||
self.get_doc_before_save()
|
||||
def before_save(self):
|
||||
if not self.is_new():
|
||||
self.get_doc_before_save()
|
||||
|
||||
def on_update(self):
|
||||
if not self.is_new():
|
||||
b, a = self.get_doc_before_save(), self
|
||||
diff = dictify(get_diff(a, b))
|
||||
if diff:
|
||||
user = session.user
|
||||
def on_update(self):
|
||||
if not self.is_new():
|
||||
b, a = self.get_doc_before_save(), self
|
||||
diff = dictify(get_diff(a, b))
|
||||
if diff:
|
||||
user = session.user
|
||||
|
||||
fields = [changed[0] for changed in diff.changed]
|
||||
fields = [changed[0] for changed in diff.changed]
|
||||
|
||||
if 'status' in fields:
|
||||
rooms = chat_room.get(user, filters = ['Chat Room', 'type', '=', 'Direct'])
|
||||
update = dict(user = user, data = dict(status = self.status))
|
||||
if 'status' in fields:
|
||||
rooms = chat_room.get(user, filters = ['Chat Room', 'type', '=', 'Direct'])
|
||||
update = dict(user = user, data = dict(status = self.status))
|
||||
|
||||
for room in rooms:
|
||||
frappe.publish_realtime('frappe.chat.profile:update', update, room = room.name, after_commit = True)
|
||||
for room in rooms:
|
||||
frappe.publish_realtime('frappe.chat.profile:update', update, room = room.name, after_commit = True)
|
||||
|
||||
if 'enable_chat' in fields:
|
||||
update = dict(user = user, data = dict(enable_chat = bool(self.enable_chat)))
|
||||
frappe.publish_realtime('frappe.chat.profile:update', update, user = user, after_commit = True)
|
||||
if 'enable_chat' in fields:
|
||||
update = dict(user = user, data = dict(enable_chat = bool(self.enable_chat)))
|
||||
frappe.publish_realtime('frappe.chat.profile:update', update, user = user, after_commit = True)
|
||||
|
||||
def authenticate(user):
|
||||
if user != session.user:
|
||||
frappe.throw(_("Sorry, you're not authorized."))
|
||||
if user != session.user:
|
||||
frappe.throw(_("Sorry, you're not authorized."))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get(user, fields = None):
|
||||
duser = frappe.get_doc('User', user)
|
||||
dprof = frappe.get_doc('Chat Profile', user)
|
||||
duser = frappe.get_doc('User', user)
|
||||
dprof = frappe.get_doc('Chat Profile', user)
|
||||
|
||||
# If you're adding something here, make sure the client recieves it.
|
||||
profile = dict(
|
||||
# User
|
||||
name = duser.name,
|
||||
email = duser.email,
|
||||
first_name = duser.first_name,
|
||||
last_name = duser.last_name,
|
||||
username = duser.username,
|
||||
avatar = duser.user_image,
|
||||
bio = duser.bio,
|
||||
# Chat Profile
|
||||
status = dprof.status,
|
||||
chat_background = dprof.chat_background,
|
||||
message_preview = bool(dprof.message_preview),
|
||||
notification_tones = bool(dprof.notification_tones),
|
||||
conversation_tones = bool(dprof.conversation_tones),
|
||||
enable_chat = bool(dprof.enable_chat)
|
||||
)
|
||||
profile = filter_dict(profile, fields)
|
||||
# If you're adding something here, make sure the client recieves it.
|
||||
profile = dict(
|
||||
# User
|
||||
name = duser.name,
|
||||
email = duser.email,
|
||||
first_name = duser.first_name,
|
||||
last_name = duser.last_name,
|
||||
username = duser.username,
|
||||
avatar = duser.user_image,
|
||||
bio = duser.bio,
|
||||
# Chat Profile
|
||||
status = dprof.status,
|
||||
chat_background = dprof.chat_background,
|
||||
message_preview = bool(dprof.message_preview),
|
||||
notification_tones = bool(dprof.notification_tones),
|
||||
conversation_tones = bool(dprof.conversation_tones),
|
||||
enable_chat = bool(dprof.enable_chat)
|
||||
)
|
||||
profile = filter_dict(profile, fields)
|
||||
|
||||
return dictify(profile)
|
||||
return dictify(profile)
|
||||
|
||||
@frappe.whitelist()
|
||||
def create(user, exists_ok = False, fields = None):
|
||||
authenticate(user)
|
||||
authenticate(user)
|
||||
|
||||
exists_ok, fields = safe_json_loads(exists_ok, fields)
|
||||
exists_ok, fields = safe_json_loads(exists_ok, fields)
|
||||
|
||||
result = frappe.db.sql("""
|
||||
SELECT *
|
||||
FROM `tabChat Profile`
|
||||
WHERE `user` = '{user}'
|
||||
""".format(user = user))
|
||||
try:
|
||||
dprof = frappe.new_doc('Chat Profile')
|
||||
dprof.user = user
|
||||
dprof.save(ignore_permissions = True)
|
||||
except frappe.DuplicateEntryError:
|
||||
frappe.clear_messages()
|
||||
if not exists_ok:
|
||||
frappe.throw(_('Chat Profile for User {0} exists.').format(user))
|
||||
|
||||
if result:
|
||||
if not exists_ok:
|
||||
frappe.throw(_('Chat Profile for User {0} exists.').format(user))
|
||||
else:
|
||||
dprof = frappe.new_doc('Chat Profile')
|
||||
dprof.user = user
|
||||
dprof.save(ignore_permissions = True)
|
||||
profile = get(user, fields = fields)
|
||||
|
||||
profile = get(user, fields = fields)
|
||||
|
||||
return profile
|
||||
return profile
|
||||
|
||||
@frappe.whitelist()
|
||||
def update(user, data):
|
||||
authenticate(user)
|
||||
authenticate(user)
|
||||
|
||||
data = safe_json_loads(data)
|
||||
data = safe_json_loads(data)
|
||||
|
||||
dprof = frappe.get_doc('Chat Profile', user)
|
||||
dprof.update(data)
|
||||
dprof.save(ignore_permissions = True)
|
||||
dprof = frappe.get_doc('Chat Profile', user)
|
||||
dprof.update(data)
|
||||
dprof.save(ignore_permissions = True)
|
||||
|
|
@ -358,7 +358,7 @@ def attach_file(filename=None, filedata=None, doctype=None, docname=None, folder
|
|||
doc.set(docfield, _file.file_url)
|
||||
doc.save()
|
||||
|
||||
return f.as_dict()
|
||||
return _file.as_dict()
|
||||
|
||||
def check_parent_permission(parent, child_doctype):
|
||||
if parent:
|
||||
|
|
|
|||
|
|
@ -569,6 +569,23 @@ def browse(context, site):
|
|||
else:
|
||||
click.echo("\nSite named \033[1m{}\033[0m doesn't exist\n".format(site))
|
||||
|
||||
|
||||
@click.command('start-recording')
|
||||
@pass_context
|
||||
def start_recording(context):
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
frappe.recorder.start()
|
||||
|
||||
|
||||
@click.command('stop-recording')
|
||||
@pass_context
|
||||
def stop_recording(context):
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
frappe.recorder.stop()
|
||||
|
||||
|
||||
commands = [
|
||||
add_system_manager,
|
||||
backup,
|
||||
|
|
@ -592,5 +609,7 @@ commands = [
|
|||
_use,
|
||||
set_last_active_for_user,
|
||||
publish_realtime,
|
||||
browse
|
||||
browse,
|
||||
start_recording,
|
||||
stop_recording,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -223,15 +223,16 @@ def export_csv(context, doctype, path):
|
|||
frappe.destroy()
|
||||
|
||||
@click.command('export-fixtures')
|
||||
@click.option('--app', default=None, help='Export fixtures of a specific app')
|
||||
@pass_context
|
||||
def export_fixtures(context):
|
||||
def export_fixtures(context, app=None):
|
||||
"Export fixtures"
|
||||
from frappe.utils.fixtures import export_fixtures
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
export_fixtures()
|
||||
export_fixtures(app=app)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
from __future__ import unicode_literals
|
||||
import json
|
||||
from six import iteritems
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.moduleview import (get_data, get_onboard_items, config_exists, get_module_link_items_from_list)
|
||||
|
||||
def get_modules_from_all_apps_for_user(user=None):
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
|
||||
all_modules = get_modules_from_all_apps()
|
||||
user_blocked_modules = frappe.get_doc('User', user).get_blocked_modules()
|
||||
|
||||
allowed_modules_list = [m for m in all_modules if m.get("module_name") not in user_blocked_modules]
|
||||
|
||||
empty_tables_by_module = get_all_empty_tables_by_module()
|
||||
|
||||
home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings')
|
||||
if home_settings:
|
||||
home_settings = json.loads(home_settings)
|
||||
|
||||
for module in allowed_modules_list:
|
||||
module_name = module["module_name"]
|
||||
if module_name in empty_tables_by_module:
|
||||
module["onboard_present"] = 1
|
||||
|
||||
if home_settings:
|
||||
category_settings = home_settings[module.get("category")] if module.get("category") else {}
|
||||
if module_name not in category_settings:
|
||||
module["hidden"] = 1
|
||||
else:
|
||||
links = category_settings[module_name]["links"]
|
||||
if links:
|
||||
module["links"] = get_module_link_items_from_list(module["app"], module_name, links.split(","))
|
||||
|
||||
return allowed_modules_list
|
||||
|
||||
def get_modules_from_all_apps():
|
||||
modules_list = []
|
||||
for app in frappe.get_installed_apps():
|
||||
modules_list += get_modules_from_app(app)
|
||||
return modules_list
|
||||
|
||||
def get_modules_from_app(app):
|
||||
try:
|
||||
modules = frappe.get_attr(app + '.config.desktop.get_data')() or {}
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
active_domains = frappe.get_active_domains()
|
||||
|
||||
if isinstance(modules, dict):
|
||||
active_modules_list = []
|
||||
for m, module in iteritems(modules):
|
||||
module['module_name'] = m
|
||||
active_modules_list.append(module)
|
||||
else:
|
||||
for m in modules:
|
||||
if m.get("type") == "module" and "category" not in m:
|
||||
m["category"] = "Modules"
|
||||
|
||||
# Only newly formatted modules that have a category to be shown on desk
|
||||
modules = [m for m in modules if m.get("category")]
|
||||
active_modules_list = []
|
||||
|
||||
for m in modules:
|
||||
to_add = True
|
||||
module_name = m.get("module_name")
|
||||
|
||||
# Check Domain
|
||||
if is_domain(m) and module_name not in active_domains:
|
||||
to_add = False
|
||||
|
||||
# Check if config
|
||||
if is_module(m) and not config_exists(app, frappe.scrub(module_name)):
|
||||
to_add = False
|
||||
|
||||
if "condition" in m and not m["condition"]:
|
||||
to_add = False
|
||||
|
||||
if to_add:
|
||||
m["app"] = app
|
||||
active_modules_list.append(m)
|
||||
|
||||
return active_modules_list
|
||||
|
||||
def get_all_empty_tables_by_module():
|
||||
results = frappe.db.sql("""
|
||||
SELECT
|
||||
name, module
|
||||
FROM information_schema.tables as i
|
||||
JOIN tabDocType as d
|
||||
ON i.table_name = CONCAT('tab', d.name)
|
||||
WHERE table_rows = 0;
|
||||
|
||||
""")
|
||||
|
||||
empty_tables_by_module = {}
|
||||
|
||||
for doctype, module in results:
|
||||
if module in empty_tables_by_module:
|
||||
empty_tables_by_module[module].append(doctype)
|
||||
else:
|
||||
empty_tables_by_module[module] = [doctype]
|
||||
|
||||
return empty_tables_by_module
|
||||
|
||||
def is_domain(module):
|
||||
return module.get("category") == "Domains"
|
||||
|
||||
def is_module(module):
|
||||
return module.get("type") == "module"
|
||||
44
frappe/config/customization.py
Normal file
44
frappe/config/customization.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
{
|
||||
"label": _("Customize"),
|
||||
"icon": "fa fa-glass",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Customize Form",
|
||||
"description": _("Change field properties (hide, readonly, permission etc.)")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Custom Field",
|
||||
"description": _("Add fields to forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"label": _("Custom Translations"),
|
||||
"name": "Translation",
|
||||
"description": _("Add your own translations")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Custom Script",
|
||||
"description": _("Add custom javascript to forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "DocType",
|
||||
"description": _("Add custom forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"label": _("Custom Tags"),
|
||||
"name": "Tag Category",
|
||||
"description": _("Add your own Tag Categories")
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
|
|
@ -12,11 +12,7 @@ def get_data():
|
|||
"name": "ToDo",
|
||||
"label": _("To Do"),
|
||||
"description": _("Documents assigned to you and by you."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "File",
|
||||
"label": _("Files"),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
|
|
@ -24,6 +20,18 @@ def get_data():
|
|||
"label": _("Calendar"),
|
||||
"link": "List/Event/Calendar",
|
||||
"description": _("Event and other calendars."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Note",
|
||||
"description": _("Private and public Notes."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "File",
|
||||
"label": _("Files"),
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
|
|
@ -32,11 +40,6 @@ def get_data():
|
|||
"description": _("Chat messages and other notifications."),
|
||||
"data_doctype": "Communication"
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Note",
|
||||
"description": _("Private and public Notes."),
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"label": _("Activity"),
|
||||
|
|
@ -52,6 +55,7 @@ def get_data():
|
|||
"type": "doctype",
|
||||
"name": "Newsletter",
|
||||
"description": _("Newsletters to contacts, leads."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
|
|
|
|||
|
|
@ -1,91 +1,102 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
# Administration
|
||||
{
|
||||
"module_name": "Desk",
|
||||
"category": "Administration",
|
||||
"label": _("Tools"),
|
||||
"color": "#FFF5A7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-calendar",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"module_name": "File Manager",
|
||||
"color": "#AA784D",
|
||||
"doctype": "File",
|
||||
"icon": "octicon octicon-file-directory",
|
||||
"label": _("File Manager"),
|
||||
"link": "List/File",
|
||||
"type": "list",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"module_name": "Website",
|
||||
"color": "#16a085",
|
||||
"icon": "octicon octicon-globe",
|
||||
"type": "module",
|
||||
"hidden": 1
|
||||
"description": "Todos, notes, calendar and newsletter."
|
||||
},
|
||||
{
|
||||
"module_name": "Integrations",
|
||||
"color": "#16a085",
|
||||
"icon": "octicon octicon-globe",
|
||||
"type": "module",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"module_name": "Setup",
|
||||
"module_name": "Settings",
|
||||
"category": "Administration",
|
||||
"label": _("Settings"),
|
||||
"color": "#bdc3c7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-settings",
|
||||
"type": "module",
|
||||
"hidden": 1
|
||||
"description": "Data import, printing, email and workflows."
|
||||
},
|
||||
{
|
||||
"module_name": 'Email Inbox',
|
||||
"type": 'list',
|
||||
"label": 'Email Inbox',
|
||||
"_label": _('Email Inbox'),
|
||||
"_id": 'Email Inbox',
|
||||
"_doctype": 'Communication',
|
||||
"icon": 'fa fa-envelope-o',
|
||||
"color": '#589494',
|
||||
"link": 'List/Communication/Inbox'
|
||||
"module_name": "Users and Permissions",
|
||||
"category": "Administration",
|
||||
"label": _("Users and Permissions"),
|
||||
"color": "#bdc3c7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-settings",
|
||||
"type": "module",
|
||||
"description": "Setup roles and permissions for users on documents."
|
||||
},
|
||||
{
|
||||
"module_name": "Customization",
|
||||
"category": "Administration",
|
||||
"label": _("Customization"),
|
||||
"color": "#bdc3c7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-settings",
|
||||
"type": "module",
|
||||
"description": "Customize forms, custom fields, scripts and translations."
|
||||
},
|
||||
{
|
||||
"module_name": "Integrations",
|
||||
"category": "Administration",
|
||||
"label": _("Integrations"),
|
||||
"color": "#16a085",
|
||||
"icon": "octicon octicon-globe",
|
||||
"type": "module",
|
||||
"description": "DropBox, Woocomerce, AWS, Shopify and GoCardless."
|
||||
},
|
||||
{
|
||||
"module_name": 'Contacts',
|
||||
"category": "Administration",
|
||||
"label": _("Contacts"),
|
||||
"type": 'module',
|
||||
"icon": "octicon octicon-book",
|
||||
"color": '#ffaedb',
|
||||
"description": "People Contacts and Address Book."
|
||||
},
|
||||
{
|
||||
"module_name": "Core",
|
||||
"label": "Developer",
|
||||
"category": "Administration",
|
||||
"_label": _("Developer"),
|
||||
"label": "Developer",
|
||||
"color": "#589494",
|
||||
"icon": "octicon octicon-circuit-board",
|
||||
"type": "module",
|
||||
"system_manager": 1,
|
||||
"hidden": 1
|
||||
"condition": getattr(frappe.local.conf, 'developer_mode', 0),
|
||||
"description": "Doctypes, dev tools and logs."
|
||||
},
|
||||
|
||||
# Places
|
||||
{
|
||||
"module_name": 'Contacts',
|
||||
"type": 'module',
|
||||
"icon": "octicon octicon-book",
|
||||
"color": '#FFAEDB',
|
||||
"hidden": 1,
|
||||
"module_name": "Website",
|
||||
"category": "Places",
|
||||
"label": _("Website"),
|
||||
"_label": _("Website"),
|
||||
"color": "#16a085",
|
||||
"icon": "octicon octicon-globe",
|
||||
"type": "module",
|
||||
"description": "Webpages, webforms, blogs and website theme."
|
||||
},
|
||||
{
|
||||
"module_name": 'Social',
|
||||
"category": "Places",
|
||||
"label": _('Social'),
|
||||
"icon": "octicon octicon-heart",
|
||||
"type": 'link',
|
||||
"link": 'social/home',
|
||||
"link": '#social/home',
|
||||
"color": '#FF4136',
|
||||
'standard': 1,
|
||||
'idx': 15
|
||||
'idx': 15,
|
||||
"description": "Build your profile and share posts with other users."
|
||||
},
|
||||
{
|
||||
"module_name": 'Settings',
|
||||
"color": "#bdc3c7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-settings",
|
||||
"type": "module"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,51 +1,181 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
from frappe.desk.moduleview import add_setup_section
|
||||
|
||||
def get_data():
|
||||
return [{
|
||||
"label": _("Settings"),
|
||||
"icon": "fa fa-wrench",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "System Settings",
|
||||
"label": _("System Settings"),
|
||||
"description": _("Language, Date and Time settings"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Domain Settings",
|
||||
"label": _("Domain Settings"),
|
||||
"description": _("Enable / Disable Domains"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Settings",
|
||||
"label": _("Print Settings"),
|
||||
"description": _("Print Style, PDF Size"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Settings",
|
||||
"label": _("Website Settings"),
|
||||
"description": _("Landing Page, Website Theme, Brand Setup and more"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "S3 Backup Settings",
|
||||
"label": _("S3 Backup Settings"),
|
||||
"description": _("Enable / Disable Backup, Backup Frequency"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "SMS Settings",
|
||||
"label": _("SMS Settings"),
|
||||
"description": _("SMS Gateway URL, Message & Receiver Parameter"),
|
||||
"hide_count": True
|
||||
}
|
||||
]
|
||||
}]
|
||||
data = [
|
||||
{
|
||||
"label": _("Core"),
|
||||
"icon": "fa fa-wrench",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "System Settings",
|
||||
"label": _("System Settings"),
|
||||
"description": _("Language, Date and Time settings"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Error Log",
|
||||
"description": _("Log of error on automated events (scheduler).")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Error Snapshot",
|
||||
"description": _("Log of error during requests.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Domain Settings",
|
||||
"label": _("Domain Settings"),
|
||||
"description": _("Enable / Disable Domains"),
|
||||
"hide_count": True
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Data"),
|
||||
"icon": "fa fa-th",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Data Import",
|
||||
"label": _("Import Data"),
|
||||
"icon": "octicon octicon-cloud-upload",
|
||||
"description": _("Import Data from CSV / Excel files.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Data Export",
|
||||
"label": _("Export Data"),
|
||||
"icon": "octicon octicon-cloud-upload",
|
||||
"description": _("Export Data in CSV / Excel format.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Naming Series",
|
||||
"description": _("Set numbering series for transactions."),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Rename Tool",
|
||||
"label": _("Bulk Rename"),
|
||||
"description": _("Rename many items by uploading a .csv file."),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Bulk Update",
|
||||
"label": _("Bulk Update"),
|
||||
"description": _("Update many values at one time."),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"name": "backups",
|
||||
"label": _("Download Backups"),
|
||||
"description": _("List of backups available for download"),
|
||||
"icon": "fa fa-download"
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Deleted Document",
|
||||
"label": _("Deleted Documents"),
|
||||
"description": _("Restore or permanently delete a document.")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Email"),
|
||||
"icon": "fa fa-envelope",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Account",
|
||||
"description": _("Add / Manage Email Accounts.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Domain",
|
||||
"description": _("Add / Manage Email Domains.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Notification",
|
||||
"description": _("Setup Notifications based on various criteria.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Template",
|
||||
"description": _("Email Templates for common queries.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Auto Email Report",
|
||||
"description": _("Setup Reports to be emailed at regular intervals"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Newsletter",
|
||||
"description": _("Create and manage newsletter")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Printing"),
|
||||
"icon": "fa fa-print",
|
||||
"items": [
|
||||
{
|
||||
"type": "page",
|
||||
"label": _("Print Format Builder"),
|
||||
"name": "print-format-builder",
|
||||
"description": _("Drag and Drop tool to build and customize Print Formats.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Settings",
|
||||
"description": _("Set default format, page size, print style etc.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Format",
|
||||
"description": _("Customized HTML Templates for printing transactions.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Style",
|
||||
"description": _("Stylesheets for Print Formats")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Workflow"),
|
||||
"icon": "fa fa-random",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Workflow",
|
||||
"description": _("Define workflows for forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Workflow State",
|
||||
"description": _("States for workflow (e.g. Draft, Approved, Cancelled).")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Workflow Action",
|
||||
"description": _("Actions for workflow (e.g. Approve, Cancel).")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Assignment Rule",
|
||||
"description": _("Set up rules for user assignments.")
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
add_setup_section(data, "frappe", "website", _("Website"), "fa fa-globe")
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -1,289 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
from frappe.desk.moduleview import add_setup_section
|
||||
|
||||
def get_data():
|
||||
data = [
|
||||
{
|
||||
"label": _("Users"),
|
||||
"icon": "fa fa-group",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "User",
|
||||
"description": _("System and Website Users")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Role",
|
||||
"description": _("User Roles")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Role Profile",
|
||||
"description": _("Role Profile")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Permissions"),
|
||||
"icon": "fa fa-lock",
|
||||
"items": [
|
||||
{
|
||||
"type": "page",
|
||||
"name": "permission-manager",
|
||||
"label": _("Role Permissions Manager"),
|
||||
"icon": "fa fa-lock",
|
||||
"description": _("Set Permissions on Document Types and Roles")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "User Permission",
|
||||
"label": _("User Permissions"),
|
||||
"icon": "fa fa-lock",
|
||||
"description": _("Restrict user for specific document")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Role Permission for Page and Report",
|
||||
"description": _("Set custom roles for page and report")
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"doctype": "User",
|
||||
"icon": "fa fa-eye-open",
|
||||
"name": "Permitted Documents For User",
|
||||
"description": _("Check which Documents are readable by a User")
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"doctype": "DocShare",
|
||||
"icon": "fa fa-share",
|
||||
"name": "Document Share Report",
|
||||
"description": _("Report of all document shares")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Settings"),
|
||||
"icon": "fa fa-wrench",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "System Settings",
|
||||
"label": _("System Settings"),
|
||||
"description": _("Language, Date and Time settings"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Error Log",
|
||||
"description": _("Log of error on automated events (scheduler).")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Error Snapshot",
|
||||
"description": _("Log of error during requests.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Domain Settings",
|
||||
"label": _("Domain Settings"),
|
||||
"description": _("Enable / Disable Domains"),
|
||||
"hide_count": True
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Data"),
|
||||
"icon": "fa fa-th",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Data Import",
|
||||
"label": _("Import Data"),
|
||||
"icon": "octicon octicon-cloud-upload",
|
||||
"description": _("Import Data from CSV / Excel files.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Data Export",
|
||||
"label": _("Export Data"),
|
||||
"icon": "octicon octicon-cloud-upload",
|
||||
"description": _("Export Data in CSV / Excel format.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Naming Series",
|
||||
"description": _("Set numbering series for transactions."),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Rename Tool",
|
||||
"label": _("Bulk Rename"),
|
||||
"description": _("Rename many items by uploading a .csv file."),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Bulk Update",
|
||||
"label": _("Bulk Update"),
|
||||
"description": _("Update many values at one time."),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"name": "backups",
|
||||
"label": _("Download Backups"),
|
||||
"description": _("List of backups available for download"),
|
||||
"icon": "fa fa-download"
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Deleted Document",
|
||||
"label": _("Deleted Documents"),
|
||||
"description": _("Restore or permanently delete a document.")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Email"),
|
||||
"icon": "fa fa-envelope",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Account",
|
||||
"description": _("Add / Manage Email Accounts.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Domain",
|
||||
"description": _("Add / Manage Email Domains.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Notification",
|
||||
"description": _("Setup Notifications based on various criteria.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Template",
|
||||
"description": _("Email Templates for common queries.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Auto Email Report",
|
||||
"description": _("Setup Reports to be emailed at regular intervals"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Newsletter",
|
||||
"description": _("Create and manage newsletter")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Printing"),
|
||||
"icon": "fa fa-print",
|
||||
"items": [
|
||||
{
|
||||
"type": "page",
|
||||
"label": _("Print Format Builder"),
|
||||
"name": "print-format-builder",
|
||||
"description": _("Drag and Drop tool to build and customize Print Formats.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Settings",
|
||||
"description": _("Set default format, page size, print style etc.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Format",
|
||||
"description": _("Customized HTML Templates for printing transactions.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Style",
|
||||
"description": _("Stylesheets for Print Formats")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Workflow"),
|
||||
"icon": "fa fa-random",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Workflow",
|
||||
"description": _("Define workflows for forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Workflow State",
|
||||
"description": _("States for workflow (e.g. Draft, Approved, Cancelled).")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Workflow Action",
|
||||
"description": _("Actions for workflow (e.g. Approve, Cancel).")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Customize"),
|
||||
"icon": "fa fa-glass",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Customize Form",
|
||||
"description": _("Change field properties (hide, readonly, permission etc.)"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Custom Field",
|
||||
"description": _("Add fields to forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"label": _("Custom Translations"),
|
||||
"name": "Translation",
|
||||
"description": _("Add your own translations")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Custom Script",
|
||||
"description": _("Add custom javascript to forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "DocType",
|
||||
"description": _("Add custom forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"label": _("Custom Tags"),
|
||||
"name": "Tag Category",
|
||||
"description": _("Add your own Tag Categories")
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Applications"),
|
||||
"items":[
|
||||
{
|
||||
"type": "page",
|
||||
"name": "applications",
|
||||
"label": _("Application Installer"),
|
||||
"description": _("Install Applications."),
|
||||
"icon": "fa fa-download"
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
add_setup_section(data, "frappe", "website", _("Website"), "fa fa-globe")
|
||||
return data
|
||||
67
frappe/config/users_and_permissions.py
Normal file
67
frappe/config/users_and_permissions.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
{
|
||||
"label": _("Users"),
|
||||
"icon": "fa fa-group",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "User",
|
||||
"description": _("System and Website Users")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Role",
|
||||
"description": _("User Roles")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Role Profile",
|
||||
"description": _("Role Profile")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Permissions"),
|
||||
"icon": "fa fa-lock",
|
||||
"items": [
|
||||
{
|
||||
"type": "page",
|
||||
"name": "permission-manager",
|
||||
"label": _("Role Permissions Manager"),
|
||||
"icon": "fa fa-lock",
|
||||
"description": _("Set Permissions on Document Types and Roles")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "User Permission",
|
||||
"label": _("User Permissions"),
|
||||
"icon": "fa fa-lock",
|
||||
"description": _("Restrict user for specific document")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Role Permission for Page and Report",
|
||||
"description": _("Set custom roles for page and report")
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"doctype": "User",
|
||||
"icon": "fa fa-eye-open",
|
||||
"name": "Permitted Documents For User",
|
||||
"description": _("Check which Documents are readable by a User")
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"doctype": "DocShare",
|
||||
"icon": "fa fa-share",
|
||||
"name": "Document Share Report",
|
||||
"description": _("Report of all document shares")
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
|
|
@ -11,11 +11,13 @@ def get_data():
|
|||
"type": "doctype",
|
||||
"name": "Web Page",
|
||||
"description": _("Content web page."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Web Form",
|
||||
"description": _("User editable form on Website."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
|
|
@ -26,6 +28,11 @@ def get_data():
|
|||
"name": "Website Slideshow",
|
||||
"description": _("Embed image slideshows in website pages."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Route Meta",
|
||||
"description": _("Add meta tags to your web pages"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -35,6 +42,7 @@ def get_data():
|
|||
"type": "doctype",
|
||||
"name": "Blog Post",
|
||||
"description": _("Single Post (article)."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
|
|
@ -56,11 +64,13 @@ def get_data():
|
|||
"type": "doctype",
|
||||
"name": "Website Settings",
|
||||
"description": _("Setup of top navigation bar, footer and logo."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Theme",
|
||||
"description": _("List of themes for Website."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
|
|
@ -86,6 +96,7 @@ def get_data():
|
|||
"type": "doctype",
|
||||
"name": "Portal Settings",
|
||||
"label": _("Portal Settings"),
|
||||
"onboard": 1,
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -101,12 +101,13 @@ def get_permitted_and_not_permitted_links(doctype):
|
|||
not_permitted_links = []
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
allowed_doctypes = frappe.permissions.get_doctypes_with_read()
|
||||
|
||||
for df in meta.get_link_fields():
|
||||
if df.options not in ("Customer", "Supplier", "Company", "Sales Partner"):
|
||||
continue
|
||||
|
||||
if frappe.has_permission(df.options):
|
||||
if df.options in allowed_doctypes:
|
||||
permitted_links.append(df)
|
||||
else:
|
||||
not_permitted_links.append(df)
|
||||
|
|
@ -145,10 +146,9 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil
|
|||
_doctypes = tuple([d for d in _doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)])
|
||||
|
||||
all_doctypes = [d[0] for d in doctypes + _doctypes]
|
||||
valid_doctypes = []
|
||||
allowed_doctypes = frappe.permissions.get_doctypes_with_read()
|
||||
|
||||
for doctype in all_doctypes:
|
||||
if frappe.has_permission(doctype):
|
||||
valid_doctypes.append([doctype])
|
||||
valid_doctypes = sorted(set(all_doctypes).intersection(set(allowed_doctypes)))
|
||||
valid_doctypes = [[doctype] for doctype in valid_doctypes]
|
||||
|
||||
return sorted(valid_doctypes)
|
||||
return valid_doctypes
|
||||
|
|
|
|||
|
|
@ -56,10 +56,13 @@ def logout_feed(user, reason):
|
|||
subject = _("{0} logged out: {1}").format(get_fullname(user), frappe.bold(reason))
|
||||
add_authentication_log(subject, user, operation="Logout")
|
||||
|
||||
def get_feed_match_conditions(user=None, force=True):
|
||||
def get_feed_match_conditions(user=None, doctype='Comment'):
|
||||
if not user: user = frappe.session.user
|
||||
|
||||
conditions = ['`tabCommunication`.owner={user} or `tabCommunication`.reference_owner={user}'.format(user=frappe.db.escape(user))]
|
||||
conditions = ['`tab{doctype}`.owner={user} or `tab{doctype}`.reference_owner={user}'.format(
|
||||
user = frappe.db.escape(user),
|
||||
doctype = doctype
|
||||
)]
|
||||
|
||||
user_permissions = frappe.permissions.get_user_permissions(user)
|
||||
can_read = frappe.get_user().get_can_read()
|
||||
|
|
@ -68,9 +71,13 @@ def get_feed_match_conditions(user=None, force=True):
|
|||
list(set(can_read) - set(list(user_permissions)))]
|
||||
|
||||
if can_read_doctypes:
|
||||
conditions += ["""(`tabCommunication`.reference_doctype is null
|
||||
or `tabCommunication`.reference_doctype = ''
|
||||
or `tabCommunication`.reference_doctype in ({}))""".format(", ".join(can_read_doctypes))]
|
||||
conditions += ["""(`tab{doctype}`.reference_doctype is null
|
||||
or `tab{doctype}`.reference_doctype = ''
|
||||
or `tab{doctype}`.reference_doctype
|
||||
in ({values}))""".format(
|
||||
doctype = doctype,
|
||||
values =", ".join(can_read_doctypes)
|
||||
)]
|
||||
|
||||
if user_permissions:
|
||||
can_read_docs = []
|
||||
|
|
@ -79,7 +86,8 @@ def get_feed_match_conditions(user=None, force=True):
|
|||
can_read_docs.append('{}|{}'.format(doctype, frappe.db.escape(n.get('doc', ''))))
|
||||
|
||||
if can_read_docs:
|
||||
conditions.append("concat_ws('|', `tabCommunication`.reference_doctype, `tabCommunication`.reference_name) in ({})".format(
|
||||
", ".join(can_read_docs)))
|
||||
conditions.append("concat_ws('|', `tab{doctype}`.reference_doctype, `tab{doctype}`.reference_name) in ({values})".format(
|
||||
doctype = doctype,
|
||||
values = ", ".join(can_read_docs)))
|
||||
|
||||
return "(" + " or ".join(conditions) + ")"
|
||||
return "(" + " or ".join(conditions) + ")"
|
||||
|
|
|
|||
0
frappe/core/doctype/comment/__init__.py
Normal file
0
frappe/core/doctype/comment/__init__.py
Normal file
8
frappe/core/doctype/comment/comment.js
Normal file
8
frappe/core/doctype/comment/comment.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Comment', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
535
frappe/core/doctype/comment/comment.json
Normal file
535
frappe/core/doctype/comment/comment.json
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2019-02-07 10:10:46.845678",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Comment",
|
||||
"fieldname": "comment_type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Comment Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Comment\nLike\nInfo\nLabel\nWorkflow\nCreated\nSubmitted\nCancelled\nUpdated\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nBot\nRelinked\nEdit",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "comment_email",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Comment Email",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Subject",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "comment_by",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Comment By",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "published",
|
||||
"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": "Published",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "seen",
|
||||
"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": "Seen",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_5",
|
||||
"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,
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Reference Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"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": "Reference Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "reference_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "link_doctype",
|
||||
"fieldtype": "Link",
|
||||
"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": "Link DocType",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "link_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"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": "Link Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "link_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_owner",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Reference Owner",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "content",
|
||||
"fieldtype": "HTML Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Content",
|
||||
"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
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-08 09:18:33.843171",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Comment",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Website Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "comment_type",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
184
frappe/core/doctype/comment/comment.py
Normal file
184
frappe/core/doctype/comment/comment.py
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
import frappe
|
||||
from frappe import _
|
||||
import json
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.user.user import extract_mentions
|
||||
from frappe.utils import get_fullname, get_link_to_form
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.database.schema import add_column
|
||||
from frappe.exceptions import ImplicitCommitError
|
||||
|
||||
class Comment(Document):
|
||||
def after_insert(self):
|
||||
self.notify_mentions()
|
||||
|
||||
frappe.publish_realtime('new_communication', self.as_dict(),
|
||||
doctype=self.reference_doctype, docname=self.reference_name,
|
||||
after_commit=True)
|
||||
|
||||
def validate(self):
|
||||
if not self.comment_email:
|
||||
self.comment_email = frappe.session.user
|
||||
|
||||
def on_update(self):
|
||||
update_comment_in_doc(self)
|
||||
|
||||
def on_trash(self):
|
||||
self.remove_comment_from_cache()
|
||||
frappe.publish_realtime('delete_communication', self.as_dict(),
|
||||
doctype= self.reference_doctype, docname = self.reference_name,
|
||||
after_commit=True)
|
||||
|
||||
def remove_comment_from_cache(self):
|
||||
_comments = get_comments_from_parent(self)
|
||||
for c in _comments:
|
||||
if c.get("name")==self.name:
|
||||
_comments.remove(c)
|
||||
|
||||
update_comments_in_parent(self.reference_doctype, self.reference_name, _comments)
|
||||
|
||||
def notify_mentions(self):
|
||||
if self.reference_doctype and self.reference_name and self.content:
|
||||
mentions = extract_mentions(self.content)
|
||||
|
||||
if not mentions:
|
||||
return
|
||||
|
||||
sender_fullname = get_fullname(frappe.session.user)
|
||||
title_field = frappe.get_meta(self.reference_doctype).get_title_field()
|
||||
title = self.reference_name if title_field == "name" else \
|
||||
frappe.db.get_value(self.reference_doctype, self.reference_name, title_field)
|
||||
|
||||
if title != self.reference_name:
|
||||
parent_doc_label = "{0}: {1} (#{2})".format(_(self.reference_doctype),
|
||||
title, self.reference_name)
|
||||
else:
|
||||
parent_doc_label = "{0}: {1}".format(_(self.reference_doctype),
|
||||
self.reference_name)
|
||||
|
||||
subject = _("{0} mentioned you in a comment in {1}").format(sender_fullname, parent_doc_label)
|
||||
|
||||
recipients = [frappe.db.get_value("User", {"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1}, "email")
|
||||
for name in mentions]
|
||||
link = get_link_to_form(self.reference_doctype, self.reference_name, label=parent_doc_label)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients = recipients,
|
||||
sender = frappe.session.user,
|
||||
subject = subject,
|
||||
template = "mentioned_in_comment",
|
||||
args = {
|
||||
"body_content": _("{0} mentioned you in a comment in {1}").format(sender_fullname, link),
|
||||
"comment": self,
|
||||
"link": link
|
||||
},
|
||||
header = [_('New Mention'), 'orange']
|
||||
)
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Comment", ["reference_doctype", "reference_name"])
|
||||
frappe.db.add_index("Comment", ["link_doctype", "link_name"])
|
||||
|
||||
|
||||
def update_comment_in_doc(doc):
|
||||
"""Updates `_comments` (JSON) property in parent Document.
|
||||
Creates a column `_comments` if property does not exist.
|
||||
|
||||
Only user created Communication or Comment of type Comment are saved.
|
||||
|
||||
`_comments` format
|
||||
|
||||
{
|
||||
"comment": [String],
|
||||
"by": [user],
|
||||
"name": [Comment Document name]
|
||||
}"""
|
||||
|
||||
# only comments get updates, not likes, assignments etc.
|
||||
if doc.doctype == 'Comment' and doc.comment_type != 'Comment':
|
||||
return
|
||||
|
||||
def get_truncated(content):
|
||||
return (content[:97] + '...') if len(content) > 100 else content
|
||||
|
||||
if doc.reference_doctype and doc.reference_name and doc.content:
|
||||
_comments = get_comments_from_parent(doc)
|
||||
|
||||
updated = False
|
||||
for c in _comments:
|
||||
if c.get("name")==doc.name:
|
||||
c["comment"] = get_truncated(doc.content)
|
||||
updated = True
|
||||
|
||||
if not updated:
|
||||
_comments.append({
|
||||
"comment": get_truncated(doc.content),
|
||||
|
||||
# "comment_email" for Comment and "sender" for Communication
|
||||
"by": getattr(doc, 'comment_email', None) or getattr(doc, 'sender', None) or doc.owner,
|
||||
"name": doc.name
|
||||
})
|
||||
|
||||
update_comments_in_parent(doc.reference_doctype, doc.reference_name, _comments)
|
||||
|
||||
|
||||
def get_comments_from_parent(doc):
|
||||
'''
|
||||
get the list of comments cached in the document record in the column
|
||||
`_comments`
|
||||
'''
|
||||
try:
|
||||
_comments = frappe.db.get_value(doc.reference_doctype, doc.reference_name, "_comments") or "[]"
|
||||
|
||||
except Exception as e:
|
||||
if frappe.db.is_missing_table_or_column(e):
|
||||
_comments = "[]"
|
||||
|
||||
else:
|
||||
raise
|
||||
|
||||
try:
|
||||
return json.loads(_comments)
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
def update_comments_in_parent(reference_doctype, reference_name, _comments):
|
||||
"""Updates `_comments` property in parent Document with given dict.
|
||||
|
||||
:param _comments: Dict of comments."""
|
||||
if not reference_doctype or frappe.db.get_value("DocType", reference_doctype, "issingle"):
|
||||
return
|
||||
|
||||
try:
|
||||
# use sql, so that we do not mess with the timestamp
|
||||
frappe.db.sql("""update `tab{0}` set `_comments`=%s where name=%s""".format(reference_doctype), # nosec
|
||||
(json.dumps(_comments[-50:]), reference_name))
|
||||
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e) and getattr(frappe.local, 'request', None):
|
||||
# missing column and in request, add column and update after commit
|
||||
frappe.local._comments = (getattr(frappe.local, "_comments", [])
|
||||
+ [(reference_doctype, reference_name, _comments)])
|
||||
else:
|
||||
raise ImplicitCommitError
|
||||
|
||||
else:
|
||||
if not frappe.flags.in_patch:
|
||||
reference_doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
if getattr(reference_doc, "route", None):
|
||||
clear_cache(reference_doc.route)
|
||||
|
||||
def update_comments_in_parent_after_request():
|
||||
"""update _comments in parent if _comments column is missing"""
|
||||
if hasattr(frappe.local, "_comments"):
|
||||
for (reference_doctype, reference_name, _comments) in frappe.local._comments:
|
||||
add_column(reference_doctype, "_comments", "Text")
|
||||
update_comments_in_parent(reference_doctype, reference_name, _comments)
|
||||
|
||||
frappe.db.commit()
|
||||
57
frappe/core/doctype/comment/test_comment.py
Normal file
57
frappe/core/doctype/comment/test_comment.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, json
|
||||
import unittest
|
||||
|
||||
class TestComment(unittest.TestCase):
|
||||
def test_comment_creation(self):
|
||||
test_doc = frappe.get_doc(dict(doctype = 'ToDo', description = 'test'))
|
||||
test_doc.insert()
|
||||
comment = test_doc.add_comment('Comment', 'test comment')
|
||||
|
||||
test_doc.reload()
|
||||
|
||||
# check if updated in _comments cache
|
||||
comments = json.loads(test_doc.get('_comments'))
|
||||
self.assertEqual(comments[0].get('name'), comment.name)
|
||||
self.assertEqual(comments[0].get('comment'), comment.content)
|
||||
|
||||
# check document creation
|
||||
comment_1 = frappe.get_all('Comment', fields = ['*'], filters = dict(
|
||||
reference_doctype = test_doc.doctype,
|
||||
reference_name = test_doc.name
|
||||
))[0]
|
||||
|
||||
self.assertEqual(comment_1.content, 'test comment')
|
||||
|
||||
# test via blog
|
||||
def test_public_comment(self):
|
||||
from frappe.website.doctype.blog_post.test_blog_post import make_test_blog
|
||||
test_blog = make_test_blog()
|
||||
|
||||
frappe.db.sql("delete from `tabComment` where reference_doctype = 'Blog Post'")
|
||||
|
||||
from frappe.templates.includes.comments.comments import add_comment
|
||||
add_comment('hello', 'test@test.com', 'Good Tester',
|
||||
'Blog Post', test_blog.name, test_blog.route)
|
||||
|
||||
self.assertEqual(frappe.get_all('Comment', fields = ['*'], filters = dict(
|
||||
reference_doctype = test_blog.doctype,
|
||||
reference_name = test_blog.name
|
||||
))[0].published, 1)
|
||||
|
||||
frappe.db.sql("delete from `tabComment` where reference_doctype = 'Blog Post'")
|
||||
|
||||
add_comment('pleez vizits my site http://mysite.com', 'test@test.com', 'bad commentor',
|
||||
'Blog Post', test_blog.name, test_blog.route)
|
||||
|
||||
self.assertEqual(frappe.get_all('Comment', fields = ['*'], filters = dict(
|
||||
reference_doctype = test_blog.doctype,
|
||||
reference_name = test_blog.name
|
||||
))[0].published, 0)
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
import frappe
|
||||
from frappe import _
|
||||
import json
|
||||
from frappe.core.doctype.user.user import extract_mentions
|
||||
from frappe.utils import get_fullname, get_link_to_form
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.database.schema import add_column
|
||||
from frappe.exceptions import ImplicitCommitError
|
||||
|
||||
def on_trash(doc):
|
||||
if doc.communication_type != "Comment":
|
||||
return
|
||||
|
||||
if doc.reference_doctype == "Message":
|
||||
return
|
||||
|
||||
if (doc.comment_type or "Comment") != "Comment":
|
||||
frappe.only_for("System Manager")
|
||||
|
||||
_comments = get_comments_from_parent(doc)
|
||||
for c in _comments:
|
||||
if c.get("name")==doc.name:
|
||||
_comments.remove(c)
|
||||
|
||||
update_comments_in_parent(doc.reference_doctype, doc.reference_name, _comments)
|
||||
|
||||
def update_comment_in_doc(doc):
|
||||
"""Updates `_comments` (JSON) property in parent Document.
|
||||
Creates a column `_comments` if property does not exist.
|
||||
|
||||
Only user created comments Communication or Comment of type Comment are saved.
|
||||
|
||||
`_comments` format
|
||||
|
||||
{
|
||||
"comment": [String],
|
||||
"by": [user],
|
||||
"name": [Comment Document name]
|
||||
}"""
|
||||
|
||||
if doc.communication_type not in ("Comment", "Communication"):
|
||||
return
|
||||
|
||||
if doc.communication_type == 'Comment' and doc.comment_type != 'Comment':
|
||||
# other updates
|
||||
return
|
||||
|
||||
def get_content(doc):
|
||||
return (doc.content[:97] + '...') if len(doc.content) > 100 else doc.content
|
||||
|
||||
if doc.reference_doctype and doc.reference_name and doc.content:
|
||||
_comments = get_comments_from_parent(doc)
|
||||
|
||||
updated = False
|
||||
for c in _comments:
|
||||
if c.get("name")==doc.name:
|
||||
c["comment"] = get_content(doc)
|
||||
updated = True
|
||||
|
||||
if not updated:
|
||||
_comments.append({
|
||||
"comment": get_content(doc),
|
||||
"by": doc.sender or doc.owner,
|
||||
"name": doc.name
|
||||
})
|
||||
|
||||
update_comments_in_parent(doc.reference_doctype, doc.reference_name, _comments)
|
||||
|
||||
def notify_mentions(doc):
|
||||
if doc.communication_type != "Comment":
|
||||
return
|
||||
|
||||
if doc.reference_doctype and doc.reference_name and doc.content and doc.comment_type=="Comment":
|
||||
mentions = extract_mentions(doc.content)
|
||||
|
||||
if not mentions:
|
||||
return
|
||||
|
||||
sender_fullname = get_fullname(frappe.session.user)
|
||||
title_field = frappe.get_meta(doc.reference_doctype).get_title_field()
|
||||
title = doc.reference_name if title_field == "name" else \
|
||||
frappe.db.get_value(doc.reference_doctype, doc.reference_name, title_field)
|
||||
|
||||
if title != doc.reference_name:
|
||||
parent_doc_label = "{0}: {1} (#{2})".format(_(doc.reference_doctype),
|
||||
title, doc.reference_name)
|
||||
else:
|
||||
parent_doc_label = "{0}: {1}".format(_(doc.reference_doctype),
|
||||
doc.reference_name)
|
||||
|
||||
subject = _("{0} mentioned you in a comment in {1}").format(sender_fullname, parent_doc_label)
|
||||
|
||||
recipients = [frappe.db.get_value("User", {"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1}, "email")
|
||||
for name in mentions]
|
||||
link = get_link_to_form(doc.reference_doctype, doc.reference_name, label=parent_doc_label)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=recipients,
|
||||
sender=frappe.session.user,
|
||||
subject=subject,
|
||||
template="mentioned_in_comment",
|
||||
args={
|
||||
"body_content": _("{0} mentioned you in a comment in {1}").format(sender_fullname, link),
|
||||
"comment": doc,
|
||||
"link": link
|
||||
},
|
||||
header=[_('New Mention'), 'orange']
|
||||
)
|
||||
|
||||
def get_comments_from_parent(doc):
|
||||
try:
|
||||
_comments = frappe.db.get_value(doc.reference_doctype, doc.reference_name, "_comments") or "[]"
|
||||
|
||||
except Exception as e:
|
||||
if frappe.db.is_missing_table_or_column(e):
|
||||
_comments = "[]"
|
||||
|
||||
else:
|
||||
raise
|
||||
|
||||
try:
|
||||
return json.loads(_comments)
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
def update_comments_in_parent(reference_doctype, reference_name, _comments):
|
||||
"""Updates `_comments` property in parent Document with given dict.
|
||||
|
||||
:param _comments: Dict of comments."""
|
||||
if not reference_doctype or frappe.db.get_value("DocType", reference_doctype, "issingle"):
|
||||
return
|
||||
|
||||
try:
|
||||
# use sql, so that we do not mess with the timestamp
|
||||
frappe.db.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (reference_doctype,
|
||||
"%s", "%s"), (json.dumps(_comments), reference_name))
|
||||
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e) and getattr(frappe.local, 'request', None):
|
||||
# missing column and in request, add column and update after commit
|
||||
frappe.local._comments = (getattr(frappe.local, "_comments", [])
|
||||
+ [(reference_doctype, reference_name, _comments)])
|
||||
else:
|
||||
raise ImplicitCommitError
|
||||
|
||||
else:
|
||||
if not frappe.flags.in_patch:
|
||||
reference_doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
if getattr(reference_doc, "route", None):
|
||||
clear_cache(reference_doc.route)
|
||||
|
||||
def add_info_comment(**kwargs):
|
||||
kwargs.update({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Comment",
|
||||
"comment_type": "Info",
|
||||
"status": "Closed"
|
||||
})
|
||||
return frappe.get_doc(kwargs).insert(ignore_permissions=True)
|
||||
|
||||
def update_comments_in_parent_after_request():
|
||||
"""update _comments in parent if _comments column is missing"""
|
||||
if hasattr(frappe.local, "_comments"):
|
||||
for (reference_doctype, reference_name, _comments) in frappe.local._comments:
|
||||
add_column(reference_doctype, "_comments", "Text")
|
||||
update_comments_in_parent(reference_doctype, reference_name, _comments)
|
||||
|
||||
frappe.db.commit()
|
||||
|
|
@ -6,13 +6,12 @@ import frappe
|
|||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import validate_email_add, get_fullname, strip_html, cstr
|
||||
from frappe.core.doctype.communication.comment import (notify_mentions,
|
||||
update_comment_in_doc, on_trash)
|
||||
from frappe.core.doctype.communication.email import (validate_email,
|
||||
notify, _notify, update_parent_mins_to_first_response)
|
||||
from frappe.core.utils import get_parent_doc, set_timeline_doc
|
||||
from frappe.utils.bot import BotReply
|
||||
from frappe.utils import parse_addr
|
||||
from frappe.core.doctype.comment.comment import update_comment_in_doc
|
||||
|
||||
from collections import Counter
|
||||
|
||||
|
|
@ -87,19 +86,15 @@ class Communication(Document):
|
|||
if not (self.reference_doctype and self.reference_name):
|
||||
return
|
||||
|
||||
if self.reference_doctype == "Communication" and self.sent_or_received == "Sent" and \
|
||||
self.communication_type != 'Comment':
|
||||
if self.reference_doctype == "Communication" and self.sent_or_received == "Sent":
|
||||
frappe.db.set_value("Communication", self.reference_name, "status", "Replied")
|
||||
|
||||
if self.communication_type in ("Communication", "Comment"):
|
||||
if self.communication_type == "Communication":
|
||||
# send new comment to listening clients
|
||||
frappe.publish_realtime('new_communication', self.as_dict(),
|
||||
doctype=self.reference_doctype, docname=self.reference_name,
|
||||
after_commit=True)
|
||||
|
||||
if self.communication_type == "Comment":
|
||||
notify_mentions(self)
|
||||
|
||||
elif self.communication_type in ("Chat", "Notification", "Bot"):
|
||||
if self.reference_name == frappe.session.user:
|
||||
message = self.as_dict()
|
||||
|
|
@ -111,26 +106,20 @@ class Communication(Document):
|
|||
user=self.reference_name, after_commit=True)
|
||||
|
||||
def on_update(self):
|
||||
"""Update parent status as `Open` or `Replied`."""
|
||||
# add to _comment property of the doctype, so it shows up in
|
||||
# comments count for the list view
|
||||
update_comment_in_doc(self)
|
||||
|
||||
if self.comment_type != 'Updated':
|
||||
update_parent_mins_to_first_response(self)
|
||||
update_comment_in_doc(self)
|
||||
self.bot_reply()
|
||||
|
||||
def on_trash(self):
|
||||
if (not self.flags.ignore_permissions
|
||||
and self.communication_type=="Comment" and self.comment_type != "Comment"):
|
||||
|
||||
# prevent deletion of auto-created comments if not ignore_permissions
|
||||
frappe.throw(_("Sorry! You cannot delete auto-generated comments"))
|
||||
|
||||
if self.communication_type in ("Communication", "Comment"):
|
||||
if self.communication_type == "Communication":
|
||||
# send delete comment to listening clients
|
||||
frappe.publish_realtime('delete_communication', self.as_dict(),
|
||||
doctype= self.reference_doctype, docname = self.reference_name,
|
||||
after_commit=True)
|
||||
# delete the comments from _comment
|
||||
on_trash(self)
|
||||
|
||||
def set_status(self):
|
||||
if not self.is_new():
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
:param sender: Communcation sender (default current user).
|
||||
:param recipients: Communication recipients as list.
|
||||
:param communication_medium: Medium of communication (default **Email**).
|
||||
:param send_mail: Send via email (default **False**).
|
||||
:param send_email: Send via email (default **False**).
|
||||
:param print_html: HTML Print format to be sent as attachment.
|
||||
:param print_format: Print Format name of parent document to be sent as attachment.
|
||||
:param attachments: List of attachments as list of files or JSON string.
|
||||
|
|
@ -50,6 +50,9 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
|
|||
if not sender:
|
||||
sender = get_formatted_email(frappe.session.user)
|
||||
|
||||
if isinstance(recipients, list):
|
||||
recipients = ', '.join(recipients)
|
||||
|
||||
comm = frappe.get_doc({
|
||||
"doctype":"Communication",
|
||||
"subject": subject,
|
||||
|
|
@ -307,7 +310,8 @@ def set_incoming_outgoing_accounts(doc):
|
|||
|
||||
doc.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": doc.reference_doctype, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender", "name"], as_dict=True)
|
||||
["email_id", "always_use_account_email_id_as_sender", "name",
|
||||
"always_use_account_name_as_sender_name"], as_dict=True)
|
||||
|
||||
if not doc.incoming_email_account:
|
||||
doc.incoming_email_account = frappe.db.get_value("Email Account",
|
||||
|
|
@ -317,12 +321,14 @@ def set_incoming_outgoing_accounts(doc):
|
|||
# if from address is not the default email account
|
||||
doc.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"email_id": doc.sender, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender", "name", "send_unsubscribe_message"], as_dict=True) or frappe._dict()
|
||||
["email_id", "always_use_account_email_id_as_sender", "name",
|
||||
"send_unsubscribe_message", "always_use_account_name_as_sender_name"], as_dict=True) or frappe._dict()
|
||||
|
||||
if not doc.outgoing_email_account:
|
||||
doc.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"default_outgoing": 1, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender", "name", "send_unsubscribe_message"],as_dict=True) or frappe._dict()
|
||||
["email_id", "always_use_account_email_id_as_sender", "name",
|
||||
"send_unsubscribe_message", "always_use_account_name_as_sender_name"],as_dict=True) or frappe._dict()
|
||||
|
||||
if doc.sent_or_received == "Sent":
|
||||
doc.db_set("email_account", doc.outgoing_email_account.name)
|
||||
|
|
@ -494,9 +500,9 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments
|
|||
communication._notify(print_html=print_html, print_format=print_format, attachments=attachments,
|
||||
recipients=recipients, cc=cc, bcc=bcc)
|
||||
|
||||
except frappe.db.InternalError:
|
||||
except frappe.db.InternalError as e:
|
||||
# deadlock, try again
|
||||
if frappe.db.is_deadlocked():
|
||||
if frappe.db.is_deadlocked(e):
|
||||
frappe.db.rollback()
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -3,7 +3,26 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DocField(Document):
|
||||
pass
|
||||
def get_link_doctype(self):
|
||||
'''Returns the Link doctype for the docfield (if applicable)
|
||||
if fieldtype is Link: Returns "options"
|
||||
if fieldtype is Table MultiSelect: Returns "options" of the Link field in the Child Table
|
||||
'''
|
||||
if self.fieldtype == 'Link':
|
||||
return self.options
|
||||
|
||||
if self.fieldtype == 'Table MultiSelect':
|
||||
table_doctype = self.options
|
||||
|
||||
link_doctype = frappe.db.get_value('DocField', {
|
||||
'fieldtype': 'Link',
|
||||
'parenttype': 'DocType',
|
||||
'parent': table_doctype,
|
||||
'in_list_view': 1
|
||||
}, 'options')
|
||||
|
||||
return link_doctype
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class {classname}(Document):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('{doctype}', {{
|
||||
refresh: function(frm) {{
|
||||
// refresh: function(frm) {{
|
||||
|
||||
}}
|
||||
// }}
|
||||
}});
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ class DocType(Document):
|
|||
self.permissions = []
|
||||
|
||||
self.scrub_field_names()
|
||||
self.scrub_options_in_select()
|
||||
self.set_default_in_list_view()
|
||||
self.set_default_translatable()
|
||||
self.validate_series()
|
||||
|
|
@ -202,17 +201,6 @@ class DocType(Document):
|
|||
# unique is automatically an index
|
||||
if d.unique: d.search_index = 0
|
||||
|
||||
def scrub_options_in_select(self):
|
||||
"""Strip options for whitespaces"""
|
||||
for field in self.fields:
|
||||
if field.fieldtype == "Select" and field.options is not None:
|
||||
options_list = []
|
||||
for i, option in enumerate(field.options.split("\n")):
|
||||
_option = option.strip()
|
||||
if i==0 or _option:
|
||||
options_list.append(_option)
|
||||
field.options = '\n'.join(options_list)
|
||||
|
||||
def validate_series(self, autoname=None, name=None):
|
||||
"""Validate if `autoname` property is correctly set."""
|
||||
if not autoname: autoname = self.autoname
|
||||
|
|
@ -705,6 +693,20 @@ def validate_fields(meta):
|
|||
frappe.throw(_('DocType <b>{0}</b> provided for the field <b>{1}</b> must have atleast one Link field')
|
||||
.format(doctype, docfield.fieldname), frappe.ValidationError)
|
||||
|
||||
def scrub_options_in_select(field):
|
||||
"""Strip options for whitespaces"""
|
||||
|
||||
if field.fieldtype == "Select" and field.options is not None:
|
||||
options_list = []
|
||||
for i, option in enumerate(field.options.split("\n")):
|
||||
_option = option.strip()
|
||||
if i==0 or _option:
|
||||
options_list.append(_option)
|
||||
field.options = '\n'.join(options_list)
|
||||
|
||||
def scrub_fetch_from(field):
|
||||
if hasattr(field, 'fetch_from') and getattr(field, 'fetch_from'):
|
||||
field.fetch_from = field.fetch_from.strip('\n').strip()
|
||||
|
||||
fields = meta.get("fields")
|
||||
fieldname_list = [d.fieldname for d in fields]
|
||||
|
|
@ -734,6 +736,8 @@ def validate_fields(meta):
|
|||
check_unique_and_text(d)
|
||||
check_illegal_depends_on_conditions(d)
|
||||
check_table_multiselect_option(d)
|
||||
scrub_options_in_select(d)
|
||||
scrub_fetch_from(d)
|
||||
|
||||
check_fold(fields)
|
||||
check_search_fields(meta, fields)
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ class Domain(Document):
|
|||
self.setup_roles()
|
||||
self.setup_properties()
|
||||
self.set_values()
|
||||
# always set the desktop icons while changing the domain settings
|
||||
self.setup_desktop_icons()
|
||||
|
||||
if not int(frappe.defaults.get_defaults().setup_complete or 0):
|
||||
# if setup not complete, setup desktop etc.
|
||||
self.setup_sidebar_items()
|
||||
|
|
@ -89,12 +88,6 @@ class Domain(Document):
|
|||
frappe.db.set_value('Portal Settings', None, 'default_role',
|
||||
self.data.get('default_portal_role'))
|
||||
|
||||
def setup_desktop_icons(self):
|
||||
'''set desktop icons form `data.desktop_icons`'''
|
||||
from frappe.desk.doctype.desktop_icon.desktop_icon import set_desktop_icons
|
||||
if self.data.desktop_icons:
|
||||
set_desktop_icons(self.data.desktop_icons)
|
||||
|
||||
def setup_properties(self):
|
||||
if self.data.properties:
|
||||
for args in self.data.properties:
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ class File(NestedSet):
|
|||
def set_folder_size(self):
|
||||
"""Set folder size if folder"""
|
||||
if self.is_folder and not self.is_new():
|
||||
self.file_size = frappe.utils.cint(self.get_folder_size())
|
||||
self.file_size = cint(self.get_folder_size())
|
||||
self.db_set('file_size', self.file_size)
|
||||
|
||||
for folder in self.get_ancestors():
|
||||
|
|
@ -176,6 +176,9 @@ class File(NestedSet):
|
|||
"""
|
||||
full_path = self.get_full_path()
|
||||
|
||||
if full_path.startswith('http'):
|
||||
return True
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
frappe.throw(_("File {0} does not exist").format(self.file_url), IOError)
|
||||
|
||||
|
|
@ -231,7 +234,7 @@ class File(NestedSet):
|
|||
else:
|
||||
try:
|
||||
image, filename, extn = get_web_image(self.file_url)
|
||||
except (requests.exceptions.HTTPError, requests.exceptions.SSLError, IOError):
|
||||
except (requests.exceptions.HTTPError, requests.exceptions.SSLError, IOError, TypeError):
|
||||
return
|
||||
|
||||
size = width, height
|
||||
|
|
@ -384,6 +387,9 @@ class File(NestedSet):
|
|||
elif file_path.startswith("/files/"):
|
||||
file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/"))
|
||||
|
||||
elif file_path.startswith("http"):
|
||||
pass
|
||||
|
||||
elif not self.file_url:
|
||||
frappe.throw(_("There is some problem with the file url: {0}").format(file_path))
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,22 @@
|
|||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
import frappe, json, re
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Language(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
validate_with_regex(self.language_code, "Language Code")
|
||||
|
||||
def before_rename(self, old, new, merge=False):
|
||||
validate_with_regex(new, "Name")
|
||||
|
||||
def validate_with_regex(name, label):
|
||||
pattern = re.compile("^[a-zA-Z]+[-_]*[a-zA-Z]+$")
|
||||
if not pattern.match(name):
|
||||
frappe.throw(_("""{0} must begin and end with a letter and can only contain letters,
|
||||
hyphen or underscore.""").format(label))
|
||||
|
||||
def export_languages_json():
|
||||
'''Export list of all languages'''
|
||||
|
|
|
|||
|
|
@ -71,10 +71,6 @@ frappe.ui.form.on('User', {
|
|||
frm.toggle_display(['sb1', 'sb3', 'modules_access'], false);
|
||||
|
||||
if(!frm.is_new()) {
|
||||
frm.add_custom_button(__("Set Desktop Icons"), function() {
|
||||
frappe.frappe_toolbar.modules_select.show(doc.name);
|
||||
}, null, "btn-default")
|
||||
|
||||
if(has_access_to_edit_user()) {
|
||||
|
||||
frm.add_custom_button(__("Set User Permissions"), function() {
|
||||
|
|
|
|||
|
|
@ -1511,7 +1511,7 @@
|
|||
"columns": 0,
|
||||
"default": "",
|
||||
"description": "",
|
||||
"fieldname": "desktop_icon_access",
|
||||
"fieldname": "sb_allow_modules",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
|
|
@ -1520,7 +1520,7 @@
|
|||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Allow Desktop Icon",
|
||||
"label": "Allow Modules",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 1,
|
||||
|
|
@ -1601,6 +1601,39 @@
|
|||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "home_settings",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Home Settings",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
|
|
@ -2303,7 +2336,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 5,
|
||||
"modified": "2018-11-21 12:34:57.652854",
|
||||
"modified": "2019-03-03 11:10:06.162540",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ class User(Document):
|
|||
self.name = self.email
|
||||
|
||||
def onload(self):
|
||||
from frappe.config import get_modules_from_all_apps
|
||||
self.set_onload('all_modules',
|
||||
[m.module_name for m in frappe.db.get_all('Desktop Icon',
|
||||
fields=['module_name'], filters={'standard': 1}, order_by="module_name")])
|
||||
[m.get("module_name") for m in get_modules_from_all_apps()])
|
||||
|
||||
def before_insert(self):
|
||||
self.flags.in_insert = True
|
||||
|
|
@ -328,6 +328,12 @@ class User(Document):
|
|||
and reference_doctype='User'
|
||||
and (reference_name=%s or owner=%s)""", (self.name, self.name))
|
||||
|
||||
# unlink contact
|
||||
frappe.db.sql("""update `tabContact`
|
||||
set `user`=null
|
||||
where `user`=%s""", (self.name))
|
||||
|
||||
|
||||
def before_rename(self, old_name, new_name, merge=False):
|
||||
self.check_demo()
|
||||
frappe.clear_cache(user=old_name)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,84 @@
|
|||
# Copyright (c) 2017, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
from frappe.core.doctype.user_permission.user_permission import add_user_permissions
|
||||
|
||||
#import frappe
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestUserPermission(unittest.TestCase):
|
||||
pass
|
||||
def test_apply_to_all(self):
|
||||
''' Create User permission for User having access to all applicable Doctypes'''
|
||||
user = get_user()
|
||||
param = get_params(user, apply = 1)
|
||||
created = add_user_permissions(param)
|
||||
self.assertEquals(created, 1)
|
||||
|
||||
def test_for_applicable_on_update_from_apply_to_all(self):
|
||||
''' Update User Permission from all to some applicable Doctypes'''
|
||||
user = get_user()
|
||||
param = get_params(user, applicable = ["Chat Room", "Chat Message"])
|
||||
create = add_user_permissions(param)
|
||||
frappe.db.commit()
|
||||
|
||||
removed_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user))
|
||||
created_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room"))
|
||||
created_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Message"))
|
||||
|
||||
self.assertIsNone(removed_apply_to_all)
|
||||
self.assertIsNotNone(created_applicable_first)
|
||||
self.assertIsNotNone(created_applicable_second)
|
||||
self.assertEquals(create, 1)
|
||||
|
||||
def test_for_apply_to_all_on_update_from_applicable(self):
|
||||
''' Update User Permission from some to all applicable Doctypes'''
|
||||
user = get_user()
|
||||
param = get_params(user, apply = 1)
|
||||
created = add_user_permissions(param)
|
||||
created_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user))
|
||||
removed_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room"))
|
||||
removed_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Message"))
|
||||
|
||||
|
||||
self.assertIsNotNone(created_apply_to_all)
|
||||
self.assertIsNone(removed_applicable_first)
|
||||
self.assertIsNone(removed_applicable_second)
|
||||
self.assertEquals(created, 1)
|
||||
|
||||
def get_user():
|
||||
if frappe.db.exists('User', 'test_bulk_creation_update@example.com'):
|
||||
return frappe.get_doc('User', 'test_bulk_creation_update@example.com')
|
||||
else:
|
||||
user = frappe.new_doc('User')
|
||||
user.email = 'test_bulk_creation_update@example.com'
|
||||
user.first_name = 'Test_Bulk_Creation'
|
||||
user.add_roles("System Manager")
|
||||
return user
|
||||
|
||||
def get_params(user, apply = None , applicable = None):
|
||||
''' Return param to insert '''
|
||||
param = {
|
||||
"user": user.name,
|
||||
"doctype":"User",
|
||||
"docname":user.name
|
||||
}
|
||||
if apply:
|
||||
param.update({"apply_to_all_doctypes": 1})
|
||||
param.update({"applicable_doctypes": []})
|
||||
if applicable:
|
||||
param.update({"apply_to_all_doctypes": 0})
|
||||
param.update({"applicable_doctypes": applicable})
|
||||
return param
|
||||
|
||||
def get_exists_param(user, applicable = None):
|
||||
''' param to check existing Document '''
|
||||
param = {
|
||||
"user": user.name,
|
||||
"allow": "User",
|
||||
"for_value": user.name,
|
||||
}
|
||||
if applicable:
|
||||
param.update({"applicable_for": applicable})
|
||||
else:
|
||||
param.update({"apply_to_all_doctypes": 1})
|
||||
return param
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
|
|
@ -222,7 +222,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-11-12 16:26:12.362352",
|
||||
"modified": "2019-02-13 22:58:27.428741",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User Permission",
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ def get_user_permissions(user=None):
|
|||
if not user:
|
||||
user = frappe.session.user
|
||||
|
||||
if user == "Administrator":
|
||||
return {}
|
||||
|
||||
cached_user_permissions = frappe.cache().hget("user_permissions", user)
|
||||
|
||||
if cached_user_permissions is not None:
|
||||
|
|
@ -76,8 +79,8 @@ def get_user_permissions(user=None):
|
|||
|
||||
out = frappe._dict(out)
|
||||
frappe.cache().hset("user_permissions", user, out)
|
||||
except frappe.db.SQLError:
|
||||
if frappe.db.is_table_missing():
|
||||
except frappe.db.SQLError as e:
|
||||
if frappe.db.is_table_missing(e):
|
||||
# called from patch
|
||||
pass
|
||||
|
||||
|
|
@ -111,12 +114,100 @@ def get_permitted_documents(doctype):
|
|||
return [d.get('doc') for d in get_user_permissions().get(doctype, []) \
|
||||
if d.get('doc')]
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_applicable_doc_perm(user, doctype, docname):
|
||||
frappe.only_for('System Manager')
|
||||
applicable = []
|
||||
doc_exists = frappe.get_all('User Permission',
|
||||
fields=['name'],
|
||||
filters={"user": user,
|
||||
"allow": doctype,
|
||||
"for_value": docname,
|
||||
"apply_to_all_doctypes":1,
|
||||
}, limit=1)
|
||||
if doc_exists:
|
||||
applicable = get_linked_doctypes(doctype).keys()
|
||||
else:
|
||||
data = frappe.get_all('User Permission',
|
||||
fields=['applicable_for'],
|
||||
filters={"user": user,
|
||||
"allow": doctype,
|
||||
"for_value":docname,
|
||||
})
|
||||
for d in data:
|
||||
applicable.append(d.applicable_for)
|
||||
return applicable
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def clear_user_permissions(user, for_doctype):
|
||||
frappe.only_for('System Manager')
|
||||
|
||||
total = frappe.db.count('User Permission', filters = dict(user=user, allow=for_doctype))
|
||||
if total:
|
||||
frappe.db.sql('DELETE FROM `tabUser Permission` WHERE `user`=%s AND `allow`=%s', (user, for_doctype))
|
||||
frappe.clear_cache()
|
||||
return total
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_user_permissions(data):
|
||||
''' Add and update the user permissions '''
|
||||
frappe.only_for('System Manager')
|
||||
if isinstance(data, frappe.string_types):
|
||||
data = json.loads(data)
|
||||
data = frappe._dict(data)
|
||||
|
||||
d = check_applicable_doc_perm(data.user, data.doctype, data.docname)
|
||||
exists = frappe.db.exists("User Permission", {"user": data.user, "allow": data.doctype, "for_value": data.docname, "apply_to_all_doctypes": 1})
|
||||
if data.apply_to_all_doctypes == 1 and not exists:
|
||||
remove_applicable(d, data.user, data.doctype, data.docname)
|
||||
insert_user_perm(data.user, data.doctype, data.docname, apply_to_all = 1)
|
||||
return 1
|
||||
else:
|
||||
remove_apply_to_all(data.user, data.doctype, data.docname)
|
||||
update_applicable(d, data.applicable_doctypes, data.user, data.doctype, data.docname)
|
||||
for applicable in data.applicable_doctypes :
|
||||
if applicable not in d:
|
||||
insert_user_perm(data.user, data.doctype, data.docname, applicable = applicable)
|
||||
elif exists:
|
||||
insert_user_perm(data.user, data.doctype, data.docname, applicable = applicable)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def insert_user_perm(user, doctype, docname, apply_to_all=None, applicable=None):
|
||||
user_perm = frappe.new_doc("User Permission")
|
||||
user_perm.user = user
|
||||
user_perm.allow = doctype
|
||||
user_perm.for_value = docname
|
||||
if applicable:
|
||||
user_perm.applicable_for = applicable
|
||||
user_perm.apply_to_all_doctypes = 0
|
||||
else:
|
||||
user_perm.apply_to_all_doctypes = 1
|
||||
user_perm.insert()
|
||||
|
||||
def remove_applicable(d, user, doctype, docname):
|
||||
for applicable_for in d:
|
||||
frappe.db.sql("""DELETE FROM `tabUser Permission`
|
||||
WHERE `user`=%s
|
||||
AND `applicable_for`=%s
|
||||
AND `allow`=%s
|
||||
AND `for_value`=%s
|
||||
""", (user, applicable_for, doctype, docname))
|
||||
|
||||
def remove_apply_to_all(user, doctype, docname):
|
||||
frappe.db.sql("""DELETE from `tabUser Permission`
|
||||
WHERE `user`=%s
|
||||
AND `apply_to_all_doctypes`=1
|
||||
AND `allow`=%s
|
||||
AND `for_value`=%s
|
||||
""",(user, doctype, docname))
|
||||
|
||||
def update_applicable(already_applied, to_apply, user, doctype, docname):
|
||||
for applied in already_applied:
|
||||
if applied not in to_apply:
|
||||
frappe.db.sql("""DELETE FROM `tabUser Permission`
|
||||
WHERE `user`=%s
|
||||
AND `applicable_for`=%s
|
||||
AND `allow`=%s
|
||||
AND `for_value`=%s
|
||||
""",(user, applied, doctype, docname))
|
||||
|
|
@ -1,22 +1,123 @@
|
|||
frappe.listview_settings['User Permission'] = {
|
||||
|
||||
onload: function(list_view) {
|
||||
list_view.page.add_menu_item(__("Clear User Permissions"), () => {
|
||||
var me = this;
|
||||
list_view.page.add_inner_button( __("Add / Update"), function() {
|
||||
let dialog =new frappe.ui.Dialog({
|
||||
title : __('Add User Permissions'),
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'user',
|
||||
label: __('For User'),
|
||||
fieldtype: 'Link',
|
||||
options: 'User',
|
||||
reqd: 1,
|
||||
onchange: function() {
|
||||
dialog.fields_dict.doctype.set_input(undefined);
|
||||
dialog.fields_dict.docname.set_input(undefined);
|
||||
dialog.set_df_property("docname", "hidden", 1);
|
||||
dialog.set_df_property("apply_to_all_doctypes", "hidden", 1);
|
||||
dialog.set_df_property("applicable_doctypes", "hidden", 1);
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'doctype',
|
||||
label: __('Document Type'),
|
||||
fieldtype: 'Link',
|
||||
options: 'DocType',
|
||||
reqd: 1,
|
||||
onchange: function() {
|
||||
me.on_doctype_change(dialog);
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'docname',
|
||||
label: __('Document Name'),
|
||||
fieldtype: 'Dynamic Link',
|
||||
options: 'doctype',
|
||||
hidden: 1,
|
||||
onchange: function() {
|
||||
let field = dialog.fields_dict["docname"];
|
||||
if(field.value != field.last_value) {
|
||||
if(dialog.fields_dict.doctype.value && dialog.fields_dict.docname.value && dialog.fields_dict.user.value){
|
||||
me.get_applicable_doctype(dialog).then(applicable => {
|
||||
me.get_multi_select_options(dialog, applicable).then(options => {
|
||||
me.applicable_options = options;
|
||||
me.on_docname_change(dialog, options, applicable);
|
||||
if(options.length > 5){
|
||||
dialog.fields_dict.applicable_doctypes.setup_select_all();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'apply_to_all_doctypes',
|
||||
label: __('Apply to all Documents Types'),
|
||||
fieldtype: 'Check',
|
||||
checked: 1,
|
||||
hidden: 1,
|
||||
onchange: function() {
|
||||
if(dialog.fields_dict.doctype.value && dialog.fields_dict.docname.value && dialog.fields_dict.user.value){
|
||||
me.on_apply_to_all_doctypes_change(dialog, me.applicable_options);
|
||||
if(me.applicable_options.length > 5){
|
||||
dialog.fields_dict.applicable_doctypes.setup_select_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: __("Applicable Document Types"),
|
||||
fieldname: "applicable_doctypes",
|
||||
fieldtype: "MultiCheck",
|
||||
options: [],
|
||||
columns: 2,
|
||||
hidden: 1
|
||||
},
|
||||
],
|
||||
primary_action: (data) => {
|
||||
data = me.validate(dialog, data);
|
||||
frappe.call({
|
||||
async: false,
|
||||
method: "frappe.core.doctype.user_permission.user_permission.add_user_permissions",
|
||||
args: {
|
||||
data : data
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message === 1) {
|
||||
frappe.show_alert({message:__("User Permissions created sucessfully"), indicator:'blue'});
|
||||
} else {
|
||||
frappe.show_alert({message:__("Nothing to update"), indicator:'red'});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
dialog.hide();
|
||||
list_view.refresh();
|
||||
},
|
||||
primary_action_label: __('Submit')
|
||||
});
|
||||
dialog.show();
|
||||
});
|
||||
list_view.page.add_inner_button( __("Bulk Delete"), function() {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __('Clear User Permissions'),
|
||||
fields: [
|
||||
{
|
||||
'fieldname': 'user',
|
||||
'label': __('For User'),
|
||||
'fieldtype': 'Link',
|
||||
'options': 'User',
|
||||
'reqd': 1
|
||||
fieldname: 'user',
|
||||
label: __('For User'),
|
||||
fieldtype: 'Link',
|
||||
options: 'User',
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
'fieldname': 'for_doctype',
|
||||
'label': __('For Document Type'),
|
||||
'fieldtype': 'Link',
|
||||
'options': 'DocType',
|
||||
'reqd': 1
|
||||
fieldname: 'for_doctype',
|
||||
label: __('For Document Type'),
|
||||
fieldtype: 'Link',
|
||||
options: 'DocType',
|
||||
reqd: 1
|
||||
},
|
||||
],
|
||||
primary_action: (data) => {
|
||||
|
|
@ -31,6 +132,8 @@ frappe.listview_settings['User Permission'] = {
|
|||
let message = '';
|
||||
if (data === 0) {
|
||||
message = __('No records deleted');
|
||||
} else if(data === 1) {
|
||||
message = __('{0} record deleted', [data]);
|
||||
} else {
|
||||
message = __('{0} records deleted', [data]);
|
||||
}
|
||||
|
|
@ -43,10 +146,95 @@ frappe.listview_settings['User Permission'] = {
|
|||
});
|
||||
|
||||
},
|
||||
primary_action_label: __('Clear')
|
||||
primary_action_label: __('Delete')
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
});
|
||||
},
|
||||
|
||||
validate: function(dialog, data) {
|
||||
if(dialog.fields_dict.applicable_doctypes.get_unchecked_options().length == 0) {
|
||||
data.apply_to_all_doctypes = 1;
|
||||
data.applicable_doctypes = [];
|
||||
return data;
|
||||
}
|
||||
if(data.apply_to_all_doctypes == 0 && !("applicable_doctypes" in data)) {
|
||||
frappe.throw("Please select applicable Doctypes");
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
get_applicable_doctype: function(dialog) {
|
||||
return new Promise(resolve => {
|
||||
frappe.call({
|
||||
method: 'frappe.core.doctype.user_permission.user_permission.check_applicable_doc_perm',
|
||||
async: false,
|
||||
args:{
|
||||
user: dialog.fields_dict.user.value,
|
||||
doctype: dialog.fields_dict.doctype.value,
|
||||
docname: dialog.fields_dict.docname.value
|
||||
}
|
||||
}).then(r => {
|
||||
resolve(r.message);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
get_multi_select_options: function(dialog, applicable){
|
||||
return new Promise(resolve => {
|
||||
frappe.call({
|
||||
method: 'frappe.desk.form.linked_with.get_linked_doctypes',
|
||||
async: false,
|
||||
args:{
|
||||
user: dialog.fields_dict.user.value,
|
||||
doctype: dialog.fields_dict.doctype.value,
|
||||
docname: dialog.fields_dict.docname.value
|
||||
}
|
||||
}).then(r => {
|
||||
var options = [];
|
||||
for(var d in r.message){
|
||||
var checked = ($.inArray(d, applicable) != -1) ? 1 : 0;
|
||||
options.push({ "label":d, "value": d , "checked": checked});
|
||||
}
|
||||
resolve(options);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
on_doctype_change: function(dialog) {
|
||||
dialog.set_df_property("docname", "hidden", 0);
|
||||
dialog.set_df_property("docname", "reqd", 1);
|
||||
dialog.set_df_property("apply_to_all_doctypes", "hidden", 0);
|
||||
dialog.set_value("apply_to_all_doctypes","checked",1);
|
||||
},
|
||||
|
||||
on_docname_change: function(dialog, options, applicable) {
|
||||
if(applicable.length != 0 ) {
|
||||
dialog.set_primary_action("Update");
|
||||
dialog.set_title("Update User Permissions");
|
||||
dialog.set_df_property("applicable_doctypes", "options", options);
|
||||
if(dialog.fields_dict.applicable_doctypes.get_checked_options().length == options.length) {
|
||||
dialog.set_df_property("applicable_doctypes", "hidden", 1);
|
||||
} else {
|
||||
dialog.set_df_property("applicable_doctypes", "hidden", 0);
|
||||
dialog.set_df_property("apply_to_all_doctypes", "checked", 0);
|
||||
}
|
||||
} else {
|
||||
dialog.set_primary_action("Submit");
|
||||
dialog.set_title("Add User Permissions");
|
||||
dialog.set_df_property("applicable_doctypes", "options", options);
|
||||
dialog.set_df_property("applicable_doctypes", "hidden", 1);
|
||||
}
|
||||
},
|
||||
|
||||
on_apply_to_all_doctypes_change: function(dialog, options) {
|
||||
if(dialog.fields_dict.apply_to_all_doctypes.get_value() == 0) {
|
||||
dialog.set_df_property("applicable_doctypes", "hidden", 0);
|
||||
dialog.set_df_property("applicable_doctypes", "options", options);
|
||||
} else {
|
||||
dialog.set_df_property("applicable_doctypes", "options", options);
|
||||
dialog.set_df_property("applicable_doctypes", "hidden", 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) 2016, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('User Permission for Page and Report', {
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.role_area.hide();
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
if(!frm.roles_editor) {
|
||||
frm.role_area = $('<div style="min-height: 300px">')
|
||||
.appendTo(frm.fields_dict.roles_html.wrapper);
|
||||
frm.roles_editor = new frappe.RoleEditor(frm.role_area, frm);
|
||||
}
|
||||
},
|
||||
|
||||
page: function(frm) {
|
||||
frm.trigger("get_roles");
|
||||
},
|
||||
|
||||
report: function(frm){
|
||||
frm.trigger("get_roles");
|
||||
},
|
||||
|
||||
get_roles: function(frm) {
|
||||
frm.role_area.show();
|
||||
|
||||
return frappe.call({
|
||||
method:"get_custom_roles",
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
refresh_field('roles');
|
||||
frm.roles_editor.show();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
update: function(frm) {
|
||||
if(frm.roles_editor) {
|
||||
frm.roles_editor.set_roles_in_table();
|
||||
}
|
||||
|
||||
return frappe.call({
|
||||
method:"set_custom_roles",
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
refresh_field('roles');
|
||||
frm.roles_editor.show();
|
||||
frappe.msgprint(__("Successfully Updated"));
|
||||
frm.reload_doc();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,309 +0,0 @@
|
|||
{
|
||||
"allow_copy": 1,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-02-13 17:33:25.157332",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "set_role_for",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Set Role For",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nPage\nReport",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.set_role_for == 'Page'",
|
||||
"fieldname": "page",
|
||||
"fieldtype": "Link",
|
||||
"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": "Page",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Page",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.set_role_for == 'Report'",
|
||||
"fieldname": "report",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Report",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Report",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "roles_permission",
|
||||
"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": "Roles Permission",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "roles_html",
|
||||
"fieldtype": "HTML",
|
||||
"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": "Roles Html",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "roles",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Roles",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Has Role",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "update",
|
||||
"fieldtype": "Button",
|
||||
"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": "Update",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 1,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-12-21 04:24:24.963988",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User Permission for Page and Report",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, 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 UserPermissionforPageandReport(Document):
|
||||
def get_custom_roles(self):
|
||||
args = self.get_args()
|
||||
self.set('roles', [])
|
||||
|
||||
name = frappe.db.get_value('Custom Role', args, "name")
|
||||
if name:
|
||||
doc = frappe.get_doc('Custom Role', name)
|
||||
else:
|
||||
doctype = self.set_role_for
|
||||
docname = self.page if self.set_role_for == 'Page' else self.report
|
||||
doc = frappe.get_doc(doctype, docname)
|
||||
|
||||
self.set('roles', doc.roles)
|
||||
|
||||
def set_custom_roles(self):
|
||||
args = self.get_args()
|
||||
name = frappe.db.get_value('Custom Role', args, "name")
|
||||
|
||||
args.update({
|
||||
'doctype': 'Custom Role',
|
||||
'roles': self.roles
|
||||
})
|
||||
|
||||
if name:
|
||||
doc = frappe.get_doc("Custom Role", name)
|
||||
doc.set('roles', self.roles)
|
||||
doc.save()
|
||||
else:
|
||||
frappe.get_doc(args).insert()
|
||||
|
||||
def get_args(self, row=None):
|
||||
name = self.page if self.set_role_for == 'Page' else self.report
|
||||
check_for_field = self.set_role_for.replace(" ","_").lower()
|
||||
|
||||
return {
|
||||
check_for_field: name
|
||||
}
|
||||
|
||||
def update_status(self):
|
||||
return frappe.render_template
|
||||
|
|
@ -44,14 +44,14 @@ def get_todays_events(as_list=False):
|
|||
|
||||
def get_unseen_likes():
|
||||
"""Returns count of unseen likes"""
|
||||
return frappe.db.sql("""select count(*) from `tabCommunication`
|
||||
return frappe.db.sql("""select count(*) from `tabComment`
|
||||
where
|
||||
communication_type='Comment'
|
||||
comment_type='Like'
|
||||
and modified >= (NOW() - INTERVAL '1' YEAR)
|
||||
and comment_type='Like'
|
||||
and owner is not null and owner!=%(user)s
|
||||
and reference_owner=%(user)s
|
||||
and seen=0""", {"user": frappe.session.user})[0][0]
|
||||
and seen=0
|
||||
""", {"user": frappe.session.user})[0][0]
|
||||
|
||||
def get_unread_emails():
|
||||
"returns unread emails for a user"
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
First screen after login. Array of module icons based on permission.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<div><input class="form-control desktop-app-search" type="text" placeholder="{%= __("Search Application") %}">
|
||||
</div>
|
||||
{% if (frappe.user.has_role("System Manager")) { %}
|
||||
<p class="text-right"><a href="#applications" class="btn btn-sm btn-default">Install new applications</a>
|
||||
</p>
|
||||
{% } %}
|
||||
<hr>
|
||||
<p class="text-muted small">{%= __("Checked items will be shown on desktop") %}</p>
|
||||
<div class="list-group all-applications-list">
|
||||
{% for(var i=0, l=all_modules.length; i < l; i++) {
|
||||
var module_name = all_modules[i];
|
||||
var module = frappe.get_module(module_name);
|
||||
if (desktop_items.indexOf(module_name)===-1
|
||||
|| frappe.user.is_module_blocked(module_name)) { continue; }
|
||||
%}
|
||||
<div class="list-group-item" data-label="{%= module.label %}" data-name="{%= module.name %}">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" {% if (user_desktop_items.indexOf(module.name)!==-1) { %} checked {% } %}
|
||||
data-name="{%= module.name %}"
|
||||
{{ module.force_show ? "disabled" : ""}}> {%= __(module.label) %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
</div>
|
||||
|
|
@ -1,344 +1 @@
|
|||
frappe.provide('frappe.desktop');
|
||||
|
||||
frappe.pages['desktop'].on_page_load = function(wrapper) {
|
||||
|
||||
// load desktop
|
||||
if(!frappe.list_desktop) {
|
||||
frappe.desktop.set_background();
|
||||
}
|
||||
frappe.desktop.refresh(wrapper);
|
||||
};
|
||||
|
||||
frappe.pages['desktop'].on_page_show = function(wrapper) {
|
||||
if(frappe.list_desktop) {
|
||||
$("body").attr("data-route", "list-desktop");
|
||||
}
|
||||
};
|
||||
|
||||
$.extend(frappe.desktop, {
|
||||
refresh: function(wrapper) {
|
||||
if (wrapper) {
|
||||
this.wrapper = $(wrapper);
|
||||
}
|
||||
|
||||
this.render();
|
||||
this.make_sortable();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var me = this;
|
||||
frappe.utils.set_title(__("Desktop"));
|
||||
|
||||
var template = frappe.list_desktop ? "desktop_list_view" : "desktop_icon_grid";
|
||||
|
||||
var all_icons = frappe.get_desktop_icons();
|
||||
var explore_icon = {
|
||||
module_name: 'Explore',
|
||||
label: 'Explore',
|
||||
_label: __('Explore'),
|
||||
_id: 'Explore',
|
||||
_doctype: '',
|
||||
icon: 'octicon octicon-telescope',
|
||||
color: '#7578f6',
|
||||
link: 'modules'
|
||||
};
|
||||
explore_icon.app_icon = frappe.ui.app_icon.get_html(explore_icon);
|
||||
all_icons.push(explore_icon);
|
||||
|
||||
frappe.desktop.wrapper.html(frappe.render_template(template, {
|
||||
// all visible icons
|
||||
desktop_items: all_icons,
|
||||
}));
|
||||
|
||||
frappe.desktop.setup_module_click();
|
||||
|
||||
// notifications
|
||||
frappe.desktop.show_pending_notifications();
|
||||
$(document).on("notification-update", function() {
|
||||
me.show_pending_notifications();
|
||||
});
|
||||
|
||||
$(document).trigger("desktop-render");
|
||||
|
||||
},
|
||||
|
||||
render_help_messages: function(help_messages) {
|
||||
var wrapper = frappe.desktop.wrapper.find('.help-message-wrapper');
|
||||
var $help_messages = wrapper.find('.help-messages');
|
||||
|
||||
var set_current_message = function(idx) {
|
||||
idx = cint(idx);
|
||||
wrapper.current_message_idx = idx;
|
||||
wrapper.find('.left-arrow, .right-arrow').addClass('disabled');
|
||||
wrapper.find('.help-message-item').addClass('hidden');
|
||||
wrapper.find('[data-message-idx="'+idx+'"]').removeClass('hidden');
|
||||
if(idx > 0) {
|
||||
wrapper.find('.left-arrow').removeClass('disabled');
|
||||
}
|
||||
if(idx < help_messages.length - 1) {
|
||||
wrapper.find('.right-arrow').removeClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
if(help_messages) {
|
||||
wrapper.removeClass('hidden');
|
||||
help_messages.forEach(function(message, i) {
|
||||
var $message = $('<div class="help-message-item hidden"></div>')
|
||||
.attr('data-message-idx', i)
|
||||
.html(frappe.render_template('desktop_help_message', message))
|
||||
.appendTo($help_messages);
|
||||
|
||||
});
|
||||
|
||||
set_current_message(0);
|
||||
|
||||
wrapper.find('.close').on('click', function() {
|
||||
wrapper.addClass('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
wrapper.find('.left-arrow').on('click', function() {
|
||||
if(wrapper.current_message_idx) {
|
||||
set_current_message(wrapper.current_message_idx - 1);
|
||||
}
|
||||
})
|
||||
|
||||
wrapper.find('.right-arrow').on('click', function() {
|
||||
if(help_messages.length > wrapper.current_message_idx + 1) {
|
||||
set_current_message(wrapper.current_message_idx + 1);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
setup_module_click: function() {
|
||||
frappe.desktop.wiggling = false;
|
||||
|
||||
if(frappe.list_desktop) {
|
||||
frappe.desktop.wrapper.on("click", ".desktop-list-item", function() {
|
||||
frappe.desktop.open_module($(this));
|
||||
});
|
||||
} else {
|
||||
frappe.desktop.wrapper.on("click", ".app-icon, .app-icon-svg", function() {
|
||||
if ( !frappe.desktop.wiggling ) {
|
||||
frappe.desktop.open_module($(this).parent());
|
||||
}
|
||||
});
|
||||
}
|
||||
frappe.desktop.wrapper.on("click", ".circle", function() {
|
||||
var doctype = $(this).attr('data-doctype');
|
||||
if(doctype) {
|
||||
frappe.ui.notifications.show_open_count_list(doctype);
|
||||
}
|
||||
});
|
||||
|
||||
frappe.desktop.setup_wiggle();
|
||||
},
|
||||
|
||||
setup_wiggle: () => {
|
||||
// Wiggle, Wiggle, Wiggle.
|
||||
const DURATION_LONG_PRESS = 1000;
|
||||
|
||||
var timer_id = 0;
|
||||
const $cases = frappe.desktop.wrapper.find('.case-wrapper');
|
||||
const $icons = frappe.desktop.wrapper.find('.app-icon');
|
||||
const $notis = $(frappe.desktop.wrapper.find('.circle').toArray().filter((object) => {
|
||||
// This hack is so bad, I should punch myself.
|
||||
// Seriously, punch yourself.
|
||||
const text = $(object).find('.circle-text').html();
|
||||
|
||||
return text;
|
||||
}));
|
||||
|
||||
const clearWiggle = () => {
|
||||
const $closes = $cases.find('.module-remove');
|
||||
$closes.hide();
|
||||
$notis.show();
|
||||
|
||||
$icons.removeClass('wiggle');
|
||||
|
||||
frappe.desktop.wiggling = false;
|
||||
};
|
||||
|
||||
frappe.desktop.wrapper.on('mousedown', '.app-icon', () => {
|
||||
timer_id = setTimeout(() => {
|
||||
frappe.desktop.wiggling = true;
|
||||
// hide all notifications.
|
||||
$notis.hide();
|
||||
|
||||
$cases.each((i) => {
|
||||
const $case = $($cases[i]);
|
||||
const template =
|
||||
`
|
||||
<div class="circle module-remove" style="background-color:#E0E0E0; color:#212121">
|
||||
<div class="circle-text">
|
||||
<b>
|
||||
×
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$case.append(template);
|
||||
const $close = $case.find('.module-remove');
|
||||
const name = $case.attr('title');
|
||||
$close.click(() => {
|
||||
// good enough to create dynamic dialogs?
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __(`Hide ${name}?`)
|
||||
});
|
||||
dialog.set_primary_action(__('Hide'), () => {
|
||||
frappe.call({
|
||||
method: 'frappe.desk.doctype.desktop_icon.desktop_icon.hide',
|
||||
args: { name: name },
|
||||
freeze: true,
|
||||
callback: (response) =>
|
||||
{
|
||||
if ( response.message ) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
dialog.hide();
|
||||
|
||||
clearWiggle();
|
||||
});
|
||||
// Hacks, Hacks and Hacks.
|
||||
var $cancel = dialog.get_close_btn();
|
||||
$cancel.click(() => {
|
||||
clearWiggle();
|
||||
});
|
||||
$cancel.html(__(`Cancel`));
|
||||
|
||||
dialog.show();
|
||||
});
|
||||
});
|
||||
|
||||
$icons.addClass('wiggle');
|
||||
|
||||
}, DURATION_LONG_PRESS);
|
||||
});
|
||||
frappe.desktop.wrapper.on('mouseup mouseleave', '.app-icon', () => {
|
||||
clearTimeout(timer_id);
|
||||
});
|
||||
|
||||
// also stop wiggling if clicked elsewhere.
|
||||
$('body').click((event) => {
|
||||
if ( frappe.desktop.wiggling ) {
|
||||
const $target = $(event.target);
|
||||
// our target shouldn't be .app-icons or .close
|
||||
const $parent = $target.parents('.case-wrapper');
|
||||
if ( $parent.length == 0 )
|
||||
clearWiggle();
|
||||
}
|
||||
});
|
||||
// end wiggle
|
||||
},
|
||||
|
||||
open_module: function(parent) {
|
||||
var link = parent.attr("data-link");
|
||||
if(link) {
|
||||
if(link.indexOf('javascript:')===0) {
|
||||
eval(link.substr(11));
|
||||
} else if(link.substr(0, 1)==="/" || link.substr(0, 4)==="http") {
|
||||
window.open(link, "_blank");
|
||||
} else {
|
||||
frappe.set_route(link);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
var module = frappe.get_module(parent.attr("data-name"));
|
||||
if (module && module.onclick) {
|
||||
module.onclick();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
make_sortable: function() {
|
||||
if (frappe.dom.is_touchscreen() || frappe.list_desktop) {
|
||||
return;
|
||||
}
|
||||
|
||||
new Sortable($("#icon-grid").get(0), {
|
||||
animation: 150,
|
||||
onUpdate: function(event) {
|
||||
var new_order = [];
|
||||
$("#icon-grid .case-wrapper").each(function(i, e) {
|
||||
new_order.push($(this).attr("data-name"));
|
||||
});
|
||||
|
||||
frappe.call({
|
||||
method: 'frappe.desk.doctype.desktop_icon.desktop_icon.set_order',
|
||||
args: {
|
||||
'new_order': new_order,
|
||||
'user': frappe.session.user
|
||||
},
|
||||
quiet: true
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_background: function() {
|
||||
frappe.ui.set_user_background(frappe.boot.user.background_image, null,
|
||||
frappe.boot.user.background_style);
|
||||
},
|
||||
|
||||
show_pending_notifications: function() {
|
||||
var modules_list = frappe.get_desktop_icons();
|
||||
for (var i=0, l=modules_list.length; i < l; i++) {
|
||||
var module = modules_list[i];
|
||||
|
||||
var module_doctypes = frappe.boot.notification_info.module_doctypes[module.module_name];
|
||||
|
||||
var sum = 0;
|
||||
|
||||
if(module_doctypes && frappe.boot.notification_info.open_count_doctype) {
|
||||
// sum all doctypes for a module
|
||||
for (var j=0, k=module_doctypes.length; j < k; j++) {
|
||||
var doctype = module_doctypes[j];
|
||||
let count = (frappe.boot.notification_info.open_count_doctype[doctype] || 0);
|
||||
count = typeof count == "string" ? parseInt(count) : count;
|
||||
sum += count;
|
||||
}
|
||||
}
|
||||
|
||||
if(frappe.boot.notification_info.open_count_doctype
|
||||
&& frappe.boot.notification_info.open_count_doctype[module.module_name]!=null) {
|
||||
// notification count explicitly for doctype
|
||||
let count = frappe.boot.notification_info.open_count_doctype[module.module_name] || 0;
|
||||
count = typeof count == "string" ? parseInt(count) : count;
|
||||
sum += count;
|
||||
}
|
||||
|
||||
if(frappe.boot.notification_info.open_count_module
|
||||
&& frappe.boot.notification_info.open_count_module[module.module_name]!=null) {
|
||||
// notification count explicitly for module
|
||||
let count = frappe.boot.notification_info.open_count_module[module.module_name] || 0;
|
||||
count = typeof count == "string" ? parseInt(count) : count;
|
||||
sum += count;
|
||||
}
|
||||
|
||||
// if module found
|
||||
if(module._id.indexOf('/')===-1 && !module._report) {
|
||||
var notifier = $(".module-count-" + frappe.scrub(module._id));
|
||||
if(notifier.length) {
|
||||
notifier.toggle(sum ? true : false);
|
||||
var circle = notifier.find(".circle-text");
|
||||
var text = sum || '';
|
||||
if(text > 99) {
|
||||
text = '99+';
|
||||
}
|
||||
|
||||
if(circle.length) {
|
||||
circle.html(text);
|
||||
} else {
|
||||
notifier.html(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
frappe.pages['desktop'].on_page_load = function() {};
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"creation": "2013-02-14 17:37:37.000000",
|
||||
"content": null,
|
||||
"creation": "2019-01-29 13:11:48.872579",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"icon": "fa fa-th",
|
||||
"idx": 1,
|
||||
"modified": "2013-07-11 14:41:56.000000",
|
||||
"icon": "icon-th",
|
||||
"idx": 0,
|
||||
"modified": "2019-01-29 13:11:48.872579",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "desktop",
|
||||
|
|
@ -15,6 +16,9 @@
|
|||
"role": "All"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Desktop"
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import functools
|
||||
import frappe
|
||||
from past.builtins import cmp
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_help_messages():
|
||||
'''Return help messages for the desktop (called via `get_help_messages` hook)
|
||||
|
||||
Format for message:
|
||||
|
||||
{
|
||||
title: _('Add Employees to Manage Them'),
|
||||
description: _('Add your Employees so you can manage leaves, expenses and payroll'),
|
||||
action: 'Add Employee',
|
||||
route: 'List/Employee'
|
||||
}
|
||||
|
||||
'''
|
||||
messages = []
|
||||
for fn in frappe.get_hooks('get_help_messages'):
|
||||
messages += frappe.get_attr(fn)()
|
||||
|
||||
return sorted(messages, key = functools.cmp_to_key(lambda a, b: cmp(a.get('count'), b.get('count'))))
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<div><span class="indicator blue">{{ title }}</span></div>
|
||||
<p>{{ description }}</p>
|
||||
<div>
|
||||
<a class="btn btn-sm btn-default" href="#{{ route }}">{{ action }}</a>
|
||||
<span class="help-progress" title="{{ __("You have made {0} of {1}", [count, target]) }}">
|
||||
<span class="help-progress-bar" style="width: {{ Math.floor(count/target*100) }}%"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<div style="text-align: center; padding-top: calc(40px + 3%)">
|
||||
<div id="icon-grid">
|
||||
{% for (var i=0, l=desktop_items.length; i < l; i++) { %}
|
||||
{{ frappe.render_template("desktop_module_icon", desktop_items[i]) }}
|
||||
{% } %}
|
||||
</div>
|
||||
<div class="help-message-wrapper hidden">
|
||||
<div class="help-message-container">
|
||||
<a class="close pull-right" style="margin-right: -7px;">
|
||||
<i class="octicon octicon-x"></i></a>
|
||||
<div class="help-messages">
|
||||
|
||||
</div>
|
||||
<a class="left-arrow octicon octicon-chevron-left">
|
||||
</a>
|
||||
<a class="right-arrow octicon octicon-chevron-right">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear: both"></div>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<div class="container page-body">
|
||||
<div class="row">
|
||||
<div class="layout-main-section">
|
||||
<div class="page-content desktop-list" style="margin-top: 40px;">
|
||||
{% for (var i=0, l=desktop_items.length; i < l; i++) {
|
||||
var module = desktop_items[i];
|
||||
%}
|
||||
<div class="desktop-list-item" id="module-icon-{%= module._id %}"
|
||||
data-name="{%= module.name %}" data-link="{%= module.link %}"
|
||||
title="{%= module._label %}">
|
||||
<h4>
|
||||
<i class="{{ module.icon }} text-muted"
|
||||
style="font-size: 20px; margin-right: 15px;"></i>
|
||||
{{ module._label }}
|
||||
</h4>
|
||||
<span class="open-notification global module-count-{{ module._id }}"
|
||||
style="display: none;"></span>
|
||||
</div>
|
||||
{% } %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<div class="case-wrapper"
|
||||
data-name="{{ module_name }}" data-link="{{ link }}" title="{{ _label }}">
|
||||
{{ app_icon }}
|
||||
<div class="case-label ellipsis">
|
||||
<div class="circle module-count-{{ frappe.scrub(_id) }}" data-doctype="{{ _doctype }}" style="display: none;">
|
||||
<span class="circle-text"></span>
|
||||
</div>
|
||||
<!-- <span id="module-count-{{ _id }}" class="octicon octicon-primitive-dot circle" style="display: None"></span> -->
|
||||
<span class="case-label-text">{{ _label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
0
frappe/core/page/recorder/__init__.py
Normal file
0
frappe/core/page/recorder/__init__.py
Normal file
26
frappe/core/page/recorder/recorder.js
Normal file
26
frappe/core/page/recorder/recorder.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
frappe.pages['recorder'].on_page_load = function(wrapper) {
|
||||
frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'Recorder',
|
||||
single_column: true
|
||||
});
|
||||
|
||||
frappe.recorder = new Recorder(wrapper);
|
||||
$(wrapper).bind('show', function() {
|
||||
frappe.recorder.show();
|
||||
});
|
||||
|
||||
frappe.require('/assets/js/frappe-recorder.min.js');
|
||||
};
|
||||
|
||||
class Recorder {
|
||||
constructor(wrapper) {
|
||||
this.wrapper = $(wrapper);
|
||||
this.container = this.wrapper.find('.layout-main-section');
|
||||
this.container.append($('<div class="recorder-container"></div>'));
|
||||
}
|
||||
|
||||
show() {
|
||||
|
||||
}
|
||||
}
|
||||
23
frappe/core/page/recorder/recorder.json
Normal file
23
frappe/core/page/recorder/recorder.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"content": null,
|
||||
"creation": "2019-02-08 08:17:45.392739",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2019-02-08 08:23:04.416426",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "recorder",
|
||||
"owner": "Administrator",
|
||||
"page_name": "Recorder",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Administrator"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Recorder"
|
||||
}
|
||||
|
|
@ -119,7 +119,7 @@ frappe.ui.form.on("Customize Form", {
|
|||
frm.set_df_property("sort_field", "options", fields);
|
||||
}
|
||||
|
||||
if(frappe.route_options) {
|
||||
if(frappe.route_options && frappe.route_options.doc_type) {
|
||||
setTimeout(function() {
|
||||
frm.set_value("doc_type", frappe.route_options.doc_type);
|
||||
frappe.route_options = null;
|
||||
|
|
|
|||
|
|
@ -881,9 +881,14 @@ class Database(object):
|
|||
|
||||
def get_descendants(self, doctype, name):
|
||||
'''Return descendants of the current record'''
|
||||
lft, rgt = self.get_value(doctype, name, ('lft', 'rgt'))
|
||||
return self.sql_list('''select name from `tab{doctype}`
|
||||
where lft > {lft} and rgt < {rgt}'''.format(doctype=doctype, lft=lft, rgt=rgt))
|
||||
node_location_indexes = self.get_value(doctype, name, ('lft', 'rgt'))
|
||||
if node_location_indexes:
|
||||
lft, rgt = node_location_indexes
|
||||
return self.sql_list('''select name from `tab{doctype}`
|
||||
where lft > {lft} and rgt < {rgt}'''.format(doctype=doctype, lft=lft, rgt=rgt))
|
||||
else:
|
||||
# when document does not exist
|
||||
return []
|
||||
|
||||
def is_missing_table_or_column(self, e):
|
||||
return self.is_missing_column(e) or self.is_missing_table(e)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class MariaDBDatabase(Database):
|
|||
self.type_map = {
|
||||
'Currency': ('decimal', '18,6'),
|
||||
'Int': ('int', '11'),
|
||||
'Long Int': ('bigint', '20'), # convert int to bigint if length is more than 11
|
||||
'Long Int': ('bigint', '20'),
|
||||
'Float': ('decimal', '18,6'),
|
||||
'Percent': ('decimal', '18,6'),
|
||||
'Check': ('int', '1'),
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class PostgresDatabase(Database):
|
|||
self.type_map = {
|
||||
'Currency': ('decimal', '18,6'),
|
||||
'Int': ('bigint', None),
|
||||
'Long Int': ('bigint', None), # convert int to bigint if length is more than 11
|
||||
'Long Int': ('bigint', None),
|
||||
'Float': ('decimal', '18,6'),
|
||||
'Percent': ('decimal', '18,6'),
|
||||
'Check': ('smallint', None),
|
||||
|
|
@ -40,8 +40,8 @@ class PostgresDatabase(Database):
|
|||
'Long Text': ('text', ''),
|
||||
'Code': ('text', ''),
|
||||
'Text Editor': ('text', ''),
|
||||
'Markdown Editor': ('longtext', ''),
|
||||
'HTML Editor': ('longtext', ''),
|
||||
'Markdown Editor': ('text', ''),
|
||||
'HTML Editor': ('text', ''),
|
||||
'Date': ('date', ''),
|
||||
'Datetime': ('timestamp', None),
|
||||
'Time': ('time', '6'),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
|
|
@ -14,6 +15,7 @@
|
|||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -40,10 +42,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -70,10 +74,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -100,10 +106,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -130,10 +138,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -159,10 +169,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -189,10 +201,76 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"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": "Description",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "category",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Category",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -219,10 +297,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -249,10 +329,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -279,10 +361,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -308,10 +392,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -339,10 +425,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -370,10 +458,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -401,10 +491,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -431,10 +523,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -460,10 +554,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -490,10 +586,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -520,10 +618,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -550,10 +650,12 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
|
|
@ -580,6 +682,7 @@
|
|||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
|
|
@ -593,7 +696,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-05-08 15:41:31.121652",
|
||||
"modified": "2019-01-24 04:58:58.720618",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Desktop Icon",
|
||||
|
|
@ -602,7 +705,6 @@
|
|||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
|
@ -629,5 +731,6 @@
|
|||
"sort_order": "DESC",
|
||||
"title_field": "module_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ def get_desktop_icons(user=None):
|
|||
user_icons = frappe.cache().hget('desktop_icons', user)
|
||||
|
||||
if not user_icons:
|
||||
fields = ['module_name', 'hidden', 'label', 'link', 'type', 'icon', 'color',
|
||||
fields = ['module_name', 'hidden', 'label', 'link', 'type', 'icon', 'color', 'description', 'category',
|
||||
'_doctype', '_report', 'idx', 'force_show', 'reverse', 'custom', 'standard', 'blocked']
|
||||
|
||||
active_domains = frappe.get_active_domains()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from __future__ import unicode_literals
|
|||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.form.load import get_docinfo
|
||||
import frappe.share
|
||||
|
||||
class DuplicateToDoError(frappe.ValidationError): pass
|
||||
|
|
@ -16,15 +15,11 @@ def get(args=None):
|
|||
if not args:
|
||||
args = frappe.local.form_dict
|
||||
|
||||
get_docinfo(frappe.get_doc(args.get("doctype"), args.get("name")))
|
||||
|
||||
return frappe.db.sql("""SELECT `owner`, `description`
|
||||
FROM `tabToDo`
|
||||
WHERE reference_type=%(doctype)s
|
||||
AND reference_name=%(name)s
|
||||
AND status='Open'
|
||||
ORDER BY modified DESC
|
||||
LIMIT 5""", args, as_dict=True)
|
||||
return frappe.get_all('ToDo', fields = ['owner', 'description'], filters = dict(
|
||||
reference_type = args.get('doctype'),
|
||||
reference_name = args.get('name'),
|
||||
status = 'Open'
|
||||
), limit = 5)
|
||||
|
||||
@frappe.whitelist()
|
||||
def add(args=None):
|
||||
|
|
@ -100,7 +95,8 @@ def add_multiple(args=None):
|
|||
add(args)
|
||||
|
||||
def remove_from_todo_if_already_assigned(doctype, docname):
|
||||
owner = frappe.db.get_value("ToDo", {"reference_type": doctype, "reference_name": docname, "status":"Open"}, "owner")
|
||||
owner = frappe.db.get_value("ToDo", {"reference_type": doctype, "reference_name": docname,
|
||||
"status":"Open"}, "owner")
|
||||
if owner:
|
||||
remove(doctype, docname, owner)
|
||||
|
||||
|
|
@ -125,9 +121,18 @@ def remove(doctype, name, assign_to):
|
|||
return get({"doctype": doctype, "name": name})
|
||||
|
||||
def clear(doctype, name):
|
||||
for assign_to in frappe.db.sql_list("""select owner from `tabToDo`
|
||||
where reference_type=%(doctype)s and reference_name=%(name)s""", locals()):
|
||||
remove(doctype, name, assign_to)
|
||||
'''
|
||||
Clears assignments, return False if not assigned.
|
||||
'''
|
||||
assignments = frappe.db.get_all('ToDo', fields=['owner'], filters =
|
||||
dict(reference_type = doctype, reference_name = name))
|
||||
if not assignments:
|
||||
return False
|
||||
|
||||
for assign_to in assignments:
|
||||
remove(doctype, name, assign_to.owner)
|
||||
|
||||
return True
|
||||
|
||||
def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
|
||||
description=None, notify=0):
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ def get_docinfo(doc=None, doctype=None, name=None):
|
|||
frappe.response["docinfo"] = {
|
||||
"attachments": get_attachments(doc.doctype, doc.name),
|
||||
"communications": _get_communications(doc.doctype, doc.name),
|
||||
'comments': get_comments(doc.doctype, doc.name),
|
||||
'total_comments': len(json.loads(doc.get('_comments') or '[]')),
|
||||
'versions': get_versions(doc),
|
||||
"assignments": get_assignments(doc.doctype, doc.name),
|
||||
|
|
@ -120,6 +121,19 @@ def get_communications(doctype, name, start=0, limit=20):
|
|||
return _get_communications(doctype, name, start, limit)
|
||||
|
||||
|
||||
def get_comments(doctype, name):
|
||||
comments = frappe.get_all('Comment', fields = ['*'], filters = dict(
|
||||
reference_doctype = doctype,
|
||||
reference_name = name
|
||||
))
|
||||
|
||||
# convert to markdown (legacy ?)
|
||||
for c in comments:
|
||||
if c.comment_type == 'Comment':
|
||||
c.content = frappe.utils.markdown(c.content)
|
||||
|
||||
return comments
|
||||
|
||||
def _get_communications(doctype, name, start=0, limit=20):
|
||||
communications = get_communication_data(doctype, name, start, limit)
|
||||
for c in communications:
|
||||
|
|
@ -130,8 +144,6 @@ def _get_communications(doctype, name, start=0, limit=20):
|
|||
"attached_to_name": c.name}
|
||||
))
|
||||
|
||||
elif c.communication_type=="Comment" and c.comment_type=="Comment":
|
||||
c.content = frappe.utils.markdown(c.content)
|
||||
return communications
|
||||
|
||||
def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=None,
|
||||
|
|
@ -144,17 +156,13 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
|
|||
`timeline_doctype`, `timeline_name`, `reference_doctype`, `reference_name`,
|
||||
`link_doctype`, `link_name`, `read_by_recipient`, `rating`, 'Communication' AS `doctype`'''
|
||||
|
||||
conditions = '''communication_type in ('Communication', 'Comment', 'Feedback')
|
||||
conditions = '''communication_type in ('Communication', 'Feedback')
|
||||
and (
|
||||
(reference_doctype=%(doctype)s and reference_name=%(name)s)
|
||||
or (
|
||||
(timeline_doctype=%(doctype)s and timeline_name=%(name)s)
|
||||
and (
|
||||
communication_type='Communication'
|
||||
or (
|
||||
communication_type='Comment'
|
||||
and comment_type in ('Created', 'Updated', 'Submitted', 'Cancelled', 'Deleted')
|
||||
)))
|
||||
(timeline_doctype=%(doctype)s and timeline_name=%(name)s)
|
||||
and (communication_type='Communication')
|
||||
)
|
||||
)'''
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -57,23 +57,23 @@ def validate_link():
|
|||
frappe.response['message'] = 'Ok'
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_comment(doc):
|
||||
def add_comment(reference_doctype, reference_name, content, comment_email):
|
||||
"""allow any logged user to post a comment"""
|
||||
doc = frappe.get_doc(json.loads(doc))
|
||||
|
||||
doc.content = clean_email_html(doc.content)
|
||||
|
||||
if not (doc.doctype=="Communication" and doc.communication_type=='Comment'):
|
||||
frappe.throw(_("This method can only be used to create a Comment"), frappe.PermissionError)
|
||||
|
||||
doc.insert(ignore_permissions=True)
|
||||
doc = frappe.get_doc(dict(
|
||||
doctype = 'Comment',
|
||||
reference_doctype = reference_doctype,
|
||||
reference_name = reference_name,
|
||||
content = clean_email_html(content),
|
||||
comment_email = comment_email,
|
||||
comment_type = 'Comment'
|
||||
)).insert(ignore_permissions = True)
|
||||
|
||||
return doc.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_comment(name, content):
|
||||
"""allow only owner to update comment"""
|
||||
doc = frappe.get_doc('Communication', name)
|
||||
doc = frappe.get_doc('Comment', name)
|
||||
|
||||
if frappe.session.user not in ['Administrator', doc.owner]:
|
||||
frappe.throw(_('Comment can only be edited by the owner'), frappe.PermissionError)
|
||||
|
|
|
|||
|
|
@ -64,13 +64,12 @@ def _toggle_like(doctype, name, add, user=None):
|
|||
def remove_like(doctype, name):
|
||||
"""Remove previous Like"""
|
||||
# remove Comment
|
||||
frappe.delete_doc("Communication", [c.name for c in frappe.get_all("Communication",
|
||||
frappe.delete_doc("Comment", [c.name for c in frappe.get_all("Comment",
|
||||
filters={
|
||||
"communication_type": "Comment",
|
||||
"comment_type": "Like",
|
||||
"reference_doctype": doctype,
|
||||
"reference_name": name,
|
||||
"owner": frappe.session.user,
|
||||
"comment_type": "Like"
|
||||
}
|
||||
)], ignore_permissions=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.boot import get_allowed_pages, get_allowed_reports
|
||||
from frappe.desk.doctype.desktop_icon.desktop_icon import set_hidden, clear_desktop_icons_cache
|
||||
|
|
@ -24,7 +25,7 @@ def hide_module(module):
|
|||
set_hidden(module, frappe.session.user, 1)
|
||||
clear_desktop_icons_cache()
|
||||
|
||||
def get_data(module):
|
||||
def get_data(module, build=True):
|
||||
"""Get module data for the module view `desk/#Module/[name]`"""
|
||||
doctype_info = get_doctype_info(module)
|
||||
data = build_config_from_file(module)
|
||||
|
|
@ -40,7 +41,43 @@ def get_data(module):
|
|||
data = combine_common_sections(data)
|
||||
data = apply_permissions(data)
|
||||
|
||||
#set_last_modified(data)
|
||||
# set_last_modified(data)
|
||||
|
||||
if build:
|
||||
exists_cache = {}
|
||||
def doctype_contains_a_record(name):
|
||||
exists = exists_cache.get(name)
|
||||
if not exists:
|
||||
if not frappe.db.get_value('DocType', name, 'issingle'):
|
||||
exists = frappe.db.count(name)
|
||||
else:
|
||||
exists = True
|
||||
exists_cache[name] = exists
|
||||
return exists
|
||||
|
||||
for section in data:
|
||||
for item in section["items"]:
|
||||
# Onboarding
|
||||
|
||||
# First disable based on exists of depends_on list
|
||||
doctype = item.get("doctype")
|
||||
dependencies = item.get("dependencies") or None
|
||||
if not dependencies and doctype:
|
||||
item["dependencies"] = [doctype]
|
||||
|
||||
dependencies = item.get("dependencies")
|
||||
if dependencies:
|
||||
incomplete_dependencies = [d for d in dependencies if not doctype_contains_a_record(d)]
|
||||
if len(incomplete_dependencies):
|
||||
item["incomplete_dependencies"] = incomplete_dependencies
|
||||
|
||||
if item.get("onboard"):
|
||||
# Mark Spotlights for initial
|
||||
if item.get("type") == "doctype":
|
||||
name = item.get("name")
|
||||
count = doctype_contains_a_record(name)
|
||||
|
||||
item["count"] = count
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -184,14 +221,24 @@ def get_config(app, module):
|
|||
config = frappe.get_module("{app}.config.{module}".format(app=app, module=module))
|
||||
config = config.get_data()
|
||||
|
||||
for section in config:
|
||||
sections = [s for s in config if s.get("condition", True)]
|
||||
|
||||
for section in sections:
|
||||
for item in section["items"]:
|
||||
if item["type"]=="report" and frappe.db.get_value("Report", item["name"], "disabled")==1:
|
||||
section["items"].remove(item)
|
||||
continue
|
||||
if not "label" in item:
|
||||
item["label"] = _(item["name"])
|
||||
return config
|
||||
|
||||
return sections
|
||||
|
||||
def config_exists(app, module):
|
||||
try:
|
||||
frappe.get_module("{app}.config.{module}".format(app=app, module=module))
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def add_setup_section(config, app, module, label, icon):
|
||||
"""Add common sections to `/desk#Module/Setup`"""
|
||||
|
|
@ -213,6 +260,75 @@ def get_setup_section(app, module, label, icon):
|
|||
"items": section["items"]
|
||||
}
|
||||
|
||||
|
||||
def get_onboard_items(app, module):
|
||||
try:
|
||||
sections = get_config(app, module)
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
onboard_items = []
|
||||
fallback_items = []
|
||||
|
||||
if not sections:
|
||||
doctype_info = get_doctype_info(module)
|
||||
sections = build_standard_config(module, doctype_info)
|
||||
|
||||
for section in sections:
|
||||
for item in section["items"]:
|
||||
if item.get("onboard", 0) == 1:
|
||||
onboard_items.append(item)
|
||||
|
||||
# in case onboard is not set
|
||||
fallback_items.append(item)
|
||||
|
||||
if len(onboard_items) > 5:
|
||||
return onboard_items
|
||||
|
||||
return onboard_items or fallback_items
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_links(app, module):
|
||||
try:
|
||||
sections = get_config(app, frappe.scrub(module))
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
link_names = []
|
||||
|
||||
for section in sections:
|
||||
for item in section["items"]:
|
||||
link_names.append(item.get("label"))
|
||||
print(link_names)
|
||||
return link_names
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_module_link_items_from_dict(module_link_list_map):
|
||||
module_link_list_map = json.loads(module_link_list_map)
|
||||
module_links = {}
|
||||
for module, data in module_link_list_map.items():
|
||||
print(data)
|
||||
module_links[module] = get_module_link_items_from_list(data["app"], module, data["links"])
|
||||
return module_links
|
||||
|
||||
|
||||
def get_module_link_items_from_list(app, module, list_of_link_names):
|
||||
try:
|
||||
sections = get_config(app, frappe.scrub(module))
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
links = []
|
||||
for section in sections:
|
||||
for item in section["items"]:
|
||||
if item.get("label", "") in list_of_link_names:
|
||||
links.append(item)
|
||||
|
||||
return links
|
||||
|
||||
|
||||
def set_last_modified(data):
|
||||
for section in data:
|
||||
for item in section["items"]:
|
||||
|
|
|
|||
|
|
@ -251,6 +251,11 @@ def get_open_count(doctype, name, items=[]):
|
|||
:param transactions: List of transactions (json/dict)
|
||||
:param filters: optional filters (json/list)'''
|
||||
|
||||
if frappe.flags.in_migrate or frappe.flags.in_install:
|
||||
return {
|
||||
'count': []
|
||||
}
|
||||
|
||||
frappe.has_permission(doc=frappe.get_doc(doctype, name), throw=True)
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
|
|
|||
|
|
@ -53,14 +53,7 @@ frappe.pages['activity'].on_page_load = function(wrapper) {
|
|||
}
|
||||
|
||||
frappe.set_route("List", "Activity Log", "Report");
|
||||
}, 'fa fa-th')
|
||||
|
||||
this.page.add_menu_item(__('Show Likes'), function() {
|
||||
frappe.route_options = {
|
||||
show_likes: true
|
||||
};
|
||||
me.page.list.refresh();
|
||||
}, 'octicon octicon-heart');
|
||||
}, 'fa fa-th');
|
||||
};
|
||||
|
||||
frappe.pages['activity'].on_page_show = function() {
|
||||
|
|
@ -158,9 +151,7 @@ frappe.activity.render_heatmap = function(page) {
|
|||
data: {}
|
||||
});
|
||||
|
||||
heatmap.update({
|
||||
dataPoints: r.message
|
||||
});
|
||||
heatmap.update(r.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -196,8 +187,7 @@ frappe.views.Activity = class Activity extends frappe.views.BaseList {
|
|||
get_args() {
|
||||
return {
|
||||
start: this.start,
|
||||
page_length: this.page_length,
|
||||
show_likes: (frappe.route_options || {}).show_likes || 0
|
||||
page_length: this.page_length
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,43 +7,45 @@ from frappe.utils import cint
|
|||
from frappe.core.doctype.activity_log.feed import get_feed_match_conditions
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_feed(start, page_length, show_likes=False):
|
||||
def get_feed(start, page_length):
|
||||
"""get feed"""
|
||||
match_conditions = get_feed_match_conditions(frappe.session.user)
|
||||
match_conditions_communication = get_feed_match_conditions(frappe.session.user, 'Communication')
|
||||
match_conditions_comment = get_feed_match_conditions(frappe.session.user, 'Comment')
|
||||
|
||||
result = frappe.db.sql("""select X.*
|
||||
from (select name, owner, modified, creation, seen, comment_type,
|
||||
reference_doctype, reference_name, link_doctype, link_name, subject,
|
||||
communication_type, communication_medium, content
|
||||
from `tabCommunication`
|
||||
reference_doctype, reference_name, link_doctype, link_name, subject,
|
||||
communication_type, communication_medium, content
|
||||
from
|
||||
`tabCommunication`
|
||||
where
|
||||
communication_type in ("Communication", "Comment")
|
||||
and communication_medium != "Email"
|
||||
and (comment_type is null or comment_type != "Like"
|
||||
or (comment_type="Like" and (owner=%(user)s or reference_owner=%(user)s)))
|
||||
{match_conditions}
|
||||
{show_likes}
|
||||
union
|
||||
communication_type = "Communication"
|
||||
and communication_medium != "Email"
|
||||
and {match_conditions_communication}
|
||||
UNION
|
||||
select name, owner, modified, creation, '0', 'Updated',
|
||||
reference_doctype, reference_name, link_doctype, link_name, subject,
|
||||
'Comment', '', content
|
||||
from `tabActivity Log`) X
|
||||
reference_doctype, reference_name, link_doctype, link_name, subject,
|
||||
'Comment', '', content
|
||||
from
|
||||
`tabActivity Log`
|
||||
UNION
|
||||
select name, owner, modified, creation, '0', comment_type,
|
||||
reference_doctype, reference_name, link_doctype, link_name, '',
|
||||
'Comment', '', content
|
||||
from
|
||||
`tabComment`
|
||||
where
|
||||
{match_conditions_comment}
|
||||
) X
|
||||
order by X.creation DESC
|
||||
limit %(start)s, %(page_length)s"""
|
||||
.format(match_conditions="and {0}".format(match_conditions) if match_conditions else "",
|
||||
show_likes="and comment_type='Like'" if show_likes else ""),
|
||||
{
|
||||
.format(match_conditions_comment = match_conditions_comment,
|
||||
match_conditions_communication = match_conditions_communication), {
|
||||
"user": frappe.session.user,
|
||||
"start": cint(start),
|
||||
"page_length": cint(page_length)
|
||||
}, as_dict=True)
|
||||
|
||||
if show_likes:
|
||||
# mark likes as seen!
|
||||
frappe.db.sql("""update `tabCommunication` set seen=1
|
||||
where comment_type='Like' and reference_owner=%s""", frappe.session.user)
|
||||
frappe.local.flags.commit = True
|
||||
|
||||
return result
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
frappe.pages['modules'].on_page_load = function(wrapper) {
|
||||
var page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'Modules',
|
||||
single_column: false
|
||||
});
|
||||
|
||||
frappe.modules_page = page;
|
||||
frappe.module_links = {};
|
||||
page.section_data = {};
|
||||
|
||||
page.wrapper.find('.page-head h1').css({'padding-left': '15px'});
|
||||
// page.wrapper.find('.page-content').css({'margin-top': '0px'});
|
||||
|
||||
// menu
|
||||
page.add_menu_item(__('Set Desktop Icons'), function() {
|
||||
frappe.frappe_toolbar.modules_select
|
||||
.show(frappe.session.user);
|
||||
});
|
||||
|
||||
if(frappe.user.has_role('System Manager')) {
|
||||
page.add_menu_item(__('Install Apps'), function() {
|
||||
frappe.set_route("applications");
|
||||
});
|
||||
}
|
||||
|
||||
page.get_page_modules = () => {
|
||||
return frappe.get_desktop_icons(true)
|
||||
.filter(d => d.type==='module' && !d.blocked)
|
||||
.sort((a, b) => { return (a._label > b._label) ? 1 : -1; });
|
||||
};
|
||||
|
||||
let get_module_sidebar_item = (item) => `<li class="strong module-sidebar-item">
|
||||
<a class="module-link" data-name="${item.module_name}" href="#${item.link}">
|
||||
<i class="fa fa-chevron-right pull-right" style="display: none;"></i>
|
||||
<span>${item._label}</span>
|
||||
</a>
|
||||
</li>`;
|
||||
|
||||
let get_sidebar_html = () => {
|
||||
let sidebar_items_html = page.get_page_modules()
|
||||
.map(get_module_sidebar_item.bind(this)).join("");
|
||||
|
||||
return `<ul class="module-sidebar-nav overlay-sidebar nav nav-pills nav-stacked">
|
||||
${sidebar_items_html}
|
||||
<li class="divider"></li>
|
||||
</ul>`;
|
||||
};
|
||||
|
||||
// render sidebar
|
||||
page.sidebar.html(get_sidebar_html());
|
||||
|
||||
// help click
|
||||
page.main.on("click", '.module-section-link[data-type="help"]', function() {
|
||||
frappe.help.show_video($(this).attr("data-youtube-id"));
|
||||
return false;
|
||||
});
|
||||
|
||||
// notifications click
|
||||
page.main.on("click", '.open-notification', function() {
|
||||
var doctype = $(this).attr('data-doctype');
|
||||
if(doctype) {
|
||||
frappe.ui.notifications.show_open_count_list(doctype);
|
||||
}
|
||||
});
|
||||
|
||||
page.activate_link = function(link) {
|
||||
page.last_link = link;
|
||||
page.wrapper.find('.module-sidebar-item.active, .module-link.active').removeClass('active');
|
||||
$(link).addClass('active').parent().addClass("active");
|
||||
show_section($(link).attr('data-name'));
|
||||
};
|
||||
|
||||
var show_section = function(module_name) {
|
||||
if (!module_name) return;
|
||||
if(module_name in page.section_data) {
|
||||
render_section(page.section_data[module_name]);
|
||||
} else {
|
||||
page.main.empty();
|
||||
return frappe.call({
|
||||
method: "frappe.desk.moduleview.get",
|
||||
args: {
|
||||
module: module_name
|
||||
},
|
||||
callback: function(r) {
|
||||
var m = frappe.get_module(module_name);
|
||||
m.data = r.message.data;
|
||||
process_data(module_name, m.data);
|
||||
page.section_data[module_name] = m;
|
||||
render_section(m);
|
||||
},
|
||||
freeze: true,
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var render_section = function(m) {
|
||||
page.set_title(__(m.label));
|
||||
page.main.html(frappe.render_template('modules_section', m));
|
||||
|
||||
// if(frappe.utils.is_xs() || frappe.utils.is_sm()) {
|
||||
// // call this after a timeout, becuase a refresh will set the page to the top
|
||||
// setTimeout(function() {
|
||||
// $(document).scrollTop($('.module-body').offset().top - 150);
|
||||
// }, 100);
|
||||
// }
|
||||
|
||||
//setup_section_toggle();
|
||||
frappe.app.update_notification_count_in_modules();
|
||||
};
|
||||
|
||||
var process_data = function(module_name, data) {
|
||||
frappe.module_links[module_name] = [];
|
||||
data.forEach(function(section) {
|
||||
section.items.forEach(function(item) {
|
||||
item.style = '';
|
||||
if(item.type==="doctype") {
|
||||
item.doctype = item.name;
|
||||
|
||||
// map of doctypes that belong to a module
|
||||
frappe.module_links[module_name].push(item.name);
|
||||
}
|
||||
if(!item.route) {
|
||||
if(item.link) {
|
||||
item.route=strip(item.link, "#");
|
||||
}
|
||||
else if(item.type==="doctype") {
|
||||
if(frappe.model.is_single(item.doctype)) {
|
||||
item.route = 'Form/' + item.doctype;
|
||||
} else {
|
||||
if (item.filters) {
|
||||
frappe.route_options=item.filters;
|
||||
}
|
||||
item.route="List/" + item.doctype;
|
||||
//item.style = 'font-weight: 500;';
|
||||
}
|
||||
// item.style = 'font-weight: bold;';
|
||||
}
|
||||
else if(item.type==="report" && item.is_query_report) {
|
||||
item.route="query-report/" + item.name;
|
||||
}
|
||||
else if(item.type==="report") {
|
||||
item.route="List/" + item.doctype + "/Report/" + item.name;
|
||||
}
|
||||
else if(item.type==="page") {
|
||||
item.route=item.name;
|
||||
}
|
||||
}
|
||||
|
||||
if(item.route_options) {
|
||||
item.route += "?" + $.map(item.route_options, function(value, key) {
|
||||
return encodeURIComponent(key) + "=" + encodeURIComponent(value); }).join('&');
|
||||
}
|
||||
|
||||
if(item.type==="page" || item.type==="help" || item.type==="report" ||
|
||||
(item.doctype && frappe.model.can_read(item.doctype))) {
|
||||
item.shown = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
frappe.pages['modules'].on_page_show = function(wrapper) {
|
||||
let route = frappe.get_route();
|
||||
let modules = frappe.modules_page.get_page_modules().map(d => d.module_name);
|
||||
$("body").attr("data-sidebar", 1);
|
||||
if(route.length > 1) {
|
||||
// activate section based on route
|
||||
let module_name = route[1];
|
||||
if(modules.includes(module_name)) {
|
||||
frappe.modules_page.activate_link(
|
||||
frappe.modules_page.sidebar.find('.module-link[data-name="'+ module_name +'"]'));
|
||||
} else {
|
||||
frappe.throw(__(`Module ${module_name} not found.`));
|
||||
}
|
||||
} else if(frappe.modules_page.last_link) {
|
||||
// open last link
|
||||
frappe.set_route('modules', frappe.modules_page.last_link.attr('data-name'));
|
||||
} else {
|
||||
// first time, open the first page
|
||||
frappe.modules_page.activate_link(frappe.modules_page.sidebar.find('.module-link:first'));
|
||||
}
|
||||
};
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"content": null,
|
||||
"creation": "2016-03-07 04:46:00.420330",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2016-03-07 04:46:00.420330",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "modules",
|
||||
"owner": "Administrator",
|
||||
"page_name": "modules",
|
||||
"roles": [],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"title": "Modules"
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
<div class="module-body">
|
||||
{% for (var i=0; i < data.length; i++) { var section = data[i]; %}
|
||||
{% if ((i % 2)===0) { %}<div class="row module-section">{% } %}
|
||||
<div class="col-sm-6 module-section-column">
|
||||
<div class="h4 section-head">
|
||||
{{ section.label }}
|
||||
</div>
|
||||
<div class="section-body">
|
||||
{% for (var j=0; j < section.items.length; j++) {
|
||||
var item = section.items[j];
|
||||
if(item.shown) { %}
|
||||
<div style="min-height: 22px; margin-top: 8px;">
|
||||
<a class="module-section-link small" data-type="{{ item.type }}"
|
||||
{% if(item.type==="help") { %}
|
||||
data-youtube-id="{{ item.youtube_id }}"{% } %}
|
||||
href="#{{ item.route }}" style="{{ item.style }}">
|
||||
{{ item.label || __(item.name) }}
|
||||
</a>
|
||||
{% if(item.type==="doctype") { %}
|
||||
<span class="open-notification global hide"
|
||||
data-doctype="{{ item.doctype || item.name }}"></span>
|
||||
{% } %}
|
||||
</div>
|
||||
{% } %}
|
||||
{% } %}
|
||||
</div>
|
||||
</div>
|
||||
{% if ((i % 2)===1 || i===data.length-1) { %}</div>{% } %}
|
||||
{% } %}
|
||||
</div>
|
||||
|
|
@ -190,30 +190,34 @@ def run(report_name, filters=None, user=None):
|
|||
|
||||
def get_prepared_report_result(report, filters, dn="", user=None):
|
||||
latest_report_data = {}
|
||||
# Only look for completed prepared reports with given filters.
|
||||
doc_list = frappe.get_all("Prepared Report",
|
||||
filters={"status": "Completed", "report_name": report.name, "filters": filters, "owner": user})
|
||||
|
||||
doc = None
|
||||
if len(doc_list):
|
||||
if dn:
|
||||
# Get specified dn
|
||||
doc = frappe.get_doc("Prepared Report", dn)
|
||||
else:
|
||||
if dn:
|
||||
# Get specified dn
|
||||
doc = frappe.get_doc("Prepared Report", dn)
|
||||
else:
|
||||
# Only look for completed prepared reports with given filters.
|
||||
doc_list = frappe.get_all("Prepared Report", filters={"status": "Completed", "filters": json.dumps(filters), "owner": user})
|
||||
if doc_list:
|
||||
# Get latest
|
||||
doc = frappe.get_doc("Prepared Report", doc_list[0])
|
||||
|
||||
# Prepared Report data is stored in a GZip compressed JSON file
|
||||
attached_file_name = frappe.db.get_value("File", {"attached_to_doctype": doc.doctype, "attached_to_name":doc.name}, "name")
|
||||
attached_file = frappe.get_doc('File', attached_file_name)
|
||||
compressed_content = attached_file.get_content()
|
||||
uncompressed_content = gzip_decompress(compressed_content)
|
||||
data = json.loads(uncompressed_content)
|
||||
if data:
|
||||
latest_report_data = {
|
||||
"columns": json.loads(doc.columns) if doc.columns else data[0],
|
||||
"result": data
|
||||
}
|
||||
if doc:
|
||||
try:
|
||||
# Prepared Report data is stored in a GZip compressed JSON file
|
||||
attached_file_name = frappe.db.get_value("File", {"attached_to_doctype": doc.doctype, "attached_to_name":doc.name}, "name")
|
||||
attached_file = frappe.get_doc('File', attached_file_name)
|
||||
compressed_content = attached_file.get_content()
|
||||
uncompressed_content = gzip_decompress(compressed_content)
|
||||
data = json.loads(uncompressed_content)
|
||||
if data:
|
||||
latest_report_data = {
|
||||
"columns": json.loads(doc.columns) if doc.columns else data[0],
|
||||
"result": data
|
||||
}
|
||||
except Exception:
|
||||
frappe.delete_doc("Prepared Report", doc.name)
|
||||
frappe.db.commit()
|
||||
doc = None
|
||||
|
||||
latest_report_data.update({
|
||||
"prepared_report": True,
|
||||
|
|
@ -238,45 +242,53 @@ def export_query():
|
|||
report_name = data["report_name"]
|
||||
if isinstance(data.get("file_format_type"), string_types):
|
||||
file_format_type = data["file_format_type"]
|
||||
|
||||
if isinstance(data.get("visible_idx"), string_types):
|
||||
visible_idx = json.loads(data.get("visible_idx"))
|
||||
else:
|
||||
visible_idx = None
|
||||
|
||||
if file_format_type == "Excel":
|
||||
|
||||
data = run(report_name, filters)
|
||||
data = frappe._dict(data)
|
||||
columns = get_columns_dict(data.columns)
|
||||
|
||||
result = [[]]
|
||||
|
||||
# add column headings
|
||||
for idx in range(len(data.columns)):
|
||||
result[0].append(columns[idx]["label"])
|
||||
|
||||
# build table from dict
|
||||
if isinstance(data.result[0], dict):
|
||||
for i,row in enumerate(data.result):
|
||||
# only rows which are visible in the report
|
||||
if row and (i in visible_idx):
|
||||
row_list = []
|
||||
for idx in range(len(data.columns)):
|
||||
row_list.append(row.get(columns[idx]["fieldname"], row.get(columns[idx]["label"], "")))
|
||||
result.append(row_list)
|
||||
elif not row:
|
||||
result.append([])
|
||||
else:
|
||||
result = result + [d for i,d in enumerate(data.result) if (i in visible_idx)]
|
||||
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
xlsx_file = make_xlsx(result, "Query Report")
|
||||
xlsx_data = build_xlsx_data(columns, data, visible_idx)
|
||||
xlsx_file = make_xlsx(xlsx_data, "Query Report")
|
||||
|
||||
frappe.response['filename'] = report_name + '.xlsx'
|
||||
frappe.response['filecontent'] = xlsx_file.getvalue()
|
||||
frappe.response['type'] = 'binary'
|
||||
|
||||
|
||||
def build_xlsx_data(columns, data, visible_idx):
|
||||
result = [[]]
|
||||
|
||||
# add column headings
|
||||
for idx in range(len(data.columns)):
|
||||
result[0].append(columns[idx]["label"])
|
||||
|
||||
# build table from result
|
||||
for i, row in enumerate(data.result):
|
||||
# only pick up rows that are visible in the report
|
||||
if i in visible_idx:
|
||||
row_data = []
|
||||
|
||||
if isinstance(row, list):
|
||||
row_data = row
|
||||
elif isinstance(row, dict) and row:
|
||||
for idx in range(len(data.columns)):
|
||||
label = columns[idx]["label"]
|
||||
fieldname = columns[idx]["fieldname"]
|
||||
|
||||
row_data.append(row.get(fieldname, row.get(label, "")))
|
||||
|
||||
result.append(row_data)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_report_module_dotted_path(module, report_name):
|
||||
return frappe.local.module_app[scrub(module)] + "." + scrub(module) \
|
||||
+ ".report." + scrub(report_name) + "." + scrub(report_name)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ def get_form_params():
|
|||
"""Stringify GET request parameters."""
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
|
||||
del data["cmd"]
|
||||
data.pop('cmd', None)
|
||||
data.pop('data', None)
|
||||
|
||||
if "csrf_token" in data:
|
||||
del data["csrf_token"]
|
||||
|
||||
|
|
@ -212,23 +214,27 @@ def delete_items():
|
|||
"""delete selected items"""
|
||||
import json
|
||||
|
||||
il = sorted(json.loads(frappe.form_dict.get('items')), reverse=True)
|
||||
items = sorted(json.loads(frappe.form_dict.get('items')), reverse=True)
|
||||
doctype = frappe.form_dict.get('doctype')
|
||||
|
||||
failed = []
|
||||
if len(items) > 10:
|
||||
frappe.enqueue('frappe.desk.reportview.delete_bulk',
|
||||
doctype=doctype, items=items)
|
||||
else:
|
||||
delete_bulk(doctype, items)
|
||||
|
||||
for i, d in enumerate(il):
|
||||
def delete_bulk(doctype, items):
|
||||
failed = []
|
||||
for i, d in enumerate(items):
|
||||
try:
|
||||
frappe.delete_doc(doctype, d)
|
||||
if len(il) >= 5:
|
||||
if len(items) >= 5:
|
||||
frappe.publish_realtime("progress",
|
||||
dict(progress=[i+1, len(il)], title=_('Deleting {0}').format(doctype), description=d),
|
||||
dict(progress=[i+1, len(items)], title=_('Deleting {0}').format(doctype), description=d),
|
||||
user=frappe.session.user)
|
||||
except Exception:
|
||||
failed.append(d)
|
||||
|
||||
return failed
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_sidebar_stats(stats, doctype, filters=[]):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
|
|
@ -1063,6 +1064,40 @@
|
|||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "enable_outgoing",
|
||||
"description": "Uses the Email Address Name mentioned in this Account as the Sender's Name for all emails sent using this Account.",
|
||||
"fieldname": "always_use_account_name_as_sender_name",
|
||||
"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": "Always use Account's Name as Sender's Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
|
|
@ -1563,7 +1598,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-01-30 11:02:41.011412",
|
||||
"modified": "2019-02-12 17:09:50.653403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Account",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,13 @@ import frappe
|
|||
from frappe.model.document import Document
|
||||
|
||||
class EmailGroupMember(Document):
|
||||
pass
|
||||
def after_delete(self):
|
||||
email_group = frappe.get_doc('Email Group', self.email_group)
|
||||
email_group.update_total_subscribers()
|
||||
|
||||
def after_insert(self):
|
||||
email_group = frappe.get_doc('Email Group', self.email_group)
|
||||
email_group.update_total_subscribers()
|
||||
|
||||
def after_doctype_insert():
|
||||
frappe.db.add_unique("Email Group Member", ("email_group", "email"))
|
||||
frappe.db.add_unique("Email Group Member", ("email_group", "email"))
|
||||
|
|
|
|||
|
|
@ -35,5 +35,5 @@ class EmailUnsubscribe(Document):
|
|||
def on_update(self):
|
||||
if self.reference_doctype and self.reference_name:
|
||||
doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||
doc.add_comment("Label", _("Left this conversation"), comment_by=self.email)
|
||||
doc.add_comment("Label", _("Left this conversation"), comment_email=self.email)
|
||||
|
||||
|
|
|
|||
|
|
@ -216,8 +216,15 @@ def get_context(context):
|
|||
please enable Allow Print For {0} in Print Settings""".format(status)),
|
||||
title=_("Error in Notification"))
|
||||
else:
|
||||
return [{"print_format_attachment":1, "doctype":doc.doctype, "name": doc.name,
|
||||
"print_format":self.print_format, "print_letterhead": print_settings.with_letterhead}]
|
||||
return [{
|
||||
"print_format_attachment": 1,
|
||||
"doctype": doc.doctype,
|
||||
"name": doc.name,
|
||||
"print_format": self.print_format,
|
||||
"print_letterhead": print_settings.with_letterhead,
|
||||
"lang": frappe.db.get_value('Print Format', self.print_format, 'default_print_language')
|
||||
if self.print_format else 'en'
|
||||
}]
|
||||
|
||||
|
||||
def get_template(self):
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ class EMail:
|
|||
self.reply_to = validate_email_add(strip(self.reply_to) or self.sender, True)
|
||||
|
||||
self.replace_sender()
|
||||
self.replace_sender_name()
|
||||
|
||||
self.recipients = [strip(r) for r in self.recipients]
|
||||
self.cc = [strip(r) for r in self.cc]
|
||||
|
|
@ -188,6 +189,12 @@ class EMail:
|
|||
sender_name, sender_email = parse_addr(self.sender)
|
||||
self.sender = email.utils.formataddr((str(Header(sender_name or self.email_account.name, 'utf-8')), self.email_account.email_id))
|
||||
|
||||
def replace_sender_name(self):
|
||||
if cint(self.email_account.always_use_account_name_as_sender_name):
|
||||
self.set_header('X-Original-From', self.sender)
|
||||
sender_name, sender_email = parse_addr(self.sender)
|
||||
self.sender = email.utils.formataddr((str(Header(self.email_account.name, 'utf-8')), sender_email))
|
||||
|
||||
def set_message_id(self, message_id, is_notification=False):
|
||||
if message_id:
|
||||
self.msg_root["Message-Id"] = '<' + message_id + '>'
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue