Selaa lähdekoodia

Merge branch 'staging'

version-14
Nabin Hait 7 vuotta sitten
vanhempi
commit
311dff662a
100 muutettua tiedostoa jossa 2603 lisäystä ja 853 poistoa
  1. +1
    -0
      .eslintrc
  2. BIN
      .github/logo.png
  3. +2
    -0
      .gitignore
  4. +3
    -0
      .vscode/settings.json
  5. +21
    -0
      LICENSE
  6. +2
    -0
      Makefile
  7. +28
    -5
      README.md
  8. +3
    -3
      frappe/__init__.py
  9. +3
    -4
      frappe/build.js
  10. +25
    -6
      frappe/build.py
  11. +3
    -2
      frappe/commands/utils.py
  12. +6
    -0
      frappe/config/integrations.py
  13. +2
    -1
      frappe/contacts/doctype/address/address.json
  14. +10
    -7
      frappe/core/doctype/communication/communication.py
  15. +0
    -3
      frappe/core/doctype/communication/email.py
  16. +302
    -242
      frappe/core/doctype/docshare/docshare.json
  17. +13
    -0
      frappe/core/doctype/doctype/doctype.py
  18. +37
    -4
      frappe/core/doctype/doctype/test_doctype.py
  19. +5
    -3
      frappe/core/doctype/domain_settings/domain_settings.js
  20. +1
    -1
      frappe/core/doctype/language/language.py
  21. +0
    -5
      frappe/core/doctype/user/test_user.py
  22. +1
    -1
      frappe/core/page/data_import_tool/data_import_tool.py
  23. +1
    -1
      frappe/core/page/data_import_tool/exporter.py
  24. +33
    -33
      frappe/core/page/data_import_tool/test_exporter_fixtures.py
  25. +117
    -3
      frappe/core/page/desktop/desktop.js
  26. +2
    -2
      frappe/custom/doctype/custom_field/custom_field.py
  27. +1
    -0
      frappe/data/Framework.sql
  28. +13
    -1
      frappe/defaults.py
  29. +14
    -1
      frappe/desk/doctype/desktop_icon/desktop_icon.py
  30. +23
    -0
      frappe/desk/doctype/todo/test_todo.js
  31. +9
    -9
      frappe/desk/doctype/todo/todo.json
  32. +1
    -1
      frappe/desk/form/linked_with.py
  33. +33
    -23
      frappe/desk/page/setup_wizard/setup_wizard.js
  34. +1
    -2
      frappe/desk/reportview.py
  35. BIN
      frappe/docs/assets/img/webhook.png
  36. +103
    -0
      frappe/docs/user/en/guides/integration/webhooks.md
  37. +0
    -1
      frappe/email/doctype/email_account/email_account.py
  38. +24
    -8
      frappe/email/doctype/email_alert/email_alert.js
  39. +74
    -11
      frappe/email/doctype/email_alert/email_alert.json
  40. +1
    -1
      frappe/email/doctype/email_alert/email_alert.py
  41. +3
    -3
      frappe/email/doctype/email_queue/email_queue.json
  42. +2
    -2
      frappe/email/queue.py
  43. +1
    -1
      frappe/hooks.py
  44. +1
    -1
      frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
  45. +2
    -2
      frappe/integrations/doctype/stripe_settings/stripe_settings.py
  46. +57
    -0
      frappe/integrations/doctype/webhook/__init__.py
  47. +23
    -0
      frappe/integrations/doctype/webhook/test_webhook.js
  48. +21
    -0
      frappe/integrations/doctype/webhook/test_webhook.py
  49. +46
    -0
      frappe/integrations/doctype/webhook/webhook.js
  50. +367
    -0
      frappe/integrations/doctype/webhook/webhook.json
  51. +73
    -0
      frappe/integrations/doctype/webhook/webhook.py
  52. +0
    -0
      frappe/integrations/doctype/webhook_data/__init__.py
  53. +130
    -0
      frappe/integrations/doctype/webhook_data/webhook_data.json
  54. +10
    -0
      frappe/integrations/doctype/webhook_data/webhook_data.py
  55. +0
    -0
      frappe/integrations/doctype/webhook_header/__init__.py
  56. +101
    -0
      frappe/integrations/doctype/webhook_header/webhook_header.json
  57. +10
    -0
      frappe/integrations/doctype/webhook_header/webhook_header.py
  58. +56
    -25
      frappe/model/document.py
  59. +2
    -0
      frappe/patches/v6_20x/remove_roles_from_website_user.py
  60. +8
    -1
      frappe/public/build.json
  61. +5
    -2
      frappe/public/css/desk.css
  62. +16
    -0
      frappe/public/css/list.css
  63. +8
    -0
      frappe/public/css/report-rtl.css
  64. +4
    -0
      frappe/public/css/website.css
  65. BIN
      frappe/public/images/default-avatar.png
  66. +1
    -1
      frappe/public/js/frappe/assets.js
  67. +2
    -2
      frappe/public/js/frappe/desk.js
  68. +1
    -1
      frappe/public/js/frappe/form/controls/check.js
  69. +4
    -2
      frappe/public/js/frappe/form/controls/color.js
  70. +44
    -5
      frappe/public/js/frappe/form/controls/text_editor.js
  71. +3
    -2
      frappe/public/js/frappe/form/grid.js
  72. +9
    -1
      frappe/public/js/frappe/form/grid_row.js
  73. +6
    -1
      frappe/public/js/frappe/form/layout.js
  74. +2
    -1
      frappe/public/js/frappe/form/share.js
  75. +3
    -1
      frappe/public/js/frappe/form/templates/grid_body.html
  76. +15
    -3
      frappe/public/js/frappe/form/templates/set_sharing.html
  77. +1
    -1
      frappe/public/js/frappe/socketio_client.js
  78. +94
    -0
      frappe/public/js/frappe/ui/capture.js
  79. +1
    -1
      frappe/public/js/frappe/ui/toolbar/notifications.js
  80. +1
    -1
      frappe/public/js/frappe/ui/toolbar/search_utils.js
  81. +8
    -3
      frappe/public/js/frappe/upload.js
  82. +8
    -4
      frappe/public/js/frappe/views/image/image_view.js
  83. +4
    -4
      frappe/public/js/frappe/views/image/image_view_item_row.html
  84. +10
    -2
      frappe/public/js/frappe/views/treeview.js
  85. +1
    -1
      frappe/public/js/legacy/clientscriptAPI.js
  86. +1
    -1
      frappe/public/js/legacy/form.js
  87. +2
    -0
      frappe/public/js/lib/jquery.jrumble.min.js
  88. +2
    -0
      frappe/public/js/lib/webcam.min.js
  89. +7
    -3
      frappe/public/less/desk.less
  90. +20
    -0
      frappe/public/less/list.less
  91. +5
    -0
      frappe/public/less/website.less
  92. +2
    -1
      frappe/sessions.py
  93. +19
    -1
      frappe/share.py
  94. +4
    -2
      frappe/templates/includes/blog/blogger.html
  95. +1
    -1
      frappe/templates/includes/list/list.html
  96. +18
    -12
      frappe/test_runner.py
  97. +1
    -0
      frappe/tests/test_domainification.py
  98. +2
    -2
      frappe/translate.py
  99. +218
    -186
      frappe/translations/af.csv
  100. +219
    -186
      frappe/translations/am.csv

+ 1
- 0
.eslintrc Näytä tiedosto

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


BIN
.github/logo.png Näytä tiedosto

Before After
Leveys: 615  |  Korkeus: 530  |  Koko: 12 KiB

+ 2
- 0
.gitignore Näytä tiedosto

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

+ 3
- 0
.vscode/settings.json Näytä tiedosto

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

+ 21
- 0
LICENSE Näytä tiedosto

@@ -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
- 0
Makefile Näytä tiedosto

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

+ 28
- 5
README.md Näytä tiedosto

@@ -1,9 +1,33 @@
## Frappé Framework

[![Build Status](https://travis-ci.org/frappe/frappe.png)](https://travis-ci.org/frappe/frappe)
<div align="center">
<img src=".github/logo.png" height="256">
<h1>
<a href="https://frappe.io">
frappé
</a>
</h1>
<h3>
a web framework with <a href="https://www.youtube.com/watch?v=LOjk3m0wTwg">"batteries included"
</h3>
<h5>
it's pronounced - <em>fra-pay</em>
</h5>
</div>

<div align="center">
<a href="https://travis-ci.org/frappe/frappe">
<img src="https://img.shields.io/travis/frappe/frappe.svg?style=flat-square">
</a>
<a href='https://frappe.io/docs'>
<img src='https://img.shields.io/badge/docs-📖-7575FF.svg?style=flat-square'/>
</a>
</div>


Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com) Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com)


