diff --git a/frappe/__version__.py b/frappe/__version__.py index 3ef413303b..284940332b 100644 --- a/frappe/__version__.py +++ b/frappe/__version__.py @@ -1,2 +1,2 @@ from __future__ import unicode_literals -__version__ = "5.1.3" +__version__ = "5.1.4" diff --git a/frappe/commands.py b/frappe/commands.py index 1d7ae3aff8..4f5c316af6 100644 --- a/frappe/commands.py +++ b/frappe/commands.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals, absolute_import import sys import os -import subprocess import json import click import hashlib @@ -140,7 +139,7 @@ def reinstall(context): frappe.clear_cache() installed = frappe.get_installed_apps() frappe.clear_cache() - except Exception, e: + except Exception: installed = [] finally: if frappe.db: @@ -191,8 +190,6 @@ def migrate(context, rebuild_website=False): import frappe.translate from frappe.desk.notifications import clear_notifications - verbose = context.verbose - for site in context.sites: print 'Migrating', site frappe.init(site=site) @@ -312,15 +309,16 @@ def destroy_all_sessions(context): frappe.destroy() @click.command('sync-www') +@click.option('--force', help='Rebuild all pages', is_flag=True, default=False) @pass_context -def sync_www(context): +def sync_www(context, force=False): "Sync files from static pages from www directory to Web Pages" from frappe.website import statics for site in context.sites: try: frappe.init(site=site) frappe.connect() - statics.sync_statics(rebuild=context.force) + statics.sync_statics(rebuild=force) frappe.db.commit() finally: frappe.destroy() @@ -341,32 +339,17 @@ def build_website(context): frappe.destroy() @click.command('setup-docs') -@click.argument('app') -@click.argument('docs-app') -@click.argument('path') @pass_context -def setup_docs(context,app, docs_app, path): +def setup_docs(context): "Setup docs in target folder of target app" from frappe.utils.setup_docs import setup_docs + from frappe.website import statics for site in context.sites: try: frappe.init(site=site) frappe.connect() - setup_docs(app, docs_app, path) - finally: - frappe.destroy() - -@click.command('build-docs') -@click.argument('app') -@pass_context -def build_docs(context, app): - "Build docs from /src to /www folder in app" - from frappe.utils.autodoc import build - frappe.destroy() - for site in context.sites: - try: - frappe.init(site=site) - build(app) + setup_docs() + statics.sync_statics(rebuild=True) finally: frappe.destroy() @@ -826,7 +809,6 @@ commands = [ sync_www, build_website, setup_docs, - build_docs, reset_perms, execute, celery, diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index fa2c8cb37e..eba36506be 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -11,7 +11,6 @@ from frappe.utils import get_fullname class Comment(Document): """Comments are added to Documents via forms or views like blogs etc.""" - __doclink__ = "https://frappe.io/docs/models/core/comment" no_feed_on_delete = True def get_feed(self): @@ -134,4 +133,3 @@ def on_doctype_update(): frappe.db.commit() frappe.db.sql("""alter table `tabComment` add index comment_doctype_docname_index(comment_doctype, comment_docname)""") - diff --git a/frappe/core/doctype/docfield/docfield.py b/frappe/core/doctype/docfield/docfield.py index 9dae3aea53..6b53be3288 100644 --- a/frappe/core/doctype/docfield/docfield.py +++ b/frappe/core/doctype/docfield/docfield.py @@ -6,5 +6,4 @@ from __future__ import unicode_literals from frappe.model.document import Document class DocField(Document): - __doclink__ = "https://frappe.io/docs/models/core/docfield" pass diff --git a/frappe/core/doctype/docperm/docperm.json b/frappe/core/doctype/docperm/docperm.json index 04f8b0c19e..971f50911e 100644 --- a/frappe/core/doctype/docperm/docperm.json +++ b/frappe/core/doctype/docperm/docperm.json @@ -33,6 +33,14 @@ "label": "Apply User Permissions", "permlevel": 0 }, + { + "description": "Apply this rule if the User is the Owner", + "fieldname": "if_owner", + "fieldtype": "Check", + "label": "If user is the owner", + "permlevel": 0, + "precision": "" + }, { "fieldname": "column_break_2", "fieldtype": "Column Break", @@ -232,7 +240,7 @@ "idx": 1, "issingle": 0, "istable": 1, - "modified": "2015-03-18 06:09:58.928014", + "modified": "2015-07-22 07:39:40.471092", "modified_by": "Administrator", "module": "Core", "name": "DocPerm", diff --git a/frappe/core/doctype/docperm/docperm.py b/frappe/core/doctype/docperm/docperm.py index 919ed1179e..36ed9acbe6 100644 --- a/frappe/core/doctype/docperm/docperm.py +++ b/frappe/core/doctype/docperm/docperm.py @@ -7,5 +7,4 @@ import frappe from frappe.model.document import Document class DocPerm(Document): - __doclink__ = "https://frappe.io/docs/models/v5.x/core/docperm" pass diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 170fc72989..c0145e4bec 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -18,7 +18,6 @@ form_grid_templates = { } class DocType(Document): - __doclink__ = "https://frappe.io/docs/models/core/doctype" def get_feed(self): return self.name @@ -363,14 +362,21 @@ def validate_permissions(doctype, for_remove=False): def check_double(d): has_similar = False + similar_because_of = "" for p in permissions: - if (p.role==d.role and p.permlevel==d.permlevel - and p.apply_user_permissions==d.apply_user_permissions and p!=d): - has_similar = True - break + if p.role==d.role and p.permlevel==d.permlevel and p!=d: + if p.apply_user_permissions==d.apply_user_permissions: + has_similar = True + similar_because_of = _("Apply User Permissions") + break + elif p.if_owner==d.if_owner: + similar_because_of = _("If Owner") + has_similar = True + break if has_similar: - frappe.throw(_("{0}: Only one rule allowed with the same Role, Level and Apply User Permissions").format(get_txt(d))) + frappe.throw(_("{0}: Only one rule allowed with the same Role, Level and {1}")\ + .format(get_txt(d), similar_because_of)) def check_level_zero_is_set(d): if cint(d.permlevel) > 0 and d.role != 'All': @@ -467,4 +473,3 @@ def init_list(doctype): doc = frappe.get_meta(doctype) make_boilerplate("controller_list.js", doc) make_boilerplate("controller_list.html", doc) - diff --git a/frappe/core/doctype/module_def/module_def.py b/frappe/core/doctype/module_def/module_def.py index 75b7e14825..44df59cb52 100644 --- a/frappe/core/doctype/module_def/module_def.py +++ b/frappe/core/doctype/module_def/module_def.py @@ -7,7 +7,6 @@ import frappe, os from frappe.model.document import Document class ModuleDef(Document): - __doclink__ = "https://frappe.io/docs/models/core/module_def" def on_update(self): """If in `developer_mode`, create folder for module and add in `modules.txt` of app if missing.""" @@ -39,7 +38,3 @@ class ModuleDef(Document): frappe.clear_cache() frappe.setup_module_map() - - - - diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py index fef48e2f0b..9c4059d792 100644 --- a/frappe/core/doctype/page/page.py +++ b/frappe/core/doctype/page/page.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.build import html_to_js_template +from frappe import conf class Page(Document): def autoname(self): @@ -36,7 +37,6 @@ class Page(Document): Writes the .txt for this page and if write_content is checked, it will write out a .html file """ - from frappe import conf from frappe.core.doctype.doctype.doctype import make_module_and_roles make_module_and_roles(self, "roles") diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 51fc55df99..e1789d655d 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -1,519 +1,519 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 1, - "creation": "2014-03-11 14:55:00", - "description": "Represents a User in the system.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Master", + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 1, + "creation": "2014-03-11 14:55:00", + "description": "Represents a User in the system.", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Master", "fields": [ { - "fieldname": "sb0_5", - "fieldtype": "Section Break", - "label": "", + "fieldname": "sb0_5", + "fieldtype": "Section Break", + "label": "", "permlevel": 0 - }, + }, { - "default": "1", - "fieldname": "enabled", - "fieldtype": "Check", - "in_list_view": 0, - "label": "Enabled", - "no_copy": 0, - "oldfieldname": "enabled", - "oldfieldtype": "Check", - "permlevel": 0, + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "in_list_view": 0, + "label": "Enabled", + "no_copy": 0, + "oldfieldname": "enabled", + "oldfieldtype": "Check", + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "section_break_3", - "fieldtype": "Section Break", - "permlevel": 0, + "fieldname": "section_break_3", + "fieldtype": "Section Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "label": "Email", - "no_copy": 1, - "oldfieldname": "email", - "oldfieldtype": "Data", - "options": "Email", - "permlevel": 0, - "reqd": 1, + "fieldname": "email", + "fieldtype": "Data", + "hidden": 0, + "label": "Email", + "no_copy": 1, + "oldfieldname": "email", + "oldfieldtype": "Data", + "options": "Email", + "permlevel": 0, + "reqd": 1, "search_index": 0 - }, + }, { - "fieldname": "first_name", - "fieldtype": "Data", - "in_list_view": 0, - "label": "First Name", - "no_copy": 0, - "oldfieldname": "first_name", - "oldfieldtype": "Data", - "permlevel": 0, + "fieldname": "first_name", + "fieldtype": "Data", + "in_list_view": 0, + "label": "First Name", + "no_copy": 0, + "oldfieldname": "first_name", + "oldfieldtype": "Data", + "permlevel": 0, "reqd": 1 - }, + }, { - "fieldname": "middle_name", - "fieldtype": "Data", - "label": "Middle Name (Optional)", - "no_copy": 0, - "oldfieldname": "middle_name", - "oldfieldtype": "Data", + "fieldname": "middle_name", + "fieldtype": "Data", + "label": "Middle Name (Optional)", + "no_copy": 0, + "oldfieldname": "middle_name", + "oldfieldtype": "Data", "permlevel": 0 - }, + }, { - "fieldname": "last_name", - "fieldtype": "Data", - "in_list_view": 0, - "label": "Last Name", - "oldfieldname": "last_name", - "oldfieldtype": "Data", + "fieldname": "last_name", + "fieldtype": "Data", + "in_list_view": 0, + "label": "Last Name", + "oldfieldname": "last_name", + "oldfieldtype": "Data", "permlevel": 0 - }, + }, { - "default": "1", - "depends_on": "eval:doc.__islocal", - "fieldname": "send_welcome_email", - "fieldtype": "Check", - "label": "Send Welcome Email", - "permlevel": 0, + "default": "1", + "depends_on": "eval:doc.__islocal", + "fieldname": "send_welcome_email", + "fieldtype": "Check", + "label": "Send Welcome Email", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "unsubscribed", - "fieldtype": "Check", - "hidden": 1, - "label": "Unsubscribed", - "no_copy": 1, + "fieldname": "unsubscribed", + "fieldtype": "Check", + "hidden": 1, + "label": "Unsubscribed", + "no_copy": 1, "permlevel": 0 - }, + }, { - "fieldname": "column_break0", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_width": "50%", + "fieldname": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "permlevel": 0, + "print_width": "50%", "width": "50%" - }, + }, { - "description": "", - "fieldname": "language", - "fieldtype": "Select", - "label": "Language", - "options": "Loading...", + "description": "", + "fieldname": "language", + "fieldtype": "Select", + "label": "Language", + "options": "Loading...", "permlevel": 0 - }, + }, { - "description": "", - "fieldname": "time_zone", - "fieldtype": "Select", - "label": "Timezone", + "description": "", + "fieldname": "time_zone", + "fieldtype": "Select", + "label": "Timezone", "permlevel": 0 - }, + }, { - "depends_on": "eval:!doc.__islocal", - "fieldname": "change_password", - "fieldtype": "Section Break", - "label": "", + "depends_on": "eval:!doc.__islocal", + "fieldname": "change_password", + "fieldtype": "Section Break", + "label": "", "permlevel": 0 - }, + }, { - "fieldname": "new_password", - "fieldtype": "Password", - "label": "Set New Password", - "no_copy": 1, + "fieldname": "new_password", + "fieldtype": "Password", + "label": "Set New Password", + "no_copy": 1, "permlevel": 0 - }, + }, { - "depends_on": "", - "fieldname": "send_password_update_notification", - "fieldtype": "Check", - "label": "Send Password Update Notification", - "permlevel": 0, + "depends_on": "", + "fieldname": "send_password_update_notification", + "fieldtype": "Check", + "label": "Send Password Update Notification", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "reset_password_key", - "fieldtype": "Data", - "hidden": 1, - "label": "Reset Password Key", - "no_copy": 1, - "permlevel": 0, - "print_hide": 1, + "fieldname": "reset_password_key", + "fieldtype": "Data", + "hidden": 1, + "label": "Reset Password Key", + "no_copy": 1, + "permlevel": 0, + "print_hide": 1, "read_only": 1 - }, + }, { - "depends_on": "eval:!doc.__islocal", - "fieldname": "display_settings", - "fieldtype": "Section Break", - "label": "", + "depends_on": "eval:!doc.__islocal", + "fieldname": "display_settings", + "fieldtype": "Section Break", + "label": "", "permlevel": 0 - }, + }, { - "description": "Get your globally recognized avatar from Gravatar.com", - "fieldname": "user_image", - "fieldtype": "Attach", - "hidden": 0, - "label": "User Image", - "no_copy": 1, + "description": "Get your globally recognized avatar from Gravatar.com", + "fieldname": "user_image", + "fieldtype": "Attach", + "hidden": 0, + "label": "User Image", + "no_copy": 1, "permlevel": 0 - }, + }, { - "fieldname": "cb21", - "fieldtype": "Column Break", + "fieldname": "cb21", + "fieldtype": "Column Break", "permlevel": 0 - }, + }, { - "fieldname": "user_image_show", - "fieldtype": "Image", - "label": "user_image_show", - "options": "user_image", + "fieldname": "user_image_show", + "fieldtype": "Image", + "label": "user_image_show", + "options": "user_image", "permlevel": 0 - }, + }, { - "fieldname": "email_settings", - "fieldtype": "Section Break", - "label": "Email Settings", - "permlevel": 0, + "fieldname": "email_settings", + "fieldtype": "Section Break", + "label": "Email Settings", + "permlevel": 0, "precision": "" - }, + }, { - "default": "1", - "fieldname": "thread_notify", - "fieldtype": "Check", - "label": "Send Notifications for Transactions I Follow", - "permlevel": 0, + "default": "1", + "fieldname": "thread_notify", + "fieldtype": "Check", + "label": "Send Notifications for Transactions I Follow", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "email_signature", - "fieldtype": "Small Text", - "label": "Email Signature", - "no_copy": 1, + "fieldname": "email_signature", + "fieldtype": "Small Text", + "label": "Email Signature", + "no_copy": 1, "permlevel": 0 - }, + }, { - "fieldname": "background", - "fieldtype": "Section Break", - "label": "", - "permlevel": 0, + "fieldname": "background", + "fieldtype": "Section Break", + "label": "", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "background_image", - "fieldtype": "Attach", - "label": "Background Image", - "permlevel": 0, + "fieldname": "background_image", + "fieldtype": "Attach", + "label": "Background Image", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "background_style", - "fieldtype": "Select", - "label": "Background Style", - "options": "Fill Screen\nTile", - "permlevel": 0, + "fieldname": "background_style", + "fieldtype": "Select", + "label": "Background Style", + "options": "Fill Screen\nTile", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "short_bio", - "fieldtype": "Section Break", - "label": "", + "fieldname": "short_bio", + "fieldtype": "Section Break", + "label": "", "permlevel": 0 - }, + }, { - "fieldname": "gender", - "fieldtype": "Select", - "label": "Gender", - "oldfieldname": "gender", - "oldfieldtype": "Select", - "options": "\nMale\nFemale\nOther", - "permlevel": 0, + "fieldname": "gender", + "fieldtype": "Select", + "label": "Gender", + "oldfieldname": "gender", + "oldfieldtype": "Select", + "options": "\nMale\nFemale\nOther", + "permlevel": 0, "search_index": 0 - }, + }, { - "fieldname": "birth_date", - "fieldtype": "Date", - "label": "Birth Date", - "no_copy": 1, - "oldfieldname": "birth_date", - "oldfieldtype": "Date", + "fieldname": "birth_date", + "fieldtype": "Date", + "label": "Birth Date", + "no_copy": 1, + "oldfieldname": "birth_date", + "oldfieldtype": "Date", "permlevel": 0 - }, + }, { - "fieldname": "location", - "fieldtype": "Data", - "label": "Location", - "no_copy": 1, + "fieldname": "location", + "fieldtype": "Data", + "label": "Location", + "no_copy": 1, "permlevel": 0 - }, + }, { - "fieldname": "column_break_22", - "fieldtype": "Column Break", + "fieldname": "column_break_22", + "fieldtype": "Column Break", "permlevel": 0 - }, + }, { - "fieldname": "bio", - "fieldtype": "Small Text", - "label": "Bio", - "no_copy": 1, - "permlevel": 0, + "fieldname": "bio", + "fieldtype": "Small Text", + "label": "Bio", + "no_copy": 1, + "permlevel": 0, "set_only_once": 0 - }, + }, { - "description": "Check / Uncheck roles assigned to the User. Click on the Role to find out what permissions that Role has.", - "fieldname": "sb1", - "fieldtype": "Section Break", - "label": "Roles", - "permlevel": 1, + "description": "Check / Uncheck roles assigned to the User. Click on the Role to find out what permissions that Role has.", + "fieldname": "sb1", + "fieldtype": "Section Break", + "label": "Roles", + "permlevel": 1, "read_only": 1 - }, + }, { - "fieldname": "roles_html", - "fieldtype": "HTML", - "label": "Roles HTML", - "permlevel": 0, + "fieldname": "roles_html", + "fieldtype": "HTML", + "label": "Roles HTML", + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "user_roles", - "fieldtype": "Table", - "hidden": 1, - "label": "Roles Assigned", - "options": "UserRole", - "permlevel": 1, - "print_hide": 1, + "fieldname": "user_roles", + "fieldtype": "Table", + "hidden": 1, + "label": "Roles Assigned", + "options": "UserRole", + "permlevel": 1, + "print_hide": 1, "read_only": 1 - }, + }, { - "default": "", - "description": "Uncheck modules to hide from user's desktop", - "fieldname": "modules_access", - "fieldtype": "Section Break", - "label": "Modules Access", - "permlevel": 1, + "default": "", + "description": "Uncheck modules to hide from user's desktop", + "fieldname": "modules_access", + "fieldtype": "Section Break", + "label": "Modules Access", + "permlevel": 1, "precision": "" - }, + }, { - "fieldname": "modules_html", - "fieldtype": "HTML", - "label": "Modules HTML", - "permlevel": 1, + "fieldname": "modules_html", + "fieldtype": "HTML", + "label": "Modules HTML", + "permlevel": 1, "precision": "" - }, + }, { - "fieldname": "block_modules", - "fieldtype": "Table", - "hidden": 1, - "label": "Block Modules", - "options": "Block Module", - "permlevel": 1, + "fieldname": "block_modules", + "fieldtype": "Table", + "hidden": 1, + "label": "Block Modules", + "options": "Block Module", + "permlevel": 1, "precision": "" - }, + }, { - "description": "These values will be automatically updated in transactions and also will be useful to restrict permissions for this user on transactions containing these values.", - "fieldname": "sb2", - "fieldtype": "Section Break", - "hidden": 1, - "label": "Defaults", - "oldfieldtype": "Column Break", - "permlevel": 1, - "print_width": "50%", - "read_only": 1, + "description": "These values will be automatically updated in transactions and also will be useful to restrict permissions for this user on transactions containing these values.", + "fieldname": "sb2", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Defaults", + "oldfieldtype": "Column Break", + "permlevel": 1, + "print_width": "50%", + "read_only": 1, "width": "50%" - }, + }, { - "description": "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields, go to Customize Form.", - "fieldname": "defaults", - "fieldtype": "Table", - "hidden": 1, - "label": "User Defaults", - "no_copy": 1, - "options": "DefaultValue", + "description": "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields, go to \"Customize Form\".", + "fieldname": "defaults", + "fieldtype": "Table", + "hidden": 1, + "label": "User Defaults", + "no_copy": 1, + "options": "DefaultValue", "permlevel": 0 - }, + }, { - "fieldname": "sb3", - "fieldtype": "Section Break", - "label": "Security Settings", - "oldfieldtype": "Section Break", - "permlevel": 0, + "fieldname": "sb3", + "fieldtype": "Section Break", + "label": "Security Settings", + "oldfieldtype": "Section Break", + "permlevel": 0, "read_only": 1 - }, + }, { - "default": "System User", - "description": "User Type \"System User\" can access Desktop. \"Website User\" can only be logged into the website and portal pages. ", - "fieldname": "user_type", - "fieldtype": "Select", - "in_list_view": 1, - "label": "User Type", - "oldfieldname": "user_type", - "oldfieldtype": "Select", - "options": "System User\nWebsite User", - "permlevel": 0, - "read_only": 1, + "default": "System User", + "description": "User Type \"System User\" can access Desktop. \"Website User\" can only be logged into the website and portal pages. ", + "fieldname": "user_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "User Type", + "oldfieldname": "user_type", + "oldfieldtype": "Select", + "options": "System User\nWebsite User", + "permlevel": 0, + "read_only": 1, "reqd": 1 - }, + }, { - "description": "Allow user to login only after this hour (0-24)", - "fieldname": "login_after", - "fieldtype": "Int", - "label": "Login After", - "permlevel": 0, + "description": "Allow user to login only after this hour (0-24)", + "fieldname": "login_after", + "fieldtype": "Int", + "label": "Login After", + "permlevel": 0, "read_only": 1 - }, + }, { - "description": "Allow user to login only before this hour (0-24)", - "fieldname": "login_before", - "fieldtype": "Int", - "label": "Login Before", - "permlevel": 0, + "description": "Allow user to login only before this hour (0-24)", + "fieldname": "login_before", + "fieldtype": "Int", + "label": "Login Before", + "permlevel": 0, "read_only": 1 - }, + }, { - "description": "Restrict user from this IP address only. Multiple IP addresses can be added by separating with commas. Also accepts partial IP addresses like (111.111.111)", - "fieldname": "restrict_ip", - "fieldtype": "Data", - "label": "Restrict IP", - "permlevel": 0, + "description": "Restrict user from this IP address only. Multiple IP addresses can be added by separating with commas. Also accepts partial IP addresses like (111.111.111)", + "fieldname": "restrict_ip", + "fieldtype": "Data", + "label": "Restrict IP", + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "column_break1", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_width": "50%", + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "permlevel": 0, + "print_width": "50%", "width": "50%" - }, + }, { - "fieldname": "last_login", - "fieldtype": "Read Only", - "hidden": 0, - "label": "Last Login", - "no_copy": 1, - "oldfieldname": "last_login", - "oldfieldtype": "Read Only", - "permlevel": 0, - "read_only": 1, - "reqd": 0, + "fieldname": "last_login", + "fieldtype": "Read Only", + "hidden": 0, + "label": "Last Login", + "no_copy": 1, + "oldfieldname": "last_login", + "oldfieldtype": "Read Only", + "permlevel": 0, + "read_only": 1, + "reqd": 0, "search_index": 0 - }, + }, { - "fieldname": "last_ip", - "fieldtype": "Read Only", - "label": "Last IP", - "no_copy": 1, - "oldfieldname": "last_ip", - "oldfieldtype": "Read Only", - "permlevel": 0, + "fieldname": "last_ip", + "fieldtype": "Read Only", + "label": "Last IP", + "no_copy": 1, + "oldfieldname": "last_ip", + "oldfieldtype": "Read Only", + "permlevel": 0, "read_only": 1 - }, + }, { - "description": "Stores the JSON of last known versions of various installed apps. It is used to show release notes.", - "fieldname": "last_known_versions", - "fieldtype": "Text", - "hidden": 1, - "label": "Last Known Versions", - "permlevel": 0, - "precision": "", + "description": "Stores the JSON of last known versions of various installed apps. It is used to show release notes.", + "fieldname": "last_known_versions", + "fieldtype": "Text", + "hidden": 1, + "label": "Last Known Versions", + "permlevel": 0, + "precision": "", "read_only": 1 - }, + }, { - "fieldname": "third_party_authentication", - "fieldtype": "Section Break", - "label": "Third Party Authentication", + "fieldname": "third_party_authentication", + "fieldtype": "Section Break", + "label": "Third Party Authentication", "permlevel": 1 - }, + }, { - "fieldname": "fb_username", - "fieldtype": "Data", - "label": "Facebook Username", - "no_copy": 1, - "permlevel": 0, + "fieldname": "fb_username", + "fieldtype": "Data", + "label": "Facebook Username", + "no_copy": 1, + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "fb_userid", - "fieldtype": "Data", - "label": "Facebook User ID", - "no_copy": 1, - "permlevel": 0, + "fieldname": "fb_userid", + "fieldtype": "Data", + "label": "Facebook User ID", + "no_copy": 1, + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "google_userid", - "fieldtype": "Data", - "label": "Google User ID", - "no_copy": 1, - "permlevel": 0, + "fieldname": "google_userid", + "fieldtype": "Data", + "label": "Google User ID", + "no_copy": 1, + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "column_break_49", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_49", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "github_userid", - "fieldtype": "Data", - "label": "Github User ID", - "no_copy": 1, - "permlevel": 0, + "fieldname": "github_userid", + "fieldtype": "Data", + "label": "Github User ID", + "no_copy": 1, + "permlevel": 0, "read_only": 1 - }, + }, { - "fieldname": "github_username", - "fieldtype": "Data", - "label": "Github Username", - "no_copy": 1, - "permlevel": 0, + "fieldname": "github_username", + "fieldtype": "Data", + "label": "Github Username", + "no_copy": 1, + "permlevel": 0, "read_only": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-user", - "idx": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 5, - "modified": "2015-06-01 01:00:32.901851", - "modified_by": "Administrator", - "module": "Core", - "name": "User", - "owner": "Administrator", + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "icon-user", + "idx": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 5, + "modified": "2015-07-27 01:00:32.901851", + "modified_by": "Administrator", + "module": "Core", + "name": "User", + "owner": "Administrator", "permissions": [ { - "create": 1, - "delete": 1, - "email": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "permlevel": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "permlevel": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "submit": 0, "write": 1 } - ], - "read_only": 0, + ], + "read_only": 0, "search_fields": "first_name, last_name" -} \ No newline at end of file +} diff --git a/frappe/core/page/data_import_tool/data_import_tool.py b/frappe/core/page/data_import_tool/data_import_tool.py index 8c44c7088a..6b3ec60f72 100644 --- a/frappe/core/page/data_import_tool/data_import_tool.py +++ b/frappe/core/page/data_import_tool/data_import_tool.py @@ -30,12 +30,13 @@ def get_doctype_options(): doctype = frappe.form_dict['doctype'] return [doctype] + [d.options for d in frappe.get_meta(doctype).get_table_fields()] -def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False): +def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False, pre_process=None): from frappe.utils.csvutils import read_csv_content from frappe.core.page.data_import_tool.importer import upload print "Importing " + path with open(path, "r") as infile: - upload(rows = read_csv_content(infile.read()), ignore_links=ignore_links, overwrite=overwrite, submit_after_import=submit) + upload(rows = read_csv_content(infile.read()), ignore_links=ignore_links, overwrite=overwrite, + submit_after_import=submit, pre_process=pre_process) def export_csv(doctype, path): from frappe.core.page.data_import_tool.exporter import get_template @@ -80,7 +81,8 @@ def export_fixture(doctype, app): export_json(doctype, frappe.get_app_path(app, "fixtures", frappe.scrub(doctype) + ".json")) -def import_doc(path, overwrite=False, ignore_links=False, ignore_insert=False, insert=False, submit=False): +def import_doc(path, overwrite=False, ignore_links=False, ignore_insert=False, + insert=False, submit=False, pre_process=None): if os.path.isdir(path): files = [os.path.join(path, f) for f in os.listdir(path)] else: @@ -89,8 +91,8 @@ def import_doc(path, overwrite=False, ignore_links=False, ignore_insert=False, i for f in files: if f.endswith(".json"): frappe.flags.mute_emails = True - frappe.modules.import_file.import_file_by_path(f, data_import=True, force=True) + frappe.modules.import_file.import_file_by_path(f, data_import=True, force=True, pre_process=pre_process) frappe.flags.mute_emails = False elif f.endswith(".csv"): - import_file_by_path(f, ignore_links=ignore_links, overwrite=overwrite, submit=submit) + import_file_by_path(f, ignore_links=ignore_links, overwrite=overwrite, submit=submit, pre_process=pre_process) frappe.db.commit() diff --git a/frappe/core/page/data_import_tool/importer.py b/frappe/core/page/data_import_tool/importer.py index 1ad6256b27..f05a7638f4 100644 --- a/frappe/core/page/data_import_tool/importer.py +++ b/frappe/core/page/data_import_tool/importer.py @@ -15,7 +15,8 @@ from frappe.utils import cint, cstr, flt from frappe.core.page.data_import_tool.data_import_tool import get_data_keys @frappe.whitelist() -def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=None, ignore_links=False): +def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=None, + ignore_links=False, pre_process=None): """upload data""" frappe.flags.mute_emails = True # extra input params @@ -200,6 +201,9 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, doc = None doc = get_doc(row_idx) + if pre_process: + pre_process(doc) + try: frappe.local.message_log = [] if parentfield: diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js index b9264833ff..d2dbe1a6f1 100644 --- a/frappe/core/page/permission_manager/permission_manager.js +++ b/frappe/core/page/permission_manager/permission_manager.js @@ -191,6 +191,7 @@ frappe.PermissionEngine = Class.extend({ if (d.permlevel===0) { me.setup_user_permissions(d, role_cell); + me.setup_if_owner(d, role_cell); } var cell = me.add_cell(row, d, "permlevel"); @@ -269,6 +270,12 @@ frappe.PermissionEngine = Class.extend({ d.help = ""; }, + setup_if_owner: function(d, role_cell) { + var checkbox = this.add_check(role_cell, d, "if_owner") + .removeClass("col-md-4") + .css({"margin-top": "15px"}); + }, + rights: ["read", "write", "create", "delete", "submit", "cancel", "amend", "print", "email", "report", "import", "export", "set_user_permissions"], diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 0dfd3f222a..4960e5aacf 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -19,7 +19,7 @@ def get_roles_and_doctypes(): name not in ('DocType') and exists(select * from `tabDocField` where parent=dt.name)""")], "roles": [d[0] for d in frappe.db.sql("""select name from tabRole where name not in - ('Guest', 'Administrator')""")] + ('Administrator')""")] } @frappe.whitelist() diff --git a/frappe/custom/doctype/custom_field/custom_field.js b/frappe/custom/doctype/custom_field/custom_field.js index 6e154f6520..81c37ec3fc 100644 --- a/frappe/custom/doctype/custom_field/custom_field.js +++ b/frappe/custom/doctype/custom_field/custom_field.js @@ -52,7 +52,7 @@ cur_frm.cscript.fieldtype = function(doc, dt, dn) { __('Name of the Document Type (DocType) you want this field to be linked to. e.g. Customer'); } else if(doc.fieldtype == 'Select') { cur_frm.fields_dict['options_help'].disp_area.innerHTML = - __('Options for select. Each option on a new line. e.g.:
Option 1
Option 2
Option 3
'); + __('Options for select. Each option on a new line.')+' '+__('e.g.:')+'
'+__('Option 1')+'
'+__('Option 2')+'
'+__('Option 3')+'
'; } else if(doc.fieldtype == 'Dynamic Link') { cur_frm.fields_dict['options_help'].disp_area.innerHTML = __('Fieldname which will be the DocType for this link field.'); diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 2334905ac4..8b45d72288 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -1,136 +1,136 @@ { - "autoname": "DL.####", - "creation": "2013-01-29 17:55:08", - "docstatus": 0, - "doctype": "DocType", + "autoname": "DL.####", + "creation": "2013-01-29 17:55:08", + "docstatus": 0, + "doctype": "DocType", "fields": [ { - "fieldname": "doc_type", - "fieldtype": "Link", - "hidden": 0, - "in_list_view": 1, - "label": "Enter Form Type", - "no_copy": 0, - "options": "DocType", - "permlevel": 0, + "fieldname": "doc_type", + "fieldtype": "Link", + "hidden": 0, + "in_list_view": 1, + "label": "Enter Form Type", + "no_copy": 0, + "options": "DocType", + "permlevel": 0, "search_index": 0 - }, + }, { - "depends_on": "doc_type", - "fieldname": "properties", - "fieldtype": "Section Break", - "label": "", + "depends_on": "doc_type", + "fieldname": "properties", + "fieldtype": "Section Break", + "label": "", "permlevel": 0 - }, + }, { - "fieldname": "default_print_format", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Default Print Format", - "no_copy": 0, - "options": "Print Format", - "permlevel": 0, + "fieldname": "default_print_format", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Default Print Format", + "no_copy": 0, + "options": "Print Format", + "permlevel": 0, "search_index": 0 - }, + }, { - "description": "Fields separated by comma (,) will be included in the
Search By list of Search dialog box", - "fieldname": "search_fields", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Search Fields", - "no_copy": 0, - "permlevel": 0, + "description": "Fields separated by comma (,) will be included in the \"Search By\" list of Search dialog box", + "fieldname": "search_fields", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Search Fields", + "no_copy": 0, + "permlevel": 0, "search_index": 0 - }, + }, { - "fieldname": "column_break_5", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_5", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "", - "fieldname": "max_attachments", - "fieldtype": "Int", - "label": "Max Attachments", - "no_copy": 0, - "permlevel": 0, + "depends_on": "", + "fieldname": "max_attachments", + "fieldtype": "Int", + "label": "Max Attachments", + "no_copy": 0, + "permlevel": 0, "search_index": 0 - }, + }, { - "fieldname": "allow_copy", - "fieldtype": "Check", - "label": "Hide Copy", - "no_copy": 0, - "permlevel": 0, + "fieldname": "allow_copy", + "fieldtype": "Check", + "label": "Hide Copy", + "no_copy": 0, + "permlevel": 0, "search_index": 0 - }, + }, { - "depends_on": "doc_type", - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "permlevel": 0, + "depends_on": "doc_type", + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "sort_field", - "fieldtype": "Select", - "label": "Sort Field", + "fieldname": "sort_field", + "fieldtype": "Select", + "label": "Sort Field", "permlevel": 0 - }, + }, { - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "sort_order", - "fieldtype": "Select", - "label": "Sort Order", - "options": "ASC\nDESC", + "fieldname": "sort_order", + "fieldtype": "Select", + "label": "Sort Order", + "options": "ASC\nDESC", "permlevel": 0 - }, + }, { - "depends_on": "doc_type", - "description": "Customize Label, Print Hide, Default etc.", - "fieldname": "fields_section_break", - "fieldtype": "Section Break", - "label": "Fields", + "depends_on": "doc_type", + "description": "Customize Label, Print Hide, Default etc.", + "fieldname": "fields_section_break", + "fieldtype": "Section Break", + "label": "Fields", "permlevel": 0 - }, + }, { - "fieldname": "fields", - "fieldtype": "Table", - "label": "Fields", - "no_copy": 0, - "options": "Customize Form Field", - "permlevel": 0, + "fieldname": "fields", + "fieldtype": "Table", + "label": "Fields", + "no_copy": 0, + "options": "Customize Form Field", + "permlevel": 0, "search_index": 0 } - ], - "hide_toolbar": 1, - "icon": "icon-glass", - "idx": 1, - "issingle": 1, - "modified": "2015-03-25 06:18:19.010091", - "modified_by": "Administrator", - "module": "Custom", - "name": "Customize Form", - "owner": "Administrator", + ], + "hide_toolbar": 1, + "icon": "icon-glass", + "idx": 1, + "issingle": 1, + "modified": "2015-07-27 01:00:32.901851", + "modified_by": "Administrator", + "module": "Custom", + "name": "Customize Form", + "owner": "Administrator", "permissions": [ { - "create": 1, - "email": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "share": 1, + "submit": 0, "write": 1 } - ], + ], "search_fields": "doc_type" -} \ No newline at end of file +} diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index 539178832d..356fd1d1a0 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -1,304 +1,304 @@ { - "allow_copy": 0, - "autoname": "hash", - "creation": "2013-02-22 01:27:32", - "docstatus": 0, - "doctype": "DocType", + "allow_copy": 0, + "autoname": "hash", + "creation": "2013-02-22 01:27:32", + "docstatus": 0, + "doctype": "DocType", "fields": [ { - "fieldname": "label_and_type", - "fieldtype": "Section Break", - "label": "Label and Type", - "permlevel": 0, + "fieldname": "label_and_type", + "fieldtype": "Section Break", + "label": "Label and Type", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "label", - "fieldtype": "Data", - "hidden": 0, - "in_list_view": 1, - "label": "Label", - "oldfieldname": "label", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "reqd": 0, + "fieldname": "label", + "fieldtype": "Data", + "hidden": 0, + "in_list_view": 1, + "label": "Label", + "oldfieldname": "label", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "reqd": 0, "search_index": 1 - }, + }, { - "default": "Data", - "fieldname": "fieldtype", - "fieldtype": "Select", - "hidden": 0, - "in_list_view": 1, - "label": "Type", - "oldfieldname": "fieldtype", - "oldfieldtype": "Select", - "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", - "permlevel": 0, - "print_hide": 0, - "reqd": 1, + "default": "Data", + "fieldname": "fieldtype", + "fieldtype": "Select", + "hidden": 0, + "in_list_view": 1, + "label": "Type", + "oldfieldname": "fieldtype", + "oldfieldtype": "Select", + "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", + "permlevel": 0, + "print_hide": 0, + "reqd": 1, "search_index": 1 - }, + }, { - "fieldname": "fieldname", - "fieldtype": "Data", - "hidden": 0, - "in_list_view": 1, - "label": "Name", - "oldfieldname": "fieldname", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "read_only": 1, - "reqd": 0, + "fieldname": "fieldname", + "fieldtype": "Data", + "hidden": 0, + "in_list_view": 1, + "label": "Name", + "oldfieldname": "fieldname", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "read_only": 1, + "reqd": 0, "search_index": 1 - }, + }, { - "fieldname": "reqd", - "fieldtype": "Check", - "hidden": 0, - "label": "Mandatory", - "oldfieldname": "reqd", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, + "fieldname": "reqd", + "fieldtype": "Check", + "hidden": 0, + "label": "Mandatory", + "oldfieldname": "reqd", + "oldfieldtype": "Check", + "permlevel": 0, + "print_hide": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, "width": "50px" - }, + }, { - "fieldname": "unique", - "fieldtype": "Check", - "label": "Unique", - "permlevel": 0, + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "in_list_view", - "fieldtype": "Check", - "label": "In List View", + "fieldname": "in_list_view", + "fieldtype": "Check", + "label": "In List View", "permlevel": 0 - }, + }, { - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", - "description": "Set non-standard precision for a Float or Currency field", - "fieldname": "precision", - "fieldtype": "Select", - "label": "Precision", - "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9", - "permlevel": 0, + "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", + "description": "Set non-standard precision for a Float or Currency field", + "fieldname": "precision", + "fieldtype": "Select", + "label": "Precision", + "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9", + "permlevel": 0, "precision": "" - }, + }, { - "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", - "fieldname": "options", - "fieldtype": "Text", - "hidden": 0, - "in_list_view": 1, - "label": "Options", - "oldfieldname": "options", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "reqd": 0, + "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", + "fieldname": "options", + "fieldtype": "Text", + "hidden": 0, + "in_list_view": 1, + "label": "Options", + "oldfieldname": "options", + "oldfieldtype": "Text", + "permlevel": 0, + "print_hide": 0, + "reqd": 0, "search_index": 0 - }, + }, { - "fieldname": "permissions", - "fieldtype": "Section Break", - "label": "Permissions", - "permlevel": 0, + "fieldname": "permissions", + "fieldtype": "Section Break", + "label": "Permissions", + "permlevel": 0, "precision": "" - }, + }, { - "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples):
\nmyfield\neval:doc.myfield=='My Value'
\neval:doc.age>18", - "fieldname": "depends_on", - "fieldtype": "Data", - "hidden": 0, - "label": "Depends On", - "oldfieldname": "depends_on", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, + "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18", + "fieldname": "depends_on", + "fieldtype": "Data", + "hidden": 0, + "label": "Depends On", + "oldfieldname": "depends_on", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, "reqd": 0 - }, + }, { - "default": "0", - "fieldname": "permlevel", - "fieldtype": "Int", - "hidden": 0, - "in_list_view": 1, - "label": "Perm Level", - "oldfieldname": "permlevel", - "oldfieldtype": "Int", - "permlevel": 0, - "print_hide": 0, - "reqd": 0, + "default": "0", + "fieldname": "permlevel", + "fieldtype": "Int", + "hidden": 0, + "in_list_view": 1, + "label": "Perm Level", + "oldfieldname": "permlevel", + "oldfieldtype": "Int", + "permlevel": 0, + "print_hide": 0, + "reqd": 0, "search_index": 0 - }, + }, { - "fieldname": "hidden", - "fieldtype": "Check", - "hidden": 0, - "label": "Hidden", - "oldfieldname": "hidden", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, + "fieldname": "hidden", + "fieldtype": "Check", + "hidden": 0, + "label": "Hidden", + "oldfieldname": "hidden", + "oldfieldtype": "Check", + "permlevel": 0, + "print_hide": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, "width": "50px" - }, + }, { - "fieldname": "column_break_14", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_14", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "ignore_user_permissions", - "fieldtype": "Check", - "label": "Ignore User Permissions", + "fieldname": "ignore_user_permissions", + "fieldtype": "Check", + "label": "Ignore User Permissions", "permlevel": 0 - }, + }, { - "fieldname": "allow_on_submit", - "fieldtype": "Check", - "hidden": 0, - "label": "Allow on Submit", - "oldfieldname": "allow_on_submit", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, + "fieldname": "allow_on_submit", + "fieldtype": "Check", + "hidden": 0, + "label": "Allow on Submit", + "oldfieldname": "allow_on_submit", + "oldfieldtype": "Check", + "permlevel": 0, + "print_hide": 0, "reqd": 0 - }, + }, { - "fieldname": "report_hide", - "fieldtype": "Check", - "hidden": 0, - "label": "Report Hide", - "oldfieldname": "report_hide", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, + "fieldname": "report_hide", + "fieldtype": "Check", + "hidden": 0, + "label": "Report Hide", + "oldfieldname": "report_hide", + "oldfieldtype": "Check", + "permlevel": 0, + "print_hide": 0, "reqd": 0 - }, + }, { - "fieldname": "display", - "fieldtype": "Section Break", - "label": "Display", - "permlevel": 0, + "fieldname": "display", + "fieldtype": "Section Break", + "label": "Display", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "default", - "fieldtype": "Text", - "hidden": 0, - "label": "Default", - "oldfieldname": "default", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "reqd": 0, + "fieldname": "default", + "fieldtype": "Text", + "hidden": 0, + "label": "Default", + "oldfieldname": "default", + "oldfieldtype": "Text", + "permlevel": 0, + "print_hide": 0, + "reqd": 0, "search_index": 0 - }, + }, { - "fieldname": "in_filter", - "fieldtype": "Check", - "hidden": 0, - "label": "In Filter", - "oldfieldname": "in_filter", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_width": "50px", - "reqd": 0, + "fieldname": "in_filter", + "fieldtype": "Check", + "hidden": 0, + "label": "In Filter", + "oldfieldname": "in_filter", + "oldfieldtype": "Check", + "permlevel": 0, + "print_hide": 0, + "print_width": "50px", + "reqd": 0, "width": "50px" - }, + }, { - "fieldname": "column_break_21", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_21", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "description", - "fieldtype": "Text", - "hidden": 0, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_width": "300px", - "reqd": 0, + "fieldname": "description", + "fieldtype": "Text", + "hidden": 0, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "permlevel": 0, + "print_hide": 0, + "print_width": "300px", + "reqd": 0, "width": "300px" - }, + }, { - "fieldname": "print_hide", - "fieldtype": "Check", - "hidden": 0, - "label": "Print Hide", - "oldfieldname": "print_hide", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "reqd": 0, + "fieldname": "print_hide", + "fieldtype": "Check", + "hidden": 0, + "label": "Print Hide", + "oldfieldname": "print_hide", + "oldfieldtype": "Check", + "permlevel": 0, + "print_hide": 0, + "reqd": 0, "search_index": 0 - }, + }, { - "description": "Print Width of the field, if the field is a column in a table", - "fieldname": "print_width", - "fieldtype": "Data", - "label": "Print Width", - "permlevel": 0, - "print_width": "50px", + "description": "Print Width of the field, if the field is a column in a table", + "fieldname": "print_width", + "fieldtype": "Data", + "label": "Print Width", + "permlevel": 0, + "print_width": "50px", "width": "50px" - }, + }, { - "fieldname": "width", - "fieldtype": "Data", - "hidden": 0, - "in_list_view": 1, - "label": "Width", - "oldfieldname": "width", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_width": "50px", - "reqd": 0, - "search_index": 0, + "fieldname": "width", + "fieldtype": "Data", + "hidden": 0, + "in_list_view": 1, + "label": "Width", + "oldfieldname": "width", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "print_width": "50px", + "reqd": 0, + "search_index": 0, "width": "50px" - }, + }, { - "fieldname": "is_custom_field", - "fieldtype": "Check", - "hidden": 1, - "label": "Is Custom Field", - "permlevel": 0, - "precision": "", + "fieldname": "is_custom_field", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Custom Field", + "permlevel": 0, + "precision": "", "read_only": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "issingle": 0, - "istable": 1, - "modified": "2015-04-24 11:37:52.879004", - "modified_by": "Administrator", - "module": "Custom", - "name": "Customize Form Field", - "owner": "Administrator", - "permissions": [], + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "issingle": 0, + "istable": 1, + "modified": "2015-07-27 01:00:32.901851", + "modified_by": "Administrator", + "module": "Custom", + "name": "Customize Form Field", + "owner": "Administrator", + "permissions": [], "read_only": 0 -} \ No newline at end of file +} diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index fe4d59df8d..18416fad6b 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -11,6 +11,7 @@ from frappe.modules import scrub, get_module_path from frappe.utils import flt, cint, get_html_format from frappe.translate import send_translations import frappe.desk.reportview +from frappe.permissions import get_role_permissions def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) @@ -144,20 +145,35 @@ def get_filtered_data(ref_doctype, columns, data): linked_doctypes = get_linked_doctypes(columns, data) match_filters_per_doctype = get_user_match_filters(linked_doctypes, ref_doctype) shared = frappe.share.get_shared(ref_doctype) + columns_dict = get_columns_dict(columns) + + role_permissions = get_role_permissions(frappe.get_meta(ref_doctype)) + if_owner = role_permissions.get("if_owner", {}).get("report") if match_filters_per_doctype: for row in data: if shared and row[linked_doctypes[ref_doctype]] in shared: result.append(row) - elif has_match(row, linked_doctypes, match_filters_per_doctype): + elif has_match(row, linked_doctypes, match_filters_per_doctype, ref_doctype, if_owner, columns_dict): result.append(row) else: result = list(data) return result -def has_match(row, linked_doctypes, doctype_match_filters): +def has_match(row, linked_doctypes, doctype_match_filters, ref_doctype, if_owner, columns_dict): + """Returns True if after evaluating permissions for each linked doctype + - There is an owner match for the ref_doctype + - `and` There is a user permission match for all linked doctypes + + Returns True if the row is empty + + Note: + Each doctype could have multiple conflicting user permission doctypes. + Hence even if one of the sets allows a match, it is true. + This behavior is equivalent to the trickling of user permissions of linked doctypes to the ref doctype. + """ resultant_match = True if not row: @@ -167,20 +183,33 @@ def has_match(row, linked_doctypes, doctype_match_filters): for doctype, filter_list in doctype_match_filters.items(): matched_for_doctype = False - for match_filters in filter_list: - match = True - for dt, idx in linked_doctypes.items(): - if dt in match_filters and row[idx] not in match_filters[dt]: - match = False + if doctype==ref_doctype and if_owner: + idx = linked_doctypes.get("User") + if (idx is not None + and row[idx]==frappe.session.user + and columns_dict[idx]==columns_dict.get("owner")): + # owner match is true + matched_for_doctype = True + + if not matched_for_doctype: + for match_filters in filter_list: + match = True + for dt, idx in linked_doctypes.items(): + # case handled above + if dt=="User" and columns_dict[idx]==columns_dict.get("owner"): + continue + + if dt in match_filters and row[idx] not in match_filters[dt]: + match = False + break + + # each doctype could have multiple conflicting user permission doctypes, hence using OR + # so that even if one of the sets allows a match, it is true + matched_for_doctype = matched_for_doctype or match + + if matched_for_doctype: break - # each doctype could have multiple conflicting user permission doctypes, hence using OR - # so that even if one of the sets allows a match, it is true - matched_for_doctype = matched_for_doctype or match - - if matched_for_doctype: - break - # each doctype's user permissions should match the row! hence using AND resultant_match = resultant_match and matched_for_doctype @@ -192,16 +221,16 @@ def has_match(row, linked_doctypes, doctype_match_filters): def get_linked_doctypes(columns, data): linked_doctypes = {} - for idx, col in enumerate(columns): - if isinstance(col, basestring): - col = col.split(":") - if len(col) > 1 and col[1].startswith("Link"): - link_dt = col[1].split("/")[1] - linked_doctypes[link_dt] = idx + columns_dict = get_columns_dict(columns) - # dict - elif col.get("fieldtype")=="Link" and col.get("options"): - linked_doctypes[col["options"]] = col["fieldname"] + for idx, col in enumerate(columns): + df = columns_dict[idx] + if df.get("fieldtype")=="Link": + if isinstance(col, basestring): + linked_doctypes[df["options"]] = idx + else: + # dict + linked_doctypes[df["options"]] = df["fieldname"] # remove doctype if column is empty for doctype, key in linked_doctypes.items(): @@ -210,6 +239,35 @@ def get_linked_doctypes(columns, data): return linked_doctypes +def get_columns_dict(columns): + """Returns a dict with column docfield values as dict + The keys for the dict are both idx and fieldname, + so either index or fieldname can be used to search for a column's docfield properties + """ + columns_dict = {} + for idx, col in enumerate(columns): + col_dict = {} + + # string + if isinstance(col, basestring): + col = col.split(":") + if len(col) > 1: + if "/" in col[1]: + col_dict["fieldtype"], col_dict["options"] = col[1].split("/") + else: + col_dict["fieldtype"] = col[1] + + col_dict["fieldname"] = col[0].lower() + + # dict + else: + col_dict.update(col) + + columns_dict[idx] = col_dict + columns_dict[col_dict["fieldname"]] = col_dict + + return columns_dict + def get_user_match_filters(doctypes, ref_doctype): match_filters = {} diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py index 0fbf693095..714d64423d 100644 --- a/frappe/email/smtp.py +++ b/frappe/email/smtp.py @@ -55,7 +55,8 @@ def get_outgoing_email_account(raise_exception_not_set=True, append_to=None): email_account = get_default_outgoing_email_account(raise_exception_not_set=raise_exception_not_set) if not email_account and raise_exception_not_set: - frappe.throw(_("Please setup default Email Account from Setup > Email > Email Account")) + frappe.throw(_("Please setup default Email Account from Setup > Email > Email Account"), + frappe.OutgoingEmailError) frappe.local.outgoing_email_account[append_to or "default"] = email_account diff --git a/frappe/hooks.py b/frappe/hooks.py index a8efdb5539..eaa9c2512b 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -2,12 +2,35 @@ from __future__ import unicode_literals app_name = "frappe" app_title = "Frappe Framework" app_publisher = "Frappe Technologies Pvt. Ltd." -app_description = "Full Stack Web Application Framework in Python" -app_icon = "octicon octicon-circuit-board" -app_version = "5.1.3" -app_color = "orange" +app_description = """## Frappe Framework -app_email = "support@frappe.io" +Frappe is a full stack web application framework written in Python, +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 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. + +Frappe also has a plug-in architecture that can be used to build plugins +to ERPNext. + +### Links: + +- Project Home: [https://frappe.io](https://frappe.io) +- Tutorial: [https://frappe.io/tutorial](https://frappe.io/tutorial) +- GitHub: [https://github.com/frappe/frappe](https://github.com/frappe/frappe) +- Forum: [https://discuss.erpnext.com](https://discuss.erpnext.com) +""" + +app_icon = "octicon octicon-circuit-board" +app_version = "5.1.4" +app_color = "orange" +github_link = "https://github.com/frappe/frappe" + +app_email = "info@frappe.io" before_install = "frappe.utils.install.before_install" after_install = "frappe.utils.install.after_install" @@ -35,8 +58,7 @@ web_include_js = [ bootstrap = "assets/frappe/css/bootstrap.css" web_include_css = [ - "assets/css/frappe-web.css", - "website_theme.css" + "assets/css/frappe-web.css" ] website_route_rules = [ {"from_route": "/blog", "to_route": "Blog Post"}, diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index e4fdd72b68..8fddfe3a20 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -453,7 +453,7 @@ class BaseDocument(object): return self._precision[cache_key][fieldname] - def get_formatted(self, fieldname, doc=None, currency=None): + def get_formatted(self, fieldname, doc=None, currency=None, absolute_value=False): from frappe.utils.formatters import format_value df = self.meta.get_field(fieldname) @@ -461,7 +461,10 @@ class BaseDocument(object): from frappe.model.meta import get_default_df df = get_default_df(fieldname) - return format_value(self.get(fieldname), df=df, doc=doc or self, currency=currency) + val = self.get(fieldname) + if absolute_value and isinstance(val, (int, float)): + val = abs(self.get(fieldname)) + return format_value(val, df=df, doc=doc or self, currency=currency) def is_print_hide(self, fieldname, df=None, for_print=True): """Returns true if fieldname is to be hidden for print. diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index a20c17c43d..a1d21275dd 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -294,6 +294,10 @@ class DatabaseQuery(object): self.add_user_permissions(user_permissions, user_permission_doctypes=role_permissions.get("user_permission_doctypes").get("read")) + if role_permissions.get("if_owner", {}).get("read"): + self.match_conditions.append("`tab{0}`.owner = '{1}'".format(self.doctype, + frappe.db.escape(frappe.session.user))) + if as_condition: conditions = "" if self.match_conditions: diff --git a/frappe/model/naming.py b/frappe/model/naming.py index 3a7a4a7cb6..c23bd3d633 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -177,7 +177,7 @@ def append_number_if_name_exists(doc): if frappe.db.exists(doc.doctype, doc.name): last = frappe.db.sql("""select name from `tab{}` where name regexp '{}-[[:digit:]]+' - order by name desc limit 1""".format(doc.doctype, doc.name)) + order by length(name) desc, name desc limit 1""".format(doc.doctype, doc.name)) if last: count = str(cint(last[0][0].rsplit("-", 1)[1]) + 1) diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index 2ecad22f28..0aea4fd2ca 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -7,19 +7,19 @@ import frappe, os, json from frappe.modules import get_module_path, scrub_dt_dn from frappe.utils import get_datetime_str -def import_files(module, dt=None, dn=None, force=False): +def import_files(module, dt=None, dn=None, force=False, pre_process=None): if type(module) is list: out = [] for m in module: - out.append(import_file(m[0], m[1], m[2], force=force)) + out.append(import_file(m[0], m[1], m[2], force=force, pre_process=pre_process)) return out else: - return import_file(module, dt, dn, force=force) + return import_file(module, dt, dn, force=force, pre_process=pre_process) -def import_file(module, dt, dn, force=False): +def import_file(module, dt, dn, force=False, pre_process=None): """Sync a file from txt if modifed, return false if not updated""" path = get_file_path(module, dt, dn) - ret = import_file_by_path(path, force) + ret = import_file_by_path(path, force, pre_process=pre_process) return ret def get_file_path(module, dt, dn): @@ -30,7 +30,7 @@ def get_file_path(module, dt, dn): return path -def import_file_by_path(path, force=False, data_import=False): +def import_file_by_path(path, force=False, data_import=False, pre_process=None): frappe.flags.in_import = True try: docs = read_doc_from_file(path) @@ -51,7 +51,7 @@ def import_file_by_path(path, force=False, data_import=False): original_modified = doc.get("modified") - import_doc(doc, force=force, data_import=data_import) + import_doc(doc, force=force, data_import=data_import, pre_process=pre_process) if original_modified: # since there is a new timestamp on the file, update timestamp in @@ -87,10 +87,12 @@ ignore_values = { ignore_doctypes = ["Page Role", "DocPerm"] -def import_doc(docdict, force=False, data_import=False): +def import_doc(docdict, force=False, data_import=False, pre_process=None): frappe.flags.in_import = True docdict["__islocal"] = 1 doc = frappe.get_doc(docdict) + if pre_process: + pre_process(doc) ignore = [] diff --git a/frappe/patches.txt b/frappe/patches.txt index 412650279c..535f0c8feb 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -2,7 +2,7 @@ execute:frappe.db.sql("""update `tabPatch Log` set patch=replace(patch, '.4_0.', frappe.patches.v5_0.convert_to_barracuda_and_utf8mb4 execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2014-01-24 execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2015-05-15 -execute:frappe.reload_doc('core', 'doctype', 'docperm') #2014-06-04 +execute:frappe.reload_doc('core', 'doctype', 'docperm') #2014-06-24 execute:frappe.reload_doc('core', 'doctype', 'page') #2013-13-26 execute:frappe.reload_doc('core', 'doctype', 'report') #2014-06-03 execute:frappe.reload_doc('core', 'doctype', 'version') #2014-02-21 diff --git a/frappe/permissions.py b/frappe/permissions.py index c3183d2cd0..480a2ba2c3 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -17,7 +17,11 @@ def check_admin_or_system_manager(user=None): frappe.throw(_("Not permitted"), frappe.PermissionError) def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): - """check if user has permission""" + """Returns True if user has permission `ptype` for given `doctype`. + If `doc` is passed, it also checks user, share and owner permissions. + + Note: if Table DocType is passed, it always returns True. + """ if not user: user = frappe.session.user if frappe.is_table(doctype): @@ -65,6 +69,11 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): if isinstance(doc, basestring): doc = frappe.get_doc(meta.name, doc) + # if owner match, then return True + if doc.owner == frappe.session.user and role_permissions["if_owner"].get(ptype) and ptype!="create": + return True + + # check if user permission if role_permissions["apply_user_permissions"].get(ptype): if not user_has_permission(doc, verbose=verbose, user=user, user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or []): @@ -80,6 +89,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): return True def get_doc_permissions(doc, verbose=False, user=None): + """Returns a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`""" if not user: user = frappe.session.user if frappe.is_table(doc.doctype): @@ -102,23 +112,62 @@ def get_doc_permissions(doc, verbose=False, user=None): user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or []): role_permissions[ptype] = 0 - # update share permissions - role_permissions.update(frappe.db.get_value("DocShare", - {"share_doctype": doc.doctype, "share_name": doc.name, "user": user}, - ["read", "write", "share"], as_dict=True) or {}) + # apply owner permissions on top of existing permissions + if doc.owner == frappe.session.user: + role_permissions.update(role_permissions.if_owner) + + update_share_permissions(role_permissions, doc, user) return role_permissions +def update_share_permissions(role_permissions, doc, user): + """Updates share permissions on `role_permissions` for given doc, if shared""" + share_ptypes = ("read", "write", "share") + permissions_by_share = frappe.db.get_value("DocShare", + {"share_doctype": doc.doctype, "share_name": doc.name, "user": user}, + share_ptypes, as_dict=True) + + if permissions_by_share: + for ptype in share_ptypes: + if ptype: + role_permissions[ptype] = 1 + def get_role_permissions(meta, user=None, verbose=False): + """Returns dict of evaluated role permissions like `{"read": True, "write":False}` + + If user permissions are applicable, it adds a dict of user permissions like + + { + // user permissions will apply on these rights + "apply_user_permissions": {"read": 1, "write": 1}, + + // doctypes that will be applicable for each right + "user_permission_doctypes": { + "read": [ + // AND between "DocType 1" and "DocType 2" + ["DocType 1", "DocType 2"], + + // OR + + ["DocType 3"] + + ] + } + + "if_owner": {"read": 1, "write": 1} + } + """ if not user: user = frappe.session.user cache_key = (meta.name, user) if not frappe.local.role_permissions.get(cache_key): - perms = frappe._dict({ "apply_user_permissions": {}, "user_permission_doctypes": {} }) + perms = frappe._dict({ "apply_user_permissions": {}, "user_permission_doctypes": {}, "if_owner": {} }) user_roles = frappe.get_roles(user) for p in meta.permissions: if cint(p.permlevel)==0 and (p.role in user_roles): + # apply only for level 0 + for ptype in rights: perms[ptype] = perms.get(ptype, 0) or cint(p.get(ptype)) @@ -126,6 +175,10 @@ def get_role_permissions(meta, user=None, verbose=False): perms["apply_user_permissions"][ptype] = (perms["apply_user_permissions"].get(ptype, 1) and p.get("apply_user_permissions")) + # build if_owner dict if applicable for this right + if p.if_owner and p.get(ptype): + perms["if_owner"][ptype] = 1 + if p.apply_user_permissions: if p.user_permission_doctypes: # set user_permission_doctypes in perms diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css index 7f3d649951..a85f9ae799 100644 --- a/frappe/public/css/form.css +++ b/frappe/public/css/form.css @@ -32,6 +32,9 @@ margin: 0px; padding: 15px; } +.form-section .form-section-heading { + margin: 25px 0px 15px 0px; +} .empty-section { display: none !important; } diff --git a/frappe/public/css/website.css b/frappe/public/css/website.css index 3608b07b5b..74a50bf87b 100644 --- a/frappe/public/css/website.css +++ b/frappe/public/css/website.css @@ -285,6 +285,7 @@ body { } .avatar-empty { border: 1px dashed #d1d8dd; + border-radius: 4px; } .avatar-small { margin-right: 5px; diff --git a/frappe/public/images/favicon.png b/frappe/public/images/favicon.png index 00d97cb834..62ff240fb2 100644 Binary files a/frappe/public/images/favicon.png and b/frappe/public/images/favicon.png differ diff --git a/frappe/public/images/frappe.svg b/frappe/public/images/frappe.svg deleted file mode 100644 index 6b6ea9df2b..0000000000 --- a/frappe/public/images/frappe.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - -image/svg+xml - - - - - - - diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index c26269a163..e3c4417057 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -105,20 +105,6 @@ frappe.ui.form.Layout = Class.extend({ }); }, - make_column: function(df) { - this.column = $('
\ -
\ -
\ -
').appendTo(this.section.body) - .find("form") - .on("submit", function() { return false; }) - - // distribute all columns equally - var colspan = cint(12 / this.section.find(".form-column").length); - this.section.find(".form-column").removeClass() - .addClass("form-column") - .addClass("col-sm-" + colspan); - }, make_field: function(df, colspan) { !this.section && this.make_section(); !this.column && this.make_column(); @@ -181,7 +167,7 @@ frappe.ui.form.Layout = Class.extend({ section.df = df; if(df) { if(df.label) { - $('

