Merge branch 'staging'

This commit is contained in:
Nabin Hait 2017-10-04 18:20:50 +05:30
commit 311dff662a
182 changed files with 16129 additions and 12417 deletions

View file

@ -54,6 +54,7 @@
"Taggle": true,
"Gantt": true,
"Slick": true,
"Webcam": true,
"PhotoSwipe": true,
"PhotoSwipeUI_Default": true,
"fluxify": true,

BIN
.github/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

2
.gitignore vendored
View file

@ -9,3 +9,5 @@ locale
dist/
build/
frappe/docs/current
.vscode
node_modules

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"python.linting.pylintEnabled": false
}

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2016-2017 Frappé Technologies Pvt. Ltd. <developers@frappe.io>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

2
Makefile Normal file
View file

@ -0,0 +1,2 @@
clean:
python setup.py clean

View file

@ -1,9 +1,33 @@
## Frappé Framework
<div align="center">
<img src=".github/logo.png" height="256">
<h1>
<a href="https://frappe.io">
frappé
</a>
</h1>
<h3>
a web framework with <a href="https://www.youtube.com/watch?v=LOjk3m0wTwg">"batteries included"
</h3>
<h5>
it's pronounced - <em>fra-pay</em>
</h5>
</div>
[![Build Status](https://travis-ci.org/frappe/frappe.png)](https://travis-ci.org/frappe/frappe)
<div align="center">
<a href="https://travis-ci.org/frappe/frappe">
<img src="https://img.shields.io/travis/frappe/frappe.svg?style=flat-square">
</a>
<a href='https://frappe.io/docs'>
<img src='https://img.shields.io/badge/docs-📖-7575FF.svg?style=flat-square'/>
</a>
</div>
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com)
### Table of Contents
* [Installation](#installation)
* [License](#license)
### Installation
[Install via Frappé Bench](https://github.com/frappe/bench)
@ -20,5 +44,4 @@ For details and documentation, see the website
[https://frappe.io](https://frappe.io)
### License
MIT License
This repository has been released under the [MIT License](LICENSE).

View file

@ -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."""

View file

@ -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,

View file

@ -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")

View file

@ -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():

View file

@ -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"),
}
]
}
]

View file

@ -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,

View file

@ -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 },

View file

@ -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

View file

@ -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
}
}

View file

@ -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)]

View file

@ -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))

View file

@ -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>
`;
},

View file

@ -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))

View file

@ -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)
#

View file

@ -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"))

View file

@ -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)

View file

@ -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)

View file

@ -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>
&times
</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) {

View file

@ -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()

View file

@ -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,

View file

@ -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)

View file

@ -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

View file

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: ToDo", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new ToDo
() => frappe.tests.make('ToDo', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View file

@ -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",

View file

@ -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:

View file

@ -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>`);

View file

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -0,0 +1,103 @@
# Webhooks
Webhooks are "user-defined HTTP callbacks". You can create webhook which triggers on Doc Event of the selected DocType. When the `doc_events` occurs, the source site makes an HTTP request to the URI configured for the webhook. Users can configure them to cause events on one site to invoke behaviour on another.
#### Configure Webhook
To add Webhook go to
> Integrations > External Documents > Webhook
Webhook
<img class="screenshot" src="/docs/assets/img/webhook.png">
1. Select the DocType for which hook needs to be triggered e.g. Note
2. Select the DocEvent for which hook needs to be triggered e.g. on_trash
3. Enter a valid request URL. On occurence of DocEvent, POST request with doc's json as data is made to the URL.
4. Optionally you can add headers to the request to be made. Useful for sending api key if required.
5. Optionally you can select fields and set its `key` to be sent as data json
e.g. Webhook
- **DocType** : `Quotation`
- **Doc Event** : `on_update`
- **Request URL** : `https://httpbin.org/post`
- **Webhook Data** :
1. **Fieldname** : `name` and **Key** : `id`
2. **Fieldname** : `items` and **Key** : `lineItems`
Note: if no headers or data is present, request will be made without any header or body
Example response of request sent by frappe server on `Quotation` - `on_update` to https://httpbin.org/post:
```
{
"args": {},
"data": "{\"lineItems\": [{\"stock_qty\": 1.0, \"base_price_list_rate\": 1.0, \"image\": \"\", \"creation\": \"2017-09-14 13:41:58.373023\", \"base_amount\": 1.0, \"qty\": 1.0, \"margin_rate_or_amount\": 0.0, \"rate\": 1.0, \"owner\": \"Administrator\", \"stock_uom\": \"Unit\", \"base_net_amount\": 1.0, \"page_break\": 0, \"modified_by\": \"Administrator\", \"base_net_rate\": 1.0, \"discount_percentage\": 0.0, \"item_name\": \"I1\", \"amount\": 1.0, \"actual_qty\": 0.0, \"net_rate\": 1.0, \"conversion_factor\": 1.0, \"warehouse\": \"Finished Goods - R\", \"docstatus\": 0, \"prevdoc_docname\": null, \"uom\": \"Unit\", \"description\": \"I1\", \"parent\": \"QTN-00001\", \"brand\": null, \"gst_hsn_code\": null, \"base_rate\": 1.0, \"item_code\": \"I1\", \"projected_qty\": 0.0, \"margin_type\": \"\", \"doctype\": \"Quotation Item\", \"rate_with_margin\": 0.0, \"pricing_rule\": null, \"price_list_rate\": 1.0, \"name\": \"QUOD/00001\", \"idx\": 1, \"item_tax_rate\": \"{}\", \"item_group\": \"Products\", \"modified\": \"2017-09-14 17:09:51.239271\", \"parenttype\": \"Quotation\", \"customer_item_code\": null, \"net_amount\": 1.0, \"prevdoc_doctype\": null, \"parentfield\": \"items\"}], \"id\": \"QTN-00001\"}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Content-Length": "1075",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.18.1"
},
"json": {
"id": "QTN-00001",
"lineItems": [
{
"actual_qty": 0.0,
"amount": 1.0,
"base_amount": 1.0,
"base_net_amount": 1.0,
"base_net_rate": 1.0,
"base_price_list_rate": 1.0,
"base_rate": 1.0,
"brand": null,
"conversion_factor": 1.0,
"creation": "2017-09-14 13:41:58.373023",
"customer_item_code": null,
"description": "I1",
"discount_percentage": 0.0,
"docstatus": 0,
"doctype": "Quotation Item",
"gst_hsn_code": null,
"idx": 1,
"image": "",
"item_code": "I1",
"item_group": "Products",
"item_name": "I1",
"item_tax_rate": "{}",
"margin_rate_or_amount": 0.0,
"margin_type": "",
"modified": "2017-09-14 17:09:51.239271",
"modified_by": "Administrator",
"name": "QUOD/00001",
"net_amount": 1.0,
"net_rate": 1.0,
"owner": "Administrator",
"page_break": 0,
"parent": "QTN-00001",
"parentfield": "items",
"parenttype": "Quotation",
"prevdoc_docname": null,
"prevdoc_doctype": null,
"price_list_rate": 1.0,
"pricing_rule": null,
"projected_qty": 0.0,
"qty": 1.0,
"rate": 1.0,
"rate_with_margin": 0.0,
"stock_qty": 1.0,
"stock_uom": "Unit",
"uom": "Unit",
"warehouse": "Finished Goods - R"
}
]
},
"url": "https://httpbin.org/post"
}
```

View file

@ -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()

View file

@ -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);

View file

@ -705,36 +705,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "attach_print",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Attach Print",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -794,6 +764,99 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "attach_print",
"columns": 0,
"fieldname": "column_break_25",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "attach_print",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Attach Print",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "attach_print",
"fieldname": "print_format",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print Format",
"length": 0,
"no_copy": 0,
"options": "Print Format",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
@ -808,7 +871,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-08-13 22:43:49.079330",
"modified": "2017-09-26 20:10:00.061780",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Alert",

View file

@ -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 = []

View file

@ -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",

View file

@ -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)

View file

@ -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"

View file

@ -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')

View file

@ -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

View file

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and contributors
# For license information, please see license.txt
import frappe
def run_webhooks(doc, method):
'''Run webhooks for this method'''
if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install:
return
if frappe.flags.webhooks_executed is None:
frappe.flags.webhooks_executed = {}
if frappe.flags.webhooks == None:
# load webhooks from cache
webhooks = frappe.cache().get_value('webhooks')
if webhooks==None:
# query webhooks
webhooks_list = frappe.get_all('Webhook',
fields=["name", "webhook_docevent", "webhook_doctype"])
# make webhooks map for cache
webhooks = {}
for w in webhooks_list:
webhooks.setdefault(w.webhook_doctype, []).append(w)
frappe.cache().set_value('webhooks', webhooks)
frappe.flags.webhooks = webhooks
# get webhooks for this doctype
webhooks_for_doc = frappe.flags.webhooks.get(doc.doctype, None)
if not webhooks_for_doc:
# no webhooks, quit
return
def _webhook_request(webhook):
if not webhook.name in frappe.flags.webhooks_executed.get(doc.name, []):
frappe.enqueue("frappe.integrations.doctype.webhook.webhook.enqueue_webhook", doc=doc, webhook=webhook)
# keep list of webhooks executed for this doc in this request
# so that we don't run the same webhook for the same document multiple times
# in one request
frappe.flags.webhooks_executed.setdefault(doc.name, []).append(webhook.name)
event_list = ["on_update", "after_insert", "on_submit", "on_cancel", "on_trash"]
if not doc.flags.in_insert:
# value change is not applicable in insert
event_list.append('on_change')
event_list.append('before_update_after_submit')
for webhook in webhooks_for_doc:
event = method if method in event_list else None
if event and webhook.webhook_docevent == event:
_webhook_request(webhook)

View file

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Webhook", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Webhook
() => frappe.tests.make('Webhook', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestWebhook(unittest.TestCase):
def test_validate_docevents(self):
doc = frappe.new_doc("Webhook")
doc.webhook_doctype = "User"
doc.webhook_docevent = "on_submit"
doc.request_url = "https://httpbin.org/post"
self.assertRaises(frappe.ValidationError, doc.save)
def test_validate_request_url(self):
doc = frappe.new_doc("Webhook")
doc.webhook_doctype = "User"
doc.webhook_docevent = "after_insert"
doc.request_url = "httpbin.org?post"
self.assertRaises(frappe.ValidationError, doc.save)

View file

@ -0,0 +1,46 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.webhook = {
set_fieldname_select: function(frm) {
var doc = frm.doc;
if (doc.webhook_doctype) {
frappe.model.with_doctype(doc.webhook_doctype, function() {
var fields = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) {
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
d.fieldtype === 'Table') {
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
}
else if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') {
return { label: d.label, value: d.fieldname };
}
else {
return null;
}
});
fields.unshift({"label":"Name (Doc Name)","value":"name"});
frappe.meta.get_docfield("Webhook Data", "fieldname", frm.doc.name).options = [""].concat(fields);
});
}
}
};
frappe.ui.form.on('Webhook', {
refresh: function(frm) {
frappe.webhook.set_fieldname_select(frm);
},
webhook_doctype: function(frm) {
frappe.webhook.set_fieldname_select(frm);
}
});
frappe.ui.form.on("Webhook Data", {
fieldname: function(frm, doctype, name) {
var doc = frappe.get_doc(doctype, name);
var df = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) {
return doc.fieldname == d.fieldname ? d : null;
})[0];
doc.key = df != undefined ? df.fieldname : "name";
frm.refresh_field("webhook_data");
}
});

View file

@ -0,0 +1,367 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-09-08 16:16:13.060641",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "sb_doc_events",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Doc Events",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "webhook_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 1,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_doc_events",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "webhook_docevent",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Doc Event",
"length": 0,
"no_copy": 0,
"options": "after_insert\non_update\non_submit\non_cancel\non_trash\non_update_after_submit\non_change",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb_webhook",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Webhook Request",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "request_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Request URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb_webhook_headers",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Webhook Headers",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "webhook_headers",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Headers",
"length": 0,
"no_copy": 0,
"options": "Webhook Header",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb_webhook_data",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Webhook Data",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "webhook_data",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Data",
"length": 0,
"no_copy": 0,
"options": "Webhook Data",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-09-14 13:16:53.974340",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Webhook",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View file

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json, requests
from frappe import _
from frappe.model.document import Document
from six.moves.urllib.parse import urlparse
from time import sleep
class Webhook(Document):
def autoname(self):
self.name = self.webhook_doctype + "-" + self.webhook_docevent
def validate(self):
self.validate_docevent()
self.validate_request_url()
self.validate_repeating_fields()
def on_update(self):
frappe.cache().delete_value('webhooks')
def validate_docevent(self):
if self.webhook_doctype:
is_submittable = frappe.get_value("DocType", self.webhook_doctype, "is_submittable")
if not is_submittable and self.webhook_docevent in ["on_submit", "on_cancel", "on_update_after_submit"]:
frappe.throw(_("DocType must be Submittable for the selected Doc Event"))
def validate_request_url(self):
try:
request_url = urlparse(self.request_url).netloc
if not request_url:
raise frappe.ValidationError
except Exception as e:
frappe.throw(_("Check Request URL"), exc=e)
def validate_repeating_fields(self):
"""Error when Same Field is entered multiple times in webhook_data"""
webhook_data = []
for entry in self.webhook_data:
webhook_data.append(entry.fieldname)
if len(webhook_data)!= len(set(webhook_data)):
frappe.throw(_("Same Field is entered more than once"))
def enqueue_webhook(doc, webhook):
webhook = frappe.get_doc("Webhook", webhook.get("name"))
headers = {}
data = {}
if webhook.webhook_headers:
for h in webhook.webhook_headers:
if h.get("key") and h.get("value"):
headers[h.get("key")] = h.get("value")
if webhook.webhook_data:
for w in webhook.webhook_data:
for k, v in doc.as_dict().items():
if k == w.fieldname:
data[w.key] = v
for i in range(3):
try:
r = requests.post(webhook.request_url, data=json.dumps(data), headers=headers, timeout=5)
r.raise_for_status()
frappe.logger().debug({"webhook_success":r.text})
break
except Exception as e:
frappe.logger().debug({"webhook_error":e, "try": i+1})
sleep(3*i + 1)
if i !=2:
continue
else:
raise e

View file

@ -0,0 +1,130 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-09-14 12:08:50.302810",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fieldname",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Fieldname",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_doc_data",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "key",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-09-14 13:16:58.252176",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Webhook Data",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class WebhookData(Document):
pass

View file

@ -0,0 +1,101 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-09-08 16:27:39.195379",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "key",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "value",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Value",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-09-08 16:28:20.025612",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Webhook Header",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class WebhookHeader(Document):
pass

View file

@ -15,17 +15,18 @@ import hashlib, json
from frappe.model import optional_fields
from frappe.utils.file_manager import save_url
from frappe.utils.global_search import update_global_search
from frappe.integrations.doctype.webhook import run_webhooks
# once_only validation
# methods
def get_doc(arg1, arg2=None):
def get_doc(*args, **kwargs):
"""returns a frappe.model.Document object.
:param arg1: Document dict or DocType name.
:param arg2: [optional] document name.
There are two ways to call `get_doc`
There are multiple ways to call `get_doc`
# will fetch the latest user object (with child table) from the database
user = get_doc("User", "test@example.com")
@ -38,23 +39,39 @@ def get_doc(arg1, arg2=None):
{"role": "System Manager"}
]
})
# create new object with keyword arguments
user = get_doc(doctype='User', email_id='test@example.com')
"""
if isinstance(arg1, BaseDocument):
return arg1
elif isinstance(arg1, string_types):
doctype = arg1
else:
doctype = arg1.get("doctype")
if args:
if isinstance(args[0], BaseDocument):
# already a document
return args[0]
elif isinstance(args[0], string_types):
doctype = args[0]
elif isinstance(args[0], dict):
# passed a dict
kwargs = args[0]
else:
raise ValueError('First non keyword argument must be a string or dict')
if kwargs:
if 'doctype' in kwargs:
doctype = kwargs['doctype']
else:
raise ValueError('"doctype" is a required key')
controller = get_controller(doctype)
if controller:
return controller(arg1, arg2)
return controller(*args, **kwargs)
raise ImportError(arg1)
raise ImportError(doctype)
class Document(BaseDocument):
"""All controllers inherit from `Document`."""
def __init__(self, arg1, arg2=None):
def __init__(self, *args, **kwargs):
"""Constructor.
:param arg1: DocType name as string or document **dict**
@ -67,29 +84,37 @@ class Document(BaseDocument):
self._default_new_docs = {}
self.flags = frappe._dict()
if arg1 and isinstance(arg1, string_types):
if not arg2:
if args and args[0] and isinstance(args[0], string_types):
# first arugment is doctype
if len(args)==1:
# single
self.doctype = self.name = arg1
self.doctype = self.name = args[0]
else:
self.doctype = arg1
if isinstance(arg2, dict):
self.doctype = args[0]
if isinstance(args[1], dict):
# filter
self.name = frappe.db.get_value(arg1, arg2, "name")
self.name = frappe.db.get_value(args[0], args[1], "name")
if self.name is None:
frappe.throw(_("{0} {1} not found").format(_(arg1), arg2), frappe.DoesNotExistError)
frappe.throw(_("{0} {1} not found").format(_(args[0]), args[1]),
frappe.DoesNotExistError)
else:
self.name = arg2
self.name = args[1]
self.load_from_db()
return
elif isinstance(arg1, dict):
super(Document, self).__init__(arg1)
if args and args[0] and isinstance(args[0], dict):
# first argument is a dict
kwargs = args[0]
if kwargs:
# init base document
super(Document, self).__init__(kwargs)
self.init_valid_columns()
else:
# incorrect arguments. let's not proceed.
raise frappe.DataError("Document({0}, {1})".format(arg1, arg2))
raise ValueError('Illegal arguments')
def reload(self):
"""Reload document from database"""
@ -335,13 +360,18 @@ class Document(BaseDocument):
self._doc_before_save = frappe.get_doc(self.doctype, self.name)
return self._doc_before_save
def set_new_name(self):
def set_new_name(self, force=False):
"""Calls `frappe.naming.se_new_name` for parent and child docs."""
if self.flags.name_set and not force:
return
set_new_name(self)
# set name for children
for d in self.get_all_children():
set_new_name(d)
self.flags.name_set = True
def get_title(self):
'''Get the document title based on title_field or `title` or `name`'''
return self.get(self.meta.get_title_field())
@ -625,7 +655,7 @@ class Document(BaseDocument):
name=self.name))
def _validate_links(self):
if self.flags.ignore_links:
if self.flags.ignore_links or self._action == "cancel":
return
invalid_links, cancelled_links = self.get_invalid_links()
@ -672,6 +702,7 @@ class Document(BaseDocument):
out = Document.hook(fn)(self, *args, **kwargs)
self.run_email_alerts(method)
run_webhooks(self, method)
return out
@ -998,7 +1029,7 @@ class Document(BaseDocument):
def get_signature(self):
"""Returns signature (hash) for private URL."""
return hashlib.sha224(get_datetime_str(self.creation)).hexdigest()
return hashlib.sha224(get_datetime_str(self.creation).encode()).hexdigest()
def get_liked_by(self):
liked_by = getattr(self, "_liked_by", None)

