Merge branch 'staging'
This commit is contained in:
commit
311dff662a
182 changed files with 16129 additions and 12417 deletions
|
|
@ -54,6 +54,7 @@
|
|||
"Taggle": true,
|
||||
"Gantt": true,
|
||||
"Slick": true,
|
||||
"Webcam": true,
|
||||
"PhotoSwipe": true,
|
||||
"PhotoSwipeUI_Default": true,
|
||||
"fluxify": true,
|
||||
|
|
|
|||
BIN
.github/logo.png
vendored
Normal file
BIN
.github/logo.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -9,3 +9,5 @@ locale
|
|||
dist/
|
||||
build/
|
||||
frappe/docs/current
|
||||
.vscode
|
||||
node_modules
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"python.linting.pylintEnabled": false
|
||||
}
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2016-2017 Frappé Technologies Pvt. Ltd. <developers@frappe.io>
|
||||
|
||||
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.
|
||||
2
Makefile
Normal file
2
Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
clean:
|
||||
python setup.py clean
|
||||
31
README.md
31
README.md
|
|
@ -1,9 +1,33 @@
|
|||
## Frappé Framework
|
||||
<div align="center">
|
||||
<img src=".github/logo.png" height="256">
|
||||
<h1>
|
||||
<a href="https://frappe.io">
|
||||
frappé
|
||||
</a>
|
||||
</h1>
|
||||
<h3>
|
||||
a web framework with <a href="https://www.youtube.com/watch?v=LOjk3m0wTwg">"batteries included"
|
||||
</h3>
|
||||
<h5>
|
||||
it's pronounced - <em>fra-pay</em>
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
[](https://travis-ci.org/frappe/frappe)
|
||||
<div align="center">
|
||||
<a href="https://travis-ci.org/frappe/frappe">
|
||||
<img src="https://img.shields.io/travis/frappe/frappe.svg?style=flat-square">
|
||||
</a>
|
||||
<a href='https://frappe.io/docs'>
|
||||
<img src='https://img.shields.io/badge/docs-📖-7575FF.svg?style=flat-square'/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com)
|
||||
|
||||
### Table of Contents
|
||||
* [Installation](#installation)
|
||||
* [License](#license)
|
||||
|
||||
### Installation
|
||||
|
||||
[Install via Frappé Bench](https://github.com/frappe/bench)
|
||||
|
|
@ -20,5 +44,4 @@ For details and documentation, see the website
|
|||
[https://frappe.io](https://frappe.io)
|
||||
|
||||
### License
|
||||
|
||||
MIT License
|
||||
This repository has been released under the [MIT License](LICENSE).
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json
|
|||
from .exceptions import *
|
||||
from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template
|
||||
|
||||
__version__ = '9.0.10'
|
||||
__version__ = '9.1.0'
|
||||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
|
|
@ -602,7 +602,7 @@ def set_value(doctype, docname, fieldname, value=None):
|
|||
import frappe.client
|
||||
return frappe.client.set_value(doctype, docname, fieldname, value)
|
||||
|
||||
def get_doc(arg1, arg2=None):
|
||||
def get_doc(*args, **kwargs):
|
||||
"""Return a `frappe.model.document.Document` object of the given type and name.
|
||||
|
||||
:param arg1: DocType name as string **or** document JSON.
|
||||
|
|
@ -619,7 +619,7 @@ def get_doc(arg1, arg2=None):
|
|||
|
||||
"""
|
||||
import frappe.model.document
|
||||
return frappe.model.document.get_doc(arg1, arg2)
|
||||
return frappe.model.document.get_doc(*args, **kwargs)
|
||||
|
||||
def get_last_doc(doctype):
|
||||
"""Get last created document of this type."""
|
||||
|
|
|
|||
|
|
@ -123,10 +123,9 @@ function pack(output_path, inputs, minify) {
|
|||
}
|
||||
|
||||
function babelify(content, path, minify) {
|
||||
let presets = ['es2015', 'es2016'];
|
||||
// if(minify) {
|
||||
// presets.push('babili'); // new babel minifier
|
||||
// }
|
||||
let presets = ['env'];
|
||||
// Minification doesn't work when loading Frappe Desk
|
||||
// Avoid for now, trace the error and come back.
|
||||
try {
|
||||
return babel.transform(content, {
|
||||
presets: presets,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
from __future__ import unicode_literals, print_function
|
||||
from frappe.utils.minify import JavascriptMinify
|
||||
import subprocess
|
||||
import warnings
|
||||
|
||||
from six import iteritems, text_type
|
||||
|
||||
|
|
@ -25,12 +26,12 @@ def setup():
|
|||
except ImportError: pass
|
||||
app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules]
|
||||
|
||||
def bundle(no_compress, make_copy=False, verbose=False):
|
||||
def bundle(no_compress, make_copy=False, restore=False, verbose=False):
|
||||
"""concat / minify js files"""
|
||||
# build js files
|
||||
setup()
|
||||
|
||||
make_asset_dirs(make_copy=make_copy)
|
||||
make_asset_dirs(make_copy=make_copy, restore=restore)
|
||||
|
||||
# new nodejs build system
|
||||
command = 'node --use_strict ../apps/frappe/frappe/build.js --build'
|
||||
|
|
@ -60,7 +61,8 @@ def watch(no_compress):
|
|||
|
||||
# time.sleep(3)
|
||||
|
||||
def make_asset_dirs(make_copy=False):
|
||||
def make_asset_dirs(make_copy=False, restore=False):
|
||||
# don't even think of making assets_path absolute - rm -rf ahead.
|
||||
assets_path = os.path.join(frappe.local.sites_path, "assets")
|
||||
for dir_path in [
|
||||
os.path.join(assets_path, 'js'),
|
||||
|
|
@ -80,11 +82,28 @@ def make_asset_dirs(make_copy=False):
|
|||
|
||||
for source, target in symlinks:
|
||||
source = os.path.abspath(source)
|
||||
if not os.path.exists(target) and os.path.exists(source):
|
||||
if make_copy:
|
||||
shutil.copytree(source, target)
|
||||
if os.path.exists(source):
|
||||
if restore:
|
||||
if os.path.exists(target):
|
||||
if os.path.islink(target):
|
||||
os.unlink(target)
|
||||
else:
|
||||
shutil.rmtree(target)
|
||||
shutil.copytree(source, target)
|
||||
elif make_copy:
|
||||
if os.path.exists(target):
|
||||
warnings.warn('Target {target} already exists.'.format(target = target))
|
||||
else:
|
||||
shutil.copytree(source, target)
|
||||
else:
|
||||
if os.path.exists(target):
|
||||
if os.path.islink(target):
|
||||
os.unlink(target)
|
||||
else:
|
||||
shutil.rmtree(target)
|
||||
os.symlink(source, target)
|
||||
else:
|
||||
warnings.warn('Source {source} does not exists.'.format(source = source))
|
||||
|
||||
def build(no_compress=False, verbose=False):
|
||||
assets_path = os.path.join(frappe.local.sites_path, "assets")
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@ from frappe.utils import update_progress_bar
|
|||
|
||||
@click.command('build')
|
||||
@click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking')
|
||||
@click.option('--restore', is_flag=True, default=False, help='Copy the files instead of symlinking with force')
|
||||
@click.option('--verbose', is_flag=True, default=False, help='Verbose')
|
||||
def build(make_copy=False, verbose=False):
|
||||
def build(make_copy=False, restore = False, verbose=False):
|
||||
"Minify + concatenate JS and CSS files, build translations"
|
||||
import frappe.build
|
||||
import frappe
|
||||
frappe.init('')
|
||||
frappe.build.bundle(False, make_copy=make_copy, verbose=verbose)
|
||||
frappe.build.bundle(False, make_copy=make_copy, restore = restore, verbose=verbose)
|
||||
|
||||
@click.command('watch')
|
||||
def watch():
|
||||
|
|
|
|||
|
|
@ -72,6 +72,12 @@ def get_data():
|
|||
"name": "GSuite Templates",
|
||||
"description": _("Google GSuite Templates to integration with DocTypes"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Webhook",
|
||||
"description": _("Webhooks calling API requests into web apps"),
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -353,7 +353,8 @@
|
|||
"label": "Email Address",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"options": "Email",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class Communication(Document):
|
|||
"""create email flag queue"""
|
||||
if self.communication_type == "Communication" and self.communication_medium == "Email" \
|
||||
and self.sent_or_received == "Received" and self.uid and self.uid != -1:
|
||||
|
||||
|
||||
email_flag_queue = frappe.db.get_value("Email Flag Queue", {
|
||||
"communication": self.name,
|
||||
"is_completed": 0})
|
||||
|
|
@ -69,7 +69,7 @@ class Communication(Document):
|
|||
def after_insert(self):
|
||||
if not (self.reference_doctype and self.reference_name):
|
||||
return
|
||||
|
||||
|
||||
if self.reference_doctype == "Communication" and self.sent_or_received == "Sent":
|
||||
frappe.db.set_value("Communication", self.reference_name, "status", "Replied")
|
||||
|
||||
|
|
@ -94,9 +94,10 @@ class Communication(Document):
|
|||
|
||||
def on_update(self):
|
||||
"""Update parent status as `Open` or `Replied`."""
|
||||
update_parent_status(self)
|
||||
update_comment_in_doc(self)
|
||||
self.bot_reply()
|
||||
if self.comment_type != 'Updated':
|
||||
update_parent_status(self)
|
||||
update_comment_in_doc(self)
|
||||
self.bot_reply()
|
||||
|
||||
def on_trash(self):
|
||||
if (not self.flags.ignore_permissions
|
||||
|
|
@ -264,7 +265,7 @@ def has_permission(doc, ptype, user):
|
|||
if (doc.reference_doctype == "Communication" and doc.reference_name == doc.name) \
|
||||
or (doc.timeline_doctype == "Communication" and doc.timeline_name == doc.name):
|
||||
return
|
||||
|
||||
|
||||
if doc.reference_doctype and doc.reference_name:
|
||||
if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name):
|
||||
return True
|
||||
|
|
@ -277,7 +278,9 @@ def get_permission_query_conditions_for_communication(user):
|
|||
|
||||
if not user: user = frappe.session.user
|
||||
|
||||
if "Super Email User" in frappe.get_roles(user):
|
||||
roles = frappe.get_roles(user)
|
||||
|
||||
if "Super Email User" in roles or "System Manager" in roles:
|
||||
return None
|
||||
else:
|
||||
accounts = frappe.get_all("User Email", filters={ "parent": user },
|
||||
|
|
|
|||
|
|
@ -166,9 +166,6 @@ def _notify(doc, print_html=None, print_format=None, attachments=None,
|
|||
|
||||
def update_parent_status(doc):
|
||||
"""Update status of parent document based on who is replying."""
|
||||
if doc.communication_type != "Communication":
|
||||
return
|
||||
|
||||
parent = doc.get_parent_doc()
|
||||
if not parent:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,264 +1,324 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "hash",
|
||||
"beta": 0,
|
||||
"creation": "2015-02-04 04:33:36.330477",
|
||||
"custom": 0,
|
||||
"description": "Internal record of document shares",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 0,
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "hash",
|
||||
"beta": 0,
|
||||
"creation": "2015-02-04 04:33:36.330477",
|
||||
"custom": 0,
|
||||
"description": "Internal record of document shares",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "share_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "share_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "share_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Document Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "share_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "share_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Document Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "share_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "read",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Read",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "read",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Read",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "write",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Write",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "write",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Write",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "share",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Share",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "share",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Share",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "everyone",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Everyone",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "everyone",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Everyone",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "notify_by_email",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Notify by email",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "notify_by_email",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Notify by email",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 1,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-12-29 14:40:40.284335",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocShare",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 1,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-15 15:58:34.126438",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocShare",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 0,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 0,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 1,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 1,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -595,6 +595,15 @@ def validate_fields(meta):
|
|||
frappe.throw(_("Sort field {0} must be a valid fieldname").format(fieldname),
|
||||
InvalidFieldNameError)
|
||||
|
||||
def check_illegal_depends_on_conditions(docfield):
|
||||
''' assignment operation should not be allowed in the depends on condition.'''
|
||||
depends_on_fields = ["depends_on", "collapsible_depends_on"]
|
||||
for field in depends_on_fields:
|
||||
depends_on = docfield.get(field, None)
|
||||
if depends_on and ("=" in depends_on) and \
|
||||
re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", depends_on):
|
||||
frappe.throw(_("Invalid {0} condition").format(frappe.unscrub(field)), frappe.ValidationError)
|
||||
|
||||
fields = meta.get("fields")
|
||||
fieldname_list = [d.fieldname for d in fields]
|
||||
|
||||
|
|
@ -620,6 +629,7 @@ def validate_fields(meta):
|
|||
check_in_global_search(d)
|
||||
check_illegal_default(d)
|
||||
check_unique_and_text(d)
|
||||
check_illegal_depends_on_conditions(d)
|
||||
|
||||
check_fold(fields)
|
||||
check_search_fields(meta, fields)
|
||||
|
|
@ -753,6 +763,9 @@ def validate_permissions(doctype, for_remove=False):
|
|||
def make_module_and_roles(doc, perm_fieldname="permissions"):
|
||||
"""Make `Module Def` and `Role` records if already not made. Called while installing."""
|
||||
try:
|
||||
if doc.restrict_to_domain and not frappe.db.exists('Domain', doc.restrict_to_domain):
|
||||
frappe.get_doc(dict(doctype='Domain', domain=doc.restrict_to_domain)).insert()
|
||||
|
||||
if not frappe.db.exists("Module Def", doc.module):
|
||||
m = frappe.get_doc({"doctype": "Module Def", "module_name": doc.module})
|
||||
m.app_name = frappe.local.module_app[frappe.scrub(doc.module)]
|
||||
|
|
|
|||
|
|
@ -10,13 +10,22 @@ import unittest
|
|||
|
||||
|
||||
class TestDocType(unittest.TestCase):
|
||||
def new_doctype(self, name, unique=0):
|
||||
def new_doctype(self, name, unique=0, depends_on=''):
|
||||
return frappe.get_doc({
|
||||
"doctype": "DocType",
|
||||
"module": "Core",
|
||||
"custom": 1,
|
||||
"fields": [{"label": "Some Field", "fieldname": "some_fieldname", "fieldtype": "Data", "unique": unique}],
|
||||
"permissions": [{"role": "System Manager", "read": 1}],
|
||||
"fields": [{
|
||||
"label": "Some Field",
|
||||
"fieldname": "some_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"unique": unique,
|
||||
"depends_on": depends_on,
|
||||
}],
|
||||
"permissions": [{
|
||||
"role": "System Manager",
|
||||
"read": 1
|
||||
}],
|
||||
"name": name
|
||||
})
|
||||
|
||||
|
|
@ -71,4 +80,28 @@ class TestDocType(unittest.TestCase):
|
|||
field.fieldtype = "HTML"
|
||||
field.label = "Some HTML Field"
|
||||
doc.search_fields = "some_fieldname,some_html_field"
|
||||
self.assertRaises(frappe.ValidationError, doc.save)
|
||||
self.assertRaises(frappe.ValidationError, doc.save)
|
||||
|
||||
def test_depends_on_fields(self):
|
||||
doc = self.new_doctype("Test Depends On", depends_on="eval:doc.__islocal == 0")
|
||||
doc.insert()
|
||||
|
||||
# check if the assignment operation is allowed in depends_on
|
||||
field = doc.fields[0]
|
||||
field.depends_on = "eval:doc.__islocal = 0"
|
||||
self.assertRaises(frappe.ValidationError, doc.save)
|
||||
|
||||
def test_all_depends_on_fields_conditions(self):
|
||||
import re
|
||||
|
||||
docfields = frappe.get_all("DocField", or_filters={
|
||||
"ifnull(depends_on, '')": ("!=", ''),
|
||||
"ifnull(collapsible_depends_on, '')": ("!=", '')
|
||||
}, fields=["parent", "depends_on", "collapsible_depends_on", "fieldname", "fieldtype"])
|
||||
|
||||
pattern = """[\w\.:_]+\s*={1}\s*[\w\.@'"]+"""
|
||||
for field in docfields:
|
||||
for depends_on in ["depends_on", "collapsible_depends_on"]:
|
||||
condition = field.get(depends_on)
|
||||
if condition:
|
||||
self.assertFalse(re.match(pattern, condition))
|
||||
|
|
@ -42,9 +42,11 @@ frappe.DomainsEditor = frappe.CheckboxEditor.extend({
|
|||
|
||||
get_template: function() {
|
||||
return `
|
||||
<div class="user-role" data-domain="{{item}}">
|
||||
<input type="checkbox" style="margin-top:0px;">
|
||||
{{__(item)}}
|
||||
<div class="checkbox" data-domain="{{item}}">
|
||||
<label>
|
||||
<input type="checkbox">
|
||||
<span class="label-area small">{{ __(item) }}</span>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ def export_languages_json():
|
|||
languages = frappe.db.get_all('Language', fields=['name', 'language_name'])
|
||||
languages = [{'name': d.language_name, 'code': d.name} for d in languages]
|
||||
|
||||
languages.sort(lambda a,b: 1 if a['code'] > b['code'] else -1)
|
||||
languages.sort(key = lambda a: a['code'])
|
||||
|
||||
with open(frappe.get_app_path('frappe', 'geo', 'languages.json'), 'w') as f:
|
||||
f.write(frappe.as_json(languages))
|
||||
|
|
|
|||
|
|
@ -162,18 +162,13 @@ class TestUser(unittest.TestCase):
|
|||
# from frappe.frappeclient import FrappeClient
|
||||
# update_site_config('deny_multiple_sessions', 0)
|
||||
#
|
||||
# print 'conn1'
|
||||
# conn1 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False)
|
||||
# test_request(conn1)
|
||||
#
|
||||
# print 'conn2'
|
||||
# conn2 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False)
|
||||
# test_request(conn2)
|
||||
#
|
||||
# update_site_config('deny_multiple_sessions', 1)
|
||||
#
|
||||
# print 'conn3'
|
||||
#
|
||||
# conn3 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False)
|
||||
# test_request(conn3)
|
||||
#
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False,
|
|||
|
||||
def export_csv(doctype, path):
|
||||
from frappe.core.page.data_import_tool.exporter import get_template
|
||||
with open(path, "w") as csvfile:
|
||||
with open(path, "wb") as csvfile:
|
||||
get_template(doctype=doctype, all_doctypes="Yes", with_data="Yes")
|
||||
csvfile.write(frappe.response.result.encode("utf-8"))
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
|
|||
if field and ((select_columns and f[0] in select_columns[dt]) or not select_columns):
|
||||
tablecolumns.append(field)
|
||||
|
||||
tablecolumns.sort(lambda a, b: int(a.idx - b.idx))
|
||||
tablecolumns.sort(key = lambda a: int(a.idx))
|
||||
|
||||
_column_start_end = frappe._dict(start=0)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_simple(self):
|
||||
fixture = "Custom Script"
|
||||
path = frappe.scrub(fixture) + "_original_style.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -24,7 +24,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_simple_name_equal_default(self):
|
||||
fixture = ["Custom Script", {"name":["Item-Client"]}]
|
||||
path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -32,7 +32,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_simple_name_equal(self):
|
||||
fixture = ["Custom Script", {"name":["Item-Client"],"op":"="}]
|
||||
path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -40,7 +40,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_simple_name_not_equal(self):
|
||||
fixture = ["Custom Script", {"name":["Item-Client"],"op":"!="}]
|
||||
path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -49,7 +49,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_simple_name_at_least_equal(self):
|
||||
fixture = ["Custom Script", {"name":"Item-Cli"}]
|
||||
path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -57,7 +57,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_multi_name_equal(self):
|
||||
fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"="}]
|
||||
path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -65,7 +65,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_multi_name_not_equal(self):
|
||||
fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"!="}]
|
||||
path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -73,7 +73,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_empty_object(self):
|
||||
fixture = ["Custom Script", {}]
|
||||
path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -81,7 +81,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_just_list(self):
|
||||
fixture = ["Custom Script"]
|
||||
path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -90,7 +90,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_rex_no_flags(self):
|
||||
fixture = ["Custom Script", {"name":r"^[i|A]"}]
|
||||
path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -98,7 +98,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Script_fixture_rex_with_flags(self):
|
||||
fixture = ["Custom Script", {"name":r"^[i|A]", "flags":"L,M"}]
|
||||
path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -107,7 +107,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_simple(self):
|
||||
fixture = "Custom Field"
|
||||
path = frappe.scrub(fixture) + "_original_style.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -115,7 +115,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_simple_name_equal_default(self):
|
||||
fixture = ["Custom Field", {"name":["Item-vat"]}]
|
||||
path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -123,7 +123,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_simple_name_equal(self):
|
||||
fixture = ["Custom Field", {"name":["Item-vat"],"op":"="}]
|
||||
path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -131,7 +131,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_simple_name_not_equal(self):
|
||||
fixture = ["Custom Field", {"name":["Item-vat"],"op":"!="}]
|
||||
path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -140,7 +140,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_simple_name_at_least_equal(self):
|
||||
fixture = ["Custom Field", {"name":"Item-va"}]
|
||||
path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -148,7 +148,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_multi_name_equal(self):
|
||||
fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"="}]
|
||||
path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -156,7 +156,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_multi_name_not_equal(self):
|
||||
fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"!="}]
|
||||
path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -164,7 +164,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_empty_object(self):
|
||||
fixture = ["Custom Field", {}]
|
||||
path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -172,7 +172,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_just_list(self):
|
||||
fixture = ["Custom Field"]
|
||||
path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -181,7 +181,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_rex_no_flags(self):
|
||||
fixture = ["Custom Field", {"name":r"^[r|L]"}]
|
||||
path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -189,7 +189,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Custom_Field_fixture_rex_with_flags(self):
|
||||
fixture = ["Custom Field", {"name":r"^[i|A]", "flags":"L,M"}]
|
||||
path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -199,7 +199,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_fixture_simple(self):
|
||||
fixture = "ToDo"
|
||||
path = "Doctype_" + frappe.scrub(fixture) + "_original_style_should_be_all.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -207,7 +207,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_fixture_simple_name_equal_default(self):
|
||||
fixture = ["ToDo", {"name":["TDI00000008"]}]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -215,7 +215,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_fixture_simple_name_equal(self):
|
||||
fixture = ["ToDo", {"name":["TDI00000002"],"op":"="}]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -223,7 +223,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_simple_name_not_equal(self):
|
||||
fixture = ["ToDo", {"name":["TDI00000002"],"op":"!="}]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -232,7 +232,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_fixture_simple_name_at_least_equal(self):
|
||||
fixture = ["ToDo", {"name":"TDI"}]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -240,7 +240,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_multi_name_equal(self):
|
||||
fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"="}]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -248,7 +248,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_multi_name_not_equal(self):
|
||||
fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"!="}]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -256,7 +256,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_fixture_empty_object(self):
|
||||
fixture = ["ToDo", {}]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -264,7 +264,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_fixture_just_list(self):
|
||||
fixture = ["ToDo"]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -273,7 +273,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_fixture_rex_no_flags(self):
|
||||
fixture = ["ToDo", {"name":r"^TDi"}]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_no_flags_should_be_all.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
@ -281,7 +281,7 @@ class TestDataImportFixtures(unittest.TestCase):
|
|||
def test_Doctype_fixture_rex_with_flags(self):
|
||||
fixture = ["ToDo", {"name":r"^TDi", "flags":"L,M"}]
|
||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_with_flags_should_be_none.csv"
|
||||
# print "teste done {}".format(path)
|
||||
|
||||
export_csv(fixture, path)
|
||||
self.assertTrue(True)
|
||||
os.remove(path)
|
||||
|
|
|
|||
|
|
@ -112,13 +112,17 @@ $.extend(frappe.desktop, {
|
|||
},
|
||||
|
||||
setup_module_click: function() {
|
||||
frappe.desktop.wiggling = false;
|
||||
|
||||
if(frappe.list_desktop) {
|
||||
frappe.desktop.wrapper.on("click", ".desktop-list-item", function() {
|
||||
frappe.desktop.open_module($(this));
|
||||
});
|
||||
} else {
|
||||
frappe.desktop.wrapper.on("click", ".app-icon", function() {
|
||||
frappe.desktop.open_module($(this).parent());
|
||||
if ( !frappe.desktop.wiggling ) {
|
||||
frappe.desktop.open_module($(this).parent());
|
||||
}
|
||||
});
|
||||
}
|
||||
frappe.desktop.wrapper.on("click", ".circle", function() {
|
||||
|
|
@ -127,6 +131,116 @@ $.extend(frappe.desktop, {
|
|||
frappe.ui.notifications.show_open_count_list(doctype);
|
||||
}
|
||||
});
|
||||
|
||||
frappe.desktop.setup_wiggle();
|
||||
},
|
||||
|
||||
setup_wiggle: () => {
|
||||
// Wiggle, Wiggle, Wiggle.
|
||||
const DURATION_LONG_PRESS = 1000;
|
||||
// lesser the antidode, more the wiggle (like your drunk uncle)
|
||||
// 75 seems good to replicate the iOS feels.
|
||||
const WIGGLE_ANTIDODE = 75;
|
||||
|
||||
var timer_id = 0;
|
||||
const $cases = frappe.desktop.wrapper.find('.case-wrapper');
|
||||
const $icons = frappe.desktop.wrapper.find('.app-icon');
|
||||
const $notis = $(frappe.desktop.wrapper.find('.circle').toArray().filter((object) => {
|
||||
// This hack is so bad, I should punch myself.
|
||||
// Seriously, punch yourself.
|
||||
const text = $(object).find('.circle-text').html();
|
||||
|
||||
return text;
|
||||
}));
|
||||
|
||||
const clearWiggle = () => {
|
||||
const $closes = $cases.find('.module-remove');
|
||||
$closes.hide();
|
||||
$notis.show();
|
||||
|
||||
$icons.trigger('stopRumble');
|
||||
|
||||
frappe.desktop.wiggling = false;
|
||||
};
|
||||
|
||||
// initiate wiggling.
|
||||
$icons.jrumble({
|
||||
speed: WIGGLE_ANTIDODE // seems neat enough to match the iOS way
|
||||
});
|
||||
|
||||
frappe.desktop.wrapper.on('mousedown', '.app-icon', () => {
|
||||
timer_id = setTimeout(() => {
|
||||
frappe.desktop.wiggling = true;
|
||||
// hide all notifications.
|
||||
$notis.hide();
|
||||
|
||||
$cases.each((i) => {
|
||||
const $case = $($cases[i]);
|
||||
const template =
|
||||
`
|
||||
<div class="circle module-remove" style="background-color:#E0E0E0; color:#212121">
|
||||
<div class="circle-text">
|
||||
<b>
|
||||
×
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$case.append(template);
|
||||
const $close = $case.find('.module-remove');
|
||||
const name = $case.attr('title');
|
||||
$close.click(() => {
|
||||
// good enough to create dynamic dialogs?
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __(`Hide ${name}?`)
|
||||
});
|
||||
dialog.set_primary_action(__('Hide'), () => {
|
||||
frappe.call({
|
||||
method: 'frappe.desk.doctype.desktop_icon.desktop_icon.hide',
|
||||
args: { name: name },
|
||||
freeze: true,
|
||||
callback: (response) =>
|
||||
{
|
||||
if ( response.message ) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
dialog.hide();
|
||||
|
||||
clearWiggle();
|
||||
});
|
||||
// Hacks, Hacks and Hacks.
|
||||
var $cancel = dialog.get_close_btn();
|
||||
$cancel.click(() => {
|
||||
clearWiggle();
|
||||
});
|
||||
$cancel.html(__(`Cancel`));
|
||||
|
||||
dialog.show();
|
||||
});
|
||||
});
|
||||
|
||||
$icons.trigger('startRumble');
|
||||
}, DURATION_LONG_PRESS);
|
||||
});
|
||||
frappe.desktop.wrapper.on('mouseup mouseleave', '.app-icon', () => {
|
||||
clearTimeout(timer_id);
|
||||
});
|
||||
|
||||
// also stop wiggling if clicked elsewhere.
|
||||
$('body').click((event) => {
|
||||
if ( frappe.desktop.wiggling ) {
|
||||
const $target = $(event.target);
|
||||
// our target shouldn't be .app-icons or .close
|
||||
const $parent = $target.parents('.case-wrapper');
|
||||
if ( $parent.length == 0 )
|
||||
clearWiggle();
|
||||
}
|
||||
});
|
||||
// end wiggle
|
||||
},
|
||||
|
||||
open_module: function(parent) {
|
||||
|
|
@ -212,8 +326,8 @@ $.extend(frappe.desktop, {
|
|||
notifier.toggle(sum ? true : false);
|
||||
var circle = notifier.find(".circle-text");
|
||||
var text = sum || '';
|
||||
if(text > 20) {
|
||||
text = '20+';
|
||||
if(text > 99) {
|
||||
text = '99+';
|
||||
}
|
||||
|
||||
if(circle.length) {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ class CustomField(Document):
|
|||
if not self.label:
|
||||
frappe.throw(_("Label is mandatory"))
|
||||
# remove special characters from fieldname
|
||||
self.fieldname = filter(lambda x: x.isdigit() or x.isalpha() or '_',
|
||||
cstr(self.label).lower().replace(' ','_'))
|
||||
self.fieldname = "".join(filter(lambda x: x.isdigit() or x.isalpha() or '_',
|
||||
cstr(self.label).lower().replace(' ','_')))
|
||||
|
||||
# fieldnames should be lowercase
|
||||
self.fieldname = self.fieldname.lower()
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ CREATE TABLE `tabDocType` (
|
|||
`editable_grid` int(1) NOT NULL DEFAULT 1,
|
||||
`track_changes` int(1) NOT NULL DEFAULT 0,
|
||||
`module` varchar(255) DEFAULT NULL,
|
||||
`restrict_to_domain` varchar(255) DEFAULT NULL,
|
||||
`app` varchar(255) DEFAULT NULL,
|
||||
`autoname` varchar(255) DEFAULT NULL,
|
||||
`name_case` varchar(255) DEFAULT NULL,
|
||||
|
|
|
|||
|
|
@ -92,7 +92,19 @@ def set_default(key, value, parent, parenttype="__default"):
|
|||
:param value: Default value.
|
||||
:param parent: Usually, **User** to whom the default belongs.
|
||||
:param parenttype: [optional] default is `__default`."""
|
||||
frappe.db.sql("""delete from `tabDefaultValue` where defkey=%s and parent=%s""", (key, parent))
|
||||
if frappe.db.sql('''
|
||||
select
|
||||
defkey
|
||||
from
|
||||
tabDefaultValue
|
||||
where
|
||||
defkey=%s and parent=%s
|
||||
for update''', (key, parent)):
|
||||
frappe.db.sql("""
|
||||
delete from
|
||||
`tabDefaultValue`
|
||||
where
|
||||
defkey=%s and parent=%s""", (key, parent))
|
||||
if value != None:
|
||||
add_default(key, value, parent)
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ def get_desktop_icons(user=None):
|
|||
icon.hidden = 1
|
||||
|
||||
# sort by idx
|
||||
user_icons.sort(lambda a, b: 1 if a.idx > b.idx else -1)
|
||||
user_icons.sort(key = lambda a: a.idx)
|
||||
|
||||
# translate
|
||||
for d in user_icons:
|
||||
|
|
@ -404,3 +404,16 @@ palette = (
|
|||
('#4F8EA8', 1),
|
||||
('#428B46', 1)
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def hide(name, user = None):
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
|
||||
try:
|
||||
set_hidden(name, user, hidden = 1)
|
||||
clear_desktop_icons_cache()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
23
frappe/desk/doctype/todo/test_todo.js
Normal file
23
frappe/desk/doctype/todo/test_todo.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: ToDo", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new ToDo
|
||||
() => frappe.tests.make('ToDo', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -112,20 +112,18 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Color",
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Color",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
|
|
@ -142,18 +140,20 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Color",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Color",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
|
|
@ -544,7 +544,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-05 12:54:58.044162",
|
||||
"modified": "2017-09-30 13:57:29.398598",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "ToDo",
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ def _get_linked_doctypes(doctype):
|
|||
if not dt in ret:
|
||||
ret[dt] = {"get_parent": True}
|
||||
|
||||
for dt in ret.keys():
|
||||
for dt in list(ret.keys()):
|
||||
try:
|
||||
doctype_module = load_doctype_module(dt)
|
||||
except ImportError:
|
||||
|
|
|
|||
|
|
@ -26,11 +26,7 @@ frappe.setup = {
|
|||
}
|
||||
|
||||
frappe.pages['setup-wizard'].on_page_load = function(wrapper) {
|
||||
// setup page ui
|
||||
$(".navbar:first").toggle(false);
|
||||
|
||||
var requires = ["/assets/frappe/css/animate.min.css"].concat(
|
||||
frappe.boot.setup_wizard_requires || []);
|
||||
var requires = (frappe.boot.setup_wizard_requires || []);
|
||||
|
||||
frappe.require(requires, function() {
|
||||
frappe.call({
|
||||
|
|
@ -96,17 +92,23 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
|
|||
}
|
||||
|
||||
setup_keyboard_nav() {
|
||||
this.container.on('keydown', (e) => {
|
||||
if(e.which === 13) {
|
||||
var $target = $(e.target);
|
||||
if($target.hasClass('prev-btn')) {
|
||||
$target.trigger('click');
|
||||
} else {
|
||||
this.container.find('.next-btn').trigger('click');
|
||||
e.preventDefault();
|
||||
}
|
||||
$('body').on('keydown', this.handle_enter_press.bind(this));
|
||||
}
|
||||
|
||||
disable_keyboard_nav() {
|
||||
$('body').off('keydown', this.handle_enter_press.bind(this));
|
||||
}
|
||||
|
||||
handle_enter_press(e) {
|
||||
if (e.which === frappe.ui.keyCode.ENTER) {
|
||||
var $target = $(e.target);
|
||||
if($target.hasClass('prev-btn')) {
|
||||
$target.trigger('click');
|
||||
} else {
|
||||
this.container.find('.next-btn').trigger('click');
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
before_show_slide() {
|
||||
|
|
@ -118,6 +120,11 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
|
|||
}
|
||||
|
||||
show_slide(id) {
|
||||
if (id === this.slides.length) {
|
||||
// show_slide called on last slide
|
||||
this.action_on_complete();
|
||||
return;
|
||||
}
|
||||
super.show_slide(id);
|
||||
frappe.set_route(this.page_name, id + "");
|
||||
}
|
||||
|
|
@ -172,6 +179,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
|
|||
if (!this.current_slide.set_values()) return;
|
||||
this.update_values();
|
||||
this.show_working_state();
|
||||
this.disable_keyboard_nav();
|
||||
return frappe.call({
|
||||
method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete",
|
||||
args: {args: this.values},
|
||||
|
|
@ -181,8 +189,8 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
|
|||
localStorage.setItem("session_last_route", frappe.setup.welcome_page);
|
||||
}
|
||||
setTimeout(function() {
|
||||
// frappe.ui.toolbar.clear_cache();
|
||||
window.location = "/desk";
|
||||
// Reload
|
||||
window.location.href = '';
|
||||
}, 2000);
|
||||
setTimeout(()=> {
|
||||
$('body').removeClass('setup-state');
|
||||
|
|
@ -241,6 +249,13 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
|
|||
}
|
||||
|
||||
get_message(title, message="", loading=false) {
|
||||
const loading_html = loading
|
||||
? '<div style="width:100%;height:100%" class="lds-rolling state-icon"><div></div></div>'
|
||||
: `<div style="width:100%;height:100%" class="state-icon">
|
||||
<i class="fa fa-check-circle text-extra-muted"
|
||||
style="font-size: 64px; margin-top: -8px;"></i>
|
||||
</div>`;
|
||||
|
||||
return $(`<div class="page-card-container" data-state="setup">
|
||||
<div class="page-card">
|
||||
<div class="page-card-head">
|
||||
|
|
@ -251,12 +266,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
|
|||
</div>
|
||||
<p>${message}</p>
|
||||
<div class="state-icon-container">
|
||||
${loading
|
||||
? '<div style="width:100%;height:100%" class="lds-rolling state-icon"><div></div></div>'
|
||||
: `<div style="width:100%;height:100%" class="state-icon"><i class="fa fa-check-circle text-success"
|
||||
style="font-size: 64px; margin-top: -8px;">
|
||||
</i></div>`
|
||||
}
|
||||
${loading_html}
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ def get_stats(stats, doctype, filters=[]):
|
|||
stats[tag] = scrub_user_tags(tagcount)
|
||||
stats[tag].append([_("No Tags"), frappe.get_list(doctype,
|
||||
fields=[tag, "count(*)"],
|
||||
filters=filters +["({0} = ',' or {0} is null)".format(tag)], as_list=True)[0][1]])
|
||||
filters=filters +["({0} = ',' or {0} = '' or {0} is null)".format(tag)], as_list=True)[0][1]])
|
||||
else:
|
||||
stats[tag] = tagcount
|
||||
|
||||
|
|
@ -269,7 +269,6 @@ def get_stats(stats, doctype, filters=[]):
|
|||
except MySQLdb.OperationalError:
|
||||
# raised when _user_tags column is added on the fly
|
||||
pass
|
||||
|
||||
return stats
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
BIN
frappe/docs/assets/img/webhook.png
Normal file
BIN
frappe/docs/assets/img/webhook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
103
frappe/docs/user/en/guides/integration/webhooks.md
Normal file
103
frappe/docs/user/en/guides/integration/webhooks.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# Webhooks
|
||||
|
||||
Webhooks are "user-defined HTTP callbacks". You can create webhook which triggers on Doc Event of the selected DocType. When the `doc_events` occurs, the source site makes an HTTP request to the URI configured for the webhook. Users can configure them to cause events on one site to invoke behaviour on another.
|
||||
|
||||
#### Configure Webhook
|
||||
|
||||
To add Webhook go to
|
||||
|
||||
> Integrations > External Documents > Webhook
|
||||
|
||||
Webhook
|
||||
|
||||
<img class="screenshot" src="/docs/assets/img/webhook.png">
|
||||
|
||||
1. Select the DocType for which hook needs to be triggered e.g. Note
|
||||
2. Select the DocEvent for which hook needs to be triggered e.g. on_trash
|
||||
3. Enter a valid request URL. On occurence of DocEvent, POST request with doc's json as data is made to the URL.
|
||||
4. Optionally you can add headers to the request to be made. Useful for sending api key if required.
|
||||
5. Optionally you can select fields and set its `key` to be sent as data json
|
||||
|
||||
e.g. Webhook
|
||||
|
||||
- **DocType** : `Quotation`
|
||||
- **Doc Event** : `on_update`
|
||||
- **Request URL** : `https://httpbin.org/post`
|
||||
- **Webhook Data** :
|
||||
1. **Fieldname** : `name` and **Key** : `id`
|
||||
2. **Fieldname** : `items` and **Key** : `lineItems`
|
||||
|
||||
Note: if no headers or data is present, request will be made without any header or body
|
||||
|
||||
Example response of request sent by frappe server on `Quotation` - `on_update` to https://httpbin.org/post:
|
||||
|
||||
```
|
||||
{
|
||||
"args": {},
|
||||
"data": "{\"lineItems\": [{\"stock_qty\": 1.0, \"base_price_list_rate\": 1.0, \"image\": \"\", \"creation\": \"2017-09-14 13:41:58.373023\", \"base_amount\": 1.0, \"qty\": 1.0, \"margin_rate_or_amount\": 0.0, \"rate\": 1.0, \"owner\": \"Administrator\", \"stock_uom\": \"Unit\", \"base_net_amount\": 1.0, \"page_break\": 0, \"modified_by\": \"Administrator\", \"base_net_rate\": 1.0, \"discount_percentage\": 0.0, \"item_name\": \"I1\", \"amount\": 1.0, \"actual_qty\": 0.0, \"net_rate\": 1.0, \"conversion_factor\": 1.0, \"warehouse\": \"Finished Goods - R\", \"docstatus\": 0, \"prevdoc_docname\": null, \"uom\": \"Unit\", \"description\": \"I1\", \"parent\": \"QTN-00001\", \"brand\": null, \"gst_hsn_code\": null, \"base_rate\": 1.0, \"item_code\": \"I1\", \"projected_qty\": 0.0, \"margin_type\": \"\", \"doctype\": \"Quotation Item\", \"rate_with_margin\": 0.0, \"pricing_rule\": null, \"price_list_rate\": 1.0, \"name\": \"QUOD/00001\", \"idx\": 1, \"item_tax_rate\": \"{}\", \"item_group\": \"Products\", \"modified\": \"2017-09-14 17:09:51.239271\", \"parenttype\": \"Quotation\", \"customer_item_code\": null, \"net_amount\": 1.0, \"prevdoc_doctype\": null, \"parentfield\": \"items\"}], \"id\": \"QTN-00001\"}",
|
||||
"files": {},
|
||||
"form": {},
|
||||
"headers": {
|
||||
"Accept": "*/*",
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
"Connection": "close",
|
||||
"Content-Length": "1075",
|
||||
"Host": "httpbin.org",
|
||||
"User-Agent": "python-requests/2.18.1"
|
||||
},
|
||||
"json": {
|
||||
"id": "QTN-00001",
|
||||
"lineItems": [
|
||||
{
|
||||
"actual_qty": 0.0,
|
||||
"amount": 1.0,
|
||||
"base_amount": 1.0,
|
||||
"base_net_amount": 1.0,
|
||||
"base_net_rate": 1.0,
|
||||
"base_price_list_rate": 1.0,
|
||||
"base_rate": 1.0,
|
||||
"brand": null,
|
||||
"conversion_factor": 1.0,
|
||||
"creation": "2017-09-14 13:41:58.373023",
|
||||
"customer_item_code": null,
|
||||
"description": "I1",
|
||||
"discount_percentage": 0.0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Quotation Item",
|
||||
"gst_hsn_code": null,
|
||||
"idx": 1,
|
||||
"image": "",
|
||||
"item_code": "I1",
|
||||
"item_group": "Products",
|
||||
"item_name": "I1",
|
||||
"item_tax_rate": "{}",
|
||||
"margin_rate_or_amount": 0.0,
|
||||
"margin_type": "",
|
||||
"modified": "2017-09-14 17:09:51.239271",
|
||||
"modified_by": "Administrator",
|
||||
"name": "QUOD/00001",
|
||||
"net_amount": 1.0,
|
||||
"net_rate": 1.0,
|
||||
"owner": "Administrator",
|
||||
"page_break": 0,
|
||||
"parent": "QTN-00001",
|
||||
"parentfield": "items",
|
||||
"parenttype": "Quotation",
|
||||
"prevdoc_docname": null,
|
||||
"prevdoc_doctype": null,
|
||||
"price_list_rate": 1.0,
|
||||
"pricing_rule": null,
|
||||
"projected_qty": 0.0,
|
||||
"qty": 1.0,
|
||||
"rate": 1.0,
|
||||
"rate_with_margin": 0.0,
|
||||
"stock_qty": 1.0,
|
||||
"stock_uom": "Unit",
|
||||
"uom": "Unit",
|
||||
"warehouse": "Finished Goods - R"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": "https://httpbin.org/post"
|
||||
}
|
||||
```
|
||||
|
|
@ -273,7 +273,6 @@ class EmailAccount(Document):
|
|||
"uid_reindexed": uid_reindexed
|
||||
}
|
||||
communication = self.insert_communication(msg, args=args)
|
||||
#self.notify_update()
|
||||
|
||||
except SentEmailInInbox:
|
||||
frappe.db.rollback()
|
||||
|
|
|
|||
|
|
@ -6,13 +6,24 @@ frappe.email_alert = {
|
|||
}
|
||||
|
||||
frappe.model.with_doctype(frm.doc.document_type, function() {
|
||||
var get_select_options = function(df) {
|
||||
let get_select_options = function(df) {
|
||||
return {value: df.fieldname, label: df.fieldname + " (" + __(df.label) + ")"};
|
||||
}
|
||||
|
||||
var fields = frappe.get_doc("DocType", frm.doc.document_type).fields;
|
||||
let get_date_change_options = function() {
|
||||
let date_options = $.map(fields, function(d) {
|
||||
return (d.fieldtype=="Date" || d.fieldtype=="Datetime")?
|
||||
get_select_options(d) : null;
|
||||
});
|
||||
// append creation and modified date to Date Change field
|
||||
return date_options.concat([
|
||||
{ value: "creation", label: `creation (${__('Created On')})` },
|
||||
{ value: "modified", label: `modified (${__('Last Modified Date')})` }
|
||||
]);
|
||||
}
|
||||
|
||||
var options = $.map(fields,
|
||||
let fields = frappe.get_doc("DocType", frm.doc.document_type).fields;
|
||||
let options = $.map(fields,
|
||||
function(d) { return in_list(frappe.model.no_value_type, d.fieldtype) ?
|
||||
null : get_select_options(d); });
|
||||
|
||||
|
|
@ -21,11 +32,9 @@ frappe.email_alert = {
|
|||
frm.set_df_property("set_property_after_alert", "options", [""].concat(options));
|
||||
|
||||
// set date changed options
|
||||
frm.set_df_property("date_changed", "options", $.map(fields,
|
||||
function(d) { return (d.fieldtype=="Date" || d.fieldtype=="Datetime") ?
|
||||
get_select_options(d) : null; }));
|
||||
frm.set_df_property("date_changed", "options", get_date_change_options());
|
||||
|
||||
var email_fields = $.map(fields,
|
||||
let email_fields = $.map(fields,
|
||||
function(d) { return (d.options == "Email" ||
|
||||
(d.options=='User' && d.fieldtype=='Link')) ?
|
||||
get_select_options(d) : null; });
|
||||
|
|
@ -48,7 +57,14 @@ frappe.ui.form.on("Email Alert", {
|
|||
"istable": 0
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
frm.set_query("print_format", function() {
|
||||
return {
|
||||
"filters": {
|
||||
"doc_type": frm.doc.document_type
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function(frm) {
|
||||
frappe.email_alert.setup_fieldname_select(frm);
|
||||
|
|
|
|||
|
|
@ -705,36 +705,6 @@
|
|||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "attach_print",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Attach Print",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
|
|
@ -794,6 +764,99 @@
|
|||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "attach_print",
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_25",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Print Settings",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "attach_print",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Attach Print",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "attach_print",
|
||||
"fieldname": "print_format",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Print Format",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Print Format",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
|
|
@ -808,7 +871,7 @@
|
|||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-08-13 22:43:49.079330",
|
||||
"modified": "2017-09-26 20:10:00.061780",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Alert",
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ def get_context(context):
|
|||
please enable Allow Print For {0} in Print Settings""".format(status)),
|
||||
title=_("Error in Email Alert"))
|
||||
else:
|
||||
return [frappe.attach_print(doc.doctype, doc.name)]
|
||||
return [frappe.attach_print(doc.doctype, doc.name, None, self.print_format)]
|
||||
|
||||
context = get_context(doc)
|
||||
recipients = []
|
||||
|
|
|
|||
|
|
@ -503,7 +503,7 @@
|
|||
"columns": 0,
|
||||
"fieldname": "attachments",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 1,
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
|
|
@ -517,7 +517,7 @@
|
|||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -537,7 +537,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-07 16:29:15.780393",
|
||||
"modified": "2017-09-25 15:39:21.781324",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Queue",
|
||||
|
|
|
|||
|
|
@ -478,7 +478,7 @@ def prepare_message(email, recipient, recipients_list):
|
|||
if email.add_unsubscribe_link and email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url
|
||||
unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient,
|
||||
email.unsubscribe_method, email.unsubscribe_params)
|
||||
message = message.replace("<!--unsubscribe url-->", quopri.encodestring(unsubscribe_url))
|
||||
message = message.replace("<!--unsubscribe url-->", quopri.encodestring(unsubscribe_url.encode()).decode())
|
||||
|
||||
if email.expose_recipients == "header":
|
||||
pass
|
||||
|
|
@ -494,7 +494,7 @@ def prepare_message(email, recipient, recipients_list):
|
|||
email_sent_message = _("This email was sent to {0} and copied to {1}").format(email_sent_to,email_sent_cc)
|
||||
else:
|
||||
email_sent_message = _("This email was sent to {0}").format(email_sent_to)
|
||||
message = message.replace("<!--cc message-->", quopri.encodestring(email_sent_message))
|
||||
message = message.replace("<!--cc message-->", quopri.encodestring(email_sent_message.encode()).decode())
|
||||
|
||||
message = message.replace("<!--recipient-->", recipient)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ app_color = "orange"
|
|||
source_link = "https://github.com/frappe/frappe"
|
||||
app_license = "MIT"
|
||||
|
||||
develop_version = '8.x.x-beta'
|
||||
develop_version = '9.x.x-develop'
|
||||
|
||||
app_email = "info@frappe.io"
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ def upload_from_folder(path, dropbox_folder, dropbox_client, did_not_upload, err
|
|||
def upload_file_to_dropbox(filename, folder, dropbox_client):
|
||||
create_folder_if_not_exists(folder, dropbox_client)
|
||||
chunk_size = 4 * 1024 * 1024
|
||||
file_size = os.path.getsize(filename)
|
||||
file_size = os.path.getsize(encode(filename))
|
||||
mode = (dropbox.files.WriteMode.overwrite)
|
||||
|
||||
f = open(encode(filename), 'rb')
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import frappe
|
|||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from six.moves.urllib.parse import urlencode
|
||||
from frappe.utils import get_url, call_hook_method, cint
|
||||
from frappe.utils import get_url, call_hook_method, cint, flt
|
||||
from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway
|
||||
|
||||
class StripeSettings(Document):
|
||||
|
|
@ -62,7 +62,7 @@ class StripeSettings(Document):
|
|||
"Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))}
|
||||
|
||||
data = {
|
||||
"amount": cint(self.data.amount)*100,
|
||||
"amount": cint(flt(self.data.amount)*100),
|
||||
"currency": self.data.currency,
|
||||
"source": self.data.stripe_token_id,
|
||||
"description": self.data.description
|
||||
|
|
|
|||
57
frappe/integrations/doctype/webhook/__init__.py
Normal file
57
frappe/integrations/doctype/webhook/__init__.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
def run_webhooks(doc, method):
|
||||
'''Run webhooks for this method'''
|
||||
if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install:
|
||||
return
|
||||
|
||||
if frappe.flags.webhooks_executed is None:
|
||||
frappe.flags.webhooks_executed = {}
|
||||
|
||||
if frappe.flags.webhooks == None:
|
||||
# load webhooks from cache
|
||||
webhooks = frappe.cache().get_value('webhooks')
|
||||
if webhooks==None:
|
||||
# query webhooks
|
||||
webhooks_list = frappe.get_all('Webhook',
|
||||
fields=["name", "webhook_docevent", "webhook_doctype"])
|
||||
|
||||
# make webhooks map for cache
|
||||
webhooks = {}
|
||||
for w in webhooks_list:
|
||||
webhooks.setdefault(w.webhook_doctype, []).append(w)
|
||||
frappe.cache().set_value('webhooks', webhooks)
|
||||
|
||||
frappe.flags.webhooks = webhooks
|
||||
|
||||
# get webhooks for this doctype
|
||||
webhooks_for_doc = frappe.flags.webhooks.get(doc.doctype, None)
|
||||
|
||||
if not webhooks_for_doc:
|
||||
# no webhooks, quit
|
||||
return
|
||||
|
||||
def _webhook_request(webhook):
|
||||
if not webhook.name in frappe.flags.webhooks_executed.get(doc.name, []):
|
||||
frappe.enqueue("frappe.integrations.doctype.webhook.webhook.enqueue_webhook", doc=doc, webhook=webhook)
|
||||
|
||||
# keep list of webhooks executed for this doc in this request
|
||||
# so that we don't run the same webhook for the same document multiple times
|
||||
# in one request
|
||||
frappe.flags.webhooks_executed.setdefault(doc.name, []).append(webhook.name)
|
||||
|
||||
event_list = ["on_update", "after_insert", "on_submit", "on_cancel", "on_trash"]
|
||||
|
||||
if not doc.flags.in_insert:
|
||||
# value change is not applicable in insert
|
||||
event_list.append('on_change')
|
||||
event_list.append('before_update_after_submit')
|
||||
|
||||
for webhook in webhooks_for_doc:
|
||||
event = method if method in event_list else None
|
||||
if event and webhook.webhook_docevent == event:
|
||||
_webhook_request(webhook)
|
||||
23
frappe/integrations/doctype/webhook/test_webhook.js
Normal file
23
frappe/integrations/doctype/webhook/test_webhook.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Webhook", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Webhook
|
||||
() => frappe.tests.make('Webhook', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
21
frappe/integrations/doctype/webhook/test_webhook.py
Normal file
21
frappe/integrations/doctype/webhook/test_webhook.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestWebhook(unittest.TestCase):
|
||||
def test_validate_docevents(self):
|
||||
doc = frappe.new_doc("Webhook")
|
||||
doc.webhook_doctype = "User"
|
||||
doc.webhook_docevent = "on_submit"
|
||||
doc.request_url = "https://httpbin.org/post"
|
||||
self.assertRaises(frappe.ValidationError, doc.save)
|
||||
def test_validate_request_url(self):
|
||||
doc = frappe.new_doc("Webhook")
|
||||
doc.webhook_doctype = "User"
|
||||
doc.webhook_docevent = "after_insert"
|
||||
doc.request_url = "httpbin.org?post"
|
||||
self.assertRaises(frappe.ValidationError, doc.save)
|
||||
46
frappe/integrations/doctype/webhook/webhook.js
Normal file
46
frappe/integrations/doctype/webhook/webhook.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2017, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.webhook = {
|
||||
set_fieldname_select: function(frm) {
|
||||
var doc = frm.doc;
|
||||
if (doc.webhook_doctype) {
|
||||
frappe.model.with_doctype(doc.webhook_doctype, function() {
|
||||
var fields = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) {
|
||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
||||
d.fieldtype === 'Table') {
|
||||
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
||||
}
|
||||
else if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') {
|
||||
return { label: d.label, value: d.fieldname };
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
fields.unshift({"label":"Name (Doc Name)","value":"name"});
|
||||
frappe.meta.get_docfield("Webhook Data", "fieldname", frm.doc.name).options = [""].concat(fields);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
frappe.ui.form.on('Webhook', {
|
||||
refresh: function(frm) {
|
||||
frappe.webhook.set_fieldname_select(frm);
|
||||
},
|
||||
webhook_doctype: function(frm) {
|
||||
frappe.webhook.set_fieldname_select(frm);
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Webhook Data", {
|
||||
fieldname: function(frm, doctype, name) {
|
||||
var doc = frappe.get_doc(doctype, name);
|
||||
var df = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) {
|
||||
return doc.fieldname == d.fieldname ? d : null;
|
||||
})[0];
|
||||
doc.key = df != undefined ? df.fieldname : "name";
|
||||
frm.refresh_field("webhook_data");
|
||||
}
|
||||
});
|
||||
367
frappe/integrations/doctype/webhook/webhook.json
Normal file
367
frappe/integrations/doctype/webhook/webhook.json
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-09-08 16:16:13.060641",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "sb_doc_events",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Doc Events",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "",
|
||||
"fieldname": "webhook_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "DocType",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "cb_doc_events",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "webhook_docevent",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Doc Event",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "after_insert\non_update\non_submit\non_cancel\non_trash\non_update_after_submit\non_change",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "sb_webhook",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Webhook Request",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "request_url",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Request URL",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "sb_webhook_headers",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Webhook Headers",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "webhook_headers",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Headers",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Webhook Header",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "sb_webhook_data",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Webhook Data",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "webhook_data",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Data",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Webhook Data",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-14 13:16:53.974340",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Webhook",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
73
frappe/integrations/doctype/webhook/webhook.py
Normal file
73
frappe/integrations/doctype/webhook/webhook.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json, requests
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from six.moves.urllib.parse import urlparse
|
||||
from time import sleep
|
||||
|
||||
class Webhook(Document):
|
||||
def autoname(self):
|
||||
self.name = self.webhook_doctype + "-" + self.webhook_docevent
|
||||
|
||||
def validate(self):
|
||||
self.validate_docevent()
|
||||
self.validate_request_url()
|
||||
self.validate_repeating_fields()
|
||||
|
||||
def on_update(self):
|
||||
frappe.cache().delete_value('webhooks')
|
||||
|
||||
def validate_docevent(self):
|
||||
if self.webhook_doctype:
|
||||
is_submittable = frappe.get_value("DocType", self.webhook_doctype, "is_submittable")
|
||||
if not is_submittable and self.webhook_docevent in ["on_submit", "on_cancel", "on_update_after_submit"]:
|
||||
frappe.throw(_("DocType must be Submittable for the selected Doc Event"))
|
||||
|
||||
def validate_request_url(self):
|
||||
try:
|
||||
request_url = urlparse(self.request_url).netloc
|
||||
if not request_url:
|
||||
raise frappe.ValidationError
|
||||
except Exception as e:
|
||||
frappe.throw(_("Check Request URL"), exc=e)
|
||||
|
||||
def validate_repeating_fields(self):
|
||||
"""Error when Same Field is entered multiple times in webhook_data"""
|
||||
webhook_data = []
|
||||
for entry in self.webhook_data:
|
||||
webhook_data.append(entry.fieldname)
|
||||
|
||||
if len(webhook_data)!= len(set(webhook_data)):
|
||||
frappe.throw(_("Same Field is entered more than once"))
|
||||
|
||||
def enqueue_webhook(doc, webhook):
|
||||
webhook = frappe.get_doc("Webhook", webhook.get("name"))
|
||||
headers = {}
|
||||
data = {}
|
||||
if webhook.webhook_headers:
|
||||
for h in webhook.webhook_headers:
|
||||
if h.get("key") and h.get("value"):
|
||||
headers[h.get("key")] = h.get("value")
|
||||
if webhook.webhook_data:
|
||||
for w in webhook.webhook_data:
|
||||
for k, v in doc.as_dict().items():
|
||||
if k == w.fieldname:
|
||||
data[w.key] = v
|
||||
for i in range(3):
|
||||
try:
|
||||
r = requests.post(webhook.request_url, data=json.dumps(data), headers=headers, timeout=5)
|
||||
r.raise_for_status()
|
||||
frappe.logger().debug({"webhook_success":r.text})
|
||||
break
|
||||
except Exception as e:
|
||||
frappe.logger().debug({"webhook_error":e, "try": i+1})
|
||||
sleep(3*i + 1)
|
||||
if i !=2:
|
||||
continue
|
||||
else:
|
||||
raise e
|
||||
0
frappe/integrations/doctype/webhook_data/__init__.py
Normal file
0
frappe/integrations/doctype/webhook_data/__init__.py
Normal file
130
frappe/integrations/doctype/webhook_data/webhook_data.json
Normal file
130
frappe/integrations/doctype/webhook_data/webhook_data.json
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-09-14 12:08:50.302810",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Fieldname",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "cb_doc_data",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "key",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Key",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-14 13:16:58.252176",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Webhook Data",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
}
|
||||
10
frappe/integrations/doctype/webhook_data/webhook_data.py
Normal file
10
frappe/integrations/doctype/webhook_data/webhook_data.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class WebhookData(Document):
|
||||
pass
|
||||
0
frappe/integrations/doctype/webhook_header/__init__.py
Normal file
0
frappe/integrations/doctype/webhook_header/__init__.py
Normal file
101
frappe/integrations/doctype/webhook_header/webhook_header.json
Normal file
101
frappe/integrations/doctype/webhook_header/webhook_header.json
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-09-08 16:27:39.195379",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "key",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Key",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "value",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Value",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-08 16:28:20.025612",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Webhook Header",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
10
frappe/integrations/doctype/webhook_header/webhook_header.py
Normal file
10
frappe/integrations/doctype/webhook_header/webhook_header.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class WebhookHeader(Document):
|
||||
pass
|
||||
|
|
@ -15,17 +15,18 @@ import hashlib, json
|
|||
from frappe.model import optional_fields
|
||||
from frappe.utils.file_manager import save_url
|
||||
from frappe.utils.global_search import update_global_search
|
||||
from frappe.integrations.doctype.webhook import run_webhooks
|
||||
|
||||
# once_only validation
|
||||
# methods
|
||||
|
||||
def get_doc(arg1, arg2=None):
|
||||
def get_doc(*args, **kwargs):
|
||||
"""returns a frappe.model.Document object.
|
||||
|
||||
:param arg1: Document dict or DocType name.
|
||||
:param arg2: [optional] document name.
|
||||
|
||||
There are two ways to call `get_doc`
|
||||
There are multiple ways to call `get_doc`
|
||||
|
||||
# will fetch the latest user object (with child table) from the database
|
||||
user = get_doc("User", "test@example.com")
|
||||
|
|
@ -38,23 +39,39 @@ def get_doc(arg1, arg2=None):
|
|||
{"role": "System Manager"}
|
||||
]
|
||||
})
|
||||
|
||||
# create new object with keyword arguments
|
||||
user = get_doc(doctype='User', email_id='test@example.com')
|
||||
"""
|
||||
if isinstance(arg1, BaseDocument):
|
||||
return arg1
|
||||
elif isinstance(arg1, string_types):
|
||||
doctype = arg1
|
||||
else:
|
||||
doctype = arg1.get("doctype")
|
||||
if args:
|
||||
if isinstance(args[0], BaseDocument):
|
||||
# already a document
|
||||
return args[0]
|
||||
elif isinstance(args[0], string_types):
|
||||
doctype = args[0]
|
||||
|
||||
elif isinstance(args[0], dict):
|
||||
# passed a dict
|
||||
kwargs = args[0]
|
||||
|
||||
else:
|
||||
raise ValueError('First non keyword argument must be a string or dict')
|
||||
|
||||
if kwargs:
|
||||
if 'doctype' in kwargs:
|
||||
doctype = kwargs['doctype']
|
||||
else:
|
||||
raise ValueError('"doctype" is a required key')
|
||||
|
||||
controller = get_controller(doctype)
|
||||
if controller:
|
||||
return controller(arg1, arg2)
|
||||
return controller(*args, **kwargs)
|
||||
|
||||
raise ImportError(arg1)
|
||||
raise ImportError(doctype)
|
||||
|
||||
class Document(BaseDocument):
|
||||
"""All controllers inherit from `Document`."""
|
||||
def __init__(self, arg1, arg2=None):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Constructor.
|
||||
|
||||
:param arg1: DocType name as string or document **dict**
|
||||
|
|
@ -67,29 +84,37 @@ class Document(BaseDocument):
|
|||
self._default_new_docs = {}
|
||||
self.flags = frappe._dict()
|
||||
|
||||
if arg1 and isinstance(arg1, string_types):
|
||||
if not arg2:
|
||||
if args and args[0] and isinstance(args[0], string_types):
|
||||
# first arugment is doctype
|
||||
if len(args)==1:
|
||||
# single
|
||||
self.doctype = self.name = arg1
|
||||
self.doctype = self.name = args[0]
|
||||
else:
|
||||
self.doctype = arg1
|
||||
if isinstance(arg2, dict):
|
||||
self.doctype = args[0]
|
||||
if isinstance(args[1], dict):
|
||||
# filter
|
||||
self.name = frappe.db.get_value(arg1, arg2, "name")
|
||||
self.name = frappe.db.get_value(args[0], args[1], "name")
|
||||
if self.name is None:
|
||||
frappe.throw(_("{0} {1} not found").format(_(arg1), arg2), frappe.DoesNotExistError)
|
||||
frappe.throw(_("{0} {1} not found").format(_(args[0]), args[1]),
|
||||
frappe.DoesNotExistError)
|
||||
else:
|
||||
self.name = arg2
|
||||
self.name = args[1]
|
||||
|
||||
self.load_from_db()
|
||||
return
|
||||
|
||||
elif isinstance(arg1, dict):
|
||||
super(Document, self).__init__(arg1)
|
||||
if args and args[0] and isinstance(args[0], dict):
|
||||
# first argument is a dict
|
||||
kwargs = args[0]
|
||||
|
||||
if kwargs:
|
||||
# init base document
|
||||
super(Document, self).__init__(kwargs)
|
||||
self.init_valid_columns()
|
||||
|
||||
else:
|
||||
# incorrect arguments. let's not proceed.
|
||||
raise frappe.DataError("Document({0}, {1})".format(arg1, arg2))
|
||||
raise ValueError('Illegal arguments')
|
||||
|
||||
def reload(self):
|
||||
"""Reload document from database"""
|
||||
|
|
@ -335,13 +360,18 @@ class Document(BaseDocument):
|
|||
self._doc_before_save = frappe.get_doc(self.doctype, self.name)
|
||||
return self._doc_before_save
|
||||
|
||||
def set_new_name(self):
|
||||
def set_new_name(self, force=False):
|
||||
"""Calls `frappe.naming.se_new_name` for parent and child docs."""
|
||||
if self.flags.name_set and not force:
|
||||
return
|
||||
|
||||
set_new_name(self)
|
||||
# set name for children
|
||||
for d in self.get_all_children():
|
||||
set_new_name(d)
|
||||
|
||||
self.flags.name_set = True
|
||||
|
||||
def get_title(self):
|
||||
'''Get the document title based on title_field or `title` or `name`'''
|
||||
return self.get(self.meta.get_title_field())
|
||||
|
|
@ -625,7 +655,7 @@ class Document(BaseDocument):
|
|||
name=self.name))
|
||||
|
||||
def _validate_links(self):
|
||||
if self.flags.ignore_links:
|
||||
if self.flags.ignore_links or self._action == "cancel":
|
||||
return
|
||||
|
||||
invalid_links, cancelled_links = self.get_invalid_links()
|
||||
|
|
@ -672,6 +702,7 @@ class Document(BaseDocument):
|
|||
out = Document.hook(fn)(self, *args, **kwargs)
|
||||
|
||||
self.run_email_alerts(method)
|
||||
run_webhooks(self, method)
|
||||
|
||||
return out
|
||||
|
||||
|
|
@ -998,7 +1029,7 @@ class Document(BaseDocument):
|
|||
|
||||
def get_signature(self):
|
||||
"""Returns signature (hash) for private URL."""
|
||||
return hashlib.sha224(get_datetime_str(self.creation)).hexdigest()
|
||||
return hashlib.sha224(get_datetime_str(self.creation).encode()).hexdigest()
|
||||
|
||||
def get_liked_by(self):
|
||||
liked_by = getattr(self, "_liked_by", None)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("core", "doctype", "user_email")
|
||||
frappe.reload_doc("core", "doctype", "user")
|
||||
for user_name in frappe.get_all('User', filters={'user_type': 'Website User'}):
|
||||
user = frappe.get_doc('User', user_name)
|
||||
if user.roles:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
"public/js/frappe/misc/rating_icons.html"
|
||||
],
|
||||
"js/control.min.js": [
|
||||
"public/js/frappe/ui/capture.js",
|
||||
"public/js/frappe/form/controls/base_control.js",
|
||||
"public/js/frappe/form/controls/base_input.js",
|
||||
"public/js/frappe/form/controls/data.js",
|
||||
|
|
@ -55,12 +56,15 @@
|
|||
"js/dialog.min.js": [
|
||||
"public/js/frappe/dom.js",
|
||||
"public/js/frappe/ui/modal.html",
|
||||
|
||||
"public/js/frappe/form/formatters.js",
|
||||
"public/js/frappe/form/layout.js",
|
||||
"public/js/frappe/ui/field_group.js",
|
||||
"public/js/frappe/form/link_selector.js",
|
||||
"public/js/frappe/form/multi_select_dialog.js",
|
||||
"public/js/frappe/ui/dialog.js",
|
||||
"public/js/frappe/ui/capture.js",
|
||||
|
||||
"public/js/frappe/form/controls/base_control.js",
|
||||
"public/js/frappe/form/controls/base_input.js",
|
||||
"public/js/frappe/form/controls/data.js",
|
||||
|
|
@ -130,7 +134,9 @@
|
|||
"public/js/lib/jSignature.min.js",
|
||||
"public/js/frappe/translate.js",
|
||||
"public/js/lib/datepicker/datepicker.min.js",
|
||||
"public/js/lib/datepicker/locale-all.js"
|
||||
"public/js/lib/datepicker/locale-all.js",
|
||||
"public/js/lib/jquery.jrumble.min.js",
|
||||
"public/js/lib/webcam.min.js"
|
||||
],
|
||||
"js/desk.min.js": [
|
||||
"public/js/frappe/class.js",
|
||||
|
|
@ -168,6 +174,7 @@
|
|||
"public/js/frappe/form/link_selector.js",
|
||||
"public/js/frappe/form/multi_select_dialog.js",
|
||||
"public/js/frappe/ui/dialog.js",
|
||||
"public/js/frappe/ui/capture.js",
|
||||
"public/js/frappe/ui/app_icon.js",
|
||||
|
||||
"public/js/frappe/model/model.js",
|
||||
|
|
|
|||
|
|
@ -398,9 +398,9 @@ fieldset[disabled] .form-control {
|
|||
width: 100%;
|
||||
}
|
||||
.awesomplete > ul {
|
||||
z-index: 1041;
|
||||
z-index: 1041 !important;
|
||||
transition: none;
|
||||
background: #fff;
|
||||
background-color: #fff;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
|
@ -624,6 +624,9 @@ li.user-progress .progress-bar {
|
|||
.frappe-rtl textarea {
|
||||
direction: rtl;
|
||||
}
|
||||
.frappe-rtl .checkbox .disp-area {
|
||||
margin-right: -20px;
|
||||
}
|
||||
.text-editor {
|
||||
height: 400px;
|
||||
background-color: white;
|
||||
|
|
|
|||
|
|
@ -288,6 +288,10 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.image-view-container .image-view-row {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ebeff2;
|
||||
}
|
||||
.image-view-container .image-view-item {
|
||||
flex: 0 0 25%;
|
||||
padding: 15px;
|
||||
|
|
@ -360,6 +364,18 @@
|
|||
border-bottom: 1px solid #EBEFF2;
|
||||
}
|
||||
}
|
||||
.item-selector {
|
||||
border: 1px solid #d1d8dd;
|
||||
}
|
||||
.item-selector .image-view-row {
|
||||
width: 100%;
|
||||
}
|
||||
.item-selector .image-field {
|
||||
height: 120px;
|
||||
}
|
||||
.item-selector .placeholder-text {
|
||||
font-size: 48px;
|
||||
}
|
||||
.image-view-container.three-column .image-view-item {
|
||||
flex: 0 0 33.33333333%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
.grid-report {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.chart_area{
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.grid-report .show-zero{
|
||||
direction: rtl ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -981,3 +981,7 @@ li.footer-child-item {
|
|||
margin: 15px 0px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.blog-list-item {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
|
|
|||
BIN
frappe/public/images/default-avatar.png
Normal file
BIN
frappe/public/images/default-avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
|
|
@ -85,7 +85,7 @@ frappe.assets = {
|
|||
frappe.assets.executed_.push(path)
|
||||
}
|
||||
}
|
||||
callback();
|
||||
callback && callback();
|
||||
},
|
||||
|
||||
// check if the asset exists in
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ frappe.Application = Class.extend({
|
|||
$.each(frappe.boot.notification_info.open_count_doctype, function(doctype, count) {
|
||||
if(count) {
|
||||
$('.open-notification.global[data-doctype="'+ doctype +'"]')
|
||||
.removeClass("hide").html(count > 20 ? "20+" : count);
|
||||
.removeClass("hide").html(count > 99 ? "99+" : count);
|
||||
} else {
|
||||
$('.open-notification.global[data-doctype="'+ doctype +'"]')
|
||||
.addClass("hide");
|
||||
|
|
@ -355,7 +355,7 @@ frappe.Application = Class.extend({
|
|||
},
|
||||
make_nav_bar: function() {
|
||||
// toolbar
|
||||
if(frappe.boot) {
|
||||
if(frappe.boot && !frappe.boot.in_setup_wizard) {
|
||||
frappe.frappe_toolbar = new frappe.ui.toolbar.Toolbar();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({
|
|||
<div class="checkbox">\
|
||||
<label>\
|
||||
<span class="input-area"></span>\
|
||||
<span class="disp-area" style="display:none; margin-left: -20px;"></span>\
|
||||
<span class="disp-area"></span>\
|
||||
<span class="label-area small"></span>\
|
||||
</label>\
|
||||
<p class="help-box small text-muted"></p>\
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({
|
|||
set_formatted_input: function(value) {
|
||||
this._super(value);
|
||||
|
||||
if(!value) value = '#ffffff';
|
||||
if (!value) value = '#FFFFFF';
|
||||
const contrast = frappe.ui.color.get_contrast_color(value);
|
||||
|
||||
this.$input.css({
|
||||
"background-color": value
|
||||
"background-color": value, "color": contrast
|
||||
});
|
||||
},
|
||||
bind_events: function () {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,28 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
|||
this.setup_drag_drop();
|
||||
this.setup_image_dialog();
|
||||
this.setting_count = 0;
|
||||
|
||||
$(document).on('form-refresh', () => {
|
||||
// reset last keystroke when a new form is loaded
|
||||
this.last_keystroke_on = null;
|
||||
})
|
||||
},
|
||||
render_camera_button: (context) => {
|
||||
var ui = $.summernote.ui;
|
||||
var button = ui.button({
|
||||
contents: '<i class="fa fa-camera"/>',
|
||||
tooltip: 'Camera',
|
||||
click: () => {
|
||||
const capture = new frappe.ui.Capture();
|
||||
capture.open();
|
||||
|
||||
capture.click((data) => {
|
||||
context.invoke('editor.insertImage', data);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return button.render();
|
||||
},
|
||||
make_editor: function() {
|
||||
var me = this;
|
||||
|
|
@ -25,9 +47,12 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
|||
['color', ['color']],
|
||||
['para', ['ul', 'ol', 'paragraph', 'hr']],
|
||||
//['height', ['height']],
|
||||
['media', ['link', 'picture', 'video', 'table']],
|
||||
['media', ['link', 'picture', 'camera', 'video', 'table']],
|
||||
['misc', ['fullscreen', 'codeview']]
|
||||
],
|
||||
buttons: {
|
||||
camera: this.render_camera_button,
|
||||
},
|
||||
keyMap: {
|
||||
pc: {
|
||||
'CTRL+ENTER': ''
|
||||
|
|
@ -54,7 +79,7 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
|||
me.parse_validate_and_set_in_model(value);
|
||||
},
|
||||
onKeydown: function(e) {
|
||||
me._last_change_on = new Date();
|
||||
me.last_keystroke_on = new Date();
|
||||
var key = frappe.ui.keys.get_key(e);
|
||||
// prevent 'New DocType (Ctrl + B)' shortcut in editor
|
||||
if(['ctrl+b', 'meta+b'].indexOf(key) !== -1) {
|
||||
|
|
@ -80,6 +105,7 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
|||
'outdent': 'fa fa-outdent',
|
||||
'arrowsAlt': 'fa fa-arrows-alt',
|
||||
'bold': 'fa fa-bold',
|
||||
'camera': 'fa fa-camera',
|
||||
'caret': 'caret',
|
||||
'circle': 'fa fa-circle',
|
||||
'close': 'fa fa-close',
|
||||
|
|
@ -184,20 +210,30 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
|||
|
||||
if(this.setting_count > 2) {
|
||||
// we don't understand how the internal triggers work,
|
||||
// so if someone is setting the value third time, then quit
|
||||
// so if someone is setting the value third time in 500ms,
|
||||
// then quit
|
||||
return;
|
||||
}
|
||||
|
||||
this.setting_count += 1;
|
||||
|
||||
let time_since_last_keystroke = moment() - moment(this._last_change_on);
|
||||
let time_since_last_keystroke = moment() - moment(this.last_keystroke_on);
|
||||
|
||||
if(!this._last_change_on || (time_since_last_keystroke > 3000)) {
|
||||
if(!this.last_keystroke_on || (time_since_last_keystroke > 3000)) {
|
||||
// if 3 seconds have passed since the last keystroke and
|
||||
// we have not set any value in the last 1 second, do this
|
||||
setTimeout(() => this.setting_count = 0, 500);
|
||||
this.editor.summernote('code', value || '');
|
||||
this.last_keystroke_on = null;
|
||||
} else {
|
||||
// user is probably still in the middle of typing
|
||||
// so lets not mess up the html by re-updating it
|
||||
// keep checking every second if our 3 second barrier
|
||||
// has been completed, so that we can refresh the html
|
||||
this._setting_value = setInterval(() => {
|
||||
if(time_since_last_keystroke > 3000) {
|
||||
// 3 seconds done! lets refresh
|
||||
// safe to update
|
||||
if(this.last_value !== this.get_input_value()) {
|
||||
// if not already in sync, reset
|
||||
this.editor.summernote('code', this.last_value || '');
|
||||
|
|
@ -205,6 +241,9 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
|||
clearInterval(this._setting_value);
|
||||
this._setting_value = null;
|
||||
this.setting_count = 0;
|
||||
|
||||
// clear timestamp of last keystroke
|
||||
this.last_keystroke_on = null;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,8 +170,9 @@ frappe.ui.form.Grid = Class.extend({
|
|||
} else {
|
||||
// redraw
|
||||
var _scroll_y = $(document).scrollTop();
|
||||
|
||||
this.make_head();
|
||||
// to hide checkbox if grid is not editable
|
||||
this.header_row && this.header_row.toggle_check();
|
||||
|
||||
if(!this.grid_rows) {
|
||||
this.grid_rows = [];
|
||||
|
|
@ -652,7 +653,7 @@ frappe.ui.form.Grid = Class.extend({
|
|||
var btn = this.custom_buttons[label];
|
||||
if(!btn) {
|
||||
btn = $('<button class="btn btn-default btn-xs btn-custom">' + label + '</button>')
|
||||
.css('margin-right', '10px')
|
||||
.css('margin-right', '4px')
|
||||
.prependTo(this.grid_buttons)
|
||||
.on('click', click);
|
||||
this.custom_buttons[label] = btn;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ frappe.ui.form.GridRow = Class.extend({
|
|||
init: function(opts) {
|
||||
this.on_grid_fields_dict = {};
|
||||
this.on_grid_fields = [];
|
||||
this.row_check_html = '<input type="checkbox" class="grid-row-check pull-left">';
|
||||
this.columns = {};
|
||||
this.columns_list = [];
|
||||
$.extend(this, opts);
|
||||
this.row_check_html = '<input type="checkbox" class="grid-row-check pull-left">';
|
||||
this.make();
|
||||
},
|
||||
make: function() {
|
||||
|
|
@ -121,6 +121,8 @@ frappe.ui.form.GridRow = Class.extend({
|
|||
if(this.grid_form) {
|
||||
this.grid_form.layout && this.grid_form.layout.refresh(this.doc);
|
||||
}
|
||||
|
||||
this.toggle_check();
|
||||
},
|
||||
render_template: function() {
|
||||
this.set_row_index();
|
||||
|
|
@ -592,4 +594,10 @@ frappe.ui.form.GridRow = Class.extend({
|
|||
toggle_editable: function(fieldname, editable) {
|
||||
this.set_field_property(fieldname, 'read_only', editable ? 0 : 1);
|
||||
},
|
||||
toggle_check: function() {
|
||||
// to hide checkbox if grid is not editable
|
||||
this.wrapper
|
||||
.find('.grid-row-check')
|
||||
.css("display", this.grid.is_editable()? 'block':'none');
|
||||
}
|
||||
});
|
||||
|
|
@ -441,7 +441,12 @@ frappe.ui.form.Layout = Class.extend({
|
|||
var parent = this.frm ? this.frm.doc : null;
|
||||
|
||||
if(expression.substr(0,5)=='eval:') {
|
||||
out = eval(expression.substr(5));
|
||||
try {
|
||||
out = eval(expression.substr(5));
|
||||
} catch(e) {
|
||||
frappe.throw(__('Invalid "depends_on" expression'));
|
||||
}
|
||||
|
||||
} else if(expression.substr(0,3)=='fn:' && this.frm) {
|
||||
out = this.frm.script_manager.trigger(expression.substr(3), this.doctype, this.docname);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,8 @@ frappe.ui.form.Share = Class.extend({
|
|||
user: user,
|
||||
read: $(d.body).find(".add-share-read").prop("checked") ? 1 : 0,
|
||||
write: $(d.body).find(".add-share-write").prop("checked") ? 1 : 0,
|
||||
share: $(d.body).find(".add-share-share").prop("checked") ? 1 : 0
|
||||
share: $(d.body).find(".add-share-share").prop("checked") ? 1 : 0,
|
||||
notify: $(d.body).find(".add-share-notify").prop("checked") ? 1 : 0
|
||||
},
|
||||
btn: this,
|
||||
callback: function(r) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
<div class="small form-clickable-section grid-footer">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 grid-buttons">
|
||||
<button type="reset" class="btn btn-xs btn-danger grid-remove-rows hide">
|
||||
<button type="reset"
|
||||
class="btn btn-xs btn-danger grid-remove-rows hide"
|
||||
style="margin-right: 4px;">
|
||||
{%= __("Delete") %}</button>
|
||||
<button type="reset"
|
||||
class="grid-add-multiple-rows btn btn-xs btn-default hide"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,19 @@
|
|||
<div class="col-xs-2"><input type="checkbox" class="add-share-share" name="share"></div>
|
||||
</div>
|
||||
<p>
|
||||
<button class="btn btn-primary btn-add-share">{%= __("Add") %}</button>
|
||||
<button class="btn btn-primary btn-add-share">{{ __("Add") }}</button>
|
||||
</p>
|
||||
{% } %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6"></div>
|
||||
<div class="col-xs-6">
|
||||
<div class="checkbox">
|
||||
<label><span class="input-area">
|
||||
<input type="checkbox" class="add-share-notify"
|
||||
name="notify"></span>
|
||||
<span class="label-area small">{{ __("Notify by email") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -73,7 +73,7 @@ frappe.socketio = {
|
|||
frappe.socketio.doc_subscribe(frm.doctype, frm.docname);
|
||||
});
|
||||
|
||||
$(document).on("form_refresh", function(e, frm) {
|
||||
$(document).on("form-refresh", function(e, frm) {
|
||||
if (frm.is_new()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
94
frappe/public/js/frappe/ui/capture.js
Normal file
94
frappe/public/js/frappe/ui/capture.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
frappe.ui.Capture = class
|
||||
{
|
||||
constructor (options = { })
|
||||
{
|
||||
this.options = Object.assign({}, frappe.ui.Capture.DEFAULT_OPTIONS, options);
|
||||
this.dialog = new frappe.ui.Dialog();
|
||||
this.template =
|
||||
`
|
||||
<div class="text-center">
|
||||
<div class="img-thumbnail" style="border: none;">
|
||||
<div id="frappe-capture"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="frappe-capture-btn-toolbar" style="padding-top: 15px; padding-bottom: 15px;">
|
||||
<div class="text-center">
|
||||
<div id="frappe-capture-btn-toolbar-snap">
|
||||
<a id="frappe-capture-btn-snap">
|
||||
<i class="fa fa-fw fa-2x fa-circle-o"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-group" id="frappe-capture-btn-toolbar-knap">
|
||||
<button class="btn btn-default" id="frappe-capture-btn-discard">
|
||||
<i class="fa fa-fw fa-arrow-left"/>
|
||||
</button>
|
||||
<button class="btn btn-default" id="frappe-capture-btn-accept">
|
||||
<i class="fa fa-fw fa-arrow-right"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$(this.dialog.body).append(this.template);
|
||||
|
||||
this.$btnBarSnap = $(this.dialog.body).find('#frappe-capture-btn-toolbar-snap');
|
||||
this.$btnBarKnap = $(this.dialog.body).find('#frappe-capture-btn-toolbar-knap');
|
||||
this.$btnBarKnap.hide();
|
||||
|
||||
Webcam.set(this.options);
|
||||
}
|
||||
|
||||
open ( )
|
||||
{
|
||||
this.dialog.show();
|
||||
|
||||
Webcam.attach('#frappe-capture');
|
||||
}
|
||||
|
||||
freeze ( )
|
||||
{
|
||||
this.$btnBarSnap.hide();
|
||||
this.$btnBarKnap.show();
|
||||
|
||||
Webcam.freeze();
|
||||
}
|
||||
|
||||
unfreeze ( )
|
||||
{
|
||||
this.$btnBarSnap.show();
|
||||
this.$btnBarKnap.hide();
|
||||
|
||||
Webcam.unfreeze();
|
||||
}
|
||||
|
||||
click (callback)
|
||||
{
|
||||
$(this.dialog.body).find('#frappe-capture-btn-snap').click(() => {
|
||||
this.freeze();
|
||||
|
||||
$(this.dialog.body).find('#frappe-capture-btn-discard').click(() => {
|
||||
this.unfreeze();
|
||||
});
|
||||
|
||||
$(this.dialog.body).find('#frappe-capture-btn-accept').click(() => {
|
||||
Webcam.snap((data) => {
|
||||
callback(data);
|
||||
});
|
||||
|
||||
this.hide();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
hide ( )
|
||||
{
|
||||
Webcam.reset();
|
||||
|
||||
$(this.dialog.$wrapper).remove();
|
||||
}
|
||||
};
|
||||
frappe.ui.Capture.DEFAULT_OPTIONS =
|
||||
{
|
||||
width: 480, height: 320, flip_horiz: true
|
||||
};
|
||||
|
|
@ -43,7 +43,7 @@ frappe.ui.notifications = {
|
|||
|
||||
// switch colour on the navbar and disable if no notifications
|
||||
$(".navbar-new-comments")
|
||||
.html(this.total > 20 ? '20+' : this.total)
|
||||
.html(this.total > 99 ? '99+' : this.total)
|
||||
.toggleClass("navbar-new-comments-true", this.total ? true : false)
|
||||
.parent().toggleClass("disabled", this.total ? false : true);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ frappe.search.utils = {
|
|||
var out = {
|
||||
route: match[1]
|
||||
}
|
||||
if(match[1][0]==='Form') {
|
||||
if(match[1][0]==='Form' && match[1][2]) {
|
||||
if(match[1][1] !== match[1][2]) {
|
||||
out.label = __(match[1][1]) + " " + match[1][2].bold();
|
||||
out.value = __(match[1][1]) + " " + match[1][2];
|
||||
|
|
|
|||
|
|
@ -12,7 +12,13 @@ frappe.upload = {
|
|||
|
||||
// whether to show public/private checkbox or not
|
||||
opts.show_private = !("is_private" in opts);
|
||||
|
||||
|
||||
// make private by default
|
||||
if (!("options" in opts) || ("options" in opts &&
|
||||
(!opts.options.toLowerCase()=="public" && !opts.options.toLowerCase()=="image"))) {
|
||||
opts.is_private = 1;
|
||||
}
|
||||
|
||||
var d = null;
|
||||
// create new dialog if no parent given
|
||||
if(!opts.parent) {
|
||||
|
|
@ -237,7 +243,6 @@ frappe.upload = {
|
|||
if (args.file_size) {
|
||||
frappe.upload.validate_max_file_size(args.file_size);
|
||||
}
|
||||
|
||||
if(opts.on_attach) {
|
||||
opts.on_attach(args)
|
||||
} else {
|
||||
|
|
@ -252,7 +257,7 @@ frappe.upload = {
|
|||
frappe.upload.upload_to_server(fileobj, args, opts);
|
||||
}, __("Private or Public?"));
|
||||
} else {
|
||||
if ("is_private" in opts) {
|
||||
if (!("is_private" in args) && "is_private" in opts) {
|
||||
args["is_private"] = opts.is_private;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({
|
|||
this._super();
|
||||
this.page_title = this.page_title + ' ' + __('Images');
|
||||
},
|
||||
prepare_data: function(data) {
|
||||
data = this._super(data);
|
||||
// absolute url if cordova, else relative
|
||||
data._image_url = this.get_image_url(data);
|
||||
return data;
|
||||
},
|
||||
render_image_view: function () {
|
||||
var html = this.items.map(this.render_item.bind(this)).join("");
|
||||
this.container = $('<div>')
|
||||
|
|
@ -22,14 +28,12 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({
|
|||
this.container.append(html);
|
||||
},
|
||||
render_item: function (item) {
|
||||
var image_url = this.get_image_url(item);
|
||||
var indicator = this.get_indicator_html(item);
|
||||
return frappe.render_template("image_view_item_row", {
|
||||
data: item,
|
||||
indicator: indicator,
|
||||
subject: this.get_subject_html(item, true),
|
||||
additional_columns: this.additional_columns,
|
||||
item_image: image_url,
|
||||
color: frappe.get_palette(item.item_name)
|
||||
});
|
||||
},
|
||||
|
|
@ -112,8 +116,8 @@ frappe.views.GalleryView = Class.extend({
|
|||
}
|
||||
|
||||
return {
|
||||
src: i.image,
|
||||
msrc: i.image,
|
||||
src: i._image_url,
|
||||
msrc: i._image_url,
|
||||
name: i.name,
|
||||
w: width,
|
||||
h: height,
|
||||
|
|
|
|||
|
|
@ -13,18 +13,18 @@
|
|||
<div class="image-field"
|
||||
data-name="{{ data.name }}"
|
||||
style="
|
||||
{% if (!item_image) { %}
|
||||
{% if (!data._image_url) { %}
|
||||
background-color: {{ color }};
|
||||
{% } %}
|
||||
border: 0px;"
|
||||
>
|
||||
{% if (!item_image) { %}
|
||||
{% if (!data._image_url) { %}
|
||||
<span class="placeholder-text">
|
||||
{%= frappe.get_abbr(data._title) %}
|
||||
</span>
|
||||
{% } %}
|
||||
{% if (item_image) { %}
|
||||
<img data-name="{{ data.name }}" src="{{ item_image }}" alt="{{data.title}}">
|
||||
{% if (data._image_url) { %}
|
||||
<img data-name="{{ data.name }}" src="{{ data._image_url }}" alt="{{data.title}}">
|
||||
{% } %}
|
||||
<button class="btn btn-default zoom-view" data-name="{{data.name}}">
|
||||
<i class="fa fa-search-plus"></i>
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ frappe.views.TreeView = Class.extend({
|
|||
frappe.msgprint(__("You are not allowed to print this report"));
|
||||
return false;
|
||||
}
|
||||
var tree = $(".tree:visible").html();
|
||||
var tree = $(".tree:visible").html();
|
||||
var me = this;
|
||||
frappe.ui.get_print_settings(false, function(print_settings) {
|
||||
var title = __(me.docname || me.doctype);
|
||||
|
|
@ -339,7 +339,15 @@ frappe.views.TreeView = Class.extend({
|
|||
if (has_perm) {
|
||||
me.page.add_menu_item(menu_item["label"], menu_item["action"]);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// last menu item
|
||||
me.page.add_menu_item(__('Add to Desktop'), () => {
|
||||
const label = me.doctype === 'Account' ?
|
||||
__('Chart of Accounts') :
|
||||
__(me.doctype);
|
||||
frappe.add_to_desktop(label, me.doctype);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ _f.Frm.prototype.set_query = function(fieldname, opt1, opt2) {
|
|||
}
|
||||
|
||||
_f.Frm.prototype.set_value_if_missing = function(field, value) {
|
||||
this.set_value(field, value, true);
|
||||
return this.set_value(field, value, true);
|
||||
}
|
||||
|
||||
_f.Frm.prototype.clear_table = function(fieldname) {
|
||||
|
|
|
|||
|
|
@ -497,7 +497,7 @@ _f.Frm.prototype.render_form = function(is_a_different_doc) {
|
|||
|
||||
// trigger global trigger
|
||||
// to use this
|
||||
$(document).trigger('form_refresh', [this]);
|
||||
$(document).trigger('form-refresh', [this]);
|
||||
|
||||
// fields
|
||||
this.refresh_fields();
|
||||
|
|
|
|||
2
frappe/public/js/lib/jquery.jrumble.min.js
vendored
Normal file
2
frappe/public/js/lib/jquery.jrumble.min.js
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/* jRumble v1.3 - http://jackrugile.com/jrumble - MIT License */
|
||||
(function(f){f.fn.jrumble=function(g){var a=f.extend({x:2,y:2,rotation:1,speed:15,opacity:false,opacityMin:0.5},g);return this.each(function(){var b=f(this),h=a.x*2,i=a.y*2,k=a.rotation*2,g=a.speed===0?1:a.speed,m=a.opacity,n=a.opacityMin,l,j,o=function(){var e=Math.floor(Math.random()*(h+1))-h/2,a=Math.floor(Math.random()*(i+1))-i/2,c=Math.floor(Math.random()*(k+1))-k/2,d=m?Math.random()+n:1,e=e===0&&h!==0?Math.random()<0.5?1:-1:e,a=a===0&&i!==0?Math.random()<0.5?1:-1:a;b.css("display")==="inline"&&(l=true,b.css("display","inline-block"));b.css({position:"relative",left:e+"px",top:a+"px","-ms-filter":"progid:DXImageTransform.Microsoft.Alpha(Opacity="+d*100+")",filter:"alpha(opacity="+d*100+")","-moz-opacity":d,"-khtml-opacity":d,opacity:d,"-webkit-transform":"rotate("+c+"deg)","-moz-transform":"rotate("+c+"deg)","-ms-transform":"rotate("+c+"deg)","-o-transform":"rotate("+c+"deg)",transform:"rotate("+c+"deg)"})},p={left:0,top:0,"-ms-filter":"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)",filter:"alpha(opacity=100)","-moz-opacity":1,"-khtml-opacity":1,opacity:1,"-webkit-transform":"rotate(0deg)","-moz-transform":"rotate(0deg)","-ms-transform":"rotate(0deg)","-o-transform":"rotate(0deg)",transform:"rotate(0deg)"};b.bind({startRumble:function(a){a.stopPropagation();clearInterval(j);j=setInterval(o,g)},stopRumble:function(a){a.stopPropagation();clearInterval(j);l&&b.css("display","inline");b.css(p)}})})}})(jQuery);
|
||||
2
frappe/public/js/lib/webcam.min.js
vendored
Normal file
2
frappe/public/js/lib/webcam.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -195,9 +195,9 @@ textarea.form-control {
|
|||
width: 100%;
|
||||
|
||||
&> ul {
|
||||
z-index: 1041;
|
||||
z-index: 1041 !important;
|
||||
transition: none;
|
||||
background: #fff;
|
||||
background-color: #fff;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
|
@ -479,7 +479,11 @@ li.user-progress {
|
|||
}
|
||||
|
||||
.frappe-rtl input ,.frappe-rtl textarea {
|
||||
direction: rtl
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.frappe-rtl .checkbox .disp-area {
|
||||
margin-right: -20px;
|
||||
}
|
||||
|
||||
.text-editor {
|
||||
|
|
|
|||
|
|
@ -361,6 +361,11 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.image-view-row {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ebeff2;
|
||||
}
|
||||
|
||||
.image-view-item {
|
||||
flex: 0 0 100%/4;
|
||||
padding: 15px;
|
||||
|
|
@ -431,6 +436,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
.item-selector {
|
||||
border: 1px solid @border-color;
|
||||
|
||||
.image-view-row {
|
||||
width: 100%;
|
||||
}
|
||||
.image-field {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.image-view-container.three-column {
|
||||
.image-view-item {
|
||||
flex: 0 0 100%/3;
|
||||
|
|
|
|||
|
|
@ -695,3 +695,8 @@ li.footer-child-item {
|
|||
margin: 15px 0px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.blog-list-item {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ def clear_global_cache():
|
|||
frappe.model.meta.clear_cache()
|
||||
frappe.cache().delete_value(["app_hooks", "installed_apps",
|
||||
"app_modules", "module_app", "notification_config", 'system_settings'
|
||||
'scheduler_events', 'time_zone'])
|
||||
'scheduler_events', 'time_zone', 'webhooks'])
|
||||
frappe.setup_module_map()
|
||||
|
||||
|
||||
|
|
@ -163,6 +163,7 @@ def get():
|
|||
# check only when clear cache is done, and don't cache this
|
||||
if frappe.local.request:
|
||||
bootinfo["change_log"] = get_change_log()
|
||||
bootinfo["in_setup_wizard"] = not cint(frappe.db.get_single_value('System Settings', 'setup_complete'))
|
||||
bootinfo["is_first_startup"] = cint(frappe.db.get_single_value('System Settings', 'is_first_startup'))
|
||||
|
||||
bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from frappe import _
|
|||
from frappe.utils import cint
|
||||
|
||||
@frappe.whitelist()
|
||||
def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=None):
|
||||
def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=None, notify=0):
|
||||
"""Share the given document with a user."""
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
|
|
@ -39,6 +39,7 @@ def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=No
|
|||
})
|
||||
|
||||
doc.save(ignore_permissions=True)
|
||||
notify_assignment(user, doctype, name, description=None, notify=notify)
|
||||
|
||||
return doc
|
||||
|
||||
|
|
@ -134,3 +135,20 @@ def check_share_permission(doctype, name):
|
|||
"""Check if the user can share with other users"""
|
||||
if not frappe.has_permission(doctype, ptype="share", doc=name):
|
||||
frappe.throw(_("No permission to {0} {1} {2}".format("share", doctype, name)), frappe.PermissionError)
|
||||
|
||||
def notify_assignment(shared_by, doc_type, doc_name, description=None, notify=0):
|
||||
|
||||
if not (shared_by and doc_type and doc_name): return
|
||||
|
||||
from frappe.utils import get_link_to_form
|
||||
document = get_link_to_form(doc_type, doc_name, label="%s: %s" % (doc_type, doc_name))
|
||||
|
||||
arg = {
|
||||
'contact': shared_by,
|
||||
'txt': _("A new document {0} has been shared by with you {1}.").format(document,
|
||||
shared_by),
|
||||
'notify': notify
|
||||
}
|
||||
|
||||
from frappe.desk.page.chat import chat
|
||||
chat.post(**arg)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
<div class="blogger">
|
||||
<div class="inline-block" style="vertical-align: top">
|
||||
<div class="avatar avatar-large">
|
||||
<img itemprop="thumbnailUrl" src="{{ blogger_info.avatar }}" />
|
||||
<img itemprop="thumbnailUrl" src="{{ blogger_info.avatar or "/assets/frappe/images/default-avatar.png" }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-block" style="width: calc(100% - 100px)">
|
||||
<h2 class="blogger-name"><a href="/blog?blogger={{ blogger_info.name }}">{{ blogger_info.full_name }}</a></h2>
|
||||
<h3 class="blogger-name text-muted">
|
||||
<a href="/blog?blogger={{ blogger_info.name }}">{{ blogger_info.full_name }}</a>
|
||||
</h3>
|
||||
<p class="text-muted">{%if blogger_info.bio %}{{ blogger_info.bio }}{% endif %}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% if sub_title %}
|
||||
<p class="lead">{{ sub_title }}</p>
|
||||
<h4 class="text-muted">{{ sub_title }}</h4>
|
||||
{% endif %}
|
||||
{% if not result -%}
|
||||
<div class="text-muted no-results" style="min-height: 300px;">
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import frappe.utils.scheduler
|
|||
import cProfile, pstats
|
||||
from six import StringIO
|
||||
from six.moves import reload_module
|
||||
from frappe.model.naming import revert_series_if_last
|
||||
|
||||
unittest_runner = unittest.TextTestRunner
|
||||
|
||||
|
|
@ -223,8 +224,6 @@ def make_test_records(doctype, verbose=0, force=False):
|
|||
continue
|
||||
|
||||
if not options in frappe.local.test_objects:
|
||||
if options in frappe.local.test_objects:
|
||||
print("No test records or circular reference for {0}".format(options))
|
||||
frappe.local.test_objects[options] = []
|
||||
make_test_records(options, verbose, force)
|
||||
make_test_records_for_doctype(options, verbose, force)
|
||||
|
|
@ -287,6 +286,11 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
|
|||
'''Make test objects from given list of `test_records` or from `test_records.json`'''
|
||||
records = []
|
||||
|
||||
def revert_naming(d):
|
||||
if getattr(d, 'naming_series', None):
|
||||
revert_series_if_last(d.naming_series, d.name)
|
||||
|
||||
|
||||
if test_records is None:
|
||||
test_records = frappe.get_test_records(doctype)
|
||||
|
||||
|
|
@ -296,17 +300,20 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
|
|||
|
||||
d = frappe.copy_doc(doc)
|
||||
|
||||
if doc.get('name'):
|
||||
d.name = doc.get('name')
|
||||
|
||||
if frappe.local.test_objects.get(d.doctype) and not reset:
|
||||
# do not create test records, if already exists
|
||||
return []
|
||||
|
||||
if d.meta.get_field("naming_series"):
|
||||
if not d.naming_series:
|
||||
d.naming_series = "_T-" + d.doctype + "-"
|
||||
|
||||
if doc.get('name'):
|
||||
d.name = doc.get('name')
|
||||
else:
|
||||
d.set_new_name()
|
||||
|
||||
if frappe.db.exists(d.doctype, d.name) and not reset:
|
||||
frappe.db.rollback()
|
||||
# do not create test records, if already exists
|
||||
continue
|
||||
|
||||
# submit if docstatus is set to 1 for test record
|
||||
docstatus = d.docstatus
|
||||
|
||||
|
|
@ -320,18 +327,17 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
|
|||
d.submit()
|
||||
|
||||
except frappe.NameError:
|
||||
pass
|
||||
revert_naming(d)
|
||||
|
||||
except Exception as e:
|
||||
if d.flags.ignore_these_exceptions_in_test and e.__class__ in d.flags.ignore_these_exceptions_in_test:
|
||||
pass
|
||||
|
||||
revert_naming(d)
|
||||
else:
|
||||
raise
|
||||
|
||||
records.append(d.name)
|
||||
|
||||
frappe.db.commit()
|
||||
frappe.db.commit()
|
||||
|
||||
return records
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class TestDomainification(unittest.TestCase):
|
|||
|
||||
def tearDown(self):
|
||||
frappe.db.sql("delete from tabRole where name='_Test Role'")
|
||||
frappe.db.sql("delete from `tabHas Role` where role='_Test Role'")
|
||||
frappe.db.sql("delete from tabDomain where name in ('_Test Domain 1', '_Test Domain 2')")
|
||||
frappe.delete_doc('DocType', 'Test Domainification')
|
||||
|
||||
|
|
|
|||
|
|
@ -555,7 +555,7 @@ def write_csv_file(path, app_messages, lang_dict):
|
|||
:param app_messages: Translatable strings for this app.
|
||||
:param lang_dict: Full translated dict.
|
||||
"""
|
||||
app_messages.sort(lambda x,y: cmp(x[1], y[1]))
|
||||
app_messages.sort(key = lambda x: x[1])
|
||||
from csv import writer
|
||||
with open(path, 'wb') as msgfile:
|
||||
w = writer(msgfile, lineterminator='\n')
|
||||
|
|
@ -684,7 +684,7 @@ def deduplicate_messages(messages):
|
|||
op = operator.itemgetter(1)
|
||||
messages = sorted(messages, key=op)
|
||||
for k, g in itertools.groupby(messages, op):
|
||||
ret.append(g.next())
|
||||
ret.append(next(g))
|
||||
return ret
|
||||
|
||||
def get_bench_dir():
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue