diff --git a/.eslintrc b/.eslintrc index eadbd5eb526..7719dfaec06 100644 --- a/.eslintrc +++ b/.eslintrc @@ -51,7 +51,11 @@ ], "no-control-regex": [ "off" - ] + ], + "space-before-blocks": "warn", + "keyword-spacing": "warn", + "comma-spacing": "warn", + "key-spacing": "warn", }, "root": true, "globals": { diff --git a/frappe/__init__.py b/frappe/__init__.py index afd60366820..91a247b9f98 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '12.0.4' +__version__ = '12.0.5' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/boot.py b/frappe/boot.py index efa9ac2afe2..50888fcdd2f 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -82,6 +82,7 @@ def get_bootinfo(): bootinfo.energy_points_enabled = is_energy_point_enabled() bootinfo.points = get_energy_points(frappe.session.user) bootinfo.frequently_visited_links = frequently_visited_links() + bootinfo.link_preview_doctypes = get_link_preview_doctypes() return bootinfo @@ -262,3 +263,6 @@ def get_gsuite_status(): def get_success_action(): return frappe.get_all("Success Action", fields=["*"]) + +def get_link_preview_doctypes(): + return [d.name for d in frappe.db.get_all('DocType', {'show_preview_popup': 1})] \ No newline at end of file diff --git a/frappe/core/doctype/data_import/data_import.py b/frappe/core/doctype/data_import/data_import.py index dc2f9194b92..5500f1c617f 100644 --- a/frappe/core/doctype/data_import/data_import.py +++ b/frappe/core/doctype/data_import/data_import.py @@ -15,7 +15,7 @@ class DataImport(Document): def autoname(self): if not self.name: - self.name = "Import on "+ format_datetime(self.creation) + self.name = "Import on " +format_datetime(self.creation) def validate(self): if not self.import_file: diff --git a/frappe/core/page/background_jobs/background_jobs.html b/frappe/core/page/background_jobs/background_jobs.html index 07b5c164516..14b50d03880 100644 --- a/frappe/core/page/background_jobs/background_jobs.html +++ b/frappe/core/page/background_jobs/background_jobs.html @@ -3,9 +3,9 @@ - - - + + + @@ -28,13 +28,13 @@
Queue / WorkerJobCreated{{ _("Queue / Worker") }}{{ _("Job") }}{{ _("Created") }}

- Started - Queued - Failed - Finished + {{ _("Started") }} + {{ _("Queued") }} + {{ _("Failed") }} + {{ _("Finished") }}

{% else %} -

No pending or current jobs for this site

+

{{ _("No pending or current jobs for this site") }}

{% endif %} -

Last refreshed {{ frappe.datetime.now_datetime() }}

- \ No newline at end of file +

{{ _("Last refreshed") }} {{ frappe.datetime.now_datetime() }}

+ diff --git a/frappe/core/page/background_jobs/background_jobs.js b/frappe/core/page/background_jobs/background_jobs.js index 2bde8454a9b..bbc8bf049b3 100644 --- a/frappe/core/page/background_jobs/background_jobs.js +++ b/frappe/core/page/background_jobs/background_jobs.js @@ -1,7 +1,7 @@ frappe.pages['background_jobs'].on_page_load = function(wrapper) { var page = frappe.ui.make_app_page({ parent: wrapper, - title: 'Background Jobs', + title: __('Background Jobs'), single_column: true }); diff --git a/frappe/desk/link_preview.py b/frappe/desk/link_preview.py index f8252f20bca..402883a3ade 100644 --- a/frappe/desk/link_preview.py +++ b/frappe/desk/link_preview.py @@ -3,26 +3,41 @@ import json @frappe.whitelist() -def get_preview_data(doctype, docname, fields): - fields = json.loads(fields) - preview_fields = [field['name'] for field in fields if field['type'] not in no_value_fields] - preview_fields.append(frappe.get_meta(doctype).get_title_field()) - if 'name' not in fields: - preview_fields.append('name') - preview_fields.append(frappe.get_meta(doctype).image_field) +def get_preview_data(doctype, docname): + preview_fields = [] + meta = frappe.get_meta(doctype) + if not meta.show_preview_popup: return + + preview_fields = [field.fieldname for field in meta.fields \ + if field.in_preview and field.fieldtype not in no_value_fields] + + # no preview fields defined, build list from mandatory fields + if not preview_fields: + preview_fields = [field.name for field in meta.fields if field.reqd] + + title_field = meta.get_title_field() + image_field = meta.image_field + + preview_fields.append(title_field) + preview_fields.append(image_field) + preview_fields.append('name') preview_data = frappe.get_list(doctype, filters={ 'name': docname }, fields=preview_fields, limit=1) - if preview_data: - preview_data = preview_data[0] - preview_data = {k: v for k, v in preview_data.items() if v is not None} - for k,v in preview_data.items(): - if frappe.get_meta(doctype).has_field(k): - preview_data[k] = frappe.format(v,frappe.get_meta(doctype).get_field(k).fieldtype) + if not preview_data: return + + preview_data = preview_data[0] + + formatted_preview_data = { + 'preview_image': preview_data.get(image_field), + 'preview_title': preview_data.get(title_field), + 'name': preview_data.get('name'), + } - if not preview_data: - return None - return preview_data + for key, val in preview_data.items(): + if val and meta.has_field(key) and key not in [image_field, title_field, 'name']: + formatted_preview_data[meta.get_field(key).label] = frappe.format(val, meta.get_field(key).fieldtype) + return formatted_preview_data \ No newline at end of file diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index 494a6e2bb3b..f99536f9a84 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -91,7 +91,7 @@ def get_cached_contacts(txt): if not txt: return contacts - match = [d for d in contacts if (d.value and (txt in d.value or txt in d.description))] + match = [d for d in contacts if (d.value and ((d.value and txt in d.value) or (d.description and txt in d.description)))] return match def update_contact_cache(contacts): diff --git a/frappe/email/receive.py b/frappe/email/receive.py index dcd21d3c10d..a95975c4b0b 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -481,7 +481,10 @@ def get_charset(self, part): """Detect chartset.""" charset = part.get_content_charset() if not charset: - charset = chardet.detect(str(part))['encoding'] + if six.PY2: + charset = chardet.detect(str(part))['encoding'] + else: + charset = chardet.detect(part.encode())['encoding'] return charset diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py index d00883f5a39..8de3ec18343 100755 --- a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py @@ -11,6 +11,7 @@ from frappe.model.document import Document from frappe.utils import cint, split_emails from frappe.utils.background_jobs import enqueue +from rq.timeouts import JobTimeoutException from botocore.exceptions import ClientError class S3BackupSettings(Document): @@ -162,8 +163,9 @@ def delete_old_backups(limit, bucket): ) bucket = s3.Bucket(bucket) objects = bucket.meta.client.list_objects_v2(Bucket=bucket.name, Delimiter='/') - for obj in objects.get('CommonPrefixes'): - all_backups.append(obj.get('Prefix')) + if objects: + for obj in objects.get('CommonPrefixes'): + all_backups.append(obj.get('Prefix')) oldest_backup = sorted(all_backups)[0] diff --git a/frappe/public/images/fallback-thumbnail.jpg b/frappe/public/images/fallback-thumbnail.jpg new file mode 100644 index 00000000000..68a62d77667 Binary files /dev/null and b/frappe/public/images/fallback-thumbnail.jpg differ diff --git a/frappe/public/js/frappe/ui/link_preview.js b/frappe/public/js/frappe/ui/link_preview.js index 39b47f65b1e..71e92ba29dc 100644 --- a/frappe/public/js/frappe/ui/link_preview.js +++ b/frappe/public/js/frappe/ui/link_preview.js @@ -13,10 +13,10 @@ frappe.ui.LinkPreview = class { this.element = $(e.currentTarget); this.is_link = this.element.get(0).tagName.toLowerCase() === 'a'; - if(!this.element.parents().find('.popover').length) { + if (!this.element.parents().find('.popover').length) { this.identify_doc(); this.popover = this.element.data("bs.popover"); - if(this.name && this.doctype) { + if (this.name && this.doctype) { this.setup_popover_control(e); } } @@ -39,18 +39,18 @@ frappe.ui.LinkPreview = class { } setup_popover_control(e) { + if (!(frappe.boot.link_preview_doctypes || []).includes(this.doctype)) { + return; + } //If control field value is changed, new popover has to be created - this.element.on('change',()=> { + this.element.on('change', () => { this.new_popover = true; }); - if(!this.popover || this.new_popover) { - this.get_preview_fields().then(preview_fields => { - if(preview_fields.length) { - this.data_timeout = setTimeout(() => { - this.create_popover(e, preview_fields); - }, 100); - } - }); + if (!this.popover || this.new_popover) { + this.data_timeout = setTimeout(() => { + this.create_popover(e); + }, 100); + } else { this.popover_timeout = setTimeout(() => { if (this.element.is(':focus')) { @@ -61,20 +61,20 @@ frappe.ui.LinkPreview = class { } } - create_popover(e, preview_fields) { + create_popover(e) { this.new_popover = false; if (this.element.is(':focus')) { return; } - this.get_preview_fields_value(preview_fields).then((preview_data)=> { - if(preview_data) { - if(this.popover_timeout) { + this.get_preview_data().then(preview_data => { + if (preview_data) { + if (this.popover_timeout) { clearTimeout(this.popover_timeout); } this.popover_timeout = setTimeout(() => { - if(this.popover) { + if (this.popover) { let new_content = this.get_popover_html(preview_data); this.popover.options.content = new_content; } else { @@ -88,16 +88,15 @@ frappe.ui.LinkPreview = class { } show_popover(e) { - - this.default_timeout = setTimeout(()=> { + this.default_timeout = setTimeout(() => { this.clear_all_popovers(); }, 10000); - if(!this.is_link) { + if (!this.is_link) { var left = e.pageX; this.element.popover('show'); var width = $('.popover').width(); - $('.control-field-popover').css('left', (left-(width/2)) + 'px'); + $('.control-field-popover').css('left', (left - (width / 2)) + 'px'); } else { this.element.popover('show'); } @@ -109,14 +108,14 @@ frappe.ui.LinkPreview = class { if (!$('.popover:hover').length) { this.link_hovered = false; } - if(!this.link_hovered) { - if(this.data_timeout) { + if (!this.link_hovered) { + if (this.data_timeout) { clearTimeout(this.data_timeout); } if (this.popover_timeout) { clearTimeout(this.popover_timeout); } - if(this.default_timeout) { + if (this.default_timeout) { clearTimeout(this.default_timeout); } this.clear_all_popovers(); @@ -132,45 +131,10 @@ frappe.ui.LinkPreview = class { this.popovers_list.forEach($el => $el.hide()); } - get_preview_fields() { - return new Promise((resolve) => { - let dt = this.doctype; - let fields = []; - frappe.model.with_doctype(dt, () => { - let meta = frappe.get_meta(dt); - let meta_fields = meta.fields; - - if (!meta.show_preview_popup) { - // no preview - resolve([]); - return; - } - - meta_fields.filter((field) => { - // build list of fields to fetch - if(field.in_preview) { - fields.push({'name':field.fieldname,'type':field.fieldtype}); - } - }); - - // no preview fields defined, build list from mandatory fields - if(!fields.length) { - meta_fields.filter((field) => { - if(field.reqd) { - fields.push({'name':field.fieldname,'type':field.fieldtype}); - } - }); - } - resolve(fields); - }); - }); - } - - get_preview_fields_value(field_list) { + get_preview_data() { return frappe.xcall('frappe.desk.link_preview.get_preview_data', { 'doctype': this.doctype, 'docname': this.name, - 'fields': field_list, }); } @@ -195,56 +159,50 @@ frappe.ui.LinkPreview = class { } get_popover_html(preview_data) { - if(!this.href) { + if (!this.href) { this.href = window.location.href; } - if(this.href && this.href.includes(' ')) { + if (this.href && this.href.includes(' ')) { this.href = this.href.replace(new RegExp(' ', 'g'), '%20'); } let image_html = ''; let id_html = ''; let content_html = ''; - let meta = frappe.get_meta(this.doctype); - let title = preview_data.title; - if(preview_data[meta.image_field]) { - let image_url = encodeURI(preview_data[meta.image_field]); - image_html += ` -
- -
`; + if (preview_data.preview_image) { + let image_url = encodeURI(preview_data.preview_image); + image_html = ` +
+ +
+ `; } - - if(title && title != preview_data.name) { - id_html+= `${preview_data.name}`; - } - if(!title) { - title = preview_data.name; + if (preview_data.preview_title != preview_data.name) { + id_html = `${preview_data.name}`; } Object.keys(preview_data).forEach(key => { - if(key!=meta.image_field && key!='name' && key!=meta.title_field) { - let value = this.truncate_value(preview_data[key]); - let label = this.truncate_value(frappe.meta.get_label(this.doctype, key)); + if (!['preview_image', 'preview_title', 'name'].includes(key)) { + let value = frappe.ellipsis(preview_data[key], 280); + let label = key; content_html += ` -
-
${label}
-
${value}
-
+
+
${label}
+
${value}
+
`; } }); - content_html = `
${content_html}
`; - let popover_content = - `
${image_html} + let popover_content =` +
${image_html}
- ${title} + ${preview_data.preview_title} ${this.doctype} ${id_html}
@@ -252,16 +210,10 @@ frappe.ui.LinkPreview = class {
${content_html} -
`; +
+ `; return popover_content; } - truncate_value(value) { - if (value.length > 280) { - value = value.slice(0,280) + '...'; - } - return value; - } - }; diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 112ad4edde2..292ac43be7f 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -225,7 +225,11 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { // child table field const [cdt, _field] = fieldname.split(':'); const cdt_row = Object.keys(doc) - .filter(key => Array.isArray(doc[key]) && doc[key][0].doctype === cdt) + .filter(key => + Array.isArray(doc[key]) + && doc[key].length + && doc[key][0].doctype === cdt + ) .map(key => doc[key]) .map(a => a[0]) .filter(cdoc => cdoc.name === d[cdt + ':name'])[0]; @@ -525,15 +529,25 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { control.set_value(value); return this.set_control_value(doctype, docname, fieldname, value) .then((updated_doc) => { - const _data = this.data.find(d => d.name === updated_doc.name); + const _data = this.data + .filter(b => b.name === updated_doc.name) + .find(a => + // child table cell + (doctype != updated_doc.doctype && a[doctype + ":name"] == docname) + || doctype == updated_doc.doctype + ); + for (let field in _data) { if (field.includes(':')) { // child table field const [cdt, _field] = field.split(':'); const cdt_row = Object.keys(updated_doc) - .filter(key => Array.isArray(updated_doc[key]) && updated_doc[key][0].doctype === cdt) - .map(key => updated_doc[key]) - .map(a => a[0]) + .filter(key => + Array.isArray(updated_doc[key]) + && updated_doc[key].length + && updated_doc[key][0].doctype === cdt + ) + .map(key => updated_doc[key])[0] .filter(cdoc => cdoc.name === _data[cdt + ':name'])[0]; if (cdt_row) { _data[field] = cdt_row[_field]; diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less index c82479c3018..7649c0c5e8b 100644 --- a/frappe/public/less/form.less +++ b/frappe/public/less/form.less @@ -363,7 +363,7 @@ h6.uppercase, .h6.uppercase { margin-top: 0px; b { - color: @text-color !important; + color: @text-light !important; } blockquote { diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index 519a49220a9..63ec6588e5b 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -21,7 +21,7 @@ login.bind_events = function() { args.pwd = $("#login_password").val(); args.device = "desktop"; if(!args.usr || !args.pwd) { - frappe.msgprint("{{ _("Both login and password required") }}"); + frappe.msgprint('{{ _("Both login and password required") }}'); return false; } login.call(args); @@ -36,7 +36,7 @@ login.bind_events = function() { args.redirect_to = frappe.utils.get_url_arg("redirect-to") || ''; args.full_name = ($("#signup_fullname").val() || "").trim(); if(!args.email || !validate_email(args.email) || !args.full_name) { - login.set_indicator("{{ _("Valid email and name required") }}", 'red'); + login.set_indicator('{{ _("Valid email and name required") }}', 'red'); return false; } login.call(args); @@ -49,7 +49,7 @@ login.bind_events = function() { args.cmd = "frappe.core.doctype.user.user.reset_password"; args.user = ($("#forgot_email").val() || "").trim(); if(!args.user) { - login.set_indicator("{{ _("Valid Login id required.") }}", 'red'); + login.set_indicator('{{ _("Valid Login id required.") }}', 'red'); return false; } login.call(args); @@ -74,7 +74,7 @@ login.bind_events = function() { args.pwd = $("#login_password").val(); args.device = "desktop"; if(!args.usr || !args.pwd) { - login.set_indicator("{{ _("Both login and password required") }}", 'red'); + login.set_indicator('{{ _("Both login and password required") }}', 'red'); return false; } login.call(args); @@ -125,7 +125,7 @@ login.signup = function() { // Login login.call = function(args, callback) { - login.set_indicator("{{ _('Verifying...') }}", 'blue'); + login.set_indicator('{{ _("Verifying...") }}', 'blue'); return frappe.call({ type: "POST", @@ -172,7 +172,7 @@ login.login_handlers = (function() { var login_handlers = { 200: function(data) { if(data.message == 'Logged In'){ - login.set_indicator("{{ _("Success") }}", 'green'); + login.set_indicator('{{ _("Success") }}', 'green'); window.location.href = frappe.utils.get_url_arg("redirect-to") || data.home_page; } else if(data.message == 'Password Reset'){ window.location.href = data.redirect_to; @@ -196,13 +196,13 @@ login.login_handlers = (function() { } } else if(window.location.hash === '#forgot') { if(data.message==='not found') { - login.set_indicator("{{ _("Not a valid user") }}", 'red'); + login.set_indicator('{{ _("Not a valid user") }}', 'red'); } else if (data.message=='not allowed') { - login.set_indicator("{{ _("Not Allowed") }}", 'red'); + login.set_indicator('{{ _("Not Allowed") }}', 'red'); } else if (data.message=='disabled') { - login.set_indicator("{{ _("Not Allowed: Disabled User") }}", 'red'); + login.set_indicator('{{ _("Not Allowed: Disabled User") }}', 'red'); } else { - login.set_indicator("{{ _("Instructions Emailed") }}", 'green'); + login.set_indicator('{{ _("Instructions Emailed") }}', 'green'); } @@ -210,7 +210,7 @@ login.login_handlers = (function() { if(cint(data.message[0])==0) { login.set_indicator(data.message[1], 'red'); } else { - login.set_indicator("{{ _('Success') }}", 'green'); + login.set_indicator('{{ _("Success") }}', 'green'); frappe.msgprint(data.message[1]) } //login.set_indicator(__(data.message), 'green'); @@ -218,7 +218,7 @@ login.login_handlers = (function() { //OTP verification if(data.verification && data.message != 'Logged In') { - login.set_indicator("{{ _("Success") }}", 'green'); + login.set_indicator('{{ _("Success") }}', 'green'); document.cookie = "tmp_id="+data.tmp_id; @@ -231,8 +231,8 @@ login.login_handlers = (function() { } } }, - 401: get_error_handler("{{ _("Invalid Login. Try again.") }}"), - 417: get_error_handler("{{ _("Oops! Something went wrong") }}") + 401: get_error_handler('{{ _("Invalid Login. Try again.") }}'), + 417: get_error_handler('{{ _("Oops! Something went wrong") }}') }; return login_handlers; @@ -272,11 +272,11 @@ var request_otp = function(r){ $('.login-content').empty().append($('
').attr({'id':'twofactor_div'}).html( '
\
\ - Verification\ + {{ _("Verification") }}\
\
\ - \ - \ + \ + \
')); // add event handler for submit button verify_token(); @@ -287,11 +287,11 @@ var continue_otp_app = function(setup, qrcode){ var qrcode_div = $('
'); if (setup){ - direction = $('
').attr('id','qr_info').text('Enter Code displayed in OTP App.'); + direction = $('
').attr('id','qr_info').text('{{ _("Enter Code displayed in OTP App.") }}'); qrcode_div.append(direction); $('#otp_div').prepend(qrcode_div); } else { - direction = $('
').attr('id','qr_info').text('OTP setup using OTP App was not completed. Please contact Administrator.'); + direction = $('
').attr('id','qr_info').text('{{ _("OTP setup using OTP App was not completed. Please contact Administrator.") }}'); qrcode_div.append(direction); $('#otp_div').prepend(qrcode_div); } @@ -305,7 +305,7 @@ var continue_sms = function(setup, prompt){ sms_div.append(prompt) $('#otp_div').prepend(sms_div); } else { - direction = $('
').attr('id','qr_info').text(prompt || 'SMS was not sent. Please contact Administrator.'); + direction = $('
').attr('id','qr_info').text(prompt || '{{ _("SMS was not sent. Please contact Administrator.") }}'); sms_div.append(direction); $('#otp_div').prepend(sms_div) } @@ -319,7 +319,7 @@ var continue_email = function(setup, prompt){ email_div.append(prompt) $('#otp_div').prepend(email_div); } else { - var direction = $('
').attr('id','qr_info').text(prompt || 'Verification code email not sent. Please contact Administrator.'); + var direction = $('
').attr('id','qr_info').text(prompt || '{{ _("Verification code email not sent. Please contact Administrator.") }}'); email_div.append(direction); $('#otp_div').prepend(email_div); }