' + __(df.label) + '

') + $('

' + __(df.label) + '

') .appendTo(this.section); } if(df.description) { @@ -198,24 +184,49 @@ frappe.ui.form.Layout = Class.extend({ section.row = { wrapper: section }; + section.layout = me; section.refresh = function() { - if(!this.df) - return; - - // hide if explictly hidden - var hide = this.df.hidden || this.df.hidden_due_to_dependency; - - // hide if no perm - if(!hide && me.frm && !me.frm.get_perm(this.df.permlevel || 0, "read")) { - hide = true; - } - - $(this).toggleClass("hide-control", !!hide); + frappe.ui.section_refresh.apply(this); } this.column = null; section.refresh.call(section); return this.section; }, + make_column: function(df) { + if(!df) df = {}; + + var column = $('
\ +
\ +
\ +
').appendTo(this.section.body) + .find("form") + .on("submit", function() { return false; }) + + if(df.label) { + $('').appendTo(column); + } + + // distribute all columns equally + var colspan = cint(12 / this.section.find(".form-column").length); + this.section.find(".form-column").removeClass() + .addClass("form-column") + .addClass("col-sm-" + colspan); + + column.df = df; + column.layout = this; + + //this.fields_dict[df.fieldname] = column; + if(df.fieldname) { + this.fields_list.push(column); + } + + column.refresh = function() { + frappe.ui.section_refresh.apply(this); + } + + this.column = column; + }, refresh_sections: function() { var cnt = 0; this.wrapper.find(".form-section:not(.hide-control)").each(function() { @@ -385,4 +396,19 @@ frappe.ui.form.Layout = Class.extend({ this.refresh_section_count(); } -}) +}); + +frappe.ui.section_refresh = function() { + if(!this.df) + return; + + // hide if explictly hidden + var hide = this.df.hidden || this.df.hidden_due_to_dependency; + + // hide if no perm + if(!hide && this.layout && this.layout.frm && !this.layout.frm.get_perm(this.df.permlevel || 0, "read")) { + hide = true; + } + + $(this).toggleClass("hide-control", !!hide); +} diff --git a/frappe/public/js/frappe/model/perm.js b/frappe/public/js/frappe/model/perm.js index e3d0e77812..028d4c586f 100644 --- a/frappe/public/js/frappe/model/perm.js +++ b/frappe/public/js/frappe/model/perm.js @@ -89,6 +89,9 @@ $.extend(frappe.perm, { }, build_role_permissions: function(perm, meta) { + // Returns a `dict` of evaluated Role Permissions + // Apply User Permission and its DocTypes are used to display match rules in list view + $.each(meta.permissions || [], function(i, p) { // if user has this role if(user_roles.indexOf(p.role)!==-1) { @@ -100,6 +103,7 @@ $.extend(frappe.perm, { $.each(frappe.perm.rights, function(i, key) { perm[permlevel][key] = perm[permlevel][key] || (p[key] || 0); + // NOTE: this data is required for displaying match rules in list view if (permlevel===0) { var apply_user_permissions = perm[permlevel].apply_user_permissions; var current_value = (apply_user_permissions[key]===undefined ? @@ -108,6 +112,7 @@ $.extend(frappe.perm, { } }); + // NOTE: this data is required for displaying match rules in list view if (permlevel===0 && cint(p.apply_user_permissions) && p.user_permission_doctypes) { // set user_permission_doctypes in perms var user_permission_doctypes = JSON.parse(p.user_permission_doctypes); @@ -126,6 +131,10 @@ $.extend(frappe.perm, { }); } } + + if (permlevel===0 && p["if_owner"]) { + perm[0]["if_owner"] = 1; + } } }); @@ -177,6 +186,10 @@ $.extend(frappe.perm, { }); } + if (perm[0].if_owner && perm[0].read) { + match_rules.push({"Owner": user}); + } + return match_rules; }, diff --git a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js index ff4955f084..c236e226c5 100644 --- a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js +++ b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js @@ -84,15 +84,15 @@ frappe.search = { onclick: function() { var txt = '\ \ + __("new type of document")+'\ \ + __("document type..., e.g. customer")+'\ \ + __("text in document type")+'\ \ + __("module name...")+'\ \ + __("e.g. (55 + 434) / 4 or =Math.sin(Math.PI/2)...")+'\
'+__("Make a new record")+''+ - __("new type of document")+'
'+__("List a document type")+''+ - __("document type..., e.g. customer")+'
'+__("Search in a document type")+''+ - __("text in document type")+'
'+__("Open a module or tool")+''+ - __("module name...")+'
'+__("Calculate")+''+ - __("e.g. (55 + 434) / 4 or =Math.sin(Math.PI/2)...")+'
' msgprint(txt, "Search Help"); } diff --git a/frappe/public/js/frappe/views/reports/reportview.js b/frappe/public/js/frappe/views/reports/reportview.js index cd2c365d60..72cb27132b 100644 --- a/frappe/public/js/frappe/views/reports/reportview.js +++ b/frappe/public/js/frappe/views/reports/reportview.js @@ -187,17 +187,21 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ }, get_order_by: function() { + var order_by = []; + // first - var order_by = this.get_selected_table_and_column(this.sort_by_select) - + ' ' + this.sort_order_select.val(); + var sort_by_select = this.get_selected_table_and_column(this.sort_by_select); + if (sort_by_select) { + order_by.push(sort_by_select + " " + this.sort_order_select.val()); + } // second if(this.sort_by_next_select.val()) { - order_by += ', ' + this.get_selected_table_and_column(this.sort_by_next_select) - + ' ' + this.sort_order_next_select.val(); + order_by.push(this.get_selected_table_and_column(this.sort_by_next_select) + + ' ' + this.sort_order_next_select.val()); } - return order_by; + return order_by.join(", "); }, get_selected_table_and_column: function(select) { diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less index 5398616987..a60dcf3c4a 100644 --- a/frappe/public/less/form.less +++ b/frappe/public/less/form.less @@ -41,6 +41,10 @@ .form-section { margin: 0px; padding: 15px; + + .form-section-heading { + margin: 25px 0px 15px 0px; + } } .empty-section { diff --git a/frappe/sessions.py b/frappe/sessions.py index 0a13781a49..d4d98f7c18 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -55,7 +55,7 @@ def clear_sessions(user=None, keep_current=False): user = frappe.session.user for sid in frappe.db.sql("""select sid from tabSessions where user=%s and device=%s""", - (user, frappe.session.device or "desktop")): + (user, frappe.session.data.device or "desktop")): if keep_current and frappe.session.sid==sid[0]: continue else: diff --git a/frappe/templates/autodoc/api_home.html b/frappe/templates/autodoc/api_home.html new file mode 100644 index 0000000000..afef9c43e5 --- /dev/null +++ b/frappe/templates/autodoc/api_home.html @@ -0,0 +1,11 @@ + + +{% from "templates/autodoc/macros.html" import github_link, version %} +

+{{ version(app.name) }} +{{ github_link(app, app.name, True) }} +

+ +

Contents

+ +{index} diff --git a/frappe/templates/autodoc/docs_home.html b/frappe/templates/autodoc/docs_home.html new file mode 100644 index 0000000000..8c803952d9 --- /dev/null +++ b/frappe/templates/autodoc/docs_home.html @@ -0,0 +1,55 @@ + + + +{% from "templates/autodoc/macros.html" import github_link, version, discuss_link %} +

+{{ version(app.name) }} +{{ github_link(app, app.name, True) }} +

+ + + + + + + + + + + + + + +
+ App Name + + {{ app.name }} +
+ Publisher + + {{ app.publisher }} +
+ Version + + {{ app.version }} +
+ +
+ +{{ app.description }} + +
+ +

Contents

+ + +{{ discuss_link() }} + + diff --git a/frappe/templates/autodoc/doctype.html b/frappe/templates/autodoc/doctype.html index bdcd360713..faa1ce6d58 100644 --- a/frappe/templates/autodoc/doctype.html +++ b/frappe/templates/autodoc/doctype.html @@ -1,14 +1,21 @@ -{% from "templates/autodoc/macros.html" import automodule, version %} -{% macro render_doctype(name) %} -{% set doc = frappe.get_doc("DocType", name) %} -{% set controller = autodoc.get_controller(name) %} -{{ version(name) }} + + +{% from "templates/autodoc/macros.html" import automodule, version, + github_link, doctype_link, discuss_link %} +{% set doc = frappe.get_doc("DocType", doctype) %} +{% set controller = autodoc.get_controller(doctype) %} + +

