Merge branch 'develop'

This commit is contained in:
Rushabh Mehta 2016-08-10 14:28:01 +05:30
commit d2fb2a3867
24 changed files with 186 additions and 79 deletions

View file

@ -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.18"
__version__ = "7.0.19"
local = Local()

View file

@ -55,6 +55,8 @@ def application(request):
response = frappe.handler.handle()
elif frappe.request.path.startswith("/api/"):
if frappe.local.form_dict.data is None:
frappe.local.form_dict.data = request.get_data()
response = frappe.api.handle()
elif frappe.request.path.startswith('/backups'):

View file

@ -83,6 +83,29 @@ def insert(doc=None):
doc = frappe.get_doc(doc).insert()
return doc.as_dict()
@frappe.whitelist()
def insert_many(docs=None):
if isinstance(docs, basestring):
docs = json.loads(docs)
out = []
if len(docs) > 200:
frappe.throw(_('Only 200 inserts allowed in one request'))
for doc in docs:
if doc.get("parent") and doc.get("parenttype"):
# inserting a child record
parent = frappe.get_doc(doc.get("parenttype"), doc.get("parent"))
parent.append(doc.get("parentfield"), doc)
parent.save()
out.append(parent.name)
else:
doc = frappe.get_doc(doc).insert()
out.append(doc.name)
return out
@frappe.whitelist()
def save(doc):
if isinstance(doc, basestring):

View file

@ -3,7 +3,7 @@
from __future__ import unicode_literals
import re
import re, copy
import MySQLdb
import frappe
from frappe import _
@ -311,7 +311,7 @@ def validate_fields(meta):
frappe.throw(_("Max width for type Currency is 100px in row {0}").format(d.idx))
def check_in_list_view(d):
if d.in_list_view and (d.fieldtype in no_value_fields):
if d.in_list_view and (d.fieldtype in not_allowed_in_list_view):
frappe.throw(_("'In List View' not allowed for type {0} in row {1}").format(d.fieldtype, d.idx))
def check_dynamic_link_options(d):
@ -441,6 +441,10 @@ def validate_fields(meta):
frappe.throw(_("Timeline field must be a Link or Dynamic Link"), InvalidFieldNameError)
fields = meta.get("fields")
not_allowed_in_list_view = list(copy.copy(no_value_fields))
if meta.istable:
not_allowed_in_list_view.remove('Button')
for d in fields:
if not d.permlevel: d.permlevel = 0
if not d.fieldname:

View file

@ -36,7 +36,7 @@ class User(Document):
def onload(self):
self.set_onload('all_modules',
[m.module_name for m in frappe.db.get_all('Desktop Icon',
fields=['module_name'], filters={'standard': 1})])
fields=['module_name'], filters={'standard': 1}, order_by="module_name")])
def validate(self):
self.check_demo()

View file

@ -2,6 +2,13 @@
"db_name": "testdb",
"db_password": "password",
"mute_emails": true,
"limits": {
"emails": 1500,
"space": 0.157,
"expiry": "2016-07-25",
"users": 1
}
"developer_mode": 1,
"auto_cache_clear": true,

View file

@ -21,6 +21,8 @@ frappe.ui.form.on('Bulk Update', {
},
document_type: function(frm) {
// set field options
if(!frm.doc.document_type) return;
frappe.model.with_doctype(frm.doc.document_type, function() {
var options = $.map(frappe.get_meta(frm.doc.document_type).fields,
function(d) {

View file

@ -35,23 +35,15 @@ def check_user_tags(dt):
DocTags(dt).setup()
@frappe.whitelist()
def add_tag():
def add_tag(tag, dt, dn, color=None):
"adds a new tag to a record, and creates the Tag master"
f = frappe.local.form_dict
tag, color = f.get('tag'), f.get('color')
dt, dn = f.get('dt'), f.get('dn')
DocTags(dt).add(dn, tag)
return tag
@frappe.whitelist()
def remove_tag():
def remove_tag(tag, dt, dn):
"removes tag from the record"
f = frappe.local.form_dict
tag, dt, dn = f.get('tag'), f.get('dt'), f.get('dn')
DocTags(dt).remove(dn, tag)

View file

@ -1,5 +1,5 @@
email_defaults = {
"Gmail": {
frappe.email_defaults = {
"GMail": {
"email_server": "pop.gmail.com",
"use_ssl": 1,
"enable_outgoing": 1,
@ -33,8 +33,8 @@ email_defaults = {
},
};
email_defaults_imap = {
"Gmail": {
frappe.email_defaults_imap = {
"GMail": {
"email_server": "imap.gmail.com"
},
"Outlook.com": {
@ -51,11 +51,12 @@ email_defaults_imap = {
frappe.ui.form.on("Email Account", {
service: function(frm) {
$.each(email_defaults[frm.doc.service], function(key, value) {
console.log(frm.doc.service, frappe.email_defaults[frm.doc.service])
$.each(frappe.email_defaults[frm.doc.service], function(key, value) {
frm.set_value(key, value);
})
if (frm.doc.use_imap) {
$.each(email_defaults_imap[frm.doc.service], function(key, value) {
$.each(frappe.email_defaults_imap[frm.doc.service], function(key, value) {
frm.set_value(key, value);
});
}
@ -63,12 +64,12 @@ frappe.ui.form.on("Email Account", {
},
use_imap: function(frm) {
if (frm.doc.use_imap) {
$.each(email_defaults_imap[frm.doc.service], function(key, value) {
$.each(frappe.email_defaults_imap[frm.doc.service], function(key, value) {
frm.set_value(key, value);
});
}
else{
$.each(email_defaults[frm.doc.service], function(key, value) {
$.each(frappe.email_defaults[frm.doc.service], function(key, value) {
frm.set_value(key, value);
});
}

View file

@ -58,6 +58,12 @@ class FrappeClient(object):
data={"data":frappe.as_json(doc)}, verify=self.verify)
return self.post_process(res)
def insert_many(self, docs):
return self.post_request({
"cmd": "frappe.client.insert_many",
"docs": frappe.as_json(docs)
})
def update(self, doc):
url = self.url + "/api/resource/" + doc.get("doctype") + "/" + doc.get("name")
res = self.session.put(url, data={"data":frappe.as_json(doc)}, verify=self.verify)

View file

@ -361,13 +361,11 @@ def check_if_ready_for_barracuda():
def extract_sql_gzip(sql_gz_path):
try:
success = subprocess.check_output(['gzip', '-d', '-v', '-f', sql_gz_path])
subprocess.check_call(['gzip', '-d', '-v', '-f', sql_gz_path])
except:
raise
path = sql_gz_path[:-3] if success else None
return path
return sql_gz_path[:-3]
def extract_tar_files(site_name, file_path, folder_name):
# Need to do frappe.init to maintain the site locals

View file

@ -30,6 +30,9 @@ 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:

View file

@ -104,8 +104,19 @@
.grid-body .editable-row input[data-fieldtype="Currency"] {
text-align: right;
}
.grid-body .grid-static-col[data-fieldtype="Button"] .field-area {
margin-top: 5px;
margin-left: 5px;
}
.grid-body .grid-static-col[data-fieldtype="Button"] .field-area button {
height: 27px;
}
.grid-body .grid-static-col[data-fieldtype="Code"] .static-area {
margin-top: -10px;
margin-top: -5px;
}
.grid-body .grid-static-col[data-fieldtype="Code"] .static-area pre {
background: none;
border: none;
}
@media (max-width: 767px) {
.grid-body .btn-open-row {

View file

@ -238,6 +238,7 @@ table.field-info tr td {
border: 0px;
}
.image-field {
background-size: 100% 100% !important;
position: relative;
}
.image-field:hover .field-info {

View file

@ -134,7 +134,7 @@ body[data-route^="Module"] .main-menu .form-sidebar {
height: 0;
padding-bottom: 100%;
border-radius: 6px;
background-size: cover;
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center center;
}

View file

@ -291,12 +291,17 @@ frappe.ui.form.Grid = Class.extend({
this.frm.script_manager.trigger(this.df.fieldname + "_add", d.doctype, d.name);
this.refresh();
if(show && !this.allow_on_grid_editing()) {
if(show) {
if(idx) {
// always open inserted rows
this.wrapper.find("[data-idx='"+idx+"']").data("grid_row")
.toggle_view(true, callback);
} else {
this.wrapper.find(".grid-row:last").data("grid_row").toggle_view(true, callback);
if(!this.allow_on_grid_editing()) {
// open last row only if on-grid-editing is disabled
this.wrapper.find(".grid-row:last").data("grid_row")
.toggle_view(true, callback);
}
}
}
@ -820,50 +825,52 @@ frappe.ui.form.GridRow = Class.extend({
set_arrow_keys: function(field) {
var me = this;
field.$input.on('keydown', function(e) {
var values = me.frm.doc[me.grid.df.fieldname];
var fieldname = $(this).attr('data-fieldname');
// TAB
if(e.which==TAB) {
// last column
if(me.grid.wrapper.find('input:enabled:last').get(0)===this) {
setTimeout(function() {
if(me.doc.idx === values.length) {
// last row
me.grid.add_new_row(null, null, true);
me.grid.grid_rows[me.grid.grid_rows.length - 1].toggle_editable_row();
me.grid.set_focus_on_row();
} else {
me.grid.grid_rows[me.doc.idx].toggle_editable_row();
me.grid.set_focus_on_row(me.doc.idx+1);
}
}, 500);
if(field.$input) {
field.$input.on('keydown', function(e) {
var values = me.frm.doc[me.grid.df.fieldname];
var fieldname = $(this).attr('data-fieldname');
// TAB
if(e.which==TAB) {
// last column
if(me.grid.wrapper.find('input:enabled:last').get(0)===this) {
setTimeout(function() {
if(me.doc.idx === values.length) {
// last row
me.grid.add_new_row(null, null, true);
me.grid.grid_rows[me.grid.grid_rows.length - 1].toggle_editable_row();
me.grid.set_focus_on_row();
} else {
me.grid.grid_rows[me.doc.idx].toggle_editable_row();
me.grid.set_focus_on_row(me.doc.idx+1);
}
}, 500);
}
} else if(e.which==UP_ARROW) {
if(me.doc.idx > 1) {
var prev = me.grid.grid_rows[me.doc.idx-2];
prev.toggle_editable_row();
setTimeout(function() {
var input = prev.columns[fieldname].field.$input;
if(input) {
input.focus();
}
}, 400)
}
} else if(e.which==DOWN_ARROW) {
if(me.doc.idx < values.length) {
var next = me.grid.grid_rows[me.doc.idx];
next.toggle_editable_row();
setTimeout(function() {
var input = next.columns[fieldname].field.$input;
if(input) {
input.focus();
}
}, 400)
}
}
} else if(e.which==UP_ARROW) {
if(me.doc.idx > 1) {
var prev = me.grid.grid_rows[me.doc.idx-2];
prev.toggle_editable_row();
setTimeout(function() {
var input = prev.columns[fieldname].field.$input;
if(input) {
input.focus();
}
}, 400)
}
} else if(e.which==DOWN_ARROW) {
if(me.doc.idx < values.length) {
var next = me.grid.grid_rows[me.doc.idx];
next.toggle_editable_row();
setTimeout(function() {
var input = next.columns[fieldname].field.$input;
if(input) {
input.focus();
}
}, 400)
}
}
});
});
}
},
get_open_form: function() {

View file

@ -136,8 +136,22 @@
}
}
.grid-static-col[data-fieldtype="Button"] .field-area {
margin-top: 5px;
margin-left: 5px;
button {
height: 27px;
}
}
.grid-static-col[data-fieldtype="Code"] .static-area {
margin-top: -10px;
margin-top: -5px;
pre {
background: none;
border: none;
}
}
}

View file

@ -301,6 +301,7 @@ table.field-info tr td {
}
.image-field {
background-size: 100% 100% !important;
position: relative;
}

View file

@ -181,7 +181,7 @@ body[data-route^="Module"] .main-menu {
height: 0;
padding-bottom: 100%;
border-radius: 6px;
background-size: cover;
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center center;
}

33
frappe/tests/test_api.py Normal file
View file

@ -0,0 +1,33 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import unittest, frappe
from frappe.utils import get_url
class TestAPI(unittest.TestCase):
def test_insert_many(self):
from frappe.frappeclient import FrappeClient
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.insert_many([
{"doctype": "ToDo", "description": "Test API 1"},
{"doctype": "ToDo", "description": "Test API 2"},
{"doctype": "ToDo", "description": "Test API 3"},
])
self.assertTrue(frappe.db.get_value('ToDo', {'description': 'Test API 1'}))
self.assertTrue(frappe.db.get_value('ToDo', {'description': 'Test API 2'}))
self.assertTrue(frappe.db.get_value('ToDo', {'description': 'Test API 3'}))

View file

@ -11,7 +11,7 @@ class TestDB(unittest.TestCase):
def test_get_value(self):
self.assertEquals(frappe.db.get_value("User", {"name": ["=", "Administrator"]}), "Administrator")
self.assertEquals(frappe.db.get_value("User", {"name": ["like", "Admin%"]}), "Administrator")
self.assertEquals(frappe.db.get_value("User", {"name": ["!=", "Guest"]}), "Administrator")
self.assertNotEquals(frappe.db.get_value("User", {"name": ["!=", "Guest"]}), "Guest")
self.assertEquals(frappe.db.get_value("User", {"name": ["<", "B"]}), "Administrator")
self.assertEquals(frappe.db.get_value("User", {"name": ["<=", "Administrator"]}), "Administrator")

View file

@ -17,7 +17,6 @@ def test_timeout():
time.sleep(100)
class TestScheduler(TestCase):
def setUp(self):
frappe.db.set_global('enabled_scheduler_events', "")
@ -63,10 +62,8 @@ class TestScheduler(TestCase):
def test_restrict_scheduler_events(self):
frappe.set_user("Administrator")
user = frappe.get_doc("User", "Administrator")
dormant_date = add_days(today(), -5)
user.last_active = dormant_date
user.save()
frappe.db.sql('update tabUser set last_active=%s', dormant_date)
restrict_scheduler_events_if_dormant()
frappe.local.conf = _dict(frappe.get_site_config())

View file

@ -114,6 +114,10 @@ def now_datetime():
dt = convert_utc_to_user_timezone(datetime.datetime.utcnow())
return dt.replace(tzinfo=None)
def get_eta(from_time, percent_complete):
diff = time_diff(now_datetime(), from_time).total_seconds()
return str(datetime.timedelta(seconds=(100 - percent_complete) / percent_complete * diff))
def _get_time_zone():
return frappe.db.get_system_setting('time_zone') or 'Asia/Kolkata'

View file

@ -35,3 +35,4 @@ rq
schedule
cryptography
zxcvbn
psutil