diff --git a/frappe/__init__.py b/frappe/__init__.py index ee8f544f18..f1633d21b6 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template -__version__ = '8.1.2' +__version__ = '8.1.3' __title__ = "Frappe Framework" local = Local() @@ -139,6 +139,7 @@ def init(site, sites_path=None, new_site=False): local.module_app = None local.app_modules = None local.system_settings = None + local.system_country = None local.user = None local.user_perms = None @@ -946,6 +947,7 @@ def make_property_setter(args, ignore_validate=False, validate_fields_for_doctyp }) ps.flags.ignore_validate = ignore_validate ps.flags.validate_fields_for_doctype = validate_fields_for_doctype + ps.validate_fieldtype_change() ps.insert() def import_doc(path, ignore_links=False, ignore_insert=False, insert=False): @@ -1360,4 +1362,9 @@ def get_active_domains(): active_domains.append("") cache().hset("domains", "active_domains", active_domains) - return active_domains \ No newline at end of file + return active_domains + +def get_system_country(): + if local.system_country is None: + local.system_country = db.get_single_value('System Settings', 'country') or '' + return local.system_country diff --git a/frappe/contacts/doctype/address/address.json b/frappe/contacts/doctype/address/address.json index a3c8bd57d4..c3b655cec5 100644 --- a/frappe/contacts/doctype/address/address.json +++ b/frappe/contacts/doctype/address/address.json @@ -12,6 +12,7 @@ "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -41,11 +42,12 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "description": "Name of person or organization that this address belongs to.", + "description": "", "fieldname": "address_title", "fieldtype": "Data", "hidden": 0, @@ -70,6 +72,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -99,6 +102,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -127,6 +131,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -155,6 +160,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -183,6 +189,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -212,6 +219,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -240,6 +248,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -269,6 +278,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -297,6 +307,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -325,6 +336,7 @@ "width": "50%" }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -353,6 +365,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -381,6 +394,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -409,6 +423,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -439,6 +454,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -469,6 +485,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -498,6 +515,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -528,6 +546,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -569,7 +588,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-04-10 13:09:45.030542", + "modified": "2017-06-21 11:30:20.719590", "modified_by": "Administrator", "module": "Contacts", "name": "Address", diff --git a/frappe/contacts/doctype/contact/contact.json b/frappe/contacts/doctype/contact/contact.json index a9948e00d9..d40866b290 100644 --- a/frappe/contacts/doctype/contact/contact.json +++ b/frappe/contacts/doctype/contact/contact.json @@ -13,6 +13,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -42,36 +43,7 @@ "unique": 0 }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "salutation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Salutation", - "length": 0, - "no_copy": 0, - "options": "Salutation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -102,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, @@ -132,6 +105,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, @@ -163,6 +137,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -193,6 +168,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -220,6 +196,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -250,6 +227,38 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "salutation", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Salutation", + "length": 0, + "no_copy": 0, + "options": "Salutation", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -280,6 +289,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, @@ -310,6 +320,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 1, "collapsible": 0, @@ -340,6 +351,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -369,6 +381,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -398,6 +411,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -430,6 +444,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -460,6 +475,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -489,6 +505,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -518,6 +535,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -547,6 +565,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -575,6 +594,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -615,7 +635,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-04-10 13:09:27.880530", + "modified": "2017-06-21 17:17:44.694188", "modified_by": "Administrator", "module": "Contacts", "name": "Contact", diff --git a/frappe/core/doctype/custom_role/custom_role.py b/frappe/core/doctype/custom_role/custom_role.py index 2e183bc205..25257e1a23 100644 --- a/frappe/core/doctype/custom_role/custom_role.py +++ b/frappe/core/doctype/custom_role/custom_role.py @@ -7,7 +7,9 @@ import frappe from frappe.model.document import Document class CustomRole(Document): - pass + def validate(self): + if self.report and not self.ref_doctype: + self.ref_doctype = frappe.db.get_value('Report', self.report, 'ref_doctype') def get_custom_allowed_roles(field, name): allowed_roles = [] diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index ea7d87b986..60cf53ef22 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -623,10 +623,24 @@ def validate_permissions_for_doctype(doctype, for_remove=False): for perm in doctype.get("permissions"): perm.db_update() + clear_permissions_cache(doctype.name) + +def clear_permissions_cache(doctype): + frappe.clear_cache(doctype=doctype) + delete_notification_count_for(doctype) + for user in frappe.db.sql_list("""select + distinct `tabHas Role`.parent + from + `tabHas Role`, + tabDocPerm + where tabDocPerm.parent = %s + and tabDocPerm.role = `tabHas Role`.role""", doctype): + frappe.clear_cache(user=user) + def validate_permissions(doctype, for_remove=False): permissions = doctype.get("permissions") if not permissions: - frappe.throw(_('Enter at least one permission row'), frappe.MandatoryError) + frappe.msgprint(_('No Permissions Specified'), alert=True, indicator='orange') issingle = issubmittable = isimportable = False if doctype: issingle = cint(doctype.issingle) diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 143a0cf6b9..6151890183 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -39,6 +39,7 @@ class Report(Document): if self.report_type == "Report Builder": self.update_report_json() + def before_insert(self): self.set_doctype_roles() def on_update(self): @@ -48,11 +49,10 @@ class Report(Document): delete_custom_role('report', self.name) def set_doctype_roles(self): - if self.get('roles'): return - - doc = frappe.get_meta(self.ref_doctype) - roles = [{'role': d.role} for d in doc.permissions if d.permlevel==0] - self.set('roles', roles) + if not self.get('roles'): + meta = frappe.get_meta(self.ref_doctype) + roles = [{'role': d.role} for d in meta.permissions if d.permlevel==0] + self.set('roles', roles) def is_permitted(self): """Returns true if Has Role is not set or the user is allowed.""" @@ -153,7 +153,7 @@ class Report(Document): for c in columns] out = out + [list(d) for d in result] - + if as_dict: data = [] for row in out: diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 3f787a42fc..626bb1c20d 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -6,8 +6,10 @@ import frappe import frappe.defaults from frappe.modules.import_file import get_file_path, read_doc_from_file from frappe.translate import send_translations -from frappe.desk.notifications import delete_notification_count_for -from frappe.permissions import reset_perms, get_linked_doctypes, get_all_perms, setup_custom_perms +from frappe.permissions import (reset_perms, get_linked_doctypes, get_all_perms, + setup_custom_perms, add_permission) +from frappe.core.doctype.doctype.doctype import (clear_permissions_cache, + validate_permissions_for_doctype) from frappe import _ @frappe.whitelist() @@ -61,20 +63,7 @@ def get_permissions(doctype=None, role=None): @frappe.whitelist() def add(parent, role, permlevel): frappe.only_for("System Manager") - setup_custom_perms(parent) - - frappe.get_doc({ - "doctype":"Custom DocPerm", - "__islocal": 1, - "parent": parent, - "parenttype": "DocType", - "parentfield": "permissions", - "role": role, - "permlevel": permlevel, - "read": 1 - }).save() - - validate_and_reset(parent) + add_permission(parent, role, permlevel) @frappe.whitelist() def update(doctype, role, permlevel, ptype, value=None): @@ -88,7 +77,7 @@ def update(doctype, role, permlevel, ptype, value=None): frappe.db.sql("""update `tabCustom DocPerm` set `%s`=%s where name=%s"""\ % (frappe.db.escape(ptype), '%s', '%s'), (value, name)) - validate_and_reset(doctype) + validate_permissions_for_doctype(doctype) return out @@ -103,27 +92,14 @@ def remove(doctype, role, permlevel): if not frappe.get_all('Custom DocPerm', dict(parent=doctype)): frappe.throw(_('There must be atleast one permission rule.'), title=_('Cannot Remove')) - validate_and_reset(doctype, for_remove=True) - -def validate_and_reset(doctype, for_remove=False): - from frappe.core.doctype.doctype.doctype import validate_permissions_for_doctype - validate_permissions_for_doctype(doctype, for_remove) - clear_doctype_cache(doctype) + validate_permissions_for_doctype(doctype, for_remove=True) @frappe.whitelist() def reset(doctype): frappe.only_for("System Manager") reset_perms(doctype) - clear_doctype_cache(doctype) - -def clear_doctype_cache(doctype): - frappe.clear_cache(doctype=doctype) - delete_notification_count_for(doctype) - for user in frappe.db.sql_list("""select distinct `tabHas Role`.parent from `tabHas Role`, - tabDocPerm - where tabDocPerm.parent = %s - and tabDocPerm.role = `tabHas Role`.role""", doctype): - frappe.clear_cache(user=user) + clear_permissions_cache(doctype) + @frappe.whitelist() def get_users_with_role(role): diff --git a/frappe/core/report/todo/__init__.py b/frappe/core/report/todo/__init__.py deleted file mode 100644 index 0e57cb68c3..0000000000 --- a/frappe/core/report/todo/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 7265cee8a6..57ea7a2b0b 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -73,7 +73,8 @@ class CustomField(Document): @frappe.whitelist() def get_fields_label(doctype=None): - return [{"value": df.fieldname or "", "label": _(df.label or "")} for df in frappe.get_meta(doctype).get("fields")] + return [{"value": df.fieldname or "", "label": _(df.label or "")} + for df in frappe.get_meta(doctype).get("fields")] def create_custom_field_if_values_exist(doctype, df): df = frappe._dict(df) diff --git a/frappe/custom/doctype/property_setter/property_setter.py b/frappe/custom/doctype/property_setter/property_setter.py index 997d8443a1..070001fe02 100644 --- a/frappe/custom/doctype/property_setter/property_setter.py +++ b/frappe/custom/doctype/property_setter/property_setter.py @@ -3,9 +3,12 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document +not_allowed_fieldtype_change = ['naming_series'] + class PropertySetter(Document): def autoname(self): self.name = self.doc_type + "-" \ @@ -13,6 +16,18 @@ class PropertySetter(Document): + self.property def validate(self): + self.validate_fieldtype_change() + self.delete_property_setter() + + # clear cache + frappe.clear_cache(doctype = self.doc_type) + + def validate_fieldtype_change(self): + if self.field_name in not_allowed_fieldtype_change and \ + self.property == 'fieldtype': + frappe.throw(_("Field type cannot be changed for {0}").format(self.field_name)) + + def delete_property_setter(self): """delete other property setters on this, if this is new""" if self.get('__islocal'): frappe.db.sql("""delete from `tabProperty Setter` where @@ -21,9 +36,6 @@ class PropertySetter(Document): and ifnull(field_name,'') = ifnull(%(field_name)s, '') and property = %(property)s""", self.get_valid_dict()) - # clear cache - frappe.clear_cache(doctype = self.doc_type) - def get_property_list(self, dt): return frappe.db.sql("""select fieldname, label, fieldtype from tabDocField diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index 310193c5f8..48cacab3f6 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -65,7 +65,11 @@ class FormMeta(Meta): def _get_path(fname): return os.path.join(path, scrub(fname)) + system_country = frappe.get_system_country() + self._add_code(_get_path(self.name + '.js'), '__js') + if system_country: + self._add_code(_get_path(os.path.join('regional', system_country + '.js')), '__js') self._add_code(_get_path(self.name + '.css'), "__css") self._add_code(_get_path(self.name + '_list.js'), '__list_js') self._add_code(_get_path(self.name + '_calendar.js'), '__calendar_js') diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index dcc9e2bcd8..9a11894ce0 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -86,6 +86,11 @@ def update_user_name(args): first_name, last_name = first_name.split(' ', 1) if args.get("email"): + if frappe.db.exists('User', args.get('email')): + # running again + return + + args['name'] = args.get("email") _mute_emails, frappe.flags.mute_emails = frappe.flags.mute_emails, True diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index cecd1bdbd9..3d743f77da 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -9,6 +9,7 @@ import os, json from frappe import _ from frappe.modules import scrub, get_module_path from frappe.utils import flt, cint, get_html_format, cstr +from frappe.model.utils import render_include from frappe.translate import send_translations import frappe.desk.reportview from frappe.permissions import get_role_permissions @@ -55,7 +56,7 @@ def get_script(report_name): send_translations(frappe.get_lang_dict("report", report_name)) return { - "script": script, + "script": render_include(script), "html_format": html_format } @@ -104,7 +105,7 @@ def run(report_name, filters=None, user=None): if cint(report.add_total_row) and result: result = add_total_row(result, columns) - + return { "result": result, "columns": columns, @@ -145,7 +146,7 @@ def export_query(): # add column headings for idx in range(len(data.columns)): result[0].append(columns[idx]["label"]) - + # build table from dict if isinstance(data.result[0], dict): for i,row in enumerate(data.result): @@ -381,4 +382,4 @@ def get_user_match_filters(doctypes, ref_doctype): if filter_list: match_filters[dt] = filter_list - return match_filters + return match_filters \ No newline at end of file diff --git a/frappe/desk/report/__init__.py b/frappe/desk/report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/report/todo/__init__.py b/frappe/desk/report/todo/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/report/todo/todo.js b/frappe/desk/report/todo/todo.js similarity index 88% rename from frappe/core/report/todo/todo.js rename to frappe/desk/report/todo/todo.js index 805991cc40..bb2e0f7846 100644 --- a/frappe/core/report/todo/todo.js +++ b/frappe/desk/report/todo/todo.js @@ -1,5 +1,6 @@ // Copyright (c) 2016, Frappe Technologies and contributors // For license information, please see license.txt +/* eslint-disable */ frappe.query_reports["ToDo"] = { "filters": [ diff --git a/frappe/core/report/todo/todo.json b/frappe/desk/report/todo/todo.json similarity index 86% rename from frappe/core/report/todo/todo.json rename to frappe/desk/report/todo/todo.json index 18ff072c23..b42c4c9c67 100644 --- a/frappe/core/report/todo/todo.json +++ b/frappe/desk/report/todo/todo.json @@ -7,9 +7,9 @@ "doctype": "Report", "idx": 3, "is_standard": "Yes", - "modified": "2017-02-24 20:13:12.217943", + "modified": "2017-06-21 18:18:50.748793", "modified_by": "Administrator", - "module": "Core", + "module": "Desk", "name": "ToDo", "owner": "Administrator", "ref_doctype": "ToDo", diff --git a/frappe/core/report/todo/todo.py b/frappe/desk/report/todo/todo.py similarity index 98% rename from frappe/core/report/todo/todo.py rename to frappe/desk/report/todo/todo.py index 8757c05d05..a51d44fe08 100644 --- a/frappe/core/report/todo/todo.py +++ b/frappe/desk/report/todo/todo.py @@ -31,5 +31,4 @@ def execute(filters=None): result.append([todo.name, todo.priority, todo.date, todo.description, todo.owner, todo.assigned_by, todo.reference]) - return columns, result - + return columns, result \ No newline at end of file diff --git a/frappe/docs/index.html b/frappe/docs/index.html index 73d69f96ea..a70cba3109 100644 --- a/frappe/docs/index.html +++ b/frappe/docs/index.html @@ -32,8 +32,8 @@ Javascript, HTML/CSS with MySQL as the backend. It was built for ERPNext but is pretty generic and can be used to build database driven apps.