+{{ version(doctype) }} +{{ github_link(app, app.name + "/" + scrub(doc.module) + + "/doctype/" + scrub(doctype), True) }} +

{% if doc.issingle %}Single{% endif %} {% if doc.istable %}Child Table{% endif %} {% if not doc.issingle %} -

Table Name: tab{{ name }}

+

Table Name: tab{{ doctype }}

{% endif %} {{ doc.description or "" }} @@ -39,7 +46,7 @@ {% if df.options and df.fieldtype not in ("HTML") %} {% if df.fieldtype in ("Table", "Link") %} - {{ df.options }} + {{ doctype_link(df.options) }} {% else %}
{{ df.options }}
{% endif %} {% endif %} @@ -54,26 +61,28 @@ {{ automodule(controller.__module__) }} {% set parents = frappe.get_list("DocField", - {"options": name, "fieldtype": "Link"}, ["distinct parent"]) %} + filters = {"options": doctype, "fieldtype": "Link"}, fields = ["distinct parent"]) %} {% if parents %}

Linked In:

{% endif %} {% else %} {% set parents = frappe.get_list("DocField", - {"options": name, "fieldtype": "Table"}, ["parent"]) %} + filters = {"options": doctype, "fieldtype": "Table"}, fields = ["parent"]) %} {% if parents %}

