diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a29e0badee..ef9d094d93 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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.
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 24dbb5030c..88bfbb7ddc 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -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):
diff --git a/frappe/__version__.py b/frappe/__version__.py
index 111dc9172a..ecdb1cef9e 100644
--- a/frappe/__version__.py
+++ b/frappe/__version__.py
@@ -1 +1 @@
-__version__ = "4.3.0"
+__version__ = "4.4.0"
diff --git a/frappe/core/doctype/customize_form/customize_form.py b/frappe/core/doctype/customize_form/customize_form.py
index 8f9f7f8a9d..04d74eb287 100644
--- a/frappe/core/doctype/customize_form/customize_form.py
+++ b/frappe/core/doctype/customize_form/customize_form.py
@@ -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)
diff --git a/frappe/core/doctype/customize_form/test_customize_form.py b/frappe/core/doctype/customize_form/test_customize_form.py
index 7a1e4e6b4d..db4115fb8b 100644
--- a/frappe/core/doctype/customize_form/test_customize_form.py
+++ b/frappe/core/doctype/customize_form/test_customize_form.py
@@ -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)
diff --git a/frappe/core/doctype/file_data/file_data.py b/frappe/core/doctype/file_data/file_data.py
index ae0971d79e..dd239d3c05 100644
--- a/frappe/core/doctype/file_data/file_data.py
+++ b/frappe/core/doctype/file_data/file_data.py
@@ -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:
diff --git a/frappe/core/doctype/notification_count/notification_count.py b/frappe/core/doctype/notification_count/notification_count.py
index 28bd0e2ea6..fbb8ae4718 100644
--- a/frappe/core/doctype/notification_count/notification_count.py
+++ b/frappe/core/doctype/notification_count/notification_count.py
@@ -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:
diff --git a/frappe/core/doctype/todo/todo_list.html b/frappe/core/doctype/todo/todo_list.html
index 18b9dea848..20692806ee 100644
--- a/frappe/core/doctype/todo/todo_list.html
+++ b/frappe/core/doctype/todo/todo_list.html
@@ -23,7 +23,7 @@
- {%= frappe.avatar(doc.assigned_to) %}
+ {%= frappe.avatar(doc.owner) %}
diff --git a/frappe/database.py b/frappe/database.py
index 4f72cfdf6c..d40bf90cb8 100644
--- a/frappe/database.py
+++ b/frappe/database.py
@@ -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':
diff --git a/frappe/hooks.py b/frappe/hooks.py
index d0b51a5da0..deddf426e8 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -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"
diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py
index aa4f4983ee..2b92a99b38 100644
--- a/frappe/model/create_new.py
+++ b/frappe/model/create_new.py
@@ -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]
diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py
index a0d56f31f0..eda985b30c 100644
--- a/frappe/model/delete_doc.py
+++ b/frappe/model/delete_doc.py
@@ -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)
diff --git a/frappe/model/document.py b/frappe/model/document.py
index 0993c19008..0097d392d6 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -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()
diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py
index f3d0a961f4..a4a8b73c09 100644
--- a/frappe/model/rename_doc.py
+++ b/frappe/model/rename_doc.py
@@ -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
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 6e8c2fa47e..1b9d2f143c 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -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
diff --git a/frappe/patches/v4_3/__init__.py b/frappe/patches/v4_3/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/patches/v4_3/remove_allow_on_submit_customization.py b/frappe/patches/v4_3/remove_allow_on_submit_customization.py
new file mode 100644
index 0000000000..0e6cd3a11e
--- /dev/null
+++ b/frappe/patches/v4_3/remove_allow_on_submit_customization.py
@@ -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)
diff --git a/frappe/public/build.json b/frappe/public/build.json
index 6c4b1acf11..f79dd07d7b 100644
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -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",
diff --git a/frappe/public/js/frappe/form/footer.js b/frappe/public/js/frappe/form/footer.js
index 0c06abd652..817509a4f6 100644
--- a/frappe/public/js/frappe/form/footer.js
+++ b/frappe/public/js/frappe/form/footer.js
@@ -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();
}
},
});
diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js
index dedc3c35af..a788ed79c7 100644
--- a/frappe/public/js/frappe/form/save.js
+++ b/frappe/public/js/frappe/form/save.js
@@ -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;
diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js
index 19fe0487de..5a63ca3dfa 100644
--- a/frappe/public/js/frappe/model/create_new.js
+++ b/frappe/public/js/frappe/model/create_new.js
@@ -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];
}
}
diff --git a/frappe/public/js/frappe/views/container.js b/frappe/public/js/frappe/views/container.js
index b391097e6a..363692a7e3 100644
--- a/frappe/public/js/frappe/views/container.js
+++ b/frappe/public/js/frappe/views/container.js
@@ -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;
- }
-})
diff --git a/frappe/public/js/frappe/views/factory.js b/frappe/public/js/frappe/views/factory.js
new file mode 100644
index 0000000000..73fa87ba11
--- /dev/null
+++ b/frappe/public/js/frappe/views/factory.js
@@ -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;
+ }
+});
diff --git a/frappe/public/js/frappe/views/ganttview.js b/frappe/public/js/frappe/views/ganttview.js
index f6c102db3f..03af1482d4 100644
--- a/frappe/public/js/frappe/views/ganttview.js
+++ b/frappe/public/js/frappe/views/ganttview.js
@@ -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",
diff --git a/frappe/public/js/frappe/views/query_report.js b/frappe/public/js/frappe/views/query_report.js
index b994ef829b..b7a0da4978 100644
--- a/frappe/public/js/frappe/views/query_report.js
+++ b/frappe/public/js/frappe/views/query_report.js
@@ -50,7 +50,7 @@ frappe.views.QueryReport = Class.extend({
\
'+__('For comparative filters, start with')+' ">" or "<", e.g. >5 or >01-02-2012\
'+__('For ranges')+' ('+__('values and dates')+') use ":", \
- e.g. "5:10" (to filter values between 5 & 10)
\
+ e.g. "5:10" (' + __("to filter values between 5 & 10") + ')\
').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 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
diff --git a/frappe/website/render.py b/frappe/website/render.py
index 17c9af9398..a3f01d56e6 100644
--- a/frappe/website/render.py
+++ b/frappe/website/render.py
@@ -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"):
diff --git a/frappe/widgets/query_report.py b/frappe/widgets/query_report.py
index 95b2ce9b64..e7e82f95ee 100644
--- a/frappe/widgets/query_report.py
+++ b/frappe/widgets/query_report.py
@@ -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):
diff --git a/setup.py b/setup.py
index 877718f0a6..a99b55fc12 100644
--- a/setup.py
+++ b/setup.py
@@ -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()