@@ -54,6 +54,7 @@ | |||||
"Taggle": true, | "Taggle": true, | ||||
"Gantt": true, | "Gantt": true, | ||||
"Slick": true, | "Slick": true, | ||||
"Webcam": true, | |||||
"PhotoSwipe": true, | "PhotoSwipe": true, | ||||
"PhotoSwipeUI_Default": true, | "PhotoSwipeUI_Default": true, | ||||
"fluxify": true, | "fluxify": true, | ||||
@@ -9,3 +9,5 @@ locale | |||||
dist/ | dist/ | ||||
build/ | build/ | ||||
frappe/docs/current | frappe/docs/current | ||||
.vscode | |||||
node_modules |
@@ -0,0 +1,3 @@ | |||||
{ | |||||
"python.linting.pylintEnabled": false | |||||
} |
@@ -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. |
@@ -0,0 +1,2 @@ | |||||
clean: | |||||
python setup.py clean |
@@ -1,9 +1,33 @@ | |||||
## Frappé Framework | |||||
[](https://travis-ci.org/frappe/frappe) | |||||
<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> | |||||
<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) | 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 | ### Installation | ||||
[Install via Frappé Bench](https://github.com/frappe/bench) | [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) | [https://frappe.io](https://frappe.io) | ||||
### License | ### 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 .exceptions import * | ||||
from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template | 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" | __title__ = "Frappe Framework" | ||||
local = Local() | local = Local() | ||||
@@ -602,7 +602,7 @@ def set_value(doctype, docname, fieldname, value=None): | |||||
import frappe.client | import frappe.client | ||||
return frappe.client.set_value(doctype, docname, fieldname, value) | 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. | """Return a `frappe.model.document.Document` object of the given type and name. | ||||
:param arg1: DocType name as string **or** document JSON. | :param arg1: DocType name as string **or** document JSON. | ||||
@@ -619,7 +619,7 @@ def get_doc(arg1, arg2=None): | |||||
""" | """ | ||||
import frappe.model.document | 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): | def get_last_doc(doctype): | ||||
"""Get last created document of this type.""" | """Get last created document of this type.""" | ||||
@@ -123,10 +123,9 @@ function pack(output_path, inputs, minify) { | |||||
} | } | ||||
function babelify(content, path, 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 { | try { | ||||
return babel.transform(content, { | return babel.transform(content, { | ||||
presets: presets, | presets: presets, | ||||
@@ -4,6 +4,7 @@ | |||||
from __future__ import unicode_literals, print_function | from __future__ import unicode_literals, print_function | ||||
from frappe.utils.minify import JavascriptMinify | from frappe.utils.minify import JavascriptMinify | ||||
import subprocess | import subprocess | ||||
import warnings | |||||
from six import iteritems, text_type | from six import iteritems, text_type | ||||
@@ -25,12 +26,12 @@ def setup(): | |||||
except ImportError: pass | except ImportError: pass | ||||
app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules] | 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""" | """concat / minify js files""" | ||||
# build js files | # build js files | ||||
setup() | setup() | ||||
make_asset_dirs(make_copy=make_copy) | |||||
make_asset_dirs(make_copy=make_copy, restore=restore) | |||||
# new nodejs build system | # new nodejs build system | ||||
command = 'node --use_strict ../apps/frappe/frappe/build.js --build' | command = 'node --use_strict ../apps/frappe/frappe/build.js --build' | ||||
@@ -60,7 +61,8 @@ def watch(no_compress): | |||||
# time.sleep(3) | # 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") | assets_path = os.path.join(frappe.local.sites_path, "assets") | ||||
for dir_path in [ | for dir_path in [ | ||||
os.path.join(assets_path, 'js'), | os.path.join(assets_path, 'js'), | ||||
@@ -80,11 +82,28 @@ def make_asset_dirs(make_copy=False): | |||||
for source, target in symlinks: | for source, target in symlinks: | ||||
source = os.path.abspath(source) | 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: | else: | ||||
if os.path.exists(target): | |||||
if os.path.islink(target): | |||||
os.unlink(target) | |||||
else: | |||||
shutil.rmtree(target) | |||||
os.symlink(source, target) | os.symlink(source, target) | ||||
else: | |||||
warnings.warn('Source {source} does not exists.'.format(source = source)) | |||||
def build(no_compress=False, verbose=False): | def build(no_compress=False, verbose=False): | ||||
assets_path = os.path.join(frappe.local.sites_path, "assets") | 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.command('build') | ||||
@click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking') | @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') | @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" | "Minify + concatenate JS and CSS files, build translations" | ||||
import frappe.build | import frappe.build | ||||
import frappe | import frappe | ||||
frappe.init('') | 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') | @click.command('watch') | ||||
def watch(): | def watch(): | ||||
@@ -72,6 +72,12 @@ def get_data(): | |||||
"name": "GSuite Templates", | "name": "GSuite Templates", | ||||
"description": _("Google GSuite Templates to integration with DocTypes"), | "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", | "label": "Email Address", | ||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | |||||
"options": "Email", | |||||
"permlevel": 0, | |||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
"read_only": 0, | "read_only": 0, | ||||
@@ -25,7 +25,7 @@ class Communication(Document): | |||||
"""create email flag queue""" | """create email flag queue""" | ||||
if self.communication_type == "Communication" and self.communication_medium == "Email" \ | if self.communication_type == "Communication" and self.communication_medium == "Email" \ | ||||
and self.sent_or_received == "Received" and self.uid and self.uid != -1: | and self.sent_or_received == "Received" and self.uid and self.uid != -1: | ||||
email_flag_queue = frappe.db.get_value("Email Flag Queue", { | email_flag_queue = frappe.db.get_value("Email Flag Queue", { | ||||
"communication": self.name, | "communication": self.name, | ||||
"is_completed": 0}) | "is_completed": 0}) | ||||
@@ -69,7 +69,7 @@ class Communication(Document): | |||||
def after_insert(self): | def after_insert(self): | ||||
if not (self.reference_doctype and self.reference_name): | if not (self.reference_doctype and self.reference_name): | ||||
return | return | ||||
if self.reference_doctype == "Communication" and self.sent_or_received == "Sent": | if self.reference_doctype == "Communication" and self.sent_or_received == "Sent": | ||||
frappe.db.set_value("Communication", self.reference_name, "status", "Replied") | frappe.db.set_value("Communication", self.reference_name, "status", "Replied") | ||||
@@ -94,9 +94,10 @@ class Communication(Document): | |||||
def on_update(self): | def on_update(self): | ||||
"""Update parent status as `Open` or `Replied`.""" | """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): | def on_trash(self): | ||||
if (not self.flags.ignore_permissions | 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) \ | if (doc.reference_doctype == "Communication" and doc.reference_name == doc.name) \ | ||||
or (doc.timeline_doctype == "Communication" and doc.timeline_name == doc.name): | or (doc.timeline_doctype == "Communication" and doc.timeline_name == doc.name): | ||||
return | return | ||||
if doc.reference_doctype and doc.reference_name: | if doc.reference_doctype and doc.reference_name: | ||||
if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name): | if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name): | ||||
return True | return True | ||||
@@ -277,7 +278,9 @@ def get_permission_query_conditions_for_communication(user): | |||||
if not user: user = frappe.session.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 | return None | ||||
else: | else: | ||||
accounts = frappe.get_all("User Email", filters={ "parent": user }, | 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): | def update_parent_status(doc): | ||||
"""Update status of parent document based on who is replying.""" | """Update status of parent document based on who is replying.""" | ||||
if doc.communication_type != "Communication": | |||||
return | |||||
parent = doc.get_parent_doc() | parent = doc.get_parent_doc() | ||||
if not parent: | if not parent: | ||||
return | 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": [ | "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 | "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 | "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 | "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 | "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 | "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 | "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 | "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": [ | "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 | "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 | "track_seen": 0 | ||||
} | |||||
} |
@@ -595,6 +595,15 @@ def validate_fields(meta): | |||||
frappe.throw(_("Sort field {0} must be a valid fieldname").format(fieldname), | frappe.throw(_("Sort field {0} must be a valid fieldname").format(fieldname), | ||||
InvalidFieldNameError) | 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") | fields = meta.get("fields") | ||||
fieldname_list = [d.fieldname for d in fields] | fieldname_list = [d.fieldname for d in fields] | ||||
@@ -620,6 +629,7 @@ def validate_fields(meta): | |||||
check_in_global_search(d) | check_in_global_search(d) | ||||
check_illegal_default(d) | check_illegal_default(d) | ||||
check_unique_and_text(d) | check_unique_and_text(d) | ||||
check_illegal_depends_on_conditions(d) | |||||
check_fold(fields) | check_fold(fields) | ||||
check_search_fields(meta, 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"): | def make_module_and_roles(doc, perm_fieldname="permissions"): | ||||
"""Make `Module Def` and `Role` records if already not made. Called while installing.""" | """Make `Module Def` and `Role` records if already not made. Called while installing.""" | ||||
try: | 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): | if not frappe.db.exists("Module Def", doc.module): | ||||
m = frappe.get_doc({"doctype": "Module Def", "module_name": doc.module}) | m = frappe.get_doc({"doctype": "Module Def", "module_name": doc.module}) | ||||
m.app_name = frappe.local.module_app[frappe.scrub(doc.module)] | m.app_name = frappe.local.module_app[frappe.scrub(doc.module)] | ||||
@@ -10,13 +10,22 @@ import unittest | |||||
class TestDocType(unittest.TestCase): | class TestDocType(unittest.TestCase): | ||||
def new_doctype(self, name, unique=0): | |||||
def new_doctype(self, name, unique=0, depends_on=''): | |||||
return frappe.get_doc({ | return frappe.get_doc({ | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"module": "Core", | "module": "Core", | ||||
"custom": 1, | "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 | "name": name | ||||
}) | }) | ||||
@@ -71,4 +80,28 @@ class TestDocType(unittest.TestCase): | |||||
field.fieldtype = "HTML" | field.fieldtype = "HTML" | ||||
field.label = "Some HTML Field" | field.label = "Some HTML Field" | ||||
doc.search_fields = "some_fieldname,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() { | get_template: function() { | ||||
return ` | 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> | </div> | ||||
`; | `; | ||||
}, | }, | ||||
@@ -14,7 +14,7 @@ def export_languages_json(): | |||||
languages = frappe.db.get_all('Language', fields=['name', 'language_name']) | languages = frappe.db.get_all('Language', fields=['name', 'language_name']) | ||||
languages = [{'name': d.language_name, 'code': d.name} for d in languages] | 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: | with open(frappe.get_app_path('frappe', 'geo', 'languages.json'), 'w') as f: | ||||
f.write(frappe.as_json(languages)) | f.write(frappe.as_json(languages)) | ||||
@@ -162,18 +162,13 @@ class TestUser(unittest.TestCase): | |||||
# from frappe.frappeclient import FrappeClient | # from frappe.frappeclient import FrappeClient | ||||
# update_site_config('deny_multiple_sessions', 0) | # update_site_config('deny_multiple_sessions', 0) | ||||
# | # | ||||
# print 'conn1' | |||||
# conn1 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) | # conn1 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) | ||||
# test_request(conn1) | # test_request(conn1) | ||||
# | # | ||||
# print 'conn2' | |||||
# conn2 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) | # conn2 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) | ||||
# test_request(conn2) | # test_request(conn2) | ||||
# | # | ||||
# update_site_config('deny_multiple_sessions', 1) | # update_site_config('deny_multiple_sessions', 1) | ||||
# | |||||
# print 'conn3' | |||||
# | |||||
# conn3 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) | # conn3 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) | ||||
# test_request(conn3) | # 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): | def export_csv(doctype, path): | ||||
from frappe.core.page.data_import_tool.exporter import get_template | 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") | get_template(doctype=doctype, all_doctypes="Yes", with_data="Yes") | ||||
csvfile.write(frappe.response.result.encode("utf-8")) | 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): | if field and ((select_columns and f[0] in select_columns[dt]) or not select_columns): | ||||
tablecolumns.append(field) | 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) | _column_start_end = frappe._dict(start=0) | ||||
@@ -16,7 +16,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_simple(self): | def test_Custom_Script_fixture_simple(self): | ||||
fixture = "Custom Script" | fixture = "Custom Script" | ||||
path = frappe.scrub(fixture) + "_original_style.csv" | path = frappe.scrub(fixture) + "_original_style.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -24,7 +24,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_simple_name_equal_default(self): | def test_Custom_Script_fixture_simple_name_equal_default(self): | ||||
fixture = ["Custom Script", {"name":["Item-Client"]}] | fixture = ["Custom Script", {"name":["Item-Client"]}] | ||||
path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" | path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -32,7 +32,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_simple_name_equal(self): | def test_Custom_Script_fixture_simple_name_equal(self): | ||||
fixture = ["Custom Script", {"name":["Item-Client"],"op":"="}] | fixture = ["Custom Script", {"name":["Item-Client"],"op":"="}] | ||||
path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv" | path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -40,7 +40,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_simple_name_not_equal(self): | def test_Custom_Script_fixture_simple_name_not_equal(self): | ||||
fixture = ["Custom Script", {"name":["Item-Client"],"op":"!="}] | fixture = ["Custom Script", {"name":["Item-Client"],"op":"!="}] | ||||
path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" | path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -49,7 +49,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_simple_name_at_least_equal(self): | def test_Custom_Script_fixture_simple_name_at_least_equal(self): | ||||
fixture = ["Custom Script", {"name":"Item-Cli"}] | fixture = ["Custom Script", {"name":"Item-Cli"}] | ||||
path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" | path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -57,7 +57,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_multi_name_equal(self): | def test_Custom_Script_fixture_multi_name_equal(self): | ||||
fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"="}] | fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"="}] | ||||
path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv" | path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -65,7 +65,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_multi_name_not_equal(self): | def test_Custom_Script_fixture_multi_name_not_equal(self): | ||||
fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"!="}] | fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"!="}] | ||||
path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" | path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -73,7 +73,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_empty_object(self): | def test_Custom_Script_fixture_empty_object(self): | ||||
fixture = ["Custom Script", {}] | fixture = ["Custom Script", {}] | ||||
path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" | path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -81,7 +81,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_just_list(self): | def test_Custom_Script_fixture_just_list(self): | ||||
fixture = ["Custom Script"] | fixture = ["Custom Script"] | ||||
path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" | path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -90,7 +90,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_rex_no_flags(self): | def test_Custom_Script_fixture_rex_no_flags(self): | ||||
fixture = ["Custom Script", {"name":r"^[i|A]"}] | fixture = ["Custom Script", {"name":r"^[i|A]"}] | ||||
path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv" | path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -98,7 +98,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Script_fixture_rex_with_flags(self): | def test_Custom_Script_fixture_rex_with_flags(self): | ||||
fixture = ["Custom Script", {"name":r"^[i|A]", "flags":"L,M"}] | fixture = ["Custom Script", {"name":r"^[i|A]", "flags":"L,M"}] | ||||
path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv" | path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -107,7 +107,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_simple(self): | def test_Custom_Field_fixture_simple(self): | ||||
fixture = "Custom Field" | fixture = "Custom Field" | ||||
path = frappe.scrub(fixture) + "_original_style.csv" | path = frappe.scrub(fixture) + "_original_style.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -115,7 +115,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_simple_name_equal_default(self): | def test_Custom_Field_fixture_simple_name_equal_default(self): | ||||
fixture = ["Custom Field", {"name":["Item-vat"]}] | fixture = ["Custom Field", {"name":["Item-vat"]}] | ||||
path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" | path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -123,7 +123,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_simple_name_equal(self): | def test_Custom_Field_fixture_simple_name_equal(self): | ||||
fixture = ["Custom Field", {"name":["Item-vat"],"op":"="}] | fixture = ["Custom Field", {"name":["Item-vat"],"op":"="}] | ||||
path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv" | path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -131,7 +131,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_simple_name_not_equal(self): | def test_Custom_Field_fixture_simple_name_not_equal(self): | ||||
fixture = ["Custom Field", {"name":["Item-vat"],"op":"!="}] | fixture = ["Custom Field", {"name":["Item-vat"],"op":"!="}] | ||||
path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" | path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -140,7 +140,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_simple_name_at_least_equal(self): | def test_Custom_Field_fixture_simple_name_at_least_equal(self): | ||||
fixture = ["Custom Field", {"name":"Item-va"}] | fixture = ["Custom Field", {"name":"Item-va"}] | ||||
path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" | path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -148,7 +148,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_multi_name_equal(self): | def test_Custom_Field_fixture_multi_name_equal(self): | ||||
fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"="}] | fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"="}] | ||||
path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv" | path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -156,7 +156,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_multi_name_not_equal(self): | def test_Custom_Field_fixture_multi_name_not_equal(self): | ||||
fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"!="}] | fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"!="}] | ||||
path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" | path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -164,7 +164,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_empty_object(self): | def test_Custom_Field_fixture_empty_object(self): | ||||
fixture = ["Custom Field", {}] | fixture = ["Custom Field", {}] | ||||
path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" | path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -172,7 +172,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_just_list(self): | def test_Custom_Field_fixture_just_list(self): | ||||
fixture = ["Custom Field"] | fixture = ["Custom Field"] | ||||
path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" | path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -181,7 +181,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_rex_no_flags(self): | def test_Custom_Field_fixture_rex_no_flags(self): | ||||
fixture = ["Custom Field", {"name":r"^[r|L]"}] | fixture = ["Custom Field", {"name":r"^[r|L]"}] | ||||
path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv" | path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -189,7 +189,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Custom_Field_fixture_rex_with_flags(self): | def test_Custom_Field_fixture_rex_with_flags(self): | ||||
fixture = ["Custom Field", {"name":r"^[i|A]", "flags":"L,M"}] | fixture = ["Custom Field", {"name":r"^[i|A]", "flags":"L,M"}] | ||||
path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv" | path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -199,7 +199,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_fixture_simple(self): | def test_Doctype_fixture_simple(self): | ||||
fixture = "ToDo" | fixture = "ToDo" | ||||
path = "Doctype_" + frappe.scrub(fixture) + "_original_style_should_be_all.csv" | path = "Doctype_" + frappe.scrub(fixture) + "_original_style_should_be_all.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -207,7 +207,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_fixture_simple_name_equal_default(self): | def test_Doctype_fixture_simple_name_equal_default(self): | ||||
fixture = ["ToDo", {"name":["TDI00000008"]}] | fixture = ["ToDo", {"name":["TDI00000008"]}] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -215,7 +215,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_fixture_simple_name_equal(self): | def test_Doctype_fixture_simple_name_equal(self): | ||||
fixture = ["ToDo", {"name":["TDI00000002"],"op":"="}] | fixture = ["ToDo", {"name":["TDI00000002"],"op":"="}] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -223,7 +223,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_simple_name_not_equal(self): | def test_Doctype_simple_name_not_equal(self): | ||||
fixture = ["ToDo", {"name":["TDI00000002"],"op":"!="}] | fixture = ["ToDo", {"name":["TDI00000002"],"op":"!="}] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -232,7 +232,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_fixture_simple_name_at_least_equal(self): | def test_Doctype_fixture_simple_name_at_least_equal(self): | ||||
fixture = ["ToDo", {"name":"TDI"}] | fixture = ["ToDo", {"name":"TDI"}] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -240,7 +240,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_multi_name_equal(self): | def test_Doctype_multi_name_equal(self): | ||||
fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"="}] | fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"="}] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_equal.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -248,7 +248,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_multi_name_not_equal(self): | def test_Doctype_multi_name_not_equal(self): | ||||
fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"!="}] | fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"!="}] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -256,7 +256,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_fixture_empty_object(self): | def test_Doctype_fixture_empty_object(self): | ||||
fixture = ["ToDo", {}] | fixture = ["ToDo", {}] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -264,7 +264,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_fixture_just_list(self): | def test_Doctype_fixture_just_list(self): | ||||
fixture = ["ToDo"] | fixture = ["ToDo"] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -273,7 +273,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_fixture_rex_no_flags(self): | def test_Doctype_fixture_rex_no_flags(self): | ||||
fixture = ["ToDo", {"name":r"^TDi"}] | fixture = ["ToDo", {"name":r"^TDi"}] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_no_flags_should_be_all.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_no_flags_should_be_all.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -281,7 +281,7 @@ class TestDataImportFixtures(unittest.TestCase): | |||||
def test_Doctype_fixture_rex_with_flags(self): | def test_Doctype_fixture_rex_with_flags(self): | ||||
fixture = ["ToDo", {"name":r"^TDi", "flags":"L,M"}] | fixture = ["ToDo", {"name":r"^TDi", "flags":"L,M"}] | ||||
path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_with_flags_should_be_none.csv" | path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_with_flags_should_be_none.csv" | ||||
# print "teste done {}".format(path) | |||||
export_csv(fixture, path) | export_csv(fixture, path) | ||||
self.assertTrue(True) | self.assertTrue(True) | ||||
os.remove(path) | os.remove(path) | ||||
@@ -112,13 +112,17 @@ $.extend(frappe.desktop, { | |||||
}, | }, | ||||
setup_module_click: function() { | setup_module_click: function() { | ||||
frappe.desktop.wiggling = false; | |||||
if(frappe.list_desktop) { | if(frappe.list_desktop) { | ||||
frappe.desktop.wrapper.on("click", ".desktop-list-item", function() { | frappe.desktop.wrapper.on("click", ".desktop-list-item", function() { | ||||
frappe.desktop.open_module($(this)); | frappe.desktop.open_module($(this)); | ||||
}); | }); | ||||
} else { | } else { | ||||
frappe.desktop.wrapper.on("click", ".app-icon", function() { | 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() { | frappe.desktop.wrapper.on("click", ".circle", function() { | ||||
@@ -127,6 +131,116 @@ $.extend(frappe.desktop, { | |||||
frappe.ui.notifications.show_open_count_list(doctype); | 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) { | open_module: function(parent) { | ||||
@@ -212,8 +326,8 @@ $.extend(frappe.desktop, { | |||||
notifier.toggle(sum ? true : false); | notifier.toggle(sum ? true : false); | ||||
var circle = notifier.find(".circle-text"); | var circle = notifier.find(".circle-text"); | ||||
var text = sum || ''; | var text = sum || ''; | ||||
if(text > 20) { | |||||
text = '20+'; | |||||
if(text > 99) { | |||||
text = '99+'; | |||||
} | } | ||||
if(circle.length) { | if(circle.length) { | ||||
@@ -18,8 +18,8 @@ class CustomField(Document): | |||||
if not self.label: | if not self.label: | ||||
frappe.throw(_("Label is mandatory")) | frappe.throw(_("Label is mandatory")) | ||||
# remove special characters from fieldname | # 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 | # fieldnames should be lowercase | ||||
self.fieldname = self.fieldname.lower() | self.fieldname = self.fieldname.lower() | ||||
@@ -117,6 +117,7 @@ CREATE TABLE `tabDocType` ( | |||||
`editable_grid` int(1) NOT NULL DEFAULT 1, | `editable_grid` int(1) NOT NULL DEFAULT 1, | ||||
`track_changes` int(1) NOT NULL DEFAULT 0, | `track_changes` int(1) NOT NULL DEFAULT 0, | ||||
`module` varchar(255) DEFAULT NULL, | `module` varchar(255) DEFAULT NULL, | ||||
`restrict_to_domain` varchar(255) DEFAULT NULL, | |||||
`app` varchar(255) DEFAULT NULL, | `app` varchar(255) DEFAULT NULL, | ||||
`autoname` varchar(255) DEFAULT NULL, | `autoname` varchar(255) DEFAULT NULL, | ||||
`name_case` 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 value: Default value. | ||||
:param parent: Usually, **User** to whom the default belongs. | :param parent: Usually, **User** to whom the default belongs. | ||||
:param parenttype: [optional] default is `__default`.""" | :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: | if value != None: | ||||
add_default(key, value, parent) | add_default(key, value, parent) | ||||
@@ -95,7 +95,7 @@ def get_desktop_icons(user=None): | |||||
icon.hidden = 1 | icon.hidden = 1 | ||||
# sort by idx | # 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 | # translate | ||||
for d in user_icons: | for d in user_icons: | ||||
@@ -404,3 +404,16 @@ palette = ( | |||||
('#4F8EA8', 1), | ('#4F8EA8', 1), | ||||
('#428B46', 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 |
@@ -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, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "color", | |||||
"fieldtype": "Color", | |||||
"fieldname": "column_break_2", | |||||
"fieldtype": "Column Break", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_global_search": 0, | "in_global_search": 0, | ||||
"in_list_view": 1, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | "in_standard_filter": 0, | ||||
"label": "Color", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | |||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
"read_only": 0, | "read_only": 0, | ||||
@@ -142,18 +140,20 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "column_break_2", | |||||
"fieldtype": "Column Break", | |||||
"fieldname": "color", | |||||
"fieldtype": "Color", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
"in_global_search": 0, | "in_global_search": 0, | ||||
"in_list_view": 0, | |||||
"in_list_view": 1, | |||||
"in_standard_filter": 0, | "in_standard_filter": 0, | ||||
"label": "Color", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | |||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
"read_only": 0, | "read_only": 0, | ||||
@@ -544,7 +544,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2017-09-05 12:54:58.044162", | |||||
"modified": "2017-09-30 13:57:29.398598", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Desk", | "module": "Desk", | ||||
"name": "ToDo", | "name": "ToDo", | ||||
@@ -119,7 +119,7 @@ def _get_linked_doctypes(doctype): | |||||
if not dt in ret: | if not dt in ret: | ||||
ret[dt] = {"get_parent": True} | ret[dt] = {"get_parent": True} | ||||
for dt in ret.keys(): | |||||
for dt in list(ret.keys()): | |||||
try: | try: | ||||
doctype_module = load_doctype_module(dt) | doctype_module = load_doctype_module(dt) | ||||
except ImportError: | except ImportError: | ||||
@@ -26,11 +26,7 @@ frappe.setup = { | |||||
} | } | ||||
frappe.pages['setup-wizard'].on_page_load = function(wrapper) { | 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.require(requires, function() { | ||||
frappe.call({ | frappe.call({ | ||||
@@ -96,17 +92,23 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||||
} | } | ||||
setup_keyboard_nav() { | 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() { | before_show_slide() { | ||||
@@ -118,6 +120,11 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||||
} | } | ||||
show_slide(id) { | show_slide(id) { | ||||
if (id === this.slides.length) { | |||||
// show_slide called on last slide | |||||
this.action_on_complete(); | |||||
return; | |||||
} | |||||
super.show_slide(id); | super.show_slide(id); | ||||
frappe.set_route(this.page_name, 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; | if (!this.current_slide.set_values()) return; | ||||
this.update_values(); | this.update_values(); | ||||
this.show_working_state(); | this.show_working_state(); | ||||
this.disable_keyboard_nav(); | |||||
return frappe.call({ | return frappe.call({ | ||||
method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete", | method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete", | ||||
args: {args: this.values}, | 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); | localStorage.setItem("session_last_route", frappe.setup.welcome_page); | ||||
} | } | ||||
setTimeout(function() { | setTimeout(function() { | ||||
// frappe.ui.toolbar.clear_cache(); | |||||
window.location = "/desk"; | |||||
// Reload | |||||
window.location.href = ''; | |||||
}, 2000); | }, 2000); | ||||
setTimeout(()=> { | setTimeout(()=> { | ||||
$('body').removeClass('setup-state'); | $('body').removeClass('setup-state'); | ||||
@@ -241,6 +249,13 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||||
} | } | ||||
get_message(title, message="", loading=false) { | 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"> | return $(`<div class="page-card-container" data-state="setup"> | ||||
<div class="page-card"> | <div class="page-card"> | ||||
<div class="page-card-head"> | <div class="page-card-head"> | ||||
@@ -251,12 +266,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||||
</div> | </div> | ||||
<p>${message}</p> | <p>${message}</p> | ||||
<div class="state-icon-container"> | <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> | </div> | ||||
</div>`); | </div>`); | ||||
@@ -259,7 +259,7 @@ def get_stats(stats, doctype, filters=[]): | |||||
stats[tag] = scrub_user_tags(tagcount) | stats[tag] = scrub_user_tags(tagcount) | ||||
stats[tag].append([_("No Tags"), frappe.get_list(doctype, | stats[tag].append([_("No Tags"), frappe.get_list(doctype, | ||||
fields=[tag, "count(*)"], | 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: | else: | ||||
stats[tag] = tagcount | stats[tag] = tagcount | ||||
@@ -269,7 +269,6 @@ def get_stats(stats, doctype, filters=[]): | |||||
except MySQLdb.OperationalError: | except MySQLdb.OperationalError: | ||||
# raised when _user_tags column is added on the fly | # raised when _user_tags column is added on the fly | ||||
pass | pass | ||||
return stats | return stats | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
@@ -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 | "uid_reindexed": uid_reindexed | ||||
} | } | ||||
communication = self.insert_communication(msg, args=args) | communication = self.insert_communication(msg, args=args) | ||||
#self.notify_update() | |||||
except SentEmailInInbox: | except SentEmailInInbox: | ||||
frappe.db.rollback() | frappe.db.rollback() | ||||
@@ -6,13 +6,24 @@ frappe.email_alert = { | |||||
} | } | ||||
frappe.model.with_doctype(frm.doc.document_type, function() { | 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) + ")"}; | 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) ? | function(d) { return in_list(frappe.model.no_value_type, d.fieldtype) ? | ||||
null : get_select_options(d); }); | null : get_select_options(d); }); | ||||
@@ -21,11 +32,9 @@ frappe.email_alert = { | |||||
frm.set_df_property("set_property_after_alert", "options", [""].concat(options)); | frm.set_df_property("set_property_after_alert", "options", [""].concat(options)); | ||||
// set date changed 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" || | function(d) { return (d.options == "Email" || | ||||
(d.options=='User' && d.fieldtype=='Link')) ? | (d.options=='User' && d.fieldtype=='Link')) ? | ||||
get_select_options(d) : null; }); | get_select_options(d) : null; }); | ||||
@@ -48,7 +57,14 @@ frappe.ui.form.on("Email Alert", { | |||||
"istable": 0 | "istable": 0 | ||||
} | } | ||||
} | } | ||||
}) | |||||
}); | |||||
frm.set_query("print_format", function() { | |||||
return { | |||||
"filters": { | |||||
"doc_type": frm.doc.document_type | |||||
} | |||||
} | |||||
}); | |||||
}, | }, | ||||
refresh: function(frm) { | refresh: function(frm) { | ||||
frappe.email_alert.setup_fieldname_select(frm); | frappe.email_alert.setup_fieldname_select(frm); | ||||
@@ -711,8 +711,8 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "attach_print", | |||||
"fieldtype": "Check", | |||||
"fieldname": "message_examples", | |||||
"fieldtype": "HTML", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
@@ -720,7 +720,68 @@ | |||||
"in_global_search": 0, | "in_global_search": 0, | ||||
"in_list_view": 0, | "in_list_view": 0, | ||||
"in_standard_filter": 0, | "in_standard_filter": 0, | ||||
"label": "Attach Print", | |||||
"label": "Message Examples", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "<h5>Message Example</h5>\n\n<pre><h3>Order Overdue</h3>\n\n<p>Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.</p>\n\n<!-- show last comment -->\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n<h4>Details</h4>\n\n<ul>\n<li>Customer: {{ doc.customer }}\n<li>Amount: {{ doc.total_amount }}\n</ul>\n</pre>", | |||||
"permlevel": 0, | |||||
"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": "view_properties", | |||||
"fieldtype": "Button", | |||||
"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": "View Properties (via Customize Form)", | |||||
"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": 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, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
@@ -741,8 +802,8 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "message_examples", | |||||
"fieldtype": "HTML", | |||||
"fieldname": "attach_print", | |||||
"fieldtype": "Check", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
@@ -750,11 +811,11 @@ | |||||
"in_global_search": 0, | "in_global_search": 0, | ||||
"in_list_view": 0, | "in_list_view": 0, | ||||
"in_standard_filter": 0, | "in_standard_filter": 0, | ||||
"label": "Message Examples", | |||||
"label": "Attach Print", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "<h5>Message Example</h5>\n\n<pre><h3>Order Overdue</h3>\n\n<p>Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.</p>\n\n<!-- show last comment -->\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n<h4>Details</h4>\n\n<ul>\n<li>Customer: {{ doc.customer }}\n<li>Amount: {{ doc.total_amount }}\n</ul>\n</pre>", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | |||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
"read_only": 0, | "read_only": 0, | ||||
@@ -771,8 +832,9 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "view_properties", | |||||
"fieldtype": "Button", | |||||
"depends_on": "attach_print", | |||||
"fieldname": "print_format", | |||||
"fieldtype": "Link", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
@@ -780,9 +842,10 @@ | |||||
"in_global_search": 0, | "in_global_search": 0, | ||||
"in_list_view": 0, | "in_list_view": 0, | ||||
"in_standard_filter": 0, | "in_standard_filter": 0, | ||||
"label": "View Properties (via Customize Form)", | |||||
"label": "Print Format", | |||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "Print Format", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
@@ -808,7 +871,7 @@ | |||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"menu_index": 0, | "menu_index": 0, | ||||
"modified": "2017-08-13 22:43:49.079330", | |||||
"modified": "2017-09-26 20:10:00.061780", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Email", | "module": "Email", | ||||
"name": "Email Alert", | "name": "Email Alert", | ||||
@@ -117,7 +117,7 @@ def get_context(context): | |||||
please enable Allow Print For {0} in Print Settings""".format(status)), | please enable Allow Print For {0} in Print Settings""".format(status)), | ||||
title=_("Error in Email Alert")) | title=_("Error in Email Alert")) | ||||
else: | 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) | context = get_context(doc) | ||||
recipients = [] | recipients = [] | ||||
@@ -503,7 +503,7 @@ | |||||
"columns": 0, | "columns": 0, | ||||
"fieldname": "attachments", | "fieldname": "attachments", | ||||
"fieldtype": "Code", | "fieldtype": "Code", | ||||
"hidden": 1, | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
"in_filter": 0, | "in_filter": 0, | ||||
@@ -517,7 +517,7 @@ | |||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | "print_hide_if_no_value": 0, | ||||
"read_only": 0, | |||||
"read_only": 1, | |||||
"remember_last_selected_value": 0, | "remember_last_selected_value": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -537,7 +537,7 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 0, | "max_attachments": 0, | ||||
"modified": "2017-07-07 16:29:15.780393", | |||||
"modified": "2017-09-25 15:39:21.781324", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Email", | "module": "Email", | ||||
"name": "Email Queue", | "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 | 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, | unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient, | ||||
email.unsubscribe_method, email.unsubscribe_params) | 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": | if email.expose_recipients == "header": | ||||
pass | 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) | email_sent_message = _("This email was sent to {0} and copied to {1}").format(email_sent_to,email_sent_cc) | ||||
else: | else: | ||||
email_sent_message = _("This email was sent to {0}").format(email_sent_to) | 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) | message = message.replace("<!--recipient-->", recipient) | ||||
@@ -11,7 +11,7 @@ app_color = "orange" | |||||
source_link = "https://github.com/frappe/frappe" | source_link = "https://github.com/frappe/frappe" | ||||
app_license = "MIT" | app_license = "MIT" | ||||
develop_version = '8.x.x-beta' | |||||
develop_version = '9.x.x-develop' | |||||
app_email = "info@frappe.io" | 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): | def upload_file_to_dropbox(filename, folder, dropbox_client): | ||||
create_folder_if_not_exists(folder, dropbox_client) | create_folder_if_not_exists(folder, dropbox_client) | ||||
chunk_size = 4 * 1024 * 1024 | chunk_size = 4 * 1024 * 1024 | ||||
file_size = os.path.getsize(filename) | |||||
file_size = os.path.getsize(encode(filename)) | |||||
mode = (dropbox.files.WriteMode.overwrite) | mode = (dropbox.files.WriteMode.overwrite) | ||||
f = open(encode(filename), 'rb') | f = open(encode(filename), 'rb') | ||||
@@ -7,7 +7,7 @@ import frappe | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe import _ | from frappe import _ | ||||
from six.moves.urllib.parse import urlencode | 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 | from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway | ||||
class StripeSettings(Document): | class StripeSettings(Document): | ||||
@@ -62,7 +62,7 @@ class StripeSettings(Document): | |||||
"Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} | "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} | ||||
data = { | data = { | ||||
"amount": cint(self.data.amount)*100, | |||||
"amount": cint(flt(self.data.amount)*100), | |||||
"currency": self.data.currency, | "currency": self.data.currency, | ||||
"source": self.data.stripe_token_id, | "source": self.data.stripe_token_id, | ||||
"description": self.data.description | "description": self.data.description | ||||
@@ -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) |
@@ -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() | |||||
]); | |||||
}); |
@@ -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) |
@@ -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"); | |||||
} | |||||
}); |
@@ -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 | |||||
} |
@@ -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,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 | |||||
} |
@@ -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,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 | |||||
} |
@@ -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.model import optional_fields | ||||
from frappe.utils.file_manager import save_url | from frappe.utils.file_manager import save_url | ||||
from frappe.utils.global_search import update_global_search | from frappe.utils.global_search import update_global_search | ||||
from frappe.integrations.doctype.webhook import run_webhooks | |||||
# once_only validation | # once_only validation | ||||
# methods | # methods | ||||
def get_doc(arg1, arg2=None): | |||||
def get_doc(*args, **kwargs): | |||||
"""returns a frappe.model.Document object. | """returns a frappe.model.Document object. | ||||
:param arg1: Document dict or DocType name. | :param arg1: Document dict or DocType name. | ||||
:param arg2: [optional] document 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 | # will fetch the latest user object (with child table) from the database | ||||
user = get_doc("User", "test@example.com") | user = get_doc("User", "test@example.com") | ||||
@@ -38,23 +39,39 @@ def get_doc(arg1, arg2=None): | |||||
{"role": "System Manager"} | {"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) | controller = get_controller(doctype) | ||||
if controller: | if controller: | ||||
return controller(arg1, arg2) | |||||
return controller(*args, **kwargs) | |||||
raise ImportError(arg1) | |||||
raise ImportError(doctype) | |||||
class Document(BaseDocument): | class Document(BaseDocument): | ||||
"""All controllers inherit from `Document`.""" | """All controllers inherit from `Document`.""" | ||||
def __init__(self, arg1, arg2=None): | |||||
def __init__(self, *args, **kwargs): | |||||
"""Constructor. | """Constructor. | ||||
:param arg1: DocType name as string or document **dict** | :param arg1: DocType name as string or document **dict** | ||||
@@ -67,29 +84,37 @@ class Document(BaseDocument): | |||||
self._default_new_docs = {} | self._default_new_docs = {} | ||||
self.flags = frappe._dict() | 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 | # single | ||||
self.doctype = self.name = arg1 | |||||
self.doctype = self.name = args[0] | |||||
else: | else: | ||||
self.doctype = arg1 | |||||
if isinstance(arg2, dict): | |||||
self.doctype = args[0] | |||||
if isinstance(args[1], dict): | |||||
# filter | # 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: | 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: | else: | ||||
self.name = arg2 | |||||
self.name = args[1] | |||||
self.load_from_db() | self.load_from_db() | ||||
return | |||||
if args and args[0] and isinstance(args[0], dict): | |||||
# first argument is a dict | |||||
kwargs = args[0] | |||||
elif isinstance(arg1, dict): | |||||
super(Document, self).__init__(arg1) | |||||
if kwargs: | |||||
# init base document | |||||
super(Document, self).__init__(kwargs) | |||||
self.init_valid_columns() | self.init_valid_columns() | ||||
else: | else: | ||||
# incorrect arguments. let's not proceed. | # incorrect arguments. let's not proceed. | ||||
raise frappe.DataError("Document({0}, {1})".format(arg1, arg2)) | |||||
raise ValueError('Illegal arguments') | |||||
def reload(self): | def reload(self): | ||||
"""Reload document from database""" | """Reload document from database""" | ||||
@@ -335,13 +360,18 @@ class Document(BaseDocument): | |||||
self._doc_before_save = frappe.get_doc(self.doctype, self.name) | self._doc_before_save = frappe.get_doc(self.doctype, self.name) | ||||
return self._doc_before_save | 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.""" | """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_new_name(self) | ||||
# set name for children | # set name for children | ||||
for d in self.get_all_children(): | for d in self.get_all_children(): | ||||
set_new_name(d) | set_new_name(d) | ||||
self.flags.name_set = True | |||||
def get_title(self): | def get_title(self): | ||||
'''Get the document title based on title_field or `title` or `name`''' | '''Get the document title based on title_field or `title` or `name`''' | ||||
return self.get(self.meta.get_title_field()) | return self.get(self.meta.get_title_field()) | ||||
@@ -625,7 +655,7 @@ class Document(BaseDocument): | |||||
name=self.name)) | name=self.name)) | ||||
def _validate_links(self): | def _validate_links(self): | ||||
if self.flags.ignore_links: | |||||
if self.flags.ignore_links or self._action == "cancel": | |||||
return | return | ||||
invalid_links, cancelled_links = self.get_invalid_links() | invalid_links, cancelled_links = self.get_invalid_links() | ||||
@@ -672,6 +702,7 @@ class Document(BaseDocument): | |||||
out = Document.hook(fn)(self, *args, **kwargs) | out = Document.hook(fn)(self, *args, **kwargs) | ||||
self.run_email_alerts(method) | self.run_email_alerts(method) | ||||
run_webhooks(self, method) | |||||
return out | return out | ||||
@@ -998,7 +1029,7 @@ class Document(BaseDocument): | |||||
def get_signature(self): | def get_signature(self): | ||||
"""Returns signature (hash) for private URL.""" | """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): | def get_liked_by(self): | ||||
liked_by = getattr(self, "_liked_by", None) | liked_by = getattr(self, "_liked_by", None) | ||||
@@ -1,6 +1,8 @@ | |||||
import frappe | import frappe | ||||
def execute(): | 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'}): | for user_name in frappe.get_all('User', filters={'user_type': 'Website User'}): | ||||
user = frappe.get_doc('User', user_name) | user = frappe.get_doc('User', user_name) | ||||
if user.roles: | if user.roles: | ||||
@@ -23,6 +23,7 @@ | |||||
"public/js/frappe/misc/rating_icons.html" | "public/js/frappe/misc/rating_icons.html" | ||||
], | ], | ||||
"js/control.min.js": [ | "js/control.min.js": [ | ||||
"public/js/frappe/ui/capture.js", | |||||
"public/js/frappe/form/controls/base_control.js", | "public/js/frappe/form/controls/base_control.js", | ||||
"public/js/frappe/form/controls/base_input.js", | "public/js/frappe/form/controls/base_input.js", | ||||
"public/js/frappe/form/controls/data.js", | "public/js/frappe/form/controls/data.js", | ||||
@@ -55,12 +56,15 @@ | |||||
"js/dialog.min.js": [ | "js/dialog.min.js": [ | ||||
"public/js/frappe/dom.js", | "public/js/frappe/dom.js", | ||||
"public/js/frappe/ui/modal.html", | "public/js/frappe/ui/modal.html", | ||||
"public/js/frappe/form/formatters.js", | "public/js/frappe/form/formatters.js", | ||||
"public/js/frappe/form/layout.js", | "public/js/frappe/form/layout.js", | ||||
"public/js/frappe/ui/field_group.js", | "public/js/frappe/ui/field_group.js", | ||||
"public/js/frappe/form/link_selector.js", | "public/js/frappe/form/link_selector.js", | ||||
"public/js/frappe/form/multi_select_dialog.js", | "public/js/frappe/form/multi_select_dialog.js", | ||||
"public/js/frappe/ui/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_control.js", | ||||
"public/js/frappe/form/controls/base_input.js", | "public/js/frappe/form/controls/base_input.js", | ||||
"public/js/frappe/form/controls/data.js", | "public/js/frappe/form/controls/data.js", | ||||
@@ -130,7 +134,9 @@ | |||||
"public/js/lib/jSignature.min.js", | "public/js/lib/jSignature.min.js", | ||||
"public/js/frappe/translate.js", | "public/js/frappe/translate.js", | ||||
"public/js/lib/datepicker/datepicker.min.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": [ | "js/desk.min.js": [ | ||||
"public/js/frappe/class.js", | "public/js/frappe/class.js", | ||||
@@ -168,6 +174,7 @@ | |||||
"public/js/frappe/form/link_selector.js", | "public/js/frappe/form/link_selector.js", | ||||
"public/js/frappe/form/multi_select_dialog.js", | "public/js/frappe/form/multi_select_dialog.js", | ||||
"public/js/frappe/ui/dialog.js", | "public/js/frappe/ui/dialog.js", | ||||
"public/js/frappe/ui/capture.js", | |||||
"public/js/frappe/ui/app_icon.js", | "public/js/frappe/ui/app_icon.js", | ||||
"public/js/frappe/model/model.js", | "public/js/frappe/model/model.js", | ||||
@@ -398,9 +398,9 @@ fieldset[disabled] .form-control { | |||||
width: 100%; | width: 100%; | ||||
} | } | ||||
.awesomplete > ul { | .awesomplete > ul { | ||||
z-index: 1041; | |||||
z-index: 1041 !important; | |||||
transition: none; | transition: none; | ||||
background: #fff; | |||||
background-color: #fff; | |||||
max-height: 200px; | max-height: 200px; | ||||
overflow-y: auto; | overflow-y: auto; | ||||
overflow-x: hidden; | overflow-x: hidden; | ||||
@@ -624,6 +624,9 @@ li.user-progress .progress-bar { | |||||
.frappe-rtl textarea { | .frappe-rtl textarea { | ||||
direction: rtl; | direction: rtl; | ||||
} | } | ||||
.frappe-rtl .checkbox .disp-area { | |||||
margin-right: -20px; | |||||
} | |||||
.text-editor { | .text-editor { | ||||
height: 400px; | height: 400px; | ||||
background-color: white; | background-color: white; | ||||
@@ -288,6 +288,10 @@ | |||||
display: flex; | display: flex; | ||||
flex-wrap: wrap; | flex-wrap: wrap; | ||||
} | } | ||||
.image-view-container .image-view-row { | |||||
display: flex; | |||||
border-bottom: 1px solid #ebeff2; | |||||
} | |||||
.image-view-container .image-view-item { | .image-view-container .image-view-item { | ||||
flex: 0 0 25%; | flex: 0 0 25%; | ||||
padding: 15px; | padding: 15px; | ||||
@@ -360,6 +364,18 @@ | |||||
border-bottom: 1px solid #EBEFF2; | 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 { | .image-view-container.three-column .image-view-item { | ||||
flex: 0 0 33.33333333%; | flex: 0 0 33.33333333%; | ||||
} | } | ||||
@@ -1,3 +1,11 @@ | |||||
.grid-report { | .grid-report { | ||||
direction: ltr; | direction: ltr; | ||||
} | } | ||||
.chart_area{ | |||||
direction: ltr; | |||||
} | |||||
.grid-report .show-zero{ | |||||
direction: rtl ; | |||||
} |
@@ -981,3 +981,7 @@ li.footer-child-item { | |||||
margin: 15px 0px; | margin: 15px 0px; | ||||
max-width: 100%; | max-width: 100%; | ||||
} | } | ||||
.blog-list-item { | |||||
padding-top: 30px; | |||||
padding-bottom: 30px; | |||||
} |
@@ -85,7 +85,7 @@ frappe.assets = { | |||||
frappe.assets.executed_.push(path) | frappe.assets.executed_.push(path) | ||||
} | } | ||||
} | } | ||||
callback(); | |||||
callback && callback(); | |||||
}, | }, | ||||
// check if the asset exists in | // 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) { | $.each(frappe.boot.notification_info.open_count_doctype, function(doctype, count) { | ||||
if(count) { | if(count) { | ||||
$('.open-notification.global[data-doctype="'+ doctype +'"]') | $('.open-notification.global[data-doctype="'+ doctype +'"]') | ||||
.removeClass("hide").html(count > 20 ? "20+" : count); | |||||
.removeClass("hide").html(count > 99 ? "99+" : count); | |||||
} else { | } else { | ||||
$('.open-notification.global[data-doctype="'+ doctype +'"]') | $('.open-notification.global[data-doctype="'+ doctype +'"]') | ||||
.addClass("hide"); | .addClass("hide"); | ||||
@@ -355,7 +355,7 @@ frappe.Application = Class.extend({ | |||||
}, | }, | ||||
make_nav_bar: function() { | make_nav_bar: function() { | ||||
// toolbar | // toolbar | ||||
if(frappe.boot) { | |||||
if(frappe.boot && !frappe.boot.in_setup_wizard) { | |||||
frappe.frappe_toolbar = new frappe.ui.toolbar.Toolbar(); | frappe.frappe_toolbar = new frappe.ui.toolbar.Toolbar(); | ||||
} | } | ||||
@@ -5,7 +5,7 @@ frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({ | |||||
<div class="checkbox">\ | <div class="checkbox">\ | ||||
<label>\ | <label>\ | ||||
<span class="input-area"></span>\ | <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>\ | <span class="label-area small"></span>\ | ||||
</label>\ | </label>\ | ||||
<p class="help-box small text-muted"></p>\ | <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) { | set_formatted_input: function(value) { | ||||
this._super(value); | this._super(value); | ||||
if(!value) value = '#ffffff'; | |||||
if (!value) value = '#FFFFFF'; | |||||
const contrast = frappe.ui.color.get_contrast_color(value); | |||||
this.$input.css({ | this.$input.css({ | ||||
"background-color": value | |||||
"background-color": value, "color": contrast | |||||
}); | }); | ||||
}, | }, | ||||
bind_events: function () { | bind_events: function () { | ||||
@@ -6,6 +6,28 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ | |||||
this.setup_drag_drop(); | this.setup_drag_drop(); | ||||
this.setup_image_dialog(); | this.setup_image_dialog(); | ||||
this.setting_count = 0; | 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() { | make_editor: function() { | ||||
var me = this; | var me = this; | ||||
@@ -25,9 +47,12 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ | |||||
['color', ['color']], | ['color', ['color']], | ||||
['para', ['ul', 'ol', 'paragraph', 'hr']], | ['para', ['ul', 'ol', 'paragraph', 'hr']], | ||||
//['height', ['height']], | //['height', ['height']], | ||||
['media', ['link', 'picture', 'video', 'table']], | |||||
['media', ['link', 'picture', 'camera', 'video', 'table']], | |||||
['misc', ['fullscreen', 'codeview']] | ['misc', ['fullscreen', 'codeview']] | ||||
], | ], | ||||
buttons: { | |||||
camera: this.render_camera_button, | |||||
}, | |||||
keyMap: { | keyMap: { | ||||
pc: { | pc: { | ||||
'CTRL+ENTER': '' | 'CTRL+ENTER': '' | ||||
@@ -54,7 +79,7 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ | |||||
me.parse_validate_and_set_in_model(value); | me.parse_validate_and_set_in_model(value); | ||||
}, | }, | ||||
onKeydown: function(e) { | onKeydown: function(e) { | ||||
me._last_change_on = new Date(); | |||||
me.last_keystroke_on = new Date(); | |||||
var key = frappe.ui.keys.get_key(e); | var key = frappe.ui.keys.get_key(e); | ||||
// prevent 'New DocType (Ctrl + B)' shortcut in editor | // prevent 'New DocType (Ctrl + B)' shortcut in editor | ||||
if(['ctrl+b', 'meta+b'].indexOf(key) !== -1) { | 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', | 'outdent': 'fa fa-outdent', | ||||
'arrowsAlt': 'fa fa-arrows-alt', | 'arrowsAlt': 'fa fa-arrows-alt', | ||||
'bold': 'fa fa-bold', | 'bold': 'fa fa-bold', | ||||
'camera': 'fa fa-camera', | |||||
'caret': 'caret', | 'caret': 'caret', | ||||
'circle': 'fa fa-circle', | 'circle': 'fa fa-circle', | ||||
'close': 'fa fa-close', | 'close': 'fa fa-close', | ||||
@@ -184,20 +210,30 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ | |||||
if(this.setting_count > 2) { | if(this.setting_count > 2) { | ||||
// we don't understand how the internal triggers work, | // 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; | return; | ||||
} | } | ||||
this.setting_count += 1; | 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); | setTimeout(() => this.setting_count = 0, 500); | ||||
this.editor.summernote('code', value || ''); | this.editor.summernote('code', value || ''); | ||||
this.last_keystroke_on = null; | |||||
} else { | } 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(() => { | this._setting_value = setInterval(() => { | ||||
if(time_since_last_keystroke > 3000) { | if(time_since_last_keystroke > 3000) { | ||||
// 3 seconds done! lets refresh | |||||
// safe to update | |||||
if(this.last_value !== this.get_input_value()) { | if(this.last_value !== this.get_input_value()) { | ||||
// if not already in sync, reset | // if not already in sync, reset | ||||
this.editor.summernote('code', this.last_value || ''); | this.editor.summernote('code', this.last_value || ''); | ||||
@@ -205,6 +241,9 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ | |||||
clearInterval(this._setting_value); | clearInterval(this._setting_value); | ||||
this._setting_value = null; | this._setting_value = null; | ||||
this.setting_count = 0; | this.setting_count = 0; | ||||
// clear timestamp of last keystroke | |||||
this.last_keystroke_on = null; | |||||
} | } | ||||
}, 1000); | }, 1000); | ||||
} | } | ||||
@@ -170,8 +170,9 @@ frappe.ui.form.Grid = Class.extend({ | |||||
} else { | } else { | ||||
// redraw | // redraw | ||||
var _scroll_y = $(document).scrollTop(); | var _scroll_y = $(document).scrollTop(); | ||||
this.make_head(); | this.make_head(); | ||||
// to hide checkbox if grid is not editable | |||||
this.header_row && this.header_row.toggle_check(); | |||||
if(!this.grid_rows) { | if(!this.grid_rows) { | ||||
this.grid_rows = []; | this.grid_rows = []; | ||||
@@ -652,7 +653,7 @@ frappe.ui.form.Grid = Class.extend({ | |||||
var btn = this.custom_buttons[label]; | var btn = this.custom_buttons[label]; | ||||
if(!btn) { | if(!btn) { | ||||
btn = $('<button class="btn btn-default btn-xs btn-custom">' + label + '</button>') | btn = $('<button class="btn btn-default btn-xs btn-custom">' + label + '</button>') | ||||
.css('margin-right', '10px') | |||||
.css('margin-right', '4px') | |||||
.prependTo(this.grid_buttons) | .prependTo(this.grid_buttons) | ||||
.on('click', click); | .on('click', click); | ||||
this.custom_buttons[label] = btn; | this.custom_buttons[label] = btn; | ||||
@@ -2,10 +2,10 @@ frappe.ui.form.GridRow = Class.extend({ | |||||
init: function(opts) { | init: function(opts) { | ||||
this.on_grid_fields_dict = {}; | this.on_grid_fields_dict = {}; | ||||
this.on_grid_fields = []; | this.on_grid_fields = []; | ||||
this.row_check_html = '<input type="checkbox" class="grid-row-check pull-left">'; | |||||
this.columns = {}; | this.columns = {}; | ||||
this.columns_list = []; | this.columns_list = []; | ||||
$.extend(this, opts); | $.extend(this, opts); | ||||
this.row_check_html = '<input type="checkbox" class="grid-row-check pull-left">'; | |||||
this.make(); | this.make(); | ||||
}, | }, | ||||
make: function() { | make: function() { | ||||
@@ -121,6 +121,8 @@ frappe.ui.form.GridRow = Class.extend({ | |||||
if(this.grid_form) { | if(this.grid_form) { | ||||
this.grid_form.layout && this.grid_form.layout.refresh(this.doc); | this.grid_form.layout && this.grid_form.layout.refresh(this.doc); | ||||
} | } | ||||
this.toggle_check(); | |||||
}, | }, | ||||
render_template: function() { | render_template: function() { | ||||
this.set_row_index(); | this.set_row_index(); | ||||
@@ -592,4 +594,10 @@ frappe.ui.form.GridRow = Class.extend({ | |||||
toggle_editable: function(fieldname, editable) { | toggle_editable: function(fieldname, editable) { | ||||
this.set_field_property(fieldname, 'read_only', editable ? 0 : 1); | 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; | var parent = this.frm ? this.frm.doc : null; | ||||
if(expression.substr(0,5)=='eval:') { | 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) { | } else if(expression.substr(0,3)=='fn:' && this.frm) { | ||||
out = this.frm.script_manager.trigger(expression.substr(3), this.doctype, this.docname); | out = this.frm.script_manager.trigger(expression.substr(3), this.doctype, this.docname); | ||||
} else { | } else { | ||||
@@ -140,7 +140,8 @@ frappe.ui.form.Share = Class.extend({ | |||||
user: user, | user: user, | ||||
read: $(d.body).find(".add-share-read").prop("checked") ? 1 : 0, | read: $(d.body).find(".add-share-read").prop("checked") ? 1 : 0, | ||||
write: $(d.body).find(".add-share-write").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, | btn: this, | ||||
callback: function(r) { | callback: function(r) { | ||||
@@ -7,7 +7,9 @@ | |||||
<div class="small form-clickable-section grid-footer"> | <div class="small form-clickable-section grid-footer"> | ||||
<div class="row"> | <div class="row"> | ||||
<div class="col-sm-6 grid-buttons"> | <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> | {%= __("Delete") %}</button> | ||||
<button type="reset" | <button type="reset" | ||||
class="grid-add-multiple-rows btn btn-xs btn-default hide" | 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 class="col-xs-2"><input type="checkbox" class="add-share-share" name="share"></div> | ||||
</div> | </div> | ||||
<p> | <p> | ||||
<button class="btn btn-primary btn-add-share">{%= __("Add") %}</button> | |||||
<button class="btn btn-primary btn-add-share">{{ __("Add") }}</button> | |||||
</p> | </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); | 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()) { | if (frm.is_new()) { | ||||
return; | return; | ||||
} | } | ||||
@@ -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 | // switch colour on the navbar and disable if no notifications | ||||
$(".navbar-new-comments") | $(".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) | .toggleClass("navbar-new-comments-true", this.total ? true : false) | ||||
.parent().toggleClass("disabled", this.total ? false : true); | .parent().toggleClass("disabled", this.total ? false : true); | ||||
}, | }, | ||||
@@ -51,7 +51,7 @@ frappe.search.utils = { | |||||
var out = { | var out = { | ||||
route: match[1] | route: match[1] | ||||
} | } | ||||
if(match[1][0]==='Form') { | |||||
if(match[1][0]==='Form' && match[1][2]) { | |||||
if(match[1][1] !== match[1][2]) { | if(match[1][1] !== match[1][2]) { | ||||
out.label = __(match[1][1]) + " " + match[1][2].bold(); | out.label = __(match[1][1]) + " " + match[1][2].bold(); | ||||
out.value = __(match[1][1]) + " " + match[1][2]; | out.value = __(match[1][1]) + " " + match[1][2]; | ||||
@@ -12,7 +12,13 @@ frappe.upload = { | |||||
// whether to show public/private checkbox or not | // whether to show public/private checkbox or not | ||||
opts.show_private = !("is_private" in opts); | 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; | var d = null; | ||||
// create new dialog if no parent given | // create new dialog if no parent given | ||||
if(!opts.parent) { | if(!opts.parent) { | ||||
@@ -237,7 +243,6 @@ frappe.upload = { | |||||
if (args.file_size) { | if (args.file_size) { | ||||
frappe.upload.validate_max_file_size(args.file_size); | frappe.upload.validate_max_file_size(args.file_size); | ||||
} | } | ||||
if(opts.on_attach) { | if(opts.on_attach) { | ||||
opts.on_attach(args) | opts.on_attach(args) | ||||
} else { | } else { | ||||
@@ -252,7 +257,7 @@ frappe.upload = { | |||||
frappe.upload.upload_to_server(fileobj, args, opts); | frappe.upload.upload_to_server(fileobj, args, opts); | ||||
}, __("Private or Public?")); | }, __("Private or Public?")); | ||||
} else { | } else { | ||||
if ("is_private" in opts) { | |||||
if (!("is_private" in args) && "is_private" in opts) { | |||||
args["is_private"] = opts.is_private; | args["is_private"] = opts.is_private; | ||||
} | } | ||||
@@ -14,6 +14,12 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({ | |||||
this._super(); | this._super(); | ||||
this.page_title = this.page_title + ' ' + __('Images'); | 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 () { | render_image_view: function () { | ||||
var html = this.items.map(this.render_item.bind(this)).join(""); | var html = this.items.map(this.render_item.bind(this)).join(""); | ||||
this.container = $('<div>') | this.container = $('<div>') | ||||
@@ -22,14 +28,12 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({ | |||||
this.container.append(html); | this.container.append(html); | ||||
}, | }, | ||||
render_item: function (item) { | render_item: function (item) { | ||||
var image_url = this.get_image_url(item); | |||||
var indicator = this.get_indicator_html(item); | var indicator = this.get_indicator_html(item); | ||||
return frappe.render_template("image_view_item_row", { | return frappe.render_template("image_view_item_row", { | ||||
data: item, | data: item, | ||||
indicator: indicator, | indicator: indicator, | ||||
subject: this.get_subject_html(item, true), | subject: this.get_subject_html(item, true), | ||||
additional_columns: this.additional_columns, | additional_columns: this.additional_columns, | ||||
item_image: image_url, | |||||
color: frappe.get_palette(item.item_name) | color: frappe.get_palette(item.item_name) | ||||
}); | }); | ||||
}, | }, | ||||
@@ -112,8 +116,8 @@ frappe.views.GalleryView = Class.extend({ | |||||
} | } | ||||
return { | return { | ||||
src: i.image, | |||||
msrc: i.image, | |||||
src: i._image_url, | |||||
msrc: i._image_url, | |||||
name: i.name, | name: i.name, | ||||
w: width, | w: width, | ||||
h: height, | h: height, | ||||
@@ -13,18 +13,18 @@ | |||||
<div class="image-field" | <div class="image-field" | ||||
data-name="{{ data.name }}" | data-name="{{ data.name }}" | ||||
style=" | style=" | ||||
{% if (!item_image) { %} | |||||
{% if (!data._image_url) { %} | |||||
background-color: {{ color }}; | background-color: {{ color }}; | ||||
{% } %} | {% } %} | ||||
border: 0px;" | border: 0px;" | ||||
> | > | ||||
{% if (!item_image) { %} | |||||
{% if (!data._image_url) { %} | |||||
<span class="placeholder-text"> | <span class="placeholder-text"> | ||||
{%= frappe.get_abbr(data._title) %} | {%= frappe.get_abbr(data._title) %} | ||||
</span> | </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}}"> | <button class="btn btn-default zoom-view" data-name="{{data.name}}"> | ||||
<i class="fa fa-search-plus"></i> | <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")); | frappe.msgprint(__("You are not allowed to print this report")); | ||||
return false; | return false; | ||||
} | } | ||||
var tree = $(".tree:visible").html(); | |||||
var tree = $(".tree:visible").html(); | |||||
var me = this; | var me = this; | ||||
frappe.ui.get_print_settings(false, function(print_settings) { | frappe.ui.get_print_settings(false, function(print_settings) { | ||||
var title = __(me.docname || me.doctype); | var title = __(me.docname || me.doctype); | ||||
@@ -339,7 +339,15 @@ frappe.views.TreeView = Class.extend({ | |||||
if (has_perm) { | if (has_perm) { | ||||
me.page.add_menu_item(menu_item["label"], menu_item["action"]); | 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) { | _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) { | _f.Frm.prototype.clear_table = function(fieldname) { | ||||
@@ -497,7 +497,7 @@ _f.Frm.prototype.render_form = function(is_a_different_doc) { | |||||
// trigger global trigger | // trigger global trigger | ||||
// to use this | // to use this | ||||
$(document).trigger('form_refresh', [this]); | |||||
$(document).trigger('form-refresh', [this]); | |||||
// fields | // fields | ||||
this.refresh_fields(); | this.refresh_fields(); | ||||
@@ -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); |
@@ -195,9 +195,9 @@ textarea.form-control { | |||||
width: 100%; | width: 100%; | ||||
&> ul { | &> ul { | ||||
z-index: 1041; | |||||
z-index: 1041 !important; | |||||
transition: none; | transition: none; | ||||
background: #fff; | |||||
background-color: #fff; | |||||
max-height: 200px; | max-height: 200px; | ||||
overflow-y: auto; | overflow-y: auto; | ||||
overflow-x: hidden; | overflow-x: hidden; | ||||
@@ -479,7 +479,11 @@ li.user-progress { | |||||
} | } | ||||
.frappe-rtl input ,.frappe-rtl textarea { | .frappe-rtl input ,.frappe-rtl textarea { | ||||
direction: rtl | |||||
direction: rtl; | |||||
} | |||||
.frappe-rtl .checkbox .disp-area { | |||||
margin-right: -20px; | |||||
} | } | ||||
.text-editor { | .text-editor { | ||||
@@ -361,6 +361,11 @@ | |||||
display: flex; | display: flex; | ||||
flex-wrap: wrap; | flex-wrap: wrap; | ||||
.image-view-row { | |||||
display: flex; | |||||
border-bottom: 1px solid #ebeff2; | |||||
} | |||||
.image-view-item { | .image-view-item { | ||||
flex: 0 0 100%/4; | flex: 0 0 100%/4; | ||||
padding: 15px; | 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-container.three-column { | ||||
.image-view-item { | .image-view-item { | ||||
flex: 0 0 100%/3; | flex: 0 0 100%/3; | ||||
@@ -695,3 +695,8 @@ li.footer-child-item { | |||||
margin: 15px 0px; | margin: 15px 0px; | ||||
max-width: 100%; | 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.model.meta.clear_cache() | ||||
frappe.cache().delete_value(["app_hooks", "installed_apps", | frappe.cache().delete_value(["app_hooks", "installed_apps", | ||||
"app_modules", "module_app", "notification_config", 'system_settings' | "app_modules", "module_app", "notification_config", 'system_settings' | ||||
'scheduler_events', 'time_zone']) | |||||
'scheduler_events', 'time_zone', 'webhooks']) | |||||
frappe.setup_module_map() | frappe.setup_module_map() | ||||
@@ -163,6 +163,7 @@ def get(): | |||||
# check only when clear cache is done, and don't cache this | # check only when clear cache is done, and don't cache this | ||||
if frappe.local.request: | if frappe.local.request: | ||||
bootinfo["change_log"] = get_change_log() | 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["is_first_startup"] = cint(frappe.db.get_single_value('System Settings', 'is_first_startup')) | ||||
bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version") | bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version") | ||||
@@ -7,7 +7,7 @@ from frappe import _ | |||||
from frappe.utils import cint | from frappe.utils import cint | ||||
@frappe.whitelist() | @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.""" | """Share the given document with a user.""" | ||||
if not user: | if not user: | ||||
user = frappe.session.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) | doc.save(ignore_permissions=True) | ||||
notify_assignment(user, doctype, name, description=None, notify=notify) | |||||
return doc | return doc | ||||
@@ -134,3 +135,20 @@ def check_share_permission(doctype, name): | |||||
"""Check if the user can share with other users""" | """Check if the user can share with other users""" | ||||
if not frappe.has_permission(doctype, ptype="share", doc=name): | if not frappe.has_permission(doctype, ptype="share", doc=name): | ||||
frappe.throw(_("No permission to {0} {1} {2}".format("share", doctype, name)), frappe.PermissionError) | 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="blogger"> | ||||
<div class="inline-block" style="vertical-align: top"> | <div class="inline-block" style="vertical-align: top"> | ||||
<div class="avatar avatar-large"> | <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> | </div> | ||||
<div class="inline-block" style="width: calc(100% - 100px)"> | <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> | <p class="text-muted">{%if blogger_info.bio %}{{ blogger_info.bio }}{% endif %}</p> | ||||
</div> | </div> | ||||
</div> | </div> |
@@ -1,5 +1,5 @@ | |||||
{% if sub_title %} | {% if sub_title %} | ||||
<p class="lead">{{ sub_title }}</p> | |||||
<h4 class="text-muted">{{ sub_title }}</h4> | |||||
{% endif %} | {% endif %} | ||||
{% if not result -%} | {% if not result -%} | ||||
<div class="text-muted no-results" style="min-height: 300px;"> | <div class="text-muted no-results" style="min-height: 300px;"> | ||||
@@ -13,6 +13,7 @@ import frappe.utils.scheduler | |||||
import cProfile, pstats | import cProfile, pstats | ||||
from six import StringIO | from six import StringIO | ||||
from six.moves import reload_module | from six.moves import reload_module | ||||
from frappe.model.naming import revert_series_if_last | |||||
unittest_runner = unittest.TextTestRunner | unittest_runner = unittest.TextTestRunner | ||||
@@ -223,8 +224,6 @@ def make_test_records(doctype, verbose=0, force=False): | |||||
continue | continue | ||||
if not options in frappe.local.test_objects: | 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] = [] | frappe.local.test_objects[options] = [] | ||||
make_test_records(options, verbose, force) | make_test_records(options, verbose, force) | ||||
make_test_records_for_doctype(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`''' | '''Make test objects from given list of `test_records` or from `test_records.json`''' | ||||
records = [] | 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: | if test_records is None: | ||||
test_records = frappe.get_test_records(doctype) | test_records = frappe.get_test_records(doctype) | ||||
@@ -296,16 +300,19 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False): | |||||
d = frappe.copy_doc(doc) | d = frappe.copy_doc(doc) | ||||
if d.meta.get_field("naming_series"): | |||||
if not d.naming_series: | |||||
d.naming_series = "_T-" + d.doctype + "-" | |||||
if doc.get('name'): | if doc.get('name'): | ||||
d.name = doc.get('name') | d.name = doc.get('name') | ||||
else: | |||||
d.set_new_name() | |||||
if frappe.local.test_objects.get(d.doctype) and not reset: | |||||
if frappe.db.exists(d.doctype, d.name) and not reset: | |||||
frappe.db.rollback() | |||||
# do not create test records, if already exists | # 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 + "-" | |||||
continue | |||||
# submit if docstatus is set to 1 for test record | # submit if docstatus is set to 1 for test record | ||||
docstatus = d.docstatus | docstatus = d.docstatus | ||||
@@ -320,18 +327,17 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False): | |||||
d.submit() | d.submit() | ||||
except frappe.NameError: | except frappe.NameError: | ||||
pass | |||||
revert_naming(d) | |||||
except Exception as e: | except Exception as e: | ||||
if d.flags.ignore_these_exceptions_in_test and e.__class__ in d.flags.ignore_these_exceptions_in_test: | if d.flags.ignore_these_exceptions_in_test and e.__class__ in d.flags.ignore_these_exceptions_in_test: | ||||
pass | |||||
revert_naming(d) | |||||
else: | else: | ||||
raise | raise | ||||
records.append(d.name) | records.append(d.name) | ||||
frappe.db.commit() | |||||
frappe.db.commit() | |||||
return records | return records | ||||
@@ -20,6 +20,7 @@ class TestDomainification(unittest.TestCase): | |||||
def tearDown(self): | def tearDown(self): | ||||
frappe.db.sql("delete from tabRole where name='_Test Role'") | 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.db.sql("delete from tabDomain where name in ('_Test Domain 1', '_Test Domain 2')") | ||||
frappe.delete_doc('DocType', 'Test Domainification') | 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 app_messages: Translatable strings for this app. | ||||
:param lang_dict: Full translated dict. | :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 | from csv import writer | ||||
with open(path, 'wb') as msgfile: | with open(path, 'wb') as msgfile: | ||||
w = writer(msgfile, lineterminator='\n') | w = writer(msgfile, lineterminator='\n') | ||||
@@ -684,7 +684,7 @@ def deduplicate_messages(messages): | |||||
op = operator.itemgetter(1) | op = operator.itemgetter(1) | ||||
messages = sorted(messages, key=op) | messages = sorted(messages, key=op) | ||||
for k, g in itertools.groupby(messages, op): | for k, g in itertools.groupby(messages, op): | ||||
ret.append(g.next()) | |||||
ret.append(next(g)) | |||||
return ret | return ret | ||||
def get_bench_dir(): | def get_bench_dir(): | ||||