"
- + __("Apart from Role based Permission Rules, you can apply User Permissions based on DocTypes.")
- + "
"
-
- + "
"
- + __("These permissions will apply for all transactions where the permitted record is linked. For example, if Company C is added to User Permissions of user X, user X will only be able to see transactions that has company C as a linked value.")
- + "
"
-
- + "
"
- + __("These will also be set as default values for those links, if only one such permission record is defined.")
- + "
"
-
- + "
"
- + __("A user can be permitted to multiple records of the same DocType.")
- + "
'
- +__("These restrictions will apply for Document Types where 'Apply User Permissions' is checked for the permission rule and a field with this value is present.")
- +'
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.
-
-
Frappe Framework was designed to build ERPNext, open source
-ERP for managing small and medium sized businesses.
-
-
-
-
-
-
\ No newline at end of file
diff --git a/frappe/docs/index.txt b/frappe/docs/index.txt
deleted file mode 100644
index 4bdfdc4ac8..0000000000
--- a/frappe/docs/index.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-assets
-user
-contents
-current
-install
-license
diff --git a/frappe/docs/install.md b/frappe/docs/install.md
deleted file mode 100644
index 7350f8f4ab..0000000000
--- a/frappe/docs/install.md
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-# Installation
-
-Frappe Framework is based on the Frappe Framework, a full stack web framework based on Python, MariaDB, Redis, Node.
-
-To intall Frappe Framework, you will have to install the Frappe Bench, the command-line, package manager and site manager for Frappe Framework. For more details, read the Bench README.
-
-After you have installed Frappe Bench, go to you bench folder, which is `frappe.bench` by default and setup **frappe**.
-
- bench get-app frappe {{ source_link }}
-
-Then create a new site to install the app.
-
- bench new-site mysite
-
-This will create a new folder in your `/sites` directory and create a new database for this site.
-
-Next, install frappe in this site
-
- bench --site mysite install-app frappe
-
-To run this locally, run
-
- bench start
-
-Fire up your browser and go to http://localhost:8000 and you should see the login screen. Login as **Administrator** and **admin** (or the password you set at the time of `new-site`) and you are set.
-
-
-
\ No newline at end of file
diff --git a/frappe/docs/license.html b/frappe/docs/license.html
deleted file mode 100644
index 602685d65f..0000000000
--- a/frappe/docs/license.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
MIT
-
-
The MIT License (MIT)
-
-
Copyright (c) 2016 Frappe Technologies Pvt. Ltd.
-
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
-
\ No newline at end of file
diff --git a/frappe/docs/user/en/guides/app-development/generating-docs.md b/frappe/docs/user/en/guides/app-development/generating-docs.md
index 8494533d22..0775967494 100755
--- a/frappe/docs/user/en/guides/app-development/generating-docs.md
+++ b/frappe/docs/user/en/guides/app-development/generating-docs.md
@@ -1,18 +1,17 @@
# Generating Documentation Website for your App
-Frappe version 6.7 onwards includes a full-blown documentation generator so that you can easily create a website for your app that has both user docs and developers docs (auto-generated). These pages are generated as static HTML pages so that you can add them as GitHub pages.
+Frappe version 6.7 onwards includes a full-blown documentation generator so that you can easily create a website for your app that has both user docs and developers docs (auto-generated).
+
+Version 8.7 onwards, these will be generated in a target app.
## Writing Docs
### 1. Setting up docs
-#### 1.1. Setup `docs.py`
-
The first step is to setup the docs folder. For that you must create a new file in your app `config/docs.py` if it is not auto-generated. In your `docs.py` file, add the following module properties.
source_link = "https://github.com/[orgname]/[reponame]"
- docs_base_url = "https://[orgname].github.io/[reponame]"
headline = "This is what my app does"
sub_heading = "Slightly more details with key features"
long_description = """(long description in markdown)"""
@@ -29,16 +28,6 @@ The first step is to setup the docs folder. For that you must create a new file
pass
-#### 1.2. Generate `/docs`
-
-To generate the docs for the `current` version, go to the command line and write
-
- bench --site [site] build-docs [appname]
-
-If you want to maintain versions of your docs, then you can add a version number instead of `current`
-
-This will create a `/docs` folder in your app.
-
### 2. Add User Documentation
To add user documentation, add folders and pages in your `/docs/user` folder in the same way you would build a website pages in the `www` folder.
@@ -54,61 +43,28 @@ Some quick tips:
While linking make sure you add `{{ docs_base_url }}` to all your links.
- {% raw %}Link Description{% endraw %}
+ {% raw %}Link Description{% endraw %}
### 4. Adding Images
You can add images in the `/docs/assets` folder. You can add links to the images as follows:
- {% raw %}{% endraw %}
-
----
-
-## Setting up output docs
-
-The output docs are generated in your `docs/appname` folder using the `write-docs` command.
-
----
-
-## Viewing Locally
-
-To test your docs locally, add a `--local` option to the `write-docs` command.
-
- bench --site [site] write-docs [appname] --local
-
-Then it will build urls so that you can view these files locally. To view them locally in your browser, you can use the Python SimpleHTTPServer
-
-Run this from your `docs/myapp` folder:
-
- python -m SimpleHTTPServer 8080
+ {% raw %}{% endraw %}
---
-## Publishing to GitHub Pages
-To publish your docs on GitHub pages, you will have to create an empty and orphan branch in your repository called `gh-pages` and push your documentation there.
+## Building Docs
-1. To easily publish your docs on gh-pages, commit and push your `apps/docs` folder on you master branch first.
-2. The `/docs` generation will also generate a `/docs` folder in your bench, parallel to your `/sites` folder. e.g. `/frappe-bench/docs`
-3. Generate you documentation using the `write-docs` command.
-4. Go to your docs folder `cd docs/myapp`
-5. Checkout the gh-pages branch `git checkout --orphan gh-pages`
-6. Push your documentation to Github.
+You must create a new app that will have the output of the docs, which is called the "target" app. For example, the docs for ERPNext are hosted at erpnext.org, which is based on the app "foundation". You can create a new app just to push docs of any other app.
-Note > The branch name `gh-pages` is only if you are using GitHub. If you are hosting this on any other static file server, you can create any other orphan branch instead.
+To output docs to another app,
-Putting it all together:
+ bench --site [site] build-docs [app] --target [target_app]
- # build the apps/docs folder and write the compiled docs at docs/appname
- bench --site [site] build-docs [appname]
+This will create a new folder `/docs` inside the `www` folder of the target app and generate automatic docs (from code), model references and copy user docs and assets.
- # commit to the gh-pages branch (for GitHub Pages)
- cd docs/appname
- git checkout --orphan gh-pages
- git remote add origin [remote git repository]
- git add *
- git commit -m "Documentation Initialization"
- git push origin gh-pages
+To view the docs, just go the the `/docs` url on your target app. Example:
-To check your documentation online go to: https://[orgname].github.io/[reponame]
+ https://erpnext.org/docs
diff --git a/frappe/docs/user/en/guides/desk/making_graphs.md b/frappe/docs/user/en/guides/desk/making_graphs.md
index 9234fa58b4..720c9217bf 100644
--- a/frappe/docs/user/en/guides/desk/making_graphs.md
+++ b/frappe/docs/user/en/guides/desk/making_graphs.md
@@ -1,61 +1,100 @@
# Making Graphs
-The Frappe UI **Graph** object enables you to render simple line and bar graphs for a discreet set of data points. You can also set special checkpoint values and summary stats.
+The Frappe UI **Graph** object enables you to render simple line, bar or percentage graphs for single or multiple discreet sets of data points. You can also set special checkpoint values and summary stats.
### Example: Line graph
-Here's is an example of a simple sales graph:
-
- render_graph: function() {
- $('.form-graph').empty();
-
- var months = ['Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
- var values = [2410, 3100, 1700, 1200, 2700, 1600, 2740, 1000, 850, 1500, 400, 2013];
-
- var goal = 2500;
- var current_val = 2013;
-
- new frappe.ui.Graph({
- parent: $('.form-graph'),
- width: 700,
- height: 140,
- mode: 'line-graph',
-
- title: 'Sales',
- subtitle: 'Monthly',
- y_values: values,
- x_points: months,
-
- specific_values: [
- {
- name: "Goal",
- line_type: "dashed", // "dashed" or "solid"
- value: goal
- },
- ],
- summary_values: [
- {
- name: "This month",
- color: 'green', // Indicator colors: 'grey', 'blue', 'red',
- // 'green', 'orange', 'purple', 'darkgrey',
- // 'black', 'yellow', 'lightblue'
- value: '₹ ' + current_val
- },
- {
- name: "Goal",
- color: 'blue',
- value: '₹ ' + goal
- },
- {
- name: "Completed",
- color: 'green',
- value: (current_val/goal*100).toFixed(1) + "%"
- }
- ]
- });
- },
-
-
-
-Setting the mode to 'bar-graph':
+Here's an example of a simple sales graph:
+
+ // Data
+ let months = ['August, 2016', 'September, 2016', 'October, 2016', 'November, 2016',
+ 'December, 2016', 'January, 2017', 'February, 2017', 'March, 2017', 'April, 2017',
+ 'May, 2017', 'June, 2017', 'July, 2017'];
+
+ let values1 = [24100, 31000, 17000, 12000, 27000, 16000, 27400, 11000, 8500, 15000, 4000, 20130];
+ let values2 = [17890, 10400, 12350, 20400, 17050, 23000, 7100, 13800, 16000, 20400, 11000, 13000];
+ let goal = 25000;
+ let current_val = 20130;
+
+ let g = new frappe.ui.Graph({
+ parent: $('.form-graph').empty(),
+ height: 200, // optional
+ mode: 'line', // 'line', 'bar' or 'percentage'
+
+ title: 'Sales',
+ subtitle: 'Monthly',
+
+ y: [
+ {
+ title: 'Data 1',
+ values: values1,
+ formatted: values1.map(d => '$ ' + d),
+ color: 'green' // Indicator colors: 'grey', 'blue', 'red',
+ // 'green', 'light-green', 'orange', 'purple', 'darkgrey',
+ // 'black', 'yellow', 'lightblue'
+ },
+ {
+ title: 'Data 2',
+ values: values2,
+ formatted: values2.map(d => '$ ' + d),
+ color: 'light-green'
+ }
+ ],
+
+ x: {
+ values: months.map(d => d.substring(0, 3)),
+ formatted: months
+ },
+
+ specific_values: [
+ {
+ name: 'Goal',
+ line_type: 'dashed', // 'dashed' or 'solid'
+ value: goal
+ },
+ ],
+
+ summary: [
+ {
+ name: 'This month',
+ color: 'orange',
+ value: '$ ' + current_val
+ },
+ {
+ name: 'Goal',
+ color: 'blue',
+ value: '$ ' + goal
+ },
+ {
+ name: 'Completed',
+ color: 'green',
+ value: (current_val/goal*100).toFixed(1) + "%"
+ }
+ ]
+ });
+
+
+
+`bar` mode yeilds:
+
+You can set the `colors` property of `x` to an array of color values for `percentage` mode:
+
+
+
+You can also change the values of an existing graph with a new set of `y` values:
+
+ setTimeout(() => {
+ g.change_values([
+ {
+ values: data[2],
+ formatted: data[2].map(d => d + 'L')
+ },
+ {
+ values: data[3],
+ formatted: data[3].map(d => d + 'L')
+ }
+ ]);
+ }, 1000);
+
+
diff --git a/frappe/docs/user/en/guides/reports-and-printing/how-to-make-query-report.md b/frappe/docs/user/en/guides/reports-and-printing/how-to-make-query-report.md
index 9eb11b066a..1e22757d7e 100755
--- a/frappe/docs/user/en/guides/reports-and-printing/how-to-make-query-report.md
+++ b/frappe/docs/user/en/guides/reports-and-printing/how-to-make-query-report.md
@@ -39,7 +39,7 @@ You can define complex queries such as:
### 4. Advanced (adding filters)
-If you are making a standard report, you can add filters in your query report just like [script reports](https://frappe.github.io/frappe/user/en/guides/reports-and-printing/how-to-make-script-reports) by adding a `.js` file in your query report folder. To include filters in your query, use `%(filter_key)s` where your filter value will be shown.
+If you are making a standard report, you can add filters in your query report just like [script reports](https://frappe.io/docs/user/en/guides/reports-and-printing/how-to-make-script-reports) by adding a `.js` file in your query report folder. To include filters in your query, use `%(filter_key)s` where your filter value will be shown.
For example
diff --git a/frappe/docs/user/en/guides/reports-and-printing/how-to-make-script-reports.md b/frappe/docs/user/en/guides/reports-and-printing/how-to-make-script-reports.md
index ab03c11051..74479a8b9c 100755
--- a/frappe/docs/user/en/guides/reports-and-printing/how-to-make-script-reports.md
+++ b/frappe/docs/user/en/guides/reports-and-printing/how-to-make-script-reports.md
@@ -4,9 +4,9 @@ You can create tabulated reports using server side scripts by creating a new Rep
> Note: You will need Administrator Permissions for this.
-Since these reports give you unrestricted access via Python scripts, they can only be created by Administrators. The script part of the report becomes a part of the repository of the application. If you have not created an app, [read this](https://frappe.github.io/frappe/user/en/guides/app-development/).
+Since these reports give you unrestricted access via Python scripts, they can only be created by Administrators. The script part of the report becomes a part of the repository of the application. If you have not created an app, [read this](https://frappe.io/docs/user/en/guides/app-development/).
-> Note: You must be in [Developer Mode](https://frappe.github.io/frappe/user/en/guides/app-development/how-enable-developer-mode-in-frappe) to do this
+> Note: You must be in [Developer Mode](https://frappe.io/docs/user/en/guides/app-development/how-enable-developer-mode-in-frappe) to do this
### 1. Create a new Report
diff --git a/frappe/docs/user/en/tutorial/before.md b/frappe/docs/user/en/tutorial/before.md
index 80f34e01dd..426101e1cc 100755
--- a/frappe/docs/user/en/tutorial/before.md
+++ b/frappe/docs/user/en/tutorial/before.md
@@ -6,11 +6,12 @@
#### 1. Python
-Frappe uses Python (v2.7) for server-side programming. It is highly recommended to learn Python before you start building apps with Frappe.
+Frappe uses Python (v2.7) for server-side programming. It is highly recommended to learn Python before you start building apps with Frappe.
To write quality server-side code, you must also include automated tests.
Resources:
+
1. [Codecademy Tutorial for Python](https://www.codecademy.com/learn/python)
1. [Official Python Tutorial](https://docs.python.org/2.7/tutorial/index.html)
1. [Basics of Test-driven development](http://code.tutsplus.com/tutorials/beginning-test-driven-development-in-python--net-30137)
@@ -19,11 +20,12 @@ Resources:
#### 2. MariaDB / MySQL
-To create database-driven apps with Frappe, you must understand the basics of database management, like how to install, login, create new databases, and basic SQL queries.
+To create database-driven apps with Frappe, you must understand the basics of database management, like how to install, login, create new databases, and basic SQL queries.
Resources:
+
1. [Codecademy Tutorial for SQL](https://www.codecademy.com/learn/learn-sql)
- 1. [A basic MySQL tutorial by DigitalOcean](https://www.digitalocean.com/community/tutorials/a-basic-mysql-tutorial)
+ 1. [A basic MySQL tutorial by DigitalOcean](https://www.digitalocean.com/community/tutorials/a-basic-mysql-tutorial)
1. [Getting started with MariaDB](https://mariadb.com/kb/en/mariadb/documentation/getting-started/)
---
@@ -33,6 +35,7 @@ Resources:
If you want to build user interfaces using Frappe, you will need to learn basic HTML / CSS and the Boostrap CSS Framework.
Resources:
+
1. [Codecademy Tutorial for HTML/CSS](https://www.codecademy.com/learn/learn-html-css)
1. [Getting started with Bootstrap](https://getbootstrap.com/getting-started/)
@@ -44,6 +47,7 @@ To customize forms and create rich user interfaces, you should learn JavaScript
Resources:
+
1. [Codecademy Tutorial for JavaScript](https://www.codecademy.com/learn/learn-javascript)
1. [Codecademy Tutorial for jQuery](https://www.codecademy.com/learn/jquery)
---
@@ -53,6 +57,7 @@ Resources:
If you are customizing Print templates or Web pages, you need to learn the Jinja Templating language. It is an easy way to create dynamic web pages (HTML).
Resources:
+
1. [Primer on Jinja Templating](https://realpython.com/blog/python/primer-on-jinja-templating/)
1. [Official Documentation](http://jinja.pocoo.org/)
@@ -63,6 +68,7 @@ Resources:
Learn how to contribute back to an open source project using Git and GitHub, two great tools to help you manage your code and share it with others.
Resources:
+
1. [Basic Git Tutorial](https://try.github.io)
2. [How to contribute to Open Source](https://opensource.guide/how-to-contribute/)
diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py
index cc5c394d9f..846d8101e6 100644
--- a/frappe/model/create_new.py
+++ b/frappe/model/create_new.py
@@ -11,6 +11,7 @@ from frappe.utils import nowdate, nowtime, now_datetime
import frappe.defaults
from frappe.model.db_schema import type_map
import copy
+from frappe.core.doctype.user_permission.user_permission import get_user_permissions
def get_new_doc(doctype, parent_doc = None, parentfield = None, as_dict=False):
if doctype not in frappe.local.new_doc_templates:
@@ -47,7 +48,7 @@ def make_new_doc(doctype):
return doc
def set_user_and_static_default_values(doc):
- user_permissions = frappe.defaults.get_user_permissions()
+ user_permissions = get_user_permissions()
defaults = frappe.defaults.get_defaults()
for df in doc.meta.get("fields"):
@@ -103,7 +104,7 @@ def get_static_default_value(df, user_permissions):
def set_dynamic_default_values(doc, parent_doc, parentfield):
# these values should not be cached
- user_permissions = frappe.defaults.get_user_permissions()
+ user_permissions = get_user_permissions()
for df in frappe.get_meta(doc["doctype"]).get("fields"):
if df.get("default"):
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index eeee7f4bf7..56e4cc343d 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -388,7 +388,7 @@ class DatabaseQuery(object):
# apply user permissions?
if role_permissions.get("apply_user_permissions", {}).get("read"):
# get user permissions
- user_permissions = frappe.defaults.get_user_permissions(self.user)
+ user_permissions = frappe.permissions.get_user_permissions(self.user)
self.add_user_permissions(user_permissions,
user_permission_doctypes=role_permissions.get("user_permission_doctypes").get("read"))
diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py
index 3bf49a5f6a..e242a48c88 100644
--- a/frappe/model/delete_doc.py
+++ b/frappe/model/delete_doc.py
@@ -11,7 +11,7 @@ from frappe.utils.file_manager import remove_all
from frappe.utils.password import delete_all_passwords_for
from frappe import _
from frappe.model.naming import revert_series_if_last
-from frappe.utils.global_search import delete_for_document
+from frappe.utils.global_search import delete_for_document
def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False,
ignore_permissions=False, flags=None, ignore_on_trash=False):
@@ -158,8 +158,13 @@ def update_flags(doc, flags=None, ignore_permissions=False):
def check_permission_and_not_submitted(doc):
# permission
- if not doc.flags.ignore_permissions and frappe.session.user!="Administrator" and (not doc.has_permission("delete") or (doc.doctype=="DocType" and not doc.custom)):
- frappe.msgprint(_("User not allowed to delete {0}: {1}").format(doc.doctype, doc.name), raise_exception=True)
+ if (not doc.flags.ignore_permissions
+ and frappe.session.user!="Administrator"
+ and (
+ not doc.has_permission("delete")
+ or (doc.doctype=="DocType" and not doc.custom))):
+ frappe.msgprint(_("User not allowed to delete {0}: {1}")
+ .format(doc.doctype, doc.name), raise_exception=frappe.PermissionError)
# check if submitted
if doc.docstatus == 1:
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 8adf636701..3347ae8287 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -189,3 +189,4 @@ frappe.patches.v8_1.enable_allow_error_traceback_in_system_settings
frappe.patches.v8_1.update_format_options_in_auto_email_report
frappe.patches.v8_1.delete_custom_docperm_if_doctype_not_exists
frappe.patches.v8_5.delete_email_group_member_with_invalid_emails
+frappe.patches.v8_x.update_user_permission
diff --git a/frappe/patches/v8_x/__init__.py b/frappe/patches/v8_x/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/patches/v8_x/update_user_permission.py b/frappe/patches/v8_x/update_user_permission.py
new file mode 100644
index 0000000000..4ceb26e945
--- /dev/null
+++ b/frappe/patches/v8_x/update_user_permission.py
@@ -0,0 +1,25 @@
+import frappe
+
+def execute():
+ frappe.reload_doc('core', 'doctype', 'user_permission')
+ frappe.delete_doc('core', 'page', 'user-permissions')
+ for perm in frappe.db.sql("""
+ select
+ name, parent, defkey, defvalue
+ from
+ tabDefaultValue
+ where
+ parent not in ('__default', '__global')
+ and
+ substr(defkey,1,1)!='_'
+ and
+ parenttype='User Permission'
+ """, as_dict=True):
+ frappe.get_doc(dict(
+ doctype='User Permission',
+ user=perm.parent,
+ allow=perm.defkey,
+ for_value=perm.defvalue
+ )).insert(ignore_permissions = True)
+
+ frappe.db.sql('delete from tabDefaultValue where parenttype="User Permission"')
diff --git a/frappe/permissions.py b/frappe/permissions.py
index 13da62d366..29f223d08e 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -7,7 +7,6 @@ import frappe, copy, json
from frappe import _, msgprint
from frappe.utils import cint
import frappe.share
-
rights = ("read", "write", "create", "delete", "submit", "cancel", "amend",
"print", "email", "report", "import", "export", "set_user_permissions", "share")
@@ -25,6 +24,9 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
"""
if not user: user = frappe.session.user
+ if verbose:
+ print('--- Checking for {0} {1} ---'.format(doctype, doc.name if doc else '-'))
+
if frappe.is_table(doctype):
if verbose: print("Table type, always true")
return True
@@ -40,7 +42,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
return False
if user=="Administrator":
- if verbose: print("Administrator")
+ if verbose: print("Allowing Administrator")
return True
def false_if_not_shared():
@@ -210,7 +212,10 @@ def get_role_permissions(meta, user=None, verbose=False):
if p.user_permission_doctypes:
# set user_permission_doctypes in perms
- user_permission_doctypes = json.loads(p.user_permission_doctypes)
+ try:
+ user_permission_doctypes = json.loads(p.user_permission_doctypes)
+ except ValueError:
+ user_permission_doctypes = []
else:
user_permission_doctypes = get_linked_doctypes(meta.name)
@@ -247,8 +252,12 @@ def get_role_permissions(meta, user=None, verbose=False):
return frappe.local.role_permissions[cache_key]
+def get_user_permissions(user):
+ from frappe.core.doctype.user_permission.user_permission import get_user_permissions
+ return get_user_permissions(user)
+
def user_has_permission(doc, verbose=True, user=None, user_permission_doctypes=None):
- from frappe.defaults import get_user_permissions
+ from frappe.core.doctype.user_permission.user_permission import get_user_permissions
user_permissions = get_user_permissions(user)
user_permission_doctypes = get_user_permission_doctypes(user_permission_doctypes, user_permissions)
@@ -258,6 +267,10 @@ def user_has_permission(doc, verbose=True, user=None, user_permission_doctypes=N
messages = {}
+ if not user_permission_doctypes:
+ # no doctypes restricted
+ end_result = True
+
# check multiple sets of user_permission_doctypes using OR condition
for doctypes in user_permission_doctypes:
result = True
@@ -309,9 +322,9 @@ def has_controller_permissions(doc, ptype, user=None):
def get_doctypes_with_read():
return list(set([p.parent for p in get_valid_perms()]))
-def get_valid_perms(doctype=None):
+def get_valid_perms(doctype=None, user=None):
'''Get valid permissions for the current user from DocPerm and Custom DocPerm'''
- roles = get_roles()
+ roles = get_roles(user)
perms = get_perms_for(roles)
custom_perms = get_perms_for(roles, 'Custom DocPerm')
@@ -360,7 +373,8 @@ def get_roles(user=None, with_standard=True):
def get_perms_for(roles, perm_doctype='DocPerm'):
'''Get perms for given roles'''
- return frappe.db.sql("""select * from `tab{doctype}` where docstatus=0
+ return frappe.db.sql("""
+ select * from `tab{doctype}` where docstatus=0
and ifnull(permlevel,0)=0
and role in ({roles})""".format(doctype = perm_doctype,
roles=", ".join(["%s"]*len(roles))), tuple(roles), as_dict=1)
@@ -386,22 +400,28 @@ def set_user_permission_if_allowed(doctype, name, user, with_message=False):
if get_role_permissions(frappe.get_meta(doctype), user).set_user_permissions!=1:
add_user_permission(doctype, name, user, with_message)
-def add_user_permission(doctype, name, user, with_message=False):
- '''Add user default'''
- if name not in frappe.defaults.get_user_permissions(user).get(doctype, []):
+def add_user_permission(doctype, name, user, apply=False):
+ '''Add user permission'''
+ from frappe.core.doctype.user_permission.user_permission import get_user_permissions
+ if name not in get_user_permissions(user).get(doctype, []):
if not frappe.db.exists(doctype, name):
frappe.throw(_("{0} {1} not found").format(_(doctype), name), frappe.DoesNotExistError)
- frappe.defaults.add_default(doctype, name, user, "User Permission")
- elif with_message:
- msgprint(_("Permission already set"))
+ frappe.get_doc(dict(
+ doctype='User Permission',
+ user=user,
+ allow=doctype,
+ for_value=name,
+ apply_for_all_roles=apply
+ )).insert()
-def remove_user_permission(doctype, name, user, default_value_name=None):
- frappe.defaults.clear_default(key=doctype, value=name, parent=user, parenttype="User Permission",
- name=default_value_name)
+def remove_user_permission(doctype, name, user):
+ user_permission_name = frappe.db.get_value('User Permission',
+ dict(user=user, allow=doctype, for_value=name))
+ frappe.delete_doc('User Permission', user_permission_name)
def clear_user_permissions_for_doctype(doctype):
- frappe.defaults.clear_default(parenttype="User Permission", key=doctype)
+ frappe.cache().delete_value('user_permissions')
def can_import(doctype, raise_exception=False):
if not ("System Manager" in frappe.get_roles() or has_permission(doctype, "import")):
@@ -426,9 +446,10 @@ def apply_user_permissions(doctype, ptype, user=None):
def get_user_permission_doctypes(user_permission_doctypes, user_permissions):
"""returns a list of list like [["User", "Blog Post"], ["User"]]"""
- if cint(frappe.db.get_single_value("System Settings", "ignore_user_permissions_if_missing")):
+ if cint(frappe.get_system_settings('ignore_user_permissions_if_missing')):
# select those user permission doctypes for which user permissions exist!
- user_permission_doctypes = [list(set(doctypes).intersection(set(user_permissions.keys())))
+ user_permission_doctypes = [
+ list(set(doctypes).intersection(set(user_permissions.keys())))
for doctypes in user_permission_doctypes]
if len(user_permission_doctypes) > 1:
@@ -452,6 +473,22 @@ def get_user_permission_doctypes(user_permission_doctypes, user_permissions):
return user_permission_doctypes
+def update_permission_property(doctype, role, permlevel, ptype, value=None, validate=True):
+ '''Update a property in Custom Perm'''
+ from frappe.core.doctype.doctype.doctype import validate_permissions_for_doctype
+ out = setup_custom_perms(doctype)
+
+ name = frappe.get_value('Custom DocPerm', dict(parent=doctype, role=role,
+ permlevel=permlevel))
+
+ frappe.db.sql("""
+ update `tabCustom DocPerm`
+ set `{0}`=%s where name=%s""".format(ptype), (value, name))
+ if validate:
+ validate_permissions_for_doctype(doctype)
+
+ return out
+
def setup_custom_perms(parent):
'''if custom permssions are not setup for the current doctype, set them up'''
if not frappe.db.exists('Custom DocPerm', dict(parent=parent)):
diff --git a/frappe/public/build.json b/frappe/public/build.json
index b350c8151a..054421286e 100755
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -52,7 +52,8 @@
"public/css/desktop.css",
"public/css/form.css",
"public/css/mobile.css",
- "public/css/kanban.css"
+ "public/css/kanban.css",
+ "public/css/graphs.css"
],
"css/frappe-rtl.css": [
"public/css/bootstrap-rtl.css",
@@ -164,7 +165,7 @@
"public/js/frappe/query_string.js",
"public/js/frappe/ui/charts.js",
- "public/js/frappe/ui/graph.js",
+ "public/js/frappe/ui/graphs.js",
"public/js/frappe/ui/comment.js",
"public/js/frappe/misc/rating_icons.html",
diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css
index bfce576e37..22ecdb993b 100644
--- a/frappe/public/css/desk.css
+++ b/frappe/public/css/desk.css
@@ -258,7 +258,7 @@ a[disabled="disabled"]:hover {
}
.link-btn {
position: absolute;
- top: 2px;
+ top: 3px;
right: 4px;
border-radius: 2px;
padding: 3px;
diff --git a/frappe/public/css/docs.css b/frappe/public/css/docs.css
index 20b11d5cb1..2fdd0ae21d 100644
--- a/frappe/public/css/docs.css
+++ b/frappe/public/css/docs.css
@@ -597,9 +597,3 @@ a.edit:visited,
.page-content-wrapper > .row .col-sm-4 {
display: none;
}
-.screenshot {
- border: 2px solid #d1d8dd;
- box-shadow: 1px 1px 7px rgba(0, 0, 0, 0.15);
- margin: 15px 0px;
- max-width: 100%;
-}
diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css
index 0d21271862..c56811e892 100644
--- a/frappe/public/css/form.css
+++ b/frappe/public/css/form.css
@@ -678,80 +678,6 @@ select.form-control {
padding: 10px;
margin: 10px;
}
-.graph-container .graphics {
- margin-top: 10px;
- padding: 10px 0px;
-}
-.graph-container .stats-group {
- display: flex;
- justify-content: space-around;
- flex: 1;
-}
-.graph-container .stats-container {
- display: flex;
- justify-content: space-around;
-}
-.graph-container .stats-container .stats {
- padding-bottom: 15px;
-}
-.graph-container .stats-container .stats-title {
- color: #8D99A6;
-}
-.graph-container .stats-container .stats-value {
- font-size: 20px;
- font-weight: 300;
-}
-.graph-container .stats-container .stats-description {
- font-size: 12px;
- color: #8D99A6;
-}
-.graph-container .stats-container .graph-data .stats-value {
- color: #98d85b;
-}
-.bar-graph .axis,
-.line-graph .axis {
- font-size: 10px;
- fill: #6a737d;
-}
-.bar-graph .axis line,
-.line-graph .axis line {
- stroke: rgba(27, 31, 35, 0.1);
-}
-.data-points circle {
- fill: #28a745;
- stroke: #fff;
- stroke-width: 2;
-}
-.data-points g.mini {
- fill: #98d85b;
-}
-.data-points path {
- fill: none;
- stroke: #28a745;
- stroke-opacity: 1;
- stroke-width: 2px;
-}
-.line-graph .path {
- fill: none;
- stroke: #28a745;
- stroke-opacity: 1;
- stroke-width: 2px;
-}
-line.dashed {
- stroke-dasharray: 5,3;
-}
-.tick.x-axis-label {
- display: block;
-}
-.tick .specific-value {
- text-anchor: start;
-}
-.tick .y-value-text {
- text-anchor: end;
-}
-.tick .x-value-text {
- text-anchor: middle;
-}
body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] {
height: 80px !important;
}
diff --git a/frappe/public/css/graphs.css b/frappe/public/css/graphs.css
new file mode 100644
index 0000000000..a9fdf62dc9
--- /dev/null
+++ b/frappe/public/css/graphs.css
@@ -0,0 +1,274 @@
+/* graphs */
+.graph-container .graph-focus-margin {
+ margin: 0px 5%;
+}
+.graph-container .graph-graphics {
+ margin-top: 10px;
+ padding: 10px 0px;
+ position: relative;
+}
+.graph-container .graph-stats-group {
+ display: flex;
+ justify-content: space-around;
+ flex: 1;
+}
+.graph-container .graph-stats-container {
+ display: flex;
+ justify-content: space-around;
+ padding-top: 10px;
+}
+.graph-container .graph-stats-container .stats {
+ padding-bottom: 15px;
+}
+.graph-container .graph-stats-container .stats-title {
+ color: #8D99A6;
+}
+.graph-container .graph-stats-container .stats-value {
+ font-size: 20px;
+ font-weight: 300;
+}
+.graph-container .graph-stats-container .stats-description {
+ font-size: 12px;
+ color: #8D99A6;
+}
+.graph-container .graph-stats-container .graph-data .stats-value {
+ color: #98d85b;
+}
+.graph-container .bar-graph .axis,
+.graph-container .line-graph .axis {
+ font-size: 10px;
+ fill: #6a737d;
+}
+.graph-container .bar-graph .axis line,
+.graph-container .line-graph .axis line {
+ stroke: rgba(27, 31, 35, 0.1);
+}
+.graph-container .percentage-graph {
+ margin-top: 35px;
+}
+.graph-container .percentage-graph .progress {
+ margin-bottom: 0px;
+}
+.graph-container .graph-data-points circle {
+ stroke: #fff;
+ stroke-width: 2;
+}
+.graph-container .graph-data-points path {
+ fill: none;
+ stroke-opacity: 1;
+ stroke-width: 2px;
+}
+.graph-container line.graph-dashed {
+ stroke-dasharray: 5,3;
+}
+.graph-container .tick.x-axis-label {
+ display: block;
+}
+.graph-container .tick .specific-value {
+ text-anchor: start;
+}
+.graph-container .tick .y-value-text {
+ text-anchor: end;
+}
+.graph-container .tick .x-value-text {
+ text-anchor: middle;
+}
+.graph-container .graph-svg-tip {
+ position: absolute;
+ z-index: 99999;
+ padding: 10px;
+ font-size: 12px;
+ color: #959da5;
+ text-align: center;
+ background: rgba(0, 0, 0, 0.8);
+ border-radius: 3px;
+}
+.graph-container .graph-svg-tip.comparison {
+ padding: 0;
+ text-align: left;
+ pointer-events: none;
+}
+.graph-container .graph-svg-tip.comparison .title {
+ display: block;
+ padding: 10px;
+ margin: 0;
+ font-weight: 600;
+ line-height: 1;
+ pointer-events: none;
+}
+.graph-container .graph-svg-tip.comparison ul {
+ margin: 0;
+ white-space: nowrap;
+ list-style: none;
+}
+.graph-container .graph-svg-tip.comparison li {
+ display: inline-block;
+ padding: 5px 10px;
+}
+.graph-container .graph-svg-tip ul,
+.graph-container .graph-svg-tip ol {
+ padding-left: 0;
+ display: flex;
+}
+.graph-container .graph-svg-tip ul.data-point-list li {
+ min-width: 90px;
+ flex: 1;
+}
+.graph-container .graph-svg-tip strong {
+ color: #dfe2e5;
+}
+.graph-container .graph-svg-tip::after {
+ position: absolute;
+ bottom: -10px;
+ left: 50%;
+ width: 5px;
+ height: 5px;
+ margin: 0 0 0 -5px;
+ content: " ";
+ border: 5px solid transparent;
+ border-top-color: rgba(0, 0, 0, 0.8);
+}
+.graph-container .stroke.grey {
+ stroke: #F0F4F7;
+}
+.graph-container .stroke.blue {
+ stroke: #5e64ff;
+}
+.graph-container .stroke.red {
+ stroke: #ff5858;
+}
+.graph-container .stroke.light-green {
+ stroke: #98d85b;
+}
+.graph-container .stroke.green {
+ stroke: #28a745;
+}
+.graph-container .stroke.orange {
+ stroke: #ffa00a;
+}
+.graph-container .stroke.purple {
+ stroke: #743ee2;
+}
+.graph-container .stroke.darkgrey {
+ stroke: #b8c2cc;
+}
+.graph-container .stroke.black {
+ stroke: #36414C;
+}
+.graph-container .stroke.yellow {
+ stroke: #FEEF72;
+}
+.graph-container .stroke.light-blue {
+ stroke: #7CD6FD;
+}
+.graph-container .stroke.lightblue {
+ stroke: #7CD6FD;
+}
+.graph-container .fill.grey {
+ fill: #F0F4F7;
+}
+.graph-container .fill.blue {
+ fill: #5e64ff;
+}
+.graph-container .fill.red {
+ fill: #ff5858;
+}
+.graph-container .fill.light-green {
+ fill: #98d85b;
+}
+.graph-container .fill.green {
+ fill: #28a745;
+}
+.graph-container .fill.orange {
+ fill: #ffa00a;
+}
+.graph-container .fill.purple {
+ fill: #743ee2;
+}
+.graph-container .fill.darkgrey {
+ fill: #b8c2cc;
+}
+.graph-container .fill.black {
+ fill: #36414C;
+}
+.graph-container .fill.yellow {
+ fill: #FEEF72;
+}
+.graph-container .fill.light-blue {
+ fill: #7CD6FD;
+}
+.graph-container .fill.lightblue {
+ fill: #7CD6FD;
+}
+.graph-container .background.grey {
+ background: #F0F4F7;
+}
+.graph-container .background.blue {
+ background: #5e64ff;
+}
+.graph-container .background.red {
+ background: #ff5858;
+}
+.graph-container .background.light-green {
+ background: #98d85b;
+}
+.graph-container .background.green {
+ background: #28a745;
+}
+.graph-container .background.orange {
+ background: #ffa00a;
+}
+.graph-container .background.purple {
+ background: #743ee2;
+}
+.graph-container .background.darkgrey {
+ background: #b8c2cc;
+}
+.graph-container .background.black {
+ background: #36414C;
+}
+.graph-container .background.yellow {
+ background: #FEEF72;
+}
+.graph-container .background.light-blue {
+ background: #7CD6FD;
+}
+.graph-container .background.lightblue {
+ background: #7CD6FD;
+}
+.graph-container .border-top.grey {
+ border-top: 3px solid #F0F4F7;
+}
+.graph-container .border-top.blue {
+ border-top: 3px solid #5e64ff;
+}
+.graph-container .border-top.red {
+ border-top: 3px solid #ff5858;
+}
+.graph-container .border-top.light-green {
+ border-top: 3px solid #98d85b;
+}
+.graph-container .border-top.green {
+ border-top: 3px solid #28a745;
+}
+.graph-container .border-top.orange {
+ border-top: 3px solid #ffa00a;
+}
+.graph-container .border-top.purple {
+ border-top: 3px solid #743ee2;
+}
+.graph-container .border-top.darkgrey {
+ border-top: 3px solid #b8c2cc;
+}
+.graph-container .border-top.black {
+ border-top: 3px solid #36414C;
+}
+.graph-container .border-top.yellow {
+ border-top: 3px solid #FEEF72;
+}
+.graph-container .border-top.light-blue {
+ border-top: 3px solid #7CD6FD;
+}
+.graph-container .border-top.lightblue {
+ border-top: 3px solid #7CD6FD;
+}
diff --git a/frappe/public/css/website.css b/frappe/public/css/website.css
index 9a92bccf38..b9b2d733bb 100644
--- a/frappe/public/css/website.css
+++ b/frappe/public/css/website.css
@@ -430,6 +430,9 @@ h6 a {
color: inherit !important;
text-decoration: none;
}
+li {
+ line-height: 1.7em;
+}
.navbar-brand {
max-width: none;
}
@@ -503,6 +506,9 @@ h6 a {
min-height: 140px;
border-top: 1px solid #EBEFF2;
}
+.page_content {
+ padding-bottom: 30px;
+}
.carousel-control .icon {
position: absolute;
top: 50%;
@@ -599,7 +605,7 @@ fieldset {
}
.web-sidebar .sidebar-item {
margin: 0px;
- padding: 12px 0px;
+ padding-bottom: 12px;
border: none;
color: #8D99A6;
font-size: 12px;
@@ -607,21 +613,14 @@ fieldset {
.web-sidebar .sidebar-item .badge {
font-weight: normal;
}
-.web-sidebar .sidebar-item:first-child {
- padding-top: 10px;
-}
-.web-sidebar .sidebar-item:last-child {
- padding-bottom: 10px;
-}
.web-sidebar .sidebar-item a {
- color: #8D99A6;
+ color: #36414C !important;
}
.web-sidebar .sidebar-item a.active {
color: #36414C !important;
font-weight: 500 !important;
}
.web-sidebar .sidebar-items {
- margin-top: -10px;
margin-bottom: 30px;
}
.web-sidebar .sidebar-items .title {
@@ -675,69 +674,25 @@ fieldset {
.web-list-item:last-child {
border-bottom: 0px;
}
-.blog-info {
- text-align: center;
- margin-top: 30px;
-}
-.post-description {
- padding-bottom: 8px;
-}
-.post-description p {
- margin-bottom: 8px;
-}
-.blog-footer {
- padding: 5px 15px;
- border-top: 1px solid #EBEFF2;
- margin: 0px -15px -20px -15px;
-}
-.blog-list-content .website-list .result {
+.website-list .result {
border: 0px;
}
-.blog-list-content .web-list-item:hover {
+.web-list-item:hover {
background: transparent;
}
-.blog-category {
- letter-spacing: 0.5px;
- text-align: center;
- margin-bottom: 30px;
-}
-.author {
- letter-spacing: 0.5px;
- border-bottom: 1px solid #EBEFF2;
- padding-bottom: 30px;
-}
-.blogger {
- padding-top: 0px;
- padding-bottom: 50px;
-}
-.blog-dot:before {
+.spacer-dot:before {
padding-right: 8px;
padding-left: 8px;
content: "\2022";
}
-.blog-list-item {
- margin-top: 30px;
- margin-bottom: 30px;
-}
-.blog-list-item .blog-header {
- font-size: 1.6em;
-}
-.blog-header {
- font-weight: 700;
- font-size: 2em;
-}
.add-comment-section {
padding-bottom: 30px;
}
-.blog-comments {
- position: relative;
- border-top: 1px solid #d1d8dd;
-}
-.blog-comment-row {
+.comment-row {
margin: 0px -15px;
padding: 15px;
}
-.blog-comment-row:last-child {
+.comment-row:last-child {
margin-bottom: 30px;
border-bottom: 0px;
}
@@ -837,7 +792,7 @@ a.active {
}
.sidebar-block,
.page-content {
- padding-top: 50px;
+ padding-top: 30px;
padding-bottom: 50px;
}
.your-account-info {
@@ -871,19 +826,6 @@ a.active {
li.footer-child-item {
margin: 15px 0px;
}
-.blog-info {
- text-align: center;
- margin-top: 30px;
-}
-.blog-text {
- padding-top: 50px;
- padding-bottom: 50px;
- font-size: 18px;
- line-height: 1.5;
-}
-.blog-text p {
- margin-bottom: 30px;
-}
.comment-view {
padding-bottom: 30px;
}
@@ -927,7 +869,7 @@ li.footer-child-item {
overflow: hidden;
}
.vert-line > div + div {
- border-left: 1px solid #EBEFF2;
+ border-left: 1px solid #d1d8dd;
}
.vert-line > div {
padding-bottom: 2000px;
@@ -994,3 +936,13 @@ li.footer-child-item {
padding: 10px;
border-radius: 4px;
}
+.docfields pre {
+ background-color: transparent;
+ border: none;
+}
+.screenshot {
+ border: 1px solid #d1d8dd;
+ box-shadow: 1px 1px 7px rgba(0, 0, 0, 0.15);
+ margin: 15px 0px;
+ max-width: 100%;
+}
diff --git a/frappe/public/js/frappe/defaults.js b/frappe/public/js/frappe/defaults.js
index 24ba5e630e..570ae61e2a 100644
--- a/frappe/public/js/frappe/defaults.js
+++ b/frappe/public/js/frappe/defaults.js
@@ -77,10 +77,6 @@ frappe.defaults = {
},
get_user_permissions: function() {
- return frappe.defaults.user_permissions;
+ return frappe.boot.user_permissions;
},
- set_user_permissions: function(user_permissions) {
- if(!user_permissions) return;
- frappe.defaults.user_permissions = $.extend(frappe.defaults.user_permissions || {}, user_permissions);
- }
}
diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js
index d27aa619e0..ca2b4ab9bd 100644
--- a/frappe/public/js/frappe/form/dashboard.js
+++ b/frappe/public/js/frappe/form/dashboard.js
@@ -418,9 +418,8 @@ frappe.ui.form.Dashboard = Class.extend({
this.graph_area.empty().removeClass('hidden');
$.extend(args, {
parent: me.graph_area,
- width: 710,
- height: 140,
- mode: 'line-graph'
+ mode: 'line',
+ height: 140
});
new frappe.ui.Graph(args);
diff --git a/frappe/public/js/frappe/list/list_renderer.js b/frappe/public/js/frappe/list/list_renderer.js
index 1a6b6aa07c..1ec5b3e37e 100644
--- a/frappe/public/js/frappe/list/list_renderer.js
+++ b/frappe/public/js/frappe/list/list_renderer.js
@@ -271,7 +271,10 @@ frappe.views.ListRenderer = Class.extend({
setup_filterable: function () {
var me = this;
+
+ this.list_view.wrapper &&
this.list_view.wrapper.on('click', '.result-list .filterable', function (e) {
+ e.stopPropagation();
var filters = $(this).attr('data-filter').split('|');
var added = false;
@@ -294,7 +297,9 @@ frappe.views.ListRenderer = Class.extend({
me.list_view.refresh(true);
}
});
- this.wrapper.on('click', '.list-item', function (e) {
+
+ this.list_view.wrapper &&
+ this.list_view.wrapper.on('click', '.list-item', function (e) {
// don't open in case of checkbox, like, filterable
if ($(e.target).hasClass('filterable')
|| $(e.target).hasClass('octicon-heart')
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 05facf346d..a7024a9fb7 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -599,9 +599,9 @@ frappe.views.ListView = frappe.ui.BaseList.extend({
}, true);
}
if (frappe.model.can_set_user_permissions(this.doctype)) {
- this.page.add_menu_item(__('User Permissions Manager'), function () {
- frappe.set_route('user-permissions', {
- doctype: me.doctype
+ this.page.add_menu_item(__('User Permissions'), function () {
+ frappe.set_route('List', 'User Permission', {
+ allow: me.doctype
});
}, true);
}
diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js
index 04496c6238..a25c3e70ca 100644
--- a/frappe/public/js/frappe/model/create_new.js
+++ b/frappe/public/js/frappe/model/create_new.js
@@ -127,8 +127,10 @@ $.extend(frappe.model, {
var user_default = "";
var user_permissions = frappe.defaults.get_user_permissions();
var meta = frappe.get_meta(doc.doctype);
- var has_user_permissions = (df.fieldtype==="Link" && user_permissions
- && df.ignore_user_permissions != 1 && user_permissions[df.options]);
+ var has_user_permissions = (df.fieldtype==="Link"
+ && user_permissions
+ && df.ignore_user_permissions != 1
+ && user_permissions[df.options]);
// don't set defaults for "User" link field using User Permissions!
if (df.fieldtype==="Link" && df.options!=="User") {
diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js
index adb2e845a6..7b22c5fd47 100644
--- a/frappe/public/js/frappe/model/model.js
+++ b/frappe/public/js/frappe/model/model.js
@@ -112,7 +112,6 @@ $.extend(frappe.model, {
localStorage["_doctype:" + doctype] = JSON.stringify(r.docs);
}
frappe.model.init_doctype(doctype);
- frappe.defaults.set_user_permissions(r.user_permissions);
if(r.user_settings) {
// remember filters and other settings from last view
diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js
index b338d23f3d..0d74e61b84 100644
--- a/frappe/public/js/frappe/ui/filters/filters.js
+++ b/frappe/public/js/frappe/ui/filters/filters.js
@@ -87,6 +87,7 @@ frappe.ui.FilterList = Class.extend({
}
var filter = this.push_new_filter(doctype, fieldname, condition, value);
+ if (!filter) return;
if(this.wrapper.find('.clear-filters').hasClass("hide")) {
this.wrapper.find('.clear-filters').removeClass("hide");
diff --git a/frappe/public/js/frappe/ui/graph.js b/frappe/public/js/frappe/ui/graph.js
deleted file mode 100644
index 2347f13b0d..0000000000
--- a/frappe/public/js/frappe/ui/graph.js
+++ /dev/null
@@ -1,308 +0,0 @@
-// specific_values = [
-// {
-// name: "Average",
-// line_type: "dashed", // "dashed" or "solid"
-// value: 10
-// },
-
-// summary_values = [
-// {
-// name: "Total",
-// color: 'blue', // Indicator colors: 'grey', 'blue', 'red', 'green', 'orange',
-// // 'purple', 'darkgrey', 'black', 'yellow', 'lightblue'
-// value: 80
-// }
-// ]
-
-frappe.ui.Graph = class Graph {
- constructor({
- parent = null,
-
- width = 0, height = 0,
- title = '', subtitle = '',
-
- y_values = [],
- x_points = [],
-
- specific_values = [],
- summary_values = [],
-
- color = '',
- mode = '',
- } = {}) {
-
- if(Object.getPrototypeOf(this) === frappe.ui.Graph.prototype) {
- if(mode === 'line-graph') {
- return new frappe.ui.LineGraph(arguments[0]);
- } else if(mode === 'bar-graph') {
- return new frappe.ui.BarGraph(arguments[0]);
- }
- }
-
- this.parent = parent;
-
- this.width = width;
- this.height = height;
-
- this.title = title;
- this.subtitle = subtitle;
-
- this.y_values = y_values;
- this.x_points = x_points;
-
- this.specific_values = specific_values;
- this.summary_values = summary_values;
-
- this.color = color;
- this.mode = mode;
-
- this.$graph = null;
-
- frappe.require("assets/frappe/js/lib/snap.svg-min.js", this.setup.bind(this));
- }
-
- setup() {
- this.setup_container();
- this.refresh();
- }
-
- refresh() {
- this.setup_values();
- this.setup_components();
- this.make_y_axis();
- this.make_x_axis();
- this.make_units();
- if(this.specific_values.length > 0) {
- this.show_specific_values();
- }
- this.setup_group();
-
- if(this.summary_values.length > 0) {
- this.show_summary();
- }
- }
-
- setup_container() {
- this.container = $('
').format(**next_item)
-
- out = out.replace('{next}', html)
-
- return out
-
-
def load_properties(page_info):
'''Load properties like no_cache, title from raw'''
if not page_info.title:
- page_info.title = extract_title(page_info.source, page_info.name)
+ page_info.title = extract_title(page_info.source, page_info.route)
- if page_info.title and not '{% block title %}' in page_info.source:
- page_info.source += '\n{% block title %}{{ title }}{% endblock %}'
+ # if page_info.title and not '{% block title %}' in page_info.source:
+ # if not page_info.only_content:
+ # page_info.source += '\n{% block title %}{{ title }}{% endblock %}'
if "" in page_info.source:
page_info.no_breadcrumbs = 1
@@ -304,13 +282,19 @@ def load_properties(page_info):
if "" in page_info.source:
page_info.show_sidebar = 1
+ if "" in page_info.source:
+ page_info.add_breadcrumbs = 1
+
if "" in page_info.source:
page_info.no_header = 1
- else:
- # every page needs a header
- # add missing header if there is no
tag
- if (not '{% block header %}' in page_info.source) and (not '
{{ title }}
{% endblock %}'
+ # else:
+ # # every page needs a header
+ # # add missing header if there is no
tag
+ # if (not '{% block header %}' in page_info.source) and (not '
{{ title }}
{% endblock %}'
if "" in page_info.source:
page_info.no_cache = 1
@@ -346,7 +330,7 @@ def sync_global_search():
for app in frappe.get_installed_apps(frappe_last=True):
app_path = frappe.get_app_path(app)
- folders = frappe.local.flags.web_pages_folders or ('www', 'templates/pages')
+ folders = get_start_folders()
for start in folders:
for basepath, folders, files in os.walk(os.path.join(app_path, start)):
@@ -374,3 +358,5 @@ def sync_global_search():
sync_global_search()
+def get_start_folders():
+ return frappe.local.flags.web_pages_folders or ('www', 'templates/pages')
\ No newline at end of file
diff --git a/frappe/website/utils.py b/frappe/website/utils.py
index 31ff4acddc..9424ec722e 100644
--- a/frappe/website/utils.py
+++ b/frappe/website/utils.py
@@ -5,9 +5,9 @@ from __future__ import unicode_literals
import frappe, re, os
from six import iteritems
-
def delete_page_cache(path):
cache = frappe.cache()
+ cache.delete_value('full_index')
groups = ("website_page", "page_context")
if path:
for name in groups:
@@ -184,38 +184,89 @@ def abs_url(path):
path = "/" + path
return path
+def get_toc(route, url_prefix=None, app=None):
+ '''Insert full index (table of contents) for {index} tag'''
+ from frappe.website.utils import get_full_index
+
+ full_index = get_full_index(app=app)
+
+ return frappe.get_template("templates/includes/full_index.html").render({
+ "full_index": full_index,
+ "url_prefix": url_prefix or "/",
+ "route": route.rstrip('/')
+ })
+
+def get_next_link(route, url_prefix=None, app=None):
+ # insert next link
+ next_item = None
+ route = route.rstrip('/')
+ children_map = get_full_index(app=app)
+ parent_route = os.path.dirname(route)
+ children = children_map[parent_route]
+
+ if parent_route and children:
+ for i, c in enumerate(children):
+ if c.route == route and i < (len(children) - 1):
+ next_item = children[i+1]
+ next_item.url_prefix = url_prefix or "/"
+
+ if next_item:
+ if next_item.route and next_item.title:
+ html = ('
').format(**next_item)
+
+ return html
+
+ return ''
+
def get_full_index(route=None, app=None):
"""Returns full index of the website for www upto the n-th level"""
+ from frappe.website.router import get_pages
+
if not frappe.local.flags.children_map:
- from frappe.website.router import get_pages
- children_map = {}
- pages = get_pages(app=app)
-
- # make children map
- for route, page_info in iteritems(pages):
- parent_route = os.path.dirname(route)
- children_map.setdefault(parent_route, []).append(page_info)
-
- if frappe.flags.local_docs:
- page_info.extn = '.html'
-
- # order as per index if present
- for route, children in children_map.items():
- page_info = pages[route]
- if page_info.index:
- new_children = []
- page_info.extn = ''
- for name in page_info.index:
- child_route = page_info.route + '/' + name
- if child_route in pages:
- new_children.append(pages[child_route])
-
- # add remaining pages not in index.txt
- for c in children:
- if c not in new_children:
- new_children.append(c)
-
- children_map[route] = new_children
+ def _build():
+ children_map = {}
+ added = []
+ pages = get_pages(app=app)
+
+ # make children map
+ for route, page_info in iteritems(pages):
+ parent_route = os.path.dirname(route)
+ if parent_route not in added:
+ children_map.setdefault(parent_route, []).append(page_info)
+
+ # order as per index if present
+ for route, children in children_map.items():
+ if not route in pages:
+ # no parent (?)
+ continue
+
+ page_info = pages[route]
+ if page_info.index or ('index' in page_info.template):
+ new_children = []
+ page_info.extn = ''
+ for name in (page_info.index or []):
+ child_route = page_info.route + '/' + name
+ if child_route in pages:
+ if child_route not in added:
+ new_children.append(pages[child_route])
+ added.append(child_route)
+
+ # add remaining pages not in index.txt
+ _children = sorted(children, lambda a, b: cmp(
+ os.path.basename(a.route), os.path.basename(b.route)))
+
+ for child_route in _children:
+ if child_route not in new_children:
+ if child_route not in added:
+ new_children.append(child_route)
+ added.append(child_route)
+
+ children_map[route] = new_children
+
+ return children_map
+
+ children_map = frappe.cache().get_value('website_full_index', _build)
frappe.local.flags.children_map = children_map
@@ -223,12 +274,14 @@ def get_full_index(route=None, app=None):
def extract_title(source, path):
'''Returns title from `<!-- title -->` or <h1> or path'''
+ title = ''
+
if "', source)[0].strip()
elif "
" in source:
match = re.findall('
([^<]*)', source)
title = match[0].strip()[:300]
- else:
- title = os.path.basename(path).replace('_', ' ').replace('-', ' ').title()
+ if not title:
+ title = os.path.basename(path.rsplit('.', )[0].rstrip('/')).replace('_', ' ').replace('-', ' ').title()
return title
diff --git a/frappe/www/message.html b/frappe/www/message.html
index 0eb183fcfd..4697534a2e 100644
--- a/frappe/www/message.html
+++ b/frappe/www/message.html
@@ -3,7 +3,19 @@
{% block title %}{{ title or _("Message") }}{% endblock %}
{% block page_content %}
-
+
{% for item in children_map[route] %}-
- {{ item.title }}
+ {{ item.title }}
+ {#
{% if children_map[item.route] %}
{{ make_item_list(item.route, children_map) }}
{% endif %}
+ #}
{% endfor %}
diff --git a/frappe/templates/includes/web_sidebar.html b/frappe/templates/includes/web_sidebar.html index 764ce4d277..d452e8af3f 100644 --- a/frappe/templates/includes/web_sidebar.html +++ b/frappe/templates/includes/web_sidebar.html @@ -22,7 +22,7 @@ {% endif %} {% for item in sidebar_items -%}