View file

@ -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:

View file

@ -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",

View file

@ -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;

View file

@ -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%;
}

View file

@ -1,3 +1,11 @@
.grid-report {
direction: ltr;
}
.chart_area{
direction: ltr;
}
.grid-report .show-zero{
direction: rtl ;
}

View file

@ -981,3 +981,7 @@ li.footer-child-item {
margin: 15px 0px;
max-width: 100%;
}
.blog-list-item {
padding-top: 30px;
padding-bottom: 30px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -85,7 +85,7 @@ frappe.assets = {
frappe.assets.executed_.push(path)
}
}
callback();
callback && callback();
},
// check if the asset exists in

View file

@ -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();
}

View file

@ -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>\

View file

@ -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 () {

View file

@ -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);
}

View file

@ -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;

View file

@ -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');
}
});

View file

@ -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 {

View file

@ -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) {

View file

@ -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"

View file

@ -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>

View file

@ -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;
}

View file

@ -0,0 +1,94 @@
frappe.ui.Capture = class
{
constructor (options = { })
{
this.options = Object.assign({}, frappe.ui.Capture.DEFAULT_OPTIONS, options);
this.dialog = new frappe.ui.Dialog();
this.template =
`
<div class="text-center">
<div class="img-thumbnail" style="border: none;">
<div id="frappe-capture"/>
</div>
</div>
<div id="frappe-capture-btn-toolbar" style="padding-top: 15px; padding-bottom: 15px;">
<div class="text-center">
<div id="frappe-capture-btn-toolbar-snap">
<a id="frappe-capture-btn-snap">
<i class="fa fa-fw fa-2x fa-circle-o"/>
</a>
</div>
<div class="btn-group" id="frappe-capture-btn-toolbar-knap">
<button class="btn btn-default" id="frappe-capture-btn-discard">
<i class="fa fa-fw fa-arrow-left"/>
</button>
<button class="btn btn-default" id="frappe-capture-btn-accept">
<i class="fa fa-fw fa-arrow-right"/>
</button>
</div>
</div>
</div>
`;
$(this.dialog.body).append(this.template);
this.$btnBarSnap = $(this.dialog.body).find('#frappe-capture-btn-toolbar-snap');
this.$btnBarKnap = $(this.dialog.body).find('#frappe-capture-btn-toolbar-knap');
this.$btnBarKnap.hide();
Webcam.set(this.options);
}
open ( )
{
this.dialog.show();
Webcam.attach('#frappe-capture');
}
freeze ( )
{
this.$btnBarSnap.hide();
this.$btnBarKnap.show();
Webcam.freeze();
}
unfreeze ( )
{
this.$btnBarSnap.show();
this.$btnBarKnap.hide();
Webcam.unfreeze();
}
click (callback)
{
$(this.dialog.body).find('#frappe-capture-btn-snap').click(() => {
this.freeze();
$(this.dialog.body).find('#frappe-capture-btn-discard').click(() => {
this.unfreeze();
});
$(this.dialog.body).find('#frappe-capture-btn-accept').click(() => {
Webcam.snap((data) => {
callback(data);
});
this.hide();
});
});
}
hide ( )
{
Webcam.reset();
$(this.dialog.$wrapper).remove();
}
};
frappe.ui.Capture.DEFAULT_OPTIONS =
{
width: 480, height: 320, flip_horiz: true
};

View file

@ -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);
},

View file

@ -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];

View file

@ -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;
}

View file

@ -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,

View file

@ -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>

View file

@ -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);
});
}
});

View file

@ -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) {

View file

@ -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();

View file

@ -0,0 +1,2 @@
/* jRumble v1.3 - http://jackrugile.com/jrumble - MIT License */
(function(f){f.fn.jrumble=function(g){var a=f.extend({x:2,y:2,rotation:1,speed:15,opacity:false,opacityMin:0.5},g);return this.each(function(){var b=f(this),h=a.x*2,i=a.y*2,k=a.rotation*2,g=a.speed===0?1:a.speed,m=a.opacity,n=a.opacityMin,l,j,o=function(){var e=Math.floor(Math.random()*(h+1))-h/2,a=Math.floor(Math.random()*(i+1))-i/2,c=Math.floor(Math.random()*(k+1))-k/2,d=m?Math.random()+n:1,e=e===0&&h!==0?Math.random()<0.5?1:-1:e,a=a===0&&i!==0?Math.random()<0.5?1:-1:a;b.css("display")==="inline"&&(l=true,b.css("display","inline-block"));b.css({position:"relative",left:e+"px",top:a+"px","-ms-filter":"progid:DXImageTransform.Microsoft.Alpha(Opacity="+d*100+")",filter:"alpha(opacity="+d*100+")","-moz-opacity":d,"-khtml-opacity":d,opacity:d,"-webkit-transform":"rotate("+c+"deg)","-moz-transform":"rotate("+c+"deg)","-ms-transform":"rotate("+c+"deg)","-o-transform":"rotate("+c+"deg)",transform:"rotate("+c+"deg)"})},p={left:0,top:0,"-ms-filter":"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)",filter:"alpha(opacity=100)","-moz-opacity":1,"-khtml-opacity":1,opacity:1,"-webkit-transform":"rotate(0deg)","-moz-transform":"rotate(0deg)","-ms-transform":"rotate(0deg)","-o-transform":"rotate(0deg)",transform:"rotate(0deg)"};b.bind({startRumble:function(a){a.stopPropagation();clearInterval(j);j=setInterval(o,g)},stopRumble:function(a){a.stopPropagation();clearInterval(j);l&&b.css("display","inline");b.css(p)}})})}})(jQuery);

2
frappe/public/js/lib/webcam.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -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 {

View file

@ -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;

View file

@ -695,3 +695,8 @@ li.footer-child-item {
margin: 15px 0px;
max-width: 100%;
}
.blog-list-item {
padding-top: 30px;
padding-bottom: 30px;
}

View file

@ -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")

View file

@ -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)

View file

@ -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>

View file

@ -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;">

View file

@ -13,6 +13,7 @@ import frappe.utils.scheduler
import cProfile, pstats
from six import StringIO
from six.moves import reload_module
from frappe.model.naming import revert_series_if_last
unittest_runner = unittest.TextTestRunner
@ -223,8 +224,6 @@ def make_test_records(doctype, verbose=0, force=False):
continue
if not options in frappe.local.test_objects:
if options in frappe.local.test_objects:
print("No test records or circular reference for {0}".format(options))
frappe.local.test_objects[options] = []
make_test_records(options, verbose, force)
make_test_records_for_doctype(options, verbose, force)
@ -287,6 +286,11 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
'''Make test objects from given list of `test_records` or from `test_records.json`'''
records = []
def revert_naming(d):
if getattr(d, 'naming_series', None):
revert_series_if_last(d.naming_series, d.name)
if test_records is None:
test_records = frappe.get_test_records(doctype)
@ -296,17 +300,20 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
d = frappe.copy_doc(doc)
if doc.get('name'):
d.name = doc.get('name')
if frappe.local.test_objects.get(d.doctype) and not reset:
# do not create test records, if already exists
return []
if d.meta.get_field("naming_series"):
if not d.naming_series:
d.naming_series = "_T-" + d.doctype + "-"
if doc.get('name'):
d.name = doc.get('name')
else:
d.set_new_name()
if frappe.db.exists(d.doctype, d.name) and not reset:
frappe.db.rollback()
# do not create test records, if already exists
continue
# submit if docstatus is set to 1 for test record
docstatus = d.docstatus
@ -320,18 +327,17 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
d.submit()
except frappe.NameError:
pass
revert_naming(d)
except Exception as e:
if d.flags.ignore_these_exceptions_in_test and e.__class__ in d.flags.ignore_these_exceptions_in_test:
pass
revert_naming(d)
else:
raise
records.append(d.name)
frappe.db.commit()
frappe.db.commit()
return records

View file

@ -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')

View file

@ -555,7 +555,7 @@ def write_csv_file(path, app_messages, lang_dict):
:param app_messages: Translatable strings for this app.
:param lang_dict: Full translated dict.
"""
app_messages.sort(lambda x,y: cmp(x[1], y[1]))
app_messages.sort(key = lambda x: x[1])
from csv import writer
with open(path, 'wb') as msgfile:
w = writer(msgfile, lineterminator='\n')
@ -684,7 +684,7 @@ def deduplicate_messages(messages):
op = operator.itemgetter(1)
messages = sorted(messages, key=op)
for k, g in itertools.groupby(messages, op):
ret.append(g.next())
ret.append(next(g))
return ret
def get_bench_dir():

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more