diff --git a/frappe/__init__.py b/frappe/__init__.py index 62576bea8e..c2008a059d 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '12.0.0' +__version__ = '12.0.1' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index 8a429b93f9..864a60e498 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -33,7 +33,7 @@ class AutoRepeat(Document): def before_insert(self): if not frappe.flags.in_test: start_date = self.start_date - today_date = today() + today_date = getdate(today()) if start_date <= today_date: start_date = today_date @@ -61,7 +61,8 @@ class AutoRepeat(Document): frappe.throw(_("Enable Allow Auto Repeat for the doctype {0} in Customize Form").format(self.reference_doctype)) def validate_dates(self): - self.validate_from_to_dates('start_date', 'end_date') + if self.end_date: + self.validate_from_to_dates('start_date', 'end_date') if self.end_date == self.start_date: frappe.throw(_('{0} should not be same as {1}').format(frappe.bold('End Date'), frappe.bold('Start Date'))) @@ -325,7 +326,9 @@ def set_auto_repeat_as_completed(): doc.save() @frappe.whitelist() -def make_auto_repeat(doctype, docname, frequency, start_date, end_date = None): +def make_auto_repeat(doctype, docname, frequency = 'Daily', start_date = None, end_date = None): + if not start_date: + start_date = getdate(today()) doc = frappe.new_doc('Auto Repeat') doc.reference_doctype = doctype doc.reference_document = docname diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 6e6cce52bf..8fac317100 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -473,7 +473,7 @@ class File(NestedSet): return None - def save_file(self, content=None, decode=False): + def save_file(self, content=None, decode=False, ignore_existing_file_check=False): file_exists = False self.content = content if decode: @@ -490,12 +490,15 @@ class File(NestedSet): self.content_hash = get_content_hash(self.content) self.content_type = mimetypes.guess_type(self.file_name)[0] + _file = False + # check if a file exists with the same content hash and is also in the same folder (public or private) - _file = frappe.get_value("File", { - "content_hash": self.content_hash, - "is_private": self.is_private - }, - ["file_url"]) + if not ignore_existing_file_check: + _file = frappe.get_value("File", { + "content_hash": self.content_hash, + "is_private": self.is_private + }, + ["file_url"]) if _file: self.file_url = _file diff --git a/frappe/core/doctype/session_default_settings/session_default_settings.js b/frappe/core/doctype/session_default_settings/session_default_settings.js new file mode 100644 index 0000000000..f7cce14809 --- /dev/null +++ b/frappe/core/doctype/session_default_settings/session_default_settings.js @@ -0,0 +1,15 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.ui.form.on('Session Default Settings', { + refresh: function(frm) { + frm.set_query('ref_doctype', 'session_defaults', function() { + return { + filters: { + issingle: 0, + istable: 0 + } + }; + }); + } +}); diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 53d1110e83..73677b1a95 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -45,7 +45,7 @@ def get(chart_name, from_date=None, to_date=None, refresh = None): '''.format( unit_function = get_unit_function(chart.based_on, timegrain), datefield = chart.based_on, - aggregate_function = chart.chart_type, + aggregate_function = get_aggregate_function(chart.chart_type), value_field = chart.value_based_on or '1', doctype = chart.document_type, conditions = conditions, @@ -67,6 +67,13 @@ def get(chart_name, from_date=None, to_date=None, refresh = None): }] } +def get_aggregate_function(chart_type): + return { + "Sum": "SUM", + "Count": "COUNT", + "Average": "AVG" + }[chart_type] + def convert_to_dates(data, timegrain): result = [] for d in data: diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 7767d2a021..53a1c6c13d 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -240,9 +240,6 @@ class DatabaseQuery(object): _is_query(field) - invalid_characters_regex = r".*[^a-zA-Z0-9-_ ,`'\"\*\.\(\)].*" - if re.match(invalid_characters_regex, field): - frappe.throw(_("Illegal characters in SQL query")) def extract_tables(self): """extract tables from fields""" @@ -691,9 +688,6 @@ class DatabaseQuery(object): if 'select' in _lower and ' from ' in _lower: frappe.throw(_('Cannot use sub-query in order by')) - invalid_characters_regex = r".*[^a-z0-9-_ ,`'\"\.\(\)].*" - if re.match(invalid_characters_regex, _lower): - frappe.throw(_("Illegal characters in SQL query")) for field in parameters.split(","): if "." in field and field.strip().startswith("`tab"): diff --git a/frappe/patches/v12_0/fix_public_private_files.py b/frappe/patches/v12_0/fix_public_private_files.py new file mode 100644 index 0000000000..3f8f30379e --- /dev/null +++ b/frappe/patches/v12_0/fix_public_private_files.py @@ -0,0 +1,33 @@ +import frappe +import os + +def execute(): + files = frappe.get_all('File', + fields=['is_private', 'file_url', 'name'], + filters={'is_folder': 0}) + + for file in files: + file_url = file.file_url + if file.is_private: + if not file_url.startswith('/private/files/'): + generate_file(file.name) + else: + if file_url.startswith('/private/files/'): + generate_file(file.name) + +def generate_file(file_name): + try: + file_doc = frappe.get_doc('File', file_name) + # private + new_doc = frappe.new_doc('File') + new_doc.is_private = file_doc.is_private + new_doc.file_name = file_doc.file_name + # to create copy of file in right location + # if the file doc is private then the file will be created in /private folder + # if the file doc is public then the file will be created in /files folder + new_doc.save_file(content=file_doc.get_content(), ignore_existing_file_check=True) + + file_doc.file_url = new_doc.file_url + file_doc.save() + except IOError: + pass \ No newline at end of file diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index e5d4d91d32..2621448af9 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -300,6 +300,10 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ return a.parsed_date - b.parsed_date; }); + // Preselect oldest entry + if (me.start < 1 && r.values.length === 1) { + results[0].checked = 1; + } } me.render_result_list(results, more); } diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 9141bcc525..362378c21e 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -598,7 +598,6 @@ class FilterArea { } const doctype_fields = this.list_view.meta.fields; - fields = fields.concat(doctype_fields.filter( df => df.in_standard_filter && frappe.model.is_value_type(df.fieldtype) @@ -617,12 +616,17 @@ class FilterArea { options = options.join("\n"); } } + let default_value = (fieldtype === 'Link') ? frappe.defaults.get_user_default(options) : null; + if (['__default', '__global'].includes(default_value)) { + default_value = null; + } return { fieldtype: fieldtype, label: __(df.label), options: options, fieldname: df.fieldname, condition: condition, + default: default_value, onchange: () => this.refresh_list_view(), ignore_link_validation: fieldtype === 'Dynamic Link' }; diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js index ed5091b52f..e392de44b1 100644 --- a/frappe/public/js/frappe/ui/toolbar/toolbar.js +++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js @@ -255,6 +255,12 @@ frappe.ui.toolbar.setup_session_defaults = function() { }; } frappe.prompt(fields, function(values) { + //if default is not set for a particular field in prompt + fields.forEach(function(d) { + if (!values[d.fieldname]) { + values[d.fieldname] = ""; + } + }); frappe.call({ method: 'frappe.core.doctype.session_default_settings.session_default_settings.set_session_default_values', args: { @@ -266,7 +272,7 @@ frappe.ui.toolbar.setup_session_defaults = function() { 'message': __('Session Defaults Saved'), 'indicator': 'green' }); - frappe.clear_cache(); + frappe.ui.toolbar.clear_cache(); } else { frappe.show_alert({ 'message': __('An error occurred while setting Session Defaults'), diff --git a/frappe/public/js/frappe/web_form/web_form_list.js b/frappe/public/js/frappe/web_form/web_form_list.js index 709ee66e06..c92b62f013 100644 --- a/frappe/public/js/frappe/web_form/web_form_list.js +++ b/frappe/public/js/frappe/web_form/web_form_list.js @@ -154,7 +154,7 @@ export default class WebFormList { add_heading(row, "Sr."); this.columns.forEach(col => { let th = document.createElement("th"); - let text = document.createTextNode(col.label); + let text = document.createTextNode(__(col.label)); th.appendChild(text); row.appendChild(th); }); diff --git a/frappe/templates/emails/auto_email_report.html b/frappe/templates/emails/auto_email_report.html index 50ab5b1fe3..a658c988a9 100644 --- a/frappe/templates/emails/auto_email_report.html +++ b/frappe/templates/emails/auto_email_report.html @@ -33,9 +33,15 @@ {% for row in data %} {% for col in columns %} - - {{- frappe.format(row[col.fieldname], col, row) -}} - + {% if row[col.fieldname] == 'Total' %} + + {{- row[col.fieldname] -}} + + {% else %} + + {{- frappe.format(row[col.fieldname], col, row) -}} + + {% endif %} {% endfor %} {% endfor %} diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index f8745b82c3..7a27fb3c3b 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -71,7 +71,7 @@ def render_template(template, context, is_path=None, safe_render=True): or (template.endswith('.html') and '\n' not in template)): return get_jenv().get_template(template).render(context) else: - if safe_render and "__" in template: + if safe_render and ".__" in template: throw("Illegal template") try: return get_jenv().from_string(template).render(context)