Child Table Of

{% endif %} {% endif %} -{% endmacro %} + + +{{ discuss_link() }} diff --git a/frappe/templates/autodoc/macros.html b/frappe/templates/autodoc/macros.html index 249f6f0673..3d2fcad1fb 100644 --- a/frappe/templates/autodoc/macros.html +++ b/frappe/templates/autodoc/macros.html @@ -1,6 +1,5 @@ {% macro automodule(name) %} {% set m = autodoc.automodule(name) %} -{{ version(name) }} {% for obj in m.members %} {% if obj.type=="function" %} {{ render_function(obj, name) }} @@ -10,12 +9,6 @@ {% endfor %} {% endmacro %} -{% macro version(name) %} -

- Version {{ autodoc.get_version(name) }} -

-{% endmacro %} - {% macro render_class(obj) %}

Class {{ obj.name }}

{% if obj.bases %} @@ -51,3 +44,26 @@ {{ arg }}{% if default_idx >= 0 %}={{ args[3][default_idx] }}{% endif %}{% if not loop.last %}, {% endif %} {%- endfor %} {%- endmacro %} + +{% macro version(name) %} + + Version {{ autodoc.get_version(name) }} +{% endmacro %} + +{% macro github_link(app, file_path, tree=False) %} + Source +{% endmacro %} + +{% macro discuss_link() %} +
+Discuss this on the forum +{% endmacro %} + +{% macro doctype_link(doctype) %} +{% set module = frappe.db.get_value("DocType", doctype, "module") %} +{% if doctype and module %} +{{ doctype }} +{% endif %} +{% endmacro %} diff --git a/frappe/templates/autodoc/models_home.html b/frappe/templates/autodoc/models_home.html new file mode 100644 index 0000000000..9eade5df27 --- /dev/null +++ b/frappe/templates/autodoc/models_home.html @@ -0,0 +1,13 @@ + + +{% from "templates/autodoc/macros.html" import github_link, version %} +

+{{ version(app.name) }} +{{ github_link(app, app.name, True) }} +

+ +

Browse DocTypes by Module

+ +

Contents

+ +{index} diff --git a/frappe/templates/autodoc/module_home.html b/frappe/templates/autodoc/module_home.html new file mode 100644 index 0000000000..797a818f7b --- /dev/null +++ b/frappe/templates/autodoc/module_home.html @@ -0,0 +1,11 @@ + + +{% from "templates/autodoc/macros.html" import github_link, version %} +

+{{ version(app.name) }} +{{ github_link(app, app.name + "/" + scrub(name), True) }} +

+ +

DocTypes for {{ name }}

+ +{index} diff --git a/frappe/templates/autodoc/package_index.html b/frappe/templates/autodoc/package_index.html new file mode 100644 index 0000000000..d4ee738b21 --- /dev/null +++ b/frappe/templates/autodoc/package_index.html @@ -0,0 +1,11 @@ + + +{% from "templates/autodoc/macros.html" import github_link, version %} +

+{{ version(app.name) }} +{{ github_link(app, title, True) }} +

+ +

Package Contents

+ +{index} diff --git a/frappe/templates/autodoc/pymodule.html b/frappe/templates/autodoc/pymodule.html new file mode 100644 index 0000000000..33c0f9d66b --- /dev/null +++ b/frappe/templates/autodoc/pymodule.html @@ -0,0 +1,12 @@ + + +{%- from "templates/autodoc/macros.html" import automodule, github_link, + version, discuss_link -%} +

+{{ version(app.name) }} +{{ github_link(app, name.replace(".", "/") + ".py") }} +

+ +{{ automodule(name) }} + +{{ discuss_link() }} diff --git a/frappe/templates/base.html b/frappe/templates/base.html index c02a063309..2b301ec723 100644 --- a/frappe/templates/base.html +++ b/frappe/templates/base.html @@ -44,7 +44,7 @@ {% endif -%} - +
diff --git a/frappe/templates/includes/list/list.html b/frappe/templates/includes/list/list.html index 6e14df3cf7..907120155d 100644 --- a/frappe/templates/includes/list/list.html +++ b/frappe/templates/includes/list/list.html @@ -19,3 +19,4 @@
{%- endif %} + diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py new file mode 100644 index 0000000000..88757dd167 --- /dev/null +++ b/frappe/tests/test_permissions.py @@ -0,0 +1,229 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt +from __future__ import unicode_literals + +"""Use blog post test to test user permissions logic""" + +import frappe +import frappe.defaults +import unittest +import json +import frappe.model.meta +from frappe.core.page.user_permissions.user_permissions import add, remove, get_permissions +from frappe.permissions import clear_user_permissions_for_doctype, get_doc_permissions + +test_records = frappe.get_test_records('Blog Post') + +test_dependencies = ["User"] + +class TestPermissions(unittest.TestCase): + def setUp(self): + frappe.clear_cache(doctype="Blog Post") + + user = frappe.get_doc("User", "test1@example.com") + user.add_roles("Website Manager") + + user = frappe.get_doc("User", "test2@example.com") + user.add_roles("Blogger") + + frappe.set_user("test1@example.com") + + def tearDown(self): + frappe.set_user("Administrator") + frappe.db.set_value("Blogger", "_Test Blogger 1", "user", None) + + clear_user_permissions_for_doctype("Blog Category") + clear_user_permissions_for_doctype("Blog Post") + clear_user_permissions_for_doctype("Blogger") + + frappe.db.sql("""update `tabDocPerm` set user_permission_doctypes=null + where parent='Blog Post' and permlevel=0 and apply_user_permissions=1 + and `read`=1""") + + frappe.db.sql("""update `tabDocPerm` set if_owner=0 + where parent='Blog Post' and permlevel=0 and permlevel=0 and role='Blogger'""") + + def test_basic_permission(self): + post = frappe.get_doc("Blog Post", "_test-blog-post") + self.assertTrue(post.has_permission("read")) + + def test_user_permissions_in_doc(self): + frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", + "test2@example.com") + + frappe.set_user("test2@example.com") + + post = frappe.get_doc("Blog Post", "_test-blog-post") + self.assertFalse(post.has_permission("read")) + self.assertFalse(get_doc_permissions(post).get("read")) + + post1 = frappe.get_doc("Blog Post", "_test-blog-post-1") + self.assertTrue(post1.has_permission("read")) + self.assertTrue(get_doc_permissions(post1).get("read")) + + def test_user_permissions_in_report(self): + frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") + + frappe.set_user("test2@example.com") + names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "blog_category"])] + + self.assertTrue("_test-blog-post-1" in names) + self.assertFalse("_test-blog-post" in names) + + def test_default_values(self): + frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") + + frappe.set_user("test2@example.com") + doc = frappe.new_doc("Blog Post") + self.assertEquals(doc.get("blog_category"), "_Test Blog Category 1") + + def test_user_link_match_doc(self): + blogger = frappe.get_doc("Blogger", "_Test Blogger 1") + blogger.user = "test2@example.com" + blogger.save() + + frappe.set_user("test2@example.com") + + post = frappe.get_doc("Blog Post", "_test-blog-post-2") + self.assertTrue(post.has_permission("read")) + + post1 = frappe.get_doc("Blog Post", "_test-blog-post-1") + self.assertFalse(post1.has_permission("read")) + + def test_user_link_match_report(self): + blogger = frappe.get_doc("Blogger", "_Test Blogger 1") + blogger.user = "test2@example.com" + blogger.save() + + frappe.set_user("test2@example.com") + + names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "owner"])] + self.assertTrue("_test-blog-post-2" in names) + self.assertFalse("_test-blog-post-1" in names) + + def test_set_user_permissions(self): + frappe.set_user("test1@example.com") + add("test2@example.com", "Blog Post", "_test-blog-post") + + def test_not_allowed_to_set_user_permissions(self): + frappe.set_user("test2@example.com") + + # this user can't add user permissions + self.assertRaises(frappe.PermissionError, add, + "test2@example.com", "Blog Post", "_test-blog-post") + + def test_read_if_explicit_user_permissions_are_set(self): + self.test_set_user_permissions() + + frappe.set_user("test2@example.com") + + # user can only access permitted blog post + doc = frappe.get_doc("Blog Post", "_test-blog-post") + self.assertTrue(doc.has_permission("read")) + + # and not this one + doc = frappe.get_doc("Blog Post", "_test-blog-post-1") + self.assertFalse(doc.has_permission("read")) + + def test_not_allowed_to_remove_user_permissions(self): + self.test_set_user_permissions() + defname = get_permissions("test2@example.com", "Blog Post", "_test-blog-post")[0].name + + frappe.set_user("test2@example.com") + + # user cannot remove their own user permissions + self.assertRaises(frappe.PermissionError, remove, + "test2@example.com", defname, "Blog Post", "_test-blog-post") + + def test_user_permissions_based_on_blogger(self): + frappe.set_user("test2@example.com") + doc = frappe.get_doc("Blog Post", "_test-blog-post-1") + self.assertTrue(doc.has_permission("read")) + + frappe.set_user("test1@example.com") + add("test2@example.com", "Blog Post", "_test-blog-post") + + frappe.set_user("test2@example.com") + doc = frappe.get_doc("Blog Post", "_test-blog-post-1") + self.assertFalse(doc.has_permission("read")) + + doc = frappe.get_doc("Blog Post", "_test-blog-post") + self.assertTrue(doc.has_permission("read")) + + def test_set_only_once(self): + blog_post = frappe.get_meta("Blog Post") + blog_post.get_field("title").set_only_once = 1 + doc = frappe.get_doc("Blog Post", "_test-blog-post-1") + doc.title = "New" + self.assertRaises(frappe.CannotChangeConstantError, doc.save) + blog_post.get_field("title").set_only_once = 0 + + def test_user_permission_doctypes(self): + frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", + "test2@example.com") + frappe.permissions.add_user_permission("Blogger", "_Test Blogger 1", + "test2@example.com") + + frappe.set_user("test2@example.com") + + frappe.db.sql("""update `tabDocPerm` set user_permission_doctypes=%s + where parent='Blog Post' and permlevel=0 and apply_user_permissions=1 + and `read`=1""", json.dumps(["Blogger"])) + + frappe.model.meta.clear_cache("Blog Post") + + doc = frappe.get_doc("Blog Post", "_test-blog-post") + self.assertFalse(doc.has_permission("read")) + + doc = frappe.get_doc("Blog Post", "_test-blog-post-2") + self.assertTrue(doc.has_permission("read")) + + frappe.model.meta.clear_cache("Blog Post") + + def if_owner_setup(self): + frappe.db.sql("""update `tabDocPerm` set if_owner=1 + where parent='Blog Post' and permlevel=0 and permlevel=0 and role='Blogger'""") + + frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", + "test2@example.com") + frappe.permissions.add_user_permission("Blogger", "_Test Blogger 1", + "test2@example.com") + + frappe.db.sql("""update `tabDocPerm` set user_permission_doctypes=%s + where parent='Blog Post' and permlevel=0 and apply_user_permissions=1 + and `read`=1""", json.dumps(["Blog Category"])) + + frappe.model.meta.clear_cache("Blog Post") + + def test_insert_if_owner_with_user_permissions(self): + """If `If Owner` is checked for a Role, check if that document is allowed to be read, updated, submitted, etc. except be created, even if the document is restricted based on User Permissions.""" + self.if_owner_setup() + + frappe.set_user("test2@example.com") + + doc = frappe.get_doc({ + "doctype": "Blog Post", + "blog_category": "_Test Blog Category", + "blogger": "_Test Blogger 1", + "title": "_Test Blog Post Title", + "content": "_Test Blog Post Content" + }) + + self.assertRaises(frappe.PermissionError, doc.insert) + + frappe.set_user("Administrator") + frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category", + "test2@example.com") + + frappe.set_user("test2@example.com") + doc.insert() + + frappe.set_user("Administrator") + frappe.permissions.remove_user_permission("Blog Category", "_Test Blog Category", + "test2@example.com") + + frappe.set_user("test2@example.com") + doc = frappe.get_doc(doc.doctype, doc.name) + self.assertTrue(doc.has_permission("read")) + self.assertTrue(doc.has_permission("write")) + self.assertFalse(doc.has_permission("create")) diff --git a/frappe/utils/autodoc.py b/frappe/utils/autodoc.py index 831feabf0e..70f6ee588d 100644 --- a/frappe/utils/autodoc.py +++ b/frappe/utils/autodoc.py @@ -8,50 +8,9 @@ frappe.utils.autodoc Inspect elements of a given module and return its objects """ -import inspect, importlib, re, frappe, os, shutil +import inspect, importlib, re, frappe from frappe.model.document import get_controller -from markdown2 import markdown -def build(app): - app_path = frappe.get_app_path(app) - source = frappe.get_app_path(app, "src") - dest = frappe.get_app_path(app, "www") - for basepath, folders, files in os.walk(source): - destpath = os.path.join(dest, os.path.relpath(basepath, source)) - - # make target dir if missing - if not os.path.exists(destpath): - os.makedirs(destpath) - - # delete removed folders in source from dest - for destfolder in os.listdir(destpath): - if os.path.isdir(os.path.join(destpath, destfolder)): - if destfolder not in folders: - os.path.join(destpath, destfolder) - shutil.rmtree(os.path.join(destpath, destfolder)) - - for fname in files: - # delete file - if os.path.exists(os.path.join(destpath, fname)): - os.remove(os.path.join(destpath, fname)) - - print fname - if fname.rsplit(".", 1)[-1] in ("md", "html"): - # render template and build file - with open(os.path.join(destpath, fname.rsplit(".", 1)[0] + ".html"), "w") as destfile: - if fname.endswith(".md"): - # convert markdown to html before rendering - with open(os.path.join(basepath, fname), "r") as template_file: - template = markdown(template_file.read()) - html = frappe.render_template(template, {}).encode("utf-8") - destfile.write(html) - else: - template_path = os.path.relpath(os.path.join(basepath, fname), app_path) - html = frappe.render_template(template_path, {}, is_path=True).encode("utf-8") - destfile.write(html) - else: - # not a template, copy - shutil.copyfile(os.path.join(basepath, fname), os.path.join(destpath, fname)) def automodule(name): """Returns a list of attributes for given module string. @@ -85,16 +44,22 @@ def automodule(name): "members": filter(None, attributes), } +installed = None def get_version(name): + print name + global installed + + if not installed: + installed = frappe.get_installed_apps() + def _for_module(m): return importlib.import_module(m.split(".")[0]).__version__ - if "." in name or name=="frappe": + if "." in name or name in installed: return _for_module(name) else: return _for_module(get_controller(name).__module__) - def get_class_info(class_obj, module_name): members = [] for attrname in dir(class_obj): @@ -117,15 +82,14 @@ def get_class_info(class_obj, module_name): } def get_function_info(value): - docs = getattr(value, "__doc__", "") - if docs: - return { - "name": value.__name__, - "type": "function", - "args": inspect.getargspec(value), - "docs": parse(docs), - "whitelisted": value in frappe.whitelisted - } + docs = getattr(value, "__doc__") + return { + "name": value.__name__, + "type": "function", + "args": inspect.getargspec(value), + "docs": parse(docs) if docs else 'No docs', + "whitelisted": value in frappe.whitelisted + } def parse(docs): """Parse __docs__ text into markdown. Will parse directives like `:param name:` etc""" @@ -179,17 +143,3 @@ def strip_leading_tabs(docs): def automodel(doctype): """return doctype template""" pass - -def get_doclink(name): - """Returns `__doclink__` property of a module or DocType if exists""" - if name=="[Select]": return "" - - if "." in name: - obj = frappe.get_attr(name) - else: - obj = get_controller(name) - - if hasattr(obj, "__doclink__"): - return obj.__doclink__ - else: - return "" diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 0d5ed56d9a..25532b5774 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -32,9 +32,10 @@ def get_allowed_functions_for_jenv(): import frappe import frappe.utils import frappe.utils.data - from frappe.utils.autodoc import automodule, get_doclink, get_version + from frappe.utils.autodoc import automodule, get_version from frappe.model.document import get_controller from frappe.website.utils import get_shade + from frappe.modules import scrub datautils = {} for key, obj in frappe.utils.data.__dict__.items(): @@ -76,14 +77,14 @@ def get_allowed_functions_for_jenv(): }, "autodoc": { "get_version": get_version, - "get_doclink": get_doclink, "automodule": automodule, "get_controller": get_controller }, "get_visible_columns": \ frappe.get_attr("frappe.templates.pages.print.get_visible_columns"), "_": frappe._, - "get_shade": get_shade + "get_shade": get_shade, + "scrub": scrub } def get_jloader(): diff --git a/frappe/utils/setup_docs.py b/frappe/utils/setup_docs.py index c47e6c389e..a9e0f61b28 100644 --- a/frappe/utils/setup_docs.py +++ b/frappe/utils/setup_docs.py @@ -6,20 +6,50 @@ Call from command line: """ -import os, json, frappe +import os, json, frappe, markdown2, shutil class setup_docs(object): - def __init__(self, app, docs_app, path): - """Generate source templates for models reference and module API. - - Must set globals `self.models_base_path`, `self.api_base_path` and `self.app_path`. + def __init__(self): + """Generate source templates for models reference and module API + and templates at `templates/autodoc` """ - self.app = app - self.app_path = frappe.get_app_path(app) - if path[0]=="/": path = path[1:] - path = frappe.get_app_path(docs_app, path) + self.app = frappe.get_hooks("autodoc").get("for_app")[0] + docs_app = frappe.get_hooks("autodoc").get("docs_app")[0] + + hooks = frappe.get_hooks(app_name = self.app) + self.app_title = hooks.get("app_title")[0] + + self.app_path = frappe.get_app_path(self.app) + path = frappe.get_app_path(docs_app, "www", "current") + + print "Deleting current..." + shutil.rmtree(path, ignore_errors = True) + os.makedirs(path) + + self.app_context = { + "app": { + "name": self.app, + "title": self.app_title, + "description": markdown2.markdown(hooks.get("app_description")[0]), + "version": hooks.get("app_version")[0], + "publisher": hooks.get("app_publisher")[0], + "github_link": hooks.get("github_link")[0], + } + } + + # make home page + with open(os.path.join(path, "index.html"), "w") as home: + home.write(frappe.render_template("templates/autodoc/docs_home.html", + self.app_context)) + + # make folders self.models_base_path = os.path.join(path, "models") + self.make_folder(self.models_base_path, + template = "templates/autodoc/models_home.html") + self.api_base_path = os.path.join(path, "api") + self.make_folder(self.api_base_path, + template = "templates/autodoc/api_home.html") for basepath, folders, files in os.walk(self.app_path): if "doctype" not in basepath: @@ -28,7 +58,9 @@ class setup_docs(object): module_folder = os.path.join(self.models_base_path, module) - self.make_folder(module_folder) + self.make_folder(module_folder, + template = "templates/autodoc/module_home.html", + context = {"name": module}) self.update_index_txt(module_folder) if "doctype" in basepath: @@ -70,35 +102,37 @@ class setup_docs(object): if not os.path.exists(module_doc_path): print "Writing " + module_doc_path with open(module_doc_path, "w") as f: - f.write("""

%(name)s

- - - {%%- from "templates/autodoc/macros.html" import automodule -%%} - - {{ automodule("%(name)s") }}""" % {"name": self.app + "." + module_name}) + context = {"name": self.app + "." + module_name} + context.update(self.app_context) + f.write(frappe.render_template("templates/autodoc/pymodule.html", + context)) self.update_index_txt(module_folder) - def make_folder(self, path): + def make_folder(self, path, template=None, context=None): + if not template: + template = "templates/autodoc/package_index.html" + if not os.path.exists(path): os.makedirs(path) - index_txt_path = os.path.join(path, "index.txt") - if not os.path.exists(index_txt_path): + index_txt_path = os.path.join(path, "index.txt") print "Writing " + index_txt_path with open(index_txt_path, "w") as f: f.write("") - index_md_path = os.path.join(path, "index.html") - if not os.path.exists(index_md_path): - name = os.path.basename(path) - if name==".": - name = self.app - print "Writing " + index_md_path - with open(index_md_path, "w") as f: - f.write("""

{0}

- -{{index}}""".format(name)) + index_html_path = os.path.join(path, "index.html") + if not context: + name = os.path.basename(path) + if name==".": + name = self.app + context = { + "title": name + } + context.update(self.app_context) + print "Writing " + index_html_path + with open(index_html_path, "w") as f: + f.write(frappe.render_template(template, context)) def update_index_txt(self, path): index_txt_path = os.path.join(path, "index.txt") @@ -126,12 +160,7 @@ class setup_docs(object): print "Writing " + model_path with open(model_path, "w") as f: - f.write("""

%(doctype)s

- - - {%% from "templates/autodoc/doctype.html" import render_doctype %%} - - {{ render_doctype("%(doctype)s") }} - - - """ % {"doctype": doctype_real_name}) + context = {"doctype": doctype_real_name} + context.update(self.app_context) + f.write(frappe.render_template("templates/autodoc/doctype.html", + context).encode("utf-8")) diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index fd7647834a..0ff58aa563 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -1,177 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt from __future__ import unicode_literals - -"""Use blog post test to test user permissions logic""" - import frappe -import frappe.defaults import unittest -import json -import frappe.model.meta -from frappe.core.page.user_permissions.user_permissions import add, remove, get_permissions -from frappe.permissions import clear_user_permissions_for_doctype, get_doc_permissions -test_records = frappe.get_test_records('Blog Post') - -test_dependencies = ["User"] class TestBlogPost(unittest.TestCase): - def setUp(self): - frappe.clear_cache(doctype="Blog Post") + pass - user = frappe.get_doc("User", "test1@example.com") - user.add_roles("Website Manager") - - user = frappe.get_doc("User", "test2@example.com") - user.add_roles("Blogger") - - frappe.set_user("test1@example.com") - - def tearDown(self): - frappe.set_user("Administrator") - frappe.db.set_value("Blogger", "_Test Blogger 1", "user", None) - - clear_user_permissions_for_doctype("Blog Category") - clear_user_permissions_for_doctype("Blog Post") - clear_user_permissions_for_doctype("Blogger") - - frappe.db.sql("""update `tabDocPerm` set user_permission_doctypes=null - where parent='Blog Post' and permlevel=0 and apply_user_permissions=1 - and `read`=1""") - - def test_basic_permission(self): - post = frappe.get_doc("Blog Post", "_test-blog-post") - self.assertTrue(post.has_permission("read")) - - def test_user_permissions_in_doc(self): - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", - "test2@example.com") - - frappe.set_user("test2@example.com") - - post = frappe.get_doc("Blog Post", "_test-blog-post") - self.assertFalse(post.has_permission("read")) - self.assertFalse(get_doc_permissions(post).get("read")) - - post1 = frappe.get_doc("Blog Post", "_test-blog-post-1") - self.assertTrue(post1.has_permission("read")) - self.assertTrue(get_doc_permissions(post1).get("read")) - - def test_user_permissions_in_report(self): - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") - - frappe.set_user("test2@example.com") - names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "blog_category"])] - - self.assertTrue("_test-blog-post-1" in names) - self.assertFalse("_test-blog-post" in names) - - def test_default_values(self): - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") - - frappe.set_user("test2@example.com") - doc = frappe.new_doc("Blog Post") - self.assertEquals(doc.get("blog_category"), "_Test Blog Category 1") - - def test_user_link_match_doc(self): - blogger = frappe.get_doc("Blogger", "_Test Blogger 1") - blogger.user = "test2@example.com" - blogger.save() - - frappe.set_user("test2@example.com") - - post = frappe.get_doc("Blog Post", "_test-blog-post-2") - self.assertTrue(post.has_permission("read")) - - post1 = frappe.get_doc("Blog Post", "_test-blog-post-1") - self.assertFalse(post1.has_permission("read")) - - def test_user_link_match_report(self): - blogger = frappe.get_doc("Blogger", "_Test Blogger 1") - blogger.user = "test2@example.com" - blogger.save() - - frappe.set_user("test2@example.com") - - names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "owner"])] - self.assertTrue("_test-blog-post-2" in names) - self.assertFalse("_test-blog-post-1" in names) - - def test_set_user_permissions(self): - frappe.set_user("test1@example.com") - add("test2@example.com", "Blog Post", "_test-blog-post") - - def test_not_allowed_to_set_user_permissions(self): - frappe.set_user("test2@example.com") - - # this user can't add user permissions - self.assertRaises(frappe.PermissionError, add, - "test2@example.com", "Blog Post", "_test-blog-post") - - def test_read_if_explicit_user_permissions_are_set(self): - self.test_set_user_permissions() - - frappe.set_user("test2@example.com") - - # user can only access permitted blog post - doc = frappe.get_doc("Blog Post", "_test-blog-post") - self.assertTrue(doc.has_permission("read")) - - # and not this one - doc = frappe.get_doc("Blog Post", "_test-blog-post-1") - self.assertFalse(doc.has_permission("read")) - - def test_not_allowed_to_remove_user_permissions(self): - self.test_set_user_permissions() - defname = get_permissions("test2@example.com", "Blog Post", "_test-blog-post")[0].name - - frappe.set_user("test2@example.com") - - # user cannot remove their own user permissions - self.assertRaises(frappe.PermissionError, remove, - "test2@example.com", defname, "Blog Post", "_test-blog-post") - - def test_user_permissions_based_on_blogger(self): - frappe.set_user("test2@example.com") - doc = frappe.get_doc("Blog Post", "_test-blog-post-1") - self.assertTrue(doc.has_permission("read")) - - frappe.set_user("test1@example.com") - add("test2@example.com", "Blog Post", "_test-blog-post") - - frappe.set_user("test2@example.com") - doc = frappe.get_doc("Blog Post", "_test-blog-post-1") - self.assertFalse(doc.has_permission("read")) - - doc = frappe.get_doc("Blog Post", "_test-blog-post") - self.assertTrue(doc.has_permission("read")) - - def test_set_only_once(self): - blog_post = frappe.get_meta("Blog Post") - blog_post.get_field("title").set_only_once = 1 - doc = frappe.get_doc("Blog Post", "_test-blog-post-1") - doc.title = "New" - self.assertRaises(frappe.CannotChangeConstantError, doc.save) - blog_post.get_field("title").set_only_once = 0 - - def test_user_permission_doctypes(self): - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", - "test2@example.com") - frappe.permissions.add_user_permission("Blogger", "_Test Blogger 1", - "test2@example.com") - - frappe.set_user("test2@example.com") - - frappe.db.sql("""update `tabDocPerm` set user_permission_doctypes=%s - where parent='Blog Post' and permlevel=0 and apply_user_permissions=1 - and `read`=1""", json.dumps(["Blogger"])) - - frappe.model.meta.clear_cache("Blog Post") - - doc = frappe.get_doc("Blog Post", "_test-blog-post") - self.assertFalse(doc.has_permission("read")) - - doc = frappe.get_doc("Blog Post", "_test-blog-post-2") - self.assertTrue(doc.has_permission("read")) - - frappe.model.meta.clear_cache("Blog Post") diff --git a/frappe/website/doctype/web_form/web_form.json b/frappe/website/doctype/web_form/web_form.json index 54b3a42ba7..af2998fbfb 100644 --- a/frappe/website/doctype/web_form/web_form.json +++ b/frappe/website/doctype/web_form/web_form.json @@ -214,7 +214,7 @@ "precision": "" }, { - "description": "In JSON as
[{\"title\":\"Jobs\", \"name\":\"jobs\"}]
", + "description": "In JSON as [{\"title\":\"Jobs\", \"name\":\"jobs\"}]", "fieldname": "breadcrumbs", "fieldtype": "Small Text", "label": "Breadcrumbs", @@ -262,4 +262,4 @@ "sort_field": "modified", "sort_order": "DESC", "title_field": "title" -} \ No newline at end of file +} diff --git a/frappe/website/doctype/web_page/web_page.json b/frappe/website/doctype/web_page/web_page.json index f671e49593..e9fa1bc3ea 100644 --- a/frappe/website/doctype/web_page/web_page.json +++ b/frappe/website/doctype/web_page/web_page.json @@ -200,22 +200,41 @@ "icon": "icon-file-alt", "idx": 1, "max_attachments": 20, - "modified": "2015-07-13 04:46:59.435179", + "modified": "2015-07-22 12:38:08.696692", "modified_by": "Administrator", "module": "Website", "name": "Web Page", "owner": "Administrator", "permissions": [ { + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Website Manager", + "share": 1, + "submit": 0, + "write": 0 + }, + { + "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, "email": 1, + "export": 1, + "import": 0, "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Website Manager", + "role": "Guest", + "set_user_permissions": 0, "share": 1, "submit": 0, "write": 1 diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index 9df4654b72..ecb6f234bc 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -231,7 +231,7 @@ "permlevel": 0 }, { - "description": "An icon file with .ico extension. Should be 16 x 16 px. Generated using a favicon generator. [favicon-generator.org]", + "description": "An icon file with .ico extension. Should be 16 x 16 px. Generated using a favicon generator. [favicon-generator.org]", "fieldname": "favicon", "fieldtype": "Attach", "label": "FavIcon", @@ -296,4 +296,4 @@ "submit": 0 } ] -} \ No newline at end of file +} diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index fb5b34a83f..3f168edc35 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -118,8 +118,6 @@ def get_website_settings(): context.web_include_css = hooks.web_include_css or [] - add_website_theme(context) - via_hooks = frappe.get_hooks("website_context") for key in via_hooks: context[key] = via_hooks[key] @@ -127,10 +125,12 @@ def get_website_settings(): and isinstance(context[key], (list, tuple)): context[key] = context[key][0] + add_website_theme(context) + if not context.get("favicon"): context["favicon"] = "/assets/frappe/images/favicon.png" - if settings.favicon: + if settings.favicon and settings.favicon != "attach_files:": context["favicon"] = settings.favicon return context diff --git a/frappe/website/doctype/website_theme/website_theme.json b/frappe/website/doctype/website_theme/website_theme.json index 5b8293ebce..b01c2576ab 100644 --- a/frappe/website/doctype/website_theme/website_theme.json +++ b/frappe/website/doctype/website_theme/website_theme.json @@ -1,304 +1,304 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "field:theme", - "creation": "2015-02-18 12:46:38.168929", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Master", + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "field:theme", + "creation": "2015-02-18 12:46:38.168929", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Master", "fields": [ { - "allow_on_submit": 0, - "fieldname": "theme", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Theme", - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, + "allow_on_submit": 0, + "fieldname": "theme", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Theme", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, "set_only_once": 0 - }, + }, { - "allow_on_submit": 0, - "default": "Website", - "fieldname": "module", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Module", - "no_copy": 0, - "options": "Module Def", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, + "allow_on_submit": 0, + "default": "Website", + "fieldname": "module", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Module", + "no_copy": 0, + "options": "Module Def", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, "set_only_once": 0 - }, + }, { - "default": "1", - "description": "This must be checked if the below style settings are applicable", - "fieldname": "apply_style", - "fieldtype": "Check", - "hidden": 0, - "label": "Apply Style", - "permlevel": 0, + "default": "1", + "description": "This must be checked if the below style settings are applicable", + "fieldname": "apply_style", + "fieldtype": "Check", + "hidden": 0, + "label": "Apply Style", + "permlevel": 0, "precision": "" - }, + }, { - "default": "1", - "fieldname": "custom", - "fieldtype": "Check", - "label": "Custom?", - "permlevel": 0, + "default": "1", + "fieldname": "custom", + "fieldtype": "Check", + "label": "Custom?", + "permlevel": 0, "precision": "" - }, + }, { - "allow_on_submit": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, + "allow_on_submit": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, "set_only_once": 0 - }, + }, { - "description": "Link to your Bootstrap theme", - "fieldname": "bootstrap", - "fieldtype": "Small Text", - "label": "Link to Bootstrap CSS", - "permlevel": 0, + "description": "Link to your Bootstrap theme", + "fieldname": "bootstrap", + "fieldtype": "Small Text", + "label": "Link to Bootstrap CSS", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "apply_style", - "fieldname": "section_break_14", - "fieldtype": "Section Break", - "permlevel": 0, + "depends_on": "apply_style", + "fieldname": "section_break_14", + "fieldtype": "Section Break", + "permlevel": 0, "precision": "" - }, + }, { - "default": "", - "fieldname": "font_size", - "fieldtype": "Select", - "label": "Font Size", - "options": "\n12px\n13px\n14px\n15px\n16px\n17px\n18px", - "permlevel": 0, + "default": "", + "fieldname": "font_size", + "fieldtype": "Select", + "label": "Font Size", + "options": "\n12px\n13px\n14px\n15px\n16px\n17px\n18px", + "permlevel": 0, "precision": "" - }, + }, { - "description": "Add the name of a Google Web Font e.g. \"Open Sans\"", - "fieldname": "text_webfont", - "fieldtype": "Data", - "label": "Google Font (Text)", - "permlevel": 0, + "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"", + "fieldname": "text_webfont", + "fieldtype": "Data", + "label": "Google Font (Text)", + "permlevel": 0, "precision": "" - }, + }, { - "description": "Add the name of a Google Web Font e.g. \"Open Sans\"", - "fieldname": "heading_webfont", - "fieldtype": "Data", - "label": "Google Font (Heading)", - "permlevel": 0, + "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"", + "fieldname": "heading_webfont", + "fieldtype": "Data", + "label": "Google Font (Heading)", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "column_break_18", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_18", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "text_color", - "fieldtype": "Data", - "label": "Text Color", - "permlevel": 0, + "fieldname": "text_color", + "fieldtype": "Data", + "label": "Text Color", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "link_color", - "fieldtype": "Data", - "label": "Link Color", - "permlevel": 0, + "fieldname": "link_color", + "fieldtype": "Data", + "label": "Link Color", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "heading_style", - "fieldtype": "Select", - "label": "Heading Style", - "options": "\nUPPERCASE\nTitle Case\nlowercase", - "permlevel": 0, + "fieldname": "heading_style", + "fieldtype": "Select", + "label": "Heading Style", + "options": "\nUPPERCASE\nTitle Case\nlowercase", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "apply_style", - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "permlevel": 0, + "depends_on": "apply_style", + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "top_bar_color", - "fieldtype": "Data", - "label": "Top Bar Color", - "permlevel": 0, + "fieldname": "top_bar_color", + "fieldtype": "Data", + "label": "Top Bar Color", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "top_bar_text_color", - "fieldtype": "Data", - "label": "Top Bar Text Color", - "permlevel": 0, + "fieldname": "top_bar_text_color", + "fieldtype": "Data", + "label": "Top Bar Text Color", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "no_sidebar", - "fieldtype": "Check", - "label": "Hide Sidebar", - "permlevel": 0, + "fieldname": "no_sidebar", + "fieldtype": "Check", + "label": "Hide Sidebar", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "footer_color", - "fieldtype": "Data", - "label": "Footer Color", - "permlevel": 0, + "fieldname": "footer_color", + "fieldtype": "Data", + "label": "Footer Color", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "footer_text_color", - "fieldtype": "Data", - "label": "Footer Text Color", - "permlevel": 0, + "fieldname": "footer_text_color", + "fieldtype": "Data", + "label": "Footer Text Color", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "apply_style", - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "permlevel": 0, + "depends_on": "apply_style", + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "background_color", - "fieldtype": "Data", - "label": "Background Color", - "permlevel": 0, + "fieldname": "background_color", + "fieldtype": "Data", + "label": "Background Color", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "column_break_6", - "fieldtype": "Column Break", - "permlevel": 0, + "fieldname": "column_break_6", + "fieldtype": "Column Break", + "permlevel": 0, "precision": "" - }, + }, { - "description": "If image is selected, color will be ignored.", - "fieldname": "background_image", - "fieldtype": "Attach Image", - "label": "Background Image", - "permlevel": 0, + "description": "If image is selected, color will be ignored.", + "fieldname": "background_image", + "fieldtype": "Attach Image", + "label": "Background Image", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "", - "fieldname": "section_break_21", - "fieldtype": "Section Break", - "permlevel": 0, + "depends_on": "", + "fieldname": "section_break_21", + "fieldtype": "Section Break", + "permlevel": 0, "precision": "" - }, + }, { - "depends_on": "apply_style", - "description": "", - "fieldname": "css", - "fieldtype": "Code", - "label": "Style using CSS", - "permlevel": 0, + "depends_on": "apply_style", + "description": "", + "fieldname": "css", + "fieldtype": "Code", + "label": "Style using CSS", + "permlevel": 0, "precision": "" - }, + }, { - "fieldname": "js", - "fieldtype": "Code", - "label": "JavaScript", - "permlevel": 0, + "fieldname": "js", + "fieldtype": "Code", + "label": "JavaScript", + "permlevel": 0, "precision": "" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "modified": "2015-07-13 04:45:02.429785", - "modified_by": "Administrator", - "module": "Website", - "name": "Website Theme", - "name_case": "", - "owner": "Administrator", + ], + "hide_heading": 0, + "hide_toolbar": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "modified": "2015-07-27 01:00:32.901851", + "modified_by": "Administrator", + "module": "Website", + "name": "Website Theme", + "name_case": "", + "owner": "Administrator", "permissions": [ { - "create": 1, - "delete": 1, - "permlevel": 0, - "read": 1, - "role": "Website Manager", + "create": 1, + "delete": 1, + "permlevel": 0, + "read": 1, + "role": "Website Manager", "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 0, - "export": 1, - "import": 1, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Administrator", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 0, + "export": 1, + "import": 1, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Administrator", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 1 } - ], - "read_only": 0, - "read_only_onload": 0, - "search_fields": "", - "sort_field": "modified", - "sort_order": "DESC", + ], + "read_only": 0, + "read_only_onload": 0, + "search_fields": "", + "sort_field": "modified", + "sort_order": "DESC", "title_field": "" -} \ No newline at end of file +} diff --git a/frappe/website/doctype/website_theme/website_theme.py b/frappe/website/doctype/website_theme/website_theme.py index 33601b0935..2cea0329f4 100644 --- a/frappe/website/doctype/website_theme/website_theme.py +++ b/frappe/website/doctype/website_theme/website_theme.py @@ -63,13 +63,20 @@ def use_theme(theme): def add_website_theme(context): bootstrap = frappe.get_hooks("bootstrap")[0] - website_theme = get_active_theme() - context.theme = website_theme and website_theme.as_dict() or frappe._dict() - if website_theme: - if website_theme.bootstrap: - bootstrap = website_theme.bootstrap + web_include_css = context.web_include_css + context.theme = frappe._dict() - context.no_sidebar = website_theme.no_sidebar + if not context.disable_website_theme: + website_theme = get_active_theme() + context.theme = website_theme and website_theme.as_dict() or frappe._dict() + + if website_theme: + if website_theme.bootstrap: + bootstrap = website_theme.bootstrap + + context.no_sidebar = website_theme.no_sidebar + + context.web_include_css = ["website_theme.css"] + context.web_include_css context.web_include_css = [bootstrap] + context.web_include_css diff --git a/frappe/website/js/website.js b/frappe/website/js/website.js index 4e4eb1d054..5adf938279 100644 --- a/frappe/website/js/website.js +++ b/frappe/website/js/website.js @@ -340,6 +340,9 @@ $.extend(frappe, { // change id of current page $(".page-container").attr("id", "page-" + data.path); + // set data-path value in body + $("body").attr("data-path", data.path); + // clear page-header-right $(".page-header-right").html(""); diff --git a/frappe/workflow/doctype/workflow/workflow.json b/frappe/workflow/doctype/workflow/workflow.json index 00244cdd2e..1d5b2232a8 100644 --- a/frappe/workflow/doctype/workflow/workflow.json +++ b/frappe/workflow/doctype/workflow/workflow.json @@ -1,100 +1,100 @@ { - "allow_rename": 1, - "autoname": "field:workflow_name", - "creation": "2012-12-28 10:49:55", - "description": "Defines workflow states and rules for a document.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Transaction", + "allow_rename": 1, + "autoname": "field:workflow_name", + "creation": "2012-12-28 10:49:55", + "description": "Defines workflow states and rules for a document.", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Transaction", "fields": [ { - "fieldname": "workflow_name", - "fieldtype": "Data", - "in_list_view": 0, - "label": "Workflow Name", - "permlevel": 0, - "read_only": 0, + "fieldname": "workflow_name", + "fieldtype": "Data", + "in_list_view": 0, + "label": "Workflow Name", + "permlevel": 0, + "read_only": 0, "reqd": 1 - }, + }, { - "description": "DocType on which this Workflow is applicable.", - "fieldname": "document_type", - "fieldtype": "Link", - "in_list_view": 0, - "label": "Document Type", - "options": "DocType", - "permlevel": 0, + "description": "DocType on which this Workflow is applicable.", + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 0, + "label": "Document Type", + "options": "DocType", + "permlevel": 0, "reqd": 1 - }, + }, { - "description": "If checked, all other workflows become inactive.", - "fieldname": "is_active", - "fieldtype": "Check", - "in_list_view": 0, - "label": "Is Active", + "description": "If checked, all other workflows become inactive.", + "fieldname": "is_active", + "fieldtype": "Check", + "in_list_view": 0, + "label": "Is Active", "permlevel": 0 - }, + }, { - "description": "Different \"States\" this document can exist in. Like \"Open\", \"Pending Approval\" etc.", - "fieldname": "states_head", - "fieldtype": "Section Break", - "label": "States", + "description": "Different \"States\" this document can exist in. Like \"Open\", \"Pending Approval\" etc.", + "fieldname": "states_head", + "fieldtype": "Section Break", + "label": "States", "permlevel": 0 - }, + }, { - "description": "All possible Workflow States and roles of the workflow.
Docstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"", - "fieldname": "states", - "fieldtype": "Table", - "label": "Document States", - "options": "Workflow Document State", - "permlevel": 0, + "description": "All possible Workflow States and roles of the workflow. \nDocstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"", + "fieldname": "states", + "fieldtype": "Table", + "label": "Document States", + "options": "Workflow Document State", + "permlevel": 0, "reqd": 1 - }, + }, { - "description": "Rules for how states are transitions, like next state and which role is allowed to change state etc.", - "fieldname": "transition_rules", - "fieldtype": "Section Break", - "label": "Transition Rules", + "description": "Rules for how states are transitions, like next state and which role is allowed to change state etc.", + "fieldname": "transition_rules", + "fieldtype": "Section Break", + "label": "Transition Rules", "permlevel": 0 - }, + }, { - "description": "Rules defining transition of state in the workflow.", - "fieldname": "transitions", - "fieldtype": "Table", - "label": "Transitions", - "options": "Workflow Transition", - "permlevel": 0, + "description": "Rules defining transition of state in the workflow.", + "fieldname": "transitions", + "fieldtype": "Table", + "label": "Transitions", + "options": "Workflow Transition", + "permlevel": 0, "reqd": 1 - }, + }, { - "default": "workflow_state", - "description": "Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)", - "fieldname": "workflow_state_field", - "fieldtype": "Data", - "label": "Workflow State Field", + "default": "workflow_state", + "description": "Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)", + "fieldname": "workflow_state_field", + "fieldtype": "Data", + "label": "Workflow State Field", "permlevel": 0 } - ], - "icon": "icon-random", - "idx": 1, - "modified": "2015-02-05 05:11:49.280959", - "modified_by": "Administrator", - "module": "Workflow", - "name": "Workflow", - "owner": "Administrator", + ], + "icon": "icon-random", + "idx": 1, + "modified": "2015-07-27 01:00:32.901851", + "modified_by": "Administrator", + "module": "Workflow", + "name": "Workflow", + "owner": "Administrator", "permissions": [ { - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "submit": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "submit": 0, "write": 1 } ] -} \ No newline at end of file +} diff --git a/setup.py b/setup.py index cd68a5b602..e3e366f215 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -version = "5.1.3" +version = "5.1.4" with open("requirements.txt", "r") as f: install_requires = f.readlines()