소스 검색

Merge branch 'staging'

version-14
mbauskar 8 년 전
부모
커밋
6404c6e167
100개의 변경된 파일770개의 추가작업 그리고 1032개의 파일을 삭제
  1. +3
    -1
      .eslintrc
  2. +2
    -0
      .travis.yml
  3. +1
    -1
      ISSUE_TEMPLATE.md
  4. +2
    -2
      README.md
  5. +1
    -1
      attributions.md
  6. +6
    -6
      frappe/__init__.py
  7. +1
    -1
      frappe/api.py
  8. +2
    -1
      frappe/async.py
  9. +1
    -1
      frappe/auth.py
  10. +4
    -2
      frappe/boot.py
  11. +18
    -15
      frappe/build.js
  12. +4
    -4
      frappe/build.py
  13. +0
    -1
      frappe/change_log/v6/v6_16_4.md
  14. +1
    -1
      frappe/change_log/v7/v7_2_0.md
  15. +2
    -0
      frappe/change_log/v8/v8_7_0.md
  16. +9
    -3
      frappe/client.py
  17. +2
    -2
      frappe/commands/__init__.py
  18. +16
    -37
      frappe/commands/docs.py
  19. +2
    -1
      frappe/commands/site.py
  20. +1
    -34
      frappe/config/docs.py
  21. +0
    -7
      frappe/config/setup.py
  22. +8
    -7
      frappe/core/doctype/communication/comment.py
  23. +1
    -2
      frappe/core/doctype/communication/feed.py
  24. +0
    -0
      frappe/core/doctype/doctype/boilerplate/controller._py
  25. +0
    -0
      frappe/core/doctype/doctype/boilerplate/test_controller._py
  26. +2
    -2
      frappe/core/doctype/doctype/doctype.py
  27. +2
    -2
      frappe/core/doctype/domain/domain.json
  28. +23
    -0
      frappe/core/doctype/domain/test_domain.js
  29. +7
    -3
      frappe/core/doctype/feedback_trigger/feedback_trigger.py
  30. +4
    -4
      frappe/core/doctype/file/file.py
  31. +2
    -3
      frappe/core/doctype/has_role/has_role.py
  32. +3
    -2
      frappe/core/doctype/page/page.py
  33. +33
    -0
      frappe/core/doctype/report/test_query_report.js
  34. +71
    -41
      frappe/core/doctype/system_settings/system_settings.json
  35. +1
    -0
      frappe/core/doctype/system_settings/system_settings.py
  36. +23
    -0
      frappe/core/doctype/system_settings/test_system_settings.js
  37. +5
    -1
      frappe/core/doctype/user/user.js
  38. +13
    -17
      frappe/core/doctype/user/user.py
  39. +0
    -0
      frappe/core/doctype/user_permission/__init__.py
  40. +23
    -0
      frappe/core/doctype/user_permission/test_user_permission.js
  41. +10
    -0
      frappe/core/doctype/user_permission/test_user_permission.py
  42. +10
    -0
      frappe/core/doctype/user_permission/user_permission.js
  43. +188
    -0
      frappe/core/doctype/user_permission/user_permission.json
  44. +80
    -0
      frappe/core/doctype/user_permission/user_permission.py
  45. +1
    -1
      frappe/core/page/data_import_tool/exporter.py
  46. +2
    -1
      frappe/core/page/data_import_tool/importer.py
  47. +0
    -25
      frappe/core/page/desktop/desktop.js
  48. +16
    -8
      frappe/core/page/permission_manager/permission_manager.js
  49. +3
    -13
      frappe/core/page/permission_manager/permission_manager.py
  50. +0
    -1
      frappe/core/page/user_permissions/README.md
  51. +0
    -3
      frappe/core/page/user_permissions/__init__.py
  52. +0
    -365
      frappe/core/page/user_permissions/user_permissions.js
  53. +0
    -19
      frappe/core/page/user_permissions/user_permissions.json
  54. +0
    -109
      frappe/core/page/user_permissions/user_permissions.py
  55. +6
    -6
      frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py
  56. +1
    -0
      frappe/custom/doctype/customize_form/test_customize_form.js
  57. +12
    -12
      frappe/database.py
  58. +4
    -19
      frappe/defaults.py
  59. +11
    -9
      frappe/desk/doctype/event/event.py
  60. +0
    -11
      frappe/desk/form/load.py
  61. +2
    -2
      frappe/desk/form/meta.py
  62. +3
    -2
      frappe/desk/form/run_method.py
  63. +8
    -3
      frappe/desk/page/backups/backups.py
  64. +6
    -3
      frappe/desk/page/chat/chat.py
  65. +1
    -1
      frappe/desk/page/setup_wizard/install_fixtures.py
  66. +4
    -3
      frappe/desk/query_builder.py
  67. +4
    -3
      frappe/desk/reportview.py
  68. BIN
      frappe/docs/assets/img/desk/animated_line_graph.gif
  69. BIN
      frappe/docs/assets/img/desk/bar_graph.png
  70. BIN
      frappe/docs/assets/img/desk/line_graph.png
  71. BIN
      frappe/docs/assets/img/desk/line_graph_sales.png
  72. BIN
      frappe/docs/assets/img/desk/percentage_graph.png
  73. +0
    -10
      frappe/docs/contents.html
  74. +0
    -9
      frappe/docs/contents.py
  75. +0
    -57
      frappe/docs/index.html
  76. +25
    -0
      frappe/docs/index.md
  77. +0
    -6
      frappe/docs/index.txt
  78. +0
    -30
      frappe/docs/install.md
  79. +0
    -16
      frappe/docs/license.html
  80. +2
    -0
      frappe/docs/user/en/bench/guides/diagnosing-the-scheduler.md
  81. +4
    -0
      frappe/docs/user/en/bench/guides/index.md
  82. +4
    -2
      frappe/docs/user/en/bench/guides/manual-setup.md
  83. +2
    -2
      frappe/docs/user/en/bench/guides/settings-limits.md
  84. +2
    -0
      frappe/docs/user/en/bench/guides/setup-multitenancy.md
  85. +3
    -1
      frappe/docs/user/en/bench/guides/setup-production.md
  86. +2
    -0
      frappe/docs/user/en/bench/index.md
  87. +5
    -3
      frappe/docs/user/en/bench/resources/background-services.md
  88. +2
    -0
      frappe/docs/user/en/bench/resources/bench-commands-cheatsheet.md
  89. +2
    -0
      frappe/docs/user/en/bench/resources/bench-procfile.md
  90. +4
    -0
      frappe/docs/user/en/bench/resources/index.md
  91. +3
    -1
      frappe/docs/user/en/guides/app-development/adding-custom-button-to-form.md
  92. +2
    -0
      frappe/docs/user/en/guides/app-development/adding-module-icons-on-desktop.md
  93. +4
    -2
      frappe/docs/user/en/guides/app-development/custom-module-icon.md
  94. +8
    -6
      frappe/docs/user/en/guides/app-development/dialogs-types.md
  95. +2
    -0
      frappe/docs/user/en/guides/app-development/executing-code-on-doctype-events.md
  96. +2
    -2
      frappe/docs/user/en/guides/app-development/exporting-customizations.md
  97. +3
    -3
      frappe/docs/user/en/guides/app-development/fetch-custom-field-value-from-master-to-all-related-transactions.md
  98. +13
    -57
      frappe/docs/user/en/guides/app-development/generating-docs.md
  99. +2
    -0
      frappe/docs/user/en/guides/app-development/how-enable-developer-mode-in-frappe.md
  100. +3
    -1
      frappe/docs/user/en/guides/app-development/how-to-create-custom-fields-during-app-installation.md

+ 3
- 1
.eslintrc 파일 보기

@@ -118,6 +118,8 @@
"getCookie": true,
"getCookies": true,
"get_url_arg": true,
"QUnit": true
"QUnit": true,
"Snap": true,
"mina": true
}
}

+ 2
- 0
.travis.yml 파일 보기

@@ -15,6 +15,8 @@ services:
- mysql

install:
- pip install flake8==3.3.0
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
- sudo rm /etc/apt/sources.list.d/docker.list
- sudo apt-get purge -y mysql-common mysql-server mysql-client
- nvm install v7.10.0


+ 1
- 1
ISSUE_TEMPLATE.md 파일 보기

@@ -6,4 +6,4 @@

1.

Frappe version:
Frappé version:

+ 2
- 2
README.md 파일 보기

@@ -1,4 +1,4 @@
## Frappe Framework
## Frappé Framework

[![Build Status](https://travis-ci.org/frappe/frappe.png)](https://travis-ci.org/frappe/frappe)

@@ -6,7 +6,7 @@ Full-stack web application framework that uses Python and MariaDB on the server

### Installation

[Install via Frappe Bench](https://github.com/frappe/bench)
[Install via Frappé Bench](https://github.com/frappe/bench)

## Contributing



+ 1
- 1
attributions.md 파일 보기

@@ -1,4 +1,4 @@
## Frappe framework includes these public works
## Frappé framework includes these public works

### Javascript / CSS



+ 6
- 6
frappe/__init__.py 파일 보기

@@ -6,7 +6,7 @@ globals attached to frappe module
"""
from __future__ import unicode_literals, print_function

from six import iteritems
from six import iteritems, text_type
from werkzeug.local import Local, release_local
import os, sys, importlib, inspect, json

@@ -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__ = '8.6.8'
__version__ = '8.7.0'
__title__ = "Frappe Framework"

local = Local()
@@ -57,14 +57,14 @@ def _(msg, lang=None):

def as_unicode(text, encoding='utf-8'):
'''Convert to unicode if required'''
if isinstance(text, unicode):
if isinstance(text, text_type):
return text
elif text==None:
return ''
elif isinstance(text, basestring):
return unicode(text, encoding)
return text_type(text, encoding)
else:
return unicode(text)
return text_type(text)

def get_lang_dict(fortype, name=None):
"""Returns the translated language dict for the given type and name.
@@ -880,7 +880,7 @@ def get_file_json(path):

def read_file(path, raise_not_found=False):
"""Open a file and return its content as Unicode."""
if isinstance(path, unicode):
if isinstance(path, text_type):
path = path.encode("utf-8")

if os.path.exists(path):


+ 1
- 1
frappe/api.py 파일 보기

@@ -8,7 +8,7 @@ import frappe.handler
import frappe.client
from frappe.utils.response import build_response
from frappe import _
from urlparse import urlparse
from six.moves.urllib.parse import urlparse
from urllib import urlencode

def handle():


+ 2
- 1
frappe/async.py 파일 보기

@@ -9,6 +9,7 @@ import frappe
import os
import time
import redis
from io import FileIO
from frappe.utils import get_site_path
from frappe import conf

@@ -138,7 +139,7 @@ def get_redis_server():
return redis_server


class FileAndRedisStream(file):
class FileAndRedisStream(FileIO):
def __init__(self, *args, **kwargs):
ret = super(FileAndRedisStream, self).__init__(*args, **kwargs)
self.count = 0


+ 1
- 1
frappe/auth.py 파일 보기

@@ -17,7 +17,7 @@ from frappe.translate import get_lang_code
from frappe.utils.password import check_password
from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log

from urllib import quote
from six.moves.urllib.parse import quote

class HTTPRequest:
def __init__(self):


+ 4
- 2
frappe/boot.py 파일 보기

@@ -3,7 +3,7 @@

from __future__ import unicode_literals

from six import iteritems
from six import iteritems, text_type

"""
bootstrap client session
@@ -17,6 +17,7 @@ from frappe.utils.change_log import get_versions
from frappe.translate import get_lang_dict
from frappe.email.inbox import get_email_accounts
from frappe.core.doctype.feedback_trigger.feedback_trigger import get_enabled_feedback_trigger
from frappe.core.doctype.user_permission.user_permission import get_user_permissions

def get_bootinfo():
"""build and return boot info"""
@@ -30,6 +31,7 @@ def get_bootinfo():

# system info
bootinfo.sysdefaults = frappe.defaults.get_defaults()
bootinfo.user_permissions = get_user_permissions()
bootinfo.server_date = frappe.utils.nowdate()

if frappe.session['user'] != 'Guest':
@@ -66,7 +68,7 @@ def get_bootinfo():
frappe.get_attr(method)(bootinfo)

if bootinfo.lang:
bootinfo.lang = unicode(bootinfo.lang)
bootinfo.lang = text_type(bootinfo.lang)
bootinfo.versions = {k: v['version'] for k, v in get_versions().items()}

bootinfo.error_report_email = frappe.get_hooks("error_report_email")


+ 18
- 15
frappe/build.js 파일 보기

@@ -1,3 +1,4 @@
/*eslint-disable no-console */
const path = require('path');
const fs = require('fs');
const babel = require('babel-core');
@@ -17,7 +18,7 @@ const apps_contents = fs.readFileSync(path_join(sites_path, 'apps.txt'), 'utf8')
const apps = apps_contents.split('\n');
const app_paths = apps.map(app => path_join(apps_path, app, app)) // base_path of each app
const assets_path = path_join(sites_path, 'assets');
const build_map = make_build_map();
let build_map = make_build_map();
const file_watcher_port = get_conf().file_watcher_port;

// command line args
@@ -62,6 +63,7 @@ function watch() {
io.emit('reload_js', filename);
}
});
watch_build_json();
});

io.on('connection', function (socket) {
@@ -225,11 +227,7 @@ function compile_less_file(file, less_path, public_path) {
function watch_less(ondirty) {
const less_paths = app_paths.map(path => path_join(path, 'public', 'less'));

const to_watch = [];
for (const less_path of less_paths) {
if (!fs.existsSync(less_path)) continue;
to_watch.push(less_path);
}
const to_watch = filter_valid_paths(less_paths);
chokidar.watch(to_watch).on('change', (filename, stats) => {
console.log(filename, 'dirty');
var last_index = filename.lastIndexOf('/');
@@ -255,17 +253,9 @@ function watch_less(ondirty) {
function watch_js(ondirty) {
const js_paths = app_paths.map(path => path_join(path, 'public', 'js'));

const to_watch = [];
for (const js_path of js_paths) {
if (!fs.existsSync(js_path)) continue;
to_watch.push(js_path);
}
const to_watch = filter_valid_paths(js_paths);
chokidar.watch(to_watch).on('change', (filename, stats) => {
console.log(filename, 'dirty');
var last_index = filename.lastIndexOf('/');
const js_path = filename.slice(0, last_index);
const public_path = path_join(js_path, '..');

// build the target js file for which this js/html file is input
for (const target in build_map) {
const sources = build_map[target];
@@ -278,6 +268,19 @@ function watch_js(ondirty) {
});
}

function watch_build_json() {
const build_json_paths = app_paths.map(path => path_join(path, 'public', 'build.json'));
const to_watch = filter_valid_paths(build_json_paths);
chokidar.watch(to_watch).on('change', (filename) => {
console.log(filename, 'updated');
build_map = make_build_map();
});
}

function filter_valid_paths(paths) {
return paths.filter(path => fs.existsSync(path));
}

function html_to_js_template(path, content) {
let key = path.split('/');
key = key[key.length - 1];


+ 4
- 4
frappe/build.py 파일 보기

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

from six import iteritems
from six import iteritems, text_type

"""
Build the `public` folders and setup languages
@@ -121,7 +121,7 @@ def get_build_maps():
timestamps = {}

def pack(target, sources, no_compress, verbose):
from cStringIO import StringIO
from six import StringIO

outtype, outtxt = target.split(".")[-1], ''
jsm = JavascriptMinify()
@@ -135,7 +135,7 @@ def pack(target, sources, no_compress, verbose):
timestamps[f] = os.path.getmtime(f)
try:
with open(f, 'r') as sourcefile:
data = unicode(sourcefile.read(), 'utf-8', errors='ignore')
data = text_type(sourcefile.read(), 'utf-8', errors='ignore')

extn = f.rsplit(".", 1)[1]

@@ -144,7 +144,7 @@ def pack(target, sources, no_compress, verbose):
jsm.minify(tmpin, tmpout)
minified = tmpout.getvalue()
if minified:
outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';'
outtxt += text_type(minified or '', 'utf-8').strip('\n') + ';'

if verbose:
print("{0}: {1}k".format(f, int(len(minified) / 1024)))


+ 0
- 1
frappe/change_log/v6/v6_16_4.md 파일 보기

@@ -1,2 +1 @@
- Developer Tutorial [Videos](http://frappe.github.io/frappe/user/videos/)
- Increased uploaded file size limit upto 10MB

+ 1
- 1
frappe/change_log/v7/v7_2_0.md 파일 보기

@@ -7,7 +7,7 @@
- Delete selected rows
- Map selected rows from one document to another.
- Show Totals button in report
- OpenID Connect for Frappe
- OpenID Connect for Frappé
- Threading based on message id in Email Queue
- New control object daterangepicker for filtering
- Orientation selection in PDF


+ 2
- 0
frappe/change_log/v8/v8_7_0.md 파일 보기

@@ -0,0 +1,2 @@
### User Permissions
- User Permission is now a DocType, a new UX for the existing Role and User Permission managers to make it easy to enter permissions. For more details please check <a href="https://erpnext.org/docs/user/manual/en/setting-up/users-and-permissions/user-permissions">User Permissions</a>

+ 9
- 3
frappe/client.py 파일 보기

@@ -61,16 +61,22 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False):

try:
filters = json.loads(filters)
except ValueError:
# name passed, not json
except (TypeError, ValueError):
# filters are not passesd, not json
pass

try:
fieldname = json.loads(fieldname)
except ValueError:
except (TypeError, ValueError):
# name passed, not json
fieldname = "name"
pass

# check whether the used filters were really parseable and usable
# and did not just result in an empty string or dict
if not filters:
filters = None

return frappe.db.get_value(doctype, filters, fieldname, as_dict=as_dict, debug=debug)

@frappe.whitelist()


+ 2
- 2
frappe/commands/__init__.py 파일 보기

@@ -5,11 +5,11 @@ from __future__ import unicode_literals, absolute_import, print_function
import sys
import click
import cProfile
import StringIO
import pstats
import frappe
import frappe.utils
from functools import wraps
from six import StringIO

click.disable_unicode_literals_warning = True

@@ -25,7 +25,7 @@ def pass_context(f):

if profile:
pr.disable()
s = StringIO.StringIO()
s = StringIO()
ps = pstats.Stats(pr, stream=s)\
.sort_stats('cumtime', 'tottime', 'ncalls')
ps.print_stats()


+ 16
- 37
frappe/commands/docs.py 파일 보기

@@ -1,31 +1,9 @@
from __future__ import unicode_literals, absolute_import
import click
import os
import os, shutil
import frappe
from frappe.commands import pass_context


@click.command('write-docs')
@pass_context
@click.argument('app')
@click.option('--target', default=None)
@click.option('--local', default=False, is_flag=True, help='Run app locally')
def write_docs(context, app, target=None, local=False):
"Setup docs in target folder of target app"
from frappe.utils.setup_docs import setup_docs

if not target:
target = os.path.abspath(os.path.join("..", "docs", app))

for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
make = setup_docs(app)
make.make_docs(target, local)
finally:
frappe.destroy()

@click.command('build-docs')
@pass_context
@click.argument('app')
@@ -36,23 +14,25 @@ def write_docs(context, app, target=None, local=False):
def build_docs(context, app, docs_version="current", target=None, local=False, watch=False):
"Setup docs in target folder of target app"
from frappe.utils import watch as start_watch
if not target:
target = os.path.abspath(os.path.join("..", "docs", app))
from frappe.utils.setup_docs import add_breadcrumbs_tag

for site in context.sites:
_build_docs_once(site, app, docs_version, target, local)

if watch:
def trigger_make(source_path, event_type):
if "/templates/autodoc/" in source_path:
_build_docs_once(site, app, docs_version, target, local)

elif ("/docs.css" in source_path
or "/docs/" in source_path
or "docs.py" in source_path):
_build_docs_once(site, app, docs_version, target, local, only_content_updated=True)

apps_path = frappe.get_app_path(app, "..", "..")
if "/docs/user/" in source_path:
# user file
target_path = frappe.get_app_path(target, 'www', 'docs', 'user',
os.path.relpath(source_path, start=frappe.get_app_path(app, 'docs', 'user')))
shutil.copy(source_path, target_path)
add_breadcrumbs_tag(target_path)

if source_path.endswith('/docs/index.md'):
target_path = frappe.get_app_path(target, 'www', 'docs', 'index.md')
shutil.copy(source_path, target_path)

apps_path = frappe.get_app_path(app)
start_watch(apps_path, handler=trigger_make)

def _build_docs_once(site, app, docs_version, target, local, only_content_updated=False):
@@ -62,17 +42,16 @@ def _build_docs_once(site, app, docs_version, target, local, only_content_update

frappe.init(site=site)
frappe.connect()
make = setup_docs(app)
make = setup_docs(app, target)

if not only_content_updated:
make.build(docs_version)

make.make_docs(target, local)
#make.make_docs(target, local)

finally:
frappe.destroy()

commands = [
build_docs,
write_docs,
]

+ 2
- 1
frappe/commands/site.py 파일 보기

@@ -9,6 +9,7 @@ from frappe.commands.scheduler import _is_scheduler_enabled
from frappe.limits import update_limits, get_limits
from frappe.installer import update_site_config
from frappe.utils import touch_file, get_site_path
from six import text_type

@click.command('new-site')
@click.argument('site')
@@ -428,7 +429,7 @@ def set_limit(context, site, limit, value):

@click.command('set-limits')
@click.option('--site', help='site name')
@click.option('--limit', 'limits', type=(unicode, unicode), multiple=True)
@click.option('--limit', 'limits', type=(text_type, text_type), multiple=True)
@pass_context
def set_limits(context, site, limits):
_set_limits(context, site, limits)


+ 1
- 34
frappe/config/docs.py 파일 보기

@@ -2,38 +2,5 @@

from __future__ import unicode_literals

docs_version = "7.x.x"

source_link = "https://github.com/frappe/frappe"
docs_base_url = "https://frappe.github.io/frappe"
headline = "Superhero Web Framework"
sub_heading = "Build extensions to ERPNext or make your own app"
hide_install = True
long_description = """Frappe is a full stack web application framework written in Python,
Javascript, HTML/CSS with MySQL as the backend. It was built for ERPNext
but is pretty generic and can be used to build database driven apps.

The key differece in Frappe compared to other frameworks is that Frappe
is that meta-data is also treated as data and is used to build front-ends
very easily. Frappe comes with a full blown admin UI called the **Desk**
that handles forms, navigation, lists, menus, permissions, file attachment
and much more out of the box.

Frappe also has a plug-in architecture that can be used to build plugins
to ERPNext.

Frappe Framework was designed to build [ERPNext](https://erpnext.com), open source
ERP for managing small and medium sized businesses.

[Get started with the Tutorial](https://frappe.github.io/frappe/user/)
"""
google_analytics_id = 'UA-8911157-23'

def get_context(context):
context.brand_html = ('<img class="brand-logo" src="'+context.docs_base_url
+'/assets/img/frappe-bird-white.png"> Frappé Framework</img>')
context.top_bar_items = [
{"label": "Tutorials", "url": context.docs_base_url + "/user", "right": 1},
{"label": "API", "url": context.docs_base_url + "/current", "right": 1},
{"label": "Forum", "url": 'https://discuss.erpnext.com', "right": 1}
]
docs_base_url = "/docs"

+ 0
- 7
frappe/config/setup.py 파일 보기

@@ -31,13 +31,6 @@ def get_data():
"icon": "fa fa-lock",
"description": _("Set Permissions on Document Types and Roles")
},
{
"type": "page",
"name": "user-permissions",
"label": _("User Permissions Manager"),
"icon": "fa fa-shield",
"description": _("Set Permissions per User")
},
{
"type": "page",
"name": "modules_setup",


+ 8
- 7
frappe/core/doctype/communication/comment.py 파일 보기

@@ -82,12 +82,7 @@ def notify_mentions(doc):

sender_fullname = get_fullname(frappe.session.user)
parent_doc_label = "{0} {1}".format(_(doc.reference_doctype), doc.reference_name)
subject = _("{0} mentioned you in a comment in {1}").format(sender_fullname, parent_doc_label)
message = frappe.get_template("templates/emails/mentioned_in_comment.html").render({
"sender_fullname": sender_fullname,
"comment": doc,
"link": get_link_to_form(doc.reference_doctype, doc.reference_name, label=parent_doc_label)
})
subject = _("{0} mentioned you in a comment").format(sender_fullname)

recipients = [frappe.db.get_value("User", {"enabled": 1, "username": username, "user_type": "System User"})
for username in mentions]
@@ -96,7 +91,13 @@ def notify_mentions(doc):
recipients=recipients,
sender=frappe.session.user,
subject=subject,
message=message
template="mentioned_in_comment",
args={
"sender_fullname": sender_fullname,
"comment": doc,
"link": get_link_to_form(doc.reference_doctype, doc.reference_name, label=parent_doc_label)
},
header=[_('New Mention'), 'orange']
)

def get_comments_from_parent(doc):


+ 1
- 2
frappe/core/doctype/communication/feed.py 파일 보기

@@ -3,7 +3,6 @@

from __future__ import unicode_literals
import frappe
import frappe.defaults
import frappe.permissions
from frappe.model.document import Document
from frappe.utils import get_fullname
@@ -68,7 +67,7 @@ def get_feed_match_conditions(user=None, force=True):

conditions = ['`tabCommunication`.owner="{user}" or `tabCommunication`.reference_owner="{user}"'.format(user=frappe.db.escape(user))]

user_permissions = frappe.defaults.get_user_permissions(user)
user_permissions = frappe.permissions.get_user_permissions(user)
can_read = frappe.get_user().get_can_read()

can_read_doctypes = ['"{}"'.format(doctype) for doctype in


frappe/core/doctype/doctype/boilerplate/controller.py → frappe/core/doctype/doctype/boilerplate/controller._py 파일 보기


frappe/core/doctype/doctype/boilerplate/test_controller.py → frappe/core/doctype/doctype/boilerplate/test_controller._py 파일 보기


+ 2
- 2
frappe/core/doctype/doctype/doctype.py 파일 보기

@@ -330,10 +330,10 @@ class DocType(Document):

def make_controller_template(self):
"""Make boilerplate controller template."""
make_boilerplate("controller.py", self)
make_boilerplate("controller._py", self)

if not (self.istable or self.issingle):
make_boilerplate("test_controller.py", self.as_dict())
make_boilerplate("test_controller._py", self.as_dict())

if not self.istable:
make_boilerplate("controller.js", self.as_dict())


+ 2
- 2
frappe/core/doctype/domain/domain.json 파일 보기

@@ -26,7 +26,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Domain",
"length": 0,
@@ -54,7 +54,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-16 13:03:25.430679",
"modified": "2017-07-26 21:29:00.353105",
"modified_by": "Administrator",
"module": "Core",
"name": "Domain",


+ 23
- 0
frappe/core/doctype/domain/test_domain.js 파일 보기

@@ -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: Domain", function (assert) {
let done = assert.async();

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

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

});

+ 7
- 3
frappe/core/doctype/feedback_trigger/feedback_trigger.py 파일 보기

@@ -66,12 +66,16 @@ def send_feedback_request(reference_doctype, reference_name, trigger="Manual", d
feedback_request, url = get_feedback_request_url(reference_doctype,
reference_name, details.get("recipients"), trigger)

feedback_url = frappe.render_template("templates/emails/feedback_request_url.html", { "url": url })
feedback_msg = frappe.render_template("templates/emails/feedback_request_url.html", { "url": url })

# appending feedback url to message body
details.update({ "message": "{message}{feedback_url}".format(
message = "{message}{feedback_msg}".format(
message=details.get("message"),
feedback_url=feedback_url)
feedback_msg=feedback_msg
)
details.update({
"message": message,
"header": [details.get('subject'), 'blue']
})

if details:


+ 4
- 4
frappe/core/doctype/file/file.py 파일 보기

@@ -10,12 +10,10 @@ naming for same name files: file.gif, file-1.gif, file-2.gif etc

import frappe
import json
import urllib
import os
import shutil
import requests
import requests.exceptions
import StringIO
import mimetypes, imghdr

from frappe.utils.file_manager import delete_file_data_content, get_content_hash, get_random_filename
@@ -23,6 +21,8 @@ from frappe import _
from frappe.utils.nestedset import NestedSet
from frappe.utils import strip, get_files_path
from PIL import Image, ImageOps
from six import StringIO
from six.moves.urllib.parse import unquote
import zipfile

class FolderNotEmpty(frappe.ValidationError): pass
@@ -372,7 +372,7 @@ def get_web_image(file_url):
frappe.msgprint(_("Unable to read file format for {0}").format(file_url))
raise

image = Image.open(StringIO.StringIO(r.content))
image = Image.open(StringIO(r.content))

try:
filename, extn = file_url.rsplit("/", 1)[1].rsplit(".", 1)
@@ -383,7 +383,7 @@ def get_web_image(file_url):
extn = None

extn = get_extension(filename, extn, r.content)
filename = "/files/" + strip(urllib.unquote(filename))
filename = "/files/" + strip(unquote(filename))

return image, filename, extn



+ 2
- 3
frappe/core/doctype/has_role/has_role.py 파일 보기

@@ -7,7 +7,6 @@ import frappe
from frappe.model.document import Document

class HasRole(Document):
def validate(self):
if cint(self.get("__islocal")) and frappe.db.exists("Has Role", {
"parent": self.parent, "role": self.role}):
def before_insert(self):
if frappe.db.exists("Has Role", {"parent": self.parent, "role": self.role}):
frappe.throw(frappe._("User '{0}' already has the role '{1}'").format(self.parent, self.role))

+ 3
- 2
frappe/core/doctype/page/page.py 파일 보기

@@ -10,6 +10,7 @@ from frappe.model.utils import render_include
from frappe import conf, _
from frappe.desk.form.meta import get_code_files_via_hooks, get_js
from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles
from six import text_type

class Page(Document):
def autoname(self):
@@ -111,13 +112,13 @@ class Page(Document):
fpath = os.path.join(path, page_name + '.css')
if os.path.exists(fpath):
with open(fpath, 'r') as f:
self.style = unicode(f.read(), "utf-8")
self.style = text_type(f.read(), "utf-8")

# html as js template
for fname in os.listdir(path):
if fname.endswith(".html"):
with open(os.path.join(path, fname), 'r') as f:
template = unicode(f.read(), "utf-8")
template = text_type(f.read(), "utf-8")
if "<!-- jinja -->" in template:
context = frappe._dict({})
try:


+ 33
- 0
frappe/core/doctype/report/test_query_report.js 파일 보기

@@ -0,0 +1,33 @@
// Test for creating query report
QUnit.test("Test Query Report", function(assert){
assert.expect(2);
let done = assert.async();
let random = frappe.utils.get_random(10);
frappe.run_serially([
() => frappe.set_route('List', 'ToDo'),
() => frappe.new_doc('ToDo'),
() => frappe.quick_entry.dialog.set_value('description', random),
() => frappe.quick_entry.insert(),
() => {
return frappe.tests.make('Report', [
{report_name: 'ToDo List Report'},
{report_type: 'Query Report'},
{ref_doctype: 'ToDo'}
]);
},
() => frappe.set_route('Form','Report', 'ToDo List Report'),

//Query
() => cur_frm.set_value('query','select description,owner,status from `tabToDo`'),
() => cur_frm.save(),
() => frappe.set_route('query-report','ToDo List Report'),
() => frappe.timeout(5),
() => {
assert.ok($('div.slick-header-column').length == 4,'Correct numbers of columns visible');
//To check if the result is present
assert.ok($('div.r1:contains('+random+')').is(':visible'),'Result is visible in report');
frappe.timeout(3);
},
() => done()
]);
});

+ 71
- 41
frappe/core/doctype/system_settings/system_settings.json 파일 보기

@@ -527,7 +527,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "security",
"fieldname": "permissions",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -536,10 +536,11 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Security",
"label": "Permissions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -556,10 +557,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "06:00",
"description": "Session Expiry in Hours e.g. 06:00",
"fieldname": "session_expiry",
"fieldtype": "Data",
"description": "If Apply User Permissions is checked for Report DocType but no User Permissions are defined for Report for a User, then all Reports are shown to that User",
"fieldname": "ignore_user_permissions_if_missing",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -567,11 +567,11 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Session Expiry",
"label": "Ignore User Permissions If Missing",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -588,10 +588,10 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "720:00",
"description": "In Hours",
"fieldname": "session_expiry_mobile",
"fieldtype": "Data",
"default": "0",
"description": "If Apply Strict User Permission is checked and User Permission is defined for a DocType for a User, then all the documents where value of the link is blank, will not be shown to that User",
"fieldname": "apply_strict_user_permissions",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -599,7 +599,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Session Expiry Mobile",
"label": "Apply Strict User Permissions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -614,16 +614,45 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "security",
"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": "Security",
"length": 0,
"no_copy": 0,
"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,
"default": "0",
"description": "If enabled, the password strength will be enforced based on the Minimum Password Score value. A value of 2 being medium strong and 4 being very strong.",
"fieldname": "enable_password_policy",
"fieldtype": "Check",
"default": "06:00",
"description": "Session Expiry in Hours e.g. 06:00",
"fieldname": "session_expiry",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -631,11 +660,11 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enable Password Policy",
"label": "Session Expiry",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -652,10 +681,10 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "2",
"depends_on": "eval:doc.enable_password_policy==1",
"fieldname": "minimum_password_score",
"fieldtype": "Select",
"default": "720:00",
"description": "In Hours",
"fieldname": "session_expiry_mobile",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -663,10 +692,9 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Minimum Password Score",
"label": "Session Expiry Mobile",
"length": 0,
"no_copy": 0,
"options": "2\n4",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -685,8 +713,10 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_13",
"fieldtype": "Column Break",
"default": "0",
"description": "If enabled, the password strength will be enforced based on the Minimum Password Score value. A value of 2 being medium strong and 4 being very strong.",
"fieldname": "enable_password_policy",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -694,6 +724,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enable Password Policy",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -714,9 +745,10 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Note: Multiple sessions will be allowed in case of mobile device",
"fieldname": "deny_multiple_sessions",
"fieldtype": "Check",
"default": "2",
"depends_on": "eval:doc.enable_password_policy==1",
"fieldname": "minimum_password_score",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -724,9 +756,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow only one session per user",
"label": "Minimum Password Score",
"length": 0,
"no_copy": 0,
"options": "2\n4",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -745,9 +778,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "If Apply User Permissions is checked for Report DocType but no User Permissions are defined for Report for a User, then all Reports are shown to that User",
"fieldname": "ignore_user_permissions_if_missing",
"fieldtype": "Check",
"fieldname": "column_break_13",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -755,7 +787,6 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Ignore User Permissions If Missing",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -776,9 +807,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"description": "If Apply Strict User Permission is checked and User Permission is defined for a DocType for a User, then all the documents where value of the link is blank, will not be shown to that User",
"fieldname": "apply_strict_user_permissions",
"description": "Note: Multiple sessions will be allowed in case of mobile device",
"fieldname": "deny_multiple_sessions",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -787,7 +817,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Apply Strict User Permissions",
"label": "Allow only one session per user",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -997,7 +1027,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-23 07:48:10.453011",
"modified": "2017-07-20 22:57:56.466867",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",
@@ -1032,4 +1062,4 @@
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0
}
}

+ 1
- 0
frappe/core/doctype/system_settings/system_settings.py 파일 보기

@@ -35,6 +35,7 @@ class SystemSettings(Document):

frappe.cache().delete_value('system_settings')
frappe.cache().delete_value('time_zone')
frappe.local.system_settings = {}

@frappe.whitelist()
def load():


+ 23
- 0
frappe/core/doctype/system_settings/test_system_settings.js 파일 보기

@@ -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: System Settings", function (assert) {
let done = assert.async();

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

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

});

+ 5
- 1
frappe/core/doctype/user/user.js 파일 보기

@@ -59,9 +59,13 @@ frappe.ui.form.on('User', {
frappe.route_options = {
"user": doc.name
};
frappe.set_route("user-permissions");
frappe.set_route('List', 'User Permission');
}, null, "btn-default")

frm.add_custom_button(__('View Permitted Documents'),
() => frappe.set_route('query-report', 'Permitted Documents For User',
{user: frm.doc.name}));

frm.toggle_display(['sb1', 'sb3', 'modules_access'], true);
}



+ 13
- 17
frappe/core/doctype/user/user.py 파일 보기

@@ -1,7 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
from __future__ import unicode_literals, print_function
import frappe
from frappe.model.document import Document
from frappe.utils import cint, has_gravatar, format_datetime, now_datetime, get_formatted_email
@@ -191,7 +191,7 @@ class User(Document):
self.email_new_password(new_password)

except frappe.OutgoingEmailError:
print frappe.get_traceback()
print(frappe.get_traceback())
pass # email server not set, don't send email

@Document.hook
@@ -858,25 +858,21 @@ def notify_admin_access_to_system_manager(login_manager=None):
and login_manager.user == "Administrator"
and frappe.local.conf.notify_admin_access_to_system_manager):

message = """<p>
{dear_system_manager} <br><br>
{access_message} <br><br>
{is_it_unauthorized}
</p>""".format(
dear_system_manager=_("Dear System Manager,"),
site = '<a href="{0}" target="_blank">{0}</a>'.format(frappe.local.request.host_url)
date_and_time = '<b>{0}</b>'.format(format_datetime(now_datetime(), format_string="medium"))
ip_address = frappe.local.request_ip

access_message=_("""Administrator accessed {0} on {1} via IP Address {2}.""").format(
"""<a href="{site}" target="_blank">{site}</a>""".format(site=frappe.local.request.host_url),
"""<b>{date_and_time}</b>""".format(date_and_time=format_datetime(now_datetime(), format_string="medium")),
frappe.local.request_ip
),
access_message = _('Administrator accessed {0} on {1} via IP Address {2}.').format(
site, date_and_time, ip_address)

is_it_unauthorized=_("If you think this is unauthorized, please change the Administrator password.")
frappe.sendmail(
recipients=get_system_managers(),
subject=_("Administrator Logged In"),
template="administrator_logged_in",
args={'access_message': access_message},
header=['Access Notification', 'orange']
)

frappe.sendmail(recipients=get_system_managers(), subject=_("Administrator Logged In"),
message=message)

def extract_mentions(txt):
"""Find all instances of @username in the string.
The mentions will be separated by non-word characters or may appear at the start of the string"""


+ 0
- 0
frappe/core/doctype/user_permission/__init__.py 파일 보기


+ 23
- 0
frappe/core/doctype/user_permission/test_user_permission.js 파일 보기

@@ -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: User Permission", function (assert) {
let done = assert.async();

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

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

});

+ 10
- 0
frappe/core/doctype/user_permission/test_user_permission.py 파일 보기

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

#import frappe
import unittest

class TestUserPermission(unittest.TestCase):
pass

+ 10
- 0
frappe/core/doctype/user_permission/user_permission.js 파일 보기

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

frappe.ui.form.on('User Permission', {
refresh: function(frm) {
frm.add_custom_button(__('View Permitted Documents'),
() => frappe.set_route('query-report', 'Permitted Documents For User',
{user: frm.doc.user}));
}
});

+ 188
- 0
frappe/core/doctype/user_permission/user_permission.json 파일 보기

@@ -0,0 +1,188 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-07-17 14:25:27.881871",
"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": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"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": 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": "allow",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Allow",
"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": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "for_value",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "For Value",
"length": 0,
"no_copy": 0,
"options": "allow",
"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": 1,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "If you un-check this, you will have to apply manually for each Role + Document Type combination",
"fieldname": "apply_for_all_roles",
"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": "Apply for all Roles for this User",
"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": 0,
"max_attachments": 0,
"modified": "2017-07-27 22:55:58.647315",
"modified_by": "Administrator",
"module": "Core",
"name": "User Permission",
"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": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "user",
"track_changes": 1,
"track_seen": 0
}

+ 80
- 0
frappe/core/doctype/user_permission/user_permission.py 파일 보기

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

from __future__ import unicode_literals
import frappe, json
from frappe.model.document import Document
from frappe.permissions import (get_valid_perms, update_permission_property)
from frappe import _

class UserPermission(Document):
def on_update(self):
frappe.cache().delete_value('user_permissions')

if self.apply_for_all_roles:
self.apply_user_permissions_to_all_roles()

def apply_user_permissions_to_all_roles(self):
# add apply user permissions for all roles that
# for this doctype
def show_progress(i, l):
if l > 2:
frappe.publish_realtime("progress",
dict(progress=[i, l], title=_('Updating...')),
user=frappe.session.user)


roles = frappe.get_roles(self.user)
linked = frappe.db.sql('''select distinct parent from tabDocField
where fieldtype="Link" and options=%s''', self.allow)
for i, link in enumerate(linked):
doctype = link[0]
for perm in get_valid_perms(doctype, self.user):
# if the role is applicable to the user
show_progress(i+1, len(linked))
if perm.role in roles:
if not perm.apply_user_permissions:
update_permission_property(doctype, perm.role, 0,
'apply_user_permissions', '1')

try:
user_permission_doctypes = json.loads(perm.user_permission_doctypes or '[]')
except ValueError:
user_permission_doctypes = []

if self.allow not in user_permission_doctypes:
user_permission_doctypes.append(self.allow)
update_permission_property(doctype, perm.role, 0,
'user_permission_doctypes', json.dumps(user_permission_doctypes), validate=False)

show_progress(len(linked), len(linked))

def on_trash(self): # pylint: disable=no-self-use
frappe.cache().delete_value('user_permissions')

def get_user_permissions(user=None):
'''Get all users permissions for the user as a dict of doctype'''
if not user:
user = frappe.session.user

out = frappe.cache().hget("user_permissions", user)

if not out:
out = {}
try:
for perm in frappe.get_all('User Permission',
fields=['allow', 'for_value'], filters=dict(user=user)):
out.setdefault(perm.allow, []).append(perm.for_value)

# add profile match
if user not in out.get("User", []):
out.setdefault("User", []).append(user)

frappe.cache().hset("user_permissions", user, out)
except frappe.SQLError as e:
if e.args[0]==1146:
# called from patch
pass

return out

+ 1
- 1
frappe/core/page/data_import_tool/exporter.py 파일 보기

@@ -284,7 +284,7 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data
if from_data_import == "Yes" and excel_format == "Yes":
filename = frappe.generate_hash("", 10)
with open(filename, 'wb') as f:
f.write(cstr(w.getvalue()).encode("utf-8"))
f.write(cstr(w.getvalue()).encode("utf-8"))
f = open(filename)
reader = csv.reader(f)



+ 2
- 1
frappe/core/page/data_import_tool/importer.py 파일 보기

@@ -17,6 +17,7 @@ from frappe.utils.file_manager import save_url

from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys
from six import text_type

@frappe.whitelist()
def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, no_email=True, overwrite=None,
@@ -308,7 +309,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
doc = parent.append(parentfield, doc)
parent.save()
log('Inserted row for %s at #%s' % (as_link(parenttype,
doc.parent), unicode(doc.idx)))
doc.parent),text_type(doc.idx)))
else:
if overwrite and doc["name"] and frappe.db.exists(doctype, doc["name"]):
original = frappe.get_doc(doctype, doc["name"])


+ 0
- 25
frappe/core/page/desktop/desktop.js 파일 보기

@@ -50,7 +50,6 @@ $.extend(frappe.desktop, {
desktop_items: all_icons,
}));

frappe.desktop.setup_help_messages();
frappe.desktop.setup_module_click();

// notifications
@@ -63,30 +62,6 @@ $.extend(frappe.desktop, {

},

setup_help_messages: function() {
// {
// title: 'Sign up for a Premium Plan',
// description: 'Sign up for a premium plan and add users, get more disk space and priority support',
// action: 'Select Plan',
// route: 'usage-info'
// }

// TEMP: test activiation without this message.
return;

// if(!frappe.user.has_role('System Manager')) {
// return;
// }

// frappe.call({
// method: 'frappe.core.page.desktop.desktop.get_help_messages',
// callback: function(r) {
// frappe.desktop.render_help_messages(r.message);
// }
// });

},

render_help_messages: function(help_messages) {
var wrapper = frappe.desktop.wrapper.find('.help-message-wrapper');
var $help_messages = wrapper.find('.help-messages');


+ 16
- 8
frappe/core/page/permission_manager/permission_manager.js 파일 보기

@@ -21,6 +21,7 @@ frappe.pages['permission-manager'].refresh = function(wrapper) {
frappe.PermissionEngine = Class.extend({
init: function(wrapper) {
this.wrapper = wrapper;
this.page = wrapper.page;
this.body = $(this.wrapper).find(".perm-engine");
this.make();
this.refresh();
@@ -55,6 +56,10 @@ frappe.PermissionEngine = Class.extend({
.change(function() {
me.refresh();
});

this.page.add_inner_button(__('Set User Permissions'), () => {
return frappe.set_route('List', 'User Permission');
});
this.set_from_route();
},
set_from_route: function() {
@@ -133,11 +138,11 @@ frappe.PermissionEngine = Class.extend({
refresh: function() {
var me = this;
if(!me.doctype_select) {
this.body.html("<p class='text-muted'>" + __("Loading") + "...</div>");
this.body.html("<p class='text-muted'>" + __("Loading") + "...</p>");
return;
}
if(!me.get_doctype() && !me.get_role()) {
this.body.html("<p class='text-muted'>"+__("Select Document Type or Role to start.")+"</div>");
this.body.html("<p class='text-muted'>"+__("Select Document Type or Role to start.")+"</p>");
return;
}
// get permissions
@@ -247,10 +252,13 @@ frappe.PermissionEngine = Class.extend({

setup_user_permissions: function(d, role_cell) {
var me = this;
d.help = frappe.render('<ul class="user-permission-help small hidden" style="margin-left: -10px;">\
<li style="margin-top: 7px;"><a class="show-user-permission-doctypes grey">{%= __("Select Document Types") %}</a></li>\
<li style="margin-top: 3px;"><a class="show-user-permissions grey">{%= __("Show User Permissions") %}</a></li>\
</ul>', {});
d.help = `<ul class="user-permission-help small hidden"
style="margin-left: -10px;">
<li style="margin-top: 7px;"><a class="show-user-permission-doctypes">
${__("Select Document Types")}</a></li>
<li style="margin-top: 3px;"><a class="show-user-permissions">
${__("Show User Permissions")}</a></li>
</ul>`;

var checkbox = this.add_check(role_cell, d, "apply_user_permissions")
.removeClass("col-md-4")
@@ -336,8 +344,8 @@ frappe.PermissionEngine = Class.extend({
var me = this;

this.body.on("click", ".show-user-permissions", function() {
frappe.route_options = { doctype: me.get_doctype() || "" };
frappe.set_route("user-permissions");
frappe.route_options = { allow: me.get_doctype() || "" };
frappe.set_route('List', 'User Permission');
});

this.body.on("click", "input[type='checkbox']", function() {


+ 3
- 13
frappe/core/page/permission_manager/permission_manager.py 파일 보기

@@ -7,7 +7,7 @@ import frappe.defaults
from frappe.modules.import_file import get_file_path, read_doc_from_file
from frappe.translate import send_translations
from frappe.permissions import (reset_perms, get_linked_doctypes, get_all_perms,
setup_custom_perms, add_permission)
setup_custom_perms, add_permission, update_permission_property)
from frappe.core.doctype.doctype.doctype import (clear_permissions_cache,
validate_permissions_for_doctype)
from frappe import _
@@ -68,18 +68,8 @@ def add(parent, role, permlevel):
@frappe.whitelist()
def update(doctype, role, permlevel, ptype, value=None):
frappe.only_for("System Manager")

out = None
if setup_custom_perms(doctype):
out = 'refresh'

name = frappe.get_value('Custom DocPerm', dict(parent=doctype, role=role, permlevel=permlevel))

frappe.db.sql("""update `tabCustom DocPerm` set `%s`=%s where name=%s"""\
% (frappe.db.escape(ptype), '%s', '%s'), (value, name))
validate_permissions_for_doctype(doctype)

return out
out = update_permission_property(doctype, role, permlevel, ptype, value)
return 'refresh' if out else None

@frappe.whitelist()
def remove(doctype, role, permlevel):


+ 0
- 1
frappe/core/page/user_permissions/README.md 파일 보기

@@ -1 +0,0 @@
Interface to set user defaults (DefaultValue).

+ 0
- 3
frappe/core/page/user_permissions/__init__.py 파일 보기

@@ -1,3 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt


+ 0
- 365
frappe/core/page/user_permissions/user_permissions.js 파일 보기

@@ -1,365 +0,0 @@
frappe.pages['user-permissions'].on_page_load = function(wrapper) {
var page = frappe.ui.make_app_page({
parent: wrapper,
title: __("User Permissions Manager"),
icon: "fa fa-shield",
single_column: true
});

frappe.breadcrumbs.add("Setup");

$("<div class='user-settings' \
style='min-height: 200px; padding: 15px;'></div>\
<p style='padding: 15px; padding-bottom: 0px;'>\
<a class='view-role-permissions grey'>" + __("Edit Role Permissions") + "</a>\
</p><hr><div style='padding: 0px 15px;'>\
<h4>"+__("Help for User Permissions")+":</h4>\
<ol>\
<li>"
+ __("Apart from Role based Permission Rules, you can apply User Permissions based on DocTypes.")
+ "</li>"

+ "<li>"
+ __("These permissions will apply for all transactions where the permitted record is linked. For example, if Company C is added to User Permissions of user X, user X will only be able to see transactions that has company C as a linked value.")
+ "</li>"

+ "<li>"
+ __("These will also be set as default values for those links, if only one such permission record is defined.")
+ "</li>"

+ "<li>"
+ __("A user can be permitted to multiple records of the same DocType.")
+ "</li>\
</ol></div>").appendTo(page.main);
wrapper.user_permissions = new frappe.UserPermissions(wrapper);
}

frappe.pages['user-permissions'].refresh = function(wrapper) {
wrapper.user_permissions.set_from_route();
}

frappe.UserPermissions = Class.extend({
init: function(wrapper) {
this.wrapper = wrapper;
this.body = $(this.wrapper).find(".user-settings");
this.filters = {};
this.make();
this.refresh();
},
make: function() {
var me = this;

$(this.wrapper).find(".view-role-permissions").on("click", function() {
frappe.route_options = { doctype: me.get_doctype() || "" };
frappe.set_route("permission-manager");
})

return frappe.call({
module:"frappe.core",
page:"user_permissions",
method: "get_users_and_links",
callback: function(r) {
me.options = r.message;

me.filters.user = me.wrapper.page.add_field({
fieldname: "user",
label: __("User"),
fieldtype: "Select",
options: ([__("Select User") + "..."].concat(r.message.users)).join("\n")
});

me.filters.doctype = me.wrapper.page.add_field({
fieldname: "doctype",
label: __("DocType"),
fieldtype: "Select",
options: ([__("Select DocType") + "..."].concat(me.get_link_names())).join("\n")
});

me.filters.user_permission = me.wrapper.page.add_field({
fieldname: "user_permission",
label: __("Name"),
fieldtype: "Link",
options: "[Select]"
});

if(frappe.user_roles.includes("System Manager")) {
me.download = me.wrapper.page.add_field({
fieldname: "download",
label: __("Download"),
fieldtype: "Button",
icon: "fa fa-download"
});

me.upload = me.wrapper.page.add_field({
fieldname: "upload",
label: __("Upload"),
fieldtype: "Button",
icon: "fa fa-upload"
});
}

// bind change event
$.each(me.filters, function(k, f) {
f.$input.on("change", function() {
me.refresh();
});
});

// change options in user_permission link
me.filters.doctype.$input.on("change", function() {
me.filters.user_permission.df.options = me.get_doctype();
});

me.set_from_route();
me.setup_download_upload();
}
});
},
setup_download_upload: function() {
var me = this;
me.download.$input.on("click", function() {
window.location.href = frappe.urllib.get_base_url()
+ "/api/method/frappe.core.page.user_permissions.user_permissions.get_user_permissions_csv";
});

me.upload.$input.on("click", function() {
var d = new frappe.ui.Dialog({
title: __("Upload User Permissions"),
fields: [
{
fieldtype:"HTML",
options: '<p class="text-muted"><ol>'+
"<li>"+__("Upload CSV file containing all user permissions in the same format as Download.")+"</li>"+
"<li><strong>"+__("Any existing permission will be deleted / overwritten.")+"</strong></li>"+
'</p>'
},
{
fieldtype:"Attach", fieldname:"attach",
}
],
primary_action_label: __("Upload and Sync"),
primary_action: function() {
var filedata = d.fields_dict.attach.get_value();
if(!filedata) {
frappe.msgprint(__("Please attach a file"));
return;
}
frappe.call({
method:"frappe.core.page.user_permissions.user_permissions.import_user_permissions",
args: {
filedata: filedata
},
callback: function(r) {
if(!r.exc) {
frappe.msgprint(__("Permissions Updated"));
d.hide();
}
}
});
}
});
d.show();
})
},
get_link_names: function() {
return this.options.link_fields;
},
set_from_route: function() {
var me = this;
if(frappe.route_options && this.filters && !$.isEmptyObject(this.filters)) {
$.each(frappe.route_options, function(key, value) {
if(me.filters[key] && frappe.route_options[key]!=null)
me.set_filter(key, value);
});
frappe.route_options = null;
}
this.refresh();
},
set_filter: function(key, value) {
this.filters[key].$input.val(value);
},
get_user: function() {
var user = this.filters.user.$input.val();
return user== __("Select User") + "..." ? null : user;
},
get_doctype: function() {
var doctype = this.filters.doctype.$input.val();
return doctype== __("Select DocType") + "..." ? null : doctype;
},
get_user_permission: function() {
// autosuggest hack!
var user_permission = this.filters.user_permission.$input.val();
return (user_permission === "%") ? null : user_permission;
},
render: function(prop_list) {
var me = this;
this.body.empty();
this.prop_list = prop_list;
if(!prop_list || !prop_list.length) {
this.add_message(__("No User Restrictions found."));
} else {
this.show_user_permissions_table();
}
this.show_add_user_permission();
if(this.get_user() && this.get_doctype()) {
$('<button class="btn btn-default btn-sm" style="margin-left: 10px;">\
Show Allowed Documents</button>').appendTo(this.body.find(".btn-area")).on("click", function() {
frappe.route_options = {doctype: me.get_doctype(), user:me.get_user() };
frappe.set_route("query-report/Permitted Documents For User");
});
}
},
add_message: function(txt) {
$('<p class="text-muted">' + txt + '</p>').appendTo(this.body);
},
refresh: function() {
var me = this;
if(!me.filters.user) {
this.body.html("<p class='text-muted'>"+__("Loading")+"...</p>");
return;
}
if(!me.get_user() && !me.get_doctype()) {
this.body.html("<p class='text-muted'>"+__("Select User or DocType to start.")+"</p>");
return;
}
// get permissions
return frappe.call({
module: "frappe.core",
page: "user_permissions",
method: "get_permissions",
args: {
parent: me.get_user(),
defkey: me.get_doctype(),
defvalue: me.get_user_permission()
},
callback: function(r) {
me.render(r.message);
}
});
},
show_user_permissions_table: function() {
var me = this;
this.table = $("<table class='table table-bordered'>\
<thead><tr></tr></thead>\
<tbody></tbody>\
</table>").appendTo(this.body);

$('<p class="text-muted small">'
+__("These restrictions will apply for Document Types where 'Apply User Permissions' is checked for the permission rule and a field with this value is present.")
+'</p>').appendTo(this.body);

$.each([[__("Allow User"), 150], [__("If Document Type"), 150], [__("Is"),150], ["", 50]],
function(i, col) {
$("<th>")
.html(col[0])
.css("width", col[1]+"px")
.appendTo(me.table.find("thead tr"));
});


$.each(this.prop_list, function(i, d) {
var row = $("<tr>").appendTo(me.table.find("tbody"));

$("<td>").html('<a class="grey" href="#Form/User/'+encodeURIComponent(d.parent)+'">'
+d.parent+'</a>').appendTo(row);
$("<td>").html(d.defkey).appendTo(row);
$("<td>").html(d.defvalue).appendTo(row);

me.add_delete_button(row, d);
});

},
add_delete_button: function(row, d) {
var me = this;
$("<button class='btn btn-sm btn-default'><i class='fa fa-remove'></i></button>")
.appendTo($("<td>").appendTo(row))
.attr("data-name", d.name)
.attr("data-user", d.parent)
.attr("data-defkey", d.defkey)
.attr("data-defvalue", d.defvalue)
.click(function() {
return frappe.call({
module: "frappe.core",
page: "user_permissions",
method: "remove",
args: {
name: $(this).attr("data-name"),
user: $(this).attr("data-user"),
defkey: $(this).attr("data-defkey"),
defvalue: $(this).attr("data-defvalue")
},
callback: function(r) {
if(r.exc) {
frappe.msgprint(__("Did not remove"));
} else {
me.refresh();
}
}
})
});
},

show_add_user_permission: function() {
var me = this;
$("<button class='btn btn-default btn-sm'>"+__("Add A User Restriction")+"</button>")
.appendTo($('<p class="btn-area">').appendTo(this.body))
.click(function() {
var d = new frappe.ui.Dialog({
title: __("Add A New Restriction"),
fields: [
{fieldtype:"Select", label:__("Allow User"),
options:me.options.users, reqd:1, fieldname:"user"},
{fieldtype:"Select", label: __("If Document Type"), fieldname:"defkey",
options:me.get_link_names(), reqd:1},
{fieldtype:"Link", label:__("Is"), fieldname:"defvalue",
options:'[Select]', reqd:1},
{fieldtype:"Button", label: __("Add"), fieldname:"add"},
]
});
if(me.get_user()) {
d.set_value("user", me.get_user());
d.get_input("user").prop("disabled", true);
}
if(me.get_doctype()) {
d.set_value("defkey", me.get_doctype());
d.get_input("defkey").prop("disabled", true);
}
if(me.get_user_permission()) {
d.set_value("defvalue", me.get_user_permission());
d.get_input("defvalue").prop("disabled", true);
}

d.fields_dict["defvalue"].get_query = function(txt) {
if(!d.get_value("defkey")) {
frappe.throw(__("Please select Document Type"));
}

return {
doctype: d.get_value("defkey")
}
};

d.get_input("add").click(function() {
var args = d.get_values();
if(!args) {
return;
}
frappe.call({
module: "frappe.core",
page: "user_permissions",
method: "add",
args: args,
callback: function(r) {
if(r.exc) {
frappe.msgprint(__("Did not add"));
} else {
me.refresh();
}
}
})
d.hide();
});
d.show();
});
}
})

+ 0
- 19
frappe/core/page/user_permissions/user_permissions.json 파일 보기

@@ -1,19 +0,0 @@
{
"content": null,
"creation": "2013-01-01 18:50:55",
"docstatus": 0,
"doctype": "Page",
"icon": "fa fa-user",
"idx": 1,
"modified": "2014-05-28 16:53:43.103533",
"modified_by": "Administrator",
"module": "Core",
"name": "user-permissions",
"owner": "Administrator",
"page_name": "user-permissions",
"roles": [],
"script": null,
"standard": "Yes",
"style": null,
"title": "User Permissions Manager"
}

+ 0
- 109
frappe/core/page/user_permissions/user_permissions.py 파일 보기

@@ -1,109 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
import frappe
from frappe import _
import frappe.defaults
from frappe.permissions import (can_set_user_permissions, add_user_permission,
remove_user_permission, get_valid_perms)
from frappe.core.doctype.user.user import get_system_users
from frappe.utils.csvutils import UnicodeWriter, read_csv_content_from_uploaded_file
from frappe.defaults import clear_default

@frappe.whitelist()
def get_users_and_links():
return {
"users": get_system_users(),
"link_fields": get_doctypes_for_user_permissions()
}

@frappe.whitelist()
def get_permissions(parent=None, defkey=None, defvalue=None):
if defkey and not can_set_user_permissions(defkey, defvalue):
raise frappe.PermissionError

conditions, values = _build_conditions(locals())

permissions = frappe.db.sql("""select name, parent, defkey, defvalue
from tabDefaultValue
where parent not in ('__default', '__global')
and substr(defkey,1,1)!='_'
and parenttype='User Permission'
{conditions}
order by parent, defkey""".format(conditions=conditions), values, as_dict=True)

if not defkey:
out = []
doctypes = get_doctypes_for_user_permissions()
for p in permissions:
if p.defkey in doctypes:
out.append(p)
permissions = out

return permissions

def _build_conditions(filters):
conditions = []
values = {}
for key, value in filters.items():
if filters.get(key):
conditions.append("and `{key}`=%({key})s".format(key=key))
values[key] = value

return "\n".join(conditions), values

@frappe.whitelist()
def remove(user, name, defkey, defvalue):
if not can_set_user_permissions(defkey, defvalue):
frappe.throw(_("Cannot remove permission for DocType: {0} and Name: {1}").format(
defkey, defvalue), frappe.PermissionError)

remove_user_permission(defkey, defvalue, user, name)

@frappe.whitelist()
def add(user, defkey, defvalue):
if not can_set_user_permissions(defkey, defvalue):
frappe.throw(_("Cannot set permission for DocType: {0} and Name: {1}").format(
defkey, defvalue), frappe.PermissionError)

add_user_permission(defkey, defvalue, user, with_message=True)

def get_doctypes_for_user_permissions():
'''Get doctypes for the current user where user permissions are applicable'''
user_roles = frappe.get_roles()

if "System Manager" in user_roles:
doctypes = set([p.parent for p in get_valid_perms()])
else:
doctypes = set([p.parent for p in get_valid_perms() if p.set_user_permissions])
single_doctypes = set([d.name for d in frappe.get_all("DocType", {"issingle": 1})])
return sorted(doctypes.difference(single_doctypes))


@frappe.whitelist()
def get_user_permissions_csv():
out = [["User Permissions"], ["User", "Document Type", "Value"]]
out += [[a.parent, a.defkey, a.defvalue] for a in get_permissions()]

csv = UnicodeWriter()
for row in out:
csv.writerow(row)

frappe.response['result'] = str(csv.getvalue())
frappe.response['type'] = 'csv'
frappe.response['doctype'] = "User Permissions"

@frappe.whitelist()
def import_user_permissions():
frappe.only_for("System Manager")
rows = read_csv_content_from_uploaded_file(ignore_encoding=True)
clear_default(parenttype="User Permission")

if rows[0][0]!="User Permissions" and rows[1][0] != "User":
frappe.throw(frappe._("Please upload using the same template as download."))

for row in rows[2:]:
add_user_permission(row[1], row[2], row[0])

+ 6
- 6
frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py 파일 보기

@@ -16,12 +16,12 @@ def execute(filters=None):
data = frappe.get_list(doctype, fields=fields, as_list=True, user=user)

if show_permissions:
columns = columns + ["Read", "Write", "Create", "Delete", "Submit", "Cancel", "Amend", "Print", "Email",
"Report", "Import", "Export", "Share"]
data = list(data)
for i,item in enumerate(data):
temp = frappe.permissions.get_doc_permissions(frappe.get_doc(doctype, item[0]), False,user)
data[i] = item+(temp.get("read"),temp.get("write"),temp.get("create"),temp.get("delete"),temp.get("submit"),temp.get("cancel"),temp.get("amend"),temp.get("print"),temp.get("email"),temp.get("report"),temp.get("import"),temp.get("export"),temp.get("share"),)
columns = columns + ["Read", "Write", "Create", "Delete", "Submit", "Cancel", "Amend", "Print", "Email",
"Report", "Import", "Export", "Share"]
data = list(data)
for i,item in enumerate(data):
temp = frappe.permissions.get_doc_permissions(frappe.get_doc(doctype, item[0]), False,user)
data[i] = item+(temp.get("read"),temp.get("write"),temp.get("create"),temp.get("delete"),temp.get("submit"),temp.get("cancel"),temp.get("amend"),temp.get("print"),temp.get("email"),temp.get("report"),temp.get("import"),temp.get("export"),temp.get("share"),)

return columns, data



+ 1
- 0
frappe/custom/doctype/customize_form/test_customize_form.js 파일 보기

@@ -7,6 +7,7 @@ QUnit.test("test customize form", function(assert) {
let done = assert.async();
frappe.run_serially([
() => frappe.set_route('Form', 'Customize Form'),
() => frappe.timeout(2),
() => cur_frm.set_value('doc_type', 'ToDo'),
() => frappe.timeout(2),
() => {


+ 12
- 12
frappe/database.py 파일 보기

@@ -18,10 +18,10 @@ import redis
import frappe.model.meta
from frappe.utils import now, get_datetime, cstr
from frappe import _
from types import StringType, UnicodeType
from six import text_type, binary_type
from frappe.utils.global_search import sync_global_search
from frappe.model.utils.link_count import flush_local_link_count
from six import iteritems
from six import iteritems, text_type


class Database:
@@ -69,8 +69,8 @@ class Database:
use_unicode=True, charset='utf8mb4')
self._conn.converter[246]=float
self._conn.converter[12]=get_datetime
self._conn.encoders[UnicodeWithAttrs] = self._conn.encoders[UnicodeType]
self._conn.encoders[DateTimeDeltaType] = self._conn.encoders[StringType]
self._conn.encoders[UnicodeWithAttrs] = self._conn.encoders[text_type]
self._conn.encoders[DateTimeDeltaType] = self._conn.encoders[binary_type]

MYSQL_OPTION_MULTI_STATEMENTS_OFF = 1
self._conn.set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_OFF)
@@ -260,7 +260,7 @@ class Database:
else:
val = r[i]

if as_utf8 and type(val) is unicode:
if as_utf8 and type(val) is text_type:
val = val.encode('utf-8')
row_dict[self._cursor.description[i][0]] = val
ret.append(row_dict)
@@ -289,13 +289,13 @@ class Database:

if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, long)):
if isinstance(v, datetime.date):
v = unicode(v)
v = text_type(v)
if formatted:
v = formatdate(v)

# time
elif isinstance(v, (datetime.timedelta, datetime.datetime)):
v = unicode(v)
v = text_type(v)

# long
elif isinstance(v, long):
@@ -306,7 +306,7 @@ class Database:
if isinstance(v, float):
v=fmt_money(v)
elif isinstance(v, int):
v = unicode(v)
v = text_type(v)

return v

@@ -321,7 +321,7 @@ class Database:
val = self.convert_to_simple_type(c, formatted)
else:
val = c
if as_utf8 and type(val) is unicode:
if as_utf8 and type(val) is text_type:
val = val.encode('utf-8')
nr.append(val)
nres.append(nr)
@@ -333,7 +333,7 @@ class Database:
for r in res:
nr = []
for c in r:
if type(c) is unicode:
if type(c) is text_type:
c = c.encode('utf-8')
nr.append(self.convert_to_simple_type(c, formatted))
nres.append(nr)
@@ -881,10 +881,10 @@ class Database:

def escape(self, s, percent=True):
"""Excape quotes and percent in given string."""
if isinstance(s, unicode):
if isinstance(s, text_type):
s = (s or "").encode("utf-8")

s = unicode(MySQLdb.escape_string(s), "utf-8").replace("`", "\\`")
s = text_type(MySQLdb.escape_string(s), "utf-8").replace("`", "\\`")

# NOTE separating % escape, because % escape should only be done when using LIKE operator
# or when you use python format string to generate query that already has a %s


+ 4
- 19
frappe/defaults.py 파일 보기

@@ -48,25 +48,10 @@ def is_a_user_permission_key(key):
return ":" not in key and key != frappe.scrub(key)

def get_user_permissions(user=None):
if not user:
user = frappe.session.user

return build_user_permissions(user)

def build_user_permissions(user):
out = frappe.cache().hget("user_permissions", user)
if out==None:
out = {}
for key, value in frappe.db.sql("""select defkey, ifnull(defvalue, '') as defvalue
from tabDefaultValue where parent=%s and parenttype='User Permission'""", (user,)):
out.setdefault(key, []).append(value)

# add profile match
if user not in out.get("User", []):
out.setdefault("User", []).append(user)

frappe.cache().hset("user_permissions", user, out)
return out
from frappe.core.doctype.user_permission.user_permission \
import get_user_permissions as _get_user_permissions
'''Return frappe.core.doctype.user_permissions.user_permissions._get_user_permissions (kept for backward compatibility)'''
return _get_user_permissions(user)

def get_defaults(user=None):
globald = get_defaults_for()


+ 11
- 9
frappe/desk/doctype/event/event.py 파일 보기

@@ -7,7 +7,7 @@ import frappe
import json

from frappe.utils import (getdate, cint, add_months, date_diff, add_days,
nowdate, get_datetime_str, cstr, get_datetime, now_datetime)
nowdate, get_datetime_str, cstr, get_datetime, now_datetime, format_datetime)
from frappe.model.document import Document
from frappe.utils.user import get_enabled_system_users
from frappe.desk.reportview import get_filters_cond
@@ -48,20 +48,22 @@ def send_event_digest():
for user in get_enabled_system_users():
events = get_events(today, today, user.name, for_reminder=True)
if events:
text = ""
frappe.set_user_lang(user.name, user.language)

text = "<h3>" + frappe._("Events In Today's Calendar") + "</h3>"
for e in events:
e.starts_on = format_datetime(e.starts_on, 'hh:mm a')
if e.all_day:
e.starts_on = "All Day"
text += "<h4>%(starts_on)s: %(subject)s</h4><p>%(description)s</p>" % e

text += '<p style="color: #888; font-size: 80%; margin-top: 20px; padding-top: 10px; border-top: 1px solid #eee;">'\
+ frappe._("Daily Event Digest is sent for Calendar Events where reminders are set.")+'</p>'

frappe.sendmail(recipients=user.email, subject=frappe._("Upcoming Events for Today"),
content = text)
frappe.sendmail(
recipients=user.email,
subject=frappe._("Upcoming Events for Today"),
template="upcoming_events",
args={
'events': events,
},
header=[frappe._("Events in Today's Calendar"), 'blue']
)

@frappe.whitelist()
def get_events(start, end, user=None, for_reminder=False, filters=None):


+ 0
- 11
frappe/desk/form/load.py 파일 보기

@@ -70,7 +70,6 @@ def getdoctype(doctype, with_parent=False, cached_timestamp=None):
if not docs:
docs = get_meta_bundle(doctype)

frappe.response['user_permissions'] = get_user_permissions(docs)
frappe.response['user_settings'] = get_user_settings(parent_dt or doctype)

if cached_timestamp and docs[0].modified==cached_timestamp:
@@ -102,16 +101,6 @@ def get_docinfo(doc=None, doctype=None, name=None):
"rating": get_feedback_rating(doc.doctype, doc.name)
}

def get_user_permissions(meta):
out = {}
all_user_permissions = frappe.defaults.get_user_permissions()

for m in meta:
for df in m.get_fields_to_check_permissions(all_user_permissions):
out[df.options] = list(set(all_user_permissions[df.options]))

return out

def get_attachments(dt, dn):
return frappe.get_all("File", fields=["name", "file_name", "file_url", "is_private"],
filters = {"attached_to_name": dn, "attached_to_doctype": dt})


+ 2
- 2
frappe/desk/form/meta.py 파일 보기

@@ -14,7 +14,7 @@ from frappe.model.utils import render_include
from frappe.build import scrub_html_template

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


def get_meta(doctype, cached=True):
@@ -99,7 +99,7 @@ class FormMeta(Meta):
for fname in os.listdir(path):
if fname.endswith(".html"):
with open(os.path.join(path, fname), 'r') as f:
templates[fname.split('.')[0]] = scrub_html_template(unicode(f.read(), "utf-8"))
templates[fname.split('.')[0]] = scrub_html_template(text_type(f.read(), "utf-8"))

self.set("__templates", templates or None)



+ 3
- 2
frappe/desk/form/run_method.py 파일 보기

@@ -6,6 +6,7 @@ import json, inspect
import frappe
from frappe import _
from frappe.utils import cint
from six import text_type

@frappe.whitelist()
def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None):
@@ -53,7 +54,7 @@ def make_csv_output(res, dt):
"""send method response as downloadable CSV file"""
import frappe

from cStringIO import StringIO
from six import StringIO
import csv

f = StringIO()
@@ -68,6 +69,6 @@ def make_csv_output(res, dt):

f.seek(0)

frappe.response['result'] = unicode(f.read(), 'utf-8')
frappe.response['result'] = text_type(f.read(), 'utf-8')
frappe.response['type'] = 'csv'
frappe.response['doctype'] = dt.replace(' ','')

+ 8
- 3
frappe/desk/page/backups/backups.py 파일 보기

@@ -76,9 +76,14 @@ def backup_files_and_notify_user(user_email=None):
backup_files = backup(with_files=True)
get_downloadable_links(backup_files)

subject = "File backup is ready"
message = frappe.render_template('frappe/templates/emails/file_backup_notification.html', backup_files, is_path=True)
frappe.sendmail(recipients=[user_email], subject=subject, message=message)
subject = _("File backup is ready")
frappe.sendmail(
recipients=[user_email],
subject=subject,
template="file_backup_notification",
args=backup_files,
header=[subject, 'green']
)

def get_downloadable_links(backup_files):
for key in ['backup_path_files', 'backup_path_private_files']:


+ 6
- 3
frappe/desk/page/chat/chat.py 파일 보기

@@ -6,6 +6,7 @@ import frappe
from frappe.desk.notifications import delete_notification_count_for
from frappe.core.doctype.user.user import STANDARD_USERS
from frappe.utils import cint
from frappe import _

@frappe.whitelist()
def get_list(arg=None):
@@ -132,11 +133,13 @@ def _notify(contact, txt, subject=None):
frappe.sendmail(\
recipients=contact,
sender= frappe.db.get_value("User", frappe.session.user, "email"),
subject=subject or "New Message from " + get_fullname(frappe.session.user),
message=frappe.get_template("templates/emails/new_message.html").render({
subject=subject or _("New Message from {0}").format(get_fullname(frappe.session.user)),
template="new_message",
args={
"from": get_fullname(frappe.session.user),
"message": txt,
"link": get_url()
}))
},
header=[_('New Message'), 'orange'])
except frappe.OutgoingEmailError:
pass

+ 1
- 1
frappe/desk/page/setup_wizard/install_fixtures.py 파일 보기

@@ -22,7 +22,7 @@ def update_genders_and_salutations():

try:
doc.insert(ignore_permissions=True)
except frappe.DuplicateEntryError, e:
except frappe.DuplicateEntryError as e:
# pass DuplicateEntryError and continue
if e.args and e.args[0]==doc.doctype and e.args[1]==doc.name:
# make sure DuplicateEntryError is for the exact same doc and not a related doc


+ 4
- 3
frappe/desk/query_builder.py 파일 보기

@@ -8,6 +8,7 @@ out = frappe.response

from frappe.utils import cint
import frappe.defaults
from six import text_type

def get_sql_tables(q):
if q.find('WHERE') != -1:
@@ -239,17 +240,17 @@ def runquery_csv():

rows = [[rep_name], out['colnames']] + out['values']

from cStringIO import StringIO
from six import StringIO
import csv

f = StringIO()
writer = csv.writer(f)
for r in rows:
# encode only unicode type strings and not int, floats etc.
writer.writerow(map(lambda v: isinstance(v, unicode) and v.encode('utf-8') or v, r))
writer.writerow(map(lambda v: isinstance(v, text_type) and v.encode('utf-8') or v, r))

f.seek(0)
out['result'] = unicode(f.read(), 'utf-8')
out['result'] = text_type(f.read(), 'utf-8')
out['type'] = 'csv'
out['doctype'] = rep_name



+ 4
- 3
frappe/desk/reportview.py 파일 보기

@@ -10,6 +10,7 @@ import frappe.permissions
import MySQLdb
from frappe.model.db_query import DatabaseQuery
from frappe import _
from six import text_type

@frappe.whitelist()
def get():
@@ -145,16 +146,16 @@ def export_query():

# convert to csv
import csv
from cStringIO import StringIO
from six import StringIO

f = StringIO()
writer = csv.writer(f)
for r in data:
# encode only unicode type strings and not int, floats etc.
writer.writerow(map(lambda v: isinstance(v, unicode) and v.encode('utf-8') or v, r))
writer.writerow(map(lambda v: isinstance(v, text_type) and v.encode('utf-8') or v, r))

f.seek(0)
frappe.response['result'] = unicode(f.read(), 'utf-8')
frappe.response['result'] = text_type(f.read(), 'utf-8')
frappe.response['type'] = 'csv'
frappe.response['doctype'] = doctype



BIN
frappe/docs/assets/img/desk/animated_line_graph.gif 파일 보기

Before After
Width: 944  |  Height: 298  |  Size: 300 KiB

BIN
frappe/docs/assets/img/desk/bar_graph.png 파일 보기

Before After
Width: 776  |  Height: 296  |  Size: 29 KiB Width: 921  |  Height: 294  |  Size: 27 KiB

BIN
frappe/docs/assets/img/desk/line_graph.png 파일 보기

Before After
Width: 776  |  Height: 295  |  Size: 39 KiB

BIN
frappe/docs/assets/img/desk/line_graph_sales.png 파일 보기

Before After
Width: 1104  |  Height: 334  |  Size: 60 KiB

BIN
frappe/docs/assets/img/desk/percentage_graph.png 파일 보기

Before After
Width: 1132  |  Height: 151  |  Size: 17 KiB

+ 0
- 10
frappe/docs/contents.html 파일 보기

@@ -1,10 +0,0 @@
<!-- title: Table of Contents -->

<h1>Table of Contents</h1>
<br>

{% include "templates/includes/full_index.html" %}

<!-- autodoc -->
<!-- jinja -->
<!-- no-breadcrumbs -->

+ 0
- 9
frappe/docs/contents.py 파일 보기

@@ -1,9 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt

from __future__ import unicode_literals
import frappe
from frappe.website.utils import get_full_index

def get_context(context):
context.full_index = get_full_index()

+ 0
- 57
frappe/docs/index.html 파일 보기

@@ -1,57 +0,0 @@
<!-- title: Frappe Framework: Documentation -->
<!-- description: Superhero Web Framework -->
<!-- no-breadcrumbs -->
<style>
</style>

<!-- start-hero -->
<div class="splash">
<div class="container">
<div class="col-sm-10 col-sm-offset-1">
<div class="jumbotron">
<h1>Superhero Web Framework</h1>
<p>Build extensions to ERPNext or make your own app</p>
</div>
<div class="section" style="padding-top: 0px; margin-top: -30px;">
<div class="fake-browser-frame">
<img class="img-responsive browser-image feature-image"
src="assets/img/home.png">
</div>
</div>
</div>
</div>
</div>
<!-- end-hero -->


<div class="container">
<div class="col-sm-10 col-sm-offset-1">
<div class="section">
<p>Frappe is a full stack web application framework written in Python,
Javascript, HTML/CSS with MySQL as the backend. It was built for ERPNext
but is pretty generic and can be used to build database driven apps.</p>

<p>The key differece in Frappe compared to other frameworks is that Frappe
is that meta-data is also treated as data and is used to build front-ends
very easily. Frappe comes with a full blown admin UI called the <strong>Desk</strong>
that handles forms, navigation, lists, menus, permissions, file attachment
and much more out of the box.</p>

<p>Frappe also has a plug-in architecture that can be used to build plugins
to ERPNext.</p>

<p>Frappe Framework was designed to build <a href="https://erpnext.com">ERPNext</a>, open source
ERP for managing small and medium sized businesses.</p>

<p><a href="https://frappe.github.io/frappe/user/">Get started with the Tutorial</a></p>

</div>
</div>
</div>




<!-- autodoc -->
<!-- jinja -->

+ 25
- 0
frappe/docs/index.md 파일 보기

@@ -0,0 +1,25 @@
# Frappé Framework

### Tutorials, API documentation and Model Reference

Frappé is a full stack web application framework written in Python,
Javascript, HTML/CSS with MySQL as the backend. It was built for ERPNext
but is pretty generic and can be used to build database driven apps.

The key differece in Frappé compared to other frameworks is that Frappé
is that meta-data is also treated as data and is used to build front-ends
very easily. Frappé comes with a full blown admin UI called the **Desk**
that handles forms, navigation, lists, menus, permissions, file attachment
and much more out of the box.

Frappé also has a plug-in architecture that can be used to build plugins
to ERPNext.

Frappé Framework was designed to build [ERPNext](https://erpnext.com), open source
ERP for managing small and medium sized businesses.

[Get started with the Tutorial](/docs/user/)

### Feedback

You're encouraged to help improve the quality of this documentation, by sending a pull request on the [GitHub Repository](https://github.com/frappe/erpnext). If you would like to have a discussion regarding the documentation, you can do so [at the forum](https://discuss.erpnext.com).

+ 0
- 6
frappe/docs/index.txt 파일 보기

@@ -1,6 +0,0 @@
assets
user
contents
current
install
license

+ 0
- 30
frappe/docs/install.md 파일 보기

@@ -1,30 +0,0 @@
<!-- title: Frappe Framework Installation -->

# Installation

Frappe Framework is based on the <a href="https://frappe.io">Frappe Framework</a>, a full stack web framework based on Python, MariaDB, Redis, Node.

To intall Frappe Framework, you will have to install the <a href="https://github.com/frappe/bench">Frappe Bench</a>, the command-line, package manager and site manager for Frappe Framework. For more details, read the Bench README.

After you have installed Frappe Bench, go to you bench folder, which is `frappe.bench` by default and setup **frappe**.

bench get-app frappe {{ source_link }}

Then create a new site to install the app.

bench new-site mysite

This will create a new folder in your `/sites` directory and create a new database for this site.

Next, install frappe in this site

bench --site mysite install-app frappe

To run this locally, run

bench start

Fire up your browser and go to http://localhost:8000 and you should see the login screen. Login as **Administrator** and **admin** (or the password you set at the time of `new-site`) and you are set.

<!-- jinja -->
<!-- autodoc -->

+ 0
- 16
frappe/docs/license.html 파일 보기

@@ -1,16 +0,0 @@
<!-- title: License MIT -->

<h1>MIT</h1>

<p>The MIT License (MIT)</p>

<p>Copyright (c) 2016 Frappe Technologies Pvt. Ltd.</p>

<p>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:</p>

<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>

<p>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.</p>


<!-- autodoc -->

+ 2
- 0
frappe/docs/user/en/bench/guides/diagnosing-the-scheduler.md 파일 보기

@@ -1,3 +1,5 @@
# Diagnosing The Scheduler

<!-- markdown -->

If you're experiencing delays in scheduled jobs or they don't seem to run, you can run the several commands to diagnose the issue.


+ 4
- 0
frappe/docs/user/en/bench/guides/index.md 파일 보기

@@ -1 +1,5 @@
# Guides



{index}

+ 4
- 2
frappe/docs/user/en/bench/guides/manual-setup.md 파일 보기

@@ -1,3 +1,5 @@
# Manual Setup

Manual Setup
--------------

@@ -49,7 +51,7 @@ Basic Usage

* Add site

Frappe apps are run by frappe sites and you will have to create at least one
Frappé apps are run by frappe sites and you will have to create at least one
site. The new-site command allows you to do that.

bench new-site site1.local
@@ -60,7 +62,7 @@ Basic Usage

bench start

To login to Frappe / ERPNext, open your browser and go to `localhost:8000`
To login to Frappé / ERPNext, open your browser and go to `localhost:8000`

The default user name is "Administrator" and password is what you set when you created the new site.



+ 2
- 2
frappe/docs/user/en/bench/guides/settings-limits.md 파일 보기

@@ -1,6 +1,6 @@
# Setting Limits for your Site

Frappe v7 has added support for setting limits and restrictions for your site.
Frappé v7 has added support for setting limits and restrictions for your site.
These restrictions are set in the `site_config.json` file inside the site's folder.

{
@@ -36,4 +36,4 @@ Example:

You can check your usage by opening the "Usage Info" page from the toolbar / AwesomeBar. A limit will only show up on the page if it has been set.

<img class="screenshot" alt="Doctype Saved" src="{{docs_base_url}}/assets/img/usage_info.png">
<img class="screenshot" alt="Doctype Saved" src="/docs/assets/img/usage_info.png">

+ 2
- 0
frappe/docs/user/en/bench/guides/setup-multitenancy.md 파일 보기

@@ -1,3 +1,5 @@
# Setup Multitenancy

Assuming that you've already got your first site running and you've performed
the [production deployment steps](setup-production.html), this section explains how to host your second
site (and more). Your first site is automatically set as default site. You can


+ 3
- 1
frappe/docs/user/en/bench/guides/setup-production.md 파일 보기

@@ -1,3 +1,5 @@
# Setup Production

You can setup the bench for production use by configuring two programs, Supervisor and nginx. If you want to revert your Production Setup to Development Setup refer to [these commands](https://github.com/frappe/bench/wiki/Stopping-Production-and-starting-Development)

####Easy Production Setup
@@ -8,7 +10,7 @@ These steps are automated if you run `sudo bench setup production`
Supervisor
----------

Supervisor makes sure that the process that power the Frappe system keep running
Supervisor makes sure that the process that power the Frappé system keep running
and it restarts them if they happen to crash. You can generate the required
configuration for supervisor using the command `bench setup supervisor`. The
configuration will be available in `config/supervisor.conf` directory. You can


+ 2
- 0
frappe/docs/user/en/bench/index.md 파일 보기

@@ -1 +1,3 @@
# Bench

{index}

+ 5
- 3
frappe/docs/user/en/bench/resources/background-services.md 파일 보기

@@ -1,3 +1,5 @@
# Background Services

External services
-----------------

@@ -6,7 +8,7 @@ External services
* nginx (for production deployment)
* supervisor (for production deployment)

Frappe Processes
Frappé Processes
----------------


@@ -19,12 +21,12 @@ Frappe Processes

* Redis Worker Processes

* The Celery worker processes execute background jobs in the Frappe system.
* The Celery worker processes execute background jobs in the Frappé system.
These processes are automatically started when `bench start` is run and
for production are configured in supervisor configuration.

* Scheduler Process

* The Scheduler process schedules enqeueing of scheduled jobs in the
Frappe system. This process is automatically started when `bench start` is
Frappé system. This process is automatically started when `bench start` is
run and for production are configured in supervisor configuration.

+ 2
- 0
frappe/docs/user/en/bench/resources/bench-commands-cheatsheet.md 파일 보기

@@ -1,3 +1,5 @@
# Bench Commands Cheatsheet

### General Usage
* `bench --version` - Show bench version
* `bench src` - Show bench repo directory


+ 2
- 0
frappe/docs/user/en/bench/resources/bench-procfile.md 파일 보기

@@ -1,3 +1,5 @@
# Bench Procfile

`bench start` uses [honcho](http://honcho.readthedocs.org) to manage multiple processes in **developer mode**.

### Processes


+ 4
- 0
frappe/docs/user/en/bench/resources/index.md 파일 보기

@@ -1 +1,5 @@
# Resources



{index}

+ 3
- 1
frappe/docs/user/en/guides/app-development/adding-custom-button-to-form.md 파일 보기

@@ -1,3 +1,5 @@
# Adding Custom Button To Form

To create a custom button on your form, you need to edit the javascript file associated to your doctype. For example, If you want to add a custom button to User form then you must edit `user.js`.

In this file, you need to write a new method `add_custom_button` which should add a button to your form.
@@ -22,7 +24,7 @@ We should edit `frappe\core\doctype\user\user.js`

You should be seeing a button on user form as shown below,

<img class="screenshot" alt="Custom Button" src="{{docs_base_url}}/assets/img/app-development/add_custom_button.png">
<img class="screenshot" alt="Custom Button" src="/docs/assets/img/app-development/add_custom_button.png">


<!-- markdown -->

+ 2
- 0
frappe/docs/user/en/guides/app-development/adding-module-icons-on-desktop.md 파일 보기

@@ -1,3 +1,5 @@
# Adding Module Icons On Desktop

To create a module icon for a Page, List or Module, you will have to edit the `config/desktop.py` file in your app.

In this file you will have to write the `get_data` method that will return a dict object with the module icon parameters


+ 4
- 2
frappe/docs/user/en/guides/app-development/custom-module-icon.md 파일 보기

@@ -1,3 +1,5 @@
# Custom Module Icon

If you want to create a custom icon for your module, you will have to create an SVG file for your module and set the path to this file in the `desktop/config.py` of your app.<br>

This icon is loaded via AJAX first time, then it will be rendered.
@@ -8,10 +10,10 @@ Example:

def get_data():
return {
"Frappe Apps": {
"Frappé Apps": {
"color": "orange",
"icon": "assets/frappe/images/frappe.svg",
"label": _("Frappe.io Portal"),
"label": _("Frappé.io Portal"),
"type": "module"
}
}


+ 8
- 6
frappe/docs/user/en/guides/app-development/dialogs-types.md 파일 보기

@@ -1,8 +1,10 @@
Frappe provide a group of standard dialogs that are very usefull while coding.
# Dialogs Types

Frappé provide a group of standard dialogs that are very usefull while coding.

## Alert Dialog

<img class="screenshot" src="{{docs_base_url}}/assets/img/app-development/show_alert.png">
<img class="screenshot" src="/docs/assets/img/app-development/show_alert.png">

Is helpfull for show a non-obstructive message.

@@ -16,7 +18,7 @@ This dialog have 2 parameters `txt`that is the message and `seconds` that is the

## Prompt Dialog

<img class="screenshot" src="{{docs_base_url}}/assets/img/app-development/prompt.png">
<img class="screenshot" src="/docs/assets/img/app-development/prompt.png">

Is helpful for ask a value for the user

@@ -42,7 +44,7 @@ This dialog have 4 parameters, they are:
---
## Confirm Dialog

<img class="screenshot" src="{{docs_base_url}}/assets/img/app-development/confirm-dialog.png">
<img class="screenshot" src="/docs/assets/img/app-development/confirm-dialog.png">

Usefull to get a confirmation from the user before do an action

@@ -68,7 +70,7 @@ This dialog have 3 arguments, they are:

## Message Print

<img class="screenshot" src="{{docs_base_url}}/assets/img/app-development/msgprint.png">
<img class="screenshot" src="/docs/assets/img/app-development/msgprint.png">

Is helpfull for show a informational dialog for the user;

@@ -91,7 +93,7 @@ This dialog have 2 arguments, they are:

### Custom Dialog

<img class="screenshot" src="{{docs_base_url}}/assets/img/app-development/dialog.png">
<img class="screenshot" src="/docs/assets/img/app-development/dialog.png">

Frappé provide too a `Class` that you can extend and build your own custom dialogs



+ 2
- 0
frappe/docs/user/en/guides/app-development/executing-code-on-doctype-events.md 파일 보기

@@ -1,3 +1,5 @@
# Executing Code On Doctype Events

To execute code when a DocType is inserted, validated (before saving), updated, submitted, cancelled, deleted, you must write in the DocType's controller module.

#### 1. Controller Module


+ 2
- 2
frappe/docs/user/en/guides/app-development/exporting-customizations.md 파일 보기

@@ -4,12 +4,12 @@ A common use case is to extend a DocType via Custom Fields and Property Setters

You will see a button for **Export Customizations**

<img class="screenshot" src="{{ docs_base_url }}/assets/img/app-development/export-custom-1.png">
<img class="screenshot" src="/docs/assets/img/app-development/export-custom-1.png">

Here you can select the module and whether you want these particular customizations to be synced after every update.

The customizations will be exported to a new folder `custom` in the module folder of your app. The customizations will be saved by the name of the DocType

<img class="screenshot" src="{{ docs_base_url }}/assets/img/app-development/export-custom-2.png">
<img class="screenshot" src="/docs/assets/img/app-development/export-custom-2.png">

When you do `bench update` or `bench migrate` these customizations will be synced to the app.

+ 3
- 3
frappe/docs/user/en/guides/app-development/fetch-custom-field-value-from-master-to-all-related-transactions.md 파일 보기

@@ -5,11 +5,11 @@ Let's say, there is a custom field "VAT Number" in Supplier, which should be fet
#### Steps:

1. Create a Custom Field **VAT Number** for *Supplier* document with *Field Type* as **Data**.
<img class="screenshot" src="{{ docs_base_url }}/assets/img/add-vat-number-in-supplier.png">
<img class="screenshot" src="/docs/assets/img/add-vat-number-in-supplier.png">

1. Create another Custom Field **VAT Number** for *Purchase Order* document, but in this case with *Field Type* as **Read Only** or check **Read Only** checkbox. Set the **Options** as `supplier.vat_number`.
<img class="screenshot" src="{{ docs_base_url }}/assets/img/add-vat-number-in-purchase-order.png">
<img class="screenshot" src="/docs/assets/img/add-vat-number-in-purchase-order.png">

1. Go to the user menu and click "Reload".
1. Now, on selection of Supplier in a new Purchase Order, **VAT Number** will be fetched automatically from the selected Supplier.
<img class="screenshot" src="{{ docs_base_url }}/assets/img/vat-number-fetched.png">
<img class="screenshot" src="/docs/assets/img/vat-number-fetched.png">

+ 13
- 57
frappe/docs/user/en/guides/app-development/generating-docs.md 파일 보기

@@ -1,18 +1,17 @@
# Generating Documentation Website for your App

Frappe version 6.7 onwards includes a full-blown documentation generator so that you can easily create a website for your app that has both user docs and developers docs (auto-generated). These pages are generated as static HTML pages so that you can add them as GitHub pages.
Frappé version 6.7 onwards includes a full-blown documentation generator so that you can easily create a website for your app that has both user docs and developers docs (auto-generated).

Version 8.7 onwards, these will be generated in a target app.

## Writing Docs

### 1. Setting up docs

#### 1.1. Setup `docs.py`

The first step is to setup the docs folder. For that you must create a new file in your app `config/docs.py` if it is not auto-generated. In your `docs.py` file, add the following module properties.


source_link = "https://github.com/[orgname]/[reponame]"
docs_base_url = "https://[orgname].github.io/[reponame]"
headline = "This is what my app does"
sub_heading = "Slightly more details with key features"
long_description = """(long description in markdown)"""
@@ -29,16 +28,6 @@ The first step is to setup the docs folder. For that you must create a new file

pass

#### 1.2. Generate `/docs`

To generate the docs for the `current` version, go to the command line and write

bench --site [site] build-docs [appname]
If you want to maintain versions of your docs, then you can add a version number instead of `current`

This will create a `/docs` folder in your app.

### 2. Add User Documentation

To add user documentation, add folders and pages in your `/docs/user` folder in the same way you would build a website pages in the `www` folder.
@@ -51,64 +40,31 @@ Some quick tips:

### 3. Linking

While linking make sure you add `{{ docs_base_url }}` to all your links.
While linking make sure you add `/docs` to all your links.


{% raw %}<a href="{{ docs_base_url }}/user/link/to/page.html">Link Description</a>{% endraw %}
{% raw %}<a href="/docs/user/link/to/page.html">Link Description</a>{% endraw %}


### 4. Adding Images

You can add images in the `/docs/assets` folder. You can add links to the images as follows:

{% raw %}<img src="{{ docs_base_url }}/assets/img/my-img/gif" class="screenshot">{% endraw %}

---

## Setting up output docs

The output docs are generated in your `docs/appname` folder using the `write-docs` command.

---

## Viewing Locally

To test your docs locally, add a `--local` option to the `write-docs` command.

bench --site [site] write-docs [appname] --local

Then it will build urls so that you can view these files locally. To view them locally in your browser, you can use the Python SimpleHTTPServer

Run this from your `docs/myapp` folder:

python -m SimpleHTTPServer 8080
{% raw %}<img src="/docs/assets/img/my-img/gif" class="screenshot">{% endraw %}

---

## Publishing to GitHub Pages

To publish your docs on GitHub pages, you will have to create an empty and orphan branch in your repository called `gh-pages` and push your documentation there.
## Building Docs

1. To easily publish your docs on gh-pages, commit and push your `apps/docs` folder on you master branch first.
2. The `/docs` generation will also generate a `/docs` folder in your bench, parallel to your `/sites` folder. e.g. `/frappe-bench/docs`
3. Generate you documentation using the `write-docs` command.
4. Go to your docs folder `cd docs/myapp`
5. Checkout the gh-pages branch `git checkout --orphan gh-pages`
6. Push your documentation to Github.
You must create a new app that will have the output of the docs, which is called the "target" app. For example, the docs for ERPNext are hosted at erpnext.org, which is based on the app "foundation". You can create a new app just to push docs of any other app.

Note > The branch name `gh-pages` is only if you are using GitHub. If you are hosting this on any other static file server, you can create any other orphan branch instead.
To output docs to another app,

Putting it all together:
bench --site [site] build-docs [app] --target [target_app]

# build the apps/docs folder and write the compiled docs at docs/appname
bench --site [site] build-docs [appname]
This will create a new folder `/docs` inside the `www` folder of the target app and generate automatic docs (from code), model references and copy user docs and assets.

# commit to the gh-pages branch (for GitHub Pages)
cd docs/appname
git checkout --orphan gh-pages
git remote add origin [remote git repository]
git add *
git commit -m "Documentation Initialization"
git push origin gh-pages
To view the docs, just go the the `/docs` url on your target app. Example:

To check your documentation online go to: https://[orgname].github.io/[reponame]
https://erpnext.org/docs

+ 2
- 0
frappe/docs/user/en/guides/app-development/how-enable-developer-mode-in-frappe.md 파일 보기

@@ -1,3 +1,5 @@
# How Enable Developer Mode In Frappé

When you are in application design mode and you want the changes in your DocTypes, Reports etc to affect the app repository, you must be in **Developer Mode**.

To enable developer mode, update the `site_config.json` file of your site in the sites folder for example:


+ 3
- 1
frappe/docs/user/en/guides/app-development/how-to-create-custom-fields-during-app-installation.md 파일 보기

@@ -1,6 +1,8 @@
# How To Create Custom Fields During App Installation

Your custom app can automatically add **Custom Fields** to DocTypes outside of your app when it is installed to a new site.

To do this, add the new custom fields that your app requires, using the Frappe web application.
To do this, add the new custom fields that your app requires, using the Frappé web application.

In your `hooks.py` file, add `"Custom Fields"`



이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.

불러오는 중...
취소
저장