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