Kaynağa Gözat

Merge branch 'staging'

version-14
Nabin Hait 7 yıl önce
ebeveyn
işleme
311dff662a
100 değiştirilmiş dosya ile 2603 ekleme ve 853 silme
  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 Dosyayı Görüntüle

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


BIN
.github/logo.png Dosyayı Görüntüle

Önce Sonra
Genişlik: 615  |  Yükseklik: 530  |  Boyut: 12 KiB

+ 2
- 0
.gitignore Dosyayı Görüntüle

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

+ 3
- 0
.vscode/settings.json Dosyayı Görüntüle

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

+ 21
- 0
LICENSE Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

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

+ 28
- 5
README.md Dosyayı Görüntüle

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

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

### Installation

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

### License

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

+ 3
- 3
frappe/__init__.py Dosyayı Görüntüle

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

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

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

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

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

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

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


+ 3
- 4
frappe/build.js Dosyayı Görüntüle

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

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


+ 25
- 6
frappe/build.py Dosyayı Görüntüle

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

from six import iteritems, text_type

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

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

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

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

# time.sleep(3)

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

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

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


+ 3
- 2
frappe/commands/utils.py Dosyayı Görüntüle

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

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

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


+ 6
- 0
frappe/config/integrations.py Dosyayı Görüntüle

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

]
}
]

+ 2
- 1
frappe/contacts/doctype/address/address.json Dosyayı Görüntüle

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


+ 10
- 7
frappe/core/doctype/communication/communication.py Dosyayı Görüntüle

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

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

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

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

if not user: user = frappe.session.user

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

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


+ 0
- 3
frappe/core/doctype/communication/email.py Dosyayı Görüntüle

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

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

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


+ 302
- 242
frappe/core/doctype/docshare/docshare.json Dosyayı Görüntüle

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

+ 13
- 0
frappe/core/doctype/doctype/doctype.py Dosyayı Görüntüle

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

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

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

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

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

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


+ 37
- 4
frappe/core/doctype/doctype/test_doctype.py Dosyayı Görüntüle

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


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

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

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

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

def test_all_depends_on_fields_conditions(self):
import re

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

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

+ 5
- 3
frappe/core/doctype/domain_settings/domain_settings.js Dosyayı Görüntüle

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

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


+ 1
- 1
frappe/core/doctype/language/language.py Dosyayı Görüntüle

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

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

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


+ 0
- 5
frappe/core/doctype/user/test_user.py Dosyayı Görüntüle

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


+ 1
- 1
frappe/core/page/data_import_tool/data_import_tool.py Dosyayı Görüntüle

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

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



+ 1
- 1
frappe/core/page/data_import_tool/exporter.py Dosyayı Görüntüle

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

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

_column_start_end = frappe._dict(start=0)



+ 33
- 33
frappe/core/page/data_import_tool/test_exporter_fixtures.py Dosyayı Görüntüle

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


+ 117
- 3
frappe/core/page/desktop/desktop.js Dosyayı Görüntüle

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

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

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

frappe.desktop.setup_wiggle();
},

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

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

$icons.trigger('stopRumble');

frappe.desktop.wiggling = false;
};

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

frappe.desktop.wrapper.on('mousedown', '.app-icon', () => {
timer_id = setTimeout(() => {
frappe.desktop.wiggling = true;
// hide all notifications.
$notis.hide();
$cases.each((i) => {
const $case = $($cases[i]);
const template =
`
<div class="circle module-remove" style="background-color:#E0E0E0; color:#212121">
<div class="circle-text">
<b>
&times
</b>
</div>
</div>
`;

$case.append(template);
const $close = $case.find('.module-remove');
const name = $case.attr('title');
$close.click(() => {
// good enough to create dynamic dialogs?
const dialog = new frappe.ui.Dialog({
title: __(`Hide ${name}?`)
});
dialog.set_primary_action(__('Hide'), () => {
frappe.call({
method: 'frappe.desk.doctype.desktop_icon.desktop_icon.hide',
args: { name: name },
freeze: true,
callback: (response) =>
{
if ( response.message ) {
location.reload();
}
}
})

dialog.hide();
clearWiggle();
});
// Hacks, Hacks and Hacks.
var $cancel = dialog.get_close_btn();
$cancel.click(() => {
clearWiggle();
});
$cancel.html(__(`Cancel`));

dialog.show();
});
});
$icons.trigger('startRumble');
}, DURATION_LONG_PRESS);
});
frappe.desktop.wrapper.on('mouseup mouseleave', '.app-icon', () => {
clearTimeout(timer_id);
});

// also stop wiggling if clicked elsewhere.
$('body').click((event) => {
if ( frappe.desktop.wiggling ) {
const $target = $(event.target);
// our target shouldn't be .app-icons or .close
const $parent = $target.parents('.case-wrapper');
if ( $parent.length == 0 )
clearWiggle();
}
});
// end wiggle
},

open_module: function(parent) {
@@ -212,8 +326,8 @@ $.extend(frappe.desktop, {
notifier.toggle(sum ? true : false);
var circle = notifier.find(".circle-text");
var text = sum || '';
if(text > 20) {
text = '20+';
if(text > 99) {
text = '99+';
}

if(circle.length) {


+ 2
- 2
frappe/custom/doctype/custom_field/custom_field.py Dosyayı Görüntüle

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

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


+ 1
- 0
frappe/data/Framework.sql Dosyayı Görüntüle

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


+ 13
- 1
frappe/defaults.py Dosyayı Görüntüle

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



+ 14
- 1
frappe/desk/doctype/desktop_icon/desktop_icon.py Dosyayı Görüntüle

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

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

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

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

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

return True

+ 23
- 0
frappe/desk/doctype/todo/test_todo.js Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

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


+ 1
- 1
frappe/desk/form/linked_with.py Dosyayı Görüntüle

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

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


+ 33
- 23
frappe/desk/page/setup_wizard/setup_wizard.js Dosyayı Görüntüle

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

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

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

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

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

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

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

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

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

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

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


+ 1
- 2
frappe/desk/reportview.py Dosyayı Görüntüle

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

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

return stats

@frappe.whitelist()


BIN
frappe/docs/assets/img/webhook.png Dosyayı Görüntüle

Önce Sonra
Genişlik: 1301  |  Yükseklik: 675  |  Boyut: 51 KiB

+ 103
- 0
frappe/docs/user/en/guides/integration/webhooks.md Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

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

except SentEmailInInbox:
frappe.db.rollback()


+ 24
- 8
frappe/email/doctype/email_alert/email_alert.js Dosyayı Görüntüle

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

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

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

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

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

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

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


+ 74
- 11
frappe/email/doctype/email_alert/email_alert.json Dosyayı Görüntüle

@@ -711,8 +711,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "attach_print",
"fieldtype": "Check",
"fieldname": "message_examples",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -720,7 +720,68 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Attach Print",
"label": "Message Examples",
"length": 0,
"no_copy": 0,
"options": "<h5>Message Example</h5>\n\n<pre>&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,
"no_copy": 0,
"permlevel": 0,
@@ -741,8 +802,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "message_examples",
"fieldtype": "HTML",
"fieldname": "attach_print",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -750,11 +811,11 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Message Examples",
"label": "Attach Print",
"length": 0,
"no_copy": 0,
"options": "<h5>Message Example</h5>\n\n<pre>&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,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -771,8 +832,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "view_properties",
"fieldtype": "Button",
"depends_on": "attach_print",
"fieldname": "print_format",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -780,9 +842,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "View Properties (via Customize Form)",
"label": "Print Format",
"length": 0,
"no_copy": 0,
"options": "Print Format",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -808,7 +871,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-08-13 22:43:49.079330",
"modified": "2017-09-26 20:10:00.061780",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Alert",


+ 1
- 1
frappe/email/doctype/email_alert/email_alert.py Dosyayı Görüntüle

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

context = get_context(doc)
recipients = []


+ 3
- 3
frappe/email/doctype/email_queue/email_queue.json Dosyayı Görüntüle

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


+ 2
- 2
frappe/email/queue.py Dosyayı Görüntüle

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

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

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



+ 1
- 1
frappe/hooks.py Dosyayı Görüntüle

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

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

app_email = "info@frappe.io"



+ 1
- 1
frappe/integrations/doctype/dropbox_settings/dropbox_settings.py Dosyayı Görüntüle

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

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


+ 2
- 2
frappe/integrations/doctype/stripe_settings/stripe_settings.py Dosyayı Görüntüle

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

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


+ 57
- 0
frappe/integrations/doctype/webhook/__init__.py Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle


+ 130
- 0
frappe/integrations/doctype/webhook_data/webhook_data.json Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle


+ 101
- 0
frappe/integrations/doctype/webhook_header/webhook_header.json Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

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

# once_only validation
# methods

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

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

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

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

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

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

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

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

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

raise ImportError(arg1)
raise ImportError(doctype)

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

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

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

self.load_from_db()
return

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

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

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

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

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

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

self.flags.name_set = True

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

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

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

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

return out

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

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

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


+ 2
- 0
frappe/patches/v6_20x/remove_roles_from_website_user.py Dosyayı Görüntüle

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

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


+ 8
- 1
frappe/public/build.json Dosyayı Görüntüle

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

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

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

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


+ 5
- 2
frappe/public/css/desk.css Dosyayı Görüntüle

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


+ 16
- 0
frappe/public/css/list.css Dosyayı Görüntüle

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


+ 8
- 0
frappe/public/css/report-rtl.css Dosyayı Görüntüle

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

.chart_area{
direction: ltr;
}

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

+ 4
- 0
frappe/public/css/website.css Dosyayı Görüntüle

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

BIN
frappe/public/images/default-avatar.png Dosyayı Görüntüle

Önce Sonra
Genişlik: 400  |  Yükseklik: 400  |  Boyut: 44 KiB

+ 1
- 1
frappe/public/js/frappe/assets.js Dosyayı Görüntüle

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

// check if the asset exists in


+ 2
- 2
frappe/public/js/frappe/desk.js Dosyayı Görüntüle

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



+ 1
- 1
frappe/public/js/frappe/form/controls/check.js Dosyayı Görüntüle

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


+ 4
- 2
frappe/public/js/frappe/form/controls/color.js Dosyayı Görüntüle

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

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

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


+ 44
- 5
frappe/public/js/frappe/form/controls/text_editor.js Dosyayı Görüntüle

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

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

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

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

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

this.setting_count += 1;

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

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

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


+ 3
- 2
frappe/public/js/frappe/form/grid.js Dosyayı Görüntüle

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

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

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


+ 9
- 1
frappe/public/js/frappe/form/grid_row.js Dosyayı Görüntüle

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

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

+ 6
- 1
frappe/public/js/frappe/form/layout.js Dosyayı Görüntüle

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

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

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


+ 2
- 1
frappe/public/js/frappe/form/share.js Dosyayı Görüntüle

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


+ 3
- 1
frappe/public/js/frappe/form/templates/grid_body.html Dosyayı Görüntüle

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


+ 15
- 3
frappe/public/js/frappe/form/templates/set_sharing.html Dosyayı Görüntüle

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

+ 1
- 1
frappe/public/js/frappe/socketio_client.js Dosyayı Görüntüle

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

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


+ 94
- 0
frappe/public/js/frappe/ui/capture.js Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

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

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


+ 1
- 1
frappe/public/js/frappe/ui/toolbar/search_utils.js Dosyayı Görüntüle

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


+ 8
- 3
frappe/public/js/frappe/upload.js Dosyayı Görüntüle

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

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

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

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



+ 8
- 4
frappe/public/js/frappe/views/image/image_view.js Dosyayı Görüntüle

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

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


+ 4
- 4
frappe/public/js/frappe/views/image/image_view_item_row.html Dosyayı Görüntüle

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


+ 10
- 2
frappe/public/js/frappe/views/treeview.js Dosyayı Görüntüle

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

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



+ 1
- 1
frappe/public/js/legacy/clientscriptAPI.js Dosyayı Görüntüle

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

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

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


+ 1
- 1
frappe/public/js/legacy/form.js Dosyayı Görüntüle

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

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

// fields
this.refresh_fields();


+ 2
- 0
frappe/public/js/lib/jquery.jrumble.min.js Dosyayı Görüntüle

@@ -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
Dosya farkı çok büyük olduğundan ihmal edildi
Dosyayı Görüntüle


+ 7
- 3
frappe/public/less/desk.less Dosyayı Görüntüle

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

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

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

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

.text-editor {


+ 20
- 0
frappe/public/less/list.less Dosyayı Görüntüle

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

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

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

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

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

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

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


+ 5
- 0
frappe/public/less/website.less Dosyayı Görüntüle

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

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

+ 2
- 1
frappe/sessions.py Dosyayı Görüntüle

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


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

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


+ 19
- 1
frappe/share.py Dosyayı Görüntüle

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

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

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

return doc

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

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

if not (shared_by and doc_type and doc_name): return

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

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

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

+ 4
- 2
frappe/templates/includes/blog/blogger.html Dosyayı Görüntüle

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

+ 1
- 1
frappe/templates/includes/list/list.html Dosyayı Görüntüle

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


+ 18
- 12
frappe/test_runner.py Dosyayı Görüntüle

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

unittest_runner = unittest.TextTestRunner

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

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

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


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

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

d = frappe.copy_doc(doc)

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

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

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

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

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

except frappe.NameError:
pass
revert_naming(d)

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

revert_naming(d)
else:
raise

records.append(d.name)

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

return records



+ 1
- 0
frappe/tests/test_domainification.py Dosyayı Görüntüle

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

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



+ 2
- 2
frappe/translate.py Dosyayı Görüntüle

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

def get_bench_dir():


+ 218
- 186
frappe/translations/af.csv
Dosya farkı çok büyük olduğundan ihmal edildi
Dosyayı Görüntüle


+ 219
- 186
frappe/translations/am.csv
Dosya farkı çok büyük olduğundan ihmal edildi
Dosyayı Görüntüle


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor

Yükleniyor…
İptal
Kaydet