Merge branch 'version-12-hotfix' into version-12
This commit is contained in:
commit
0de729ff3d
21 changed files with 112 additions and 36 deletions
|
|
@ -23,7 +23,7 @@ if sys.version[0] == '2':
|
|||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
__version__ = '12.0.3'
|
||||
__version__ = '12.0.4'
|
||||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
|
|
|
|||
|
|
@ -115,9 +115,12 @@ def set_maintenance_mode(context, state, site=None):
|
|||
|
||||
@click.command('doctor') #Passing context always gets a site and if there is no use site it breaks
|
||||
@click.option('--site', help='site name')
|
||||
def doctor(site=None):
|
||||
@pass_context
|
||||
def doctor(context, site=None):
|
||||
"Get diagnostic info about background workers"
|
||||
from frappe.utils.doctor import doctor as _doctor
|
||||
if not site:
|
||||
site = get_site(context)
|
||||
return _doctor(site=site)
|
||||
|
||||
@click.command('show-pending-jobs')
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ frappe.pages['background_jobs'].on_page_load = function(wrapper) {
|
|||
|
||||
frappe.pages['background_jobs'].on_page_show = function(wrapper) {
|
||||
frappe.pages.background_jobs.refresh_jobs();
|
||||
frappe.call({
|
||||
method: 'frappe.core.page.background_jobs.background_jobs.get_scheduler_status',
|
||||
callback: function(r) {
|
||||
frappe.pages.background_jobs.page.set_indicator(...r.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frappe.pages.background_jobs.refresh_jobs = function() {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import frappe
|
|||
from rq import Queue, Worker
|
||||
from frappe.utils.background_jobs import get_redis_conn
|
||||
from frappe.utils import format_datetime, cint
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
from frappe import _
|
||||
|
||||
colors = {
|
||||
'queued': 'orange',
|
||||
|
|
@ -49,3 +51,9 @@ def get_info(show_failed=False):
|
|||
for j in q.get_jobs()[:10]: add_job(j, q.name)
|
||||
|
||||
return jobs
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_scheduler_status():
|
||||
if is_scheduler_inactive():
|
||||
return [_("Inactive"), "red"]
|
||||
return [_("Active"), "green"]
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ def get_desktop_settings():
|
|||
if category in user_saved_modules_by_category:
|
||||
user_modules = user_saved_modules_by_category[category]
|
||||
user_modules_by_category[category] = [apply_user_saved_links(modules_by_name[m]) \
|
||||
for m in user_modules]
|
||||
for m in user_modules if modules_by_name.get(m)]
|
||||
else:
|
||||
user_modules_by_category[category] = [apply_user_saved_links(m) \
|
||||
for m in all_modules if m.get('category') == category]
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ def get_form_params():
|
|||
|
||||
# queries must always be server side
|
||||
data.query = None
|
||||
data.strict = None
|
||||
|
||||
return data
|
||||
|
||||
|
|
|
|||
|
|
@ -153,7 +153,8 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
|
|||
order_by=order_by,
|
||||
ignore_permissions=ignore_permissions,
|
||||
reference_doctype=reference_doctype,
|
||||
as_list=not as_dict)
|
||||
as_list=not as_dict,
|
||||
strict=False)
|
||||
|
||||
if doctype in UNTRANSLATED_DOCTYPES:
|
||||
values = tuple([v for v in list(values) if re.search(txt+".*", (_(v.name) if as_dict else _(v[0])), re.IGNORECASE)])
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class DatabaseQuery(object):
|
|||
ignore_permissions=False, user=None, with_comment_count=False,
|
||||
join='left join', distinct=False, start=None, page_length=None, limit=None,
|
||||
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
|
||||
update=None, add_total_row=None, user_settings=None, reference_doctype=None, return_query=False):
|
||||
update=None, add_total_row=None, user_settings=None, reference_doctype=None, return_query=False, strict=True):
|
||||
if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user):
|
||||
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
|
||||
raise frappe.PermissionError(self.doctype)
|
||||
|
|
@ -80,6 +80,7 @@ class DatabaseQuery(object):
|
|||
self.update = update
|
||||
self.user_settings_fields = copy.deepcopy(self.fields)
|
||||
self.return_query = return_query
|
||||
self.strict = strict
|
||||
|
||||
# for contextual user permission check
|
||||
# to determine which user permission is applicable on link field of specific doctype
|
||||
|
|
@ -115,8 +116,12 @@ class DatabaseQuery(object):
|
|||
args.fields = 'distinct ' + args.fields
|
||||
args.order_by = '' # TODO: recheck for alternative
|
||||
|
||||
query = """select %(fields)s from %(tables)s %(conditions)s
|
||||
%(group_by)s %(order_by)s %(limit)s""" % args
|
||||
query = """select %(fields)s
|
||||
from %(tables)s
|
||||
%(conditions)s
|
||||
%(group_by)s
|
||||
%(order_by)s
|
||||
%(limit)s""" % args
|
||||
|
||||
if self.return_query:
|
||||
return query
|
||||
|
|
@ -240,6 +245,12 @@ class DatabaseQuery(object):
|
|||
|
||||
_is_query(field)
|
||||
|
||||
if self.strict:
|
||||
if re.compile(r".*/\*.*").match(field):
|
||||
frappe.throw(_('Illegal SQL Query'))
|
||||
|
||||
if re.compile(r".*\s(union).*\s").match(field.lower()):
|
||||
frappe.throw(_('Illegal SQL Query'))
|
||||
|
||||
def extract_tables(self):
|
||||
"""extract tables from fields"""
|
||||
|
|
@ -688,6 +699,8 @@ class DatabaseQuery(object):
|
|||
if 'select' in _lower and ' from ' in _lower:
|
||||
frappe.throw(_('Cannot use sub-query in order by'))
|
||||
|
||||
if re.compile(r".*[^a-z0-9-_ ,`'\"\.\(\)].*").match(_lower):
|
||||
frappe.throw(_('Illegal SQL Query'))
|
||||
|
||||
for field in parameters.split(","):
|
||||
if "." in field and field.strip().startswith("`tab"):
|
||||
|
|
@ -755,6 +768,7 @@ def get_list(doctype, *args, **kwargs):
|
|||
kwargs.pop('cmd', None)
|
||||
kwargs.pop('ignore_permissions', None)
|
||||
kwargs.pop('data', None)
|
||||
kwargs.pop('strict', None)
|
||||
|
||||
# If doctype is child table
|
||||
if frappe.is_table(doctype):
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ def execute():
|
|||
filters={'is_folder': 0})
|
||||
|
||||
for file in files:
|
||||
file_url = file.file_url
|
||||
file_url = file.file_url or ""
|
||||
if file.is_private:
|
||||
if not file_url.startswith('/private/files/'):
|
||||
generate_file(file.name)
|
||||
|
|
@ -32,4 +32,4 @@ def generate_file(file_name):
|
|||
except IOError:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(e)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setup_sortable();
|
||||
if (!frappe.utils.is_mobile()) {
|
||||
this.setup_sortable();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setup_sortable() {
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ frappe.views.FileView.grid_view = frappe.get_user_settings('File').grid_view ||
|
|||
|
||||
function redirect_to_home_if_invalid_route() {
|
||||
const route = frappe.get_route();
|
||||
if (route[2] !== 'Home') {
|
||||
if (route[2] === 'List') {
|
||||
// if the user somehow redirects to List/File/List
|
||||
// redirect back to Home
|
||||
frappe.set_route('List', 'File', 'Home');
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ frappe.views.Page = Class.extend({
|
|||
this.wrapper.innerHTML = this.pagedoc.content;
|
||||
frappe.dom.eval(this.pagedoc.__script || this.pagedoc.script || '');
|
||||
frappe.dom.set_style(this.pagedoc.style || '');
|
||||
|
||||
// set breadcrumbs
|
||||
frappe.breadcrumbs.add(this.pagedoc.module || null);
|
||||
}
|
||||
|
||||
this.trigger_page_event('on_page_load');
|
||||
|
|
|
|||
|
|
@ -714,9 +714,10 @@ def get_url(uri=None, full_address=False):
|
|||
return uri
|
||||
|
||||
if not host_name:
|
||||
if hasattr(frappe.local, "request") and frappe.local.request and frappe.local.request.host:
|
||||
protocol = 'https://' if 'https' == frappe.get_request_header('X-Forwarded-Proto', "") else 'http://'
|
||||
host_name = protocol + frappe.local.request.host
|
||||
request_host_name = get_host_name_from_request()
|
||||
|
||||
if request_host_name:
|
||||
host_name = request_host_name
|
||||
|
||||
elif frappe.local.site:
|
||||
protocol = 'http://'
|
||||
|
|
@ -753,6 +754,11 @@ def get_url(uri=None, full_address=False):
|
|||
|
||||
return url
|
||||
|
||||
def get_host_name_from_request():
|
||||
if hasattr(frappe.local, "request") and frappe.local.request and frappe.local.request.host:
|
||||
protocol = 'https://' if 'https' == frappe.get_request_header('X-Forwarded-Proto', "") else 'http://'
|
||||
return protocol + frappe.local.request.host
|
||||
|
||||
def url_contains_port(url):
|
||||
parts = url.split(':')
|
||||
return len(parts) > 2
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import frappe.utils
|
|||
from collections import defaultdict
|
||||
from rq import Worker, Connection
|
||||
from frappe.utils.background_jobs import get_redis_conn, get_queue, get_queue_list
|
||||
from frappe.utils.scheduler import is_scheduler_disabled
|
||||
from frappe.utils.scheduler import is_scheduler_disabled, is_scheduler_inactive
|
||||
from six import iteritems
|
||||
|
||||
|
||||
|
|
@ -107,8 +107,19 @@ def doctor(site=None):
|
|||
for s in sites:
|
||||
frappe.init(s)
|
||||
frappe.connect()
|
||||
|
||||
if is_scheduler_disabled():
|
||||
print("Scheduler disabled for", s)
|
||||
|
||||
if frappe.local.conf.maintenance_mode:
|
||||
print("Maintenance mode on for", s)
|
||||
|
||||
if frappe.local.conf.pause_scheduler:
|
||||
print("Scheduler paused for", s)
|
||||
|
||||
if is_scheduler_inactive():
|
||||
print("Scheduler inactive for", s)
|
||||
|
||||
frappe.destroy()
|
||||
|
||||
# TODO improve this
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ def sanitize_html(html, linkify=False):
|
|||
return html
|
||||
|
||||
tags = (acceptable_elements + svg_elements + mathml_elements
|
||||
+ ["html", "head", "meta", "link", "body", "iframe", "style", "o:p"])
|
||||
+ ["html", "head", "meta", "link", "body", "style", "o:p"])
|
||||
attributes = {"*": acceptable_attributes, 'svg': svg_attributes}
|
||||
styles = bleach_whitelist.all_styles
|
||||
strip_comments = False
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ def get_jenv():
|
|||
import frappe
|
||||
|
||||
if not getattr(frappe.local, 'jenv', None):
|
||||
from jinja2 import Environment, DebugUndefined
|
||||
from jinja2 import DebugUndefined
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
|
||||
# frappe will be loaded last, so app templates will get precedence
|
||||
jenv = Environment(loader = get_jloader(),
|
||||
jenv = SandboxedEnvironment(loader = get_jloader(),
|
||||
undefined=DebugUndefined)
|
||||
set_filters(jenv)
|
||||
|
||||
|
|
|
|||
|
|
@ -79,14 +79,8 @@ def enqueue_events_for_site(site, queued_jobs):
|
|||
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
if frappe.local.conf.maintenance_mode:
|
||||
return
|
||||
|
||||
if frappe.local.conf.pause_scheduler:
|
||||
return
|
||||
|
||||
frappe.connect()
|
||||
if is_scheduler_disabled():
|
||||
if is_scheduler_inactive():
|
||||
return
|
||||
|
||||
enqueue_events(site=site, queued_jobs=queued_jobs)
|
||||
|
|
@ -226,6 +220,18 @@ def get_enabled_scheduler_events():
|
|||
return ["all", "hourly", "hourly_long", "daily", "daily_long",
|
||||
"weekly", "weekly_long", "monthly", "monthly_long", "cron"]
|
||||
|
||||
def is_scheduler_inactive():
|
||||
if frappe.local.conf.maintenance_mode:
|
||||
return True
|
||||
|
||||
if frappe.local.conf.pause_scheduler:
|
||||
return True
|
||||
|
||||
if is_scheduler_disabled():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_scheduler_disabled():
|
||||
if frappe.conf.disable_scheduler:
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ from frappe.utils.verified_command import get_signed_params
|
|||
class PersonalDataDownloadRequest(Document):
|
||||
def after_insert(self):
|
||||
personal_data = get_user_data(self.user)
|
||||
self.generate_file_and_send_mail(personal_data)
|
||||
|
||||
frappe.enqueue_doc(self.doctype, self.name, 'generate_file_and_send_mail',
|
||||
queue='short', personal_data=personal_data, now=frappe.flags.in_test)
|
||||
|
||||
def generate_file_and_send_mail(self, personal_data):
|
||||
"""generate the file link for download"""
|
||||
|
|
|
|||
|
|
@ -21,18 +21,25 @@ class TestRequestPersonalData(unittest.TestCase):
|
|||
|
||||
def test_file_and_email_creation(self):
|
||||
frappe.set_user('test_privacy@example.com')
|
||||
download_request = frappe.get_doc({"doctype": 'Personal Data Download Request', 'user': 'test_privacy@example.com'})
|
||||
download_request = frappe.get_doc({
|
||||
"doctype": 'Personal Data Download Request',
|
||||
'user': 'test_privacy@example.com'
|
||||
})
|
||||
download_request.save(ignore_permissions=True)
|
||||
|
||||
frappe.set_user('Administrator')
|
||||
|
||||
f = frappe.get_all('File',
|
||||
{'attached_to_doctype':'Personal Data Download Request', 'attached_to_name': download_request.name},
|
||||
['*'])
|
||||
self.assertEqual(len(f), 1)
|
||||
file_count = frappe.db.count('File', {
|
||||
'attached_to_doctype':'Personal Data Download Request',
|
||||
'attached_to_name': download_request.name
|
||||
})
|
||||
|
||||
email_queue = frappe.db.sql("""SELECT *
|
||||
FROM `tabEmail Queue`
|
||||
ORDER BY `creation` DESC""", as_dict=True)
|
||||
self.assertEqual(file_count, 1)
|
||||
|
||||
email_queue = frappe.get_all('Email Queue',
|
||||
fields=['message'],
|
||||
order_by="creation DESC",
|
||||
limit=1)
|
||||
self.assertTrue("Subject: Download Your Data" in email_queue[0].message)
|
||||
|
||||
frappe.db.sql("delete from `tabEmail Queue`")
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
<span class="indicator blue" data-text="{{ _("Forgot Password") }}"></span></div>
|
||||
<input type="email" id="forgot_email"
|
||||
class="form-control" placeholder="{{ _('Email address') }}" required autofocus>
|
||||
<button class="btn btn-sm btn-primary btn-block btn-forgot" type="submit">{{ _("Send Password") }}</button>
|
||||
<button class="btn btn-sm btn-primary btn-block btn-forgot" type="submit">{{ _("Reset Password") }}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class='form-footer'>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@ base_template_path = "templates/www/sitemap.xml"
|
|||
|
||||
def get_context(context):
|
||||
"""generate the sitemap XML"""
|
||||
host = get_request_site_address()
|
||||
|
||||
# the site might be accessible from multiple host_names
|
||||
# for e.g gadgets.erpnext.com and gadgetsinternational.com
|
||||
# so it should be picked from the request
|
||||
host = frappe.utils.get_host_name_from_request()
|
||||
|
||||
links = []
|
||||
for route, page in iteritems(get_pages()):
|
||||
if page.sitemap:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue