Merge branch 'develop'

This commit is contained in:
Pratik Vyas 2014-09-16 15:56:25 +05:30
commit 00ae80134f
30 changed files with 146 additions and 69 deletions

View file

@ -1,5 +1,9 @@
# Contributing to Frappe / ERPNext
### Update 16-Sep-14
Please send pull requests to branch v5.0
## Reporting issues
We only accept issues that are bug reports or feature requests. Bugs must be isolated and reproducible problems. Please read the following guidelines before opening any issue.

View file

@ -549,9 +549,15 @@ def import_doc(path, ignore_links=False, ignore_insert=False, insert=False):
from frappe.core.page.data_import_tool import data_import_tool
data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert)
def copy_doc(doc):
def copy_doc(doc, ignore_no_copy=True):
""" No_copy fields also get copied."""
import copy
def remove_no_copy_fields(d):
for df in d.meta.get("fields", {"no_copy": 1}):
if hasattr(d, df.fieldname):
d.set(df.fieldname, None)
if not isinstance(doc, dict):
d = doc.as_dict()
else:
@ -564,12 +570,18 @@ def copy_doc(doc):
newdoc.creation = None
newdoc.amended_from = None
newdoc.amendment_date = None
if not ignore_no_copy:
remove_no_copy_fields(newdoc)
for d in newdoc.get_all_children():
d.name = None
d.parent = None
d.set("__islocal", 1)
d.owner = None
d.creation = None
if not ignore_no_copy:
remove_no_copy_fields(d)
return newdoc
def compare(val1, condition, val2):

View file

@ -1 +1 @@
__version__ = "4.3.0"
__version__ = "4.4.0"

View file

@ -117,6 +117,11 @@ class CustomizeForm(Document):
if property == "fieldtype":
self.validate_fieldtype_change(df, meta_df[0].get(property), df.get(property))
elif property == "allow_on_submit" and df.get(property):
frappe.msgprint(_("Row {0}: Not allowed to enable Allow on Submit for standard fields")\
.format(df.idx))
continue
self.make_property_setter(property=property, value=df.get(property),
property_type=self.docfield_properties[property], fieldname=df.fieldname)

View file

@ -159,3 +159,17 @@ class TestCustomizeForm(unittest.TestCase):
frappe.local.test_objects["Property Setter"] = []
make_test_records_for_doctype("Property Setter")
def test_set_allow_on_submit(self):
d = self.get_customize_form("User")
d.get("customize_form_fields", {"fieldname": "first_name"})[0].allow_on_submit = 1
d.get("customize_form_fields", {"fieldname": "test_custom_field"})[0].allow_on_submit = 1
d.run_method("save_customization")
d = self.get_customize_form("User")
# don't allow for standard fields
self.assertEquals(d.get("customize_form_fields", {"fieldname": "first_name"})[0].allow_on_submit or 0, 0)
# allow for custom field
self.assertEquals(d.get("customize_form_fields", {"fieldname": "test_custom_field"})[0].allow_on_submit, 1)

View file

@ -17,7 +17,7 @@ class FileData(Document):
def before_insert(self):
frappe.local.rollback_observers.append(self)
def on_update(self):
def validate(self):
if not getattr(self, "ignore_duplicate_entry_error", False):
# check duplicate assignement
n_records = frappe.db.sql("""select name from `tabFile Data`
@ -28,8 +28,7 @@ class FileData(Document):
self.attached_to_name))
if len(n_records) > 0:
self.duplicate_entry = n_records[0][0]
frappe.msgprint(frappe._("Same file has already been attached to the record"))
raise frappe.DuplicateEntryError
frappe.throw(frappe._("Same file has already been attached to the record"), frappe.DuplicateEntryError)
def on_trash(self):
if self.attached_to_name:

View file

@ -89,7 +89,15 @@ def clear_notifications(user=None):
def delete_notification_count_for(doctype):
if frappe.flags.in_import: return
frappe.db.sql("""delete from `tabNotification Count` where for_doctype = %s""", (doctype,))
try:
frappe.db.sql("""delete from `tabNotification Count` where for_doctype = %s""", (doctype,))
except MySQLdb.OperationalError, e:
if e.args[0] != 1213:
raise
logger.error("Deadlock")
def clear_doctype_notifications(doc, method=None, *args, **kwargs):
if frappe.flags.in_import:

View file

@ -23,7 +23,7 @@
</div>
<div class="col-xs-1 text-right" style="margin-bottom: 3px;">
<span class="filterable" data-filter="owner,=,{%= doc.owner %}">
{%= frappe.avatar(doc.assigned_to) %}
{%= frappe.avatar(doc.owner) %}
</span>
</div>
</div>

View file

@ -6,6 +6,7 @@
from __future__ import unicode_literals
import MySQLdb
from markdown2 import UnicodeWithAttrs
import warnings
import datetime
import frappe
@ -48,6 +49,7 @@ class Database:
use_unicode=True, charset='utf8')
self._conn.converter[246]=float
self._conn.converter[12]=get_datetime
self._conn.encoders[UnicodeWithAttrs] = self._conn.encoders[unicode]
self._cursor = self._conn.cursor()
if self.user != 'root':

View file

@ -1,9 +1,9 @@
app_name = "frappe"
app_title = "Frappe Framework"
app_publisher = "Web Notes Technologies Pvt. Ltd."
app_description = "Full Stack Web Application Framwork in Python"
app_description = "Full Stack Web Application Framework in Python"
app_icon = "assets/frappe/images/frappe.svg"
app_version = "4.3.0"
app_version = "4.4.0"
app_color = "#3498db"
app_email = "support@frappe.io"

View file

@ -89,5 +89,5 @@ def get_default_value(df, defaults, user_permissions, parent_doc):
elif df.fieldtype == "Time":
return nowtime()
elif (df.fieldtype == "Select" and df.options and df.options != "[Select]"):
elif (df.fieldtype == "Select" and df.options and df.options not in ("[Select]", "Loading...")):
return df.options.split("\n")[0]

View file

@ -140,7 +140,7 @@ def check_if_doc_is_dynamically_linked(doc):
if frappe.get_meta(df.parent).issingle:
# dynamic link in single doc
refdoc = frappe.get_singles_dict(df.parent)
refdoc = frappe.db.get_singles_dict(df.parent)
if refdoc.get(df.options)==doc.doctype and refdoc.get(df.fieldname)==doc.name:
frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype,
doc.name, df.parent, ""), frappe.LinkExistsError)

View file

@ -80,7 +80,13 @@ class Document(BaseDocument):
def load_from_db(self):
if not getattr(self, "_metaclass", False) and self.meta.issingle:
self.update(frappe.db.get_singles_dict(self.doctype))
single_doc = frappe.db.get_singles_dict(self.doctype)
if not single_doc:
single_doc = frappe.new_doc(self.doctype).as_dict()
single_doc["name"] = self.doctype
del single_doc["__islocal"]
self.update(single_doc)
self.init_valid_columns()
self._fix_numeric_types()

View file

@ -299,7 +299,7 @@ def rename_dynamic_links(doctype, old, new):
# dynamic link in single, just one value to check
if frappe.get_meta(df.parent).issingle:
refdoc = frappe.get_singles_dict(df.parent)
refdoc = frappe.db.get_singles_dict(df.parent)
if refdoc.get(df.options)==doctype and refdoc.get(df.fieldname)==old:
frappe.db.sql("""update tabSingles set value=%s where

View file

@ -54,3 +54,4 @@ execute:frappe.reload_doc('website', 'doctype', 'web_form') #2014-09-04
execute:frappe.reload_doc('website', 'doctype', 'web_form_field') #2014-09-04
frappe.patches.v4_2.refactor_website_routing
frappe.patches.v4_2.set_assign_in_doc
frappe.patches.v4_3.remove_allow_on_submit_customization

View file

View file

@ -0,0 +1,11 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
for d in frappe.get_list("Property Setter", fields=["name", "doc_type"],
filters={"doctype_or_field": "DocField", "property": "allow_on_submit", "value": "1"}, ignore_permissions=True):
frappe.delete_doc("Property Setter", d.name)
frappe.clear_cache(doctype=d.doc_type)

View file

@ -107,6 +107,7 @@
"public/js/frappe/ui/tags.js",
"public/js/frappe/views/container.js",
"public/js/frappe/views/factory.js",
"public/js/frappe/views/pageview.js",
"public/js/frappe/views/doclistview.js",
"public/js/frappe/views/sidebar_stats.js",

View file

@ -55,6 +55,11 @@ frappe.ui.form.Footer = Class.extend({
},
make_tags: function() {
if (this.frm.meta.issingle) {
this.wrapper.find(".form-tags").toggle(false);
return;
}
this.frm.tags = new frappe.ui.TagEditor({
parent: this.wrapper.find(".tag-area"),
frm: this.frm,
@ -86,7 +91,7 @@ frappe.ui.form.Footer = Class.extend({
this.frm.attachments.refresh();
this.frm.comments.refresh();
this.frm.assign_to.refresh();
this.frm.tags.refresh();
this.frm.tags && this.frm.tags.refresh();
}
},
});

View file

@ -74,7 +74,7 @@ frappe.ui.form.save = function(frm, action, callback, btn) {
if(df.reqd && !frappe.model.has_value(doc.doctype, doc.name, df.fieldname)) {
has_errors = true;
error_fields[error_fields.length] = df.label;
error_fields[error_fields.length] = __(df.label);
// scroll to field
if(!me.scroll_set) {
@ -91,8 +91,8 @@ frappe.ui.form.save = function(frm, action, callback, btn) {
});
if(error_fields.length)
msgprint(__('Mandatory fields required in {0}', [(doc.parenttype
? (frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label + ' (Table)')
: doc.doctype)]) + '\n' + error_fields.join('\n'));
? (__(frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label) + ' ('+ __("Table") + ')')
: __(doc.doctype))]) + '\n' + error_fields.join('\n'));
});
return !has_errors;

View file

@ -66,7 +66,7 @@ $.extend(frappe.model, {
doc[f.fieldname] = v;
updated.push(f.fieldname);
} else if(f.fieldtype == "Select" && f.options
&& f.options!="[Select]") {
&& !in_list(["[Select]", "Loading..."], f.options)) {
doc[f.fieldname] = f.options.split("\n")[0];
}
}

View file

@ -73,34 +73,3 @@ frappe.views.Container = Class.extend({
return this.page;
}
});
frappe.views.Factory = Class.extend({
init: function(opts) {
$.extend(this, opts);
},
show: function() {
var page_name = frappe.get_route_str(),
me = this;
if(frappe.pages[page_name]) {
frappe.container.change_to(frappe.pages[page_name]);
} else {
var route = frappe.get_route();
if(route[1]) {
me.make(route);
} else {
frappe.show_not_found(route);
}
}
},
make_page: function(double_column) {
var page_name = frappe.get_route_str(),
page = frappe.container.add_page(page_name);
frappe.ui.make_app_page({
parent: page,
single_column: !double_column
});
frappe.container.change_to(page_name);
return page;
}
})

View file

@ -0,0 +1,36 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.provide('frappe.pages');
frappe.provide('frappe.views');
frappe.views.Factory = Class.extend({
init: function(opts) {
$.extend(this, opts);
},
show: function() {
var page_name = frappe.get_route_str(),
me = this;
if(frappe.pages[page_name] && page_name.indexOf("Form/")===-1) {
frappe.container.change_to(frappe.pages[page_name]);
} else {
var route = frappe.get_route();
if(route[1]) {
me.make(route);
} else {
frappe.show_not_found(route);
}
}
},
make_page: function(double_column) {
var page_name = frappe.get_route_str(),
page = frappe.container.add_page(page_name);
frappe.ui.make_app_page({
parent: page,
single_column: !double_column
});
frappe.container.change_to(page_name);
return page;
}
});

View file

@ -44,7 +44,7 @@ frappe.views.Gantt = Class.extend({
this.appframe.add_module_icon(module)
this.appframe.set_views_for(this.doctype, "gantt");
this.appframe.set_title_right("Refresh",
this.appframe.set_title_right(__("Refresh"),
function() { me.refresh(); }, "icon-refresh")
this.appframe.add_field({fieldtype:"Date", label:"From",

View file

@ -50,7 +50,7 @@ frappe.views.QueryReport = Class.extend({
<p class="text-muted"><br>\
'+__('For comparative filters, start with')+' ">" or "<", e.g. >5 or >01-02-2012\
<br>'+__('For ranges')+' ('+__('values and dates')+') use ":", \
e.g. "5:10" (to filter values between 5 & 10)</p>\
e.g. "5:10" (' + __("to filter values between 5 & 10") + ')</p>\
</div>').appendTo(this.wrapper);
this.make_toolbar();
@ -148,10 +148,7 @@ frappe.views.QueryReport = Class.extend({
var data = [];
// get filtered data
for (var i=0, l=me.dataView.getLength(); i<l; i++) {
var d = me.dataView.getItem(i);
var newd = {}; data.push(newd);
$.each(d, function(k, v) {
newd[k.replace(/ /g, "_").toLowerCase()] = v; });
data.push(me.dataView.getItem(i));
}
var content = frappe.render(html_format,
@ -235,7 +232,7 @@ frappe.views.QueryReport = Class.extend({
// Run
var me = this;
this.waiting = frappe.messages.waiting(this.wrapper.find(".waiting-area").empty().toggle(true),
"Loading Report...");
__("Loading Report") + "...");
this.wrapper.find(".results").toggle(false);
var filters = this.get_values(true);
@ -326,7 +323,7 @@ frappe.views.QueryReport = Class.extend({
var me = this;
var formatter = this.get_formatter();
this.columns = [{id: "_id", field: "_id", name: "Sr No", width: 60}]
this.columns = [{id: "_id", field: "_id", name: __("Sr No"), width: 60}]
.concat($.map(columns, function(c) {
if ($.isPlainObject(c)) {
var df = c;
@ -360,7 +357,7 @@ frappe.views.QueryReport = Class.extend({
formatter: formatter
});
col.field = df.fieldname || df.label.replace(/ /g, "_");
col.field = df.fieldname || df.label;
df.label = __(df.label);
col.name = col.id = col.label = df.label;

View file

@ -231,7 +231,7 @@ def get_messages_from_report(name):
frappe.db.get_value("DocType", report.ref_doctype, "module"))
if report.query:
messages.extend(re.findall('"([^:,^"]*):', report.query))
messages.append(report.report_name)
messages.append(report.report_name)
return clean(messages)
def get_messages_from_page_or_report(doctype, name, module=None):
@ -240,6 +240,7 @@ def get_messages_from_page_or_report(doctype, name, module=None):
file_path = frappe.get_module_path(module, doctype, name, name)
messages = get_messages_from_file(file_path + ".js")
messages += get_messages_from_file(file_path + ".html")
messages += get_messages_from_file(file_path + ".py")
return clean(messages)

View file

@ -117,12 +117,12 @@ def save_file(fname, content, dt, dn, decode=False):
content_type = mimetypes.guess_type(fname)[0]
fname = get_file_name(fname, content_hash[-6:])
method = get_hook_method('write_file', fallback=save_file_on_filesystem)
file_data = get_file_data_from_hash(content_hash)
if not file_data:
method = get_hook_method('write_file', fallback=save_file_on_filesystem)
file_data = method(fname, content, content_type=content_type)
file_data = copy(file_data)
file_data.update({
"doctype": "File Data",
"attached_to_doctype": dt,
@ -254,9 +254,9 @@ def get_file_name(fname, optional_suffix):
if len(n_records) > 0 or os.path.exists(get_files_path(fname)):
f = fname.rsplit('.', 1)
if len(f) == 1:
partial, extn = f[0], None
elif len(f) == 2:
partial, extn = f
return '{partial}{suffix}{extn}'.format(partial=partial, extn="."+extn if extn else "", suffix=optional_suffix)
partial, extn = f[0], ""
else:
partial, extn = f[0], "." + f[1]
return '{partial}{suffix}{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix)
return fname

View file

@ -171,6 +171,7 @@ def clear_cache(path=None):
else:
clear_sitemap()
frappe.clear_cache("Guest")
frappe.cache().delete_value("_website_pages")
clear_permissions()
for method in frappe.get_hooks("website_clear_cache"):

View file

@ -86,7 +86,7 @@ def run(report_name, filters=()):
method_name = get_report_module_dotted_path(module, report.name) + ".execute"
columns, result = frappe.get_attr(method_name)(frappe._dict(filters))
if report.apply_user_permissions:
if report.apply_user_permissions and result:
result = get_filtered_data(report.ref_doctype, columns, result)
if cint(report.add_total_row) and result:
@ -138,7 +138,7 @@ def add_total_row(result, columns):
def get_filtered_data(ref_doctype, columns, data):
result = []
linked_doctypes = get_linked_doctypes(columns)
linked_doctypes = get_linked_doctypes(columns, data)
match_filters_per_doctype = get_user_match_filters(linked_doctypes, ref_doctype)
if match_filters_per_doctype:
@ -183,7 +183,7 @@ def has_match(row, linked_doctypes, doctype_match_filters):
return resultant_match
def get_linked_doctypes(columns):
def get_linked_doctypes(columns, data):
linked_doctypes = {}
for idx, col in enumerate(columns):
@ -197,6 +197,11 @@ def get_linked_doctypes(columns):
elif col.get("fieldtype")=="Link" and col.get("options"):
linked_doctypes[col["options"]] = col["fieldname"]
# remove doctype if column is empty
for doctype, key in linked_doctypes.items():
if not any(d[key] for d in data if d):
del linked_doctypes[doctype]
return linked_doctypes
def get_user_match_filters(doctypes, ref_doctype):

View file

@ -1,7 +1,7 @@
from setuptools import setup, find_packages
import os
version = "4.3.0"
version = "4.4.0"
with open("requirements.txt", "r") as f:
install_requires = f.readlines()