diff --git a/.eslintrc b/.eslintrc
index 86abea34ce..0a76fdf2aa 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -116,6 +116,7 @@
"md5": true,
"$": true,
"jQuery": true,
+ "Vue": true,
"moment": true,
"hljs": true,
"Awesomplete": true,
diff --git a/bandit.yml b/bandit.yml
index fce28629e8..b8560e97c8 100644
--- a/bandit.yml
+++ b/bandit.yml
@@ -1 +1 @@
-skips: ['B605', 'B404', 'B603', 'B607']
\ No newline at end of file
+skips: ['E0203', 'B605', 'B404', 'B603', 'B607']
\ No newline at end of file
diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js
index 44dbde8fcf..4b2ae0da93 100644
--- a/cypress/integration/awesome_bar.js
+++ b/cypress/integration/awesome_bar.js
@@ -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 });
diff --git a/cypress/integration/relative_filters.js b/cypress/integration/relative_filters.js
index efc6b930b2..d79e848994 100644
--- a/cypress/integration/relative_filters.js
+++ b/cypress/integration/relative_filters.js
@@ -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');
diff --git a/cypress/integration/table_multiselect.js b/cypress/integration/table_multiselect.js
index 67b522c364..a083e514ba 100644
--- a/cypress/integration/table_multiselect.js
+++ b/cypress/integration/table_multiselect.js
@@ -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');
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index f25cc12ea6..36df14d1b0 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -49,4 +49,8 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => {
} else {
return cy.get('@input').type(value);
}
-});
\ No newline at end of file
+});
+
+Cypress.Commands.add('awesomebar', (text) => {
+ cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, { delay: 100 });
+});
diff --git a/frappe/.pylintrc b/frappe/.pylintrc
new file mode 100644
index 0000000000..4b2ea0a564
--- /dev/null
+++ b/frappe/.pylintrc
@@ -0,0 +1 @@
+disable=access-member-before-definition
\ No newline at end of file
diff --git a/frappe/__init__.py b/frappe/__init__.py
index f6aa7f9e1d..a1c3ebf34d 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -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,11 +919,15 @@ def get_hooks(hook=None, default=None, app_name=None):
append_hook(hooks, key, getattr(app_hooks, key))
return hooks
+ no_cache = conf.developer_mode or False
if app_name:
hooks = _dict(load_app_hooks(app_name))
else:
- hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
+ if no_cache:
+ hooks = _dict(load_app_hooks())
+ else:
+ hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
if hook:
return hooks.get(hook) or (default if default is not None else [])
diff --git a/frappe/app.py b/frappe/app.py
index bc4ecc9b2d..9da7ea71a0 100644
--- a/frappe/app.py
+++ b/frappe/app.py
@@ -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
diff --git a/frappe/boot.py b/frappe/boot.py
index 18fb080d4f..043c1b0361 100644
--- a/frappe/boot.py
+++ b/frappe/boot.py
@@ -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')
diff --git a/frappe/chat/doctype/chat_profile/chat_profile.py b/frappe/chat/doctype/chat_profile/chat_profile.py
index 22ce94858d..283494de85 100644
--- a/frappe/chat/doctype/chat_profile/chat_profile.py
+++ b/frappe/chat/doctype/chat_profile/chat_profile.py
@@ -9,99 +9,94 @@ import frappe
from frappe.core.doctype.version.version import get_diff
from frappe.chat.doctype.chat_room import chat_room
from frappe.chat.util import (
- safe_json_loads,
- filter_dict,
- dictify
+ safe_json_loads,
+ filter_dict,
+ dictify
)
session = frappe.session
class ChatProfile(Document):
- def before_save(self):
- if not self.is_new():
- self.get_doc_before_save()
+ def before_save(self):
+ if not self.is_new():
+ self.get_doc_before_save()
- def on_update(self):
- if not self.is_new():
- b, a = self.get_doc_before_save(), self
- diff = dictify(get_diff(a, b))
- if diff:
- user = session.user
+ def on_update(self):
+ if not self.is_new():
+ b, a = self.get_doc_before_save(), self
+ diff = dictify(get_diff(a, b))
+ if diff:
+ user = session.user
- fields = [changed[0] for changed in diff.changed]
+ fields = [changed[0] for changed in diff.changed]
- if 'status' in fields:
- rooms = chat_room.get(user, filters = ['Chat Room', 'type', '=', 'Direct'])
- update = dict(user = user, data = dict(status = self.status))
+ if 'status' in fields:
+ rooms = chat_room.get(user, filters = ['Chat Room', 'type', '=', 'Direct'])
+ update = dict(user = user, data = dict(status = self.status))
- for room in rooms:
- frappe.publish_realtime('frappe.chat.profile:update', update, room = room.name, after_commit = True)
+ for room in rooms:
+ frappe.publish_realtime('frappe.chat.profile:update', update, room = room.name, after_commit = True)
- if 'enable_chat' in fields:
- update = dict(user = user, data = dict(enable_chat = bool(self.enable_chat)))
- frappe.publish_realtime('frappe.chat.profile:update', update, user = user, after_commit = True)
+ if 'enable_chat' in fields:
+ update = dict(user = user, data = dict(enable_chat = bool(self.enable_chat)))
+ frappe.publish_realtime('frappe.chat.profile:update', update, user = user, after_commit = True)
def authenticate(user):
- if user != session.user:
- frappe.throw(_("Sorry, you're not authorized."))
+ if user != session.user:
+ frappe.throw(_("Sorry, you're not authorized."))
@frappe.whitelist()
def get(user, fields = None):
- duser = frappe.get_doc('User', user)
- dprof = frappe.get_doc('Chat Profile', user)
+ duser = frappe.get_doc('User', user)
+ dprof = frappe.get_doc('Chat Profile', user)
- # If you're adding something here, make sure the client recieves it.
- profile = dict(
- # User
- name = duser.name,
- email = duser.email,
- first_name = duser.first_name,
- last_name = duser.last_name,
- username = duser.username,
- avatar = duser.user_image,
- bio = duser.bio,
- # Chat Profile
- status = dprof.status,
- chat_background = dprof.chat_background,
- message_preview = bool(dprof.message_preview),
- notification_tones = bool(dprof.notification_tones),
- conversation_tones = bool(dprof.conversation_tones),
- enable_chat = bool(dprof.enable_chat)
- )
- profile = filter_dict(profile, fields)
+ # If you're adding something here, make sure the client recieves it.
+ profile = dict(
+ # User
+ name = duser.name,
+ email = duser.email,
+ first_name = duser.first_name,
+ last_name = duser.last_name,
+ username = duser.username,
+ avatar = duser.user_image,
+ bio = duser.bio,
+ # Chat Profile
+ status = dprof.status,
+ chat_background = dprof.chat_background,
+ message_preview = bool(dprof.message_preview),
+ notification_tones = bool(dprof.notification_tones),
+ conversation_tones = bool(dprof.conversation_tones),
+ enable_chat = bool(dprof.enable_chat)
+ )
+ profile = filter_dict(profile, fields)
- return dictify(profile)
+ return dictify(profile)
@frappe.whitelist()
def create(user, exists_ok = False, fields = None):
- authenticate(user)
+ authenticate(user)
- exists_ok, fields = safe_json_loads(exists_ok, fields)
+ exists_ok, fields = safe_json_loads(exists_ok, fields)
- result = frappe.db.sql("""
- SELECT *
- FROM `tabChat Profile`
- WHERE `user` = '{user}'
- """.format(user = user))
+ try:
+ dprof = frappe.new_doc('Chat Profile')
+ dprof.user = user
+ dprof.save(ignore_permissions = True)
+ except frappe.DuplicateEntryError:
+ frappe.clear_messages()
+ if not exists_ok:
+ frappe.throw(_('Chat Profile for User {0} exists.').format(user))
- if result:
- if not exists_ok:
- frappe.throw(_('Chat Profile for User {0} exists.').format(user))
- else:
- dprof = frappe.new_doc('Chat Profile')
- dprof.user = user
- dprof.save(ignore_permissions = True)
+ profile = get(user, fields = fields)
- profile = get(user, fields = fields)
-
- return profile
+ return profile
@frappe.whitelist()
def update(user, data):
- authenticate(user)
+ authenticate(user)
- data = safe_json_loads(data)
+ data = safe_json_loads(data)
- dprof = frappe.get_doc('Chat Profile', user)
- dprof.update(data)
- dprof.save(ignore_permissions = True)
\ No newline at end of file
+ dprof = frappe.get_doc('Chat Profile', user)
+ dprof.update(data)
+ dprof.save(ignore_permissions = True)
\ No newline at end of file
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index 5634e6108d..3798c07e91 100755
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -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,
]
diff --git a/frappe/config/__init__.py b/frappe/config/__init__.py
index e69de29bb2..34bc86dbd6 100644
--- a/frappe/config/__init__.py
+++ b/frappe/config/__init__.py
@@ -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"
diff --git a/frappe/config/customization.py b/frappe/config/customization.py
new file mode 100644
index 0000000000..1ecf039f88
--- /dev/null
+++ b/frappe/config/customization.py
@@ -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")
+ }
+ ]
+ },
+ ]
diff --git a/frappe/config/desk.py b/frappe/config/desk.py
index 8efe293411..40db97ef8c 100644
--- a/frappe/config/desk.py
+++ b/frappe/config/desk.py
@@ -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",
diff --git a/frappe/config/desktop.py b/frappe/config/desktop.py
index b620508c06..a4677a906b 100644
--- a/frappe/config/desktop.py
+++ b/frappe/config/desktop.py
@@ -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"
- }
]
diff --git a/frappe/config/settings.py b/frappe/config/settings.py
index d1c00c2e71..75c8972feb 100644
--- a/frappe/config/settings.py
+++ b/frappe/config/settings.py
@@ -1,51 +1,188 @@
+from __future__ import unicode_literals
from frappe import _
+from frappe.desk.moduleview import add_setup_section
def get_data():
- return [{
- "label": _("Settings"),
- "icon": "fa fa-wrench",
- "items": [
- {
- "type": "doctype",
- "name": "System Settings",
- "label": _("System Settings"),
- "description": _("Language, Date and Time settings"),
- "hide_count": True
- },
- {
- "type": "doctype",
- "name": "Domain Settings",
- "label": _("Domain Settings"),
- "description": _("Enable / Disable Domains"),
- "hide_count": True
- },
- {
- "type": "doctype",
- "name": "Print Settings",
- "label": _("Print Settings"),
- "description": _("Print Style, PDF Size"),
- "hide_count": True
- },
- {
- "type": "doctype",
- "name": "Website Settings",
- "label": _("Website Settings"),
- "description": _("Landing Page, Website Theme, Brand Setup and more"),
- "hide_count": True
- },
- {
- "type": "doctype",
- "name": "S3 Backup Settings",
- "label": _("S3 Backup Settings"),
- "description": _("Enable / Disable Backup, Backup Frequency"),
- "hide_count": True
- },
- {
- "type": "doctype",
- "name": "SMS Settings",
- "label": _("SMS Settings"),
- "description": _("SMS Gateway URL, Message & Receiver Parameter"),
- "hide_count": True
- }
- ]
- }]
+ data = [
+ {
+ "label": _("Core"),
+ "icon": "fa fa-wrench",
+ "items": [
+ {
+ "type": "doctype",
+ "name": "System Settings",
+ "label": _("System Settings"),
+ "description": _("Language, Date and Time settings"),
+ "hide_count": True
+ },
+ {
+ "type": "doctype",
+ "name": "Error Log",
+ "description": _("Log of error on automated events (scheduler).")
+ },
+ {
+ "type": "doctype",
+ "name": "Error Snapshot",
+ "description": _("Log of error during requests.")
+ },
+ {
+ "type": "doctype",
+ "name": "Domain Settings",
+ "label": _("Domain Settings"),
+ "description": _("Enable / Disable Domains"),
+ "hide_count": True
+ },
+ ]
+ },
+ {
+ "label": _("Data"),
+ "icon": "fa fa-th",
+ "items": [
+ {
+ "type": "doctype",
+ "name": "Data Import",
+ "label": _("Import Data"),
+ "icon": "octicon octicon-cloud-upload",
+ "description": _("Import Data from CSV / Excel files.")
+ },
+ {
+ "type": "doctype",
+ "name": "Data Export",
+ "label": _("Export Data"),
+ "icon": "octicon octicon-cloud-upload",
+ "description": _("Export Data in CSV / Excel format.")
+ },
+ {
+ "type": "doctype",
+ "name": "Naming Series",
+ "description": _("Set numbering series for transactions."),
+ "hide_count": True
+ },
+ {
+ "type": "doctype",
+ "name": "Rename Tool",
+ "label": _("Bulk Rename"),
+ "description": _("Rename many items by uploading a .csv file."),
+ "hide_count": True
+ },
+ {
+ "type": "doctype",
+ "name": "Bulk Update",
+ "label": _("Bulk Update"),
+ "description": _("Update many values at one time."),
+ "hide_count": True
+ },
+ {
+ "type": "page",
+ "name": "backups",
+ "label": _("Download Backups"),
+ "description": _("List of backups available for download"),
+ "icon": "fa fa-download"
+ },
+ {
+ "type": "doctype",
+ "name": "Deleted Document",
+ "label": _("Deleted Documents"),
+ "description": _("Restore or permanently delete a document.")
+ },
+ ]
+ },
+ {
+ "label": _("Email"),
+ "icon": "fa fa-envelope",
+ "items": [
+ {
+ "type": "doctype",
+ "name": "Email Account",
+ "description": _("Add / Manage Email Accounts.")
+ },
+ {
+ "type": "doctype",
+ "name": "Email Domain",
+ "description": _("Add / Manage Email Domains.")
+ },
+ {
+ "type": "doctype",
+ "name": "Notification",
+ "description": _("Setup Notifications based on various criteria.")
+ },
+ {
+ "type": "doctype",
+ "name": "Email Template",
+ "description": _("Email Templates for common queries.")
+ },
+ {
+ "type": "doctype",
+ "name": "Auto Email Report",
+ "description": _("Setup Reports to be emailed at regular intervals"),
+ },
+ {
+ "type": "doctype",
+ "name": "Newsletter",
+ "description": _("Create and manage newsletter")
+ }
+ ]
+ },
+ {
+ "label": _("Printing"),
+ "icon": "fa fa-print",
+ "items": [
+ {
+ "type": "page",
+ "label": _("Print Format Builder"),
+ "name": "print-format-builder",
+ "description": _("Drag and Drop tool to build and customize Print Formats.")
+ },
+ {
+ "type": "doctype",
+ "name": "Print Settings",
+ "description": _("Set default format, page size, print style etc.")
+ },
+ {
+ "type": "doctype",
+ "name": "Print Format",
+ "description": _("Customized HTML Templates for printing transactions.")
+ },
+ {
+ "type": "doctype",
+ "name": "Print Style",
+ "description": _("Stylesheets for Print Formats")
+ },
+ ]
+ },
+ {
+ "label": _("Workflow"),
+ "icon": "fa fa-random",
+ "items": [
+ {
+ "type": "doctype",
+ "name": "Workflow",
+ "description": _("Define workflows for forms.")
+ },
+ {
+ "type": "doctype",
+ "name": "Workflow State",
+ "description": _("States for workflow (e.g. Draft, Approved, Cancelled).")
+ },
+ {
+ "type": "doctype",
+ "name": "Workflow Action",
+ "description": _("Actions for workflow (e.g. Approve, Cancel).")
+ },
+ ]
+ },
+ {
+ "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
diff --git a/frappe/config/setup.py b/frappe/config/setup.py
deleted file mode 100644
index 7ed9d837ce..0000000000
--- a/frappe/config/setup.py
+++ /dev/null
@@ -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
diff --git a/frappe/config/users_and_permissions.py b/frappe/config/users_and_permissions.py
new file mode 100644
index 0000000000..6f739d43ce
--- /dev/null
+++ b/frappe/config/users_and_permissions.py
@@ -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")
+ }
+ ]
+ },
+ ]
\ No newline at end of file
diff --git a/frappe/config/website.py b/frappe/config/website.py
index de66ca0959..2de27a581b 100644
--- a/frappe/config/website.py
+++ b/frappe/config/website.py
@@ -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,
}
]
},
diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py
index 2ab680c765..38d292e9b4 100644
--- a/frappe/contacts/address_and_contact.py
+++ b/frappe/contacts/address_and_contact.py
@@ -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
diff --git a/frappe/core/doctype/activity_log/feed.py b/frappe/core/doctype/activity_log/feed.py
index b4d46eb5c1..e09436196e 100644
--- a/frappe/core/doctype/activity_log/feed.py
+++ b/frappe/core/doctype/activity_log/feed.py
@@ -56,10 +56,13 @@ def logout_feed(user, reason):
subject = _("{0} logged out: {1}").format(get_fullname(user), frappe.bold(reason))
add_authentication_log(subject, user, operation="Logout")
-def get_feed_match_conditions(user=None, force=True):
+def get_feed_match_conditions(user=None, doctype='Comment'):
if not user: user = frappe.session.user
- conditions = ['`tabCommunication`.owner={user} or `tabCommunication`.reference_owner={user}'.format(user=frappe.db.escape(user))]
+ conditions = ['`tab{doctype}`.owner={user} or `tab{doctype}`.reference_owner={user}'.format(
+ user = frappe.db.escape(user),
+ doctype = doctype
+ )]
user_permissions = frappe.permissions.get_user_permissions(user)
can_read = frappe.get_user().get_can_read()
@@ -68,9 +71,13 @@ def get_feed_match_conditions(user=None, force=True):
list(set(can_read) - set(list(user_permissions)))]
if can_read_doctypes:
- conditions += ["""(`tabCommunication`.reference_doctype is null
- or `tabCommunication`.reference_doctype = ''
- or `tabCommunication`.reference_doctype in ({}))""".format(", ".join(can_read_doctypes))]
+ conditions += ["""(`tab{doctype}`.reference_doctype is null
+ or `tab{doctype}`.reference_doctype = ''
+ or `tab{doctype}`.reference_doctype
+ in ({values}))""".format(
+ doctype = doctype,
+ values =", ".join(can_read_doctypes)
+ )]
if user_permissions:
can_read_docs = []
@@ -79,7 +86,8 @@ def get_feed_match_conditions(user=None, force=True):
can_read_docs.append('{}|{}'.format(doctype, frappe.db.escape(n.get('doc', ''))))
if can_read_docs:
- conditions.append("concat_ws('|', `tabCommunication`.reference_doctype, `tabCommunication`.reference_name) in ({})".format(
- ", ".join(can_read_docs)))
+ conditions.append("concat_ws('|', `tab{doctype}`.reference_doctype, `tab{doctype}`.reference_name) in ({values})".format(
+ doctype = doctype,
+ values = ", ".join(can_read_docs)))
- return "(" + " or ".join(conditions) + ")"
+ return "(" + " or ".join(conditions) + ")"
diff --git a/frappe/desk/page/modules/__init__.py b/frappe/core/doctype/comment/__init__.py
similarity index 100%
rename from frappe/desk/page/modules/__init__.py
rename to frappe/core/doctype/comment/__init__.py
diff --git a/frappe/core/doctype/comment/comment.js b/frappe/core/doctype/comment/comment.js
new file mode 100644
index 0000000000..a793f766cb
--- /dev/null
+++ b/frappe/core/doctype/comment/comment.js
@@ -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) {
+
+ // }
+});
diff --git a/frappe/core/doctype/comment/comment.json b/frappe/core/doctype/comment/comment.json
new file mode 100644
index 0000000000..344d6399b9
--- /dev/null
+++ b/frappe/core/doctype/comment/comment.json
@@ -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
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py
new file mode 100644
index 0000000000..24149276c8
--- /dev/null
+++ b/frappe/core/doctype/comment/comment.py
@@ -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()
diff --git a/frappe/core/doctype/comment/test_comment.py b/frappe/core/doctype/comment/test_comment.py
new file mode 100644
index 0000000000..0f46f0b3b5
--- /dev/null
+++ b/frappe/core/doctype/comment/test_comment.py
@@ -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)
+
+
+
diff --git a/frappe/core/doctype/communication/comment.py b/frappe/core/doctype/communication/comment.py
deleted file mode 100644
index b7a008cd38..0000000000
--- a/frappe/core/doctype/communication/comment.py
+++ /dev/null
@@ -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()
diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py
index d0a7433e72..3a5d9cf812 100644
--- a/frappe/core/doctype/communication/communication.py
+++ b/frappe/core/doctype/communication/communication.py
@@ -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():
diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py
index fbcd30a3f2..f29f2ad346 100755
--- a/frappe/core/doctype/communication/email.py
+++ b/frappe/core/doctype/communication/email.py
@@ -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
diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py
index 3681199bca..f3c23cbfea 100644
--- a/frappe/core/doctype/data_import/importer.py
+++ b/frappe/core/doctype/data_import/importer.py
@@ -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
diff --git a/frappe/core/doctype/doctype/boilerplate/controller._py b/frappe/core/doctype/doctype/boilerplate/controller._py
index d6af4f15aa..65f9b2bc35 100644
--- a/frappe/core/doctype/doctype/boilerplate/controller._py
+++ b/frappe/core/doctype/doctype/boilerplate/controller._py
@@ -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):
diff --git a/frappe/core/doctype/doctype/boilerplate/controller.js b/frappe/core/doctype/doctype/boilerplate/controller.js
index 87c69d29ad..6d9fb2a514 100644
--- a/frappe/core/doctype/doctype/boilerplate/controller.js
+++ b/frappe/core/doctype/doctype/boilerplate/controller.js
@@ -2,7 +2,7 @@
// For license information, please see license.txt
frappe.ui.form.on('{doctype}', {{
- refresh: function(frm) {{
+ // refresh: function(frm) {{
- }}
+ // }}
}});
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index ce252c2d70..4edf29d937 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -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 {0} provided for the field {1} 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)
diff --git a/frappe/core/doctype/domain/domain.py b/frappe/core/doctype/domain/domain.py
index ea21e73b95..a4e9f503ab 100644
--- a/frappe/core/doctype/domain/domain.py
+++ b/frappe/core/doctype/domain/domain.py
@@ -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:
diff --git a/frappe/core/doctype/language/language.py b/frappe/core/doctype/language/language.py
index 8c7e01cb62..fb18abdf5e 100644
--- a/frappe/core/doctype/language/language.py
+++ b/frappe/core/doctype/language/language.py
@@ -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'''
diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py
index 87c14c6651..6b40cd7d33 100644
--- a/frappe/core/doctype/report/report.py
+++ b/frappe/core/doctype/report/report.py
@@ -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'
diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js
index 71d7cfc0e8..d334731015 100644
--- a/frappe/core/doctype/user/user.js
+++ b/frappe/core/doctype/user/user.js
@@ -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() {
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index 4fcee60dc5..6d429f3248 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -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",
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index b191496c73..087d7c9d21 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -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
diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py
index 157fa44ae2..b83d103013 100644
--- a/frappe/core/doctype/user_permission/test_user_permission.py
+++ b/frappe/core/doctype/user_permission/test_user_permission.py
@@ -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
diff --git a/frappe/core/doctype/user_permission/user_permission.json b/frappe/core/doctype/user_permission/user_permission.json
index 9a38f8d953..c2ea05e731 100644
--- a/frappe/core/doctype/user_permission/user_permission.json
+++ b/frappe/core/doctype/user_permission/user_permission.json
@@ -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",
diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py
index f8099cac38..cdd008b1e4 100644
--- a/frappe/core/doctype/user_permission/user_permission.py
+++ b/frappe/core/doctype/user_permission/user_permission.py
@@ -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))
\ No newline at end of file
diff --git a/frappe/core/doctype/user_permission/user_permission_list.js b/frappe/core/doctype/user_permission/user_permission_list.js
index 39a4648334..00d829b2a0 100644
--- a/frappe/core/doctype/user_permission/user_permission_list.js
+++ b/frappe/core/doctype/user_permission/user_permission_list.js
@@ -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);
+ }
}
-};
+};
\ No newline at end of file
diff --git a/frappe/core/notifications.py b/frappe/core/notifications.py
index 619231e5cd..0f932d9f67 100644
--- a/frappe/core/notifications.py
+++ b/frappe/core/notifications.py
@@ -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"
diff --git a/frappe/core/page/desktop/README.md b/frappe/core/page/desktop/README.md
deleted file mode 100644
index 4ac65f50b5..0000000000
--- a/frappe/core/page/desktop/README.md
+++ /dev/null
@@ -1 +0,0 @@
-First screen after login. Array of module icons based on permission.
\ No newline at end of file
diff --git a/frappe/core/page/desktop/__init__.py b/frappe/core/page/desktop/__init__.py
index 0e57cb68c3..e69de29bb2 100644
--- a/frappe/core/page/desktop/__init__.py
+++ b/frappe/core/page/desktop/__init__.py
@@ -1,3 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
-
diff --git a/frappe/core/page/desktop/all_applications_dialog.html b/frappe/core/page/desktop/all_applications_dialog.html
deleted file mode 100644
index d26152c84b..0000000000
--- a/frappe/core/page/desktop/all_applications_dialog.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-{% if (frappe.user.has_role("System Manager")) { %}
-Install new applications
-
-{% } %}
-
-{%= __("Checked items will be shown on desktop") %}
-
- {% 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; }
- %}
-
-
-
-
-
- {% } %}
-
diff --git a/frappe/core/page/desktop/desktop.js b/frappe/core/page/desktop/desktop.js
index 24dd0c3b4e..13b64c9246 100644
--- a/frappe/core/page/desktop/desktop.js
+++ b/frappe/core/page/desktop/desktop.js
@@ -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 = $('')
- .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 =
- `
-
- `;
-
- $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() {};
\ No newline at end of file
diff --git a/frappe/core/page/desktop/desktop.json b/frappe/core/page/desktop/desktop.json
index 6255e3e6fe..66bbfbfd40 100644
--- a/frappe/core/page/desktop/desktop.json
+++ b/frappe/core/page/desktop/desktop.json
@@ -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"
}
\ No newline at end of file
diff --git a/frappe/core/page/desktop/desktop.py b/frappe/core/page/desktop/desktop.py
deleted file mode 100644
index f426a67979..0000000000
--- a/frappe/core/page/desktop/desktop.py
+++ /dev/null
@@ -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'))))
diff --git a/frappe/core/page/desktop/desktop_help_message.html b/frappe/core/page/desktop/desktop_help_message.html
deleted file mode 100644
index 7de47abf03..0000000000
--- a/frappe/core/page/desktop/desktop_help_message.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{{ title }}
-{{ description }}
-
\ No newline at end of file
diff --git a/frappe/core/page/desktop/desktop_icon_grid.html b/frappe/core/page/desktop/desktop_icon_grid.html
deleted file mode 100644
index 6b85c8a56e..0000000000
--- a/frappe/core/page/desktop/desktop_icon_grid.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
- {% for (var i=0, l=desktop_items.length; i < l; i++) { %}
- {{ frappe.render_template("desktop_module_icon", desktop_items[i]) }}
- {% } %}
-
-
-
-
diff --git a/frappe/core/page/desktop/desktop_list_view.html b/frappe/core/page/desktop/desktop_list_view.html
deleted file mode 100644
index d3dd514076..0000000000
--- a/frappe/core/page/desktop/desktop_list_view.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
- {% for (var i=0, l=desktop_items.length; i < l; i++) {
- var module = desktop_items[i];
- %}
-
-
-
- {{ module._label }}
-
-
-
- {% } %}
-
-
-
-
diff --git a/frappe/core/page/desktop/desktop_module_icon.html b/frappe/core/page/desktop/desktop_module_icon.html
deleted file mode 100644
index 3e9a451eec..0000000000
--- a/frappe/core/page/desktop/desktop_module_icon.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/frappe/core/page/recorder/__init__.py b/frappe/core/page/recorder/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/page/recorder/recorder.js b/frappe/core/page/recorder/recorder.js
new file mode 100644
index 0000000000..f38af41af0
--- /dev/null
+++ b/frappe/core/page/recorder/recorder.js
@@ -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($(''));
+ }
+
+ show() {
+
+ }
+}
diff --git a/frappe/core/page/recorder/recorder.json b/frappe/core/page/recorder/recorder.json
new file mode 100644
index 0000000000..43dfbc0e09
--- /dev/null
+++ b/frappe/core/page/recorder/recorder.json
@@ -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"
+}
\ No newline at end of file
diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js
index 125db507ea..0a6f6d4d54 100644
--- a/frappe/custom/doctype/customize_form/customize_form.js
+++ b/frappe/custom/doctype/customize_form/customize_form.js
@@ -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;
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 136e25cd93..c9b184cbd1 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -881,9 +881,14 @@ class Database(object):
def get_descendants(self, doctype, name):
'''Return descendants of the current record'''
- lft, rgt = self.get_value(doctype, name, ('lft', 'rgt'))
- return self.sql_list('''select name from `tab{doctype}`
- where lft > {lft} and rgt < {rgt}'''.format(doctype=doctype, lft=lft, rgt=rgt))
+ node_location_indexes = self.get_value(doctype, name, ('lft', 'rgt'))
+ if node_location_indexes:
+ lft, rgt = node_location_indexes
+ return self.sql_list('''select name from `tab{doctype}`
+ where lft > {lft} and rgt < {rgt}'''.format(doctype=doctype, lft=lft, rgt=rgt))
+ else:
+ # when document does not exist
+ return []
def is_missing_table_or_column(self, e):
return self.is_missing_column(e) or self.is_missing_table(e)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 30ef20467f..8a95bba228 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -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'),
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index fd0e0c72e1..cfbbb80b4c 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -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),
diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.json b/frappe/desk/doctype/desktop_icon/desktop_icon.json
index ee9765e944..59c95953ad 100644
--- a/frappe/desk/doctype/desktop_icon/desktop_icon.json
+++ b/frappe/desk/doctype/desktop_icon/desktop_icon.json
@@ -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
}
\ No newline at end of file
diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py
index 175d1ece7d..fcf10ef61d 100644
--- a/frappe/desk/doctype/desktop_icon/desktop_icon.py
+++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py
@@ -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()
diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index 0566a4d14b..7a8b059baa 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -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')
- )))
+ (timeline_doctype=%(doctype)s and timeline_name=%(name)s)
+ and (communication_type='Communication')
+ )
)'''
diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py
index 403da16361..a0a33f5d42 100644
--- a/frappe/desk/form/utils.py
+++ b/frappe/desk/form/utils.py
@@ -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)
diff --git a/frappe/desk/like.py b/frappe/desk/like.py
index ec01eace83..e7d1ff298c 100644
--- a/frappe/desk/like.py
+++ b/frappe/desk/like.py
@@ -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)
diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py
index f5a1e47bf4..43b5a30e64 100644
--- a/frappe/desk/moduleview.py
+++ b/frappe/desk/moduleview.py
@@ -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)
@@ -40,7 +40,43 @@ def get_data(module):
data = combine_common_sections(data)
data = apply_permissions(data)
- #set_last_modified(data)
+ # set_last_modified(data)
+
+ if build:
+ exists_cache = {}
+ def doctype_contains_a_record(name):
+ exists = exists_cache.get(name)
+ if not exists:
+ if not frappe.db.get_value('DocType', name, 'issingle'):
+ exists = frappe.db.count(name)
+ else:
+ exists = True
+ exists_cache[name] = exists
+ return exists
+
+ for section in data:
+ for item in section["items"]:
+ # Onboarding
+
+ # First disable based on exists of depends_on list
+ doctype = item.get("doctype")
+ dependencies = item.get("dependencies") or None
+ if not dependencies and doctype:
+ item["dependencies"] = [doctype]
+
+ dependencies = item.get("dependencies")
+ if dependencies:
+ incomplete_dependencies = [d for d in dependencies if not doctype_contains_a_record(d)]
+ if len(incomplete_dependencies):
+ item["incomplete_dependencies"] = incomplete_dependencies
+
+ if item.get("onboard"):
+ # Mark Spotlights for initial
+ if item.get("type") == "doctype":
+ name = item.get("name")
+ count = doctype_contains_a_record(name)
+
+ item["count"] = count
return data
@@ -184,14 +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`"""
diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py
index 43b67f2976..c700e8f046 100644
--- a/frappe/desk/notifications.py
+++ b/frappe/desk/notifications.py
@@ -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)
diff --git a/frappe/desk/page/activity/activity.js b/frappe/desk/page/activity/activity.js
index 4f012512e4..fe81fdf0fa 100644
--- a/frappe/desk/page/activity/activity.js
+++ b/frappe/desk/page/activity/activity.js
@@ -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
};
}
diff --git a/frappe/desk/page/activity/activity.py b/frappe/desk/page/activity/activity.py
index 54d643ade2..e25c94d0e5 100644
--- a/frappe/desk/page/activity/activity.py
+++ b/frappe/desk/page/activity/activity.py
@@ -7,43 +7,45 @@ from frappe.utils import cint
from frappe.core.doctype.activity_log.feed import get_feed_match_conditions
@frappe.whitelist()
-def get_feed(start, page_length, show_likes=False):
+def get_feed(start, page_length):
"""get feed"""
- match_conditions = get_feed_match_conditions(frappe.session.user)
+ match_conditions_communication = get_feed_match_conditions(frappe.session.user, 'Communication')
+ match_conditions_comment = get_feed_match_conditions(frappe.session.user, 'Comment')
result = frappe.db.sql("""select X.*
from (select name, owner, modified, creation, seen, comment_type,
- reference_doctype, reference_name, link_doctype, link_name, subject,
- communication_type, communication_medium, content
- from `tabCommunication`
+ reference_doctype, reference_name, link_doctype, link_name, subject,
+ communication_type, communication_medium, content
+ from
+ `tabCommunication`
where
- communication_type in ("Communication", "Comment")
- and communication_medium != "Email"
- and (comment_type is null or comment_type != "Like"
- or (comment_type="Like" and (owner=%(user)s or reference_owner=%(user)s)))
- {match_conditions}
- {show_likes}
- union
+ communication_type = "Communication"
+ and communication_medium != "Email"
+ and {match_conditions_communication}
+ UNION
select name, owner, modified, creation, '0', 'Updated',
- reference_doctype, reference_name, link_doctype, link_name, subject,
- 'Comment', '', content
- from `tabActivity Log`) X
+ reference_doctype, reference_name, link_doctype, link_name, subject,
+ 'Comment', '', content
+ from
+ `tabActivity Log`
+ UNION
+ select name, owner, modified, creation, '0', comment_type,
+ reference_doctype, reference_name, link_doctype, link_name, '',
+ 'Comment', '', content
+ from
+ `tabComment`
+ where
+ {match_conditions_comment}
+ ) X
order by X.creation DESC
limit %(start)s, %(page_length)s"""
- .format(match_conditions="and {0}".format(match_conditions) if match_conditions else "",
- show_likes="and comment_type='Like'" if show_likes else ""),
- {
+ .format(match_conditions_comment = match_conditions_comment,
+ match_conditions_communication = match_conditions_communication), {
"user": frappe.session.user,
"start": cint(start),
"page_length": cint(page_length)
}, as_dict=True)
- if show_likes:
- # mark likes as seen!
- frappe.db.sql("""update `tabCommunication` set seen=1
- where comment_type='Like' and reference_owner=%s""", frappe.session.user)
- frappe.local.flags.commit = True
-
return result
@frappe.whitelist()
diff --git a/frappe/desk/page/modules/modules.js b/frappe/desk/page/modules/modules.js
deleted file mode 100644
index 80048fbf59..0000000000
--- a/frappe/desk/page/modules/modules.js
+++ /dev/null
@@ -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) => ``;
-
- let get_sidebar_html = () => {
- let sidebar_items_html = page.get_page_modules()
- .map(get_module_sidebar_item.bind(this)).join("");
-
- return ``;
- };
-
- // 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'));
- }
-};
diff --git a/frappe/desk/page/modules/modules.json b/frappe/desk/page/modules/modules.json
deleted file mode 100644
index 1858a921b2..0000000000
--- a/frappe/desk/page/modules/modules.json
+++ /dev/null
@@ -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"
-}
\ No newline at end of file
diff --git a/frappe/desk/page/modules/modules_section.html b/frappe/desk/page/modules/modules_section.html
deleted file mode 100644
index 5d23db795a..0000000000
--- a/frappe/desk/page/modules/modules_section.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-{% for (var i=0; i < data.length; i++) { var section = data[i]; %}
-{% if ((i % 2)===0) { %}
{% } %}
-
-
- {{ section.label }}
-
-
- {% for (var j=0; j < section.items.length; j++) {
- var item = section.items[j];
- if(item.shown) { %}
-
- {% } %}
- {% } %}
-
-
-{% if ((i % 2)===1 || i===data.length-1) { %}
{% } %}
-{% } %}
-
diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py
index 8954a9d36c..82b16da891 100644
--- a/frappe/desk/query_report.py
+++ b/frappe/desk/query_report.py
@@ -190,30 +190,34 @@ def run(report_name, filters=None, user=None):
def get_prepared_report_result(report, filters, dn="", user=None):
latest_report_data = {}
- # Only look for completed prepared reports with given filters.
- doc_list = frappe.get_all("Prepared Report",
- filters={"status": "Completed", "report_name": report.name, "filters": filters, "owner": user})
-
doc = None
- if len(doc_list):
- if dn:
- # Get specified dn
- doc = frappe.get_doc("Prepared Report", dn)
- else:
+ if dn:
+ # Get specified dn
+ doc = frappe.get_doc("Prepared Report", dn)
+ else:
+ # Only look for completed prepared reports with given filters.
+ doc_list = frappe.get_all("Prepared Report", filters={"status": "Completed", "filters": json.dumps(filters), "owner": user})
+ if doc_list:
# Get latest
doc = frappe.get_doc("Prepared Report", doc_list[0])
- # Prepared Report data is stored in a GZip compressed JSON file
- attached_file_name = frappe.db.get_value("File", {"attached_to_doctype": doc.doctype, "attached_to_name":doc.name}, "name")
- attached_file = frappe.get_doc('File', attached_file_name)
- compressed_content = attached_file.get_content()
- uncompressed_content = gzip_decompress(compressed_content)
- data = json.loads(uncompressed_content)
- if data:
- latest_report_data = {
- "columns": json.loads(doc.columns) if doc.columns else data[0],
- "result": data
- }
+ if doc:
+ try:
+ # Prepared Report data is stored in a GZip compressed JSON file
+ attached_file_name = frappe.db.get_value("File", {"attached_to_doctype": doc.doctype, "attached_to_name":doc.name}, "name")
+ attached_file = frappe.get_doc('File', attached_file_name)
+ compressed_content = attached_file.get_content()
+ uncompressed_content = gzip_decompress(compressed_content)
+ data = json.loads(uncompressed_content)
+ if data:
+ latest_report_data = {
+ "columns": json.loads(doc.columns) if doc.columns else data[0],
+ "result": data
+ }
+ except Exception:
+ frappe.delete_doc("Prepared Report", doc.name)
+ frappe.db.commit()
+ doc = None
latest_report_data.update({
"prepared_report": True,
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index 362f883581..42409867fb 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -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=[]):
diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json
index 5ab3cd1630..ce9b0f30b0 100644
--- a/frappe/email/doctype/email_account/email_account.json
+++ b/frappe/email/doctype/email_account/email_account.json
@@ -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",
diff --git a/frappe/email/doctype/email_group_member/email_group_member.py b/frappe/email/doctype/email_group_member/email_group_member.py
index d0968425d0..23b279e755 100644
--- a/frappe/email/doctype/email_group_member/email_group_member.py
+++ b/frappe/email/doctype/email_group_member/email_group_member.py
@@ -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"))
\ No newline at end of file
+ frappe.db.add_unique("Email Group Member", ("email_group", "email"))
diff --git a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py
index 2f879f98ee..e532e2b7eb 100644
--- a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py
+++ b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py
@@ -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)
diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py
index 486b44bea5..ba211bdf23 100644
--- a/frappe/email/doctype/notification/notification.py
+++ b/frappe/email/doctype/notification/notification.py
@@ -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):
diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py
index b4af93e61d..4904f60831 100755
--- a/frappe/email/email_body.py
+++ b/frappe/email/email_body.py
@@ -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 + '>'
diff --git a/frappe/email/queue.py b/frappe/email/queue.py
index ba66e670dd..a7988bc46e 100755
--- a/frappe/email/queue.py
+++ b/frappe/email/queue.py
@@ -174,7 +174,8 @@ def get_email_queue(recipients, sender, subject, **kwargs):
if att.get('fid'):
_attachments.append(att)
elif att.get("print_format_attachment") == 1:
- att['lang'] = frappe.local.lang
+ if not att.get('lang', None):
+ att['lang'] = frappe.local.lang
att['print_letterhead'] = kwargs.get('print_letterhead')
_attachments.append(att)
e.attachments = json.dumps(_attachments)
diff --git a/frappe/email/receive.py b/frappe/email/receive.py
index 9bdf3a9ba3..09aa4ff57a 100644
--- a/frappe/email/receive.py
+++ b/frappe/email/receive.py
@@ -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:
diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py
index 549b3f1d1e..99b5f94bf0 100644
--- a/frappe/email/smtp.py
+++ b/frappe/email/smtp.py
@@ -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):
diff --git a/frappe/installer.py b/frappe/installer.py
index c4af23976f..792d8580e8 100755
--- a/frappe/installer.py
+++ b/frappe/installer.py
@@ -23,7 +23,7 @@ from frappe.database import setup_database
def install_db(root_login="root", root_password=None, db_name=None, source_sql=None,
admin_password=None, verbose=True, force=0, site_config=None, reinstall=False,
db_type=None):
-
+
if not db_type:
db_type = frappe.conf.db_type or 'mariadb'
@@ -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:
diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py
index 9364f4403a..8527828404 100644
--- a/frappe/model/base_document.py
+++ b/frappe/model/base_document.py
@@ -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,20 +555,39 @@ 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:
- if self.parentfield and self.idx:
- reference = _("{0}, Row {1}").format(_(self.doctype), self.idx)
+ self.throw_length_exceeded_error(df, max_length, value)
- else:
- reference = "{0} {1}".format(_(self.doctype), self.name)
+ elif column_type in ('int', 'bigint', 'smallint'):
+ max_length = max_positive_value[column_type]
- frappe.throw(_("{0}: '{1}' ({3}) will get truncated, as max characters allowed is {2}")\
- .format(reference, _(df.label), max_length, value), frappe.CharacterLengthExceededError, title=_('Value too big'))
+ 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)
+
+ else:
+ reference = "{0} {1}".format(_(self.doctype), self.name)
+
+ frappe.throw(_("{0}: '{1}' ({3}) will get truncated, as max characters allowed is {2}")\
+ .format(reference, _(df.label), max_length, value), frappe.CharacterLengthExceededError, title=_('Value too big'))
def _validate_update_after_submit(self):
# get the full doc with children
diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py
index 76176e3939..f6744c9310 100644
--- a/frappe/model/create_new.py
+++ b/frappe/model/create_new.py
@@ -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
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index 178bc427eb..9e64b913c3 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -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):
diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py
index 20e0d00199..153065c5ce 100644
--- a/frappe/model/delete_doc.py
+++ b/frappe/model/delete_doc.py
@@ -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),
diff --git a/frappe/model/document.py b/frappe/model/document.py
index 6bd82ba25a..d6876c7347 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -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,
- "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)
+ out = frappe.get_doc({
+ "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)
return out
def add_seen(self, user=None):
diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py
index 0703a064d3..94a0d0dbfa 100644
--- a/frappe/modules/utils.py
+++ b/frappe/modules/utils.py
@@ -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,14 +103,31 @@ def sync_customizations_for_doctype(data, folder):
# sync single doctype exculding the child doctype
def sync_single_doctype(doc_type):
- 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)
+ 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]:
+ _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
if doc_type == doctype or not os.path.exists(os.path.join(folder, frappe.scrub(doc_type)+".json")):
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 413699c5b3..853aed97fd 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -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
\ No newline at end of file
+execute:frappe.delete_doc("Page", "modules", ignore_missing=True)
+frappe.patches.v11_0.set_default_letter_head_source
diff --git a/frappe/patches/v11_0/drop_column_apply_user_permissions.py b/frappe/patches/v11_0/drop_column_apply_user_permissions.py
index ed0a6881af..4f46bc0907 100644
--- a/frappe/patches/v11_0/drop_column_apply_user_permissions.py
+++ b/frappe/patches/v11_0/drop_column_apply_user_permissions.py
@@ -6,8 +6,9 @@ def execute():
to_remove = ['DocPerm', 'Custom DocPerm']
for doctype in to_remove:
- if column in frappe.db.get_table_columns(doctype):
- frappe.db.sql("alter table `tab{0}` drop column {1}".format(doctype, column))
+ 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))
frappe.reload_doc('core', 'doctype', 'docperm', force=True)
frappe.reload_doc('core', 'doctype', 'custom_docperm', force=True)
diff --git a/frappe/patches/v11_0/set_default_letter_head_source.py b/frappe/patches/v11_0/set_default_letter_head_source.py
new file mode 100644
index 0000000000..069f4e3d2e
--- /dev/null
+++ b/frappe/patches/v11_0/set_default_letter_head_source.py
@@ -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"')
\ No newline at end of file
diff --git a/frappe/patches/v12_0/setup_comments_from_communications.py b/frappe/patches/v12_0/setup_comments_from_communications.py
new file mode 100644
index 0000000000..2ea6a609b6
--- /dev/null
+++ b/frappe/patches/v12_0/setup_comments_from_communications.py
@@ -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"')
\ No newline at end of file
diff --git a/frappe/patches/v6_24/sync_desktop_icons.py b/frappe/patches/v6_24/sync_desktop_icons.py
deleted file mode 100644
index 74f52e6056..0000000000
--- a/frappe/patches/v6_24/sync_desktop_icons.py
+++ /dev/null
@@ -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
diff --git a/frappe/patches/v7_0/add_communication_in_doc.py b/frappe/patches/v7_0/add_communication_in_doc.py
index 92120634ef..4db02c5bab 100644
--- a/frappe/patches/v7_0/add_communication_in_doc.py
+++ b/frappe/patches/v7_0/add_communication_in_doc.py
@@ -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",
diff --git a/frappe/patches/v7_0/desktop_icons_hidden_by_admin_as_blocked.py b/frappe/patches/v7_0/desktop_icons_hidden_by_admin_as_blocked.py
deleted file mode 100644
index 496af17cd2..0000000000
--- a/frappe/patches/v7_0/desktop_icons_hidden_by_admin_as_blocked.py
+++ /dev/null
@@ -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')
\ No newline at end of file
diff --git a/frappe/patches/v7_2/merge_knowledge_base.py b/frappe/patches/v7_2/merge_knowledge_base.py
index 81ff98230b..301d15e1dd 100644
--- a/frappe/patches/v7_2/merge_knowledge_base.py
+++ b/frappe/patches/v7_2/merge_knowledge_base.py
@@ -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')
diff --git a/frappe/patches/v8_0/fix_non_english_desktop_icons.py b/frappe/patches/v8_0/fix_non_english_desktop_icons.py
deleted file mode 100644
index b4389578ab..0000000000
--- a/frappe/patches/v8_0/fix_non_english_desktop_icons.py
+++ /dev/null
@@ -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/%'
- """)
\ No newline at end of file
diff --git a/frappe/patches/v8_0/setup_email_inbox.py b/frappe/patches/v8_0/setup_email_inbox.py
index 8cd8b28116..1bfe3b0b74 100644
--- a/frappe/patches/v8_0/setup_email_inbox.py
+++ b/frappe/patches/v8_0/setup_email_inbox.py
@@ -3,31 +3,18 @@ import frappe, json
from frappe.core.doctype.user.user import ask_pass_update, setup_user_email_inbox
def execute():
- """
+ """
depricate email inbox page if exists
remove desktop icon for email inbox page if exists
patch to remove Custom DocPerm for communication
+ add user inbox child table entry for existing email account in not exists
"""
if frappe.db.exists("Page", "email_inbox"):
frappe.delete_doc("Page", "email_inbox")
- desktop_icon = frappe.db.get_value("Desktop Icon", {
- "module_name": "Email",
- "type": "Page",
- "link": "email_inbox"
- })
-
- if desktop_icon:
- frappe.delete_doc("Desktop Icon", desktop_icon)
-
frappe.db.sql("""update `tabCustom DocPerm` set `write`=0, email=1 where parent='Communication'""")
- setup_inbox_from_email_account()
-
-def setup_inbox_from_email_account():
- """ add user inbox child table entry for existing email account in not exists """
-
frappe.reload_doc("core", "doctype", "user_email")
frappe.reload_doc("email", "doctype", "email_account")
@@ -36,4 +23,4 @@ def setup_inbox_from_email_account():
for email_account in email_accounts:
setup_user_email_inbox(email_account.get("name"), email_account.get("awaiting_password"),
- email_account.get("email_id"), email_account.get("enabled_outgoing"))
\ No newline at end of file
+ email_account.get("email_id"), email_account.get("enabled_outgoing"))
diff --git a/frappe/patches/v8_0/update_desktop_icons.py b/frappe/patches/v8_0/update_desktop_icons.py
deleted file mode 100644
index ea8527718b..0000000000
--- a/frappe/patches/v8_0/update_desktop_icons.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.utils import cstr
-
-def execute():
- """ update the desktop icons """
-
- frappe.reload_doc('desk', 'doctype', 'desktop_icon')
-
- icons = frappe.get_all("Desktop Icon", filters={ "type": "link" }, fields=["link", "name"])
-
- for icon in icons:
- # check if report exists
- icon_link = icon.get("link", "") or ""
- parts = icon_link.split("/")
- if not parts:
- continue
-
- report_name = parts[-1]
- if "report" in parts[0] and frappe.db.get_value("Report", report_name):
- frappe.db.sql(""" update `tabDesktop Icon` set _report='{report_name}'
- where name='{name}'""".format(report_name=report_name, name=icon.get("name")))
\ No newline at end of file
diff --git a/frappe/permissions.py b/frappe/permissions.py
index abe5eca84d..261049c226 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -24,8 +24,10 @@ def print_has_permission_check_logs(func):
def inner(*args, **kwargs):
frappe.flags['has_permission_check_logs'] = []
result = func(*args, **kwargs)
+ self_perm_check = True if not kwargs.get('user') else kwargs.get('user') == frappe.session.user
# print only if access denied
- if not result:
+ # and if user is checking his own permission
+ if not result and self_perm_check:
msgprint(('
').join(frappe.flags['has_permission_check_logs']))
frappe.flags.pop('has_permission_check_logs', None)
return result
diff --git a/frappe/printing/doctype/letter_head/letter_head.js b/frappe/printing/doctype/letter_head/letter_head.js
index f70224565d..ca4dad2d07 100644
--- a/frappe/printing/doctype/letter_head/letter_head.js
+++ b/frappe/printing/doctype/letter_head/letter_head.js
@@ -3,6 +3,6 @@
frappe.ui.form.on('Letter Head', {
refresh: function(frm) {
-
+ frm.flag_public_attachments = true;
}
});
diff --git a/frappe/printing/doctype/letter_head/letter_head.json b/frappe/printing/doctype/letter_head/letter_head.json
index fb10be3f84..1bee220213 100644
--- a/frappe/printing/doctype/letter_head/letter_head.json
+++ b/frappe/printing/doctype/letter_head/letter_head.json
@@ -1,241 +1,450 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "field:letter_head_name",
- "beta": 0,
- "creation": "2012-11-22 17:45:46",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "allow_copy": 0,
+ "allow_events_in_timeline": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 1,
+ "autoname": "field:letter_head_name",
+ "beta": 0,
+ "creation": "2012-11-22 17:45:46",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 0,
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "letter_head_name",
- "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": "Letter Head Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "letter_head_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "letter_head_name",
+ "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": "Letter Head Name",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "letter_head_name",
+ "oldfieldtype": "Data",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "letter_head_name",
- "fieldname": "disabled",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Disabled",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "disabled",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "letter_head_name",
+ "fieldname": "source",
+ "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": "Letter Head Based On",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Image\nHTML",
+ "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "letter_head_name",
- "description": "Check this to make this the default letter head in all prints",
- "fieldname": "is_default",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Is Default",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "is_default",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_3",
+ "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!doc.__islocal",
- "description": "Letter Head in HTML",
- "fieldname": "content",
- "fieldtype": "Text 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,
- "oldfieldname": "content",
- "oldfieldtype": "Text Editor",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "letter_head_name",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Disabled",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "disabled",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!doc.__islocal",
- "fieldname": "footer",
- "fieldtype": "Text Editor",
- "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": "Footer",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "letter_head_name",
+ "description": "",
+ "fieldname": "is_default",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Default Letter Head",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "is_default",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 1,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "collapsible_depends_on": "",
+ "columns": 0,
+ "depends_on": "eval:doc.letter_head_name && doc.source === 'Image'",
+ "fieldname": "letter_head_image_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Letter Head Image",
+ "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,
+ "depends_on": "eval:doc.letter_head_name && doc.source === 'Image'",
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "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": "Image",
+ "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,
+ "collapsible_depends_on": "",
+ "columns": 0,
+ "depends_on": "eval:doc.source==='HTML' && doc.letter_head_name",
+ "fieldname": "header_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Header",
+ "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,
+ "depends_on": "eval:!doc.__islocal && doc.source==='HTML'",
+ "description": "Letter Head in HTML",
+ "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": "Header HTML",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "content",
+ "oldfieldtype": "Text Editor",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "columns": 0,
+ "fieldname": "footer_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Footer",
+ "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,
+ "depends_on": "eval:!doc.__islocal",
+ "description": "Footer will display correctly only in PDF",
+ "fieldname": "footer",
+ "fieldtype": "HTML Editor",
+ "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": "Footer HTML",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
"unique": 0
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-font",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 3,
- "modified": "2018-04-21 17:23:55.709575",
- "modified_by": "Administrator",
- "module": "Printing",
- "name": "Letter Head",
- "owner": "Administrator",
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "icon": "fa fa-font",
+ "idx": 1,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 3,
+ "modified": "2019-02-12 09:48:26.017783",
+ "modified_by": "Administrator",
+ "module": "Printing",
+ "name": "Letter Head",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "amend": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 0,
+ "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,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "All",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
+ "amend": 0,
+ "cancel": 0,
+ "create": 0,
+ "delete": 0,
+ "email": 0,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 0,
+ "read": 1,
+ "report": 0,
+ "role": "All",
+ "set_user_permissions": 0,
+ "share": 0,
+ "submit": 0,
"write": 0
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_order": "ASC",
+ "track_changes": 1,
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/frappe/printing/doctype/letter_head/letter_head.py b/frappe/printing/doctype/letter_head/letter_head.py
index 3e9ce09c83..1a70ebcb08 100644
--- a/frappe/printing/doctype/letter_head/letter_head.py
+++ b/frappe/printing/doctype/letter_head/letter_head.py
@@ -3,16 +3,30 @@
from __future__ import unicode_literals
import frappe
+from frappe.utils import is_image
from frappe.model.document import Document
class LetterHead(Document):
+ def before_insert(self):
+ # for better UX, let user set from attachment
+ self.source = 'Image'
+
def validate(self):
+ self.set_image()
if not self.is_default:
if not frappe.db.sql("""select count(*) from `tabLetter Head` where ifnull(is_default,0)=1"""):
self.is_default = 1
+ def set_image(self):
+ if self.source=='Image':
+ if self.image and is_image(self.image):
+ self.content = '
'.format(self.image)
+ frappe.msgprint(frappe._('Header HTML set from attachment {0}').format(self.image), alert = True)
+ else:
+ frappe.msgprint(frappe._('Please attach an image file to set HTML'), alert = True, indicator = 'orange')
+
def on_update(self):
self.set_as_default()
diff --git a/frappe/printing/doctype/letter_head/test_letter_head.py b/frappe/printing/doctype/letter_head/test_letter_head.py
index 9535841817..b69e9924ea 100644
--- a/frappe/printing/doctype/letter_head/test_letter_head.py
+++ b/frappe/printing/doctype/letter_head/test_letter_head.py
@@ -7,4 +7,14 @@ import frappe
import unittest
class TestLetterHead(unittest.TestCase):
- pass
+ def test_auto_image(self):
+ letter_head = frappe.get_doc(dict(
+ doctype = 'Letter Head',
+ letter_head_name = 'Test',
+ source = 'Image',
+ image = '/public/test.png'
+ )).insert()
+
+ # test if image is automatically set
+ self.assertTrue(letter_head.image in letter_head.content)
+
diff --git a/frappe/public/build.json b/frappe/public/build.json
index 8798c0d074..9c395c9bca 100755
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -25,6 +25,9 @@
"js/frappe-vue.min.js": [
"public/js/frappe_vue.js"
],
+ "js/frappe-recorder.min.js": [
+ "public/js/frappe/recorder/recorder.js"
+ ],
"js/frappe-web.min.js": [
"public/js/frappe/class.js",
"public/js/frappe/polyfill.js",
@@ -140,6 +143,7 @@
"public/js/lib/Sortable.min.js",
"public/js/lib/jquery/jquery.hotkeys.js",
"public/js/lib/bootstrap.min.js",
+ "node_modules/vue/dist/vue.js",
"node_modules/moment/min/moment-with-locales.min.js",
"node_modules/moment-timezone/builds/moment-timezone-with-data.min.js",
"public/js/lib/socket.io.min.js",
@@ -236,7 +240,6 @@
"public/js/frappe/ui/toolbar/about.js",
"public/js/frappe/ui/toolbar/navbar.html",
"public/js/frappe/ui/toolbar/toolbar.js",
- "public/js/frappe/ui/toolbar/modules_select.js",
"public/js/frappe/ui/toolbar/notifications.js",
"public/js/frappe/views/communication.js",
"public/js/frappe/views/translation_manager.js",
@@ -390,5 +393,8 @@
],
"js/social.min.js": [
"public/js/frappe/social/social_home.js"
+ ],
+ "js/modules.min.js": [
+ "public/js/frappe/views/modules_home.js"
]
}
diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js
index 68ff55efe5..d0464d46cd 100644
--- a/frappe/public/js/frappe/desk.js
+++ b/frappe/public/js/frappe/desk.js
@@ -72,7 +72,7 @@ frappe.Application = Class.extend({
frappe.msgprint(frappe.boot.messages);
}
- if (frappe.boot.change_log && frappe.boot.change_log.length) {
+ if (frappe.boot.change_log && frappe.boot.change_log.length && !window.Cypress) {
this.show_change_log();
} else {
this.show_notes();
@@ -194,7 +194,7 @@ frappe.Application = Class.extend({
load_bootinfo: function() {
if(frappe.boot) {
frappe.modules = {};
- frappe.boot.desktop_icons.forEach(function(m) {
+ frappe.boot.allowed_modules.forEach(function(m) {
frappe.modules[m.module_name]=m;
});
frappe.model.sync(frappe.boot.docs);
@@ -264,9 +264,6 @@ frappe.Application = Class.extend({
$.extend(frappe.boot.notification_info, r.message);
$(document).trigger("notification-update");
- // update in module views
- me.update_notification_count_in_modules();
-
if(frappe.get_route()[0] != "messages") {
if(r.message.new_messages.length) {
frappe.utils.set_title_prefix("(" + r.message.new_messages.length + ")");
@@ -279,18 +276,6 @@ frappe.Application = Class.extend({
}
},
- update_notification_count_in_modules: function() {
- $.each(frappe.boot.notification_info.open_count_doctype, function(doctype, count) {
- if(count) {
- $('.open-notification.global[data-doctype="'+ doctype +'"]')
- .removeClass("hide").html(count > 99 ? "99+" : count);
- } else {
- $('.open-notification.global[data-doctype="'+ doctype +'"]')
- .addClass("hide");
- }
- });
- },
-
set_globals: function() {
frappe.session.user = frappe.boot.user.name;
frappe.session.user_email = frappe.boot.user.email;
@@ -412,6 +397,7 @@ frappe.Application = Class.extend({
}
});
dialog.set_primary_action(__('Login'), () => {
+ dialog.set_message(__('Authenticating...'));
frappe.call({
method: 'login',
args: {
@@ -591,86 +577,3 @@ frappe.get_module = function(m, default_module) {
return module;
};
-
-frappe.get_desktop_icons = function(show_hidden, show_global) {
- // filter valid icons
-
- // hidden == hidden from desktop
- // blocked == no view from modules either
-
- var out = [];
-
- var add_to_out = function(module) {
- module = frappe.get_module(module.module_name, module);
- module.app_icon = frappe.ui.app_icon.get_html(module);
- out.push(module);
- };
-
- var show_module = function(m) {
- var out = true;
- if(m.type==="page") {
- out = m.link in frappe.boot.page_info;
- } else if(m.force_show) {
- out = true;
- } else if(m._report) {
- out = m._report in frappe.boot.user.all_reports;
- } else if(m._doctype) {
- //out = frappe.model.can_read(m._doctype);
- out = frappe.boot.user.can_read.includes(m._doctype);
- } else {
- if(['Help', 'Settings'].includes(m.module_name)) {
- // no permissions necessary for learn
- out = true;
- } else if(m.module_name==='Setup' && frappe.user.has_role('System Manager')) {
- out = true;
- } else {
- out = frappe.boot.user.allow_modules.indexOf(m.module_name) !== -1;
- }
- }
- if(m.hidden && !show_hidden) {
- out = false;
- }
- if(m.blocked && !show_global) {
- out = false;
- }
- return out;
- };
-
- let m;
- for (var i=0, l=frappe.boot.desktop_icons.length; i < l; i++) {
- m = frappe.boot.desktop_icons[i];
- if ((['Setup', 'Core'].indexOf(m.module_name) === -1) && show_module(m)) {
- add_to_out(m);
- }
- }
-
- if(frappe.user_roles.includes('System Manager')) {
- m = frappe.get_module('Setup');
- if(show_module(m)) add_to_out(m);
- }
-
- if(frappe.user_roles.includes('Administrator')) {
- m = frappe.get_module('Core');
- if(show_module(m)) add_to_out(m);
- }
-
- return out;
-};
-
-frappe.add_to_desktop = function(label, doctype, report) {
- frappe.call({
- method: 'frappe.desk.doctype.desktop_icon.desktop_icon.add_user_icon',
- args: {
- 'link': frappe.get_route_str(),
- 'label': label,
- 'type': 'link',
- '_doctype': doctype,
- '_report': report
- },
- callback: function(r) {
- if(r.message) {
- frappe.show_alert(__("Added"));
- }
- }
- });
-};
diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js
index 4c584c1241..39e576ebf8 100644
--- a/frappe/public/js/frappe/dom.js
+++ b/frappe/public/js/frappe/dom.js
@@ -232,6 +232,16 @@ frappe.dom = {
frappe.ui.scroll(section.parent().parent());
}
}, 200);
+ },
+ pixel_to_inches(pixels) {
+ const div = $('');
+ div.appendTo(document.body);
+
+ const dpi_x = document.getElementById('dpi').offsetWidth;
+ const inches = pixels / dpi_x;
+ div.remove();
+
+ return inches;
}
};
diff --git a/frappe/public/js/frappe/form/controls/check.js b/frappe/public/js/frappe/form/controls/check.js
index b54571b166..a9d2e78264 100644
--- a/frappe/public/js/frappe/form/controls/check.js
+++ b/frappe/public/js/frappe/form/controls/check.js
@@ -1,16 +1,16 @@
frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({
input_type: "checkbox",
make_wrapper: function() {
- this.$wrapper = $('').appendTo(this.parent);
+ this.$wrapper = $(``).appendTo(this.parent);
},
set_input_areas: function() {
this.label_area = this.label_span = this.$wrapper.find(".label-area").get(0);
diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js
index 7ba1b0b822..d68f6254b9 100644
--- a/frappe/public/js/frappe/form/controls/geolocation.js
+++ b/frappe/public/js/frappe/form/controls/geolocation.js
@@ -16,7 +16,7 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlCode.extend({
if ($input_wrapper.is(':visible')) {
this.make_map();
} else {
- $(document).on('quick-entry-dialog-open', () => {
+ $(document).on('frappe.ui.Dialog:shown', () => {
this.make_map();
});
}
diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js
index 1d413b83f8..86a99c970f 100644
--- a/frappe/public/js/frappe/form/controls/link.js
+++ b/frappe/public/js/frappe/form/controls/link.js
@@ -157,7 +157,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
me.set_custom_query(args);
frappe.call({
- type: "GET",
+ type: "POST",
method:'frappe.desk.search.search_link',
no_spinner: true,
args: args,
diff --git a/frappe/public/js/frappe/form/controls/multicheck.js b/frappe/public/js/frappe/form/controls/multicheck.js
index 35e1a1a19b..5e8c437445 100644
--- a/frappe/public/js/frappe/form/controls/multicheck.js
+++ b/frappe/public/js/frappe/form/controls/multicheck.js
@@ -86,7 +86,9 @@ frappe.ui.form.ControlMultiCheck = frappe.ui.form.Control.extend({
this.selected_options.push(option_name);
} else {
let index = this.selected_options.indexOf(option_name);
- this.selected_options.splice(index, 1);
+ if(index > -1) {
+ this.selected_options.splice(index, 1);
+ }
}
this.df.on_change && this.df.on_change();
});
diff --git a/frappe/public/js/frappe/form/controls/multiselect.js b/frappe/public/js/frappe/form/controls/multiselect.js
index ae6a7a124e..bc4f09468b 100644
--- a/frappe/public/js/frappe/form/controls/multiselect.js
+++ b/frappe/public/js/frappe/form/controls/multiselect.js
@@ -32,6 +32,31 @@ frappe.ui.form.ControlMultiSelect = frappe.ui.form.ControlAutocomplete.extend({
});
},
+ get_value() {
+ let data = this._super();
+ // find value of label from option list and return actual value string
+ if (this.df.options && this.df.options[0].label) {
+ data = data.split(',').map(op => op.trim());
+ data = data.map(val => {
+ let option = this.df.options.find(op => op.label === val);
+ return option ? option.value : null;
+ }).filter(n => n != null).join(', ');
+ }
+ return data;
+ },
+
+ set_formatted_input(value) {
+ if (!value) return;
+ // find label of value from option list and set from it as input
+ if (this.df.options && this.df.options[0].label) {
+ value = value.split(',').map(d => d.trim()).map(val => {
+ let option = this.df.options.find(op => op.value === val);
+ return option ? option.label : val;
+ }).filter(n => n != null).join(', ');
+ }
+ this._super(value);
+ },
+
get_values() {
const value = this.get_value() || '';
const values = value.split(/\s*,\s*/).filter(d => d);
diff --git a/frappe/public/js/frappe/form/controls/signature.js b/frappe/public/js/frappe/form/controls/signature.js
index 315a8ccfe8..82dcb8f341 100644
--- a/frappe/public/js/frappe/form/controls/signature.js
+++ b/frappe/public/js/frappe/form/controls/signature.js
@@ -7,7 +7,14 @@ frappe.ui.form.ControlSignature = frappe.ui.form.ControlData.extend({
// make jSignature field
this.body = $('').appendTo(me.wrapper);
- this.make_pad();
+
+ if (this.body.is(':visible')) {
+ this.make_pad();
+ } else {
+ $(document).on('frappe.ui.Dialog:shown', () => {
+ this.make_pad();
+ });
+ }
this.img_wrapper = $(`
diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js
index 86f85971c5..3304c574ee 100644
--- a/frappe/public/js/frappe/form/footer/timeline.js
+++ b/frappe/public/js/frappe/form/footer/timeline.js
@@ -28,7 +28,7 @@ frappe.ui.form.Timeline = class Timeline {
render_input: true,
only_input: true,
on_submit: (val) => {
- val && this.insert_comment("Comment", val, this.comment_area.button);
+ val && this.insert_comment(val, this.comment_area.button);
}
});
@@ -149,10 +149,17 @@ frappe.ui.form.Timeline = class Timeline {
this.wrapper.toggle(true);
this.list.empty();
this.comment_area.set_value('');
- let communications = this.get_communications(true);
- var views = this.get_view_logs();
+ // get all communications
+ let communications = this.get_communications(true);
+
+ // append views
+ var views = this.get_view_logs();
var timeline = communications.concat(views);
+
+ // append comments
+ timeline = timeline.concat(this.get_comments());
+ // sort
timeline
.filter(a => a.content)
.sort((b, c) => me.compare_dates(b, c))
@@ -312,7 +319,7 @@ frappe.ui.form.Timeline = class Timeline {
c["delete"] = "";
c["edit"] = "";
if(c.communication_type=="Comment" && (c.comment_type || "Comment") === "Comment") {
- if(frappe.model.can_delete("Communication")) {
+ if(frappe.model.can_delete("Comment")) {
c["delete"] = '';
}
@@ -498,6 +505,22 @@ frappe.ui.form.Timeline = class Timeline {
return out;
}
+ get_comments() {
+ let docinfo = this.frm.get_docinfo();
+
+ for (let c of docinfo.comments) {
+ this.cast_comment_as_communication(c);
+ }
+
+ return docinfo.comments;
+ }
+
+ cast_comment_as_communication(c) {
+ c.sender = c.comment_email;
+ c.sender_full_name = c.comment_by;
+ c.communication_type = 'Comment';
+ }
+
build_version_comments(docinfo, out) {
var me = this;
docinfo.versions.forEach(function(version) {
@@ -530,8 +553,8 @@ frappe.ui.form.Timeline = class Timeline {
if(field_display_status === 'Read' || field_display_status === 'Write') {
parts.push(__('{0} from {1} to {2}', [
__(df.label),
- (frappe.ellipsis(p[1], 40) || '""').bold(),
- (frappe.ellipsis(p[2], 40) || '""').bold()
+ (frappe.ellipsis(frappe.utils.html2text(p[1]), 40) || '""').bold(),
+ (frappe.ellipsis(frappe.utils.html2text(p[2]), 40) || '""').bold()
]));
}
}
@@ -539,7 +562,7 @@ frappe.ui.form.Timeline = class Timeline {
return parts.length < 3;
});
if(parts.length) {
- out.push(me.get_version_comment(version, __("changed value of {0}", [parts.join(', ')])));
+ out.push(me.get_version_comment(version, __("changed value of {0}", [parts.join(', ').bold()])));
}
}
@@ -616,42 +639,22 @@ frappe.ui.form.Timeline = class Timeline {
};
}
- insert_comment(comment_type, comment, btn) {
+ insert_comment(comment, btn) {
var me = this;
return frappe.call({
method: "frappe.desk.form.utils.add_comment",
args: {
- doc:{
- doctype: "Communication",
- communication_type: "Comment",
- comment_type: comment_type || "Comment",
- reference_doctype: this.frm.doctype,
- reference_name: this.frm.docname,
- content: comment,
- sender: frappe.session.user
- }
+ reference_doctype: this.frm.doctype,
+ reference_name: this.frm.docname,
+ content: comment,
+ comment_email: frappe.session.user
},
btn: btn,
callback: function(r) {
if(!r.exc) {
me.comment_area.set_value('');
frappe.utils.play_sound("click");
-
- var comment = r.message;
- var comments = me.get_communications();
- var comment_exists = false;
- for (var i=0, l=comments.length; i
11.69) {
+ $message.text(__('This may get printed on multiple pages'));
+ } else {
+ $message.text('');
+ }
});
},
show_footer: function() {
// footer is hidden by default as reqd by pdf generation
// simple hack to show it in print preview
+ this.wrapper.find('.print-format').css({
+ display: 'flex',
+ flexDirection: 'column'
+ });
this.wrapper.find('.page-break').css({
'display': 'flex',
- 'flex-direction': 'column'
+ 'flex-direction': 'column',
+ 'flex': '1'
});
this.wrapper.find('#footer-html').attr('style', `
display: block !important;
order: 1;
- margin-top: 20px;
+ margin-top: auto;
`);
},
printit: function () {
diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js
index a0cd22163e..257b6cd3ea 100644
--- a/frappe/public/js/frappe/form/quick_entry.js
+++ b/frappe/public/js/frappe/form/quick_entry.js
@@ -116,9 +116,6 @@ frappe.ui.form.QuickEntryForm = Class.extend({
this.dialog.onhide = () => frappe.quick_entry = null;
this.dialog.show();
- this.dialog.$wrapper.on('shown.bs.modal', function() {
- $(document).trigger('quick-entry-dialog-open');
- });
this.dialog.refresh_dependency();
this.set_defaults();
diff --git a/frappe/public/js/frappe/form/templates/print_layout.html b/frappe/public/js/frappe/form/templates/print_layout.html
index bbdf415b17..b2abd11099 100644
--- a/frappe/public/js/frappe/form/templates/print_layout.html
+++ b/frappe/public/js/frappe/form/templates/print_layout.html
@@ -31,6 +31,7 @@
+
diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js
index 99d913f1cd..44898c3b5e 100644
--- a/frappe/public/js/frappe/form/toolbar.js
+++ b/frappe/public/js/frappe/form/toolbar.js
@@ -153,13 +153,6 @@ frappe.ui.form.Toolbar = Class.extend({
this.page.add_menu_item(__("Reload"), function() {
me.frm.reload_doc();}, true);
- // add to desktop
- if(me.frm.meta.issingle) {
- this.page.add_menu_item(__('Add to Desktop'), function () {
- frappe.add_to_desktop(me.frm.doctype, me.frm.doctype);
- }, true);
- }
-
// delete
if((cint(me.frm.doc.docstatus) != 1) && !me.frm.doc.__islocal
&& frappe.model.can_delete(me.frm.doctype)) {
diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js
index 04286dff20..d92ca100ce 100644
--- a/frappe/public/js/frappe/list/base_list.js
+++ b/frappe/public/js/frappe/list/base_list.js
@@ -104,7 +104,9 @@ frappe.views.BaseList = class BaseList {
}
return f;
});
- //de-dup
+ // remove null or undefined values
+ this.fields = this.fields.filter(Boolean);
+ //de-duplicate
this.fields = this.fields.uniqBy(f => f[0] + f[1]);
}
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 6052baa6c4..89133df515 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -151,6 +151,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
);
fields.forEach(f => this._add_field(f));
+
+ this.fields.forEach(f => {
+ const df = frappe.meta.get_docfield(f[1], f[0]);
+ if (df && df.fieldtype === 'Currency' && df.options && !df.options.includes(':')) {
+ this._add_field(df.options);
+ }
+ });
}
patch_refresh_and_load_lib() {
@@ -611,7 +618,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
args: {
doctype: this.doctype,
filters: this.get_filters_for_args(),
- fields: [`count(distinct ${frappe.model.get_full_column_name('name', this.doctype)}) as total_count`],
+ fields: [`count(${frappe.model.get_full_column_name('name', this.doctype)}) as total_count`],
}
}).then(r => {
this.total_count = r.message.values[0][0] || current_count;
@@ -870,7 +877,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
return return_value;
});
-
+ this.toggle_result_area();
this.render();
});
});
@@ -1008,13 +1015,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
standard: true
});
- // add to desktop
- items.push({
- label: __('Add to Desktop'),
- action: () => frappe.add_to_desktop(doctype, doctype),
- standard: true
- });
-
if (frappe.user.has_role('System Manager') && frappe.boot.developer_mode === 1) {
// edit doctype
items.push({
diff --git a/frappe/public/js/frappe/misc/user.js b/frappe/public/js/frappe/misc/user.js
index 4b6b52bea9..15e91a82c5 100644
--- a/frappe/public/js/frappe/misc/user.js
+++ b/frappe/public/js/frappe/misc/user.js
@@ -74,7 +74,7 @@ $.extend(frappe.user, {
},
get_desktop_items: function() {
// hide based on permission
- var modules_list = $.map(frappe.boot.desktop_icons, function(icon) {
+ var modules_list = $.map(frappe.boot.allowed_modules, function(icon) {
var m = icon.module_name;
var type = frappe.modules[m] && frappe.modules[m].type;
@@ -105,14 +105,6 @@ $.extend(frappe.user, {
return modules_list;
},
- is_module: function(m) {
- var icons = frappe.get_desktop_icons();
- for(var i=0; i").text(txt || "").html();
},
+
+ html2text: function(html) {
+ let d = document.createElement('div');
+ d.innerHTML = html;
+ return d.textContent;
+ },
+
is_url: function(txt) {
return txt.toLowerCase().substr(0,7)=='http://'
|| txt.toLowerCase().substr(0,8)=='https://'
@@ -665,13 +672,15 @@ Object.assign(frappe.utils, {
return `${route[0]} ${route[1]}`;
}
},
- report_total_accumulator: function(column, values, type) {
- if (column.fieldtype == "Percent" || type === "mean") {
- return values.reduce((a, b) => ({content: a.content + flt(b.content)})).content / values.length;
- } else if (frappe.model.is_numeric_field(column.fieldtype)) {
- return values.reduce((a, b) => ({content: a.content + flt(b.content)})).content;
+ report_column_total: function(values, column, type) {
+ if (column.column.fieldtype == "Percent" || type === "mean") {
+ return values.reduce((a, b) => a + flt(b)) / values.length;
+ } else if (column.column.fieldtype == "Int") {
+ return values.reduce((a, b) => a + cint(b));
+ } else if (frappe.model.is_numeric_field(column.column.fieldtype)) {
+ return values.reduce((a, b) => a + flt(b));
} else {
- return false;
+ return null;
}
}
});
diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js
index ed9b6fbaa2..1b651483a7 100644
--- a/frappe/public/js/frappe/model/create_new.js
+++ b/frappe/public/js/frappe/model/create_new.js
@@ -87,10 +87,8 @@ $.extend(frappe.model, {
var doctype = doc.doctype;
var docfields = frappe.meta.docfield_list[doctype] || [];
var updated = [];
-
for(var fid=0;fid {
- return perm.doc === docname && (perm.applicable_for === doc.doctype || !perm.applicable_for);
- });
- }
+ && allowed_records.length);
// don't set defaults for "User" link field using User Permissions!
if (df.fieldtype==="Link" && df.options!=="User") {
// 1 - look in user permissions for document_type=="Setup".
// We don't want to include permissions of transactions to be used for defaults.
if (df.linked_document_type==="Setup"
- && has_user_permissions && user_permissions[df.options].length===1) {
- return user_permissions[df.options][0].doc;
+ && has_user_permissions && allowed_records.length===1) {
+ return allowed_records[0];
}
if(!df.ignore_user_permissions) {
@@ -165,7 +161,7 @@ $.extend(frappe.model, {
}
var is_allowed_user_default = user_default &&
- (!has_user_permissions || is_doc_allowed(df.options, user_default));
+ (!has_user_permissions || allowed_records.includes(user_default));
// is this user default also allowed as per user permissions?
if (is_allowed_user_default) {
@@ -190,7 +186,7 @@ $.extend(frappe.model, {
} else if (df["default"][0]===":") {
var boot_doc = frappe.model.get_default_from_boot_docs(df, doc, parent_doc);
- var is_allowed_boot_doc = !has_user_permissions || is_doc_allowed(df.options, boot_doc);
+ var is_allowed_boot_doc = !has_user_permissions || allowed_records.includes(boot_doc);
if (is_allowed_boot_doc) {
return boot_doc;
@@ -201,7 +197,7 @@ $.extend(frappe.model, {
}
// is this default value is also allowed as per user permissions?
- var is_allowed_default = !has_user_permissions || is_doc_allowed(df.options, df.default);
+ var is_allowed_default = !has_user_permissions || allowed_records.includes(df.default);
if (df.fieldtype!=="Link" || df.options==="User" || is_allowed_default) {
return df["default"];
}
@@ -342,5 +338,3 @@ frappe.new_doc = function (doctype, opts, init_callback) {
});
}
-
-
diff --git a/frappe/public/js/frappe/model/perm.js b/frappe/public/js/frappe/model/perm.js
index 60ae89a3da..2396962b94 100644
--- a/frappe/public/js/frappe/model/perm.js
+++ b/frappe/public/js/frappe/model/perm.js
@@ -151,18 +151,10 @@ $.extend(frappe.perm, {
let rules = {};
let fields_to_check = frappe.meta.get_fields_to_check_permissions(doctype);
$.each(fields_to_check, (i, df) => {
- const user_permissions_for_doctype = user_permissions[df.options];
- // check if there are any user permission applicable for parent doctype
- const has_user_permission = user_permissions_for_doctype ? user_permissions_for_doctype
- .some(perm => !perm.applicable_for || perm.applicable_for === doctype) : false;
-
- if (has_user_permission) {
- rules[df.label] = [];
- user_permissions_for_doctype.map(permission => {
- if (!permission.applicable_for || permission.applicable_for === doctype) {
- rules[df.label].push(permission.doc);
- }
- });
+ const user_permissions_for_doctype = user_permissions[df.options] || [];
+ const allowed_records = frappe.perm.get_allowed_docs_for_doctype(user_permissions_for_doctype, doctype);
+ if (allowed_records.length) {
+ rules[df.label] = allowed_records;
}
});
if (!$.isEmptyObject(rules)) {
@@ -260,4 +252,10 @@ $.extend(frappe.perm, {
return status === "None" ? false : true;
},
+
+ get_allowed_docs_for_doctype: (user_permissions, doctype) => {
+ return (user_permissions || []).filter(perm => {
+ return (perm.applicable_for === doctype || !perm.applicable_for);
+ }).map(perm => perm.doc);
+ }
});
diff --git a/frappe/public/js/frappe/recorder/RecorderDetail.vue b/frappe/public/js/frappe/recorder/RecorderDetail.vue
new file mode 100644
index 0000000000..1836292a4f
--- /dev/null
+++ b/frappe/public/js/frappe/recorder/RecorderDetail.vue
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ request[columns[0].slug] }}
+
+
+
+ {{ request[column.slug] }}
+
+
+
+
+
+
+
+
+
+
Recorder is Inactive
+
+
+
+
No Requests found
+
Go make some noise
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frappe/public/js/frappe/recorder/RecorderRoot.vue b/frappe/public/js/frappe/recorder/RecorderRoot.vue
new file mode 100644
index 0000000000..142fdc35e0
--- /dev/null
+++ b/frappe/public/js/frappe/recorder/RecorderRoot.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/frappe/public/js/frappe/recorder/RequestDetail.vue b/frappe/public/js/frappe/recorder/RequestDetail.vue
new file mode 100644
index 0000000000..82997cbce2
--- /dev/null
+++ b/frappe/public/js/frappe/recorder/RequestDetail.vue
@@ -0,0 +1,284 @@
+
+
+
+
+
diff --git a/frappe/public/js/frappe/recorder/recorder.js b/frappe/public/js/frappe/recorder/recorder.js
new file mode 100644
index 0000000000..c5c6734aa6
--- /dev/null
+++ b/frappe/public/js/frappe/recorder/recorder.js
@@ -0,0 +1,39 @@
+import Vue from 'vue/dist/vue.js';
+import VueRouter from 'vue-router/dist/vue-router.js';
+
+import RecorderRoot from "./RecorderRoot.vue";
+
+import RecorderDetail from "./RecorderDetail.vue";
+import RequestDetail from "./RequestDetail.vue";
+
+Vue.use(VueRouter);
+const routes = [
+ {
+ name: "recorder-detail",
+ path: '/desk',
+ component: RecorderDetail,
+ },
+ {
+ name: "request-detail",
+ path: '/request/:id',
+ component: RequestDetail,
+ },
+];
+
+const router = new VueRouter({
+ mode: 'history',
+ base: "/desk#recorder/",
+ routes: routes,
+});
+
+new Vue({
+ el: ".recorder-container",
+ router: router,
+ data: {
+ page: cur_page.page.page
+ },
+ template: "",
+ components: {
+ RecorderRoot,
+ }
+});
diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js
index 031ab6f0c1..709b6881a2 100644
--- a/frappe/public/js/frappe/request.js
+++ b/frappe/public/js/frappe/request.js
@@ -75,6 +75,12 @@ frappe.call = function(opts) {
}
}
+ let url = opts.url;
+ if (!url) {
+ url = '/api/method/' + args.cmd;
+ delete args.cmd;
+ }
+
return frappe.request.call({
type: opts.type || "POST",
args: args,
@@ -87,7 +93,7 @@ frappe.call = function(opts) {
headers: opts.headers || {},
// show_spinner: !opts.no_spinner,
async: opts.async,
- url: opts.url || frappe.request.url,
+ url,
});
}
@@ -376,9 +382,12 @@ frappe.request.report_error = function(xhr, request_opts) {
var data = JSON.parse(xhr.responseText);
if (data.exc) {
var exc = (JSON.parse(data.exc) || []).join("\n");
+ var locals = (JSON.parse(data.locals) || []).join("\n");
delete data.exc;
+ delete data.locals;
} else {
var exc = "";
+ locals = "";
}
if (exc) {
@@ -410,6 +419,9 @@ frappe.request.report_error = function(xhr, request_opts) {
'Error Report
',
'' + exc + '
',
'
',
+ 'Locals
',
+ '' + locals + '
',
+ '
',
'Request Data
',
'' + JSON.stringify(request_opts, null, "\t") + '
',
'
',
diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js
index fb15f6b561..4a4c42852a 100644
--- a/frappe/public/js/frappe/router.js
+++ b/frappe/public/js/frappe/router.js
@@ -84,8 +84,15 @@ frappe.get_route = function(route) {
// for app
route = frappe.get_raw_route_str(route).split('/');
route = $.map(route, frappe._decode_str);
- var parts = route[route.length - 1].split("?");
- route[route.length - 1] = parts[0];
+ var parts = null;
+ var doc_name = route[route.length - 1];
+ // if the last part contains ? then check if it is valid query string
+ if(doc_name.indexOf("?") < doc_name.indexOf("=")){
+ parts = doc_name.split("?");
+ route[route.length - 1] = parts[0];
+ } else {
+ parts = doc_name;
+ }
if (parts.length > 1) {
var query_params = frappe.utils.get_query_params(parts[1]);
frappe.route_options = $.extend(frappe.route_options || {}, query_params);
diff --git a/frappe/public/js/frappe/toolbar.js b/frappe/public/js/frappe/toolbar.js
index e698563e9c..ce208d566a 100755
--- a/frappe/public/js/frappe/toolbar.js
+++ b/frappe/public/js/frappe/toolbar.js
@@ -21,7 +21,7 @@ $(document).on("toolbar_setup", function() {
if(limits.space || limits.users || limits.expiry || limits.emails) {
help_links = [];
- help_links.push('' + frappe._('Usage Info') + '');
+ help_links.push('' + frappe._('Usage Info') + '');
help_links.push('');
$(help_links.join("\n")).insertBefore($("#toolbar-user").find("li:first"));
}
diff --git a/frappe/public/js/frappe/ui/app_icon.js b/frappe/public/js/frappe/ui/app_icon.js
index 5ca6adb556..a2efb08aa4 100644
--- a/frappe/public/js/frappe/ui/app_icon.js
+++ b/frappe/public/js/frappe/ui/app_icon.js
@@ -35,7 +35,6 @@ frappe.ui.app_icon = {
}
});
icon = '';
- return icon;
} else {
icon = '';
}
diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js
index 81b0c77a50..0a08c5f0ec 100644
--- a/frappe/public/js/frappe/ui/dialog.js
+++ b/frappe/public/js/frappe/ui/dialog.js
@@ -85,6 +85,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
frappe.ui.open_dialogs.push(me);
me.focus_on_first_input();
me.on_page_show && me.on_page_show();
+ $(document).trigger('frappe.ui.Dialog:shown');
})
.on('scroll', function() {
var $input = $('input:focus');
@@ -152,6 +153,10 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
this.$wrapper.removeClass('fade');
}
this.$wrapper.modal("show");
+
+ // clear any message
+ this.clear_message();
+
this.primary_action_fulfilled = false;
this.is_visible = true;
return this;
diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js
index e43d4d61b9..54e35a6581 100644
--- a/frappe/public/js/frappe/ui/filters/filter.js
+++ b/frappe/public/js/frappe/ui/filters/filter.js
@@ -117,10 +117,14 @@ frappe.ui.Filter = class {
}
update_filter_tag() {
- return this._filter_value_set.then(() => {
- !this.$filter_tag ? this.make_tag() : this.set_filter_button_text();
- this.filter_edit_area.hide();
- });
+ if (this._filter_value_set) {
+ return this._filter_value_set.then(() => {
+ !this.$filter_tag ? this.make_tag() : this.set_filter_button_text();
+ this.filter_edit_area.hide();
+ });
+ } else {
+ return Promise.resolve();
+ }
}
remove() {
diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js
index dee44f4340..6e60fc2665 100644
--- a/frappe/public/js/frappe/ui/messages.js
+++ b/frappe/public/js/frappe/ui/messages.js
@@ -100,12 +100,7 @@ frappe.msgprint = function(msg, title) {
if(data.message instanceof Array) {
data.message.forEach(function(m) {
- const msg = {
- message: m,
- indicator: data.indicator,
- title: data.title
- }
- frappe.msgprint(msg);
+ frappe.msgprint(m);
});
return;
}
diff --git a/frappe/public/js/frappe/ui/toolbar/modules_select.js b/frappe/public/js/frappe/ui/toolbar/modules_select.js
deleted file mode 100644
index 1b41141c9b..0000000000
--- a/frappe/public/js/frappe/ui/toolbar/modules_select.js
+++ /dev/null
@@ -1,108 +0,0 @@
-frappe.provide('frappe.ui.toolbar');
-
-frappe.ui.toolbar.ModulesSelect = class {
- constructor() {
- this.user = frappe.boot.user.name;
- this.setup();
- }
-
- setup() {
- this.dialog = new frappe.ui.Dialog({
- title: __('Set Desktop Icons'),
- fields: [
- {
- label: __('Setup for'),
- fieldname: 'setup_for',
- fieldtype: 'Select',
- options: [
- {label: __('User'), value: 'user'},
- {label: __('Everyone'), value: 'everyone'}
- ],
- default: 'user',
- onchange: () => {
- let field = this.$setup_for;
- if(field.get_value() === 'everyone') {
- this.$user.$wrapper.hide();
- this.user = undefined;
- field.set_description(__('Limit icon choices for all users.'));
- } else {
- this.$user.$wrapper.show();
- this.user = this.$user.get_value();
- field.set_description('');
- }
- this.$icons_list.refresh();
- }
- },
- { fieldtype: 'Column Break' },
- {
- label: __('User'),
- fieldname: 'user',
- fieldtype: 'Link',
- options: 'User',
- default: frappe.boot.user.name,
- onchange: () => {
- this.user = this.get_value() || frappe.boot.user.name;
- this.$icons_list.refresh();
- }
- },
- { fieldtype: 'Section Break' },
- {
- // label: __('Icons'),
- fieldname: 'icons',
- fieldtype: 'MultiCheck',
- select_all: 1,
- columns: 2,
- get_data: () => {
- return new Promise((resolve) => {
- frappe.call({
- method: 'frappe.desk.doctype.desktop_icon.desktop_icon.get_module_icons',
- args: {user: this.user},
- freeze: true,
- callback: (r) => {
- const icons = r.message.icons;
- const user = r.message.user;
- resolve(icons
- .map(icon => {
- const uncheck = user ? icon.hidden : icon.blocked;
- return { label: icon.label, value: icon.module_name, checked:!uncheck };
- }).sort(function(a, b){
- if(a.label < b.label) return -1;
- if(a.label > b.label) return 1;
- return 0;
- })
- );
- }
- });
- });
- }
- }
- ]
- });
-
- this.dialog.set_primary_action(__('Save'), () => {
- frappe.call({
- method: 'frappe.desk.doctype.desktop_icon.desktop_icon.update_icons',
- args: {
- hidden_list: this.$icons_list.get_unchecked_options(),
- user: this.user
- },
- freeze: true,
- callback: () => {
- window.location.href = '';
- }
- });
- });
-
- this.$icons_list = this.dialog.fields_dict.icons;
- this.$setup_for = this.dialog.fields_dict.setup_for;
- this.$user = this.dialog.fields_dict.user;
- }
-
- show(user) {
- if(user) {
- this.user = user || frappe.boot.user.name;
- this.$icons_list.refresh();
- }
- this.dialog.show();
- }
-};
\ No newline at end of file
diff --git a/frappe/public/js/frappe/ui/toolbar/navbar.html b/frappe/public/js/frappe/ui/toolbar/navbar.html
index 12e4c5beaa..ea6691a7a3 100644
--- a/frappe/public/js/frappe/ui/toolbar/navbar.html
+++ b/frappe/public/js/frappe/ui/toolbar/navbar.html
@@ -4,7 +4,9 @@
- Home
+
+
+
@@ -22,8 +24,6 @@
{%= frappe.user.full_name() %}