-

The key difference in Frappe compared to other frameworks is that in Frappe, -metadata is also treated as data and it can be used to build frontend +

The key differece in Frappe compared to other frameworks is that Frappe +is that meta-data is also treated as data and is used to build front-ends very easily. Frappe comes with a full blown admin UI called the Desk that handles forms, navigation, lists, menus, permissions, file attachment and much more out of the box.

diff --git a/frappe/model/document.py b/frappe/model/document.py index 74f6ab0680..3e8db22802 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals, print_function import frappe import time -import redis from frappe import _, msgprint from frappe.utils import flt, cstr, now, get_datetime_str, file_lock from frappe.utils.background_jobs import enqueue diff --git a/frappe/model/naming.py b/frappe/model/naming.py index ef431adfda..3f0b1b7228 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -8,17 +8,16 @@ from frappe.utils import now_datetime, cint import re def set_new_name(doc): - """Sets the `name`` property for the document based on various rules. - - 1. If amened doc, set suffix. - 3. If `autoname` method is declared, then call it. - 4. If `autoname` property is set in the DocType (`meta`), then build it using the `autoname` property. - 2. If `name` is already defined, use that name - 5. If no rule defined, use hash. + """ + Sets the `name` property for the document based on various rules. - #### Note: + 1. If amended doc, set suffix. + 2. If `autoname` method is declared, then call it. + 3. If `autoname` property is set in the DocType (`meta`), then build it using the `autoname` property. + 4. If no rule defined, use hash. - :param doc: Document to be named.""" + :param doc: Document to be named. + """ doc.run_method("before_naming") diff --git a/frappe/model/utils/__init__.py b/frappe/model/utils/__init__.py index 217ad47a54..c795ae2159 100644 --- a/frappe/model/utils/__init__.py +++ b/frappe/model/utils/__init__.py @@ -61,3 +61,20 @@ def render_include(content): break return content + +def get_fetch_values(doctype, fieldname, value): + '''Returns fetch value dict for the given object + + :param doctype: Target doctype + :param fieldname: Link fieldname selected + :param value: Value selected + ''' + out = {} + meta = frappe.get_meta(doctype) + link_df = meta.get_field(fieldname) + for df in meta.get_fields_to_fetch(fieldname): + # example shipping_address.gistin + link_field, source_fieldname = df.options.split('.', 1) + out[df.fieldname] = frappe.db.get_value(link_df.options, value, source_fieldname) + + return out diff --git a/frappe/model/utils/user_settings.py b/frappe/model/utils/user_settings.py index 86498fc507..1034334049 100644 --- a/frappe/model/utils/user_settings.py +++ b/frappe/model/utils/user_settings.py @@ -16,7 +16,7 @@ def get_user_settings(doctype, for_update=False): if not for_update: update_user_settings(doctype, user_settings, True) - + return user_settings or '{}' def update_user_settings(doctype, user_settings, for_update=False): diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index 5f7c04c7f5..57c671b407 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -34,7 +34,6 @@ def get_file_path(module, dt, dn): def import_file_by_path(path, force=False, data_import=False, pre_process=None, ignore_version=None, reset_permissions=False): - frappe.flags.in_import = True try: docs = read_doc_from_file(path) except IOError: @@ -54,8 +53,10 @@ def import_file_by_path(path, force=False, data_import=False, pre_process=None, original_modified = doc.get("modified") + frappe.flags.in_import = True import_doc(doc, force=force, data_import=data_import, pre_process=pre_process, ignore_version=ignore_version, reset_permissions=reset_permissions) + frappe.flags.in_import = False if original_modified: # since there is a new timestamp on the file, update timestamp in @@ -67,7 +68,6 @@ def import_file_by_path(path, force=False, data_import=False, pre_process=None, (doc['doctype'], '%s', '%s'), (original_modified, doc['name'])) - frappe.flags.in_import = False return True def read_doc_from_file(path): diff --git a/frappe/patches.txt b/frappe/patches.txt index 9fb0206557..74ffc5315c 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -183,4 +183,5 @@ frappe.patches.v8_0.set_currency_field_precision # 2017-05-09 frappe.patches.v8_0.rename_print_to_printing frappe.patches.v7_1.disabled_print_settings_for_custom_print_format frappe.patches.v8_0.update_desktop_icons -frappe.patches.v8_0.update_gender_and_salutation \ No newline at end of file +frappe.patches.v8_0.update_gender_and_salutation +execute:frappe.db.sql('update tabReport set module="Desk" where name="ToDo"') \ No newline at end of file diff --git a/frappe/permissions.py b/frappe/permissions.py index 22342d9811..13da62d366 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -330,7 +330,7 @@ def get_all_perms(role): '''Returns valid permissions for a given role''' perms = frappe.get_all('DocPerm', fields='*', filters=dict(role=role)) custom_perms = frappe.get_all('Custom DocPerm', fields='*', filters=dict(role=role)) - doctypes_with_custom_perms = frappe.db.sql_list("""select distinct parent + doctypes_with_custom_perms = frappe.db.sql_list("""select distinct parent from `tabCustom DocPerm`""") for p in perms: @@ -458,6 +458,31 @@ def setup_custom_perms(parent): copy_perms(parent) return True +def add_permission(doctype, role, permlevel=0): + '''Add a new permission rule to the given doctype + for the given Role and Permission Level''' + from frappe.core.doctype.doctype.doctype import validate_permissions_for_doctype + setup_custom_perms(doctype) + + if frappe.db.get_value('Custom DocPerm', dict(parent=doctype, role=role, + permlevel=permlevel)): + return + + custom_docperm = frappe.get_doc({ + "doctype":"Custom DocPerm", + "__islocal": 1, + "parent": doctype, + "parenttype": "DocType", + "parentfield": "permissions", + "role": role, + 'read': 1, + "permlevel": permlevel, + }) + + custom_docperm.save() + + validate_permissions_for_doctype(doctype) + def copy_perms(parent): '''Copy all DocPerm in to Custom DocPerm for the given document''' for d in frappe.get_all('DocPerm', fields='*', filters=dict(parent=parent)): @@ -480,3 +505,4 @@ def get_linked_doctypes(dt): "options": ("!=", "[Select]") }) ])) + diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css index 64d2056de9..1272485515 100644 --- a/frappe/public/css/form.css +++ b/frappe/public/css/form.css @@ -223,6 +223,14 @@ h6.uppercase, .badge-important { background-color: #e74c3c; } +.address-box { + background-color: #fafbfc; + padding: 0px 15px; + margin: 15px 0px; + border: 1px solid #d1d8dd; + border-radius: 3px; + font-size: 12px; +} .timeline { margin: 30px 0px; } @@ -272,7 +280,7 @@ h6.uppercase, } .timeline-item.user-content .media-body { border: 1px solid #d1d8dd; - border-radius: 2px; + border-radius: 3px; margin-left: -7px; position: relative; overflow: visible; @@ -386,7 +394,7 @@ h6.uppercase, .timeline-head { background-color: white; border: 1px solid #d1d8dd; - border-radius: 2px; + border-radius: 3px; position: relative; z-index: 1; } diff --git a/frappe/public/css/website.css b/frappe/public/css/website.css index e0f122ba4d..a02316f7b9 100644 --- a/frappe/public/css/website.css +++ b/frappe/public/css/website.css @@ -290,6 +290,14 @@ a.no-decoration:active { .avatar-large .standard-image { font-size: 36px; } +.avatar-xl { + margin-right: 10px; + width: 108px; + height: 108px; +} +.avatar-xl .standard-image { + font-size: 72px; +} .avatar-xs { margin-right: 3px; margin-top: -2px; @@ -980,3 +988,8 @@ li.footer-child-item { .page-card .btn { margin-top: 30px; } +.bordered { + border: 1px solid #d1d8dd; + padding: 10px; + border-radius: 4px; +} diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index d25b4616ca..38aa00c401 100755 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -609,6 +609,7 @@ frappe.ui.form.ControlDate = frappe.ui.form.ControlData.extend({ this.set_t_for_today(); }, set_formatted_input: function(value) { + this._super(value); if(value && ((this.last_value && this.last_value !== value) || (!this.datepicker.selectedDates.length))) { @@ -1289,7 +1290,14 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ frappe._from_link = this; frappe._from_link_scrollY = $(document).scrollTop(); - frappe.ui.form.quick_entry(doctype, function(doc) { + var trimmed_doctype = doctype.replace(/ /g, ''); + var controller_name = "QuickEntryForm"; + + if(frappe.ui.form[trimmed_doctype + "QuickEntryForm"]){ + controller_name = trimmed_doctype + "QuickEntryForm"; + } + + new frappe.ui.form[controller_name](doctype, function(doc) { if(me.frm) { me.parse_validate_and_set_in_model(doc.name); } else { @@ -1530,7 +1538,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ // validate the value just entered var me = this; - if(this.df.options=="[Select]") { + if(this.df.options=="[Select]" || this.df.ignore_link_validation) { callback(value); return; } diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index a267839884..ec48b3b1da 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -1,129 +1,178 @@ frappe.provide('frappe.ui.form'); -frappe.ui.form.quick_entry = function(doctype, success) { - frappe.model.with_doctype(doctype, function() { - var mandatory = $.map(frappe.get_meta(doctype).fields, - function(d) { return (d.reqd || d.bold && !d.read_only) ? d : null }); - var meta = frappe.get_meta(doctype); - - var doc = frappe.model.get_new_doc(doctype, null, null, true); +frappe.ui.form.QuickEntryForm = Class.extend({ + init: function(doctype, success_function){ + this.doctype = doctype; + this.success_function = success_function; + this.setup(); + }, + + setup: function(){ + var me = this; + frappe.model.with_doctype(this.doctype, function() { + me.set_meta_and_mandatory_fields(); + var validate_flag = me.validate_quick_entry(); + if(!validate_flag){ + me.render_dialog(); + } + }); + }, + + set_meta_and_mandatory_fields: function(){ + this.mandatory = $.map(frappe.get_meta(this.doctype).fields, + function(d) { return (d.reqd || d.bold && !d.read_only) ? d : null; }); + this.meta = frappe.get_meta(this.doctype); + this.doc = frappe.model.get_new_doc(this.doctype, null, null, true); + }, + + validate_quick_entry: function(){ + if(this.meta.quick_entry != 1) { + frappe.set_route('Form', this.doctype, this.doc.name); + return true; + } + var mandatory_flag = this.validate_mandatory_length(); + var child_table_flag = this.validate_for_child_table(); - if(meta.quick_entry != 1) { - frappe.set_route('Form', doctype, doc.name); - return; + if (mandatory_flag || child_table_flag){ + return true; } + this.validate_for_prompt_autoname(); + }, - if(mandatory.length > 7) { + validate_mandatory_length: function(){ + if(this.mandatory.length > 7) { // too many fields, show form - frappe.set_route('Form', doctype, doc.name); - return; + frappe.set_route('Form', this.doctype, this.doc.name); + return true; } + }, - if($.map(mandatory, function(d) { return d.fieldtype==='Table' ? d : null }).length) { + validate_for_child_table: function(){ + if($.map(this.mandatory, function(d) { return d.fieldtype==='Table' ? d : null; }).length) { // has mandatory table, quit! - frappe.set_route('Form', doctype, doc.name); - return; + frappe.set_route('Form', this.doctype, this.doc.name); + return true; } + }, - if(meta.autoname && meta.autoname.toLowerCase()==='prompt') { - mandatory = [{fieldname:'__name', label:__('{0} Name', [meta.name]), - reqd: 1, fieldtype:'Data'}].concat(mandatory); + validate_for_prompt_autoname: function(){ + if(this.meta.autoname && this.meta.autoname.toLowerCase()==='prompt') { + this.mandatory = [{fieldname:'__name', label:__('{0} Name', [this.meta.name]), + reqd: 1, fieldtype:'Data'}].concat(this.mandatory); } + }, - var dialog = new frappe.ui.Dialog({ - title: __("New {0}", [__(doctype)]), - fields: mandatory, + render_dialog: function(){ + var me = this; + this.dialog = new frappe.ui.Dialog({ + title: __("New {0}", [__(this.doctype)]), + fields: this.mandatory, }); + this.dialog.doc = this.doc; + // refresh dependencies etc + this.dialog.refresh(); - var update_doc = function() { - var data = dialog.get_values(true); - $.each(data, function(key, value) { - if(key==='__name') { - dialog.doc.name = value; - } else { - if(!is_null(value)) { - dialog.doc[key] = value; - } + this.register_primary_action(); + this.render_edit_in_full_page_link(); + // ctrl+enter to save + this.dialog.wrapper.keydown(function(e) { + if((e.ctrlKey || e.metaKey) && e.which==13) { + if(!frappe.request.ajax_count) { + // not already working -- double entry + me.dialog.get_primary_btn().trigger("click"); + e.preventDefault(); + return false; } - }); - return dialog.doc; - } - - var open_doc = function() { - dialog.hide(); - update_doc(); - frappe.set_route('Form', doctype, doc.name); - } - - dialog.doc = doc; + } + }); - // refresh dependencies etc - dialog.refresh(); + this.dialog.show(); + this.set_defaults(); + }, - dialog.set_primary_action(__('Save'), function() { - if(dialog.working) return; - var data = dialog.get_values(); + register_primary_action: function(){ + var me = this; + this.dialog.set_primary_action(__('Save'), function() { + if(me.dialog.working) return; + var data = me.dialog.get_values(); if(data) { - dialog.working = true; - var values = update_doc(); - frappe.call({ - method: "frappe.client.insert", - args: { - doc: values - }, - callback: function(r) { - dialog.hide(); - // delete the old doc - frappe.model.clear_doc(dialog.doc.doctype, dialog.doc.name); - var doc = r.message; - if(success) { - success(doc); - } - frappe.ui.form.update_calling_link(doc.name); - }, - error: function() { - open_doc(); - }, - always: function() { - dialog.working = false; - }, - freeze: true - }); + me.dialog.working = true; + var values = me.update_doc(); + me.insert_document(values); } }); + }, + + insert_document: function(values){ + var me = this; + frappe.call({ + method: "frappe.client.insert", + args: { + doc: values + }, + callback: function(r) { + me.dialog.hide(); + // delete the old doc + frappe.model.clear_doc(me.dialog.doc.doctype, me.dialog.doc.name); + var doc = r.message; + if(me.success_function) { + me.success_function(doc); + } + frappe.ui.form.update_calling_link(doc.name); + }, + error: function() { + me.open_doc(); + }, + always: function() { + me.dialog.working = false; + }, + freeze: true + }); + }, + + update_doc: function(){ + var me = this; + var data = this.dialog.get_values(true); + $.each(data, function(key, value) { + if(key==='__name') { + me.dialog.doc.name = value; + } else { + if(!is_null(value)) { + me.dialog.doc[key] = value; + } + } + }); + return this.dialog.doc; + }, + open_doc: function(){ + this.dialog.hide(); + this.update_doc(); + frappe.set_route('Form', this.doctype, this.doc.name); + }, + + render_edit_in_full_page_link: function(){ + var me = this; var $link = $('
' + - __("Ctrl+enter to save") + ' | ' + __("Edit in full page") + '
').appendTo(dialog.body); + __("Ctrl+enter to save") + ' | ' + __("Edit in full page") + '').appendTo(this.dialog.body); $link.find('.edit-full').on('click', function() { // edit in form - open_doc(); - }); - - // ctrl+enter to save - dialog.wrapper.keydown(function(e) { - if((e.ctrlKey || e.metaKey) && e.which==13) { - if(!frappe.request.ajax_count) { - // not already working -- double entry - dialog.get_primary_btn().trigger("click"); - e.preventDefault(); - return false; - } - } + me.open_doc(); }); + }, - dialog.show(); - + set_defaults: function(){ + var me = this; // set defaults - $.each(dialog.fields_dict, function(fieldname, field) { - field.doctype = doc.doctype; - field.docname = doc.name; + $.each(this.dialog.fields_dict, function(fieldname, field) { + field.doctype = me.doc.doctype; + field.docname = me.doc.name; - if(!is_null(doc[fieldname])) { - field.set_input(doc[fieldname]); + if(!is_null(me.doc[fieldname])) { + field.set_input(me.doc[fieldname]); } }); - - }); -} + } +}); diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 2448734dbe..79d9ab44ef 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -318,7 +318,14 @@ frappe.new_doc = function (doctype, opts) { if(frappe.create_routes[doctype]) { frappe.set_route(frappe.create_routes[doctype]); } else { - frappe.ui.form.quick_entry(doctype, function(doc) { + var trimmed_doctype = doctype.replace(/ /g, ''); + var controller_name = "QuickEntryForm"; + + if(frappe.ui.form[trimmed_doctype + "QuickEntryForm"]){ + controller_name = trimmed_doctype + "QuickEntryForm"; + } + + new frappe.ui.form[controller_name](doctype, function(doc) { //frappe.set_route('List', doctype); var title = doc.name; var title_field = frappe.get_meta(doc.doctype).title_field; diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js index f6ac7e241e..5a08f2ad34 100644 --- a/frappe/public/js/frappe/ui/page.js +++ b/frappe/public/js/frappe/ui/page.js @@ -251,12 +251,22 @@ frappe.ui.Page = Class.extend({ }, add_inner_button: function(label, action, group) { + let _action = function() { + let btn = $(this); + let promise = action(); + if (promise && promise.then) { + btn.attr('disabled', true); + promise.then(() => { + btn.attr('disabled', false); + }) + } + } if(group) { var $group = this.get_inner_group_button(group); - return $('
  • '+label+'
  • ').on('click', action).appendTo($group.find(".dropdown-menu")); + return $('
  • '+label+'
  • ').on('click', _action).appendTo($group.find(".dropdown-menu")); } else { return $('