Merge branch 'develop' of https://github.com/frappe/frappe into website-bootstrap-4
This commit is contained in:
commit
5a96fc70de
209 changed files with 8404 additions and 5697 deletions
|
|
@ -116,6 +116,7 @@
|
|||
"md5": true,
|
||||
"$": true,
|
||||
"jQuery": true,
|
||||
"Vue": true,
|
||||
"moment": true,
|
||||
"hljs": true,
|
||||
"Awesomplete": true,
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ context('Relative Timeframe', () => {
|
|||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/'
|
||||
url: '/api/method/frappe.desk.reportview.get'
|
||||
}).as('applyFilter');
|
||||
cy.get('.filter-box .btn:contains("Apply")').click();
|
||||
cy.wait('@applyFilter');
|
||||
|
|
@ -39,7 +39,7 @@ context('Relative Timeframe', () => {
|
|||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/'
|
||||
url: '/api/method/frappe.desk.reportview.get'
|
||||
}).as('applyFilter');
|
||||
cy.get('.filter-box .btn:contains("Apply")').click();
|
||||
cy.wait('@applyFilter');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -50,3 +50,7 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => {
|
|||
return cy.get('@input').type(value);
|
||||
}
|
||||
});
|
||||
|
||||
Cypress.Commands.add('awesomebar', (text) => {
|
||||
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, { delay: 100 });
|
||||
});
|
||||
|
|
|
|||
1
frappe/.pylintrc
Normal file
1
frappe/.pylintrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
disable=access-member-before-definition
|
||||
|
|
@ -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.3'
|
||||
__version__ = '11.1.8'
|
||||
__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,9 +919,13 @@ 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:
|
||||
if no_cache:
|
||||
hooks = _dict(load_app_hooks())
|
||||
else:
|
||||
hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -78,19 +78,14 @@ def create(user, exists_ok = False, fields = None):
|
|||
|
||||
exists_ok, fields = safe_json_loads(exists_ok, fields)
|
||||
|
||||
result = frappe.db.sql("""
|
||||
SELECT *
|
||||
FROM `tabChat Profile`
|
||||
WHERE `user` = '{user}'
|
||||
""".format(user = user))
|
||||
|
||||
if result:
|
||||
if not exists_ok:
|
||||
frappe.throw(_('Chat Profile for User {0} exists.').format(user))
|
||||
else:
|
||||
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))
|
||||
|
||||
profile = get(user, fields = fields)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
import frappe
|
||||
from frappe.desk.moduleview import get_data
|
||||
from six import iteritems
|
||||
|
||||
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]
|
||||
|
||||
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):
|
||||
if module_name not in active_domains:
|
||||
to_add = False
|
||||
|
||||
if "condition" in m and not m["condition"]:
|
||||
to_add = False
|
||||
|
||||
if to_add:
|
||||
onboard_present = is_onboard_present(m) if show_onboard(m) else False
|
||||
m["onboard_present"] = onboard_present
|
||||
active_modules_list.append(m)
|
||||
|
||||
return active_modules_list
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_onboard_present(module):
|
||||
exists_cache = {}
|
||||
def exists(name, link_type):
|
||||
exists = exists_cache.get(name)
|
||||
if not exists:
|
||||
if link_type == "doctype" and not frappe.db.get_value('DocType', name, 'issingle'):
|
||||
exists = frappe.db.count(name)
|
||||
else:
|
||||
exists = True
|
||||
exists_cache[name] = exists
|
||||
return exists
|
||||
|
||||
sections = get_data(module["module_name"], False)
|
||||
for section in sections:
|
||||
for item in section["items"]:
|
||||
if exists(item.get("name"), item.get("type")):
|
||||
return True
|
||||
return False
|
||||
|
||||
def show_onboard(module):
|
||||
return module.get("type") == "module"
|
||||
|
||||
def is_domain(module):
|
||||
return module.get("category") == "Domains"
|
||||
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,109 @@
|
|||
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
|
||||
"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",
|
||||
"hidden": 1,
|
||||
"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",
|
||||
"hidden": 1,
|
||||
"description": "Customize forms, custom fields, scripts and translations."
|
||||
},
|
||||
{
|
||||
"module_name": "Integrations",
|
||||
"category": "Administration",
|
||||
"label": _("Integrations"),
|
||||
"color": "#16a085",
|
||||
"icon": "octicon octicon-globe",
|
||||
"type": "module",
|
||||
"hidden": 1,
|
||||
"description": "DropBox, Woocomerce, AWS, Shopify and GoCardless."
|
||||
},
|
||||
{
|
||||
"module_name": 'Contacts',
|
||||
"category": "Administration",
|
||||
"label": _("Contacts"),
|
||||
"type": 'module',
|
||||
"icon": "octicon octicon-book",
|
||||
"color": '#ffaedb',
|
||||
"hidden": 1,
|
||||
"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
|
||||
},
|
||||
{
|
||||
"module_name": 'Contacts',
|
||||
"type": 'module',
|
||||
"icon": "octicon octicon-book",
|
||||
"color": '#FFAEDB',
|
||||
"condition": getattr(frappe.local.conf, 'developer_mode', 0),
|
||||
"hidden": 1,
|
||||
"description": "Doctypes, dev tools and logs."
|
||||
},
|
||||
|
||||
# Places
|
||||
{
|
||||
"module_name": "Website",
|
||||
"category": "Places",
|
||||
"label": _("Website"),
|
||||
"_label": _("Website"),
|
||||
"color": "#16a085",
|
||||
"icon": "octicon octicon-globe",
|
||||
"type": "module",
|
||||
"hidden": 1,
|
||||
"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,8 +1,11 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
from frappe.desk.moduleview import add_setup_section
|
||||
|
||||
def get_data():
|
||||
return [{
|
||||
"label": _("Settings"),
|
||||
data = [
|
||||
{
|
||||
"label": _("Core"),
|
||||
"icon": "fa fa-wrench",
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -12,6 +15,16 @@ def get_data():
|
|||
"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",
|
||||
|
|
@ -19,33 +32,157 @@ def get_data():
|
|||
"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",
|
||||
"label": _("Print Settings"),
|
||||
"description": _("Print Style, PDF Size"),
|
||||
"hide_count": True
|
||||
"description": _("Set default format, page size, print style etc.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Settings",
|
||||
"label": _("Website Settings"),
|
||||
"description": _("Landing Page, Website Theme, Brand Setup and more"),
|
||||
"hide_count": True
|
||||
"name": "Print Format",
|
||||
"description": _("Customized HTML Templates for printing transactions.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "S3 Backup Settings",
|
||||
"label": _("S3 Backup Settings"),
|
||||
"description": _("Enable / Disable Backup, Backup Frequency"),
|
||||
"hide_count": True
|
||||
"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": "SMS Settings",
|
||||
"label": _("SMS Settings"),
|
||||
"description": _("SMS Gateway URL, Message & Receiver Parameter"),
|
||||
"hide_count": True
|
||||
"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": _("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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -35,6 +37,7 @@ def get_data():
|
|||
"type": "doctype",
|
||||
"name": "Blog Post",
|
||||
"description": _("Single Post (article)."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
|
|
@ -56,11 +59,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 +91,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) + ")"
|
||||
|
|
|
|||
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
|
||||
}
|
||||
176
frappe/core/doctype/comment/comment.py
Normal file
176
frappe/core/doctype/comment/comment.py
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
# -*- 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 = self.get_comments_from_parent()
|
||||
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 get_comments_from_parent(self):
|
||||
'''
|
||||
get the list of comments cached in the document record in the column
|
||||
`_comments`
|
||||
'''
|
||||
try:
|
||||
_comments = frappe.db.get_value(self.reference_doctype, self.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_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]
|
||||
}"""
|
||||
|
||||
# only comments get updates, not likes, assignments etc.
|
||||
if 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 = doc.get_comments_from_parent()
|
||||
|
||||
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),
|
||||
"by": doc.comment_email or doc.owner,
|
||||
"name": doc.name
|
||||
})
|
||||
|
||||
update_comments_in_parent(doc.reference_doctype, doc.reference_name, _comments)
|
||||
|
||||
|
||||
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,8 +6,6 @@ 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
|
||||
|
|
@ -87,19 +85,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()
|
||||
|
|
@ -114,23 +108,14 @@ class Communication(Document):
|
|||
"""Update parent status as `Open` or `Replied`."""
|
||||
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
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from frappe import _
|
|||
from frappe.utils.csvutils import getlink
|
||||
from frappe.utils.dateutils import parse_date
|
||||
|
||||
from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url, get_url_to_form
|
||||
from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url, get_absolute_url
|
||||
from six import text_type, string_types
|
||||
|
||||
|
||||
|
|
@ -418,16 +418,16 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
|
|||
# log errors
|
||||
if parentfield:
|
||||
log(**{"row": doc.idx, "title": 'Inserted row for "%s"' % (as_link(parenttype, doc.parent)),
|
||||
"link": get_url_to_form(parenttype, doc.parent), "message": 'Document successfully saved', "indicator": "green"})
|
||||
"link": get_absolute_url(parenttype, doc.parent), "message": 'Document successfully saved', "indicator": "green"})
|
||||
elif submit_after_import:
|
||||
log(**{"row": row_idx + 1, "title":'Submitted row for "%s"' % (as_link(doc.doctype, doc.name)),
|
||||
"message": "Document successfully submitted", "link": get_url_to_form(doc.doctype, doc.name), "indicator": "blue"})
|
||||
"message": "Document successfully submitted", "link": get_absolute_url(doc.doctype, doc.name), "indicator": "blue"})
|
||||
elif original:
|
||||
log(**{"row": row_idx + 1,"title":'Updated row for "%s"' % (as_link(doc.doctype, doc.name)),
|
||||
"message": "Document successfully updated", "link": get_url_to_form(doc.doctype, doc.name), "indicator": "green"})
|
||||
"message": "Document successfully updated", "link": get_absolute_url(doc.doctype, doc.name), "indicator": "green"})
|
||||
elif not update_only:
|
||||
log(**{"row": row_idx + 1, "title":'Inserted row for "%s"' % (as_link(doc.doctype, doc.name)),
|
||||
"message": "Document successfully saved", "link": get_url_to_form(doc.doctype, doc.name), "indicator": "green"})
|
||||
"message": "Document successfully saved", "link": get_absolute_url(doc.doctype, doc.name), "indicator": "green"})
|
||||
else:
|
||||
log(**{"row": row_idx + 1, "title":'Ignored row for %s' % (row[1]), "link": None,
|
||||
"message": "Document updation ignored", "indicator": "orange"})
|
||||
|
|
@ -444,7 +444,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
|
|||
error_trace = frappe.get_traceback()
|
||||
if error_trace:
|
||||
error_log_doc = frappe.log_error(error_trace)
|
||||
error_link = get_url_to_form("Error Log", error_log_doc.name)
|
||||
error_link = get_absolute_url("Error Log", error_log_doc.name)
|
||||
else:
|
||||
error_link = None
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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'''
|
||||
|
|
|
|||
|
|
@ -152,6 +152,8 @@ class Report(Document):
|
|||
|
||||
if params.get('sort_by'):
|
||||
order_by = _format(params.get('sort_by').split('.')) + ' ' + params.get('sort_order')
|
||||
elif params.get('order_by'):
|
||||
order_by = params.get('order_by')
|
||||
else:
|
||||
order_by = _format([self.ref_doctype, 'modified']) + ' desc'
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -2303,7 +2303,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 5,
|
||||
"modified": "2018-11-21 12:34:57.652854",
|
||||
"modified": "2019-01-30 13:56:10.732154",
|
||||
"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
|
||||
|
|
@ -358,6 +358,9 @@ class User(Document):
|
|||
WHERE `%s` = %s""" %
|
||||
(tab, field, '%s', field, '%s'), (new_name, old_name))
|
||||
|
||||
if frappe.db.exists("Chat Profile", old_name):
|
||||
frappe.rename_doc("Chat Profile", old_name, new_name, force=True)
|
||||
|
||||
# set email
|
||||
frappe.db.sql("""UPDATE `tabUser`
|
||||
SET email = %s
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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'))
|
||||
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),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -142,19 +154,15 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
|
|||
`communication_date`, `content`, `sender`, `sender_full_name`,
|
||||
`creation`, `subject`, `delivery_status`, `_liked_by`,
|
||||
`timeline_doctype`, `timeline_name`, `reference_doctype`, `reference_name`,
|
||||
`link_doctype`, `link_name`, `read_by_recipient`, `rating` '''
|
||||
`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')
|
||||
)))
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,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)
|
||||
|
|
@ -42,6 +42,42 @@ def get_data(module):
|
|||
|
||||
# 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
|
||||
|
||||
def build_config_from_file(module):
|
||||
|
|
@ -184,14 +220,17 @@ 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 add_setup_section(config, app, module, label, icon):
|
||||
"""Add common sections to `/desk#Module/Setup`"""
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
from
|
||||
`tabCommunication`
|
||||
where
|
||||
communication_type in ("Communication", "Comment")
|
||||
communication_type = "Communication"
|
||||
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
|
||||
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
|
||||
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,19 +190,19 @@ 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:
|
||||
# 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])
|
||||
|
||||
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)
|
||||
|
|
@ -214,6 +214,10 @@ def get_prepared_report_result(report, filters, dn="", user=None):
|
|||
"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,
|
||||
|
|
|
|||
|
|
@ -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, key=frappe.safe_decode)
|
||||
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"))
|
||||
|
|
@ -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 + '>'
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ def get_email_queue(recipients, sender, subject, **kwargs):
|
|||
if att.get('fid'):
|
||||
_attachments.append(att)
|
||||
elif att.get("print_format_attachment") == 1:
|
||||
if not att.get('lang', None):
|
||||
att['lang'] = frappe.local.lang
|
||||
att['print_letterhead'] = kwargs.get('print_letterhead')
|
||||
_attachments.append(att)
|
||||
|
|
|
|||
|
|
@ -190,12 +190,10 @@ class EmailServer:
|
|||
# compare the UIDVALIDITY of email account and imap server
|
||||
uid_validity = self.settings.uid_validity
|
||||
|
||||
responce, message = self.imap.status("Inbox", "(UIDVALIDITY UIDNEXT)")
|
||||
current_uid_validity = self.parse_imap_responce("UIDVALIDITY", message[0])
|
||||
if not current_uid_validity:
|
||||
frappe.throw(_("Can not find UIDVALIDITY in imap status response"))
|
||||
response, message = self.imap.status("Inbox", "(UIDVALIDITY UIDNEXT)")
|
||||
current_uid_validity = self.parse_imap_response("UIDVALIDITY", message[0]) or 0
|
||||
|
||||
uidnext = int(self.parse_imap_responce("UIDNEXT", message[0]) or "1")
|
||||
uidnext = int(self.parse_imap_response("UIDNEXT", message[0]) or "1")
|
||||
frappe.db.set_value("Email Account", self.settings.email_account, "uidnext", uidnext)
|
||||
|
||||
if not uid_validity or uid_validity != current_uid_validity:
|
||||
|
|
@ -223,9 +221,9 @@ class EmailServer:
|
|||
elif uid_validity == current_uid_validity:
|
||||
return
|
||||
|
||||
def parse_imap_responce(self, cmd, responce):
|
||||
def parse_imap_response(self, cmd, response):
|
||||
pattern = r"(?<={cmd} )[0-9]*".format(cmd=cmd)
|
||||
match = re.search(pattern, responce.decode('utf-8'), re.U | re.I)
|
||||
match = re.search(pattern, response.decode('utf-8'), re.U | re.I)
|
||||
if match:
|
||||
return match.group(0)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -109,7 +109,8 @@ def get_default_outgoing_email_account(raise_exception_not_set=True):
|
|||
"mail_password": "Super.Secret.Password",
|
||||
"auto_email_id": "emails@example.com",
|
||||
"email_sender_name": "Example Notifications",
|
||||
"always_use_account_email_id_as_sender": 0
|
||||
"always_use_account_email_id_as_sender": 0,
|
||||
"always_use_account_name_as_sender_name": 0
|
||||
}
|
||||
'''
|
||||
email_account = _get_email_account({"enable_outgoing": 1, "default_outgoing": 1})
|
||||
|
|
@ -128,7 +129,8 @@ def get_default_outgoing_email_account(raise_exception_not_set=True):
|
|||
"login_id": frappe.conf.get("mail_login"),
|
||||
"email_id": frappe.conf.get("auto_email_id") or frappe.conf.get("mail_login") or 'notifications@example.com',
|
||||
"password": frappe.conf.get("mail_password"),
|
||||
"always_use_account_email_id_as_sender": frappe.conf.get("always_use_account_email_id_as_sender", 0)
|
||||
"always_use_account_email_id_as_sender": frappe.conf.get("always_use_account_email_id_as_sender", 0),
|
||||
"always_use_account_name_as_sender_name": frappe.conf.get("always_use_account_name_as_sender_name", 0)
|
||||
})
|
||||
email_account.from_site_config = True
|
||||
email_account.name = frappe.conf.get("email_sender_name") or "Frappe"
|
||||
|
|
@ -182,6 +184,7 @@ class SMTPServer:
|
|||
self.use_tls = self.email_account.use_tls
|
||||
self.sender = self.email_account.email_id
|
||||
self.always_use_account_email_id_as_sender = cint(self.email_account.get("always_use_account_email_id_as_sender"))
|
||||
self.always_use_account_name_as_sender_name = cint(self.email_account.get("always_use_account_name_as_sender_name"))
|
||||
|
||||
@property
|
||||
def sess(self):
|
||||
|
|
|
|||
|
|
@ -158,9 +158,6 @@ def remove_app(app_name, dry_run=False, yes=False):
|
|||
if not dry_run:
|
||||
frappe.delete_doc("Module Def", module_name)
|
||||
|
||||
# delete desktop icons
|
||||
frappe.db.sql('delete from `tabDesktop Icon` where app=%s', app_name)
|
||||
|
||||
remove_from_installed_apps(app_name)
|
||||
|
||||
if not dry_run:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ from frappe.utils.password import get_decrypted_password, set_encrypted_password
|
|||
from frappe.utils import (cint, flt, now, cstr, strip_html, getdate, get_datetime, to_timedelta,
|
||||
sanitize_html, sanitize_email, cast_fieldtype)
|
||||
|
||||
max_positive_value = {
|
||||
'smallint': 2 ** 15,
|
||||
'int': 2 ** 31,
|
||||
'bigint': 2 ** 63
|
||||
}
|
||||
|
||||
_classes = {}
|
||||
|
||||
def get_controller(doctype):
|
||||
|
|
@ -549,12 +555,31 @@ class BaseDocument(object):
|
|||
# single doctype value type is mediumtext
|
||||
return
|
||||
|
||||
type_map = frappe.db.type_map
|
||||
|
||||
for fieldname, value in iteritems(self.get_valid_dict()):
|
||||
df = self.meta.get_field(fieldname)
|
||||
if df and df.fieldtype in data_fieldtypes and frappe.db.type_map[df.fieldtype][0]=="varchar":
|
||||
max_length = cint(df.get("length")) or cint(frappe.db.VARCHAR_LEN)
|
||||
|
||||
if not df or df.fieldtype == 'Check':
|
||||
# skip standard fields and Check fields
|
||||
continue
|
||||
|
||||
column_type = type_map[df.fieldtype][0] or None
|
||||
|
||||
if column_type == 'varchar':
|
||||
default_column_max_length = type_map[df.fieldtype][1] or None
|
||||
max_length = cint(df.get("length")) or cint(default_column_max_length)
|
||||
|
||||
if len(cstr(value)) > max_length:
|
||||
self.throw_length_exceeded_error(df, max_length, value)
|
||||
|
||||
elif column_type in ('int', 'bigint', 'smallint'):
|
||||
max_length = max_positive_value[column_type]
|
||||
|
||||
if abs(value) > max_length:
|
||||
self.throw_length_exceeded_error(df, max_length, value)
|
||||
|
||||
def throw_length_exceeded_error(self, df, max_length, value):
|
||||
if self.parentfield and self.idx:
|
||||
reference = _("{0}, Row {1}").format(_(self.doctype), self.idx)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import frappe.defaults
|
|||
from frappe.model import data_fieldtypes
|
||||
from frappe.utils import nowdate, nowtime, now_datetime
|
||||
from frappe.core.doctype.user_permission.user_permission import get_user_permissions
|
||||
from frappe.permissions import get_allowed_docs_for_doctype
|
||||
|
||||
def get_new_doc(doctype, parent_doc = None, parentfield = None, as_dict=False):
|
||||
if doctype not in frappe.local.new_doc_templates:
|
||||
|
|
@ -53,36 +54,39 @@ def set_user_and_static_default_values(doc):
|
|||
|
||||
for df in doc.meta.get("fields"):
|
||||
if df.fieldtype in data_fieldtypes:
|
||||
user_default_value = get_user_default_value(df, defaults, user_permissions)
|
||||
# user permissions for link options
|
||||
doctype_user_permissions = user_permissions.get(df.options, [])
|
||||
# Allowed records for the reference doctype (link field)
|
||||
allowed_records = get_allowed_docs_for_doctype(doctype_user_permissions, df.parent)
|
||||
|
||||
user_default_value = get_user_default_value(df, defaults, doctype_user_permissions, allowed_records)
|
||||
|
||||
if user_default_value is not None:
|
||||
doc.set(df.fieldname, user_default_value)
|
||||
|
||||
else:
|
||||
if df.fieldname != doc.meta.title_field:
|
||||
static_default_value = get_static_default_value(df, user_permissions)
|
||||
static_default_value = get_static_default_value(df, doctype_user_permissions, allowed_records)
|
||||
if static_default_value is not None:
|
||||
doc.set(df.fieldname, static_default_value)
|
||||
|
||||
def get_user_default_value(df, defaults, user_permissions):
|
||||
def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records):
|
||||
# don't set defaults for "User" link field using User Permissions!
|
||||
if df.fieldtype == "Link" and df.options != "User":
|
||||
# 1 - look in user permissions only for document_type==Setup
|
||||
# We don't want to include permissions of transactions to be used for defaults.
|
||||
if (frappe.get_meta(df.options).document_type=="Setup"
|
||||
and user_permissions_exist(df, user_permissions)
|
||||
and len(user_permissions.get(df.options))==1):
|
||||
return user_permissions.get(df.options)[0].get("doc")
|
||||
if frappe.get_meta(df.options).document_type=="Setup" and len(allowed_records)==1:
|
||||
return allowed_records[0]
|
||||
|
||||
# 2 - Look in user defaults
|
||||
user_default = defaults.get(df.fieldname)
|
||||
is_allowed_user_default = user_default and (not user_permissions_exist(df, user_permissions)
|
||||
or (user_default in user_permissions.get(df.options, [])))
|
||||
is_allowed_user_default = user_default and (not user_permissions_exist(df, doctype_user_permissions)
|
||||
or user_default in allowed_records)
|
||||
|
||||
# is this user default also allowed as per user permissions?
|
||||
if is_allowed_user_default:
|
||||
return user_default
|
||||
|
||||
def get_static_default_value(df, user_permissions):
|
||||
def get_static_default_value(df, doctype_user_permissions, allowed_records):
|
||||
# 3 - look in default of docfield
|
||||
if df.get("default"):
|
||||
if df.default == "__user":
|
||||
|
|
@ -93,8 +97,8 @@ def get_static_default_value(df, user_permissions):
|
|||
|
||||
elif not df.default.startswith(":"):
|
||||
# a simple default value
|
||||
is_allowed_default_value = (not user_permissions_exist(df, user_permissions)
|
||||
or (df.default in user_permissions.get(df.options, [])))
|
||||
is_allowed_default_value = (not user_permissions_exist(df, doctype_user_permissions)
|
||||
or (df.default in allowed_records))
|
||||
|
||||
if df.fieldtype!="Link" or df.options=="User" or is_allowed_default_value:
|
||||
return df.default
|
||||
|
|
@ -126,10 +130,10 @@ def set_dynamic_default_values(doc, parent_doc, parentfield):
|
|||
if parentfield:
|
||||
doc["parentfield"] = parentfield
|
||||
|
||||
def user_permissions_exist(df, user_permissions):
|
||||
def user_permissions_exist(df, doctype_user_permissions):
|
||||
return (df.fieldtype=="Link"
|
||||
and not getattr(df, "ignore_user_permissions", False)
|
||||
and df.options in (user_permissions or []))
|
||||
and doctype_user_permissions)
|
||||
|
||||
def get_default_based_on_another_field(df, user_permissions, parent_doc):
|
||||
# default value based on another document
|
||||
|
|
@ -139,7 +143,7 @@ def get_default_based_on_another_field(df, user_permissions, parent_doc):
|
|||
ref_fieldname = ref_doctype.lower().replace(" ", "_")
|
||||
reference_name = parent_doc.get(ref_fieldname) if parent_doc else frappe.db.get_default(ref_fieldname)
|
||||
default_value = frappe.db.get_value(ref_doctype, reference_name, df.fieldname)
|
||||
is_allowed_default_value = (not user_permissions_exist(df, user_permissions) or
|
||||
is_allowed_default_value = (not user_permissions_exist(df, user_permissions.get(df.options)) or
|
||||
(default_value in get_allowed_docs_for_doctype(user_permissions[df.options], df.parent)))
|
||||
|
||||
# is this allowed as per user permissions
|
||||
|
|
|
|||
|
|
@ -458,7 +458,7 @@ class DatabaseQuery(object):
|
|||
f.operator = '='
|
||||
|
||||
value = ""
|
||||
fallback = '""'
|
||||
fallback = "''"
|
||||
can_be_null = True
|
||||
|
||||
if 'ifnull' not in column_name:
|
||||
|
|
@ -747,6 +747,7 @@ def get_list(doctype, *args, **kwargs):
|
|||
'''wrapper for DatabaseQuery'''
|
||||
kwargs.pop('cmd', None)
|
||||
kwargs.pop('ignore_permissions', None)
|
||||
kwargs.pop('data', None)
|
||||
|
||||
# If doctype is child table
|
||||
if frappe.is_table(doctype):
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ def check_if_doc_is_linked(doc, method="Delete"):
|
|||
for item in frappe.db.get_values(link_dt, {link_field:doc.name},
|
||||
["name", "parent", "parenttype", "docstatus"], as_dict=True):
|
||||
linked_doctype = item.parenttype if item.parent else link_dt
|
||||
if linked_doctype in ("Communication", "ToDo", "DocShare", "Email Unsubscribe", 'File', 'Version', "Activity Log"):
|
||||
if linked_doctype in ("Communication", "ToDo", "DocShare", "Email Unsubscribe", 'File', 'Version', "Activity Log", 'Comment'):
|
||||
# don't check for communication and todo!
|
||||
continue
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ def check_if_doc_is_linked(doc, method="Delete"):
|
|||
def check_if_doc_is_dynamically_linked(doc, method="Delete"):
|
||||
'''Raise `frappe.LinkExistsError` if the document is dynamically linked'''
|
||||
for df in get_dynamic_link_map().get(doc.doctype, []):
|
||||
if df.parent in ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", 'File', 'Version', 'View Log'):
|
||||
if df.parent in ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", 'File', 'Version', 'View Log', 'Comment'):
|
||||
# don't check for communication and todo!
|
||||
continue
|
||||
|
||||
|
|
@ -266,59 +266,37 @@ def raise_link_exists_exception(doc, reference_doctype, reference_docname, row='
|
|||
.format(doc.doctype, doc_link, reference_doctype, reference_link, row), frappe.LinkExistsError)
|
||||
|
||||
def delete_dynamic_links(doctype, name):
|
||||
delete_doc("ToDo", frappe.db.sql_list("""select name from `tabToDo`
|
||||
where reference_type=%s and reference_name=%s""", (doctype, name)),
|
||||
ignore_permissions=True, force=True)
|
||||
|
||||
frappe.db.sql('''delete from `tabEmail Unsubscribe`
|
||||
where reference_doctype=%s and reference_name=%s''', (doctype, name))
|
||||
|
||||
# delete shares
|
||||
frappe.db.sql("""delete from `tabDocShare`
|
||||
where share_doctype=%s and share_name=%s""", (doctype, name))
|
||||
|
||||
# delete versions
|
||||
frappe.db.sql('delete from tabVersion where ref_doctype=%s and docname=%s', (doctype, name))
|
||||
|
||||
# delete comments
|
||||
frappe.db.sql("""delete from `tabCommunication`
|
||||
where
|
||||
communication_type = 'Comment'
|
||||
and reference_doctype=%s and reference_name=%s""", (doctype, name))
|
||||
|
||||
# delete view logs
|
||||
frappe.db.sql("""delete from `tabView Log`
|
||||
where reference_doctype=%s and reference_name=%s""", (doctype, name))
|
||||
delete_references('ToDo', doctype, name, 'reference_type')
|
||||
delete_references('Email Unsubscribe', doctype, name)
|
||||
delete_references('DocShare', doctype, name, 'share_doctype', 'share_name')
|
||||
delete_references('Version', doctype, name, 'ref_doctype', 'docname')
|
||||
delete_references('Comment', doctype, name)
|
||||
delete_references('View Log', doctype, name)
|
||||
|
||||
# unlink communications
|
||||
frappe.db.sql("""update `tabCommunication`
|
||||
set reference_doctype=null, reference_name=null
|
||||
clear_references('Communication', doctype, name)
|
||||
clear_references('Communication', doctype, name, 'link_doctype', 'link_name')
|
||||
clear_references('Communication', doctype, name, 'timeline_doctype', 'timeline_name')
|
||||
|
||||
clear_references('Activity Log', doctype, name)
|
||||
clear_references('Activity Log', doctype, name, 'timeline_doctype', 'timeline_name')
|
||||
|
||||
def delete_references(doctype, reference_doctype, reference_name,
|
||||
reference_doctype_field = 'reference_doctype', reference_name_field = 'reference_name'):
|
||||
frappe.db.sql('''delete from `tab{0}`
|
||||
where {1}=%s and {2}=%s'''.format(doctype, reference_doctype_field, reference_name_field), # nosec
|
||||
(reference_doctype, reference_name))
|
||||
|
||||
def clear_references(doctype, reference_doctype, reference_name,
|
||||
reference_doctype_field = 'reference_doctype', reference_name_field = 'reference_name'):
|
||||
frappe.db.sql('''update
|
||||
`tab{0}`
|
||||
set
|
||||
{1}=NULL, {2}=NULL
|
||||
where
|
||||
communication_type = 'Communication'
|
||||
and reference_doctype=%s
|
||||
and reference_name=%s""", (doctype, name))
|
||||
{1}=%s and {2}=%s'''.format(doctype, reference_doctype_field, reference_name_field), # nosec
|
||||
(reference_doctype, reference_name))
|
||||
|
||||
# unlink secondary references
|
||||
frappe.db.sql("""update `tabCommunication`
|
||||
set link_doctype=null, link_name=null
|
||||
where link_doctype=%s and link_name=%s""", (doctype, name))
|
||||
|
||||
# unlink feed
|
||||
frappe.db.sql("""update `tabCommunication`
|
||||
set timeline_doctype=null, timeline_name=null
|
||||
where timeline_doctype=%s and timeline_name=%s""", (doctype, name))
|
||||
|
||||
# unlink activity_log reference_doctype
|
||||
frappe.db.sql("""update `tabActivity Log`
|
||||
set reference_doctype=null, reference_name=null
|
||||
where
|
||||
reference_doctype=%s
|
||||
and reference_name=%s""", (doctype, name))
|
||||
|
||||
# unlink activity_log timeline_doctype
|
||||
frappe.db.sql("""update `tabActivity Log`
|
||||
set timeline_doctype=null, timeline_name=null
|
||||
where timeline_doctype=%s and timeline_name=%s""", (doctype, name))
|
||||
|
||||
def insert_feed(doc):
|
||||
from frappe.utils import get_fullname
|
||||
|
|
@ -327,8 +305,7 @@ def insert_feed(doc):
|
|||
return
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Comment",
|
||||
"doctype": "Comment",
|
||||
"comment_type": "Deleted",
|
||||
"reference_doctype": doc.doctype,
|
||||
"subject": "{0} {1}".format(_(doc.doctype), doc.name),
|
||||
|
|
|
|||
|
|
@ -165,10 +165,10 @@ class Document(BaseDocument):
|
|||
self.latest = frappe.get_doc(self.doctype, self.name)
|
||||
return self.latest
|
||||
|
||||
def check_permission(self, permtype='read', permlabel=None):
|
||||
def check_permission(self, permtype='read', permlevel=None):
|
||||
"""Raise `frappe.PermissionError` if not permitted"""
|
||||
if not self.has_permission(permtype):
|
||||
self.raise_no_permission_to(permlabel or permtype)
|
||||
self.raise_no_permission_to(permlevel or permtype)
|
||||
|
||||
def has_permission(self, permtype="read", verbose=False):
|
||||
"""Call `frappe.has_permission` if `self.flags.ignore_permissions`
|
||||
|
|
@ -999,7 +999,7 @@ class Document(BaseDocument):
|
|||
frappe.db.commit()
|
||||
|
||||
def db_get(self, fieldname):
|
||||
'''get database vale for this fieldname'''
|
||||
'''get database value for this fieldname'''
|
||||
return frappe.db.get_value(self.doctype, self.name, fieldname)
|
||||
|
||||
def check_no_back_links_exist(self):
|
||||
|
|
@ -1116,33 +1116,22 @@ class Document(BaseDocument):
|
|||
"""Returns Desk URL for this document. `/desk#Form/{doctype}/{name}`"""
|
||||
return "/desk#Form/{doctype}/{name}".format(doctype=self.doctype, name=self.name)
|
||||
|
||||
def add_comment(self, comment_type, text=None, comment_by=None, link_doctype=None, link_name=None):
|
||||
def add_comment(self, comment_type='Comment', text=None, comment_email=None, link_doctype=None, link_name=None, comment_by=None):
|
||||
"""Add a comment to this document.
|
||||
|
||||
:param comment_type: e.g. `Comment`. See Communication for more info."""
|
||||
|
||||
if comment_type=='Comment':
|
||||
out = frappe.get_doc({
|
||||
"doctype":"Communication",
|
||||
"communication_type": "Comment",
|
||||
"sender": comment_by or frappe.session.user,
|
||||
"comment_type": comment_type,
|
||||
"doctype":"Comment",
|
||||
'comment_type': comment_type,
|
||||
"comment_email": comment_email or frappe.session.user,
|
||||
"comment_by": comment_by,
|
||||
"reference_doctype": self.doctype,
|
||||
"reference_name": self.name,
|
||||
"content": text or comment_type,
|
||||
"link_doctype": link_doctype,
|
||||
"link_name": link_name
|
||||
}).insert(ignore_permissions=True)
|
||||
else:
|
||||
out = frappe.get_doc(dict(
|
||||
doctype='Version',
|
||||
ref_doctype= self.doctype,
|
||||
docname= self.name,
|
||||
data = frappe.as_json(dict(comment_type=comment_type, comment=text))
|
||||
))
|
||||
if comment_by:
|
||||
out.owner = comment_by
|
||||
out.insert(ignore_permissions=True)
|
||||
return out
|
||||
|
||||
def add_seen(self, user=None):
|
||||
|
|
|
|||
|
|
@ -83,12 +83,10 @@ def sync_customizations(app=None):
|
|||
for app_name in apps:
|
||||
for module_name in frappe.local.app_modules.get(app_name) or []:
|
||||
folder = frappe.get_app_path(app_name, module_name, 'custom')
|
||||
|
||||
if os.path.exists(folder):
|
||||
for fname in os.listdir(folder):
|
||||
with open(os.path.join(folder, fname), 'r') as f:
|
||||
data = json.loads(f.read())
|
||||
|
||||
if data.get('sync_on_migrate'):
|
||||
sync_customizations_for_doctype(data, folder)
|
||||
|
||||
|
|
@ -105,13 +103,30 @@ def sync_customizations_for_doctype(data, folder):
|
|||
|
||||
# sync single doctype exculding the child doctype
|
||||
def sync_single_doctype(doc_type):
|
||||
def _insert(data):
|
||||
if data.get(doctype_fieldname) == doc_type:
|
||||
data['doctype'] = custom_doctype
|
||||
doc = frappe.get_doc(data)
|
||||
doc.db_insert()
|
||||
|
||||
if custom_doctype != 'Custom Field':
|
||||
frappe.db.sql('delete from `tab{0}` where `{1}` =%s'.format(
|
||||
custom_doctype, doctype_fieldname), doc_type)
|
||||
|
||||
for d in data[key]:
|
||||
if d.get(doctype_fieldname) == doc_type:
|
||||
d['doctype'] = custom_doctype
|
||||
doc = frappe.get_doc(d)
|
||||
doc.db_insert()
|
||||
_insert(data)
|
||||
|
||||
else:
|
||||
for d in data[key]:
|
||||
field = frappe.db.get_value("Custom Field", {"dt": doc_type, "fieldname": d["fieldname"]})
|
||||
if not field:
|
||||
d["owner"] = "Administrator"
|
||||
_insert(d)
|
||||
else:
|
||||
custom_field = frappe.get_doc("Custom Field", field)
|
||||
custom_field.flags.ignore_validate = True
|
||||
custom_field.update(d)
|
||||
custom_field.db_update()
|
||||
|
||||
for doc_type in doctypes:
|
||||
# only sync the parent doctype and child doctype if there isn't any other child table json file
|
||||
|
|
|
|||
|
|
@ -5,15 +5,16 @@ frappe.patches.v8_0.update_global_search_table
|
|||
frappe.patches.v7_0.update_auth
|
||||
frappe.patches.v8_0.drop_in_dialog #2017-09-22
|
||||
frappe.patches.v7_2.remove_in_filter
|
||||
frappe.patches.v11_0.drop_column_apply_user_permissions
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22
|
||||
execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2018-02-20
|
||||
execute:frappe.reload_doc('core', 'doctype', 'custom_docperm')
|
||||
execute:frappe.reload_doc('core', 'doctype', 'docperm') #2018-05-29
|
||||
execute:frappe.reload_doc('core', 'doctype', 'comment')
|
||||
frappe.patches.v8_0.drop_is_custom_from_docperm
|
||||
execute:frappe.reload_doc('core', 'doctype', 'module_def') #2017-09-22
|
||||
execute:frappe.reload_doc('core', 'doctype', 'version') #2017-04-01
|
||||
frappe.patches.v11_0.replicate_old_user_permissions
|
||||
frappe.patches.v11_0.drop_column_apply_user_permissions
|
||||
frappe.patches.v11_0.reload_and_rename_view_log #2019-01-03
|
||||
frappe.patches.v7_1.rename_scheduler_log_to_error_log
|
||||
frappe.patches.v6_1.rename_file_data
|
||||
|
|
@ -145,11 +146,9 @@ frappe.patches.v6_16.feed_doc_owner
|
|||
frappe.patches.v6_21.print_settings_repeat_header_footer
|
||||
frappe.patches.v6_24.set_language_as_code
|
||||
frappe.patches.v6_20x.update_insert_after
|
||||
finally:frappe.patches.v6_24.sync_desktop_icons
|
||||
frappe.patches.v6_20x.set_allow_draft_for_print
|
||||
frappe.patches.v6_20x.remove_roles_from_website_user
|
||||
frappe.patches.v7_0.set_user_fullname
|
||||
frappe.patches.v7_0.desktop_icons_hidden_by_admin_as_blocked
|
||||
frappe.patches.v7_0.add_communication_in_doc
|
||||
frappe.patches.v7_0.update_send_after_in_bulk_email
|
||||
execute:frappe.db.sql('''delete from `tabSingles` where doctype="Email Settings"''') # 2016-06-13
|
||||
|
|
@ -183,15 +182,11 @@ frappe.patches.v8_0.deprecate_integration_broker
|
|||
frappe.patches.v8_0.update_gender_and_salutation
|
||||
frappe.patches.v8_0.setup_email_inbox #2017-03-29
|
||||
frappe.patches.v8_0.newsletter_childtable_migrate
|
||||
execute:frappe.db.sql("delete from `tabDesktop Icon` where module_name='Communication'")
|
||||
execute:frappe.db.sql("update `tabDesktop Icon` set type='list' where _doctype='Communication'")
|
||||
frappe.patches.v8_0.fix_non_english_desktop_icons # 2017-04-12
|
||||
frappe.patches.v8_0.set_doctype_values_in_custom_role
|
||||
frappe.patches.v8_0.install_new_build_system_requirements
|
||||
frappe.patches.v8_0.set_currency_field_precision # 2017-05-09
|
||||
frappe.patches.v8_0.rename_print_to_printing
|
||||
frappe.patches.v7_1.disabled_print_settings_for_custom_print_format
|
||||
frappe.patches.v8_0.update_desktop_icons
|
||||
execute:frappe.db.sql('update tabReport set module="Desk" where name="ToDo"')
|
||||
frappe.patches.v8_1.enable_allow_error_traceback_in_system_settings
|
||||
frappe.patches.v8_1.update_format_options_in_auto_email_report
|
||||
|
|
@ -236,4 +231,5 @@ frappe.patches.v11_0.fix_order_by_in_reports_json
|
|||
execute:frappe.delete_doc('Page', 'applications', ignore_missing=True)
|
||||
frappe.patches.v11_0.set_missing_creation_and_modified_value_for_user_permissions
|
||||
frappe.patches.v12_0.set_primary_key_in_series
|
||||
frappe.patches.v12_0.webpage_migrate_description_to_meta_tag
|
||||
execute:frappe.delete_doc("Page", "modules", ignore_missing=True)
|
||||
frappe.patches.v11_0.set_default_letter_head_source
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ def execute():
|
|||
to_remove = ['DocPerm', 'Custom DocPerm']
|
||||
|
||||
for doctype in to_remove:
|
||||
if frappe.db.table_exists(doctype):
|
||||
if column in frappe.db.get_table_columns(doctype):
|
||||
frappe.db.sql("alter table `tab{0}` drop column {1}".format(doctype, column))
|
||||
|
||||
|
|
|
|||
9
frappe/patches/v11_0/set_default_letter_head_source.py
Normal file
9
frappe/patches/v11_0/set_default_letter_head_source.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('Letter Head')
|
||||
|
||||
# source of all existing letter heads must be HTML
|
||||
frappe.db.sql('update `tabLetter Head` set source = "HTML"')
|
||||
25
frappe/patches/v12_0/setup_comments_from_communications.py
Normal file
25
frappe/patches/v12_0/setup_comments_from_communications.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
for comment in frappe.get_all('Communication', fields = ['*'],
|
||||
filters = dict(communication_type = 'Comment')):
|
||||
|
||||
new_comment = frappe.new_doc('Comment')
|
||||
new_comment.comment_type = comment.comment_type
|
||||
new_comment.comment_email = comment.sender
|
||||
new_comment.comment_by = comment.sender_full_name
|
||||
new_comment.subject = comment.subject
|
||||
new_comment.reference_doctype = comment.reference_doctype
|
||||
new_comment.reference_name = comment.reference_name
|
||||
new_comment.link_doctype = comment.link_doctype
|
||||
new_comment.link_name = comment.link_name
|
||||
new_comment.creation = comment.creation
|
||||
new_comment.modified = comment.modified
|
||||
new_comment.owner = comment.owner
|
||||
new_comment.modified_by = comment.modified_by
|
||||
new_comment.db_insert()
|
||||
|
||||
# clean up
|
||||
frappe.db.sql('delete from tabCommunication where communication_type = "Comment"')
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
|
||||
from frappe.desk.doctype.desktop_icon.desktop_icon import sync_from_app, get_user_copy
|
||||
import frappe.defaults
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('desk', 'doctype', 'desktop_icon')
|
||||
|
||||
frappe.db.sql('delete from `tabDesktop Icon`')
|
||||
|
||||
modules_list = []
|
||||
for app in frappe.get_installed_apps():
|
||||
modules_list += sync_from_app(app)
|
||||
|
||||
# sync hidden modules
|
||||
hidden_modules = frappe.db.get_global('hidden_modules')
|
||||
if hidden_modules:
|
||||
for m in json.loads(hidden_modules):
|
||||
try:
|
||||
desktop_icon = frappe.get_doc('Desktop Icon', {'module_name': m, 'standard': 1, 'app': app})
|
||||
desktop_icon.db_set('hidden', 1)
|
||||
except frappe.DoesNotExistError:
|
||||
pass
|
||||
|
||||
# sync user sort
|
||||
for user in frappe.get_all('User', filters={'user_type': 'System User'}):
|
||||
user_list = frappe.defaults.get_user_default('_user_desktop_items', user=user.name)
|
||||
if user_list:
|
||||
user_list = json.loads(user_list)
|
||||
for i, module_name in enumerate(user_list):
|
||||
try:
|
||||
desktop_icon = get_user_copy(module_name, user=user.name)
|
||||
desktop_icon.db_set('idx', i)
|
||||
except frappe.DoesNotExistError:
|
||||
pass
|
||||
|
||||
# set remaining icons as hidden
|
||||
for module_name in list(set([m['module_name'] for m in modules_list]) - set(user_list)):
|
||||
try:
|
||||
desktop_icon = get_user_copy(module_name, user=user.name)
|
||||
desktop_icon.db_set('hidden', 1)
|
||||
except frappe.DoesNotExistError:
|
||||
pass
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.core.doctype.communication.comment import update_comment_in_doc
|
||||
from frappe.core.doctype.comment.comment import update_comment_in_doc
|
||||
|
||||
def execute():
|
||||
for d in frappe.db.get_all("Communication",
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
# all icons hidden in standard are "blocked"
|
||||
# this is for the use case where the admin wants to remove icon for everyone
|
||||
|
||||
# in 7.0, icons may be hidden by default, but still can be shown to the user
|
||||
# e.g. Accounts, Stock etc, so we need a new property for blocked
|
||||
|
||||
if frappe.db.table_exists('Desktop Icon'):
|
||||
frappe.db.sql('update `tabDesktop Icon` set blocked = 1 where standard=1 and hidden=1')
|
||||
|
|
@ -11,12 +11,6 @@ def execute():
|
|||
update_routes(['Help Category', 'Help Article'])
|
||||
remove_from_installed_apps('knowledge_base')
|
||||
|
||||
# remove desktop icon
|
||||
desktop_icon_name = frappe.db.get_value('Desktop Icon',
|
||||
dict(module_name='Knowledge Base', type='module'))
|
||||
if desktop_icon_name:
|
||||
frappe.delete_doc('Desktop Icon', desktop_icon_name)
|
||||
|
||||
# remove module def
|
||||
if frappe.db.exists('Module Def', 'Knowledge Base'):
|
||||
frappe.delete_doc('Module Def', 'Knowledge Base')
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
# Copyright (c) 2017, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.desk.doctype.desktop_icon.desktop_icon import clear_desktop_icons_cache
|
||||
|
||||
def execute():
|
||||
frappe.db.sql("""
|
||||
update `tabDesktop Icon`
|
||||
set module_name=_doctype, label=_doctype
|
||||
where type = 'link' and _doctype != label and link like 'List/%'
|
||||
""")
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue