diff --git a/frappe/__init__.py b/frappe/__init__.py index 1325096ce1..8fddd693a3 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -37,7 +37,7 @@ class _dict(dict): return _dict(dict(self).copy()) def _(msg): - """translate object in current lang, if exists""" + """Returns translated string in current lang, if exists.""" if local.lang == "en": return msg @@ -45,12 +45,17 @@ def _(msg): return get_full_dict(local.lang).get(msg, msg) def get_lang_dict(fortype, name=None): + """Returns the translated language dict for the given type and name. + + :param fortype: must be one of `doctype`, `page`, `report`, `include`, `jsfile`, `boot` + :param name: name of the document for which assets are to be returned.""" if local.lang=="en": return {} from frappe.translate import get_dict return get_dict(fortype, name) def set_user_lang(user, user_language=None): + """Guess and set user language for the session. `frappe.local.lang`""" from frappe.translate import get_user_lang local.lang = get_user_lang(user) @@ -72,6 +77,7 @@ message_log = local("message_log") lang = local("lang") def init(site, sites_path=None): + """Initialize frappe for the current site. Reset thread locals `frappe.local`""" if getattr(local, "initialised", None): return @@ -111,6 +117,10 @@ def init(site, sites_path=None): local.initialised = True def connect(site=None, db_name=None): + """Connect to site database instance. + + :param site: If site is given, calls `frappe.init`. + :param db_name: Optional. Will use from `site_config.json`.""" from database import Database if site: init(site) @@ -120,6 +130,8 @@ def connect(site=None, db_name=None): set_user("Administrator") def get_site_config(sites_path=None, site_path=None): + """Returns `site_config.json` combined with `sites/common_site_config.json`. + `site_config` is a set of site wide settings like database name, password, email etc.""" config = {} sites_path = sites_path or getattr(local, "sites_path", None) @@ -138,7 +150,7 @@ def get_site_config(sites_path=None, site_path=None): return _dict(config) def destroy(): - """closes connection and releases werkzeug local""" + """Closes connection and releases werkzeug local.""" if db: db.close() @@ -148,6 +160,7 @@ _memc = None # memcache def cache(): + """Returns memcache connection.""" global _memc if not _memc: from frappe.memc import MClient @@ -155,10 +168,14 @@ def cache(): return _memc def get_traceback(): + """Returns error traceback.""" import utils return utils.get_traceback() def errprint(msg): + """Log error. This is sent back as `exc` in response. + + :param msg: Message.""" from utils import cstr if not request or (not "cmd" in local.form_dict): print cstr(msg) @@ -166,6 +183,9 @@ def errprint(msg): error_log.append(cstr(msg)) def log(msg): + """Add to `debug_log`. + + :param msg: Message.""" if not request: if conf.get("logging") or False: print repr(msg) @@ -174,6 +194,15 @@ def log(msg): debug_log.append(cstr(msg)) def msgprint(msg, small=0, raise_exception=0, as_table=False): + """Print a message to the user (via HTTP response). + Messages are sent in the `__server_messages` property in the + response JSON and shown in a pop-up / modal. + + :param msg: Message. + :param small: [optional] Show as a floating message in the footer. + :param raise_exception: [optional] Raise given exception and show message. + :param as_table: [optional] If `msg` is a list of lists, render as HTML table. + """ def _raise_exception(): if raise_exception: if flags.rollback_on_exception: @@ -199,9 +228,17 @@ def msgprint(msg, small=0, raise_exception=0, as_table=False): _raise_exception() def throw(msg, exc=ValidationError): + """Throw execption and show message (`msgprint`). + + :param msg: Message. + :param exc: Exception class. Default `frappe.ValidationError`""" msgprint(msg, raise_exception=exc) def create_folder(path, with_init=False): + """Create a folder in the given path and add an `__init__.py` file (optional). + + :param path: Folder path. + :param with_init: Create `__init__.py` in the new folder.""" from frappe.utils import touch_file if not os.path.exists(path): os.makedirs(path) @@ -210,6 +247,9 @@ def create_folder(path, with_init=False): touch_file(os.path.join(path, "__init__.py")) def set_user(username): + """Set current user. + + :param username: **User** name to set as current user.""" from frappe.utils.user import User local.session.user = username local.session.sid = username @@ -221,11 +261,29 @@ def set_user(username): local.role_permissions = {} def get_request_header(key, default=None): + """Return HTTP request header. + + :param key: HTTP header key. + :param default: Default value.""" return request.headers.get(key, default) def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", as_markdown=False, bulk=False, ref_doctype=None, ref_docname=None, add_unsubscribe_link=False, attachments=None, content=None, doctype=None, name=None): + """Send email using user's default **Email Account** or global default **Email Account**. + + + :param recipients: List of recipients. + :param sender: Email sender. Default is current user. + :param subject: Email Subject. + :param message: (or `content`) Email Content. + :param as_markdown: Convert content markdown to HTML. + :param bulk: Send via scheduled email sender **Bulk Email**. Don't send immediately. + :param ref_doctype: (or `doctype`) Append as communication to this DocType. + :param ref_docname: (or `name`) Append as communication to this document name. + :param add_unsubscribe_link: Allow user to unsubscribe from these emails. + :param attachments: List of attachments. + """ if bulk: import frappe.email.bulk @@ -247,12 +305,16 @@ whitelisted = [] guest_methods = [] def whitelist(allow_guest=False): """ - decorator for whitelisting a function + Decorator for whitelisting a function and making it accessible via HTTP. + Standard request will be `/api/method/[path.to.method]` - Note: if the function is allowed to be accessed by a guest user, - it must explicitly be marked as allow_guest=True + :param allow_guest: Allow non logged-in user to access this method. - for specific roles, set allow_roles = ['Administrator'] etc. + Use as: + + @frappe.whitelist() + def myfunc(param1, param2): + pass """ def innerfn(fn): global whitelisted, guest_methods @@ -266,6 +328,9 @@ def whitelist(allow_guest=False): return innerfn def only_for(roles): + """Raise `frappe.PermissionError` if the user does not have any of the given **Roles**. + + :param roles: List of roles to check.""" if not isinstance(roles, (tuple, list)): roles = (roles,) roles = set(roles) @@ -274,7 +339,10 @@ def only_for(roles): raise PermissionError def clear_cache(user=None, doctype=None): - """clear cache""" + """Clear **User**, **DocType** or global cache. + + :param user: If user is given, only user cache is cleared. + :param doctype: If doctype is given, only DocType cache is cleared.""" import frappe.sessions if doctype: import frappe.model.meta @@ -294,12 +362,14 @@ def clear_cache(user=None, doctype=None): frappe.local.role_permissions = {} def get_roles(username=None): + """Returns roles of current user.""" if not local.session: return ["Guest"] return get_user(username).get_roles() def get_user(username): + """Returns `frappe.utils.user.User` instance of given user.""" from frappe.utils.user import User if not username or username == local.session.user: return local.user @@ -307,101 +377,153 @@ def get_user(username): return User(username) def has_permission(doctype, ptype="read", doc=None, user=None): + """Raises `frappe.PermissionError` if not permitted. + + :param doctype: DocType for which permission is to be check. + :param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`. + :param doc: [optional] Checks User permissions for given doc. + :param user: [optional] Check for given user. Default: current user.""" import frappe.permissions return frappe.permissions.has_permission(doctype, ptype, doc, user=user) def is_table(doctype): + """Returns True if `istable` property (indicating child Table) is set for given DocType.""" tables = cache().get_value("is_table") if tables==None: tables = db.sql_list("select name from tabDocType where ifnull(istable,0)=1") cache().set_value("is_table", tables) return doctype in tables -def clear_perms(doctype): - db.sql("""delete from tabDocPerm where parent=%s""", doctype) - -def reset_perms(doctype): - from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for - delete_notification_count_for(doctype) - - clear_perms(doctype) - reload_doc(db.get_value("DocType", doctype, "module"), - "DocType", doctype, force=True) - def generate_hash(txt=None): - """Generates random hash for session id""" + """Generates random hash for given text + current timestamp + random string.""" import hashlib, time from .utils import random_string return hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest() def reset_metadata_version(): + """Reset `metadata_version` (Client (Javascript) build ID) hash.""" v = generate_hash() cache().set_value("metadata_version", v) return v def new_doc(doctype, parent_doc=None, parentfield=None): + """Returns a new document of the given DocType with defaults set. + + :param doctype: DocType of the new document. + :param parent_doc: [optional] add to parent document. + :param parentfield: [optional] add against this `parentfield`.""" from frappe.model.create_new import get_new_doc return get_new_doc(doctype, parent_doc, parentfield) def set_value(doctype, docname, fieldname, value): + """Set document value. Calls `frappe.client.set_value`""" import frappe.client return frappe.client.set_value(doctype, docname, fieldname, value) def get_doc(arg1, arg2=None): + """Return a `frappe.model.document.Document` object of the given type and name. + + :param arg1: DocType name as string **or** document JSON. + :param arg2: [optional] Document name as string. + + Examples: + + # insert a new document + todo = frappe.get_doc({"doctype":"ToDo", "description": "test"}) + tood.insert() + + # open an existing document + todo = frappe.get_doc("ToDo", "TD0001") + + """ import frappe.model.document return frappe.model.document.get_doc(arg1, arg2) def get_meta(doctype, cached=True): + """Get `frappe.model.meta.Meta` instance of given doctype name.""" import frappe.model.meta return frappe.model.meta.get_meta(doctype, cached=cached) def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False): + """Delete a document. Calls `frappe.model.delete_doc.delete_doc`. + + :param doctype: DocType of document to be delete. + :param name: Name of document to be delete. + :param force: Allow even if document is linked. Warning: This may lead to data integrity errors. + :param ignore_doctypes: Ignore if child table is one of these. + :param for_reload: Call `before_reload` trigger before deleting. + :param ignore_permissions: Ignore user permissions.""" import frappe.model.delete_doc frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions) def delete_doc_if_exists(doctype, name): + """Delete document if exists.""" if db.exists(doctype, name): delete_doc(doctype, name) def reload_doc(module, dt=None, dn=None, force=False): + """Reload Document from model (`[module]/[doctype]/[name]/[name].json`) files. + + :param module: Module name. + :param dt: DocType name. + :param dn: Document name. + :param force: Reload even if `modified` timestamp matches. + """ import frappe.modules return frappe.modules.reload_doc(module, dt, dn, force=force) def rename_doc(doctype, old, new, debug=0, force=False, merge=False, ignore_permissions=False): + """Rename a document. Calls `frappe.model.rename_doc.rename_doc`""" from frappe.model.rename_doc import rename_doc return rename_doc(doctype, old, new, force=force, merge=merge, ignore_permissions=ignore_permissions) -def insert(doclist): - import frappe.model - return frappe.model.insert(doclist) - def get_module(modulename): + """Returns a module object for given Python module name using `importlib.import_module`.""" return importlib.import_module(modulename) def scrub(txt): + """Returns sluggified string. e.g. `Sales Order` becomes `sales_order`.""" return txt.replace(' ','_').replace('-', '_').lower() def unscrub(txt): + """Returns titlified string. e.g. `sales_order` becomes `Sales Order`.""" return txt.replace('_',' ').replace('-', ' ').title() def get_module_path(module, *joins): + """Get the path of the given module name. + + :param module: Module name. + :param *joins: Join additional path elements using `os.path.join`.""" module = scrub(module) return get_pymodule_path(local.module_app[module] + "." + module, *joins) def get_app_path(app_name, *joins): + """Return path of given app. + + :param app: App name. + :param *joins: Join additional path elements using `os.path.join`.""" return get_pymodule_path(app_name, *joins) def get_site_path(*joins): + """Return path of current site. + + :param *joins: Join additional path elements using `os.path.join`.""" return os.path.join(local.site_path, *joins) def get_pymodule_path(modulename, *joins): + """Return path of given Python module name. + + :param modulename: Python module name. + :param *joins: Join additional path elements using `os.path.join`.""" joins = [scrub(part) for part in joins] return os.path.join(os.path.dirname(get_module(scrub(modulename)).__file__), *joins) def get_module_list(app_name): + """Get list of modules for given all via `app/modules.txt`.""" return get_file_items(os.path.join(os.path.dirname(get_module(app_name).__file__), "modules.txt")) def get_all_apps(with_frappe=False, with_internal_apps=True, sites_path=None): + """Get list of all apps via `sites/apps.txt`.""" if not sites_path: sites_path = local.sites_path @@ -413,6 +535,7 @@ def get_all_apps(with_frappe=False, with_internal_apps=True, sites_path=None): return apps def get_installed_apps(): + """Get list of installed apps in current site.""" if getattr(flags, "in_install_db", True): return [] installed = json.loads(db.get_global("installed_apps") or "[]") @@ -420,6 +543,16 @@ def get_installed_apps(): @whitelist() def get_versions(): + """Get versions of all installed apps. + + Example: + + { + "frappe": { + "title": "Frappe Framework", + "version": "5.0.0" + } + }""" versions = {} for app in get_installed_apps(): versions[app] = { @@ -434,6 +567,11 @@ def get_versions(): return versions def get_hooks(hook=None, default=None, app_name=None): + """Get hooks via `app/hooks.py` + + :param hook: Name of the hook. Will gather all hooks for this name and return as a list. + :param default: Default if no hook found. + :param app_name: Filter by app.""" def load_app_hooks(app_name=None): hooks = {} for app in [app_name] if app_name else get_installed_apps(): @@ -476,6 +614,7 @@ def get_hooks(hook=None, default=None, app_name=None): return hooks def setup_module_map(): + """Rebuild map of all modules (internal).""" _cache = cache() if conf.db_name: @@ -497,6 +636,7 @@ def setup_module_map(): _cache.set_value("module_app", local.module_app) def get_file_items(path, raise_not_found=False, ignore_empty_lines=True): + """Returns items from text file as a list. Ignores empty lines.""" content = read_file(path, raise_not_found=raise_not_found) if content: # \ufeff is no-width-break, \u200b is no-width-space @@ -507,10 +647,12 @@ def get_file_items(path, raise_not_found=False, ignore_empty_lines=True): return [] def get_file_json(path): + """Read a file and return parsed JSON object.""" with open(path, 'r') as f: return json.load(f) def read_file(path, raise_not_found=False): + """Open a file and return its content as Unicode.""" from frappe.utils import cstr if os.path.exists(path): with open(path, "r") as f: @@ -521,11 +663,13 @@ def read_file(path, raise_not_found=False): return None def get_attr(method_string): + """Get python method object from its name.""" modulename = '.'.join(method_string.split('.')[:-1]) methodname = method_string.split('.')[-1] return getattr(get_module(modulename), methodname) def call(fn, *args, **kwargs): + """Call a function and match arguments.""" if hasattr(fn, 'fnargs'): fnargs = fn.fnargs else: @@ -538,6 +682,7 @@ def call(fn, *args, **kwargs): return fn(*args, **newargs) def make_property_setter(args, ignore_validate=False, validate_fields_for_doctype=True): + """Create a new **Property Setter** (for overriding DocType and DocField properties).""" args = _dict(args) ps = get_doc({ 'doctype': "Property Setter", @@ -554,6 +699,7 @@ def make_property_setter(args, ignore_validate=False, validate_fields_for_doctyp ps.insert() def import_doc(path, ignore_links=False, ignore_insert=False, insert=False): + """Import a file using Data Import Tool.""" 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) @@ -593,10 +739,31 @@ def copy_doc(doc, ignore_no_copy=True): return newdoc def compare(val1, condition, val2): + """Compare two values using `frappe.utils.compare` + + `condition` could be: + - "^" + - "in" + - "not in" + - "=" + - "!=" + - ">" + - "<" + - ">=" + - "<=" + - "not None" + - "None" + """ import frappe.utils return frappe.utils.compare(val1, condition, val2) def respond_as_web_page(title, html, success=None, http_status_code=None): + """Send response as a web page with a message rather than JSON. Used to show permission errors etc. + + :param title: Page title and heading. + :param message: Message to be shown. + :param success: Alert message. + :param http_status_code: HTTP status code.""" local.message_title = title local.message = html local.message_success = success @@ -606,18 +773,62 @@ def respond_as_web_page(title, html, success=None, http_status_code=None): local.response['http_status_code'] = http_status_code def build_match_conditions(doctype, as_condition=True): + """Return match (User permissions) for given doctype as list or SQL.""" import frappe.desk.reportview return frappe.desk.reportview.build_match_conditions(doctype, as_condition) def get_list(doctype, *args, **kwargs): + """List database query via `frappe.model.db_query`. Will also check for permissions. + + :param doctype: DocType on which query is to be made. + :param fields: List of fields or `*`. + :param filters: List of filters (see example). + :param order_by: Order By e.g. `modified desc`. + :param limit_page_start: Start results at record #. Default 0. + :param limit_poge_length: No of records in the page. Default 20. + + Example usage: + + # simple dict filter + frappe.get_list("ToDo", fields=["name", "description"], filters = {"owner":"test@example.com"}) + + # filter as a list of lists + frappe.get_list("ToDo", fields="*", filters = [["modified", ">", "2014-01-01"]]) + + # filter as a list of dicts + frappe.get_list("ToDo", fields="*", filters = {"description": ("like", "test%")}) + """ import frappe.model.db_query return frappe.model.db_query.DatabaseQuery(doctype).execute(None, *args, **kwargs) def get_all(doctype, *args, **kwargs): + """List database query via `frappe.model.db_query`. Will **not** check for conditions. + Parameters are same as `frappe.get_list` + + :param doctype: DocType on which query is to be made. + :param fields: List of fields or `*`. Default is: `["name"]`. + :param filters: List of filters (see example). + :param order_by: Order By e.g. `modified desc`. + :param limit_page_start: Start results at record #. Default 0. + :param limit_poge_length: No of records in the page. Default 20. + + Example usage: + + # simple dict filter + frappe.get_all("ToDo", fields=["name", "description"], filters = {"owner":"test@example.com"}) + + # filter as a list of lists + frappe.get_all("ToDo", fields=["*"], filters = [["modified", ">", "2014-01-01"]]) + + # filter as a list of dicts + frappe.get_all("ToDo", fields=["*"], filters = {"description": ("like", "test%")}) + """ kwargs["ignore_permissions"] = True return get_list(doctype, *args, **kwargs) def add_version(doc): + """Insert a new **Version** of the given document. + A **Version** is a JSON dump of the current document state.""" get_doc({ "doctype": "Version", "ref_doctype": doc.doctype, @@ -626,6 +837,7 @@ def add_version(doc): }).insert(ignore_permissions=True) def get_test_records(doctype): + """Returns list of objects from `test_records.json` in the given doctype's folder.""" from frappe.modules import get_doctype_module, get_module_path path = os.path.join(get_module_path(get_doctype_module(doctype)), "doctype", scrub(doctype), "test_records.json") if os.path.exists(path): @@ -635,10 +847,21 @@ def get_test_records(doctype): return [] def format_value(value, df, doc=None, currency=None): + """Format value with given field properties. + + :param value: Value to be formatted. + :param df: DocField object with properties `fieldtype`, `options` etc.""" import frappe.utils.formatters return frappe.utils.formatters.format_value(value, df, doc, currency=currency) def get_print_format(doctype, name, print_format=None, style=None, as_pdf=False): + """Get Print Format for given document. + + :param doctype: DocType of document. + :param name: Name of document. + :param print_format: Print Format name. Default 'Standard', + :param style: Print Format style. + :param as_pdf: Return as PDF. Default False.""" from frappe.website.render import build_page local.form_dict.doctype = doctype local.form_dict.name = name diff --git a/frappe/cli.py b/frappe/cli.py index 6c23b5859c..6062592901 100755 --- a/frappe/cli.py +++ b/frappe/cli.py @@ -598,11 +598,12 @@ def sync_statics(force=False): @cmd def reset_perms(): + from frappe.permissions import reset_perms frappe.connect() for d in frappe.db.sql_list("""select name from `tabDocType` where ifnull(istable, 0)=0 and ifnull(custom, 0)=0"""): frappe.clear_cache(doctype=d) - frappe.reset_perms(d) + reset_perms(d) frappe.destroy() @cmd diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 43c8531791..23b393a9bd 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -7,6 +7,7 @@ import frappe.defaults from frappe.modules.import_file import get_file_path, read_doc_from_file from frappe.translate import send_translations from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for +from frappe.permissions import reset_perms @frappe.whitelist() def get_roles_and_doctypes(): @@ -82,7 +83,7 @@ def validate_and_reset(doctype, for_remove=False): @frappe.whitelist() def reset(doctype): frappe.only_for("System Manager") - frappe.reset_perms(doctype) + reset_perms(doctype) clear_doctype_cache(doctype) def clear_doctype_cache(doctype): diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 9044d2db2a..b5766a1d30 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -15,21 +15,6 @@ integer_docfield_properties = ["reqd", "search_index", "in_list_view", "permleve "hidden", "read_only", "ignore_user_permissions", "allow_on_submit", "report_hide", "in_filter", "no_copy", "print_hide", "unique"] -def insert(doclist): - if not isinstance(doclist, list): - doclist = [doclist] - - for d in doclist: - if isinstance(d, dict): - d["__islocal"] = 1 - else: - d.set("__islocal", 1) - - wrapper = frappe.get_doc(doclist) - wrapper.save() - - return wrapper - def rename(doctype, old, new, debug=False): import frappe.model.rename_doc frappe.model.rename_doc.rename_doc(doctype, old, new, debug) diff --git a/frappe/modules/patch_handler.py b/frappe/modules/patch_handler.py index 1a86cfebbf..e9e3e6bab3 100644 --- a/frappe/modules/patch_handler.py +++ b/frappe/modules/patch_handler.py @@ -12,7 +12,7 @@ from __future__ import unicode_literals where patch1, patch2 is module name """ -import frappe, os +import frappe, os, frappe.permissions class PatchError(Exception): pass diff --git a/frappe/patches.txt b/frappe/patches.txt index 179e6194a8..a5a843ec8b 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -12,7 +12,7 @@ frappe.patches.v4_0.change_varchar_length frappe.patches.v5_0.v4_to_v5 frappe.patches.v4_0.webnotes_to_frappe -execute:frappe.reset_perms("Module Def") +execute:frappe.permissions.reset_perms("Module Def") execute:import frappe.installer;frappe.installer.make_site_dirs() #2014-02-19 frappe.patches.v4_0.rename_profile_to_user frappe.patches.v4_0.deprecate_control_panel @@ -38,7 +38,7 @@ execute:frappe.db.sql("update tabReport set apply_user_permissions=1") #2014-06- frappe.patches.v4_0.replace_deprecated_timezones execute:import frappe.website.render; frappe.website.render.clear_cache("login"); #2014-06-10 frappe.patches.v4_0.fix_attach_field_file_url -execute:frappe.reset_perms("User") #2014-06-13 +execute:frappe.permissions.reset_perms("User") #2014-06-13 execute:frappe.db.sql("""delete from `tabUserRole` where ifnull(parentfield, '')='' or ifnull(`role`, '')=''""") #2014-08-18 frappe.patches.v4_0.remove_user_owner_custom_field execute:frappe.delete_doc("DocType", "Website Template") diff --git a/frappe/translate.py b/frappe/translate.py index 534353a54d..cad6cf1493 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -337,12 +337,16 @@ def read_csv_file(path): from csv import reader with codecs.open(path, 'r', 'utf-8') as msgfile: data = msgfile.read() + + # for japanese! #wtf + data = data.replace(chr(28), "").replace(chr(29), "") + data = reader([r.encode('utf-8') for r in data.splitlines()]) newdata = [[unicode(val, 'utf-8') for val in row] for row in data] return newdata def write_csv_file(path, app_messages, lang_dict): - """Write translation CSV file + """Write translation CSV file. :param path: File path, usually `[app]/translations`. :param app_messages: Translatable strings for this app. diff --git a/frappe/website/permissions.py b/frappe/website/permissions.py index 3c7817e784..990c9c9bf5 100644 --- a/frappe/website/permissions.py +++ b/frappe/website/permissions.py @@ -75,3 +75,12 @@ def clear_permissions(users=None): cache = frappe.cache() for user in users: cache.delete_value("website_route_permissions:{}".format(user)) + +def reset_perms(doctype): + """Reset permissions for given doctype.""" + from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for + delete_notification_count_for(doctype) + + frappe.db.sql("""delete from tabDocPerm where parent=%s""", doctype) + frappe.reload_doc(frappe.db.get_value("DocType", doctype, "module"), + "DocType", doctype, force=True)