Merge branch 'develop'
This commit is contained in:
commit
525efb3f2c
25 changed files with 1733 additions and 1531 deletions
|
|
@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json
|
|||
from .exceptions import *
|
||||
from .utils.jinja import get_jenv, get_template, render_template
|
||||
|
||||
__version__ = "7.0.19"
|
||||
__version__ = "7.0.20"
|
||||
|
||||
local = Local()
|
||||
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ class LoginManager:
|
|||
|
||||
def clear_active_sessions(self):
|
||||
"""Clear other sessions of the current user if `deny_multiple_sessions` is not set"""
|
||||
if not (frappe.conf.get("deny_multiple_sessions") or cint(frappe.db.get_system_setting('deny_multiple_sessions'))):
|
||||
if not (cint(frappe.conf.get("deny_multiple_sessions")) or cint(frappe.db.get_system_setting('deny_multiple_sessions'))):
|
||||
return
|
||||
|
||||
if frappe.session.user != "Guest":
|
||||
|
|
|
|||
2
frappe/change_log/v7/v7_0_18.md
Normal file
2
frappe/change_log/v7/v7_0_18.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
- New Feature: Ability to add multiple sessions to users. Edit **User** record and edit "Simultaneous Sessions"
|
||||
- New Feature: Select columns to export and import in **Data Import Tool**
|
||||
|
|
@ -102,6 +102,59 @@ class TestUser(unittest.TestCase):
|
|||
# Clear the user limit
|
||||
clear_limit('users')
|
||||
|
||||
def test_user_limit_for_site_with_simultaneous_sessions(self):
|
||||
from frappe.core.doctype.user.user import get_total_users
|
||||
|
||||
clear_limit('users')
|
||||
|
||||
# make sure this user counts
|
||||
user = frappe.get_doc('User', 'test@example.com')
|
||||
user.add_roles('Website Manager')
|
||||
user.save()
|
||||
|
||||
update_limits({'users': get_total_users()})
|
||||
|
||||
user.simultaneous_sessions = user.simultaneous_sessions + 1
|
||||
|
||||
self.assertRaises(MaxUsersReachedError, user.save)
|
||||
|
||||
# Clear the user limit
|
||||
clear_limit('users')
|
||||
|
||||
# def test_deny_multiple_sessions(self):
|
||||
# clear_limit('users')
|
||||
#
|
||||
# # allow one session
|
||||
# user = frappe.get_doc('User', 'test@example.com')
|
||||
# user.simultaneous_sessions = 1
|
||||
# user.new_password = 'testpassword'
|
||||
# user.save()
|
||||
#
|
||||
# def test_request(conn):
|
||||
# value = conn.get_value('User', 'first_name', {'name': 'test@example.com'})
|
||||
# self.assertTrue('first_name' in value)
|
||||
#
|
||||
# from frappe.frappeclient import FrappeClient
|
||||
# update_site_config('deny_multiple_sessions', 0)
|
||||
#
|
||||
# print 'conn1'
|
||||
# conn1 = FrappeClient(get_url(), "test@example.com", "testpassword", verify=False)
|
||||
# test_request(conn1)
|
||||
#
|
||||
# print 'conn2'
|
||||
# conn2 = FrappeClient(get_url(), "test@example.com", "testpassword", verify=False)
|
||||
# test_request(conn2)
|
||||
#
|
||||
# update_site_config('deny_multiple_sessions', 1)
|
||||
#
|
||||
# print 'conn3'
|
||||
#
|
||||
# conn3 = FrappeClient(get_url(), "test@example.com", "testpassword", verify=False)
|
||||
# test_request(conn3)
|
||||
#
|
||||
# # first connection should fail
|
||||
# test_request(conn1)
|
||||
|
||||
def test_site_expiry(self):
|
||||
update_limits({'expiry': add_to_date(today(), days=-1)})
|
||||
frappe.local.conf = _dict(frappe.get_site_config())
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -58,11 +58,18 @@ class User(Document):
|
|||
self.remove_all_roles_for_guest()
|
||||
self.validate_username()
|
||||
self.remove_disabled_roles()
|
||||
self.validate_user_limit()
|
||||
|
||||
if self.language == "Loading...":
|
||||
self.language = None
|
||||
|
||||
def on_update(self):
|
||||
# clear new password
|
||||
self.validate_user_limit()
|
||||
self.share_with_self()
|
||||
clear_notifications(user=self.name)
|
||||
frappe.clear_cache(user=self.name)
|
||||
self.send_password_notification(self.__new_password)
|
||||
|
||||
def check_demo(self):
|
||||
if frappe.session.user == 'demo@erpnext.com':
|
||||
frappe.throw('Cannot change user details in demo. Please signup for a new account at https://erpnext.com', title='Not Allowed')
|
||||
|
|
@ -122,13 +129,6 @@ class User(Document):
|
|||
else:
|
||||
self.user_type = 'Website User'
|
||||
|
||||
def on_update(self):
|
||||
# clear new password
|
||||
self.share_with_self()
|
||||
clear_notifications(user=self.name)
|
||||
frappe.clear_cache(user=self.name)
|
||||
self.send_password_notification(self.__new_password)
|
||||
|
||||
def share_with_self(self):
|
||||
if self.user_type=="System User":
|
||||
frappe.share.add(self.doctype, self.name, self.name, share=1,
|
||||
|
|
@ -576,9 +576,11 @@ def user_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
key=searchfield, mcond=get_match_cond(doctype)),
|
||||
tuple(list(STANDARD_USERS) + [txt, txt, txt, txt, start, page_len]))
|
||||
|
||||
def get_total_users(exclude_users=None):
|
||||
def get_total_users():
|
||||
"""Returns total no. of system users"""
|
||||
return len(get_system_users(exclude_users=exclude_users))
|
||||
return frappe.db.sql('''select sum(simultaneous_sessions) from `tabUser`
|
||||
where enabled=1 and user_type="System User"
|
||||
and name not in ({})'''.format(", ".join(["%s"]*len(STANDARD_USERS))), STANDARD_USERS)[0][0]
|
||||
|
||||
def get_system_users(exclude_users=None):
|
||||
if not exclude_users:
|
||||
|
|
|
|||
|
|
@ -14,19 +14,32 @@
|
|||
<br>
|
||||
</div>
|
||||
</div>
|
||||
<div class="export-import-section hide">
|
||||
<div class="row" style="max-width: 700px;">
|
||||
<div class="export-import-section hide" style="max-width: 700px;">
|
||||
<h4>{{ __("1. Select Columns") }}</h4>
|
||||
<p>
|
||||
<a class="btn btn-default btn-xs btn-select-all" style="margin-right: 7px;">
|
||||
{%= __("Select All") %}</a>
|
||||
<a class="btn btn-default btn-xs btn-select-mandatory" style="margin-right: 7px;">
|
||||
{%= __("Select Mandatory") %}</a>
|
||||
<a class="btn btn-default btn-xs btn-unselect-all">
|
||||
{%= __("Unselect All") %}</a>
|
||||
</p>
|
||||
<div class="select-columns">
|
||||
</div>
|
||||
<br>
|
||||
<h4>{{ __("2. Download") }}</h4>
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<p><a class="btn btn-default btn-sm btn-download-template">
|
||||
<p><a class="btn btn-primary btn-xs btn-download-template">
|
||||
{%= __("Download Blank Template") %}</a></p>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<h6 class="text-muted">{%= __("Recommended for inserting new records.") %}</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="max-width: 700px;">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<p><a class="btn btn-default btn-sm btn-download-data">
|
||||
<p><a class="btn btn-primary btn-xs btn-download-data">
|
||||
{%= __("Download with Data") %}</a></p>
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
|
|
@ -35,12 +48,17 @@
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<hr>
|
||||
<hr style="margin-top: 50px;">
|
||||
<h3>{%= __("Import") %}</h3>
|
||||
<p class="text-muted">{%= __("Update the template and save in CSV (Comma Separate Values) format before attaching.") %}</p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<br>
|
||||
<h4>{{ __("1. Select File") }}</h4>
|
||||
<div class="upload-area"></div>
|
||||
<br>
|
||||
|
||||
<h4>{{ __("2. Upload") }}</h4>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="always_insert">
|
||||
|
|
|
|||
|
|
@ -34,28 +34,65 @@ frappe.DataImportTool = Class.extend({
|
|||
$(frappe.render_template("data_import_main", this)).appendTo(this.page.main);
|
||||
|
||||
this.select = this.page.main.find("select.doctype");
|
||||
this.select_columns = this.page.main.find('.select-columns');
|
||||
this.select.on("change", function() {
|
||||
me.doctype = $(this).val();
|
||||
me.page.main.find(".export-import-section").toggleClass(!!me.doctype);
|
||||
if(me.doctype) {
|
||||
me.set_btn_links();
|
||||
// set button links
|
||||
}
|
||||
});
|
||||
},
|
||||
set_btn_links: function() {
|
||||
var doctype = encodeURIComponent(this.doctype);
|
||||
this.page.main.find(".btn-download-template").attr("href",
|
||||
"/api/method/frappe.core.page.data_import_tool.exporter.get_template?"
|
||||
+ "doctype=" + doctype
|
||||
+ "&parent_doctype=" + doctype
|
||||
+ "&with_data=No&all_doctypes=Yes");
|
||||
frappe.model.with_doctype(me.doctype, function() {
|
||||
me.page.main.find(".export-import-section").toggleClass(!!me.doctype);
|
||||
if(me.doctype) {
|
||||
|
||||
this.page.main.find(".btn-download-data").attr("href",
|
||||
"/api/method/frappe.core.page.data_import_tool.exporter.get_template?"
|
||||
// render select columns
|
||||
var doctype_list = [frappe.get_doc('DocType', me.doctype)];
|
||||
frappe.meta.get_table_fields(me.doctype).forEach(function(df) {
|
||||
doctype_list.push(frappe.get_doc('DocType', df.options));
|
||||
});
|
||||
|
||||
$(frappe.render_template("data_import_tool_columns", {doctype_list: doctype_list}))
|
||||
.appendTo(me.select_columns.empty());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.page.main.find('.btn-select-all').on('click', function() {
|
||||
me.select_columns.find('.select-column-check').prop('checked', true);
|
||||
});
|
||||
|
||||
this.page.main.find('.btn-unselect-all').on('click', function() {
|
||||
me.select_columns.find('.select-column-check').prop('checked', false);
|
||||
});
|
||||
|
||||
this.page.main.find('.btn-select-mandatory').on('click', function() {
|
||||
me.select_columns.find('.select-column-check').prop('checked', false);
|
||||
me.select_columns.find('.select-column-check[data-reqd="1"]').prop('checked', true);
|
||||
});
|
||||
|
||||
this.page.main.find(".btn-download-template").on('click', function() {
|
||||
window.open(me.get_export_url(false));
|
||||
});
|
||||
|
||||
this.page.main.find(".btn-download-data").on('click', function() {
|
||||
window.open(me.get_export_url(true));
|
||||
});
|
||||
|
||||
},
|
||||
get_export_url: function(with_data) {
|
||||
var doctype = this.select.val();
|
||||
var columns = {};
|
||||
|
||||
this.select_columns.find('.select-column-check:checked').each(function() {
|
||||
var _doctype = $(this).attr('data-doctype');
|
||||
var _fieldname = $(this).attr('data-fieldname');
|
||||
if(!columns[_doctype]) {
|
||||
columns[_doctype] = [];
|
||||
}
|
||||
columns[_doctype].push(_fieldname);
|
||||
});
|
||||
|
||||
return "/api/method/frappe.core.page.data_import_tool.exporter.get_template?"
|
||||
+ "doctype=" + doctype
|
||||
+ "&parent_doctype=" + doctype
|
||||
+ "&with_data=Yes&all_doctypes=Yes");
|
||||
+ "&select_columns=" + JSON.stringify(columns)
|
||||
+ "&with_data="+ (with_data ? 'Yes' : 'No')+"&all_doctypes=Yes";
|
||||
},
|
||||
make_upload: function() {
|
||||
var me = this;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<div style="margin: 15px 0px;">
|
||||
{% for doctype in doctype_list %}
|
||||
<h5 style="margin-top: 25px; margin-bottom: 5px;">{{ doctype.name }}</h5>
|
||||
<div class="row">
|
||||
{% for f in doctype.fields %}
|
||||
{% if (frappe.model.no_value_type.indexOf(f.fieldtype)===-1) %}
|
||||
<div class="col-sm-4">
|
||||
<div class="checkbox" style="margin: 5px 0px;">
|
||||
<label>
|
||||
<input type="checkbox" class="select-column-check"
|
||||
data-fieldname="{{ f.fieldname }}" data-reqd="{{ f.reqd }}"
|
||||
data-doctype="{{ doctype.name }}" checked>
|
||||
<small>{{ __(f.label) }}</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, json, os
|
||||
import frappe, json
|
||||
from frappe import _
|
||||
import frappe.permissions
|
||||
import re
|
||||
from frappe.utils.csvutils import UnicodeWriter
|
||||
from frappe.utils import cstr, cint, flt, formatdate, format_datetime
|
||||
from frappe.utils import cstr, formatdate, format_datetime
|
||||
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys
|
||||
|
||||
reflags = {
|
||||
|
|
@ -22,8 +22,10 @@ reflags = {
|
|||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data="No"):
|
||||
def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data="No", select_columns=None):
|
||||
all_doctypes = all_doctypes=="Yes"
|
||||
if select_columns:
|
||||
select_columns = json.loads(select_columns);
|
||||
docs_to_export = {}
|
||||
if doctype:
|
||||
if isinstance(doctype, basestring):
|
||||
|
|
@ -84,6 +86,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
|
||||
append_field_column(frappe._dict({
|
||||
"fieldname": "name",
|
||||
"parent": dt,
|
||||
"label": "ID",
|
||||
"fieldtype": "Data",
|
||||
"reqd": 1,
|
||||
|
|
@ -105,16 +108,27 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
|
||||
column_start_end[dt].end = len(columns) + 1
|
||||
|
||||
def append_field_column(docfield, mandatory):
|
||||
if docfield and ((mandatory and docfield.reqd) or not (mandatory or docfield.reqd)) \
|
||||
and (docfield.fieldname not in ('parenttype', 'trash_reason')) and not docfield.hidden:
|
||||
tablerow.append("")
|
||||
fieldrow.append(docfield.fieldname)
|
||||
labelrow.append(_(docfield.label))
|
||||
mandatoryrow.append(docfield.reqd and 'Yes' or 'No')
|
||||
typerow.append(docfield.fieldtype)
|
||||
inforow.append(getinforow(docfield))
|
||||
columns.append(docfield.fieldname)
|
||||
def append_field_column(docfield, for_mandatory):
|
||||
if not docfield:
|
||||
return
|
||||
if for_mandatory and not docfield.reqd:
|
||||
return
|
||||
if not for_mandatory and docfield.reqd:
|
||||
return
|
||||
if docfield.fieldname in ('parenttype', 'trash_reason'):
|
||||
return
|
||||
if docfield.hidden:
|
||||
return
|
||||
if select_columns and docfield.fieldname not in select_columns.get(docfield.parent, []):
|
||||
return
|
||||
|
||||
tablerow.append("")
|
||||
fieldrow.append(docfield.fieldname)
|
||||
labelrow.append(_(docfield.label))
|
||||
mandatoryrow.append(docfield.reqd and 'Yes' or 'No')
|
||||
typerow.append(docfield.fieldtype)
|
||||
inforow.append(getinforow(docfield))
|
||||
columns.append(docfield.fieldname)
|
||||
|
||||
def append_empty_field_column():
|
||||
tablerow.append("~")
|
||||
|
|
@ -238,6 +252,8 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
inforow = [_('Info:'), '']
|
||||
columns = [key]
|
||||
|
||||
|
||||
|
||||
build_field_columns(doctype)
|
||||
if all_doctypes:
|
||||
for d in child_doctypes:
|
||||
|
|
|
|||
|
|
@ -291,6 +291,9 @@ def update_site_config(key, value, validate=True):
|
|||
with open(get_site_config_path(), "w") as f:
|
||||
f.write(json.dumps(site_config, indent=1, sort_keys=True))
|
||||
|
||||
if frappe.local.conf:
|
||||
frappe.local.conf[key] = value
|
||||
|
||||
def get_site_config_path():
|
||||
return os.path.join(frappe.local.site_path, "site_config.json")
|
||||
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ def update_limits(limits_dict):
|
|||
limits = get_limits()
|
||||
limits.update(limits_dict)
|
||||
update_site_config("limits", limits, validate=False)
|
||||
frappe.conf.limits = limits
|
||||
frappe.local.conf.limits = limits
|
||||
|
||||
def clear_limit(key):
|
||||
'''Remove a limit option from site_config'''
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ def write_document_file(doc, record_module=None, create_init=None):
|
|||
for fieldname in frappe.model.default_fields:
|
||||
if fieldname in d:
|
||||
del d[fieldname]
|
||||
for fieldname in d.keys():
|
||||
if d[fieldname] == 0 or d[fieldname] == "":
|
||||
del d[fieldname]
|
||||
|
||||
module = record_module or get_module_name(doc)
|
||||
if create_init is None:
|
||||
|
|
|
|||
|
|
@ -134,9 +134,9 @@ body[data-route^="Module"] .main-menu .form-sidebar {
|
|||
height: 0;
|
||||
padding-bottom: 100%;
|
||||
border-radius: 6px;
|
||||
background-size: 100% 100%;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-position: center;
|
||||
}
|
||||
.form-sidebar .sidebar-image-section .standard-image {
|
||||
font-size: 72px;
|
||||
|
|
|
|||
|
|
@ -842,6 +842,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
|
|||
me.frm.attachments.remove_attachment_by_filename(me.value, function() {
|
||||
me.parse_validate_and_set_in_model(null);
|
||||
me.refresh();
|
||||
me.frm.save();
|
||||
});
|
||||
} else {
|
||||
this.dataurl = null;
|
||||
|
|
@ -918,6 +919,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
|
|||
if(selected) {
|
||||
me.parse_validate_and_set_in_model(selected);
|
||||
me.dialog.hide();
|
||||
me.frm.save();
|
||||
} else {
|
||||
msgprint(__("Please attach a file or set a URL"));
|
||||
}
|
||||
|
|
@ -988,6 +990,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
|
|||
this.parse_validate_and_set_in_model(attachment.file_url);
|
||||
this.refresh();
|
||||
this.frm.attachments.update_attachment(attachment);
|
||||
this.frm.save();
|
||||
} else {
|
||||
this.value = this.get_value();
|
||||
this.refresh();
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ frappe.form.formatters = {
|
|||
},
|
||||
Link: function(value, docfield, options, doc) {
|
||||
var doctype = docfield._options || docfield.options;
|
||||
var original_value = value;
|
||||
if(value && value.match(/^['"].*['"]$/)) {
|
||||
value.replace(/^.(.*).$/, "$1");
|
||||
}
|
||||
|
|
@ -85,7 +86,7 @@ frappe.form.formatters = {
|
|||
} else if(docfield && doctype) {
|
||||
return repl('<a class="grey" href="#Form/%(doctype)s/%(name)s" data-doctype="%(doctype)s">%(label)s</a>', {
|
||||
doctype: encodeURIComponent(doctype),
|
||||
name: encodeURIComponent(value),
|
||||
name: encodeURIComponent(original_value),
|
||||
label: __(options && options.label || value)
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,15 @@
|
|||
frappe.provide('frappe.utils');
|
||||
|
||||
frappe.utils = {
|
||||
get_random: function(len) {
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
for( var i=0; i < len; i++ )
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
|
||||
return text;
|
||||
},
|
||||
get_file_link: function(filename) {
|
||||
filename = cstr(filename);
|
||||
if(frappe.utils.is_url(filename)) {
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ frappe.upload = {
|
|||
return;
|
||||
}
|
||||
var attachment = r.message;
|
||||
opts.callback(attachment, r);
|
||||
opts.callback && opts.callback(attachment, r);
|
||||
$(document).trigger("upload_complete", attachment);
|
||||
},
|
||||
error: function(r) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ frappe.template.compile = function(str, name) {
|
|||
|
||||
// replace jinja style tags
|
||||
str = str.replace(/{{/g, "{%=").replace(/}}/g, "%}");
|
||||
|
||||
|
||||
// {% if not test %} --> {% if (!test) { %}
|
||||
str = str.replace(/{%\s?if\s?\s?not\s?([^\(][^%{]+)\s?%}/g, "{% if (! $1) { %}")
|
||||
|
||||
|
|
@ -22,7 +22,13 @@ frappe.template.compile = function(str, name) {
|
|||
|
||||
// {% for item in list %}
|
||||
// --> {% for (var i=0, len=list.length; i<len; i++) { var item = list[i]; %}
|
||||
str = str.replace(/{%\s?for\s([a-z]+)\sin\s([a-z]+)\s?%}/g, "{% for (var i=0, len=$2.length; i<len; i++) { var $1 = $2[i]; %}");
|
||||
function replacer(match, p1, p2, offset, string) {
|
||||
var i = frappe.utils.get_random(3);
|
||||
var len = frappe.utils.get_random(3);
|
||||
return "{% for (var "+i+"=0, "+len+"="+p2+".length; "+i+"<"+len+"; "+i+"++) { var "
|
||||
+p1+" = "+p2+"["+i+"]; %}";
|
||||
}
|
||||
str = str.replace(/{%\s?for\s([a-z]+)\sin\s([a-z._]+)\s?%}/g, replacer);
|
||||
|
||||
// {% endfor %} --> {% } %}
|
||||
str = str.replace(/{%\s?endif\s?%}/g, "{% }; %}");
|
||||
|
|
@ -46,7 +52,7 @@ frappe.template.compile = function(str, name) {
|
|||
.split("\r").join("\\'")
|
||||
+ "');}return _p.join('');";
|
||||
|
||||
frappe.template.debug[str] = fn_str;
|
||||
frappe.template.debug[name] = fn_str;
|
||||
try {
|
||||
frappe.template.compiled[key] = new Function("obj", fn_str);
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -181,9 +181,9 @@ body[data-route^="Module"] .main-menu {
|
|||
height: 0;
|
||||
padding-bottom: 100%;
|
||||
border-radius: 6px;
|
||||
background-size: 100% 100%;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.standard-image {
|
||||
|
|
|
|||
|
|
@ -57,15 +57,21 @@ def clear_sessions(user=None, keep_current=False, device=None):
|
|||
if not device:
|
||||
device = frappe.session.data.device or "desktop"
|
||||
|
||||
for sid in frappe.db.sql_list("""select sid from tabSessions where user=%s and device=%s""", (user, device)):
|
||||
if keep_current and frappe.session.sid==sid:
|
||||
continue
|
||||
else:
|
||||
delete_session(sid)
|
||||
simultaneous_sessions = frappe.db.get_value('User', user, 'simultaneous_sessions') or 1
|
||||
|
||||
condition = ''
|
||||
if keep_current:
|
||||
condition = ' and sid != "{0}"'.format(frappe.session.sid)
|
||||
|
||||
limit = simultaneous_sessions - 1
|
||||
|
||||
for i, sid in enumerate(frappe.db.sql_list("""select sid from tabSessions
|
||||
where user=%s and device=%s {condition}
|
||||
order by lastupdate desc limit {limit}, 100""".format(condition=condition, limit=limit),
|
||||
(user, device))):
|
||||
delete_session(sid)
|
||||
|
||||
def delete_session(sid=None, user=None):
|
||||
if not user:
|
||||
user = hasattr(frappe.local, "session") and frappe.session.user or "Guest"
|
||||
frappe.cache().hdel("session", sid)
|
||||
frappe.cache().hdel("last_db_session_update", sid)
|
||||
frappe.db.sql("""delete from tabSessions where sid=%s""", sid)
|
||||
|
|
@ -298,7 +304,7 @@ class Session:
|
|||
return (cint(parts[0]) * 3600) + (cint(parts[1]) * 60) + cint(parts[2])
|
||||
|
||||
def delete_session(self):
|
||||
delete_session(self.sid, user=self.user)
|
||||
delete_session(self.sid)
|
||||
|
||||
def start_as_guest(self):
|
||||
"""all guests share the same 'Guest' session"""
|
||||
|
|
|
|||
|
|
@ -12,15 +12,7 @@ class TestAPI(unittest.TestCase):
|
|||
frappe.db.sql('delete from `tabToDo` where description like "Test API%"')
|
||||
frappe.db.commit()
|
||||
|
||||
host = get_url()
|
||||
|
||||
if not host.startswith('http'):
|
||||
host = 'http://' + host
|
||||
|
||||
if not host.endswith(':8000'):
|
||||
host = host + ':8000'
|
||||
|
||||
server = FrappeClient(host, "Administrator", "admin", verify=False)
|
||||
server = FrappeClient(get_url(), "Administrator", "admin", verify=False)
|
||||
|
||||
server.insert_many([
|
||||
{"doctype": "ToDo", "description": "Test API 1"},
|
||||
|
|
|
|||
|
|
@ -76,12 +76,13 @@ class TestDataImport(unittest.TestCase):
|
|||
def test_import_with_children(self):
|
||||
exporter.get_template("Event", all_doctypes="Yes", with_data="No")
|
||||
content = read_csv_content(frappe.response.result)
|
||||
|
||||
content.append([None] * len(content[-2]))
|
||||
content[-1][2] = "__Test Event"
|
||||
content[-1][2] = "__Test Event with children"
|
||||
content[-1][3] = "Private"
|
||||
content[-1][4] = "2014-01-01 10:00:00.000000"
|
||||
content[-1][content[15].index("role")] = "System Manager"
|
||||
importer.upload(content)
|
||||
|
||||
ev = frappe.get_doc("Event", {"subject":"__Test Event"})
|
||||
ev = frappe.get_doc("Event", {"subject":"__Test Event with children"})
|
||||
self.assertTrue("System Manager" in [d.role for d in ev.roles])
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ def read_csv_content_from_uploaded_file(ignore_encoding=False):
|
|||
return read_csv_content(fcontent, ignore_encoding)
|
||||
|
||||
def read_csv_content_from_attached_file(doc):
|
||||
fileid = frappe.db.get_value("File", {"attached_to_doctype": doc.doctype,
|
||||
"attached_to_name":doc.name}, "name")
|
||||
fileid = frappe.get_all("File", fields = ["name"], filters = {"attached_to_doctype": doc.doctype,
|
||||
"attached_to_name":doc.name}, order_by="creation desc")
|
||||
|
||||
if fileid : fileid = fileid[0].name
|
||||
|
||||
if not fileid:
|
||||
msgprint(_("File not attached"))
|
||||
|
|
|
|||
|
|
@ -570,7 +570,7 @@ def get_url(uri=None, full_address=False):
|
|||
|
||||
if not host_name:
|
||||
if hasattr(frappe.local, "request") and frappe.local.request and frappe.local.request.host:
|
||||
protocol = 'https' == frappe.get_request_header('X-Forwarded-Proto', "") and 'https://' or 'http://'
|
||||
protocol = 'https://' if 'https' == frappe.get_request_header('X-Forwarded-Proto', "") else 'http://'
|
||||
host_name = protocol + frappe.local.request.host
|
||||
|
||||
elif frappe.local.site:
|
||||
|
|
@ -589,17 +589,22 @@ def get_url(uri=None, full_address=False):
|
|||
else:
|
||||
host_name = frappe.db.get_value("Website Settings", "Website Settings",
|
||||
"subdomain")
|
||||
if host_name and "http" not in host_name:
|
||||
host_name = "http://" + host_name
|
||||
|
||||
if not host_name:
|
||||
host_name = "http://localhost"
|
||||
|
||||
if host_name and not (host_name.startswith("http://") or host_name.startswith("https://")):
|
||||
host_name = "http://" + host_name
|
||||
|
||||
if not uri and full_address:
|
||||
uri = frappe.get_request_header("REQUEST_URI", "")
|
||||
|
||||
url = urllib.basejoin(host_name, uri) if uri else host_name
|
||||
|
||||
# add port if not added
|
||||
if frappe.conf.webserver_port and not url.rsplit(':', 1)[-1].isdigit():
|
||||
url = url + ':' + str(frappe.conf.webserver_port)
|
||||
|
||||
return url
|
||||
|
||||
def get_host_name():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue