diff --git a/frappe/__init__.py b/frappe/__init__.py index 0d5f4d8c4f..fe3a90698e 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -17,7 +17,7 @@ from faker import Faker from .exceptions import * from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader) -__version__ = '10.1.69' +__version__ = '10.1.70' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index a3864a9d0c..2c18064580 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -49,6 +49,10 @@ def clear_defaults_cache(user=None): elif frappe.flags.in_install!="frappe": frappe.cache().delete_key("defaults") +def clear_document_cache(): + frappe.local.document_cache = {} + frappe.cache().delete_key("document_cache") + def clear_doctype_cache(doctype=None): cache = frappe.cache() @@ -81,3 +85,6 @@ def clear_doctype_cache(doctype=None): for name in groups: cache.delete_value(name) + # Clear all document's cache. To clear documents of a specific DocType document_cache should be restructured + clear_document_cache() + diff --git a/frappe/core/doctype/activity_log/feed.py b/frappe/core/doctype/activity_log/feed.py index 0004a2d219..ceed5cb9d9 100644 --- a/frappe/core/doctype/activity_log/feed.py +++ b/frappe/core/doctype/activity_log/feed.py @@ -75,8 +75,8 @@ def get_feed_match_conditions(user=None, force=True): if user_permissions: can_read_docs = [] for doctype, obj in user_permissions.items(): - for n in obj.get("docs", []): - can_read_docs.append('"{}|{}"'.format(doctype, frappe.db.escape(n))) + for n in obj: + 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( diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index 0be62ffd01..43dff47745 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -56,10 +56,10 @@ def get_user_permissions(user=None): if not out.get(perm.allow): out[perm.allow] = [] - out[perm.allow].append({ + out[perm.allow].append(frappe._dict({ 'doc': doc_name, 'applicable_for': perm.get('applicable_for') - }) + })) try: for perm in frappe.get_all('User Permission', @@ -74,6 +74,7 @@ def get_user_permissions(user=None): for doc in decendants: add_doc_to_perm(perm, doc) + out = frappe._dict(out) frappe.cache().hset("user_permissions", user, out) except frappe.SQLError as e: @@ -95,7 +96,7 @@ def get_applicable_for_doctype_list(doctype, txt, searchfield, start, page_len, linked_doctypes = get_linked_doctypes(doctype, True).keys() linked_doctypes = list(linked_doctypes) linked_doctypes += [doctype] - + if txt: linked_doctypes = [d for d in linked_doctypes if txt in d.lower()] @@ -110,3 +111,13 @@ def get_applicable_for_doctype_list(doctype, txt, searchfield, start, page_len, def get_permitted_documents(doctype): return [d.get('doc') for d in get_user_permissions().get(doctype, []) \ if d.get('doc')] + +@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 diff --git a/frappe/core/doctype/user_permission/user_permission_list.js b/frappe/core/doctype/user_permission/user_permission_list.js new file mode 100644 index 0000000000..39a4648334 --- /dev/null +++ b/frappe/core/doctype/user_permission/user_permission_list.js @@ -0,0 +1,52 @@ +frappe.listview_settings['User Permission'] = { + onload: function(list_view) { + list_view.page.add_menu_item(__("Clear User Permissions"), () => { + const dialog = new frappe.ui.Dialog({ + title: __('Clear User Permissions'), + fields: [ + { + 'fieldname': 'user', + 'label': __('For User'), + 'fieldtype': 'Link', + 'options': 'User', + 'reqd': 1 + }, + { + 'fieldname': 'for_doctype', + 'label': __('For Document Type'), + 'fieldtype': 'Link', + 'options': 'DocType', + 'reqd': 1 + }, + ], + primary_action: (data) => { + // mandatory not filled + if (!data) return; + + frappe.confirm(__('Are you sure?'), () => { + frappe + .xcall('frappe.core.doctype.user_permission.user_permission.clear_user_permissions', data) + .then(data => { + dialog.hide(); + let message = ''; + if (data === 0) { + message = __('No records deleted'); + } else { + message = __('{0} records deleted', [data]); + } + frappe.show_alert({ + message, + indicator: 'green' + }); + list_view.refresh(); + }); + }); + + }, + primary_action_label: __('Clear') + }); + + dialog.show(); + }); + } +}; diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index 0c64c487fb..e705470744 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -93,7 +93,6 @@ def get_permission_query_conditions(user): } def has_permission(doc, user): - frappe.log_error(doc.owner) if doc.event_type=="Public" or doc.owner==user: return True diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 451e9da55c..e7d24454bb 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -58,7 +58,7 @@ def generate_report_result(report, filters=None, user=None): module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module") if report.is_standard == "Yes": method_name = get_report_module_dotted_path(module, report.name) + ".execute" - threshold = 10 + threshold = 60 res = [] start_time = datetime.datetime.now() diff --git a/frappe/email/doctype/email_queue/email_queue.json b/frappe/email/doctype/email_queue/email_queue.json index 15e87c65c9..fd47bb3c39 100644 --- a/frappe/email/doctype/email_queue/email_queue.json +++ b/frappe/email/doctype/email_queue/email_queue.json @@ -559,24 +559,55 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "retry", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Retry", + "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, + "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-envelope", - "idx": 1, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-09 15:34:07.229657", - "modified_by": "Administrator", - "module": "Email", - "name": "Email Queue", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-envelope", + "idx": 1, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-01-11 09:05:04.175368", + "modified_by": "Administrator", + "module": "Email", + "name": "Email Queue", + "owner": "Administrator", "permissions": [ { "amend": 0, diff --git a/frappe/email/queue.py b/frappe/email/queue.py index c06a7fb79b..0ed4044586 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -382,7 +382,7 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals email = frappe.db.sql('''select name, status, communication, message, sender, reference_doctype, reference_name, unsubscribe_param, unsubscribe_method, expose_recipients, - show_as_cc, add_unsubscribe_link, attachments + show_as_cc, add_unsubscribe_link, attachments, retry from `tabEmail Queue` where @@ -464,12 +464,16 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals except Exception as e: frappe.db.rollback() - if any("Sent" == s.status for s in recipients_list): - frappe.db.sql("""update `tabEmail Queue` set status='Partially Errored', error=%s where name=%s""", - (text_type(e), email.name), auto_commit=auto_commit) + if email.retry < 3: + frappe.db.sql("""update `tabEmail Queue` set status='Not Sent', modified=%s, retry=retry+1 where name=%s""", + (now_datetime(), email.name), auto_commit=auto_commit) else: - frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s -where name=%s""", (text_type(e), email.name), auto_commit=auto_commit) + if any("Sent" == s.status for s in recipients_list): + frappe.db.sql("""update `tabEmail Queue` set status='Partially Errored', error=%s where name=%s""", + (text_type(e), email.name), auto_commit=auto_commit) + else: + frappe.db.sql("""update `tabEmail Queue` set status='Error', error=%s + where name=%s""", (text_type(e), email.name), auto_commit=auto_commit) if email.communication: frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit) diff --git a/frappe/hooks.py b/frappe/hooks.py index 2cb67836d3..5d722b299a 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -12,7 +12,7 @@ source_link = "https://github.com/frappe/frappe" app_license = "MIT" develop_version = '12.x.x-develop' -staging_version = '11.0.3-beta.49' +staging_version = '11.0.3-beta.50' app_email = "info@frappe.io" diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py index 0d77044eeb..c6bcc42f06 100644 --- a/frappe/model/create_new.py +++ b/frappe/model/create_new.py @@ -133,13 +133,14 @@ def user_permissions_exist(df, user_permissions): def get_default_based_on_another_field(df, user_permissions, parent_doc): # default value based on another document + from frappe.permissions import get_allowed_docs_for_doctype + ref_doctype = df.default[1:] 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 - (default_value in user_permissions.get(df.options).get('docs', []))) + (default_value in get_allowed_docs_for_doctype(user_permissions[df.options], df.parent))) # is this allowed as per user permissions if is_allowed_default_value: diff --git a/frappe/public/js/frappe/db.js b/frappe/public/js/frappe/db.js index 5571dc2d49..d487c3fe56 100644 --- a/frappe/public/js/frappe/db.js +++ b/frappe/public/js/frappe/db.js @@ -30,13 +30,14 @@ frappe.db = { }); }); }, - get_value: function(doctype, filters, fieldname, callback) { + get_value: function(doctype, filters, fieldname, callback, parent) { return frappe.call({ method: "frappe.client.get_value", args: { doctype: doctype, fieldname: fieldname, - filters: filters + filters: filters, + parent: parent }, callback: function(r) { callback && callback(r.message); diff --git a/frappe/public/js/frappe/event_emitter.js b/frappe/public/js/frappe/event_emitter.js index aeed656e4e..d0938b9b4a 100644 --- a/frappe/public/js/frappe/event_emitter.js +++ b/frappe/public/js/frappe/event_emitter.js @@ -4,7 +4,7 @@ frappe.provide('frappe.utils'); */ const EventEmitterMixin = { init() { - this.jq = jQuery(this); + this.jq = jQuery({}); }, trigger(evt, data) { diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 2e83e3b612..6dc8c3d387 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -45,7 +45,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ if (val) { this.set_description(__('{0} already exists. Select another name', [val.name])); } - }); + }, + this.doc.parenttype + ); this.last_check = null; }, 1000); this.last_check = timeout; diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index 5203c57e7f..f5dfeb3893 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -181,6 +181,19 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ }, get_input_value() { - return this.quill ? this.quill.root.innerHTML : ''; + let value = this.quill ? this.quill.root.innerHTML : ''; + // quill keeps ol as a common container for both type of lists + // and uses css for appearances, this is not semantic + // so we convert ol to ul if it is unordered + const $value = $(`
" + (this.message || ""); } + if(this.message && signature && this.message.includes(signature)) { + signature = ""; + } + let reply = (this.message || "") + (signature ? ("
" + signature) : ""); let content = ''; diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index eeb5a13caa..f41c8e565f 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -616,11 +616,24 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { {for_print: false, always_show_decimals: true}, data); }; + let compareFn = null; + if (column.fieldtype === 'Date') { + compareFn = (cell, keyword) => { + if (!cell.content) return null; + if (keyword.length !== 'YYYY-MM-DD'.length) return null; + + const keywordValue = frappe.datetime.user_to_obj(keyword); + const cellValue = frappe.datetime.str_to_obj(cell.content); + return [+cellValue, +keywordValue]; + }; + } + return Object.assign(column, { id: column.fieldname, name: column.label, width: parseInt(column.width) || null, editable: false, + compareValue: compareFn, format: (value, row, column, data) => { if (this.report_settings.formatter) { return this.report_settings.formatter(value, row, column, data, format_cell); diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index eeb0385a57..fc76312bba 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -823,6 +823,19 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { // child table column const id = doctype !== this.doctype ? `${doctype}:${fieldname}` : fieldname; + let compareFn = null; + if (docfield.fieldtype === 'Date') { + compareFn = (cell, keyword) => { + if (!cell.content) return null; + if (keyword.length !== 'YYYY-MM-DD'.length) return null; + + const keywordValue = frappe.datetime.user_to_obj(keyword); + const cellValue = frappe.datetime.str_to_obj(cell.content); + return [+cellValue, +keywordValue]; + } + } + + return { id: id, field: fieldname, @@ -832,6 +845,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { width, editable, align, + compareValue: compareFn, format: (value, row, column, data) => { const d = row.reduce((acc, curr) => { if (!curr.column.docfield) return acc; diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py index 9924e92eda..13106697c5 100644 --- a/frappe/tests/test_permissions.py +++ b/frappe/tests/test_permissions.py @@ -14,6 +14,7 @@ from frappe.permissions import (add_user_permission, remove_user_permission, get_valid_perms) from frappe.core.page.permission_manager.permission_manager import update, reset from frappe.test_runner import make_test_records_for_doctype +from frappe.core.doctype.user_permission.user_permission import clear_user_permissions test_dependencies = ['Blogger', 'Blog Post', "User", "Contact", "Salutation"] @@ -427,4 +428,35 @@ class TestPermissions(unittest.TestCase): self.assertTrue(doc.has_permission("delete")) # delete the created doc - frappe.delete_doc('Blog Post', '-test-blog-post-title') \ No newline at end of file + frappe.delete_doc('Blog Post', '-test-blog-post-title') + + def test_clear_user_permissions(self): + current_user = frappe.session.user + frappe.set_user('Administrator') + clear_user_permissions_for_doctype('Blog Category', 'test2@example.com') + clear_user_permissions_for_doctype('Blog Post', 'test2@example.com') + + add_user_permission('Blog Post', '-test-blog-post-1', 'test2@example.com') + add_user_permission('Blog Post', '-test-blog-post-2', 'test2@example.com') + add_user_permission("Blog Category", '_Test Blog Category 1', 'test2@example.com') + + deleted_user_permission_count = clear_user_permissions('test2@example.com', 'Blog Post') + + self.assertEqual(deleted_user_permission_count, 2) + + blog_post_user_permission_count = frappe.db.count('User Permission', filters={ + 'user': 'test2@example.com', + 'allow': 'Blog Post' + }) + + self.assertEqual(blog_post_user_permission_count, 0) + + blog_category_user_permission_count = frappe.db.count('User Permission', filters={ + 'user': 'test2@example.com', + 'allow': 'Blog Category' + }) + + self.assertEqual(blog_category_user_permission_count, 1) + + # reset the user + frappe.set_user(current_user) diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index ca6ff8c2d3..26f90e16f2 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -106,7 +106,7 @@ def print_by_server(doctype, name, print_format=None, doc=None, no_letterhead=0) print_settings = frappe.get_doc("Print Settings") try: import cups - except ModuleNotFoundError: + except ImportError: frappe.throw("You need to install pycups to use this feature!") return try: diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index d25c56b6b1..67b29c0c06 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -68,6 +68,7 @@ class WebsiteGenerator(Document): return title_field def clear_cache(self): + super(WebsiteGenerator, self).clear_cache() clear_cache(self.route) def scrub(self, text): diff --git a/package.json b/package.json index 5b9cb1e35e..1914cb0180 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "awesomplete": "^1.1.2", "cookie": "^0.3.1", "express": "^4.16.2", - "frappe-datatable": "^1.7.3", + "frappe-datatable": "^1.8.0", "frappe-gantt": "^0.1.0", "fuse.js": "^3.2.0", "highlight.js": "^9.12.0", diff --git a/requirements.txt b/requirements.txt index c3671e8e62..4c6510df51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,6 +35,7 @@ pyasn1 zxcvbn-python unittest-xml-reporting oauthlib==2.1.0 +requests-oauthlib==1.1.0 pdfkit PyJWT PyPDF2 diff --git a/yarn.lock b/yarn.lock index 800b8d9e1f..8ebc20911b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1219,10 +1219,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frappe-datatable@^1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.7.3.tgz#dadddf01867723bf0862918dd62cfea4652416a3" - integrity sha512-72LUx0ZRRjFPLFQUzgB7Uywpxgk1rFLjyzOq5yQ5Mr2G8u0t3AoUJLRG2lAqFD49JOxezVb6Oa03Qmon1DCExA== +frappe-datatable@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.8.0.tgz#7f789ed77bdf9800143fffb1bb28a24d5dbdc27c" + integrity sha512-j3DdmYtTjhcVXCVkYjKHdZOc8tSwZapanlujdx1xzXcL7Ueo+BFiPR5WptWRfH43K3nboh3m7clcAIX7LdQR4g== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5"