### Table of Contents
* [Installation](#installation)
* [License](#license)

### Installation ### Installation


[Install via Frappé Bench](https://github.com/frappe/bench) [Install via Frappé Bench](https://github.com/frappe/bench)
@@ -20,5 +44,4 @@ For details and documentation, see the website
[https://frappe.io](https://frappe.io) [https://frappe.io](https://frappe.io)


### License ### License

MIT License
This repository has been released under the [MIT License](LICENSE).

+ 3
- 3
frappe/__init__.py Näytä tiedosto

@@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json
from .exceptions import * from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template


__version__ = '9.0.10'
__version__ = '9.1.0'
__title__ = "Frappe Framework" __title__ = "Frappe Framework"


local = Local() local = Local()
@@ -602,7 +602,7 @@ def set_value(doctype, docname, fieldname, value=None):
import frappe.client import frappe.client
return frappe.client.set_value(doctype, docname, fieldname, value) return frappe.client.set_value(doctype, docname, fieldname, value)


def get_doc(arg1, arg2=None):
def get_doc(*args, **kwargs):
"""Return a `frappe.model.document.Document` object of the given type and name. """Return a `frappe.model.document.Document` object of the given type and name.


:param arg1: DocType name as string **or** document JSON. :param arg1: DocType name as string **or** document JSON.
@@ -619,7 +619,7 @@ def get_doc(arg1, arg2=None):


""" """
import frappe.model.document import frappe.model.document
return frappe.model.document.get_doc(arg1, arg2)
return frappe.model.document.get_doc(*args, **kwargs)


def get_last_doc(doctype): def get_last_doc(doctype):
"""Get last created document of this type.""" """Get last created document of this type."""


+ 3
- 4
frappe/build.js Näytä tiedosto

@@ -123,10 +123,9 @@ function pack(output_path, inputs, minify) {
} }


function babelify(content, path, minify) { function babelify(content, path, minify) {
let presets = ['es2015', 'es2016'];
// if(minify) {
// presets.push('babili'); // new babel minifier
// }
let presets = ['env'];
// Minification doesn't work when loading Frappe Desk
// Avoid for now, trace the error and come back.
try { try {
return babel.transform(content, { return babel.transform(content, {
presets: presets, presets: presets,


+ 25
- 6
frappe/build.py Näytä tiedosto

@@ -4,6 +4,7 @@
from __future__ import unicode_literals, print_function from __future__ import unicode_literals, print_function
from frappe.utils.minify import JavascriptMinify from frappe.utils.minify import JavascriptMinify
import subprocess import subprocess
import warnings


from six import iteritems, text_type from six import iteritems, text_type


@@ -25,12 +26,12 @@ def setup():
except ImportError: pass except ImportError: pass
app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules] app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules]


def bundle(no_compress, make_copy=False, verbose=False):
def bundle(no_compress, make_copy=False, restore=False, verbose=False):
"""concat / minify js files""" """concat / minify js files"""
# build js files # build js files
setup() setup()


make_asset_dirs(make_copy=make_copy)
make_asset_dirs(make_copy=make_copy, restore=restore)


# new nodejs build system # new nodejs build system
command = 'node --use_strict ../apps/frappe/frappe/build.js --build' command = 'node --use_strict ../apps/frappe/frappe/build.js --build'
@@ -60,7 +61,8 @@ def watch(no_compress):


# time.sleep(3) # time.sleep(3)


def make_asset_dirs(make_copy=False):
def make_asset_dirs(make_copy=False, restore=False):
# don't even think of making assets_path absolute - rm -rf ahead.
assets_path = os.path.join(frappe.local.sites_path, "assets") assets_path = os.path.join(frappe.local.sites_path, "assets")
for dir_path in [ for dir_path in [
os.path.join(assets_path, 'js'), os.path.join(assets_path, 'js'),
@@ -80,11 +82,28 @@ def make_asset_dirs(make_copy=False):


for source, target in symlinks: for source, target in symlinks:
source = os.path.abspath(source) source = os.path.abspath(source)
if not os.path.exists(target) and os.path.exists(source):
if make_copy:
shutil.copytree(source, target)
if os.path.exists(source):
if restore:
if os.path.exists(target):
if os.path.islink(target):
os.unlink(target)
else:
shutil.rmtree(target)
shutil.copytree(source, target)
elif make_copy:
if os.path.exists(target):
warnings.warn('Target {target} already exists.'.format(target = target))
else:
shutil.copytree(source, target)
else: else:
if os.path.exists(target):
if os.path.islink(target):
os.unlink(target)
else:
shutil.rmtree(target)
os.symlink(source, target) os.symlink(source, target)
else:
warnings.warn('Source {source} does not exists.'.format(source = source))


def build(no_compress=False, verbose=False): def build(no_compress=False, verbose=False):
assets_path = os.path.join(frappe.local.sites_path, "assets") assets_path = os.path.join(frappe.local.sites_path, "assets")


+ 3
- 2
frappe/commands/utils.py Näytä tiedosto

@@ -8,13 +8,14 @@ from frappe.utils import update_progress_bar


@click.command('build') @click.command('build')
@click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking') @click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking')
@click.option('--restore', is_flag=True, default=False, help='Copy the files instead of symlinking with force')
@click.option('--verbose', is_flag=True, default=False, help='Verbose') @click.option('--verbose', is_flag=True, default=False, help='Verbose')
def build(make_copy=False, verbose=False):
def build(make_copy=False, restore = False, verbose=False):
"Minify + concatenate JS and CSS files, build translations" "Minify + concatenate JS and CSS files, build translations"
import frappe.build import frappe.build
import frappe import frappe
frappe.init('') frappe.init('')
frappe.build.bundle(False, make_copy=make_copy, verbose=verbose)
frappe.build.bundle(False, make_copy=make_copy, restore = restore, verbose=verbose)


@click.command('watch') @click.command('watch')
def watch(): def watch():


+ 6
- 0
frappe/config/integrations.py Näytä tiedosto

@@ -72,6 +72,12 @@ def get_data():
"name": "GSuite Templates", "name": "GSuite Templates",
"description": _("Google GSuite Templates to integration with DocTypes"), "description": _("Google GSuite Templates to integration with DocTypes"),
}, },
{
"type": "doctype",
"name": "Webhook",
"description": _("Webhooks calling API requests into web apps"),
}

] ]
} }
] ]

+ 2
- 1
frappe/contacts/doctype/address/address.json Näytä tiedosto

@@ -353,7 +353,8 @@
"label": "Email Address", "label": "Email Address",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0,
"options": "Email",
"permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,


+ 10
- 7
frappe/core/doctype/communication/communication.py Näytä tiedosto

@@ -25,7 +25,7 @@ class Communication(Document):
"""create email flag queue""" """create email flag queue"""
if self.communication_type == "Communication" and self.communication_medium == "Email" \ if self.communication_type == "Communication" and self.communication_medium == "Email" \
and self.sent_or_received == "Received" and self.uid and self.uid != -1: and self.sent_or_received == "Received" and self.uid and self.uid != -1:
email_flag_queue = frappe.db.get_value("Email Flag Queue", { email_flag_queue = frappe.db.get_value("Email Flag Queue", {
"communication": self.name, "communication": self.name,
"is_completed": 0}) "is_completed": 0})
@@ -69,7 +69,7 @@ class Communication(Document):
def after_insert(self): def after_insert(self):
if not (self.reference_doctype and self.reference_name): if not (self.reference_doctype and self.reference_name):
return return
if self.reference_doctype == "Communication" and self.sent_or_received == "Sent": if self.reference_doctype == "Communication" and self.sent_or_received == "Sent":
frappe.db.set_value("Communication", self.reference_name, "status", "Replied") frappe.db.set_value("Communication", self.reference_name, "status", "Replied")


@@ -94,9 +94,10 @@ class Communication(Document):


def on_update(self): def on_update(self):
"""Update parent status as `Open` or `Replied`.""" """Update parent status as `Open` or `Replied`."""
update_parent_status(self)
update_comment_in_doc(self)
self.bot_reply()
if self.comment_type != 'Updated':
update_parent_status(self)
update_comment_in_doc(self)
self.bot_reply()


def on_trash(self): def on_trash(self):
if (not self.flags.ignore_permissions if (not self.flags.ignore_permissions
@@ -264,7 +265,7 @@ def has_permission(doc, ptype, user):
if (doc.reference_doctype == "Communication" and doc.reference_name == doc.name) \ if (doc.reference_doctype == "Communication" and doc.reference_name == doc.name) \
or (doc.timeline_doctype == "Communication" and doc.timeline_name == doc.name): or (doc.timeline_doctype == "Communication" and doc.timeline_name == doc.name):
return return
if doc.reference_doctype and doc.reference_name: if doc.reference_doctype and doc.reference_name:
if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name): if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name):
return True return True
@@ -277,7 +278,9 @@ def get_permission_query_conditions_for_communication(user):


if not user: user = frappe.session.user if not user: user = frappe.session.user


if "Super Email User" in frappe.get_roles(user):
roles = frappe.get_roles(user)

if "Super Email User" in roles or "System Manager" in roles:
return None return None
else: else:
accounts = frappe.get_all("User Email", filters={ "parent": user }, accounts = frappe.get_all("User Email", filters={ "parent": user },


+ 0
- 3
frappe/core/doctype/communication/email.py Näytä tiedosto

@@ -166,9 +166,6 @@ def _notify(doc, print_html=None, print_format=None, attachments=None,


def update_parent_status(doc): def update_parent_status(doc):
"""Update status of parent document based on who is replying.""" """Update status of parent document based on who is replying."""
if doc.communication_type != "Communication":
return

parent = doc.get_parent_doc() parent = doc.get_parent_doc()
if not parent: if not parent:
return return


+ 302
- 242
frappe/core/doctype/docshare/docshare.json Näytä tiedosto

@@ -1,264 +1,324 @@
{ {
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "hash",
"beta": 0,
"creation": "2015-02-04 04:33:36.330477",
"custom": 0,
"description": "Internal record of document shares",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 0,
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "hash",
"beta": 0,
"creation": "2015-02-04 04:33:36.330477",
"custom": 0,
"description": "Internal record of document shares",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 0,
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0 "unique": 0
},
},
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "share_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "share_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"unique": 0 "unique": 0
},
},
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "share_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Document Name",
"length": 0,
"no_copy": 0,
"options": "share_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "share_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Document Name",
"length": 0,
"no_copy": 0,
"options": "share_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"unique": 0 "unique": 0
},
},
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "read",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Read",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "read",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Read",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
},
},
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "write",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Write",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "write",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Write",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
},
},
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "share",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Share",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "share",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Share",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
},
},
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "everyone",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Everyone",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "everyone",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Everyone",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "notify_by_email",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notify by email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "notify_by_email",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notify by email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
} }
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-12-29 14:40:40.284335",
"modified_by": "Administrator",
"module": "Core",
"name": "DocShare",
"name_case": "",
"owner": "Administrator",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-09-15 15:58:34.126438",
"modified_by": "Administrator",
"module": "Core",
"name": "DocShare",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 0,
"export": 1,
"if_owner": 0,
"import": 1,
"is_custom": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 0,
"export": 1,
"if_owner": 0,
"import": 1,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
],
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
],
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0 "track_seen": 0
}
}

+ 13
- 0
frappe/core/doctype/doctype/doctype.py Näytä tiedosto

@@ -595,6 +595,15 @@ def validate_fields(meta):
frappe.throw(_("Sort field {0} must be a valid fieldname").format(fieldname), frappe.throw(_("Sort field {0} must be a valid fieldname").format(fieldname),
InvalidFieldNameError) InvalidFieldNameError)


def check_illegal_depends_on_conditions(docfield):
''' assignment operation should not be allowed in the depends on condition.'''
depends_on_fields = ["depends_on", "collapsible_depends_on"]
for field in depends_on_fields:
depends_on = docfield.get(field, None)
if depends_on and ("=" in depends_on) and \
re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", depends_on):
frappe.throw(_("Invalid {0} condition").format(frappe.unscrub(field)), frappe.ValidationError)

fields = meta.get("fields") fields = meta.get("fields")
fieldname_list = [d.fieldname for d in fields] fieldname_list = [d.fieldname for d in fields]


@@ -620,6 +629,7 @@ def validate_fields(meta):
check_in_global_search(d) check_in_global_search(d)
check_illegal_default(d) check_illegal_default(d)
check_unique_and_text(d) check_unique_and_text(d)
check_illegal_depends_on_conditions(d)


check_fold(fields) check_fold(fields)
check_search_fields(meta, fields) check_search_fields(meta, fields)
@@ -753,6 +763,9 @@ def validate_permissions(doctype, for_remove=False):
def make_module_and_roles(doc, perm_fieldname="permissions"): def make_module_and_roles(doc, perm_fieldname="permissions"):
"""Make `Module Def` and `Role` records if already not made. Called while installing.""" """Make `Module Def` and `Role` records if already not made. Called while installing."""
try: try:
if doc.restrict_to_domain and not frappe.db.exists('Domain', doc.restrict_to_domain):
frappe.get_doc(dict(doctype='Domain', domain=doc.restrict_to_domain)).insert()

if not frappe.db.exists("Module Def", doc.module): if not frappe.db.exists("Module Def", doc.module):
m = frappe.get_doc({"doctype": "Module Def", "module_name": doc.module}) m = frappe.get_doc({"doctype": "Module Def", "module_name": doc.module})
m.app_name = frappe.local.module_app[frappe.scrub(doc.module)] m.app_name = frappe.local.module_app[frappe.scrub(doc.module)]


+ 37
- 4
frappe/core/doctype/doctype/test_doctype.py Näytä tiedosto

@@ -10,13 +10,22 @@ import unittest




class TestDocType(unittest.TestCase): class TestDocType(unittest.TestCase):
def new_doctype(self, name, unique=0):
def new_doctype(self, name, unique=0, depends_on=''):
return frappe.get_doc({ return frappe.get_doc({
"doctype": "DocType", "doctype": "DocType",
"module": "Core", "module": "Core",
"custom": 1, "custom": 1,
"fields": [{"label": "Some Field", "fieldname": "some_fieldname", "fieldtype": "Data", "unique": unique}],
"permissions": [{"role": "System Manager", "read": 1}],
"fields": [{
"label": "Some Field",
"fieldname": "some_fieldname",
"fieldtype": "Data",
"unique": unique,
"depends_on": depends_on,
}],
"permissions": [{
"role": "System Manager",
"read": 1
}],
"name": name "name": name
}) })


@@ -71,4 +80,28 @@ class TestDocType(unittest.TestCase):
field.fieldtype = "HTML" field.fieldtype = "HTML"
field.label = "Some HTML Field" field.label = "Some HTML Field"
doc.search_fields = "some_fieldname,some_html_field" doc.search_fields = "some_fieldname,some_html_field"
self.assertRaises(frappe.ValidationError, doc.save)
self.assertRaises(frappe.ValidationError, doc.save)

def test_depends_on_fields(self):
doc = self.new_doctype("Test Depends On", depends_on="eval:doc.__islocal == 0")
doc.insert()

# check if the assignment operation is allowed in depends_on
field = doc.fields[0]
field.depends_on = "eval:doc.__islocal = 0"
self.assertRaises(frappe.ValidationError, doc.save)

def test_all_depends_on_fields_conditions(self):
import re

docfields = frappe.get_all("DocField", or_filters={
"ifnull(depends_on, '')": ("!=", ''),
"ifnull(collapsible_depends_on, '')": ("!=", '')
}, fields=["parent", "depends_on", "collapsible_depends_on", "fieldname", "fieldtype"])

pattern = """[\w\.:_]+\s*={1}\s*[\w\.@'"]+"""
for field in docfields:
for depends_on in ["depends_on", "collapsible_depends_on"]:
condition = field.get(depends_on)
if condition:
self.assertFalse(re.match(pattern, condition))

+ 5
- 3
frappe/core/doctype/domain_settings/domain_settings.js Näytä tiedosto

@@ -42,9 +42,11 @@ frappe.DomainsEditor = frappe.CheckboxEditor.extend({


get_template: function() { get_template: function() {
return ` return `
<div class="user-role" data-domain="{{item}}">
<input type="checkbox" style="margin-top:0px;">
{{__(item)}}
<div class="checkbox" data-domain="{{item}}">
<label>
<input type="checkbox">
<span class="label-area small">{{ __(item) }}</span>
</label>
</div> </div>
`; `;
}, },


+ 1
- 1
frappe/core/doctype/language/language.py Näytä tiedosto

@@ -14,7 +14,7 @@ def export_languages_json():
languages = frappe.db.get_all('Language', fields=['name', 'language_name']) languages = frappe.db.get_all('Language', fields=['name', 'language_name'])
languages = [{'name': d.language_name, 'code': d.name} for d in languages] languages = [{'name': d.language_name, 'code': d.name} for d in languages]


languages.sort(lambda a,b: 1 if a['code'] > b['code'] else -1)
languages.sort(key = lambda a: a['code'])


with open(frappe.get_app_path('frappe', 'geo', 'languages.json'), 'w') as f: with open(frappe.get_app_path('frappe', 'geo', 'languages.json'), 'w') as f:
f.write(frappe.as_json(languages)) f.write(frappe.as_json(languages))


+ 0
- 5
frappe/core/doctype/user/test_user.py Näytä tiedosto

@@ -162,18 +162,13 @@ class TestUser(unittest.TestCase):
# from frappe.frappeclient import FrappeClient # from frappe.frappeclient import FrappeClient
# update_site_config('deny_multiple_sessions', 0) # update_site_config('deny_multiple_sessions', 0)
# #
# print 'conn1'
# conn1 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) # conn1 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False)
# test_request(conn1) # test_request(conn1)
# #
# print 'conn2'
# conn2 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) # conn2 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False)
# test_request(conn2) # test_request(conn2)
# #
# update_site_config('deny_multiple_sessions', 1) # update_site_config('deny_multiple_sessions', 1)
#
# print 'conn3'
#
# conn3 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) # conn3 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False)
# test_request(conn3) # test_request(conn3)
# #


+ 1
- 1
frappe/core/page/data_import_tool/data_import_tool.py Näytä tiedosto

@@ -36,7 +36,7 @@ def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False,


def export_csv(doctype, path): def export_csv(doctype, path):
from frappe.core.page.data_import_tool.exporter import get_template from frappe.core.page.data_import_tool.exporter import get_template
with open(path, "w") as csvfile:
with open(path, "wb") as csvfile:
get_template(doctype=doctype, all_doctypes="Yes", with_data="Yes") get_template(doctype=doctype, all_doctypes="Yes", with_data="Yes")
csvfile.write(frappe.response.result.encode("utf-8")) csvfile.write(frappe.response.result.encode("utf-8"))




+ 1
- 1
frappe/core/page/data_import_tool/exporter.py Näytä tiedosto

@@ -81,7 +81,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
if field and ((select_columns and f[0] in select_columns[dt]) or not select_columns): if field and ((select_columns and f[0] in select_columns[dt]) or not select_columns):
tablecolumns.append(field) tablecolumns.append(field)


tablecolumns.sort(lambda a, b: int(a.idx - b.idx))
tablecolumns.sort(key = lambda a: int(a.idx))


_column_start_end = frappe._dict(start=0) _column_start_end = frappe._dict(start=0)




+ 33
- 33
frappe/core/page/data_import_tool/test_exporter_fixtures.py Näytä tiedosto

@@ -16,7 +16,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_simple(self): def test_Custom_Script_fixture_simple(self):
fixture = "Custom Script" fixture = "Custom Script"
path = frappe.scrub(fixture) + "_original_style.csv" path = frappe.scrub(fixture) + "_original_style.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -24,7 +24,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_simple_name_equal_default(self): def test_Custom_Script_fixture_simple_name_equal_default(self):
fixture = ["Custom Script", {"name":["Item-Client"]}] fixture = ["Custom Script", {"name":["Item-Client"]}]
path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -32,7 +32,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_simple_name_equal(self): def test_Custom_Script_fixture_simple_name_equal(self):
fixture = ["Custom Script", {"name":["Item-Client"],"op":"="}] fixture = ["Custom Script", {"name":["Item-Client"],"op":"="}]
path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv" path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -40,7 +40,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_simple_name_not_equal(self): def test_Custom_Script_fixture_simple_name_not_equal(self):
fixture = ["Custom Script", {"name":["Item-Client"],"op":"!="}] fixture = ["Custom Script", {"name":["Item-Client"],"op":"!="}]
path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -49,7 +49,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_simple_name_at_least_equal(self): def test_Custom_Script_fixture_simple_name_at_least_equal(self):
fixture = ["Custom Script", {"name":"Item-Cli"}] fixture = ["Custom Script", {"name":"Item-Cli"}]
path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -57,7 +57,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_multi_name_equal(self): def test_Custom_Script_fixture_multi_name_equal(self):
fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"="}] fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"="}]
path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv" path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -65,7 +65,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_multi_name_not_equal(self): def test_Custom_Script_fixture_multi_name_not_equal(self):
fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"!="}] fixture = ["Custom Script", {"name":["Item-Client", "Customer-Client"],"op":"!="}]
path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -73,7 +73,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_empty_object(self): def test_Custom_Script_fixture_empty_object(self):
fixture = ["Custom Script", {}] fixture = ["Custom Script", {}]
path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -81,7 +81,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_just_list(self): def test_Custom_Script_fixture_just_list(self):
fixture = ["Custom Script"] fixture = ["Custom Script"]
path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -90,7 +90,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_rex_no_flags(self): def test_Custom_Script_fixture_rex_no_flags(self):
fixture = ["Custom Script", {"name":r"^[i|A]"}] fixture = ["Custom Script", {"name":r"^[i|A]"}]
path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv" path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -98,7 +98,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Script_fixture_rex_with_flags(self): def test_Custom_Script_fixture_rex_with_flags(self):
fixture = ["Custom Script", {"name":r"^[i|A]", "flags":"L,M"}] fixture = ["Custom Script", {"name":r"^[i|A]", "flags":"L,M"}]
path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv" path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -107,7 +107,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_simple(self): def test_Custom_Field_fixture_simple(self):
fixture = "Custom Field" fixture = "Custom Field"
path = frappe.scrub(fixture) + "_original_style.csv" path = frappe.scrub(fixture) + "_original_style.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -115,7 +115,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_simple_name_equal_default(self): def test_Custom_Field_fixture_simple_name_equal_default(self):
fixture = ["Custom Field", {"name":["Item-vat"]}] fixture = ["Custom Field", {"name":["Item-vat"]}]
path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" path = frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -123,7 +123,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_simple_name_equal(self): def test_Custom_Field_fixture_simple_name_equal(self):
fixture = ["Custom Field", {"name":["Item-vat"],"op":"="}] fixture = ["Custom Field", {"name":["Item-vat"],"op":"="}]
path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv" path = frappe.scrub(fixture[0]) + "_simple_name_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -131,7 +131,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_simple_name_not_equal(self): def test_Custom_Field_fixture_simple_name_not_equal(self):
fixture = ["Custom Field", {"name":["Item-vat"],"op":"!="}] fixture = ["Custom Field", {"name":["Item-vat"],"op":"!="}]
path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" path = frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -140,7 +140,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_simple_name_at_least_equal(self): def test_Custom_Field_fixture_simple_name_at_least_equal(self):
fixture = ["Custom Field", {"name":"Item-va"}] fixture = ["Custom Field", {"name":"Item-va"}]
path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" path = frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -148,7 +148,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_multi_name_equal(self): def test_Custom_Field_fixture_multi_name_equal(self):
fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"="}] fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"="}]
path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv" path = frappe.scrub(fixture[0]) + "_multi_name_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -156,7 +156,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_multi_name_not_equal(self): def test_Custom_Field_fixture_multi_name_not_equal(self):
fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"!="}] fixture = ["Custom Field", {"name":["Item-vat", "Bin-vat"],"op":"!="}]
path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" path = frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -164,7 +164,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_empty_object(self): def test_Custom_Field_fixture_empty_object(self):
fixture = ["Custom Field", {}] fixture = ["Custom Field", {}]
path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" path = frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -172,7 +172,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_just_list(self): def test_Custom_Field_fixture_just_list(self):
fixture = ["Custom Field"] fixture = ["Custom Field"]
path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" path = frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -181,7 +181,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_rex_no_flags(self): def test_Custom_Field_fixture_rex_no_flags(self):
fixture = ["Custom Field", {"name":r"^[r|L]"}] fixture = ["Custom Field", {"name":r"^[r|L]"}]
path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv" path = frappe.scrub(fixture[0]) + "_rex_no_flags.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -189,7 +189,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Custom_Field_fixture_rex_with_flags(self): def test_Custom_Field_fixture_rex_with_flags(self):
fixture = ["Custom Field", {"name":r"^[i|A]", "flags":"L,M"}] fixture = ["Custom Field", {"name":r"^[i|A]", "flags":"L,M"}]
path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv" path = frappe.scrub(fixture[0]) + "_rex_with_flags.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -199,7 +199,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_fixture_simple(self): def test_Doctype_fixture_simple(self):
fixture = "ToDo" fixture = "ToDo"
path = "Doctype_" + frappe.scrub(fixture) + "_original_style_should_be_all.csv" path = "Doctype_" + frappe.scrub(fixture) + "_original_style_should_be_all.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -207,7 +207,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_fixture_simple_name_equal_default(self): def test_Doctype_fixture_simple_name_equal_default(self):
fixture = ["ToDo", {"name":["TDI00000008"]}] fixture = ["ToDo", {"name":["TDI00000008"]}]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal_default.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -215,7 +215,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_fixture_simple_name_equal(self): def test_Doctype_fixture_simple_name_equal(self):
fixture = ["ToDo", {"name":["TDI00000002"],"op":"="}] fixture = ["ToDo", {"name":["TDI00000002"],"op":"="}]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -223,7 +223,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_simple_name_not_equal(self): def test_Doctype_simple_name_not_equal(self):
fixture = ["ToDo", {"name":["TDI00000002"],"op":"!="}] fixture = ["ToDo", {"name":["TDI00000002"],"op":"!="}]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_not_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -232,7 +232,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_fixture_simple_name_at_least_equal(self): def test_Doctype_fixture_simple_name_at_least_equal(self):
fixture = ["ToDo", {"name":"TDI"}] fixture = ["ToDo", {"name":"TDI"}]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_simple_name_at_least_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -240,7 +240,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_multi_name_equal(self): def test_Doctype_multi_name_equal(self):
fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"="}] fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"="}]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_equal.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -248,7 +248,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_multi_name_not_equal(self): def test_Doctype_multi_name_not_equal(self):
fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"!="}] fixture = ["ToDo", {"name":["TDI00000002", "TDI00000008"],"op":"!="}]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_multi_name_not_equal.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -256,7 +256,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_fixture_empty_object(self): def test_Doctype_fixture_empty_object(self):
fixture = ["ToDo", {}] fixture = ["ToDo", {}]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_empty_object_should_be_all.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -264,7 +264,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_fixture_just_list(self): def test_Doctype_fixture_just_list(self):
fixture = ["ToDo"] fixture = ["ToDo"]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_just_list_should_be_all.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -273,7 +273,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_fixture_rex_no_flags(self): def test_Doctype_fixture_rex_no_flags(self):
fixture = ["ToDo", {"name":r"^TDi"}] fixture = ["ToDo", {"name":r"^TDi"}]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_no_flags_should_be_all.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_no_flags_should_be_all.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)
@@ -281,7 +281,7 @@ class TestDataImportFixtures(unittest.TestCase):
def test_Doctype_fixture_rex_with_flags(self): def test_Doctype_fixture_rex_with_flags(self):
fixture = ["ToDo", {"name":r"^TDi", "flags":"L,M"}] fixture = ["ToDo", {"name":r"^TDi", "flags":"L,M"}]
path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_with_flags_should_be_none.csv" path = "Doctype_" + frappe.scrub(fixture[0]) + "_rex_with_flags_should_be_none.csv"
# print "teste done {}".format(path)
export_csv(fixture, path) export_csv(fixture, path)
self.assertTrue(True) self.assertTrue(True)
os.remove(path) os.remove(path)


+ 117
- 3
frappe/core/page/desktop/desktop.js Näytä tiedosto

@@ -112,13 +112,17 @@ $.extend(frappe.desktop, {
}, },


setup_module_click: function() { setup_module_click: function() {
frappe.desktop.wiggling = false;

if(frappe.list_desktop) { if(frappe.list_desktop) {
frappe.desktop.wrapper.on("click", ".desktop-list-item", function() { frappe.desktop.wrapper.on("click", ".desktop-list-item", function() {
frappe.desktop.open_module($(this)); frappe.desktop.open_module($(this));
}); });
} else { } else {
frappe.desktop.wrapper.on("click", ".app-icon", function() { frappe.desktop.wrapper.on("click", ".app-icon", function() {
frappe.desktop.open_module($(this).parent());
if ( !frappe.desktop.wiggling ) {
frappe.desktop.open_module($(this).parent());
}
}); });
} }
frappe.desktop.wrapper.on("click", ".circle", function() { frappe.desktop.wrapper.on("click", ".circle", function() {
@@ -127,6 +131,116 @@ $.extend(frappe.desktop, {
frappe.ui.notifications.show_open_count_list(doctype); frappe.ui.notifications.show_open_count_list(doctype);
} }
}); });

frappe.desktop.setup_wiggle();
},

setup_wiggle: () => {
// Wiggle, Wiggle, Wiggle.
const DURATION_LONG_PRESS = 1000;
// lesser the antidode, more the wiggle (like your drunk uncle)
// 75 seems good to replicate the iOS feels.
const WIGGLE_ANTIDODE = 75;

var timer_id = 0;
const $cases = frappe.desktop.wrapper.find('.case-wrapper');
const $icons = frappe.desktop.wrapper.find('.app-icon');
const $notis = $(frappe.desktop.wrapper.find('.circle').toArray().filter((object) => {
// This hack is so bad, I should punch myself.
// Seriously, punch yourself.
const text = $(object).find('.circle-text').html();
return text;
}));
const clearWiggle = () => {
const $closes = $cases.find('.module-remove');
$closes.hide();
$notis.show();

$icons.trigger('stopRumble');

frappe.desktop.wiggling = false;
};

// initiate wiggling.
$icons.jrumble({
speed: WIGGLE_ANTIDODE // seems neat enough to match the iOS way
});

frappe.desktop.wrapper.on('mousedown', '.app-icon', () => {
timer_id = setTimeout(() => {
frappe.desktop.wiggling = true;
// hide all notifications.
$notis.hide();
$cases.each((i) => {
const $case = $($cases[i]);
const template =
`
<div class="circle module-remove" style="background-color:#E0E0E0; color:#212121">
<div class="circle-text">
<b>
&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) { open_module: function(parent) {
@@ -212,8 +326,8 @@ $.extend(frappe.desktop, {
notifier.toggle(sum ? true : false); notifier.toggle(sum ? true : false);
var circle = notifier.find(".circle-text"); var circle = notifier.find(".circle-text");
var text = sum || ''; var text = sum || '';
if(text > 20) {
text = '20+';
if(text > 99) {
text = '99+';
} }


if(circle.length) { if(circle.length) {


+ 2
- 2
frappe/custom/doctype/custom_field/custom_field.py Näytä tiedosto

@@ -18,8 +18,8 @@ class CustomField(Document):
if not self.label: if not self.label:
frappe.throw(_("Label is mandatory")) frappe.throw(_("Label is mandatory"))
# remove special characters from fieldname # remove special characters from fieldname
self.fieldname = filter(lambda x: x.isdigit() or x.isalpha() or '_',
cstr(self.label).lower().replace(' ','_'))
self.fieldname = "".join(filter(lambda x: x.isdigit() or x.isalpha() or '_',
cstr(self.label).lower().replace(' ','_')))


# fieldnames should be lowercase # fieldnames should be lowercase
self.fieldname = self.fieldname.lower() self.fieldname = self.fieldname.lower()


+ 1
- 0
frappe/data/Framework.sql Näytä tiedosto

@@ -117,6 +117,7 @@ CREATE TABLE `tabDocType` (
`editable_grid` int(1) NOT NULL DEFAULT 1, `editable_grid` int(1) NOT NULL DEFAULT 1,
`track_changes` int(1) NOT NULL DEFAULT 0, `track_changes` int(1) NOT NULL DEFAULT 0,
`module` varchar(255) DEFAULT NULL, `module` varchar(255) DEFAULT NULL,
`restrict_to_domain` varchar(255) DEFAULT NULL,
`app` varchar(255) DEFAULT NULL, `app` varchar(255) DEFAULT NULL,
`autoname` varchar(255) DEFAULT NULL, `autoname` varchar(255) DEFAULT NULL,
`name_case` varchar(255) DEFAULT NULL, `name_case` varchar(255) DEFAULT NULL,


+ 13
- 1
frappe/defaults.py Näytä tiedosto

@@ -92,7 +92,19 @@ def set_default(key, value, parent, parenttype="__default"):
:param value: Default value. :param value: Default value.
:param parent: Usually, **User** to whom the default belongs. :param parent: Usually, **User** to whom the default belongs.
:param parenttype: [optional] default is `__default`.""" :param parenttype: [optional] default is `__default`."""
frappe.db.sql("""delete from `tabDefaultValue` where defkey=%s and parent=%s""", (key, parent))
if frappe.db.sql('''
select
defkey
from
tabDefaultValue
where
defkey=%s and parent=%s
for update''', (key, parent)):
frappe.db.sql("""
delete from
`tabDefaultValue`
where
defkey=%s and parent=%s""", (key, parent))
if value != None: if value != None:
add_default(key, value, parent) add_default(key, value, parent)




+ 14
- 1
frappe/desk/doctype/desktop_icon/desktop_icon.py Näytä tiedosto

@@ -95,7 +95,7 @@ def get_desktop_icons(user=None):
icon.hidden = 1 icon.hidden = 1


# sort by idx # sort by idx
user_icons.sort(lambda a, b: 1 if a.idx > b.idx else -1)
user_icons.sort(key = lambda a: a.idx)


# translate # translate
for d in user_icons: for d in user_icons:
@@ -404,3 +404,16 @@ palette = (
('#4F8EA8', 1), ('#4F8EA8', 1),
('#428B46', 1) ('#428B46', 1)
) )

@frappe.whitelist()
def hide(name, user = None):
if not user:
user = frappe.session.user

try:
set_hidden(name, user, hidden = 1)
clear_desktop_icons_cache()
except Exception:
return False

return True

+ 23
- 0
frappe/desk/doctype/todo/test_todo.js Näytä tiedosto

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

});

+ 9
- 9
frappe/desk/doctype/todo/todo.json Näytä tiedosto

@@ -112,20 +112,18 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "color",
"fieldtype": "Color",
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 1,
"in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Color",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
@@ -142,18 +140,20 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"fieldname": "color",
"fieldtype": "Color",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Color",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
@@ -544,7 +544,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-09-05 12:54:58.044162",
"modified": "2017-09-30 13:57:29.398598",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Desk", "module": "Desk",
"name": "ToDo", "name": "ToDo",


+ 1
- 1
frappe/desk/form/linked_with.py Näytä tiedosto

@@ -119,7 +119,7 @@ def _get_linked_doctypes(doctype):
if not dt in ret: if not dt in ret:
ret[dt] = {"get_parent": True} ret[dt] = {"get_parent": True}


for dt in ret.keys():
for dt in list(ret.keys()):
try: try:
doctype_module = load_doctype_module(dt) doctype_module = load_doctype_module(dt)
except ImportError: except ImportError:


+ 33
- 23
frappe/desk/page/setup_wizard/setup_wizard.js Näytä tiedosto

@@ -26,11 +26,7 @@ frappe.setup = {
} }


frappe.pages['setup-wizard'].on_page_load = function(wrapper) { frappe.pages['setup-wizard'].on_page_load = function(wrapper) {
// setup page ui
$(".navbar:first").toggle(false);

var requires = ["/assets/frappe/css/animate.min.css"].concat(
frappe.boot.setup_wizard_requires || []);
var requires = (frappe.boot.setup_wizard_requires || []);


frappe.require(requires, function() { frappe.require(requires, function() {
frappe.call({ frappe.call({
@@ -96,17 +92,23 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
} }


setup_keyboard_nav() { setup_keyboard_nav() {
this.container.on('keydown', (e) => {
if(e.which === 13) {
var $target = $(e.target);
if($target.hasClass('prev-btn')) {
$target.trigger('click');
} else {
this.container.find('.next-btn').trigger('click');
e.preventDefault();
}
$('body').on('keydown', this.handle_enter_press.bind(this));
}

disable_keyboard_nav() {
$('body').off('keydown', this.handle_enter_press.bind(this));
}

handle_enter_press(e) {
if (e.which === frappe.ui.keyCode.ENTER) {
var $target = $(e.target);
if($target.hasClass('prev-btn')) {
$target.trigger('click');
} else {
this.container.find('.next-btn').trigger('click');
e.preventDefault();
} }
});
}
} }


before_show_slide() { before_show_slide() {
@@ -118,6 +120,11 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
} }


show_slide(id) { show_slide(id) {
if (id === this.slides.length) {
// show_slide called on last slide
this.action_on_complete();
return;
}
super.show_slide(id); super.show_slide(id);
frappe.set_route(this.page_name, id + ""); frappe.set_route(this.page_name, id + "");
} }
@@ -172,6 +179,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
if (!this.current_slide.set_values()) return; if (!this.current_slide.set_values()) return;
this.update_values(); this.update_values();
this.show_working_state(); this.show_working_state();
this.disable_keyboard_nav();
return frappe.call({ return frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete", method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete",
args: {args: this.values}, args: {args: this.values},
@@ -181,8 +189,8 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
localStorage.setItem("session_last_route", frappe.setup.welcome_page); localStorage.setItem("session_last_route", frappe.setup.welcome_page);
} }
setTimeout(function() { setTimeout(function() {
// frappe.ui.toolbar.clear_cache();
window.location = "/desk";
// Reload
window.location.href = '';
}, 2000); }, 2000);
setTimeout(()=> { setTimeout(()=> {
$('body').removeClass('setup-state'); $('body').removeClass('setup-state');
@@ -241,6 +249,13 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
} }


get_message(title, message="", loading=false) { get_message(title, message="", loading=false) {
const loading_html = loading
? '<div style="width:100%;height:100%" class="lds-rolling state-icon"><div></div></div>'
: `<div style="width:100%;height:100%" class="state-icon">
<i class="fa fa-check-circle text-extra-muted"
style="font-size: 64px; margin-top: -8px;"></i>
</div>`;

return $(`<div class="page-card-container" data-state="setup"> return $(`<div class="page-card-container" data-state="setup">
<div class="page-card"> <div class="page-card">
<div class="page-card-head"> <div class="page-card-head">
@@ -251,12 +266,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
</div> </div>
<p>${message}</p> <p>${message}</p>
<div class="state-icon-container"> <div class="state-icon-container">
${loading
? '<div style="width:100%;height:100%" class="lds-rolling state-icon"><div></div></div>'
: `<div style="width:100%;height:100%" class="state-icon"><i class="fa fa-check-circle text-success"
style="font-size: 64px; margin-top: -8px;">
</i></div>`
}
${loading_html}
</div> </div>
</div> </div>
</div>`); </div>`);


+ 1
- 2
frappe/desk/reportview.py Näytä tiedosto

@@ -259,7 +259,7 @@ def get_stats(stats, doctype, filters=[]):
stats[tag] = scrub_user_tags(tagcount) stats[tag] = scrub_user_tags(tagcount)
stats[tag].append([_("No Tags"), frappe.get_list(doctype, stats[tag].append([_("No Tags"), frappe.get_list(doctype,
fields=[tag, "count(*)"], fields=[tag, "count(*)"],
filters=filters +["({0} = ',' or {0} is null)".format(tag)], as_list=True)[0][1]])
filters=filters +["({0} = ',' or {0} = '' or {0} is null)".format(tag)], as_list=True)[0][1]])
else: else:
stats[tag] = tagcount stats[tag] = tagcount


@@ -269,7 +269,6 @@ def get_stats(stats, doctype, filters=[]):
except MySQLdb.OperationalError: except MySQLdb.OperationalError:
# raised when _user_tags column is added on the fly # raised when _user_tags column is added on the fly
pass pass

return stats return stats


@frappe.whitelist() @frappe.whitelist()


BIN
frappe/docs/assets/img/webhook.png Näytä tiedosto

Before After
Leveys: 1301  |  Korkeus: 675  |  Koko: 51 KiB

+ 103
- 0
frappe/docs/user/en/guides/integration/webhooks.md Näytä tiedosto

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

+ 0
- 1
frappe/email/doctype/email_account/email_account.py Näytä tiedosto

@@ -273,7 +273,6 @@ class EmailAccount(Document):
"uid_reindexed": uid_reindexed "uid_reindexed": uid_reindexed
} }
communication = self.insert_communication(msg, args=args) communication = self.insert_communication(msg, args=args)
#self.notify_update()


except SentEmailInInbox: except SentEmailInInbox:
frappe.db.rollback() frappe.db.rollback()


+ 24
- 8
frappe/email/doctype/email_alert/email_alert.js Näytä tiedosto

@@ -6,13 +6,24 @@ frappe.email_alert = {
} }


frappe.model.with_doctype(frm.doc.document_type, function() { frappe.model.with_doctype(frm.doc.document_type, function() {
var get_select_options = function(df) {
let get_select_options = function(df) {
return {value: df.fieldname, label: df.fieldname + " (" + __(df.label) + ")"}; return {value: df.fieldname, label: df.fieldname + " (" + __(df.label) + ")"};
} }


var fields = frappe.get_doc("DocType", frm.doc.document_type).fields;
let get_date_change_options = function() {
let date_options = $.map(fields, function(d) {
return (d.fieldtype=="Date" || d.fieldtype=="Datetime")?
get_select_options(d) : null;
});
// append creation and modified date to Date Change field
return date_options.concat([
{ value: "creation", label: `creation (${__('Created On')})` },
{ value: "modified", label: `modified (${__('Last Modified Date')})` }
]);
}


var options = $.map(fields,
let fields = frappe.get_doc("DocType", frm.doc.document_type).fields;
let options = $.map(fields,
function(d) { return in_list(frappe.model.no_value_type, d.fieldtype) ? function(d) { return in_list(frappe.model.no_value_type, d.fieldtype) ?
null : get_select_options(d); }); null : get_select_options(d); });


@@ -21,11 +32,9 @@ frappe.email_alert = {
frm.set_df_property("set_property_after_alert", "options", [""].concat(options)); frm.set_df_property("set_property_after_alert", "options", [""].concat(options));


// set date changed options // set date changed options
frm.set_df_property("date_changed", "options", $.map(fields,
function(d) { return (d.fieldtype=="Date" || d.fieldtype=="Datetime") ?
get_select_options(d) : null; }));
frm.set_df_property("date_changed", "options", get_date_change_options());


var email_fields = $.map(fields,
let email_fields = $.map(fields,
function(d) { return (d.options == "Email" || function(d) { return (d.options == "Email" ||
(d.options=='User' && d.fieldtype=='Link')) ? (d.options=='User' && d.fieldtype=='Link')) ?
get_select_options(d) : null; }); get_select_options(d) : null; });
@@ -48,7 +57,14 @@ frappe.ui.form.on("Email Alert", {
"istable": 0 "istable": 0
} }
} }
})
});
frm.set_query("print_format", function() {
return {
"filters": {
"doc_type": frm.doc.document_type
}
}
});
}, },
refresh: function(frm) { refresh: function(frm) {
frappe.email_alert.setup_fieldname_select(frm); frappe.email_alert.setup_fieldname_select(frm);


+ 74
- 11
frappe/email/doctype/email_alert/email_alert.json Näytä tiedosto

@@ -711,8 +711,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "attach_print",
"fieldtype": "Check",
"fieldname": "message_examples",
"fieldtype": "HTML",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@@ -720,7 +720,68 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Attach Print",
"label": "Message Examples",
"length": 0,
"no_copy": 0,
"options": "<h5>Message Example</h5>\n\n<pre>&lt;h3&gt;Order Overdue&lt;/h3&gt;\n\n&lt;p&gt;Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.&lt;/p&gt;\n\n&lt;!-- show last comment --&gt;\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n&lt;h4&gt;Details&lt;/h4&gt;\n\n&lt;ul&gt;\n&lt;li&gt;Customer: {{ doc.customer }}\n&lt;li&gt;Amount: {{ doc.total_amount }}\n&lt;/ul&gt;\n</pre>",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "view_properties",
"fieldtype": "Button",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "View Properties (via Customize Form)",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "attach_print",
"columns": 0,
"fieldname": "column_break_25",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Print Settings",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -741,8 +802,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "message_examples",
"fieldtype": "HTML",
"fieldname": "attach_print",
"fieldtype": "Check",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@@ -750,11 +811,11 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Message Examples",
"label": "Attach Print",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "<h5>Message Example</h5>\n\n<pre>&lt;h3&gt;Order Overdue&lt;/h3&gt;\n\n&lt;p&gt;Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.&lt;/p&gt;\n\n&lt;!-- show last comment --&gt;\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n&lt;h4&gt;Details&lt;/h4&gt;\n\n&lt;ul&gt;\n&lt;li&gt;Customer: {{ doc.customer }}\n&lt;li&gt;Amount: {{ doc.total_amount }}\n&lt;/ul&gt;\n</pre>",
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
@@ -771,8 +832,9 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "view_properties",
"fieldtype": "Button",
"depends_on": "attach_print",
"fieldname": "print_format",
"fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@@ -780,9 +842,10 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "View Properties (via Customize Form)",
"label": "Print Format",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Print Format",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@@ -808,7 +871,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2017-08-13 22:43:49.079330",
"modified": "2017-09-26 20:10:00.061780",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Email", "module": "Email",
"name": "Email Alert", "name": "Email Alert",


+ 1
- 1
frappe/email/doctype/email_alert/email_alert.py Näytä tiedosto

@@ -117,7 +117,7 @@ def get_context(context):
please enable Allow Print For {0} in Print Settings""".format(status)), please enable Allow Print For {0} in Print Settings""".format(status)),
title=_("Error in Email Alert")) title=_("Error in Email Alert"))
else: else:
return [frappe.attach_print(doc.doctype, doc.name)]
return [frappe.attach_print(doc.doctype, doc.name, None, self.print_format)]


context = get_context(doc) context = get_context(doc)
recipients = [] recipients = []


+ 3
- 3
frappe/email/doctype/email_queue/email_queue.json Näytä tiedosto

@@ -503,7 +503,7 @@
"columns": 0, "columns": 0,
"fieldname": "attachments", "fieldname": "attachments",
"fieldtype": "Code", "fieldtype": "Code",
"hidden": 1,
"hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
@@ -517,7 +517,7 @@
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
@@ -537,7 +537,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-07-07 16:29:15.780393",
"modified": "2017-09-25 15:39:21.781324",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Email", "module": "Email",
"name": "Email Queue", "name": "Email Queue",


+ 2
- 2
frappe/email/queue.py Näytä tiedosto

@@ -478,7 +478,7 @@ def prepare_message(email, recipient, recipients_list):
if email.add_unsubscribe_link and email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url if email.add_unsubscribe_link and email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url
unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient, unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient,
email.unsubscribe_method, email.unsubscribe_params) email.unsubscribe_method, email.unsubscribe_params)
message = message.replace("<!--unsubscribe url-->", quopri.encodestring(unsubscribe_url))
message = message.replace("<!--unsubscribe url-->", quopri.encodestring(unsubscribe_url.encode()).decode())


if email.expose_recipients == "header": if email.expose_recipients == "header":
pass pass
@@ -494,7 +494,7 @@ def prepare_message(email, recipient, recipients_list):
email_sent_message = _("This email was sent to {0} and copied to {1}").format(email_sent_to,email_sent_cc) email_sent_message = _("This email was sent to {0} and copied to {1}").format(email_sent_to,email_sent_cc)
else: else:
email_sent_message = _("This email was sent to {0}").format(email_sent_to) email_sent_message = _("This email was sent to {0}").format(email_sent_to)
message = message.replace("<!--cc message-->", quopri.encodestring(email_sent_message))
message = message.replace("<!--cc message-->", quopri.encodestring(email_sent_message.encode()).decode())


message = message.replace("<!--recipient-->", recipient) message = message.replace("<!--recipient-->", recipient)




+ 1
- 1
frappe/hooks.py Näytä tiedosto

@@ -11,7 +11,7 @@ app_color = "orange"
source_link = "https://github.com/frappe/frappe" source_link = "https://github.com/frappe/frappe"
app_license = "MIT" app_license = "MIT"


develop_version = '8.x.x-beta'
develop_version = '9.x.x-develop'


app_email = "info@frappe.io" app_email = "info@frappe.io"




+ 1
- 1
frappe/integrations/doctype/dropbox_settings/dropbox_settings.py Näytä tiedosto

@@ -146,7 +146,7 @@ def upload_from_folder(path, dropbox_folder, dropbox_client, did_not_upload, err
def upload_file_to_dropbox(filename, folder, dropbox_client): def upload_file_to_dropbox(filename, folder, dropbox_client):
create_folder_if_not_exists(folder, dropbox_client) create_folder_if_not_exists(folder, dropbox_client)
chunk_size = 4 * 1024 * 1024 chunk_size = 4 * 1024 * 1024
file_size = os.path.getsize(filename)
file_size = os.path.getsize(encode(filename))
mode = (dropbox.files.WriteMode.overwrite) mode = (dropbox.files.WriteMode.overwrite)


f = open(encode(filename), 'rb') f = open(encode(filename), 'rb')


+ 2
- 2
frappe/integrations/doctype/stripe_settings/stripe_settings.py Näytä tiedosto

@@ -7,7 +7,7 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _ from frappe import _
from six.moves.urllib.parse import urlencode from six.moves.urllib.parse import urlencode
from frappe.utils import get_url, call_hook_method, cint
from frappe.utils import get_url, call_hook_method, cint, flt
from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway


class StripeSettings(Document): class StripeSettings(Document):
@@ -62,7 +62,7 @@ class StripeSettings(Document):
"Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))}
data = { data = {
"amount": cint(self.data.amount)*100,
"amount": cint(flt(self.data.amount)*100),
"currency": self.data.currency, "currency": self.data.currency,
"source": self.data.stripe_token_id, "source": self.data.stripe_token_id,
"description": self.data.description "description": self.data.description


+ 57
- 0
frappe/integrations/doctype/webhook/__init__.py Näytä tiedosto

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and contributors
# For license information, please see license.txt

import frappe

def run_webhooks(doc, method):
'''Run webhooks for this method'''
if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install:
return

if frappe.flags.webhooks_executed is None:
frappe.flags.webhooks_executed = {}

if frappe.flags.webhooks == None:
# load webhooks from cache
webhooks = frappe.cache().get_value('webhooks')
if webhooks==None:
# query webhooks
webhooks_list = frappe.get_all('Webhook',
fields=["name", "webhook_docevent", "webhook_doctype"])

# make webhooks map for cache
webhooks = {}
for w in webhooks_list:
webhooks.setdefault(w.webhook_doctype, []).append(w)
frappe.cache().set_value('webhooks', webhooks)

frappe.flags.webhooks = webhooks

# get webhooks for this doctype
webhooks_for_doc = frappe.flags.webhooks.get(doc.doctype, None)

if not webhooks_for_doc:
# no webhooks, quit
return

def _webhook_request(webhook):
if not webhook.name in frappe.flags.webhooks_executed.get(doc.name, []):
frappe.enqueue("frappe.integrations.doctype.webhook.webhook.enqueue_webhook", doc=doc, webhook=webhook)

# keep list of webhooks executed for this doc in this request
# so that we don't run the same webhook for the same document multiple times
# in one request
frappe.flags.webhooks_executed.setdefault(doc.name, []).append(webhook.name)

event_list = ["on_update", "after_insert", "on_submit", "on_cancel", "on_trash"]

if not doc.flags.in_insert:
# value change is not applicable in insert
event_list.append('on_change')
event_list.append('before_update_after_submit')

for webhook in webhooks_for_doc:
event = method if method in event_list else None
if event and webhook.webhook_docevent == event:
_webhook_request(webhook)

+ 23
- 0
frappe/integrations/doctype/webhook/test_webhook.js Näytä tiedosto

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line

QUnit.test("test: Webhook", function (assert) {
let done = assert.async();

// number of asserts
assert.expect(1);

frappe.run_serially([
// insert a new Webhook
() => frappe.tests.make('Webhook', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);

});

+ 21
- 0
frappe/integrations/doctype/webhook/test_webhook.py Näytä tiedosto

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals

import frappe
import unittest

class TestWebhook(unittest.TestCase):
def test_validate_docevents(self):
doc = frappe.new_doc("Webhook")
doc.webhook_doctype = "User"
doc.webhook_docevent = "on_submit"
doc.request_url = "https://httpbin.org/post"
self.assertRaises(frappe.ValidationError, doc.save)
def test_validate_request_url(self):
doc = frappe.new_doc("Webhook")
doc.webhook_doctype = "User"
doc.webhook_docevent = "after_insert"
doc.request_url = "httpbin.org?post"
self.assertRaises(frappe.ValidationError, doc.save)

+ 46
- 0
frappe/integrations/doctype/webhook/webhook.js Näytä tiedosto

@@ -0,0 +1,46 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.webhook = {
set_fieldname_select: function(frm) {
var doc = frm.doc;
if (doc.webhook_doctype) {
frappe.model.with_doctype(doc.webhook_doctype, function() {
var fields = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) {
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
d.fieldtype === 'Table') {
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
}
else if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') {
return { label: d.label, value: d.fieldname };
}
else {
return null;
}
});
fields.unshift({"label":"Name (Doc Name)","value":"name"});
frappe.meta.get_docfield("Webhook Data", "fieldname", frm.doc.name).options = [""].concat(fields);
});
}
}
};

frappe.ui.form.on('Webhook', {
refresh: function(frm) {
frappe.webhook.set_fieldname_select(frm);
},
webhook_doctype: function(frm) {
frappe.webhook.set_fieldname_select(frm);
}
});

frappe.ui.form.on("Webhook Data", {
fieldname: function(frm, doctype, name) {
var doc = frappe.get_doc(doctype, name);
var df = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) {
return doc.fieldname == d.fieldname ? d : null;
})[0];
doc.key = df != undefined ? df.fieldname : "name";
frm.refresh_field("webhook_data");
}
});

+ 367
- 0
frappe/integrations/doctype/webhook/webhook.json Näytä tiedosto

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

+ 73
- 0
frappe/integrations/doctype/webhook/webhook.py Näytä tiedosto

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
import json, requests
from frappe import _
from frappe.model.document import Document
from six.moves.urllib.parse import urlparse
from time import sleep

class Webhook(Document):
def autoname(self):
self.name = self.webhook_doctype + "-" + self.webhook_docevent

def validate(self):
self.validate_docevent()
self.validate_request_url()
self.validate_repeating_fields()

def on_update(self):
frappe.cache().delete_value('webhooks')

def validate_docevent(self):
if self.webhook_doctype:
is_submittable = frappe.get_value("DocType", self.webhook_doctype, "is_submittable")
if not is_submittable and self.webhook_docevent in ["on_submit", "on_cancel", "on_update_after_submit"]:
frappe.throw(_("DocType must be Submittable for the selected Doc Event"))

def validate_request_url(self):
try:
request_url = urlparse(self.request_url).netloc
if not request_url:
raise frappe.ValidationError
except Exception as e:
frappe.throw(_("Check Request URL"), exc=e)

def validate_repeating_fields(self):
"""Error when Same Field is entered multiple times in webhook_data"""
webhook_data = []
for entry in self.webhook_data:
webhook_data.append(entry.fieldname)

if len(webhook_data)!= len(set(webhook_data)):
frappe.throw(_("Same Field is entered more than once"))

def enqueue_webhook(doc, webhook):
webhook = frappe.get_doc("Webhook", webhook.get("name"))
headers = {}
data = {}
if webhook.webhook_headers:
for h in webhook.webhook_headers:
if h.get("key") and h.get("value"):
headers[h.get("key")] = h.get("value")
if webhook.webhook_data:
for w in webhook.webhook_data:
for k, v in doc.as_dict().items():
if k == w.fieldname:
data[w.key] = v
for i in range(3):
try:
r = requests.post(webhook.request_url, data=json.dumps(data), headers=headers, timeout=5)
r.raise_for_status()
frappe.logger().debug({"webhook_success":r.text})
break
except Exception as e:
frappe.logger().debug({"webhook_error":e, "try": i+1})
sleep(3*i + 1)
if i !=2:
continue
else:
raise e

+ 0
- 0
frappe/integrations/doctype/webhook_data/__init__.py Näytä tiedosto


+ 130
- 0
frappe/integrations/doctype/webhook_data/webhook_data.json Näytä tiedosto

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

+ 10
- 0
frappe/integrations/doctype/webhook_data/webhook_data.py Näytä tiedosto

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document

class WebhookData(Document):
pass

+ 0
- 0
frappe/integrations/doctype/webhook_header/__init__.py Näytä tiedosto


+ 101
- 0
frappe/integrations/doctype/webhook_header/webhook_header.json Näytä tiedosto

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

+ 10
- 0
frappe/integrations/doctype/webhook_header/webhook_header.py Näytä tiedosto

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

+ 56
- 25
frappe/model/document.py Näytä tiedosto

@@ -15,17 +15,18 @@ import hashlib, json
from frappe.model import optional_fields from frappe.model import optional_fields
from frappe.utils.file_manager import save_url from frappe.utils.file_manager import save_url
from frappe.utils.global_search import update_global_search from frappe.utils.global_search import update_global_search
from frappe.integrations.doctype.webhook import run_webhooks


# once_only validation # once_only validation
# methods # methods


def get_doc(arg1, arg2=None):
def get_doc(*args, **kwargs):
"""returns a frappe.model.Document object. """returns a frappe.model.Document object.


:param arg1: Document dict or DocType name. :param arg1: Document dict or DocType name.
:param arg2: [optional] document name. :param arg2: [optional] document name.


There are two ways to call `get_doc`
There are multiple ways to call `get_doc`


# will fetch the latest user object (with child table) from the database # will fetch the latest user object (with child table) from the database
user = get_doc("User", "test@example.com") user = get_doc("User", "test@example.com")
@@ -38,23 +39,39 @@ def get_doc(arg1, arg2=None):
{"role": "System Manager"} {"role": "System Manager"}
] ]
}) })

# create new object with keyword arguments
user = get_doc(doctype='User', email_id='test@example.com')
""" """
if isinstance(arg1, BaseDocument):
return arg1
elif isinstance(arg1, string_types):
doctype = arg1
else:
doctype = arg1.get("doctype")
if args:
if isinstance(args[0], BaseDocument):
# already a document
return args[0]
elif isinstance(args[0], string_types):
doctype = args[0]

elif isinstance(args[0], dict):
# passed a dict
kwargs = args[0]

else:
raise ValueError('First non keyword argument must be a string or dict')

if kwargs:
if 'doctype' in kwargs:
doctype = kwargs['doctype']
else:
raise ValueError('"doctype" is a required key')


controller = get_controller(doctype) controller = get_controller(doctype)
if controller: if controller:
return controller(arg1, arg2)
return controller(*args, **kwargs)


raise ImportError(arg1)
raise ImportError(doctype)


class Document(BaseDocument): class Document(BaseDocument):
"""All controllers inherit from `Document`.""" """All controllers inherit from `Document`."""
def __init__(self, arg1, arg2=None):
def __init__(self, *args, **kwargs):
"""Constructor. """Constructor.


:param arg1: DocType name as string or document **dict** :param arg1: DocType name as string or document **dict**
@@ -67,29 +84,37 @@ class Document(BaseDocument):
self._default_new_docs = {} self._default_new_docs = {}
self.flags = frappe._dict() self.flags = frappe._dict()


if arg1 and isinstance(arg1, string_types):
if not arg2:
if args and args[0] and isinstance(args[0], string_types):
# first arugment is doctype
if len(args)==1:
# single # single
self.doctype = self.name = arg1
self.doctype = self.name = args[0]
else: else:
self.doctype = arg1
if isinstance(arg2, dict):
self.doctype = args[0]
if isinstance(args[1], dict):
# filter # filter
self.name = frappe.db.get_value(arg1, arg2, "name")
self.name = frappe.db.get_value(args[0], args[1], "name")
if self.name is None: if self.name is None:
frappe.throw(_("{0} {1} not found").format(_(arg1), arg2), frappe.DoesNotExistError)
frappe.throw(_("{0} {1} not found").format(_(args[0]), args[1]),
frappe.DoesNotExistError)
else: else:
self.name = arg2
self.name = args[1]


self.load_from_db() self.load_from_db()
return

if args and args[0] and isinstance(args[0], dict):
# first argument is a dict
kwargs = args[0]


elif isinstance(arg1, dict):
super(Document, self).__init__(arg1)
if kwargs:
# init base document
super(Document, self).__init__(kwargs)
self.init_valid_columns() self.init_valid_columns()


else: else:
# incorrect arguments. let's not proceed. # incorrect arguments. let's not proceed.
raise frappe.DataError("Document({0}, {1})".format(arg1, arg2))
raise ValueError('Illegal arguments')


def reload(self): def reload(self):
"""Reload document from database""" """Reload document from database"""
@@ -335,13 +360,18 @@ class Document(BaseDocument):
self._doc_before_save = frappe.get_doc(self.doctype, self.name) self._doc_before_save = frappe.get_doc(self.doctype, self.name)
return self._doc_before_save return self._doc_before_save


def set_new_name(self):
def set_new_name(self, force=False):
"""Calls `frappe.naming.se_new_name` for parent and child docs.""" """Calls `frappe.naming.se_new_name` for parent and child docs."""
if self.flags.name_set and not force:
return

set_new_name(self) set_new_name(self)
# set name for children # set name for children
for d in self.get_all_children(): for d in self.get_all_children():
set_new_name(d) set_new_name(d)


self.flags.name_set = True

def get_title(self): def get_title(self):
'''Get the document title based on title_field or `title` or `name`''' '''Get the document title based on title_field or `title` or `name`'''
return self.get(self.meta.get_title_field()) return self.get(self.meta.get_title_field())
@@ -625,7 +655,7 @@ class Document(BaseDocument):
name=self.name)) name=self.name))


def _validate_links(self): def _validate_links(self):
if self.flags.ignore_links:
if self.flags.ignore_links or self._action == "cancel":
return return


invalid_links, cancelled_links = self.get_invalid_links() invalid_links, cancelled_links = self.get_invalid_links()
@@ -672,6 +702,7 @@ class Document(BaseDocument):
out = Document.hook(fn)(self, *args, **kwargs) out = Document.hook(fn)(self, *args, **kwargs)


self.run_email_alerts(method) self.run_email_alerts(method)
run_webhooks(self, method)


return out return out


@@ -998,7 +1029,7 @@ class Document(BaseDocument):


def get_signature(self): def get_signature(self):
"""Returns signature (hash) for private URL.""" """Returns signature (hash) for private URL."""
return hashlib.sha224(get_datetime_str(self.creation)).hexdigest()
return hashlib.sha224(get_datetime_str(self.creation).encode()).hexdigest()


def get_liked_by(self): def get_liked_by(self):
liked_by = getattr(self, "_liked_by", None) liked_by = getattr(self, "_liked_by", None)


+ 2
- 0
frappe/patches/v6_20x/remove_roles_from_website_user.py Näytä tiedosto

@@ -1,6 +1,8 @@
import frappe import frappe


def execute(): def execute():
frappe.reload_doc("core", "doctype", "user_email")
frappe.reload_doc("core", "doctype", "user")
for user_name in frappe.get_all('User', filters={'user_type': 'Website User'}): for user_name in frappe.get_all('User', filters={'user_type': 'Website User'}):
user = frappe.get_doc('User', user_name) user = frappe.get_doc('User', user_name)
if user.roles: if user.roles:


+ 8
- 1
frappe/public/build.json Näytä tiedosto

@@ -23,6 +23,7 @@
"public/js/frappe/misc/rating_icons.html" "public/js/frappe/misc/rating_icons.html"
], ],
"js/control.min.js": [ "js/control.min.js": [
"public/js/frappe/ui/capture.js",
"public/js/frappe/form/controls/base_control.js", "public/js/frappe/form/controls/base_control.js",
"public/js/frappe/form/controls/base_input.js", "public/js/frappe/form/controls/base_input.js",
"public/js/frappe/form/controls/data.js", "public/js/frappe/form/controls/data.js",
@@ -55,12 +56,15 @@
"js/dialog.min.js": [ "js/dialog.min.js": [
"public/js/frappe/dom.js", "public/js/frappe/dom.js",
"public/js/frappe/ui/modal.html", "public/js/frappe/ui/modal.html",

"public/js/frappe/form/formatters.js", "public/js/frappe/form/formatters.js",
"public/js/frappe/form/layout.js", "public/js/frappe/form/layout.js",
"public/js/frappe/ui/field_group.js", "public/js/frappe/ui/field_group.js",
"public/js/frappe/form/link_selector.js", "public/js/frappe/form/link_selector.js",
"public/js/frappe/form/multi_select_dialog.js", "public/js/frappe/form/multi_select_dialog.js",
"public/js/frappe/ui/dialog.js", "public/js/frappe/ui/dialog.js",
"public/js/frappe/ui/capture.js",

"public/js/frappe/form/controls/base_control.js", "public/js/frappe/form/controls/base_control.js",
"public/js/frappe/form/controls/base_input.js", "public/js/frappe/form/controls/base_input.js",
"public/js/frappe/form/controls/data.js", "public/js/frappe/form/controls/data.js",
@@ -130,7 +134,9 @@
"public/js/lib/jSignature.min.js", "public/js/lib/jSignature.min.js",
"public/js/frappe/translate.js", "public/js/frappe/translate.js",
"public/js/lib/datepicker/datepicker.min.js", "public/js/lib/datepicker/datepicker.min.js",
"public/js/lib/datepicker/locale-all.js"
"public/js/lib/datepicker/locale-all.js",
"public/js/lib/jquery.jrumble.min.js",
"public/js/lib/webcam.min.js"
], ],
"js/desk.min.js": [ "js/desk.min.js": [
"public/js/frappe/class.js", "public/js/frappe/class.js",
@@ -168,6 +174,7 @@
"public/js/frappe/form/link_selector.js", "public/js/frappe/form/link_selector.js",
"public/js/frappe/form/multi_select_dialog.js", "public/js/frappe/form/multi_select_dialog.js",
"public/js/frappe/ui/dialog.js", "public/js/frappe/ui/dialog.js",
"public/js/frappe/ui/capture.js",
"public/js/frappe/ui/app_icon.js", "public/js/frappe/ui/app_icon.js",


"public/js/frappe/model/model.js", "public/js/frappe/model/model.js",


+ 5
- 2
frappe/public/css/desk.css Näytä tiedosto

@@ -398,9 +398,9 @@ fieldset[disabled] .form-control {
width: 100%; width: 100%;
} }
.awesomplete > ul { .awesomplete > ul {
z-index: 1041;
z-index: 1041 !important;
transition: none; transition: none;
background: #fff;
background-color: #fff;
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
@@ -624,6 +624,9 @@ li.user-progress .progress-bar {
.frappe-rtl textarea { .frappe-rtl textarea {
direction: rtl; direction: rtl;
} }
.frappe-rtl .checkbox .disp-area {
margin-right: -20px;
}
.text-editor { .text-editor {
height: 400px; height: 400px;
background-color: white; background-color: white;


+ 16
- 0
frappe/public/css/list.css Näytä tiedosto

@@ -288,6 +288,10 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.image-view-container .image-view-row {
display: flex;
border-bottom: 1px solid #ebeff2;
}
.image-view-container .image-view-item { .image-view-container .image-view-item {
flex: 0 0 25%; flex: 0 0 25%;
padding: 15px; padding: 15px;
@@ -360,6 +364,18 @@
border-bottom: 1px solid #EBEFF2; border-bottom: 1px solid #EBEFF2;
} }
} }
.item-selector {
border: 1px solid #d1d8dd;
}
.item-selector .image-view-row {
width: 100%;
}
.item-selector .image-field {
height: 120px;
}
.item-selector .placeholder-text {
font-size: 48px;
}
.image-view-container.three-column .image-view-item { .image-view-container.three-column .image-view-item {
flex: 0 0 33.33333333%; flex: 0 0 33.33333333%;
} }


+ 8
- 0
frappe/public/css/report-rtl.css Näytä tiedosto

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

.chart_area{
direction: ltr;
}

.grid-report .show-zero{
direction: rtl ;
}

+ 4
- 0
frappe/public/css/website.css Näytä tiedosto

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

BIN
frappe/public/images/default-avatar.png Näytä tiedosto

Before After
Leveys: 400  |  Korkeus: 400  |  Koko: 44 KiB

+ 1
- 1
frappe/public/js/frappe/assets.js Näytä tiedosto

@@ -85,7 +85,7 @@ frappe.assets = {
frappe.assets.executed_.push(path) frappe.assets.executed_.push(path)
} }
} }
callback();
callback && callback();
}, },


// check if the asset exists in // check if the asset exists in


+ 2
- 2
frappe/public/js/frappe/desk.js Näytä tiedosto

@@ -260,7 +260,7 @@ frappe.Application = Class.extend({
$.each(frappe.boot.notification_info.open_count_doctype, function(doctype, count) { $.each(frappe.boot.notification_info.open_count_doctype, function(doctype, count) {
if(count) { if(count) {
$('.open-notification.global[data-doctype="'+ doctype +'"]') $('.open-notification.global[data-doctype="'+ doctype +'"]')
.removeClass("hide").html(count > 20 ? "20+" : count);
.removeClass("hide").html(count > 99 ? "99+" : count);
} else { } else {
$('.open-notification.global[data-doctype="'+ doctype +'"]') $('.open-notification.global[data-doctype="'+ doctype +'"]')
.addClass("hide"); .addClass("hide");
@@ -355,7 +355,7 @@ frappe.Application = Class.extend({
}, },
make_nav_bar: function() { make_nav_bar: function() {
// toolbar // toolbar
if(frappe.boot) {
if(frappe.boot && !frappe.boot.in_setup_wizard) {
frappe.frappe_toolbar = new frappe.ui.toolbar.Toolbar(); frappe.frappe_toolbar = new frappe.ui.toolbar.Toolbar();
} }




+ 1
- 1
frappe/public/js/frappe/form/controls/check.js Näytä tiedosto

@@ -5,7 +5,7 @@ frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({
<div class="checkbox">\ <div class="checkbox">\
<label>\ <label>\
<span class="input-area"></span>\ <span class="input-area"></span>\
<span class="disp-area" style="display:none; margin-left: -20px;"></span>\
<span class="disp-area"></span>\
<span class="label-area small"></span>\ <span class="label-area small"></span>\
</label>\ </label>\
<p class="help-box small text-muted"></p>\ <p class="help-box small text-muted"></p>\


+ 4
- 2
frappe/public/js/frappe/form/controls/color.js Näytä tiedosto

@@ -36,9 +36,11 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({
set_formatted_input: function(value) { set_formatted_input: function(value) {
this._super(value); this._super(value);


if(!value) value = '#ffffff';
if (!value) value = '#FFFFFF';
const contrast = frappe.ui.color.get_contrast_color(value);

this.$input.css({ this.$input.css({
"background-color": value
"background-color": value, "color": contrast
}); });
}, },
bind_events: function () { bind_events: function () {


+ 44
- 5
frappe/public/js/frappe/form/controls/text_editor.js Näytä tiedosto

@@ -6,6 +6,28 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
this.setup_drag_drop(); this.setup_drag_drop();
this.setup_image_dialog(); this.setup_image_dialog();
this.setting_count = 0; this.setting_count = 0;

$(document).on('form-refresh', () => {
// reset last keystroke when a new form is loaded
this.last_keystroke_on = null;
})
},
render_camera_button: (context) => {
var ui = $.summernote.ui;
var button = ui.button({
contents: '<i class="fa fa-camera"/>',
tooltip: 'Camera',
click: () => {
const capture = new frappe.ui.Capture();
capture.open();

capture.click((data) => {
context.invoke('editor.insertImage', data);
});
}
});

return button.render();
}, },
make_editor: function() { make_editor: function() {
var me = this; var me = this;
@@ -25,9 +47,12 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
['color', ['color']], ['color', ['color']],
['para', ['ul', 'ol', 'paragraph', 'hr']], ['para', ['ul', 'ol', 'paragraph', 'hr']],
//['height', ['height']], //['height', ['height']],
['media', ['link', 'picture', 'video', 'table']],
['media', ['link', 'picture', 'camera', 'video', 'table']],
['misc', ['fullscreen', 'codeview']] ['misc', ['fullscreen', 'codeview']]
], ],
buttons: {
camera: this.render_camera_button,
},
keyMap: { keyMap: {
pc: { pc: {
'CTRL+ENTER': '' 'CTRL+ENTER': ''
@@ -54,7 +79,7 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
me.parse_validate_and_set_in_model(value); me.parse_validate_and_set_in_model(value);
}, },
onKeydown: function(e) { onKeydown: function(e) {
me._last_change_on = new Date();
me.last_keystroke_on = new Date();
var key = frappe.ui.keys.get_key(e); var key = frappe.ui.keys.get_key(e);
// prevent 'New DocType (Ctrl + B)' shortcut in editor // prevent 'New DocType (Ctrl + B)' shortcut in editor
if(['ctrl+b', 'meta+b'].indexOf(key) !== -1) { if(['ctrl+b', 'meta+b'].indexOf(key) !== -1) {
@@ -80,6 +105,7 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
'outdent': 'fa fa-outdent', 'outdent': 'fa fa-outdent',
'arrowsAlt': 'fa fa-arrows-alt', 'arrowsAlt': 'fa fa-arrows-alt',
'bold': 'fa fa-bold', 'bold': 'fa fa-bold',
'camera': 'fa fa-camera',
'caret': 'caret', 'caret': 'caret',
'circle': 'fa fa-circle', 'circle': 'fa fa-circle',
'close': 'fa fa-close', 'close': 'fa fa-close',
@@ -184,20 +210,30 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({


if(this.setting_count > 2) { if(this.setting_count > 2) {
// we don't understand how the internal triggers work, // we don't understand how the internal triggers work,
// so if someone is setting the value third time, then quit
// so if someone is setting the value third time in 500ms,
// then quit
return; return;
} }


this.setting_count += 1; this.setting_count += 1;


let time_since_last_keystroke = moment() - moment(this._last_change_on);
let time_since_last_keystroke = moment() - moment(this.last_keystroke_on);


if(!this._last_change_on || (time_since_last_keystroke > 3000)) {
if(!this.last_keystroke_on || (time_since_last_keystroke > 3000)) {
// if 3 seconds have passed since the last keystroke and
// we have not set any value in the last 1 second, do this
setTimeout(() => this.setting_count = 0, 500); setTimeout(() => this.setting_count = 0, 500);
this.editor.summernote('code', value || ''); this.editor.summernote('code', value || '');
this.last_keystroke_on = null;
} else { } else {
// user is probably still in the middle of typing
// so lets not mess up the html by re-updating it
// keep checking every second if our 3 second barrier
// has been completed, so that we can refresh the html
this._setting_value = setInterval(() => { this._setting_value = setInterval(() => {
if(time_since_last_keystroke > 3000) { if(time_since_last_keystroke > 3000) {
// 3 seconds done! lets refresh
// safe to update
if(this.last_value !== this.get_input_value()) { if(this.last_value !== this.get_input_value()) {
// if not already in sync, reset // if not already in sync, reset
this.editor.summernote('code', this.last_value || ''); this.editor.summernote('code', this.last_value || '');
@@ -205,6 +241,9 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
clearInterval(this._setting_value); clearInterval(this._setting_value);
this._setting_value = null; this._setting_value = null;
this.setting_count = 0; this.setting_count = 0;

// clear timestamp of last keystroke
this.last_keystroke_on = null;
} }
}, 1000); }, 1000);
} }


+ 3
- 2
frappe/public/js/frappe/form/grid.js Näytä tiedosto

@@ -170,8 +170,9 @@ frappe.ui.form.Grid = Class.extend({
} else { } else {
// redraw // redraw
var _scroll_y = $(document).scrollTop(); var _scroll_y = $(document).scrollTop();

this.make_head(); this.make_head();
// to hide checkbox if grid is not editable
this.header_row && this.header_row.toggle_check();


if(!this.grid_rows) { if(!this.grid_rows) {
this.grid_rows = []; this.grid_rows = [];
@@ -652,7 +653,7 @@ frappe.ui.form.Grid = Class.extend({
var btn = this.custom_buttons[label]; var btn = this.custom_buttons[label];
if(!btn) { if(!btn) {
btn = $('<button class="btn btn-default btn-xs btn-custom">' + label + '</button>') btn = $('<button class="btn btn-default btn-xs btn-custom">' + label + '</button>')
.css('margin-right', '10px')
.css('margin-right', '4px')
.prependTo(this.grid_buttons) .prependTo(this.grid_buttons)
.on('click', click); .on('click', click);
this.custom_buttons[label] = btn; this.custom_buttons[label] = btn;


+ 9
- 1
frappe/public/js/frappe/form/grid_row.js Näytä tiedosto

@@ -2,10 +2,10 @@ frappe.ui.form.GridRow = Class.extend({
init: function(opts) { init: function(opts) {
this.on_grid_fields_dict = {}; this.on_grid_fields_dict = {};
this.on_grid_fields = []; this.on_grid_fields = [];
this.row_check_html = '<input type="checkbox" class="grid-row-check pull-left">';
this.columns = {}; this.columns = {};
this.columns_list = []; this.columns_list = [];
$.extend(this, opts); $.extend(this, opts);
this.row_check_html = '<input type="checkbox" class="grid-row-check pull-left">';
this.make(); this.make();
}, },
make: function() { make: function() {
@@ -121,6 +121,8 @@ frappe.ui.form.GridRow = Class.extend({
if(this.grid_form) { if(this.grid_form) {
this.grid_form.layout && this.grid_form.layout.refresh(this.doc); this.grid_form.layout && this.grid_form.layout.refresh(this.doc);
} }

this.toggle_check();
}, },
render_template: function() { render_template: function() {
this.set_row_index(); this.set_row_index();
@@ -592,4 +594,10 @@ frappe.ui.form.GridRow = Class.extend({
toggle_editable: function(fieldname, editable) { toggle_editable: function(fieldname, editable) {
this.set_field_property(fieldname, 'read_only', editable ? 0 : 1); this.set_field_property(fieldname, 'read_only', editable ? 0 : 1);
}, },
toggle_check: function() {
// to hide checkbox if grid is not editable
this.wrapper
.find('.grid-row-check')
.css("display", this.grid.is_editable()? 'block':'none');
}
}); });

+ 6
- 1
frappe/public/js/frappe/form/layout.js Näytä tiedosto

@@ -441,7 +441,12 @@ frappe.ui.form.Layout = Class.extend({
var parent = this.frm ? this.frm.doc : null; var parent = this.frm ? this.frm.doc : null;


if(expression.substr(0,5)=='eval:') { if(expression.substr(0,5)=='eval:') {
out = eval(expression.substr(5));
try {
out = eval(expression.substr(5));
} catch(e) {
frappe.throw(__('Invalid "depends_on" expression'));
}

} else if(expression.substr(0,3)=='fn:' && this.frm) { } else if(expression.substr(0,3)=='fn:' && this.frm) {
out = this.frm.script_manager.trigger(expression.substr(3), this.doctype, this.docname); out = this.frm.script_manager.trigger(expression.substr(3), this.doctype, this.docname);
} else { } else {


+ 2
- 1
frappe/public/js/frappe/form/share.js Näytä tiedosto

@@ -140,7 +140,8 @@ frappe.ui.form.Share = Class.extend({
user: user, user: user,
read: $(d.body).find(".add-share-read").prop("checked") ? 1 : 0, read: $(d.body).find(".add-share-read").prop("checked") ? 1 : 0,
write: $(d.body).find(".add-share-write").prop("checked") ? 1 : 0, write: $(d.body).find(".add-share-write").prop("checked") ? 1 : 0,
share: $(d.body).find(".add-share-share").prop("checked") ? 1 : 0
share: $(d.body).find(".add-share-share").prop("checked") ? 1 : 0,
notify: $(d.body).find(".add-share-notify").prop("checked") ? 1 : 0
}, },
btn: this, btn: this,
callback: function(r) { callback: function(r) {


+ 3
- 1
frappe/public/js/frappe/form/templates/grid_body.html Näytä tiedosto

@@ -7,7 +7,9 @@
<div class="small form-clickable-section grid-footer"> <div class="small form-clickable-section grid-footer">
<div class="row"> <div class="row">
<div class="col-sm-6 grid-buttons"> <div class="col-sm-6 grid-buttons">
<button type="reset" class="btn btn-xs btn-danger grid-remove-rows hide">
<button type="reset"
class="btn btn-xs btn-danger grid-remove-rows hide"
style="margin-right: 4px;">
{%= __("Delete") %}</button> {%= __("Delete") %}</button>
<button type="reset" <button type="reset"
class="grid-add-multiple-rows btn btn-xs btn-default hide" class="grid-add-multiple-rows btn btn-xs btn-default hide"


+ 15
- 3
frappe/public/js/frappe/form/templates/set_sharing.html Näytä tiedosto

@@ -49,7 +49,19 @@
<div class="col-xs-2"><input type="checkbox" class="add-share-share" name="share"></div> <div class="col-xs-2"><input type="checkbox" class="add-share-share" name="share"></div>
</div> </div>
<p> <p>
<button class="btn btn-primary btn-add-share">{%= __("Add") %}</button>
<button class="btn btn-primary btn-add-share">{{ __("Add") }}</button>
</p> </p>
{% } %}
</div>
<div class="row">
<div class="col-xs-6"></div>
<div class="col-xs-6">
<div class="checkbox">
<label><span class="input-area">
<input type="checkbox" class="add-share-notify"
name="notify"></span>
<span class="label-area small">{{ __("Notify by email") }}</span>
</label>
</div>
</div>
</div>
{% endif %}
</div>

+ 1
- 1
frappe/public/js/frappe/socketio_client.js Näytä tiedosto

@@ -73,7 +73,7 @@ frappe.socketio = {
frappe.socketio.doc_subscribe(frm.doctype, frm.docname); frappe.socketio.doc_subscribe(frm.doctype, frm.docname);
}); });


$(document).on("form_refresh", function(e, frm) {
$(document).on("form-refresh", function(e, frm) {
if (frm.is_new()) { if (frm.is_new()) {
return; return;
} }


+ 94
- 0
frappe/public/js/frappe/ui/capture.js Näytä tiedosto

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

+ 1
- 1
frappe/public/js/frappe/ui/toolbar/notifications.js Näytä tiedosto

@@ -43,7 +43,7 @@ frappe.ui.notifications = {


// switch colour on the navbar and disable if no notifications // switch colour on the navbar and disable if no notifications
$(".navbar-new-comments") $(".navbar-new-comments")
.html(this.total > 20 ? '20+' : this.total)
.html(this.total > 99 ? '99+' : this.total)
.toggleClass("navbar-new-comments-true", this.total ? true : false) .toggleClass("navbar-new-comments-true", this.total ? true : false)
.parent().toggleClass("disabled", this.total ? false : true); .parent().toggleClass("disabled", this.total ? false : true);
}, },


+ 1
- 1
frappe/public/js/frappe/ui/toolbar/search_utils.js Näytä tiedosto

@@ -51,7 +51,7 @@ frappe.search.utils = {
var out = { var out = {
route: match[1] route: match[1]
} }
if(match[1][0]==='Form') {
if(match[1][0]==='Form' && match[1][2]) {
if(match[1][1] !== match[1][2]) { if(match[1][1] !== match[1][2]) {
out.label = __(match[1][1]) + " " + match[1][2].bold(); out.label = __(match[1][1]) + " " + match[1][2].bold();
out.value = __(match[1][1]) + " " + match[1][2]; out.value = __(match[1][1]) + " " + match[1][2];


+ 8
- 3
frappe/public/js/frappe/upload.js Näytä tiedosto

@@ -12,7 +12,13 @@ frappe.upload = {


// whether to show public/private checkbox or not // whether to show public/private checkbox or not
opts.show_private = !("is_private" in opts); opts.show_private = !("is_private" in opts);

// make private by default
if (!("options" in opts) || ("options" in opts &&
(!opts.options.toLowerCase()=="public" && !opts.options.toLowerCase()=="image"))) {
opts.is_private = 1;
}
var d = null; var d = null;
// create new dialog if no parent given // create new dialog if no parent given
if(!opts.parent) { if(!opts.parent) {
@@ -237,7 +243,6 @@ frappe.upload = {
if (args.file_size) { if (args.file_size) {
frappe.upload.validate_max_file_size(args.file_size); frappe.upload.validate_max_file_size(args.file_size);
} }

if(opts.on_attach) { if(opts.on_attach) {
opts.on_attach(args) opts.on_attach(args)
} else { } else {
@@ -252,7 +257,7 @@ frappe.upload = {
frappe.upload.upload_to_server(fileobj, args, opts); frappe.upload.upload_to_server(fileobj, args, opts);
}, __("Private or Public?")); }, __("Private or Public?"));
} else { } else {
if ("is_private" in opts) {
if (!("is_private" in args) && "is_private" in opts) {
args["is_private"] = opts.is_private; args["is_private"] = opts.is_private;
} }




+ 8
- 4
frappe/public/js/frappe/views/image/image_view.js Näytä tiedosto

@@ -14,6 +14,12 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({
this._super(); this._super();
this.page_title = this.page_title + ' ' + __('Images'); this.page_title = this.page_title + ' ' + __('Images');
}, },
prepare_data: function(data) {
data = this._super(data);
// absolute url if cordova, else relative
data._image_url = this.get_image_url(data);
return data;
},
render_image_view: function () { render_image_view: function () {
var html = this.items.map(this.render_item.bind(this)).join(""); var html = this.items.map(this.render_item.bind(this)).join("");
this.container = $('<div>') this.container = $('<div>')
@@ -22,14 +28,12 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({
this.container.append(html); this.container.append(html);
}, },
render_item: function (item) { render_item: function (item) {
var image_url = this.get_image_url(item);
var indicator = this.get_indicator_html(item); var indicator = this.get_indicator_html(item);
return frappe.render_template("image_view_item_row", { return frappe.render_template("image_view_item_row", {
data: item, data: item,
indicator: indicator, indicator: indicator,
subject: this.get_subject_html(item, true), subject: this.get_subject_html(item, true),
additional_columns: this.additional_columns, additional_columns: this.additional_columns,
item_image: image_url,
color: frappe.get_palette(item.item_name) color: frappe.get_palette(item.item_name)
}); });
}, },
@@ -112,8 +116,8 @@ frappe.views.GalleryView = Class.extend({
} }


return { return {
src: i.image,
msrc: i.image,
src: i._image_url,
msrc: i._image_url,
name: i.name, name: i.name,
w: width, w: width,
h: height, h: height,


+ 4
- 4
frappe/public/js/frappe/views/image/image_view_item_row.html Näytä tiedosto

@@ -13,18 +13,18 @@
<div class="image-field" <div class="image-field"
data-name="{{ data.name }}" data-name="{{ data.name }}"
style=" style="
{% if (!item_image) { %}
{% if (!data._image_url) { %}
background-color: {{ color }}; background-color: {{ color }};
{% } %} {% } %}
border: 0px;" border: 0px;"
> >
{% if (!item_image) { %}
{% if (!data._image_url) { %}
<span class="placeholder-text"> <span class="placeholder-text">
{%= frappe.get_abbr(data._title) %} {%= frappe.get_abbr(data._title) %}
</span> </span>
{% } %} {% } %}
{% if (item_image) { %}
<img data-name="{{ data.name }}" src="{{ item_image }}" alt="{{data.title}}">
{% if (data._image_url) { %}
<img data-name="{{ data.name }}" src="{{ data._image_url }}" alt="{{data.title}}">
{% } %} {% } %}
<button class="btn btn-default zoom-view" data-name="{{data.name}}"> <button class="btn btn-default zoom-view" data-name="{{data.name}}">
<i class="fa fa-search-plus"></i> <i class="fa fa-search-plus"></i>


+ 10
- 2
frappe/public/js/frappe/views/treeview.js Näytä tiedosto

@@ -286,7 +286,7 @@ frappe.views.TreeView = Class.extend({
frappe.msgprint(__("You are not allowed to print this report")); frappe.msgprint(__("You are not allowed to print this report"));
return false; return false;
} }
var tree = $(".tree:visible").html();
var tree = $(".tree:visible").html();
var me = this; var me = this;
frappe.ui.get_print_settings(false, function(print_settings) { frappe.ui.get_print_settings(false, function(print_settings) {
var title = __(me.docname || me.doctype); var title = __(me.docname || me.doctype);
@@ -339,7 +339,15 @@ frappe.views.TreeView = Class.extend({
if (has_perm) { if (has_perm) {
me.page.add_menu_item(menu_item["label"], menu_item["action"]); me.page.add_menu_item(menu_item["label"], menu_item["action"]);
} }
})
});

// last menu item
me.page.add_menu_item(__('Add to Desktop'), () => {
const label = me.doctype === 'Account' ?
__('Chart of Accounts') :
__(me.doctype);
frappe.add_to_desktop(label, me.doctype);
});
} }
}); });




+ 1
- 1
frappe/public/js/legacy/clientscriptAPI.js Näytä tiedosto

@@ -238,7 +238,7 @@ _f.Frm.prototype.set_query = function(fieldname, opt1, opt2) {
} }


_f.Frm.prototype.set_value_if_missing = function(field, value) { _f.Frm.prototype.set_value_if_missing = function(field, value) {
this.set_value(field, value, true);
return this.set_value(field, value, true);
} }


_f.Frm.prototype.clear_table = function(fieldname) { _f.Frm.prototype.clear_table = function(fieldname) {


+ 1
- 1
frappe/public/js/legacy/form.js Näytä tiedosto

@@ -497,7 +497,7 @@ _f.Frm.prototype.render_form = function(is_a_different_doc) {


// trigger global trigger // trigger global trigger
// to use this // to use this
$(document).trigger('form_refresh', [this]);
$(document).trigger('form-refresh', [this]);


// fields // fields
this.refresh_fields(); this.refresh_fields();


+ 2
- 0
frappe/public/js/lib/jquery.jrumble.min.js Näytä tiedosto

@@ -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
- 0
frappe/public/js/lib/webcam.min.js
File diff suppressed because it is too large
Näytä tiedosto


+ 7
- 3
frappe/public/less/desk.less Näytä tiedosto

@@ -195,9 +195,9 @@ textarea.form-control {
width: 100%; width: 100%;


&> ul { &> ul {
z-index: 1041;
z-index: 1041 !important;
transition: none; transition: none;
background: #fff;
background-color: #fff;
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
@@ -479,7 +479,11 @@ li.user-progress {
} }


.frappe-rtl input ,.frappe-rtl textarea { .frappe-rtl input ,.frappe-rtl textarea {
direction: rtl
direction: rtl;
}

.frappe-rtl .checkbox .disp-area {
margin-right: -20px;
} }


.text-editor { .text-editor {


+ 20
- 0
frappe/public/less/list.less Näytä tiedosto

@@ -361,6 +361,11 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;


.image-view-row {
display: flex;
border-bottom: 1px solid #ebeff2;
}

.image-view-item { .image-view-item {
flex: 0 0 100%/4; flex: 0 0 100%/4;
padding: 15px; padding: 15px;
@@ -431,6 +436,21 @@
} }
} }


.item-selector {
border: 1px solid @border-color;

.image-view-row {
width: 100%;
}
.image-field {
height: 120px;
}

.placeholder-text {
font-size: 48px;
}
}

.image-view-container.three-column { .image-view-container.three-column {
.image-view-item { .image-view-item {
flex: 0 0 100%/3; flex: 0 0 100%/3;


+ 5
- 0
frappe/public/less/website.less Näytä tiedosto

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

.blog-list-item {
padding-top: 30px;
padding-bottom: 30px;
}

+ 2
- 1
frappe/sessions.py Näytä tiedosto

@@ -53,7 +53,7 @@ def clear_global_cache():
frappe.model.meta.clear_cache() frappe.model.meta.clear_cache()
frappe.cache().delete_value(["app_hooks", "installed_apps", frappe.cache().delete_value(["app_hooks", "installed_apps",
"app_modules", "module_app", "notification_config", 'system_settings' "app_modules", "module_app", "notification_config", 'system_settings'
'scheduler_events', 'time_zone'])
'scheduler_events', 'time_zone', 'webhooks'])
frappe.setup_module_map() frappe.setup_module_map()




@@ -163,6 +163,7 @@ def get():
# check only when clear cache is done, and don't cache this # check only when clear cache is done, and don't cache this
if frappe.local.request: if frappe.local.request:
bootinfo["change_log"] = get_change_log() bootinfo["change_log"] = get_change_log()
bootinfo["in_setup_wizard"] = not cint(frappe.db.get_single_value('System Settings', 'setup_complete'))
bootinfo["is_first_startup"] = cint(frappe.db.get_single_value('System Settings', 'is_first_startup')) bootinfo["is_first_startup"] = cint(frappe.db.get_single_value('System Settings', 'is_first_startup'))


bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version") bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version")


+ 19
- 1
frappe/share.py Näytä tiedosto

@@ -7,7 +7,7 @@ from frappe import _
from frappe.utils import cint from frappe.utils import cint


@frappe.whitelist() @frappe.whitelist()
def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=None):
def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=None, notify=0):
"""Share the given document with a user.""" """Share the given document with a user."""
if not user: if not user:
user = frappe.session.user user = frappe.session.user
@@ -39,6 +39,7 @@ def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=No
}) })


doc.save(ignore_permissions=True) doc.save(ignore_permissions=True)
notify_assignment(user, doctype, name, description=None, notify=notify)


return doc return doc


@@ -134,3 +135,20 @@ def check_share_permission(doctype, name):
"""Check if the user can share with other users""" """Check if the user can share with other users"""
if not frappe.has_permission(doctype, ptype="share", doc=name): if not frappe.has_permission(doctype, ptype="share", doc=name):
frappe.throw(_("No permission to {0} {1} {2}".format("share", doctype, name)), frappe.PermissionError) frappe.throw(_("No permission to {0} {1} {2}".format("share", doctype, name)), frappe.PermissionError)

def notify_assignment(shared_by, doc_type, doc_name, description=None, notify=0):

if not (shared_by and doc_type and doc_name): return

from frappe.utils import get_link_to_form
document = get_link_to_form(doc_type, doc_name, label="%s: %s" % (doc_type, doc_name))

arg = {
'contact': shared_by,
'txt': _("A new document {0} has been shared by with you {1}.").format(document,
shared_by),
'notify': notify
}

from frappe.desk.page.chat import chat
chat.post(**arg)

+ 4
- 2
frappe/templates/includes/blog/blogger.html Näytä tiedosto

@@ -1,11 +1,13 @@
<div class="blogger"> <div class="blogger">
<div class="inline-block" style="vertical-align: top"> <div class="inline-block" style="vertical-align: top">
<div class="avatar avatar-large"> <div class="avatar avatar-large">
<img itemprop="thumbnailUrl" src="{{ blogger_info.avatar }}" />
<img itemprop="thumbnailUrl" src="{{ blogger_info.avatar or "/assets/frappe/images/default-avatar.png" }}" />
</div> </div>
</div> </div>
<div class="inline-block" style="width: calc(100% - 100px)"> <div class="inline-block" style="width: calc(100% - 100px)">
<h2 class="blogger-name"><a href="/blog?blogger={{ blogger_info.name }}">{{ blogger_info.full_name }}</a></h2>
<h3 class="blogger-name text-muted">
<a href="/blog?blogger={{ blogger_info.name }}">{{ blogger_info.full_name }}</a>
</h3>
<p class="text-muted">{%if blogger_info.bio %}{{ blogger_info.bio }}{% endif %}</p> <p class="text-muted">{%if blogger_info.bio %}{{ blogger_info.bio }}{% endif %}</p>
</div> </div>
</div> </div>

+ 1
- 1
frappe/templates/includes/list/list.html Näytä tiedosto

@@ -1,5 +1,5 @@
{% if sub_title %} {% if sub_title %}
<p class="lead">{{ sub_title }}</p>
<h4 class="text-muted">{{ sub_title }}</h4>
{% endif %} {% endif %}
{% if not result -%} {% if not result -%}
<div class="text-muted no-results" style="min-height: 300px;"> <div class="text-muted no-results" style="min-height: 300px;">


+ 18
- 12
frappe/test_runner.py Näytä tiedosto

@@ -13,6 +13,7 @@ import frappe.utils.scheduler
import cProfile, pstats import cProfile, pstats
from six import StringIO from six import StringIO
from six.moves import reload_module from six.moves import reload_module
from frappe.model.naming import revert_series_if_last


unittest_runner = unittest.TextTestRunner unittest_runner = unittest.TextTestRunner


@@ -223,8 +224,6 @@ def make_test_records(doctype, verbose=0, force=False):
continue continue


if not options in frappe.local.test_objects: if not options in frappe.local.test_objects:
if options in frappe.local.test_objects:
print("No test records or circular reference for {0}".format(options))
frappe.local.test_objects[options] = [] frappe.local.test_objects[options] = []
make_test_records(options, verbose, force) make_test_records(options, verbose, force)
make_test_records_for_doctype(options, verbose, force) make_test_records_for_doctype(options, verbose, force)
@@ -287,6 +286,11 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
'''Make test objects from given list of `test_records` or from `test_records.json`''' '''Make test objects from given list of `test_records` or from `test_records.json`'''
records = [] records = []


def revert_naming(d):
if getattr(d, 'naming_series', None):
revert_series_if_last(d.naming_series, d.name)


if test_records is None: if test_records is None:
test_records = frappe.get_test_records(doctype) test_records = frappe.get_test_records(doctype)


@@ -296,16 +300,19 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False):


d = frappe.copy_doc(doc) d = frappe.copy_doc(doc)


if d.meta.get_field("naming_series"):
if not d.naming_series:
d.naming_series = "_T-" + d.doctype + "-"

if doc.get('name'): if doc.get('name'):
d.name = doc.get('name') d.name = doc.get('name')
else:
d.set_new_name()


if frappe.local.test_objects.get(d.doctype) and not reset:
if frappe.db.exists(d.doctype, d.name) and not reset:
frappe.db.rollback()
# do not create test records, if already exists # do not create test records, if already exists
return []

if d.meta.get_field("naming_series"):
if not d.naming_series:
d.naming_series = "_T-" + d.doctype + "-"
continue


# submit if docstatus is set to 1 for test record # submit if docstatus is set to 1 for test record
docstatus = d.docstatus docstatus = d.docstatus
@@ -320,18 +327,17 @@ def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
d.submit() d.submit()


except frappe.NameError: except frappe.NameError:
pass
revert_naming(d)


except Exception as e: except Exception as e:
if d.flags.ignore_these_exceptions_in_test and e.__class__ in d.flags.ignore_these_exceptions_in_test: if d.flags.ignore_these_exceptions_in_test and e.__class__ in d.flags.ignore_these_exceptions_in_test:
pass

revert_naming(d)
else: else:
raise raise


records.append(d.name) records.append(d.name)


frappe.db.commit()
frappe.db.commit()


return records return records




+ 1
- 0
frappe/tests/test_domainification.py Näytä tiedosto

@@ -20,6 +20,7 @@ class TestDomainification(unittest.TestCase):


def tearDown(self): def tearDown(self):
frappe.db.sql("delete from tabRole where name='_Test Role'") frappe.db.sql("delete from tabRole where name='_Test Role'")
frappe.db.sql("delete from `tabHas Role` where role='_Test Role'")
frappe.db.sql("delete from tabDomain where name in ('_Test Domain 1', '_Test Domain 2')") frappe.db.sql("delete from tabDomain where name in ('_Test Domain 1', '_Test Domain 2')")
frappe.delete_doc('DocType', 'Test Domainification') frappe.delete_doc('DocType', 'Test Domainification')




+ 2
- 2
frappe/translate.py Näytä tiedosto

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


def get_bench_dir(): def get_bench_dir():


+ 218
- 186
frappe/translations/af.csv
File diff suppressed because it is too large
Näytä tiedosto


+ 219
- 186
frappe/translations/am.csv
File diff suppressed because it is too large
Näytä tiedosto


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

Ladataan…
Peruuta
Tallenna