Переглянути джерело

Merge branch 'develop'

version-14
Nabin Hait 8 роки тому
джерело
коміт
c837deff60
100 змінених файлів з 4020 додано та 2432 видалено
  1. +39
    -15
      frappe/__init__.py
  2. +24
    -0
      frappe/api.py
  3. +0
    -1
      frappe/app.py
  4. +7
    -0
      frappe/auth.py
  5. +15
    -7
      frappe/boot.py
  6. +12
    -10
      frappe/build.py
  7. +24
    -0
      frappe/change_log/v7/v7_1_0.md
  8. +17
    -7
      frappe/client.py
  9. +1
    -1
      frappe/commands/docs.py
  10. +18
    -1
      frappe/commands/site.py
  11. +1
    -1
      frappe/commands/translate.py
  12. +64
    -5
      frappe/commands/utils.py
  13. +6
    -1
      frappe/config/core.py
  14. +16
    -7
      frappe/config/setup.py
  15. +69
    -3
      frappe/core/doctype/docfield/docfield.json
  16. +54
    -1
      frappe/core/doctype/docperm/docperm.json
  17. +1
    -3
      frappe/core/doctype/docshare/docshare.py
  18. +4
    -4
      frappe/core/doctype/docshare/test_docshare.py
  19. +16
    -9
      frappe/core/doctype/doctype/doctype.js
  20. +77
    -1
      frappe/core/doctype/doctype/doctype.json
  21. +8
    -3
      frappe/core/doctype/doctype/doctype.py
  22. +0
    -0
      frappe/core/doctype/error_log/__init__.py
  23. +8
    -0
      frappe/core/doctype/error_log/error_log.js
  24. +10
    -4
      frappe/core/doctype/error_log/error_log.json
  25. +21
    -0
      frappe/core/doctype/error_log/error_log.py
  26. +1
    -1
      frappe/core/doctype/error_log/error_log_list.js
  27. +12
    -0
      frappe/core/doctype/error_log/test_error_log.py
  28. +0
    -0
      frappe/core/doctype/error_snapshot/error_object.html
  29. +0
    -0
      frappe/core/doctype/error_snapshot/error_snapshot.html
  30. +31
    -4
      frappe/core/doctype/file/file.json
  31. +18
    -1
      frappe/core/doctype/file/file.py
  32. +0
    -0
      frappe/core/doctype/language/__init__.py
  33. +8
    -0
      frappe/core/doctype/language/language.js
  34. +160
    -0
      frappe/core/doctype/language/language.json
  35. +41
    -0
      frappe/core/doctype/language/language.py
  36. +12
    -0
      frappe/core/doctype/language/test_language.py
  37. +7
    -8
      frappe/core/doctype/page/page.py
  38. +36
    -0
      frappe/core/doctype/report/report.py
  39. +22
    -2
      frappe/core/doctype/report/test_report.py
  40. +17
    -0
      frappe/core/doctype/report/user_activity_report.json
  41. +31
    -2
      frappe/core/doctype/role/role.json
  42. +16
    -8
      frappe/core/doctype/role/test_records.json
  43. +0
    -1
      frappe/core/doctype/scheduler_log/README.md
  44. +0
    -3
      frappe/core/doctype/scheduler_log/__init__.py
  45. +0
    -8
      frappe/core/doctype/scheduler_log/scheduler_log.js
  46. +0
    -19
      frappe/core/doctype/scheduler_log/scheduler_log.py
  47. +0
    -12
      frappe/core/doctype/scheduler_log/test_scheduler_log.py
  48. +0
    -1
      frappe/core/doctype/system_settings/system_settings.js
  49. +680
    -601
      frappe/core/doctype/system_settings/system_settings.json
  50. +38
    -5
      frappe/core/doctype/translation/test_translation.py
  51. +0
    -3
      frappe/core/doctype/translation/translation.js
  52. +15
    -55
      frappe/core/doctype/translation/translation.json
  53. +3
    -2
      frappe/core/doctype/translation/translation.py
  54. +32
    -4
      frappe/core/doctype/user/test_user.py
  55. +0
    -2
      frappe/core/doctype/user/user.js
  56. +1472
    -1472
      frappe/core/doctype/user/user.json
  57. +82
    -14
      frappe/core/doctype/user/user.py
  58. +1
    -1
      frappe/core/notifications.py
  59. +0
    -0
      frappe/core/page/background_jobs/__init__.py
  60. +39
    -0
      frappe/core/page/background_jobs/background_jobs.html
  61. +39
    -0
      frappe/core/page/background_jobs/background_jobs.js
  62. +22
    -0
      frappe/core/page/background_jobs/background_jobs.json
  63. +51
    -0
      frappe/core/page/background_jobs/background_jobs.py
  64. +12
    -0
      frappe/core/page/background_jobs/background_jobs_outer.html
  65. +6
    -0
      frappe/core/page/data_import_tool/data_import_main.html
  66. +9
    -4
      frappe/core/page/data_import_tool/data_import_tool.js
  67. +2
    -2
      frappe/core/page/data_import_tool/data_import_tool.py
  68. +1
    -0
      frappe/core/page/data_import_tool/data_import_tool_columns.html
  69. +8
    -3
      frappe/core/page/data_import_tool/importer.py
  70. +2
    -2
      frappe/core/page/permission_manager/permission_manager.py
  71. +1
    -1
      frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js
  72. +0
    -0
      frappe/core/web_form/__init__.py
  73. +0
    -0
      frappe/core/web_form/edit_profile/__init__.py
  74. +3
    -0
      frappe/core/web_form/edit_profile/edit_profile.js
  75. +86
    -0
      frappe/core/web_form/edit_profile/edit_profile.json
  76. +7
    -0
      frappe/core/web_form/edit_profile/edit_profile.py
  77. +7
    -2
      frappe/custom/doctype/custom_script/custom_script.json
  78. +26
    -0
      frappe/custom/doctype/customize_form/customize_form.js
  79. +46
    -1
      frappe/custom/doctype/customize_form/customize_form.json
  80. +59
    -20
      frappe/custom/doctype/customize_form/customize_form.py
  81. +1
    -1
      frappe/custom/doctype/customize_form/test_customize_form.py
  82. +62
    -1
      frappe/custom/doctype/customize_form_field/customize_form_field.json
  83. +3
    -0
      frappe/data/Framework.sql
  84. +0
    -57
      frappe/data/languages.txt
  85. +2
    -1
      frappe/defaults.py
  86. +5
    -1
      frappe/desk/desk_page.py
  87. +12
    -7
      frappe/desk/doctype/bulk_update/bulk_update.json
  88. +6
    -1
      frappe/desk/doctype/bulk_update/bulk_update.py
  89. +5
    -0
      frappe/desk/doctype/desktop_icon/desktop_icon.py
  90. +4
    -4
      frappe/desk/doctype/event/event.py
  91. +3
    -0
      frappe/desk/doctype/event/event_list.js
  92. +3
    -5
      frappe/desk/doctype/note/note.js
  93. +113
    -3
      frappe/desk/doctype/note/note.json
  94. +13
    -0
      frappe/desk/doctype/note/note.py
  95. +0
    -0
      frappe/desk/doctype/note_seen_by/__init__.py
  96. +64
    -0
      frappe/desk/doctype/note_seen_by/note_seen_by.json
  97. +10
    -0
      frappe/desk/doctype/note_seen_by/note_seen_by.py
  98. +3
    -2
      frappe/desk/form/meta.py
  99. +4
    -0
      frappe/desk/page/backups/backups.js
  100. +34
    -1
      frappe/desk/page/backups/backups.py

+ 39
- 15
frappe/__init__.py Переглянути файл

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

__version__ = "7.0.47"
__version__ = '7.2.0'
__title__ = "Frappe Framework"

local = Local()

@@ -46,7 +47,7 @@ def _(msg, lang=None):
lang = local.lang

# msg should always be unicode
msg = cstr(msg)
msg = cstr(msg).strip()

return get_full_dict(local.lang).get(msg) or msg

@@ -55,8 +56,6 @@ def get_lang_dict(fortype, name=None):

:param fortype: must be one of `doctype`, `page`, `report`, `include`, `jsfile`, `boot`
:param name: name of the document for which assets are to be returned."""
if local.lang=="en":
return {}
from frappe.translate import get_dict
return get_dict(fortype, name)

@@ -172,7 +171,9 @@ def get_site_config(sites_path=None, site_path=None):
if os.path.exists(site_config):
config.update(get_file_json(site_config))
elif local.site and not local.flags.new_site:
raise IncorrectSitePath, "{0} does not exist".format(site_config)
print "{0} does not exist".format(local.site)
sys.exit(1)
#raise IncorrectSitePath, "{0} does not exist".format(site_config)

return _dict(config)

@@ -273,7 +274,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
if as_table and type(msg) in (list, tuple):
out.msg = '<table border="1px" style="border-collapse: collapse" cellpadding="2px">' + ''.join(['<tr>'+''.join(['<td>%s</td>' % c for c in r])+'</tr>' for r in msg]) + '</table>'

if flags.print_messages:
if flags.print_messages and out.msg:
print "Message: " + repr(out.msg).encode("utf-8")

if title:
@@ -488,7 +489,7 @@ def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=Fals

return out

def has_website_permission(doctype, ptype="read", doc=None, user=None, verbose=False):
def has_website_permission(doc=None, ptype='read', user=None, verbose=False):
"""Raises `frappe.PermissionError` if not permitted.

:param doctype: DocType for which permission is to be check.
@@ -499,11 +500,21 @@ def has_website_permission(doctype, ptype="read", doc=None, user=None, verbose=F
if not user:
user = session.user

hooks = (get_hooks("has_website_permission") or {}).get(doctype, [])
if hooks:
if doc:
if isinstance(doc, basestring):
doc = get_doc(doctype, doc)

doctype = doc.doctype

if doc.flags.ignore_permissions:
return True

# check permission in controller
if hasattr(doc, 'has_website_permission'):
return doc.has_website_permission(ptype, verbose=verbose)

hooks = (get_hooks("has_website_permission") or {}).get(doctype, [])
if hooks:
for method in hooks:
result = call(method, doc=doc, ptype=ptype, user=user, verbose=verbose)
# if even a single permission check is Falsy
@@ -553,7 +564,7 @@ def new_doc(doctype, parent_doc=None, parentfield=None, as_dict=False):
from frappe.model.create_new import get_new_doc
return get_new_doc(doctype, parent_doc, parentfield, as_dict=as_dict)

def set_value(doctype, docname, fieldname, value):
def set_value(doctype, docname, fieldname, value=None):
"""Set document value. Calls `frappe.client.set_value`"""
import frappe.client
return frappe.client.set_value(doctype, docname, fieldname, value)
@@ -1113,7 +1124,7 @@ def as_json(obj, indent=1):
return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler)

def are_emails_muted():
return flags.mute_emails or conf.get("mute_emails") or False
return flags.mute_emails or int(conf.get("mute_emails") or 0) or False

def get_test_records(doctype):
"""Returns list of objects from `test_records.json` in the given doctype's folder."""
@@ -1125,13 +1136,21 @@ def get_test_records(doctype):
else:
return []

def format_value(value, df, doc=None, currency=None):
def format_value(*args, **kwargs):
"""Format value with given field properties.

:param value: Value to be formatted.
:param df: DocField object with properties `fieldtype`, `options` etc."""
:param df: (Optional) DocField object with properties `fieldtype`, `options` etc."""
import frappe.utils.formatters
return frappe.utils.formatters.format_value(value, df, doc, currency=currency)
return frappe.utils.formatters.format_value(*args, **kwargs)

def format(*args, **kwargs):
"""Format value with given field properties.

:param value: Value to be formatted.
:param df: (Optional) DocField object with properties `fieldtype`, `options` etc."""
import frappe.utils.formatters
return frappe.utils.formatters.format_value(*args, **kwargs)

def get_print(doctype=None, name=None, print_format=None, style=None, html=None, as_pdf=False, doc=None):
"""Get Print Format for given document.
@@ -1241,7 +1260,12 @@ log_level = None
def logger(module=None, with_more_info=True):
'''Returns a python logger that uses StreamHandler'''
from frappe.utils.logger import get_logger
return get_logger(module or __name__, with_more_info=with_more_info)
return get_logger(module or 'default', with_more_info=with_more_info)

def log_error(message=None, title=None):
'''Log error to Error Log'''
get_doc(dict(doctype='Error Log', error=str(message or get_traceback()),
method=title)).insert(ignore_permissions=True)

def get_desk_link(doctype, name):
return '<a href="#Form/{0}/{1}" style="font-weight: bold;">{2} {1}</a>'.format(doctype, name, _(doctype))


+ 24
- 0
frappe/api.py Переглянути файл

@@ -8,6 +8,9 @@ import frappe.handler
import frappe.client
from frappe.utils.response import build_response
from frappe import _
from urlparse import urlparse
from urllib import urlencode
from frappe.integration_broker.oauth2 import oauth_server

def handle():
"""
@@ -32,6 +35,27 @@ def handle():

`/api/resource/{doctype}/{name}?run_method={method}` will run a whitelisted controller method
"""

form_dict = frappe.local.form_dict
authorization_header = frappe.get_request_header("Authorization").split(" ") if frappe.get_request_header("Authorization") else None
if authorization_header and authorization_header[0].lower() == "bearer":
token = authorization_header[1]
r = frappe.request
parsed_url = urlparse(r.url)
access_token = { "access_token": token}
uri = parsed_url.scheme + "://" + parsed_url.netloc + parsed_url.path + "?" + urlencode(access_token)
http_method = r.method
body = r.get_data()
headers = r.headers

required_scopes = frappe.db.get_value("OAuth Bearer Token", token, "scopes").split(";")

valid, oauthlib_request = oauth_server.verify_request(uri, http_method, body, headers, required_scopes)

if valid:
frappe.set_user(frappe.db.get_value("OAuth Bearer Token", token, "user"))
frappe.local.form_dict = form_dict

parts = frappe.request.path[1:].split("/",3)
call = doctype = name = None



+ 0
- 1
frappe/app.py Переглянути файл

@@ -72,7 +72,6 @@ def application(request):
raise NotFound

except HTTPException, e:
frappe.logger().error('Request Error', exc_info=True)
return e

except frappe.SessionStopped, e:


+ 7
- 0
frappe/auth.py Переглянути файл

@@ -150,6 +150,13 @@ class LoginManager:
if not resume:
frappe.response["full_name"] = self.full_name

# redirect information
redirect_to = frappe.cache().hget('redirect_after_login', self.user)
if redirect_to:
frappe.local.response["redirect_to"] = redirect_to
frappe.cache().hdel('redirect_after_login', self.user)


frappe.local.cookie_manager.set_cookie("full_name", self.full_name)
frappe.local.cookie_manager.set_cookie("user_id", self.user)
frappe.local.cookie_manager.set_cookie("user_image", self.info.user_image or "")


+ 15
- 7
frappe/boot.py Переглянути файл

@@ -103,16 +103,18 @@ def get_allowed_pages():
return page_info

def load_translations(bootinfo):
if frappe.local.lang != 'en':
messages = frappe.get_lang_dict("boot")
messages = frappe.get_lang_dict("boot")

bootinfo["lang"] = frappe.lang
bootinfo["lang"] = frappe.lang

# load translated report names
for name in bootinfo.user.all_reports:
messages[name] = frappe._(name)
# load translated report names
for name in bootinfo.user.all_reports:
messages[name] = frappe._(name)

bootinfo["__messages"] = messages
# only untranslated
messages = {k:v for k, v in messages.iteritems() if k!=v}

bootinfo["__messages"] = messages

def get_fullnames():
"""map of user fullnames"""
@@ -165,3 +167,9 @@ def load_print(bootinfo, doclist):

def load_print_css(bootinfo, print_settings):
bootinfo.print_css = frappe.get_attr("frappe.www.print.get_print_style")(print_settings.print_style or "Modern", for_legacy=True)

def get_unseen_notes():
return frappe.db.sql('''select name, title, content from tabNote where notify_on_login=1
and expire_notification_on > %s and %s not in
(select user from `tabNote Seen By` nsb
where nsb.parent=tabNote.name)''', (frappe.utils.now(), frappe.session.user), as_dict=True)

+ 12
- 10
frappe/build.py Переглянути файл

@@ -57,14 +57,19 @@ def make_asset_dirs(make_copy=False):
# symlink app/public > assets/app
for app_name in frappe.get_all_apps(True):
pymodule = frappe.get_module(app_name)
source = os.path.join(os.path.abspath(os.path.dirname(pymodule.__file__)), 'public')
target = os.path.join(assets_path, app_name)
app_base_path = os.path.abspath(os.path.dirname(pymodule.__file__))

if not os.path.exists(target) and os.path.exists(source):
if make_copy:
shutil.copytree(os.path.abspath(source), target)
else:
os.symlink(os.path.abspath(source), target)
symlinks = []
symlinks.append([os.path.join(app_base_path, 'public'), os.path.join(assets_path, app_name)])
symlinks.append([os.path.join(app_base_path, 'docs'), os.path.join(assets_path, app_name + '_docs')])

for source, target in symlinks:
source = os.path.abspath(source)
if not os.path.exists(target) and os.path.exists(source):
if make_copy:
shutil.copytree(source, target)
else:
os.symlink(source, target)

def build(no_compress=False, verbose=False):
assets_path = os.path.join(frappe.local.sites_path, "assets")
@@ -72,9 +77,6 @@ def build(no_compress=False, verbose=False):
for target, sources in get_build_maps().iteritems():
pack(os.path.join(assets_path, target), sources, no_compress, verbose)

shutil.copy(os.path.join(os.path.dirname(os.path.abspath(frappe.__file__)), 'data', 'languages.txt'), frappe.local.sites_path)
# reset_app_html()

def get_build_maps():
"""get all build.jsons with absolute paths"""
# framework js and css files


+ 24
- 0
frappe/change_log/v7/v7_1_0.md Переглянути файл

@@ -0,0 +1,24 @@
#### Gantt View
- New Gantt view for documents where date range is available

#### In-App Help
- Search for help from within the app. Click on "Help"

#### Web Form
- Add grids (child tables)
- Add page breaks (for long forms)
- Add payment gateway
- Add attachments

#### Auto Email Report
- Email reports automatically on daily / weekly / monthly basis

#### Other Fixes
- Send a popup to all users on login for a new Note by checking on "Notify users with a popup when they log in"
- Portal Users (Customers, Supplier, Students) can now have roles
- Sidebar in portal view will be rendered as per roles and can be configured from Portal Settings
- Restrict the number of backups to be saved in System Settings
- Scheduler log is now error log and as MyISAM
- A better way to export customzations and Email Alert directly from Customize Form
- Option to send email from Data Import Tool where applicable
- Integration Broker

+ 17
- 7
frappe/client.py Переглянути файл

@@ -47,22 +47,32 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False):
return frappe.db.get_value(doctype, filters, fieldname, as_dict=as_dict, debug=debug)

@frappe.whitelist()
def set_value(doctype, name, fieldname, value):
def set_value(doctype, name, fieldname, value=None):
'''Set a value using get_doc, group of values

:param doctype: DocType of the document
:param name: name of the document
:param fieldname: fieldname string or JSON / dict with key value pair
:param value: value if fieldname is JSON / dict'''

if fieldname!="idx" and fieldname in frappe.model.default_fields:
frappe.throw(_("Cannot edit standard fields"))

if not value:
values = fieldname
if isinstance(fieldname, basestring):
values = json.loads(fieldname)
else:
values = {fieldname: value}

doc = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True)
if doc and doc.parent and doc.parenttype:
doc = frappe.get_doc(doc.parenttype, doc.parent)
child = doc.getone({"doctype": doctype, "name": name})
child.set(fieldname, value)
child.update(values)
else:
doc = frappe.get_doc(doctype, name)
df = doc.meta.get_field(fieldname)
if df.fieldtype == "Read Only" or df.read_only:
frappe.throw(_("Can not edit Read Only fields"))
else:
doc.set(fieldname, value)
doc.update(values)

doc.save()



+ 1
- 1
frappe/commands/docs.py Переглянути файл

@@ -52,7 +52,7 @@ def build_docs(context, app, docs_version="current", target=None, local=False, w
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("frappe", "..", "..")
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):


+ 18
- 1
frappe/commands/site.py Переглянути файл

@@ -66,7 +66,9 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N
print "*** Scheduler is", scheduler_status, "***"

finally:
os.remove(installing)
if os.path.exists(installing):
os.remove(installing)

frappe.destroy()

@click.command('restore')
@@ -238,6 +240,20 @@ def reload_doc(context, module, doctype, docname):
finally:
frappe.destroy()

@click.command('reload-doctype')
@click.argument('doctype')
@pass_context
def reload_doctype(context, doctype):
"Reload schema for a DocType"
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
frappe.reload_doctype(doctype, force=context.force)
frappe.db.commit()
finally:
frappe.destroy()


@click.command('use')
@click.argument('site')
@@ -455,6 +471,7 @@ commands = [
new_site,
reinstall,
reload_doc,
reload_doctype,
remove_from_installed_apps,
restore,
run_patch,


+ 1
- 1
frappe/commands/translate.py Переглянути файл

@@ -33,7 +33,7 @@ def new_language(context, lang_code, app):
frappe.translate.write_translations_file(app, lang_code)

print "File created at ./apps/{app}/{app}/translations/{lang_code}.csv".format(app=app, lang_code=lang_code)
print "You will need to add the language in frappe/data/languages.txt, if you haven't done it already."
print "You will need to add the language in frappe/geo/languages.json, if you haven't done it already."

@click.command('get-untranslated')
@click.argument('lang')


+ 64
- 5
frappe/commands/utils.py Переглянути файл

@@ -171,7 +171,7 @@ def export_json(context, doctype, path, name=None):
@click.argument('path')
@pass_context
def export_csv(context, doctype, path):
"Export data import template for DocType"
"Export data import template with data for DocType"
from frappe.core.page.data_import_tool import data_import_tool
for site in context.sites:
try:
@@ -200,6 +200,13 @@ def export_fixtures(context):
def import_doc(context, path, force=False):
"Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported"
from frappe.core.page.data_import_tool import data_import_tool

if not os.path.exists(path):
path = os.path.join('..', path)
if not os.path.exists(path):
print 'Invalid path {0}'.format(path)
sys.exit(1)

for site in context.sites:
try:
frappe.init(site=site)
@@ -213,13 +220,21 @@ def import_doc(context, path, force=False):
@click.option('--only-insert', default=False, is_flag=True, help='Do not overwrite existing records')
@click.option('--submit-after-import', default=False, is_flag=True, help='Submit document after importing it')
@click.option('--ignore-encoding-errors', default=False, is_flag=True, help='Ignore encoding errors while coverting to unicode')
@click.option('--no-email', default=True, is_flag=True, help='Send email if applicable')

@pass_context
def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False):
def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False, no_email=True):
"Import CSV using data import tool"
from frappe.core.page.data_import_tool import importer
from frappe.utils.csvutils import read_csv_content
site = get_site(context)

if not os.path.exists(path):
path = os.path.join('..', path)
if not os.path.exists(path):
print 'Invalid path {0}'.format(path)
sys.exit(1)

with open(path, 'r') as csvfile:
content = read_csv_content(csvfile.read())

@@ -227,7 +242,7 @@ def import_csv(context, path, only_insert=False, submit_after_import=False, igno
frappe.connect()

try:
importer.upload(content, submit_after_import=submit_after_import,
importer.upload(content, submit_after_import=submit_after_import, no_email=no_email,
ignore_encoding_errors=ignore_encoding_errors, overwrite=not only_insert,
via_console=True)
frappe.db.commit()
@@ -284,8 +299,9 @@ def console(context):
@click.option('--driver', help="For Travis")
@click.option('--module', help="Run tests in a module")
@click.option('--profile', is_flag=True, default=False)
@click.option('--junit-xml-output', help="Destination file path for junit xml report")
@pass_context
def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None, profile=False):
def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None, profile=False, junit_xml_output=False):
"Run tests"
import frappe.test_runner
from frappe.utils import sel
@@ -299,7 +315,7 @@ def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None

try:
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests,
force=context.force, profile=profile)
force=context.force, profile=profile, junit_xml_output=junit_xml_output)
if len(ret.failures) == 0 and len(ret.errors) == 0:
ret = 0
finally:
@@ -382,6 +398,47 @@ def get_version():
if hasattr(module, "__version__"):
print "{0} {1}".format(m, module.__version__)



@click.command('setup-global-help')
@click.option('--mariadb_root_password')
def setup_global_help(mariadb_root_password=None):
'''setup help table in a separate database that will be
shared by the whole bench and set `global_help_setup` as 1 in
common_site_config.json'''

from frappe.installer import update_site_config

frappe.local.flags = frappe._dict()
frappe.local.flags.in_setup_help = True
frappe.local.flags.in_install = True
frappe.local.lang = 'en'
frappe.local.conf = frappe.get_site_config(sites_path='.')

update_site_config('global_help_setup', 1,
site_config_path=os.path.join('.', 'common_site_config.json'))

if mariadb_root_password:
frappe.local.conf.root_password = mariadb_root_password

from frappe.utils.help import sync
sync()

@click.command('setup-help')
@pass_context
def setup_help(context):
'''Setup help table in the current site (called after migrate)'''
from frappe.utils.help import sync

for site in context.sites:
try:
frappe.init(site)
frappe.connect()
sync()
finally:
frappe.destroy()


commands = [
build,
clear_cache,
@@ -406,4 +463,6 @@ commands = [
watch,
_bulk_rename,
add_to_email_queue,
setup_global_help,
setup_help
]

+ 6
- 1
frappe/config/core.py Переглянути файл

@@ -43,7 +43,7 @@ def get_data():
"items": [
{
"type": "doctype",
"name": "Scheduler Log",
"name": "Error Log",
"description": _("Errors in Background Events"),
},
{
@@ -51,6 +51,11 @@ def get_data():
"name": "Email Queue",
"description": _("Background Email Queue"),
},
{
"type": "page",
"label": _("Background Jobs"),
"name": "background_jobs",
},
{
"type": "doctype",
"name": "Error Snapshot",


+ 16
- 7
frappe/config/setup.py Переглянути файл

@@ -75,7 +75,7 @@ def get_data():
},
{
"type": "doctype",
"name": "Scheduler Log",
"name": "Error Log",
"description": _("Log of error on automated events (scheduler).")
},
{
@@ -156,8 +156,8 @@ def get_data():
},
{
"type": "doctype",
"name": "Email Group Member",
"description": _("Email Group Member List"),
"name": "Auto Email Report",
"description": _("Setup Reports to be emailed at regular intervals"),
},
]
},
@@ -222,10 +222,19 @@ def get_data():
},
{
"type": "doctype",
"name": "Dropbox Backup",
"description": _("Manage cloud backups on Dropbox"),
"hide_count": True
}
"name": "Integration Service",
"description": _("Centralize access to Integrations"),
},
{
"type": "doctype",
"name": "OAuth Client",
"description": _("Register OAuth Client App"),
},
{
"type": "doctype",
"name": "OAuth Provider Settings",
"description": _("Settings for OAuth Provider"),
},
]
},
{


+ 69
- 3
frappe/core/doctype/docfield/docfield.json Переглянути файл

@@ -15,6 +15,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "label_and_type",
"fieldtype": "Section Break",
"hidden": 0,
@@ -39,6 +40,7 @@
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"fieldname": "label",
"fieldtype": "Data",
"hidden": 0,
@@ -67,6 +69,7 @@
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
@@ -95,6 +98,7 @@
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"fieldname": "fieldname",
"fieldtype": "Data",
"hidden": 0,
@@ -121,6 +125,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reqd",
"fieldtype": "Check",
"hidden": 0,
@@ -149,6 +154,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
@@ -176,6 +182,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
@@ -202,6 +209,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "search_index",
"fieldtype": "Check",
"hidden": 0,
@@ -230,6 +238,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "in_list_view",
"fieldtype": "Check",
"hidden": 0,
@@ -256,6 +265,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bold",
"fieldtype": "Check",
"hidden": 0,
@@ -281,6 +291,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.fieldtype===\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
@@ -307,6 +318,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
@@ -333,6 +345,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"hidden": 0,
@@ -356,6 +369,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
"fieldname": "options",
"fieldtype": "Text",
@@ -383,6 +397,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default",
"fieldtype": "Small Text",
"hidden": 0,
@@ -409,6 +424,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "permissions",
"fieldtype": "Section Break",
"hidden": 0,
@@ -433,6 +449,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "depends_on",
"fieldtype": "Code",
"hidden": 0,
@@ -459,6 +476,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "hidden",
"fieldtype": "Check",
"hidden": 0,
@@ -487,6 +505,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "read_only",
"fieldtype": "Check",
"hidden": 0,
@@ -513,6 +532,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "unique",
"fieldtype": "Check",
"hidden": 0,
@@ -538,6 +558,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Do not allow user to change after set the first time",
"fieldname": "set_only_once",
"fieldtype": "Check",
@@ -563,6 +584,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_13",
"fieldtype": "Column Break",
"hidden": 0,
@@ -586,6 +608,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
@@ -615,6 +638,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "User permissions should not apply for this Link",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
@@ -640,6 +664,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"hidden": 0,
@@ -668,6 +693,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "report_hide",
"fieldtype": "Check",
"hidden": 0,
@@ -696,6 +722,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Don't HTML Encode HTML tags like &lt;script&gt; or just characters like &lt; or &gt;, as they could be intentionally used in this field",
"fieldname": "ignore_xss_filter",
"fieldtype": "Check",
@@ -722,6 +749,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "display",
"fieldtype": "Section Break",
"hidden": 0,
@@ -746,6 +774,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "in_filter",
"fieldtype": "Check",
"hidden": 0,
@@ -774,6 +803,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "no_copy",
"fieldtype": "Check",
"hidden": 0,
@@ -802,6 +832,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "print_hide",
"fieldtype": "Check",
"hidden": 0,
@@ -830,6 +861,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
@@ -856,6 +888,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "print_width",
"fieldtype": "Data",
"hidden": 0,
@@ -880,6 +913,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "width",
"fieldtype": "Data",
"hidden": 0,
@@ -908,6 +942,35 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Columns",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_22",
"fieldtype": "Column Break",
"hidden": 0,
@@ -931,6 +994,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
@@ -959,6 +1023,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "oldfieldname",
"fieldtype": "Data",
"hidden": 1,
@@ -984,6 +1049,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "oldfieldtype",
"fieldtype": "Data",
"hidden": 1,
@@ -1016,7 +1082,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:25:57.882851",
"modified": "2016-08-23 11:59:07.036627",
"modified_by": "Administrator",
"module": "Core",
"name": "DocField",
@@ -1026,5 +1092,5 @@
"read_only": 0,
"read_only_onload": 0,
"sort_order": "ASC",
"track_seen": 0
}
"track_seen": 0
}

+ 54
- 1
frappe/core/doctype/docperm/docperm.json Переглянути файл

@@ -14,6 +14,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "role_and_level",
"fieldtype": "Section Break",
"hidden": 0,
@@ -38,6 +39,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "role",
"fieldtype": "Link",
"hidden": 0,
@@ -67,6 +69,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Filter records based on User Permissions defined for a user",
"fieldname": "apply_user_permissions",
"fieldtype": "Check",
@@ -92,6 +95,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Apply this rule if the User is the Owner",
"fieldname": "if_owner",
"fieldtype": "Check",
@@ -118,6 +122,34 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "is_custom",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Is Custom",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
@@ -141,6 +173,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
@@ -170,6 +203,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"description": "JSON list of DocTypes used to apply User Permissions. If empty, all linked DocTypes will be used to apply User Permissions.",
"fieldname": "user_permission_doctypes",
@@ -196,6 +230,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hidden": 0,
@@ -220,6 +255,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "read",
"fieldtype": "Check",
@@ -249,6 +285,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "write",
"fieldtype": "Check",
@@ -278,6 +315,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "create",
"fieldtype": "Check",
@@ -307,6 +345,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "delete",
"fieldtype": "Check",
@@ -332,6 +371,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_8",
"fieldtype": "Column Break",
"hidden": 0,
@@ -355,6 +395,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "submit",
"fieldtype": "Check",
"hidden": 0,
@@ -383,6 +424,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cancel",
"fieldtype": "Check",
"hidden": 0,
@@ -411,6 +453,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amend",
"fieldtype": "Check",
"hidden": 0,
@@ -439,6 +482,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "additional_permissions",
"fieldtype": "Section Break",
"hidden": 0,
@@ -463,6 +507,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "report",
"fieldtype": "Check",
@@ -490,6 +535,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "export",
"fieldtype": "Check",
@@ -515,6 +561,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "import",
"fieldtype": "Check",
"hidden": 0,
@@ -539,6 +586,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "This role update User Permissions for a user",
"fieldname": "set_user_permissions",
"fieldtype": "Check",
@@ -564,6 +612,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_19",
"fieldtype": "Column Break",
"hidden": 0,
@@ -587,6 +636,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "share",
"fieldtype": "Check",
@@ -613,6 +663,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "print",
"fieldtype": "Check",
@@ -638,6 +689,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "email",
"fieldtype": "Check",
@@ -670,7 +722,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:27:59.695670",
"modified": "2016-09-29 08:07:20.450064",
"modified_by": "Administrator",
"module": "Core",
"name": "DocPerm",
@@ -679,5 +731,6 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "ASC",
"track_seen": 0
}

+ 1
- 3
frappe/core/doctype/docshare/docshare.py Переглянути файл

@@ -19,9 +19,7 @@ class DocShare(Document):
self.get_doc().run_method("validate_share", self)

def cascade_permissions_downwards(self):
if self.share:
self.write = 1
if self.write:
if self.share or self.write:
self.read = 1

def get_doc(self):


+ 4
- 4
frappe/core/doctype/docshare/test_docshare.py Переглянути файл

@@ -34,7 +34,7 @@ class TestDocShare(unittest.TestCase):
self.assertTrue(self.event.has_permission())

def test_share_permission(self):
frappe.share.add("Event", self.event.name, self.user, share=1)
frappe.share.add("Event", self.event.name, self.user, write=1, share=1)

frappe.set_user(self.user)
self.assertTrue(self.event.has_permission("share"))
@@ -60,14 +60,14 @@ class TestDocShare(unittest.TestCase):
self.assertRaises(frappe.PermissionError, frappe.share.add, "Event", self.event.name, self.user)

frappe.set_user("Administrator")
frappe.share.add("Event", self.event.name, self.user, share=1)
frappe.share.add("Event", self.event.name, self.user, write=1, share=1)

# test not raises
frappe.set_user(self.user)
frappe.share.add("Event", self.event.name, "test1@example.com", share=1)
frappe.share.add("Event", self.event.name, "test1@example.com", write=1, share=1)

def test_remove_share(self):
frappe.share.add("Event", self.event.name, self.user, share=1)
frappe.share.add("Event", self.event.name, self.user, write=1, share=1)

frappe.set_user(self.user)
self.assertTrue(self.event.has_permission("share"))


+ 16
- 9
frappe/core/doctype/doctype/doctype.js Переглянути файл

@@ -11,18 +11,25 @@
// }
// })

cur_frm.cscript.refresh = function(doc, cdt, cdn) {
if(doc.__islocal && (user !== "Administrator" || !frappe.boot.developer_mode)) {
cur_frm.set_value("custom", 1);
cur_frm.toggle_enable("custom", 0);
}
frappe.ui.form.on('DocType', {
refresh: function(frm) {
if(frm.doc.__islocal && (user !== "Administrator" || !frappe.boot.developer_mode)) {
frm.set_value("custom", 1);
frm.toggle_enable("custom", 0);
}

if(!frappe.boot.developer_mode && !frm.doc.custom) {
// make the document read-only
frm.set_read_only();
}

if(!frappe.boot.developer_mode && !doc.custom) {
// make the document read-only
cur_frm.set_read_only();
if(!frm.doc.__islocal) {
frm.toggle_enable("engine", 0);
}
}
}
})

// for legacy... :)
cur_frm.cscript.validate = function(doc, cdt, cdn) {
doc.server_code_compiled = null;
}

+ 77
- 1
frappe/core/doctype/doctype/doctype.json Переглянути файл

@@ -11,11 +11,13 @@
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"engine": "InnoDB",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb0",
"fieldtype": "Section Break",
"hidden": 0,
@@ -41,6 +43,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "module",
"fieldtype": "Link",
"hidden": 0,
@@ -68,6 +71,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Child Tables are shown as a Grid in other DocTypes.",
"fieldname": "istable",
"fieldtype": "Check",
@@ -95,6 +99,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"depends_on": "istable",
"fieldname": "editable_grid",
@@ -122,6 +127,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Single Types have only one record no tables associated. Values are stored in tabSingles",
"fieldname": "issingle",
"fieldtype": "Check",
@@ -149,6 +155,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb01",
"fieldtype": "Column Break",
"hidden": 0,
@@ -172,6 +179,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "document_type",
"fieldtype": "Select",
"hidden": 0,
@@ -199,6 +207,36 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "InnoDB",
"depends_on": "eval:!doc.issingle",
"fieldname": "engine",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Database Engine",
"length": 0,
"no_copy": 0,
"options": "InnoDB\nMyISAM",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "icon",
"fieldtype": "Data",
"hidden": 0,
@@ -223,6 +261,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "custom",
"fieldtype": "Check",
"hidden": 0,
@@ -247,6 +286,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "beta",
"fieldtype": "Check",
"hidden": 0,
@@ -272,6 +312,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"depends_on": "eval: doc.image_field",
"fieldname": "image_view",
@@ -299,6 +340,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "app",
"fieldtype": "Data",
"hidden": 1,
@@ -323,6 +365,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fields_section_break",
"fieldtype": "Section Break",
"hidden": 0,
@@ -348,6 +391,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fields",
"fieldtype": "Table",
"hidden": 0,
@@ -375,6 +419,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb1",
"fieldtype": "Section Break",
"hidden": 0,
@@ -399,6 +444,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Naming Options:\n<ol><li><b>field:[fieldname]</b> - By Field</li><li><b>naming_series:</b> - By Naming Series (field called naming_series must be present</li><li><b>Prompt</b> - Prompt user for a name</li><li><b>[series]</b> - Series by prefix (separated by a dot); for example PRE.#####</li></ol>",
"fieldname": "autoname",
"fieldtype": "Data",
@@ -426,6 +472,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "name_case",
"fieldtype": "Select",
"hidden": 0,
@@ -453,6 +500,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
@@ -479,6 +527,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "column_break_15",
"fieldtype": "Column Break",
@@ -503,6 +552,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.istable",
"description": "Show this field as title",
"fieldname": "title_field",
@@ -529,6 +579,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.istable",
"fieldname": "search_fields",
"fieldtype": "Data",
@@ -556,6 +607,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Must be of type \"Attach Image\"",
"fieldname": "image_field",
"fieldtype": "Data",
@@ -582,6 +634,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "modified",
"depends_on": "eval:!doc.istable",
"description": "",
@@ -609,6 +662,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "DESC",
"depends_on": "eval:!doc.istable",
"fieldname": "sort_order",
@@ -636,6 +690,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.istable",
"description": "Comments and Communications will be associated with this linked document",
"fieldname": "timeline_field",
@@ -663,6 +718,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.istable",
"fieldname": "sb2",
"fieldtype": "Section Break",
@@ -688,6 +744,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "permissions",
"fieldtype": "Table",
@@ -716,6 +773,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.istable",
"fieldname": "sb3",
"fieldtype": "Section Break",
@@ -740,6 +798,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb30",
"fieldtype": "Column Break",
"hidden": 0,
@@ -764,6 +823,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "in_create",
"fieldtype": "Check",
"hidden": 0,
@@ -790,6 +850,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "read_only",
"fieldtype": "Check",
"hidden": 0,
@@ -816,6 +877,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_submittable",
"fieldtype": "Check",
"hidden": 0,
@@ -840,6 +902,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Allow Import via Data Import Tool",
"fieldname": "allow_import",
"fieldtype": "Check",
@@ -865,6 +928,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_rename",
"fieldtype": "Check",
"hidden": 0,
@@ -891,6 +955,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "in_dialog",
"fieldtype": "Check",
"hidden": 0,
@@ -917,6 +982,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "read_only_onload",
"fieldtype": "Check",
"hidden": 0,
@@ -943,6 +1009,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "max_attachments",
"fieldtype": "Int",
"hidden": 0,
@@ -969,6 +1036,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb31",
"fieldtype": "Column Break",
"hidden": 0,
@@ -993,6 +1061,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "hide_heading",
"fieldtype": "Check",
"hidden": 0,
@@ -1019,6 +1088,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "hide_toolbar",
"fieldtype": "Check",
"hidden": 0,
@@ -1045,6 +1115,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_copy",
"fieldtype": "Check",
"hidden": 0,
@@ -1071,6 +1142,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "track_seen",
"fieldtype": "Check",
"hidden": 0,
@@ -1096,6 +1168,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "quick_entry",
"fieldtype": "Check",
@@ -1122,6 +1195,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_print_format",
"fieldtype": "Data",
"hidden": 0,
@@ -1154,7 +1228,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-07-25 12:18:27.724194",
"modified": "2016-10-13 01:13:58.133080",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",
@@ -1170,6 +1244,7 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -1190,6 +1265,7 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,


+ 8
- 3
frappe/core/doctype/doctype/doctype.py Переглянути файл

@@ -46,8 +46,8 @@ class DocType(Document):
elif self.istable:
self.allow_import = 0

self.validate_series()
self.scrub_field_names()
self.validate_series()
self.validate_document_type()
validate_fields(self)

@@ -120,6 +120,12 @@ class DocType(Document):
if not autoname and self.get("fields", {"fieldname":"naming_series"}):
self.autoname = "naming_series:"

# validate field name if autoname field:fieldname is used
if autoname and autoname.startswith('field:'):
field = autoname.split(":")[1]
if not field or field not in [ df.fieldname for df in self.fields ]:
frappe.throw(_("Invalid fieldname '{0}' in autoname".format(field)))

if autoname and (not autoname.startswith('field:')) \
and (not autoname.startswith('eval:')) \
and (not autoname.lower() in ('prompt', 'hash')) \
@@ -593,8 +599,7 @@ def make_module_and_roles(doc, perm_fieldname="permissions"):

for role in list(set(roles)):
if not frappe.db.exists("Role", role):
r = frappe.get_doc({"doctype": "Role", "role_name": role})
r.role_name = role
r = frappe.get_doc(dict(doctype= "Role", role_name=role, desk_access=1))
r.flags.ignore_mandatory = r.flags.ignore_permissions = True
r.insert()
except frappe.DoesNotExistError, e:


frappe/docs/user/fr/guides/__init__.py → frappe/core/doctype/error_log/__init__.py Переглянути файл


+ 8
- 0
frappe/core/doctype/error_log/error_log.js Переглянути файл

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

frappe.ui.form.on('Error Log', {
refresh: function(frm) {

}
});

frappe/core/doctype/scheduler_log/scheduler_log.json → frappe/core/doctype/error_log/error_log.json Переглянути файл

@@ -2,7 +2,7 @@
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "SCHLOG.#####",
"autoname": "Error-.#####",
"beta": 0,
"creation": "2013-01-16 13:09:40",
"custom": 0,
@@ -10,11 +10,14 @@
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 0,
"engine": "MyISAM",
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "seen",
"fieldtype": "Check",
@@ -41,6 +44,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "method",
"fieldtype": "Data",
"hidden": 0,
@@ -48,7 +52,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Method",
"label": "Title",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -65,6 +69,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "error",
"fieldtype": "Code",
"hidden": 0,
@@ -97,10 +102,10 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-07-03 14:24:13.581374",
"modified": "2016-10-06 03:29:47.810715",
"modified_by": "Administrator",
"module": "Core",
"name": "Scheduler Log",
"name": "Error Log",
"owner": "Administrator",
"permissions": [
{
@@ -113,6 +118,7 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,

+ 21
- 0
frappe/core/doctype/error_log/error_log.py Переглянути файл

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

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

class ErrorLog(Document):
def onload(self):
if not self.seen:
self.db_set('seen', 1)
frappe.db.commit()

def set_old_logs_as_seen():
# set logs as seen
frappe.db.sql("""update `tabError Log` set seen=1
where seen=0 and datediff(curdate(), creation) > 7""")

# clear old logs
frappe.db.sql("""delete from `tabError Log` where datediff(curdate(), creation) > 30""")

frappe/core/doctype/scheduler_log/scheduler_log_list.js → frappe/core/doctype/error_log/error_log_list.js Переглянути файл

@@ -1,4 +1,4 @@
frappe.listview_settings['Scheduler Log'] = {
frappe.listview_settings['Error Log'] = {
add_fields: ["seen"],
get_indicator: function(doc) {
if(cint(doc.seen)) {

+ 12
- 0
frappe/core/doctype/error_log/test_error_log.py Переглянути файл

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

import frappe
import unittest

# test_records = frappe.get_test_records('Error Log')

class TestErrorLog(unittest.TestCase):
pass

frappe/public/html/error_object.html → frappe/core/doctype/error_snapshot/error_object.html Переглянути файл


frappe/public/html/error_snapshot.html → frappe/core/doctype/error_snapshot/error_snapshot.html Переглянути файл


+ 31
- 4
frappe/core/doctype/file/file.json Переглянути файл

@@ -3,15 +3,18 @@
"allow_import": 1,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2012-12-12 11:19:22",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 0,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "file_name",
"fieldtype": "Data",
"hidden": 0,
@@ -38,6 +41,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "is_private",
"fieldtype": "Check",
@@ -53,17 +57,18 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "preview",
"fieldtype": "Section Break",
"hidden": 0,
@@ -89,6 +94,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "preview_html",
"fieldtype": "HTML",
"hidden": 0,
@@ -114,6 +120,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
@@ -138,6 +145,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "is_home_folder",
"fieldtype": "Check",
@@ -164,6 +172,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_attachments_folder",
"fieldtype": "Check",
"hidden": 1,
@@ -189,6 +198,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "file_size",
"fieldtype": "Int",
"hidden": 0,
@@ -213,6 +223,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
@@ -237,6 +248,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "file_url",
"fieldtype": "Code",
@@ -262,6 +274,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "thumbnail_url",
"fieldtype": "Small Text",
"hidden": 0,
@@ -287,6 +300,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "folder",
"fieldtype": "Link",
"hidden": 1,
@@ -313,6 +327,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_folder",
"fieldtype": "Check",
"hidden": 0,
@@ -338,6 +353,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "section_break_8",
"fieldtype": "Section Break",
@@ -363,6 +379,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "attached_to_doctype",
"fieldtype": "Link",
"hidden": 0,
@@ -388,6 +405,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
@@ -412,6 +430,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "attached_to_name",
"fieldtype": "Data",
"hidden": 0,
@@ -436,6 +455,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "content_hash",
"fieldtype": "Data",
"hidden": 1,
@@ -460,6 +480,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
@@ -485,6 +506,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
@@ -510,6 +532,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "old_parent",
"fieldtype": "Data",
"hidden": 1,
@@ -536,6 +559,7 @@
"hide_toolbar": 0,
"icon": "icon-file",
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
@@ -543,7 +567,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2016-02-22 09:23:59.892258",
"modified": "2016-09-21 12:23:34.017457",
"modified_by": "Administrator",
"module": "Core",
"name": "File",
@@ -590,7 +614,10 @@
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"title_field": "file_name"
"sort_order": "ASC",
"title_field": "file_name",
"track_seen": 0
}

+ 18
- 1
frappe/core/doctype/file/file.py Переглянути файл

@@ -12,6 +12,7 @@ import frappe
import json
import urllib
import os
import shutil
import requests
import requests.exceptions
import StringIO
@@ -72,6 +73,23 @@ class File(NestedSet):

self.set_folder_size()

if frappe.db.exists('File', {'name': self.name, 'is_folder': 0}):
if not self.is_folder and (self.is_private != self.db_get('is_private')):
private_files = frappe.get_site_path('private', 'files')
public_files = frappe.get_site_path('public', 'files')

if not self.is_private:
shutil.move(os.path.join(private_files, self.file_name),
os.path.join(public_files, self.file_name))

self.file_url = "/files/{0}".format(self.file_name)

else:
shutil.move(os.path.join(public_files, self.file_name),
os.path.join(private_files, self.file_name))

self.file_url = "/private/files/{0}".format(self.file_name)

def set_folder_size(self):
"""Set folder size if folder"""
if self.is_folder and not self.is_new():
@@ -350,4 +368,3 @@ def check_file_permission(file_url):
return True

raise frappe.PermissionError


frappe/docs/user/fr/guides/app-development/__init__.py → frappe/core/doctype/language/__init__.py Переглянути файл


+ 8
- 0
frappe/core/doctype/language/language.js Переглянути файл

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

frappe.ui.form.on('Language', {
refresh: function(frm) {

}
});

+ 160
- 0
frappe/core/doctype/language/language.json Переглянути файл

@@ -0,0 +1,160 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:language_code",
"beta": 0,
"creation": "2014-08-22 16:12:17.249590",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "language_code",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Language Code",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "language_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Language Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "flag",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Flag",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "based_on",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Based On",
"length": 0,
"no_copy": 0,
"options": "Language",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-globe",
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-08-23 15:06:32.827148",
"modified_by": "Administrator",
"module": "Core",
"name": "Language",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "language_name",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "language_name",
"track_seen": 0
}

+ 41
- 0
frappe/core/doctype/language/language.py Переглянути файл

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, 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

class Language(Document):
pass

def export_languages_json():
'''Export list of all languages'''
languages = frappe.db.get_all('Language', fields=['name', 'language_name'])
languages = [{'name': d.language_name, 'code': d.name} for d in languages]

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

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

def sync_languages():
'''Sync frappe/geo/languages.json with Language'''
with open(frappe.get_app_path('frappe', 'geo', 'languages.json'), 'r') as f:
data = json.loads(f.read())

for l in data:
if not frappe.db.exists('Language', l['code']):
frappe.get_doc({
'doctype': 'Language',
'language_code': l['code'],
'language_name': l['name']
}).insert()

def update_language_names():
'''Update frappe/geo/languages.json names (for use via patch)'''
with open(frappe.get_app_path('frappe', 'geo', 'languages.json'), 'r') as f:
data = json.loads(f.read())

for l in data:
frappe.db.set_value('Language', l['code'], 'language_name', l['name'])

+ 12
- 0
frappe/core/doctype/language/test_language.py Переглянути файл

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

import frappe
import unittest

# test_records = frappe.get_test_records('Language')

class TestLanguage(unittest.TestCase):
pass

+ 7
- 8
frappe/core/doctype/page/page.py Переглянути файл

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

from __future__ import unicode_literals
import frappe
import os
from frappe.model.document import Document
from frappe.build import html_to_js_template
from frappe.model.utils import render_include
@@ -49,15 +50,10 @@ class Page(Document):
from frappe.core.doctype.doctype.doctype import make_module_and_roles
make_module_and_roles(self, "roles")

if not frappe.flags.in_import and getattr(conf,'developer_mode', 0) and self.standard=='Yes':
from frappe.modules.export_file import export_to_files
from frappe.modules import get_module_path, scrub
import os
export_to_files(record_list=[['Page', self.name]])

# write files
path = os.path.join(get_module_path(self.module), 'page', scrub(self.name), scrub(self.name))
from frappe.modules.utils import export_module_json
path = export_module_json(self, self.standard=='Yes', self.module)

if path:
# js
if not os.path.exists(path + '.js'):
with open(path + '.js', 'w') as f:
@@ -132,6 +128,9 @@ class Page(Document):
template = frappe.render_template(template, context)
self.script = html_to_js_template(fname, template) + self.script

# flag for not caching this page
self._dynamic_page = True

if frappe.lang != 'en':
from frappe.translate import get_lang_js
self.script += get_lang_js("page", self.name)


+ 36
- 0
frappe/core/doctype/report/report.py Переглянути файл

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

from __future__ import unicode_literals
import frappe
import json
from frappe import _
import frappe.desk.query_report
from frappe.utils import cint
from frappe.model.document import Document
from frappe.modules.export_file import export_to_files
@@ -48,6 +50,40 @@ class Report(Document):
make_boilerplate("controller.py", self, {"name": self.name})
make_boilerplate("controller.js", self, {"name": self.name})

def get_data(self, filters=None, limit=None, user=None):
'''Run the report'''
out = []

if self.report_type in ('Query Report', 'Script Report'):
# query and script reports
data = frappe.desk.query_report.run(self.name, filters=filters, user=user)
out.append([d.split(':')[0] for d in data.get('columns')])
out += data.get('result')
else:
# standard report
params = json.loads(self.json)
columns = params.get('columns')
filters = params.get('filters')

def _format(parts):
# sort by is saved as DocType.fieldname, covert it to sql
return '`tab{0}`.`{1}`'.format(*parts)

order_by = _format(params.get('sort_by').split('.')) + ' ' + params.get('sort_order')
if params.get('sort_by_next'):
order_by += ', ' + _format(params.get('sort_by_next').split('.')) + ' ' + params.get('sort_order_next')

result = frappe.get_list(self.ref_doctype, fields = [_format([c[1], c[0]]) for c in columns],
filters=filters, order_by = order_by, as_list=True, limit=limit, user=user)

meta = frappe.get_meta(self.ref_doctype)

out.append([meta.get_label(c[0]) for c in columns])
out = out + [list(d) for d in result]

return out


@Document.whitelist
def toggle_disable(self, disable):
self.db_set("disabled", cint(disable))

+ 22
- 2
frappe/core/doctype/report/test_report.py Переглянути файл

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

import frappe
from __future__ import unicode_literals
import frappe, json, os
import unittest

test_records = frappe.get_test_records('Report')

class TestReport(unittest.TestCase):
pass
def test_report_builder(self):
if frappe.db.exists('Report', 'User Activity Report'):
frappe.delete_doc('Report', 'User Activity Report')

with open(os.path.join(os.path.dirname(__file__), 'user_activity_report.json'), 'r') as f:
frappe.get_doc(json.loads(f.read())).insert()

report = frappe.get_doc('Report', 'User Activity Report')
data = report.get_data()
self.assertEquals(data[0][0], 'ID')
self.assertEquals(data[0][1], 'User Type')
self.assertTrue('Administrator' in [d[0] for d in data])

def test_query_report(self):
report = frappe.get_doc('Report', 'Permitted Documents For User')
data = report.get_data(filters={'user': 'Administrator', 'doctype': 'DocType'})
self.assertEquals(data[0][0], 'Name')
self.assertEquals(data[0][1], 'Module')
self.assertTrue('User' in [d[0] for d in data])


+ 17
- 0
frappe/core/doctype/report/user_activity_report.json Переглянути файл

@@ -0,0 +1,17 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"is_standard": "No",
"javascript": null,
"json": "{\"filters\":[],\"columns\":[[\"name\",\"User\"],[\"user_type\",\"User\"],[\"first_name\",\"User\"],[\"last_name\",\"User\"],[\"last_active\",\"User\"],[\"role\",\"UserRole\"]],\"sort_by\":\"User.modified\",\"sort_order\":\"desc\",\"sort_by_next\":null,\"sort_order_next\":\"desc\"}",
"modified": "2016-09-01 02:59:07.728890",
"module": "Core",
"name": "User Activity Report",
"query": null,
"ref_doctype": "User",
"report_name": "User Activity Report",
"report_type": "Report Builder"
}

+ 31
- 2
frappe/core/doctype/role/role.json Переглянути файл

@@ -14,13 +14,14 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "role_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_list_view": 0,
"label": "Role Name",
"length": 0,
"no_copy": 0,
@@ -40,6 +41,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "If disabled, this role will be removed from all users.",
"fieldname": "disabled",
"fieldtype": "Check",
@@ -61,6 +63,33 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "desk_access",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Desk Access",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@@ -74,7 +103,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-07-25 05:24:24.406260",
"modified": "2016-09-23 05:38:14.727541",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",


+ 16
- 8
frappe/core/doctype/role/test_records.json Переглянути файл

@@ -1,14 +1,22 @@
[
{
"doctype": "Role",
"role_name": "_Test Role"
},
"doctype": "Role",
"role_name": "_Test Role",
"desk_access": 1
},
{
"doctype": "Role",
"role_name": "_Test Role 2"
},
"doctype": "Role",
"role_name": "_Test Role 2",
"desk_access": 1
},
{
"doctype": "Role",
"role_name": "_Test Role 3"
"doctype": "Role",
"role_name": "_Test Role 3",
"desk_access": 1
},
{
"doctype": "Role",
"role_name": "_Test Role 4",
"desk_access": 0
}
]

+ 0
- 1
frappe/core/doctype/scheduler_log/README.md Переглянути файл

@@ -1 +0,0 @@
Log exceptions (errors) raised when executing the scheduler.

+ 0
- 3
frappe/core/doctype/scheduler_log/__init__.py Переглянути файл

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


+ 0
- 8
frappe/core/doctype/scheduler_log/scheduler_log.js Переглянути файл

@@ -1,8 +0,0 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

frappe.ui.form.on('Scheduler Log', {
refresh: function(frm) {

}
});

+ 0
- 19
frappe/core/doctype/scheduler_log/scheduler_log.py Переглянути файл

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

# For license information, please see license.txt

from __future__ import unicode_literals
import frappe

from frappe.model.document import Document

class SchedulerLog(Document):
def onload(self):
if not self.seen:
self.seen = 1
self.save()

def set_old_logs_as_seen():
frappe.db.sql("""update `tabScheduler Log` set seen=1
where seen=0 and datediff(curdate(), creation) > 7""")

+ 0
- 12
frappe/core/doctype/scheduler_log/test_scheduler_log.py Переглянути файл

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

import frappe
import unittest

# test_records = frappe.get_test_records('Scheduler Log')

class TestSchedulerLog(unittest.TestCase):
pass

+ 0
- 1
frappe/core/doctype/system_settings/system_settings.js Переглянути файл

@@ -1,5 +1,4 @@
frappe.ui.form.on("System Settings", "refresh", function(frm) {
frappe.setup_language_field(frm);
frappe.call({
method: "frappe.core.doctype.system_settings.system_settings.load",
callback: function(data) {


+ 680
- 601
frappe/core/doctype/system_settings/system_settings.json
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 38
- 5
frappe/core/doctype/translation/test_translation.py Переглянути файл

@@ -9,6 +9,9 @@ import unittest
from frappe import _

class TestTranslation(unittest.TestCase):
def setUp(self):
frappe.db.sql('delete from tabTranslation')

def tearDown(self):
frappe.local.lang = 'en'
frappe.local.lang_full_dict=None
@@ -19,21 +22,51 @@ class TestTranslation(unittest.TestCase):
frappe.local.lang = key
frappe.local.lang_full_dict=None
translation = create_translation(key, val)
self.assertEquals(_(translation.source_name), val[1])
self.assertEquals(_(val[0]), val[1])

frappe.delete_doc('Translation', translation.name)
frappe.local.lang_full_dict=None
self.assertEquals(_(translation.source_name), val[0])

self.assertEquals(_(val[0]), val[0])

def test_parent_language(self):
data = [
['es', ['Test Data', 'datos de prueba']],
['es', ['Test Spanish', 'prueba de español']],
['es-MX', ['Test Data', 'pruebas de datos']]
]

for key, val in data:
create_translation(key, val)

frappe.local.lang = 'es'

frappe.local.lang_full_dict=None
self.assertTrue(_(data[0][0]), data[0][1])

frappe.local.lang_full_dict=None
self.assertTrue(_(data[1][0]), data[1][1])

frappe.local.lang = 'es-MX'

# different translation for es-MX
frappe.local.lang_full_dict=None
self.assertTrue(_(data[2][0]), data[2][1])

# from spanish (general)
frappe.local.lang_full_dict=None
self.assertTrue(_(data[1][0]), data[1][1])

def get_translation_data():
html_source_data = """ <font color="#848484" face="arial, tahoma, verdana, sans-serif">
<span style="font-size: 11px; line-height: 16.9px;">Test Data</span></font> """
html_translated_data = """ <font color="#848484" face="arial, tahoma, verdana, sans-serif">
html_source_data = """<font color="#848484" face="arial, tahoma, verdana, sans-serif">
<span style="font-size: 11px; line-height: 16.9px;">Test Data</span></font>"""
html_translated_data = """<font color="#848484" face="arial, tahoma, verdana, sans-serif">
<span style="font-size: 11px; line-height: 16.9px;"> testituloksia </span></font>"""

return {'hr': ['Test data', 'Testdaten'],
'ms': ['Test Data','ujian Data'],
'et': ['Test Data', 'testandmed'],
'es': ['Test Data', 'datos de prueba'],
'en': ['Quotation', 'Tax Invoice'],
'fi': [html_source_data, html_translated_data]}



+ 0
- 3
frappe/core/doctype/translation/translation.js Переглянути файл

@@ -3,9 +3,6 @@


frappe.ui.form.on('Translation', {
before_load: function(frm) {
frappe.setup_language_field(frm);
},
language: function(frm) {
frm.events.update_language_code(frm);
},


+ 15
- 55
frappe/core/doctype/translation/translation.json Переглянути файл

@@ -3,70 +3,22 @@
"allow_import": 1,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2016-02-17 12:21:16.175465",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "",
"fieldname": "language",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Language",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "en",
"fieldname": "language_code",
"fieldtype": "Data",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -75,6 +27,7 @@
"label": "Language Code",
"length": 0,
"no_copy": 0,
"options": "Language",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -90,6 +43,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hidden": 0,
@@ -114,6 +68,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "If your data is in HTML, please copy paste the exact HTML code with the tags.",
"fieldname": "source_name",
"fieldtype": "Code",
@@ -140,6 +95,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"hidden": 0,
@@ -164,13 +120,14 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "target_name",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_list_view": 1,
"label": "Translated",
"length": 0,
"no_copy": 0,
@@ -189,13 +146,14 @@
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-03-04 15:42:07.020950",
"modified": "2016-08-24 03:48:55.525143",
"modified_by": "Administrator",
"module": "Core",
"name": "Translation",
@@ -223,9 +181,11 @@
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "language_code"
"title_field": "source_name",
"track_seen": 0
}

+ 3
- 2
frappe/core/doctype/translation/translation.py Переглянути файл

@@ -5,10 +5,11 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.translate import clear_cache

class Translation(Document):
def on_update(self):
frappe.cache().hdel('lang_user_translations', self.language_code)
clear_cache()

def on_trash(self):
frappe.cache().hdel('lang_user_translations', self.language_code)
clear_cache()

+ 32
- 4
frappe/core/doctype/user/test_user.py Переглянути файл

@@ -8,14 +8,36 @@ import requests
from frappe.model.delete_doc import delete_doc
from frappe.utils.data import today, add_to_date
from frappe import _dict
from frappe.limits import SiteExpiredError, update_limits, clear_limit
from frappe.limits import update_limits, clear_limit
from frappe.utils import get_url
from frappe.installer import update_site_config
from frappe.core.doctype.user.user import MaxUsersReachedError

test_records = frappe.get_test_records('User')

class TestUser(unittest.TestCase):
def test_user_type(self):
new_user = frappe.get_doc(dict(doctype='User', email='test-for-type@example.com',
first_name='Tester')).insert()
self.assertEquals(new_user.user_type, 'Website User')

# role with desk access
new_user.add_roles('_Test Role 2')
new_user.save()
self.assertEquals(new_user.user_type, 'System User')

# clear role
new_user.user_roles = []
new_user.save()
self.assertEquals(new_user.user_type, 'Website User')

# role without desk access
new_user.add_roles('_Test Role 4')
new_user.save()
self.assertEquals(new_user.user_type, 'Website User')

frappe.delete_doc('User', new_user.name)


def test_delete(self):
frappe.get_doc("User", "test@example.com").add_roles("_Test Role 2")
self.assertRaises(frappe.LinkExistsError, delete_doc, "Role", "_Test Role 2")
@@ -122,6 +144,7 @@ class TestUser(unittest.TestCase):
clear_limit('users')

# def test_deny_multiple_sessions(self):
# from frappe.installer import update_site_config
# clear_limit('users')
#
# # allow one session
@@ -156,13 +179,18 @@ class TestUser(unittest.TestCase):
# test_request(conn1)

def test_site_expiry(self):
user = frappe.get_doc('User', 'test@example.com')
user.enabled = 1
user.new_password = 'testpassword'
user.save()

update_limits({'expiry': add_to_date(today(), days=-1)})
frappe.local.conf = _dict(frappe.get_site_config())

frappe.db.commit()

res = requests.post(get_url(), params={'cmd': 'login', 'usr': 'test@example.com', 'pwd': 'testpassword',
'device': 'desktop'})
res = requests.post(get_url(), params={'cmd': 'login', 'usr':
'test@example.com', 'pwd': 'testpassword', 'device': 'desktop'})

# While site is expired status code returned is 417 Failed Expectation
self.assertEqual(res.status_code, 417)


+ 0
- 2
frappe/core/doctype/user/user.js Переглянути файл

@@ -1,7 +1,5 @@
frappe.ui.form.on('User', {
before_load: function(frm) {
frappe.setup_language_field(frm);

var update_tz_select = function(user_language) {
frm.set_df_property("time_zone", "options", [""].concat(frappe.all_timezones));
}


+ 1472
- 1472
frappe/core/doctype/user/user.json
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 82
- 14
frappe/core/doctype/user/user.py Переглянути файл

@@ -38,17 +38,22 @@ class User(Document):
[m.module_name for m in frappe.db.get_all('Desktop Icon',
fields=['module_name'], filters={'standard': 1}, order_by="module_name")])

def before_insert(self):
self.flags.in_insert = True

def after_insert(self):
self.set_default_roles()

def validate(self):
self.check_demo()

self.in_insert = self.get("__islocal")

# clear new password
self.__new_password = self.new_password
self.new_password = ""

if self.name not in STANDARD_USERS:
self.validate_email_type(self.email)
self.validate_email_type(self.name)
self.add_system_manager_role()
self.set_system_user()
self.set_full_name()
@@ -70,6 +75,10 @@ class User(Document):
frappe.clear_cache(user=self.name)
self.send_password_notification(self.__new_password)

def has_website_permission(self, ptype, verbose=False):
"""Returns true if current user is the session user"""
return self.name == frappe.session.user

def check_demo(self):
if frappe.session.user == 'demo@erpnext.com':
frappe.throw('Cannot change user details in demo. Please signup for a new account at https://erpnext.com', title='Not Allowed')
@@ -89,6 +98,33 @@ class User(Document):
if not cint(self.enabled) and getattr(frappe.local, "login_manager", None):
frappe.local.login_manager.logout(user=self.name)

def set_default_roles(self):
"""Set a default role if specified by rules (`default_role`) in hooks or Portal Settings

Hooks for default roles can be set as:

default_roles = [
{'role': 'Customer', 'doctype':'Contact', 'email_field': 'email_id',
'filters': {'ifnull(customer, "")': ('!=', '')}}
]

"""
role_found = False
for rule in frappe.get_hooks('default_roles'):
filters = {rule.get('email_field'): self.email}
if rule.get('filters'):
filters.update(rule.get('filters'))

match = frappe.get_all(rule.get('doctype'), filters=filters, limit=1)
if match:
role_found = True
self.add_roles(rule.get('role'))

if not role_found:
default_role = frappe.db.get_single_value('Portal Settings', 'default_role')
if default_role:
self.add_roles(default_role)

def add_system_manager_role(self):
# if adding system manager, do nothing
if not cint(self.enabled) or ("System Manager" in [user_role.role for user_role in
@@ -116,7 +152,7 @@ class User(Document):
])

def email_new_password(self, new_password=None):
if new_password and not self.in_insert:
if new_password and not self.flags.in_insert:
_update_password(self.name, new_password)

if self.send_password_update_notification:
@@ -124,14 +160,26 @@ class User(Document):
frappe.msgprint(_("New password emailed"))

def set_system_user(self):
if self.user_roles or self.name == 'Administrator':
'''Set as System User if any of the given roles has desk_access'''
if self.has_desk_access() or self.name == 'Administrator':
self.user_type = 'System User'
else:
self.user_type = 'Website User'

def has_desk_access(self):
'''Return true if any of the set roles has desk access'''
if not self.user_roles:
return False

return len(frappe.db.sql("""select name
from `tabRole` where desk_access=1
and name in ({0}) limit 1""".format(', '.join(['%s'] * len(self.user_roles))),
[d.role for d in self.user_roles]))


def share_with_self(self):
if self.user_type=="System User":
frappe.share.add(self.doctype, self.name, self.name, share=1,
frappe.share.add(self.doctype, self.name, self.name, write=1, share=1,
flags={"ignore_share_permission": True})
else:
frappe.share.remove(self.doctype, self.name, self.name,
@@ -147,7 +195,7 @@ class User(Document):

def send_password_notification(self, new_password):
try:
if self.in_insert:
if self.flags.in_insert:
if self.name not in STANDARD_USERS:
if new_password:
# new password given, no email required
@@ -155,12 +203,15 @@ class User(Document):

if not self.flags.no_welcome_mail and self.send_welcome_email:
self.send_welcome_mail_to_user()
msgprint(_("Welcome email sent"))
self.flags.email_sent = 1
if frappe.session.user != 'Guest':
msgprint(_("Welcome email sent"))
return
else:
self.email_new_password(new_password)

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


@@ -458,6 +509,13 @@ def update_password(new_password, key=None, old_password=None):

user_doc, redirect_url = reset_user_data(user)

# get redirect url from cache
redirect_to = frappe.cache().hget('redirect_after_login', user)
if redirect_to:
redirect_url = redirect_to
frappe.cache().hdel('redirect_after_login', user)


frappe.local.login_manager.login_as(user)

if user_doc.user_type == "System User":
@@ -517,7 +575,7 @@ def verify_password(password):
frappe.local.login_manager.check_password(frappe.session.user, password)

@frappe.whitelist(allow_guest=True)
def sign_up(email, full_name):
def sign_up(email, full_name, redirect_to):
user = frappe.db.get("User", {"email": email})
if user:
if user.disabled:
@@ -526,9 +584,12 @@ def sign_up(email, full_name):
return _("Already Registered")
else:
if frappe.db.sql("""select count(*) from tabUser where
HOUR(TIMEDIFF(CURRENT_TIMESTAMP, TIMESTAMP(modified)))=1""")[0][0] > 200:
frappe.msgprint("Login is closed for sometime, please check back again in an hour.")
raise Exception, "Too Many New Users"
HOUR(TIMEDIFF(CURRENT_TIMESTAMP, TIMESTAMP(modified)))=1""")[0][0] > 300:

frappe.respond_as_web_page(_('Temperorily Disabled'),
_('Too many users signed up recently, so the registration is disabled. Please try back in an hour'),
http_status_code=429)

from frappe.utils import random_string
user = frappe.get_doc({
"doctype":"User",
@@ -540,7 +601,14 @@ def sign_up(email, full_name):
})
user.flags.ignore_permissions = True
user.insert()
return _("Registration Details Emailed.")

if redirect_to:
frappe.cache().hset('redirect_after_login', user.name, redirect_to)

if user.flags.email_sent:
return _("Please check your email for verification")
else:
return _("Please ask your administrator to verify your sign-up")

@frappe.whitelist(allow_guest=True)
def reset_password(user):
@@ -630,10 +698,10 @@ def has_permission(doc, user):
# dont allow non Administrator user to view / edit Administrator user
return False

def notifify_admin_access_to_system_manager(login_manager=None):
def notify_admin_access_to_system_manager(login_manager=None):
if (login_manager
and login_manager.user == "Administrator"
and frappe.local.conf.notifify_admin_access_to_system_manager):
and frappe.local.conf.notify_admin_access_to_system_manager):

message = """<p>
{dear_system_manager} <br><br>


+ 1
- 1
frappe/core/notifications.py Переглянути файл

@@ -7,7 +7,7 @@ import frappe
def get_notification_config():
return {
"for_doctype": {
"Scheduler Log": {"seen": 0},
"Error Log": {"seen": 0},
"Communication": {"status": "Open", "communication_type": "Communication"},
"ToDo": "frappe.core.notifications.get_things_todo",
"Event": "frappe.core.notifications.get_todays_events",


frappe/docs/user/fr/guides/basics/__init__.py → frappe/core/page/background_jobs/__init__.py Переглянути файл


+ 39
- 0
frappe/core/page/background_jobs/background_jobs.html Переглянути файл

@@ -0,0 +1,39 @@
<div class="list-jobs">
{% if jobs.length %}
<table class="table table-bordered" style="table-layout: fixed;">
<thead>
<tr>
<th style="width: 20%">Queue / Worker</th>
<th>Job</th>
<th style="width: 15%">Created</th>
</tr>
</thead>
<tbody>
{% for j in jobs %}
<tr>
<td><span class="indicator {{ j.color }}" title="{{ j.status }}">{{ j.queue.split(".").slice(-1)[0] }}</span></td>
<td style="overflow: auto;">
<div>
{{ frappe.utils.encode_tags(j.job_name) }}
</div>
{% if j.exc_info %}
<div>
<pre>{{ frappe.utils.encode_tags(j.exc_info) }}</pre>
</div>
{% endif %}
</td>
<td class="small">{{ j.creation }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p>
<span class="indicator green" style="margin-right: 20px;">Started</span>
<span class="indicator orange" style="margin-right: 20px;">Queued</span>
<span class="indicator red">Failed</span>
</p>
{% else %}
<p class="text-muted">No pending or current jobs for this site</p>
{% endif %}
<p class="text-muted" style="margin-top: 30px;">Last refreshed {{ frappe.datetime.now_datetime() }}</p>
</div>

+ 39
- 0
frappe/core/page/background_jobs/background_jobs.js Переглянути файл

@@ -0,0 +1,39 @@
frappe.pages['background_jobs'].on_page_load = function(wrapper) {
var page = frappe.ui.make_app_page({
parent: wrapper,
title: 'Background Jobs',
single_column: true
});

$(frappe.render_template('background_jobs_outer')).appendTo(page.body);
page.content = $(page.body).find('.table-area');

frappe.pages.background_jobs.page = page;
}

frappe.pages['background_jobs'].on_page_show = function(wrapper) {
frappe.pages.background_jobs.refresh_jobs();
}

frappe.pages.background_jobs.refresh_jobs = function() {
var page = frappe.pages.background_jobs.page;

// don't call if already waiting for a response
if(page.called) return;
page.called = true;
frappe.call({
method: 'frappe.core.page.background_jobs.background_jobs.get_info',
args: {
show_failed: page.body.find('.show-failed').prop('checked') ? 1 : 0
},
callback: function(r) {
page.called = false;
page.body.find('.list-jobs').remove();
$(frappe.render_template('background_jobs', {jobs:r.message || []})).appendTo(page.content);

if(frappe.get_route()[0]==='background_jobs') {
frappe.background_jobs_timeout = setTimeout(frappe.pages.background_jobs.refresh_jobs, 2000);
}
}
});
}

+ 22
- 0
frappe/core/page/background_jobs/background_jobs.json Переглянути файл

@@ -0,0 +1,22 @@
{
"content": null,
"creation": "2016-08-18 16:44:14.322642",
"docstatus": 0,
"doctype": "Page",
"idx": 0,
"modified": "2016-08-18 16:48:11.577611",
"modified_by": "Administrator",
"module": "Core",
"name": "background_jobs",
"owner": "Administrator",
"page_name": "background_jobs",
"roles": [
{
"role": "System Manager"
}
],
"script": null,
"standard": "Yes",
"style": null,
"title": "Background Jobs"
}

+ 51
- 0
frappe/core/page/background_jobs/background_jobs.py Переглянути файл

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

from __future__ import unicode_literals
import frappe

from rq import Queue, Worker
from frappe.utils.background_jobs import get_redis_conn
from frappe.utils import format_datetime, cint

colors = {
'queued': 'orange',
'failed': 'red',
'started': 'green'
}

@frappe.whitelist()
def get_info(show_failed=False):
conn = get_redis_conn()
queues = Queue.all(conn)
workers = Worker.all(conn)
jobs = []

def add_job(j, name):
if j.kwargs.get('site')==frappe.local.site or True:
jobs.append({
'job_name': j.kwargs.get('kwargs', {}).get('playbook_method') \
or str(j.kwargs.get('job_name')),
'status': j.status, 'queue': name,
'creation': format_datetime(j.created_at),
'color': colors[j.status]
})
if j.exc_info:
jobs[-1]['exc_info'] = j.exc_info

for w in workers:
j = w.get_current_job()
if j:
add_job(j, w.name)

for q in queues:
if q.name != 'failed':
for j in q.get_jobs(): add_job(j, q.name)

if cint(show_failed):
for q in queues:
if q.name == 'failed':
for j in q.get_jobs()[:10]: add_job(j, q.name)


return jobs

+ 12
- 0
frappe/core/page/background_jobs/background_jobs_outer.html Переглянути файл

@@ -0,0 +1,12 @@
<div style="padding: 20px;">
<p>
<div class="checkbox">
<label>
<input type="checkbox" class="show-failed"> {{ __("Show failed jobs") }}
</label>
</div>
</p>
<div class="table-area">

</div>
</div>

+ 6
- 0
frappe/core/page/data_import_tool/data_import_main.html Переглянути файл

@@ -77,6 +77,12 @@
{%= __("Ignore encoding errors.") %}
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="no_email" checked>
{%= __("Do not send Emails.") %}
</label>
</div>
<p>
<button class="btn btn-sm btn-primary btn-import">Import</button>
</p>


+ 9
- 4
frappe/core/page/data_import_tool/data_import_tool.js Переглянути файл

@@ -42,11 +42,15 @@ frappe.DataImportTool = Class.extend({
if(me.doctype) {

// render select columns
var doctype_list = [frappe.get_doc('DocType', me.doctype)];
var parent_doctype = frappe.get_doc('DocType', me.doctype);
parent_doctype["reqd"] = true;
var doctype_list = [parent_doctype];
frappe.meta.get_table_fields(me.doctype).forEach(function(df) {
doctype_list.push(frappe.get_doc('DocType', df.options));
var d = frappe.get_doc('DocType', df.options);
d["reqd"]=df.reqd;
doctype_list.push(d);
});

$(frappe.render_template("data_import_tool_columns", {doctype_list: doctype_list}))
.appendTo(me.select_columns.empty());
}
@@ -103,7 +107,8 @@ frappe.DataImportTool = Class.extend({
return {
submit_after_import: me.page.main.find('[name="submit_after_import"]').prop("checked"),
ignore_encoding_errors: me.page.main.find('[name="ignore_encoding_errors"]').prop("checked"),
overwrite: !me.page.main.find('[name="always_insert"]').prop("checked")
overwrite: !me.page.main.find('[name="always_insert"]').prop("checked"),
no_email: me.page.main.find('[name="no_email"]').prop("checked")
}
},
args: {


+ 2
- 2
frappe/core/page/data_import_tool/data_import_tool.py Переглянути файл

@@ -26,12 +26,12 @@ def get_doctype_options():
doctype = frappe.form_dict['doctype']
return [doctype] + [d.options for d in frappe.get_meta(doctype).get_table_fields()]

def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False, pre_process=None):
def import_file_by_path(path, ignore_links=False, overwrite=False, submit=False, pre_process=None, no_email=True):
from frappe.utils.csvutils import read_csv_content
from frappe.core.page.data_import_tool.importer import upload
print "Importing " + path
with open(path, "r") as infile:
upload(rows = read_csv_content(infile.read()), ignore_links=ignore_links, overwrite=overwrite,
upload(rows = read_csv_content(infile.read()), ignore_links=ignore_links, no_email=no_email, overwrite=overwrite,
submit_after_import=submit, pre_process=pre_process)

def export_csv(doctype, path):


+ 1
- 0
frappe/core/page/data_import_tool/data_import_tool_columns.html Переглянути файл

@@ -4,6 +4,7 @@
<div class="row">
{% for f in doctype.fields %}
{% if (frappe.model.no_value_type.indexOf(f.fieldtype)===-1) %}
{% doctype.reqd||(f.reqd=0);%}
<div class="col-sm-4">
<div class="checkbox" style="margin: 5px 0px;">
<label>


+ 8
- 3
frappe/core/page/data_import_tool/importer.py Переглянути файл

@@ -16,19 +16,24 @@ from frappe.utils import cint, cstr, flt, getdate, get_datetime
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys

@frappe.whitelist()
def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=None,
def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, no_email=True, overwrite=None,
ignore_links=False, pre_process=None, via_console=False):
"""upload data"""
frappe.flags.mute_emails = True
frappe.flags.in_import = True

# extra input params
params = json.loads(frappe.form_dict.get("params") or '{}')

if params.get("submit_after_import"):
submit_after_import = True
if params.get("ignore_encoding_errors"):
ignore_encoding_errors = True
if not params.get("no_email"):
no_email = False

frappe.flags.mute_emails = no_email

from frappe.utils.csvutils import read_csv_content_from_uploaded_file



+ 2
- 2
frappe/core/page/permission_manager/permission_manager.py Переглянути файл

@@ -18,8 +18,8 @@ def get_roles_and_doctypes():
istable=0 and
name not in ('DocType') and
exists(select * from `tabDocField` where parent=dt.name)""")],
"roles": [d[0] for d in frappe.db.sql("""select name from tabRole where name not in
('Administrator')""")]
"roles": [d[0] for d in frappe.db.sql("""select name from tabRole where
name != 'Administrator' and disabled=0""")]
}

@frappe.whitelist()


+ 1
- 1
frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js Переглянути файл

@@ -20,7 +20,7 @@ frappe.query_reports["Permitted Documents For User"] = {
return {
"query": "frappe.core.report.permitted_documents_for_user.permitted_documents_for_user.query_doctypes",
"filters": {
"user": frappe.query_report.filters_by_name.user.get_value()
"user": frappe.query_report_filters_by_name.user.get_value()
}
}
}


frappe/docs/user/fr/guides/data/__init__.py → frappe/core/web_form/__init__.py Переглянути файл


frappe/docs/user/fr/guides/deployment/__init__.py → frappe/core/web_form/edit_profile/__init__.py Переглянути файл


+ 3
- 0
frappe/core/web_form/edit_profile/edit_profile.js Переглянути файл

@@ -0,0 +1,3 @@
frappe.ready(function() {
// bind events here
})

+ 86
- 0
frappe/core/web_form/edit_profile/edit_profile.json Переглянути файл

@@ -0,0 +1,86 @@
{
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 1,
"allow_multiple": 0,
"breadcrumbs": "[{\"title\": _(\"My Account\"), \"route\": \"me\"}]",
"creation": "2016-09-19 05:16:59.242754",
"doc_type": "User",
"docstatus": 0,
"doctype": "Web Form",
"idx": 0,
"is_standard": 1,
"login_required": 1,
"modified": "2016-09-24 04:31:41.920694",
"modified_by": "Administrator",
"module": "Core",
"name": "edit-profile",
"owner": "Administrator",
"published": 1,
"route": "update-profile",
"show_sidebar": 1,
"sidebar_items": [],
"success_message": "Profile updated successfully.",
"success_url": "/me",
"title": "Update Profile",
"web_form_fields": [
{
"fieldname": "first_name",
"fieldtype": "Data",
"hidden": 0,
"label": "First Name",
"read_only": 0,
"reqd": 1
},
{
"fieldname": "middle_name",
"fieldtype": "Data",
"hidden": 0,
"label": "Middle Name (Optional)",
"read_only": 0,
"reqd": 0
},
{
"fieldname": "last_name",
"fieldtype": "Data",
"hidden": 0,
"label": "Last Name",
"read_only": 0,
"reqd": 0
},
{
"description": "",
"fieldname": "user_image",
"fieldtype": "Attach",
"hidden": 0,
"label": "User Image",
"read_only": 0,
"reqd": 0
},
{
"fieldtype": "Section Break",
"hidden": 0,
"label": "More Information",
"read_only": 0,
"reqd": 0
},
{
"fieldname": "phone",
"fieldtype": "Data",
"hidden": 0,
"label": "Phone",
"read_only": 0,
"reqd": 0
},
{
"description": "",
"fieldname": "language",
"fieldtype": "Link",
"hidden": 0,
"label": "Language",
"options": "Language",
"read_only": 0,
"reqd": 0
}
]
}

+ 7
- 0
frappe/core/web_form/edit_profile/edit_profile.py Переглянути файл

@@ -0,0 +1,7 @@
from __future__ import unicode_literals

import frappe

def get_context(context):
# do your magic here
pass

+ 7
- 2
frappe/custom/doctype/custom_script/custom_script.json Переглянути файл

@@ -9,12 +9,14 @@
"description": "Adds a custom script (client or server) to a DocType",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "dt",
"fieldtype": "Link",
"hidden": 0,
@@ -42,6 +44,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Client",
"fieldname": "script_type",
"fieldtype": "Select",
@@ -70,6 +73,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "script",
"fieldtype": "Code",
"hidden": 0,
@@ -97,6 +101,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sample",
"fieldtype": "HTML",
"hidden": 0,
@@ -130,7 +135,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-07-25 05:24:24.245725",
"modified": "2016-09-24 05:47:53.900418",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Script",
@@ -177,7 +182,7 @@
"write": 1
}
],
"quick_entry": 1,
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "ASC",


+ 26
- 0
frappe/custom/doctype/customize_form/customize_form.js Переглянути файл

@@ -4,11 +4,15 @@
frappe.provide("frappe.customize_form");

frappe.ui.form.on("Customize Form", {
setup: function(frm) {
frm.get_docfield("fields").allow_bulk_edit = 1;
},
onload: function(frm) {
frappe.customize_form.add_fields_help(frm);

frm.set_query("doc_type", function() {
return {
translate_values: false,
filters: [
['DocType', 'issingle', '=', 0],
['DocType', 'custom', '=', 0],
@@ -71,6 +75,28 @@ frappe.ui.form.on("Customize Form", {
frm.add_custom_button(__('Reset to defaults'), function() {
frappe.customize_form.confirm(__('Remove all customizations?'), frm);
}, "icon-eraser", "btn-default");

if(frappe.boot.developer_mode) {
frm.add_custom_button(__('Export Customizations'), function() {
frappe.prompt(
[
{fieldtype:'Link', fieldname:'module', options:'Module Def',
label: __('Module to Export')},
{fieldtype:'Check', fieldname:'sync_on_migrate',
label: __('Sync on Migrate'), 'default': 1},
],
function(data) {
frappe.call({
method: 'frappe.modules.utils.export_customizations',
args: {
doctype: frm.doc.doc_type,
module: data.module,
sync_on_migrate: data.sync_on_migrate
}
});
});
});
}
}

// sort order select


+ 46
- 1
frappe/custom/doctype/customize_form/customize_form.json Переглянути файл

@@ -15,6 +15,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "doc_type",
"fieldtype": "Link",
"hidden": 0,
@@ -40,6 +41,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "doc_type",
"fieldname": "properties",
"fieldtype": "Section Break",
@@ -65,6 +67,33 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "label",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Change Label (via Custom Translation)",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_print_format",
"fieldtype": "Link",
"hidden": 0,
@@ -90,6 +119,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "max_attachments",
"fieldtype": "Int",
@@ -115,6 +145,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_copy",
"fieldtype": "Check",
"hidden": 0,
@@ -139,6 +170,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "istable",
"fieldtype": "Check",
"hidden": 0,
@@ -164,6 +196,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "istable",
"fieldname": "editable_grid",
"fieldtype": "Check",
@@ -190,6 +223,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "quick_entry",
"fieldtype": "Check",
@@ -216,6 +250,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.image_field",
"fieldname": "image_view",
"fieldtype": "Check",
@@ -242,6 +277,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
@@ -266,6 +302,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Use this fieldname to generate title",
"fieldname": "title_field",
"fieldtype": "Data",
@@ -292,6 +329,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Must be of type \"Attach Image\"",
"fieldname": "image_field",
"fieldtype": "Data",
@@ -318,6 +356,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Fields separated by comma (,) will be included in the \"Search By\" list of Search dialog box",
"fieldname": "search_fields",
"fieldtype": "Data",
@@ -343,6 +382,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "doc_type",
"fieldname": "section_break_8",
"fieldtype": "Section Break",
@@ -368,6 +408,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sort_field",
"fieldtype": "Select",
"hidden": 0,
@@ -392,6 +433,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_10",
"fieldtype": "Column Break",
"hidden": 0,
@@ -416,6 +458,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sort_order",
"fieldtype": "Select",
"hidden": 0,
@@ -441,6 +484,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "doc_type",
"description": "Customize Label, Print Hide, Default etc.",
"fieldname": "fields_section_break",
@@ -467,6 +511,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fields",
"fieldtype": "Table",
"hidden": 0,
@@ -500,7 +545,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2016-07-08 04:40:57.045612",
"modified": "2016-09-16 02:36:09.171273",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form",


+ 59
- 20
frappe/custom/doctype/customize_form/customize_form.py Переглянути файл

@@ -7,6 +7,7 @@ from __future__ import unicode_literals
Thus providing a better UI from user perspective
"""
import frappe
import frappe.translate
from frappe import _
from frappe.utils import cint
from frappe.model.document import Document
@@ -54,7 +55,8 @@ docfield_properties = {
'default': 'Text',
'precision': 'Select',
'read_only': 'Check',
'length': 'Int'
'length': 'Int',
'columns': 'Int'
}

allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
@@ -83,8 +85,36 @@ class CustomizeForm(Document):
new_d[property] = d.get(property)
self.append("fields", new_d)

# load custom translation
translation = self.get_name_translation()
self.label = translation.target_name if translation else ''

# NOTE doc is sent to clientside by run_method

def get_name_translation(self):
'''Get translation object if exists of current doctype name in the default language'''
return frappe.get_value('Translation',
{'source_name': self.doc_type, 'language_code': frappe.local.lang or 'en'},
['name', 'target_name'], as_dict=True)

def set_name_translation(self):
'''Create, update custom translation for this doctype'''
current = self.get_name_translation()
if current:
if self.label and current!=self.label:
frappe.db.set_value('Translation', current.name, 'target_name', self.label)
frappe.translate.clear_cache()
else:
# clear translation
frappe.delete_doc('Translation', current.name)

else:
if self.label:
frappe.get_doc(dict(doctype='Translation',
source_name=self.doc_type,
target_name=self.label,
language_code=frappe.local.lang or 'en')).insert()

def clear_existing_doc(self):
doc_type = self.doc_type

@@ -101,10 +131,18 @@ class CustomizeForm(Document):
if not self.doc_type:
return

self.flags.update_db = False

self.set_property_setters()
self.update_custom_fields()
self.set_name_translation()
validate_fields_for_doctype(self.doc_type)

if self.flags.update_db:
from frappe.model.db_schema import updatedb
updatedb(self.doc_type)


frappe.msgprint(_("{0} updated").format(_(self.doc_type)))
frappe.clear_cache(doctype=self.doc_type)
self.fetch_to_customize()
@@ -117,7 +155,6 @@ class CustomizeForm(Document):
self.make_property_setter(property=property, value=self.get(property),
property_type=doctype_properties[property])

update_db = False
for df in self.get("fields"):
if df.get("__islocal"):
continue
@@ -144,10 +181,10 @@ class CustomizeForm(Document):

elif property == "precision" and cint(df.get("precision")) > 6 \
and cint(df.get("precision")) > cint(meta_df[0].get("precision")):
update_db = True
self.flags.update_db = True

elif property == "unique":
update_db = True
self.flags.update_db = True

elif (property == "read_only" and cint(df.get("read_only"))==0
and frappe.db.get_value("DocField", {"parent": self.doc_type, "fieldname": df.fieldname}, "read_only")==1):
@@ -158,16 +195,14 @@ class CustomizeForm(Document):
self.make_property_setter(property=property, value=df.get(property),
property_type=docfield_properties[property], fieldname=df.fieldname)

if update_db:
from frappe.model.db_schema import updatedb
updatedb(self.doc_type)

def update_custom_fields(self):
for i, df in enumerate(self.get("fields")):
if df.get("__islocal"):
self.add_custom_field(df, i)
else:
self.update_in_custom_field(df, i)
if df.get("is_custom_field"):
if not frappe.db.exists('Custom Field', {'dt': self.doc_type, 'fieldname': df.fieldname}):
self.add_custom_field(df, i)
self.flags.update_db = True
else:
self.update_in_custom_field(df, i)

self.delete_custom_fields()

@@ -179,7 +214,8 @@ class CustomizeForm(Document):
for property in docfield_properties:
d.set(property, df.get(property))

d.insert_after = self.fields[i-1].fieldname
if i!=0:
d.insert_after = self.fields[i-1].fieldname
d.idx = i

d.insert()
@@ -189,6 +225,7 @@ class CustomizeForm(Document):
meta = frappe.get_meta(self.doc_type)
meta_df = meta.get("fields", {"fieldname": df.fieldname})
if not (meta_df and meta_df[0].get("is_custom_field")):
# not a custom field
return

custom_field = frappe.get_doc("Custom Field", meta_df[0].name)
@@ -202,15 +239,17 @@ class CustomizeForm(Document):
changed = True

# check and update `insert_after` property
insert_after = self.fields[i-1].fieldname
if custom_field.insert_after != insert_after:
custom_field.insert_after = insert_after
custom_field.idx = i
changed = True
if i!=0:
insert_after = self.fields[i-1].fieldname
if custom_field.insert_after != insert_after:
custom_field.insert_after = insert_after
custom_field.idx = i
changed = True

if changed:
custom_field.flags.ignore_validate = True
custom_field.save()
custom_field.db_update()
self.flags.update_db = True
#custom_field.save()

def delete_custom_fields(self):
meta = frappe.get_meta(self.doc_type)


+ 1
- 1
frappe/custom/doctype/customize_form/test_customize_form.py Переглянути файл

@@ -109,7 +109,7 @@ class TestCustomizeForm(unittest.TestCase):
d.append("fields", {
"label": "Test Add Custom Field Via Customize Form",
"fieldtype": "Data",
"__islocal": 1
"is_custom_field": 1
})
d.run_method("save_customization")
self.assertEquals(frappe.db.get_value("Custom Field",


+ 62
- 1
frappe/custom/doctype/customize_form_field/customize_form_field.json Переглянути файл

@@ -15,6 +15,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "label_and_type",
"fieldtype": "Section Break",
"hidden": 0,
@@ -40,6 +41,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "label",
"fieldtype": "Data",
"hidden": 0,
@@ -66,6 +68,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
@@ -94,6 +97,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fieldname",
"fieldtype": "Data",
"hidden": 0,
@@ -120,6 +124,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reqd",
"fieldtype": "Check",
"hidden": 0,
@@ -148,6 +153,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "unique",
"fieldtype": "Check",
"hidden": 0,
@@ -173,6 +179,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "in_list_view",
"fieldtype": "Check",
"hidden": 0,
@@ -197,6 +204,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
@@ -221,6 +229,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
@@ -249,6 +258,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
@@ -275,6 +285,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
"fieldname": "options",
"fieldtype": "Text",
@@ -302,6 +313,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "permissions",
"fieldtype": "Section Break",
"hidden": 0,
@@ -327,6 +339,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age&gt;18",
"fieldname": "depends_on",
"fieldtype": "Code",
@@ -354,6 +367,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
@@ -381,6 +395,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "hidden",
"fieldtype": "Check",
"hidden": 0,
@@ -409,6 +424,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "read_only",
"fieldtype": "Check",
"hidden": 0,
@@ -434,6 +450,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
@@ -460,6 +477,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
@@ -486,6 +504,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_14",
"fieldtype": "Column Break",
"hidden": 0,
@@ -510,6 +529,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"hidden": 0,
@@ -534,6 +554,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"hidden": 0,
@@ -560,6 +581,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "report_hide",
"fieldtype": "Check",
"hidden": 0,
@@ -586,6 +608,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "display",
"fieldtype": "Section Break",
"hidden": 0,
@@ -611,6 +634,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default",
"fieldtype": "Text",
"hidden": 0,
@@ -637,6 +661,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "in_filter",
"fieldtype": "Check",
"hidden": 0,
@@ -665,6 +690,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_21",
"fieldtype": "Column Break",
"hidden": 0,
@@ -689,6 +715,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Text",
"hidden": 0,
@@ -717,6 +744,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "print_hide",
"fieldtype": "Check",
"hidden": 0,
@@ -743,6 +771,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
@@ -769,6 +798,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Print Width of the field, if the field is a column in a table",
"fieldname": "print_width",
"fieldtype": "Data",
@@ -796,6 +826,35 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:cur_frm.doc.istable",
"description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Columns",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "width",
"fieldtype": "Data",
"hidden": 0,
@@ -824,6 +883,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_custom_field",
"fieldtype": "Check",
"hidden": 1,
@@ -856,7 +916,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:27:58.928043",
"modified": "2016-08-23 12:03:07.126339",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form Field",
@@ -865,5 +925,6 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_order": "ASC",
"track_seen": 0
}

+ 3
- 0
frappe/data/Framework.sql Переглянути файл

@@ -42,6 +42,7 @@ CREATE TABLE `tabDocField` (
`ignore_user_permissions` int(1) NOT NULL DEFAULT 0,
`width` varchar(255) DEFAULT NULL,
`print_width` varchar(255) DEFAULT NULL,
`columns` int(11) NOT NULL DEFAULT 0,
`default` text,
`description` text,
`in_filter` int(1) NOT NULL DEFAULT 0,
@@ -76,6 +77,7 @@ CREATE TABLE `tabDocPerm` (
`permlevel` int(11) DEFAULT '0',
`role` varchar(255) DEFAULT NULL,
`match` varchar(255) DEFAULT NULL,
`is_custom` int(1) NOT NULL DEFAULT 0,
`read` int(1) NOT NULL DEFAULT 1,
`write` int(1) NOT NULL DEFAULT 1,
`create` int(1) NOT NULL DEFAULT 1,
@@ -144,6 +146,7 @@ CREATE TABLE `tabDocType` (
`tag_fields` varchar(255) DEFAULT NULL,
`subject` varchar(255) DEFAULT NULL,
`_last_update` varchar(32) DEFAULT NULL,
`engine` varchar(20) DEFAULT 'InnoDB',
`default_print_format` varchar(255) DEFAULT NULL,
`is_submittable` int(1) NOT NULL DEFAULT 0,
`_user_tags` varchar(255) DEFAULT NULL,


+ 0
- 57
frappe/data/languages.txt Переглянути файл

@@ -1,57 +0,0 @@
ar العربية
bg bǎlgarski
bn বাংলা
bo ལྷ་སའི་སྐད་
bs bosanski
ca català
cs česky
da dansk
da-DK dansk (Danmark)
de deutsch
el ελληνικά
en english
es español
es-PE Español (Perú)
et eesti
fa پارسی
fi suomalainen
fr français
gu ગુજરાતી
he עברית
hi हिंदी
hr hrvatski
hu magyar
id Indonesia
is íslenska
it italiano
ja 日本語
km ភាសាខ្មែរ
kn ಕನ್ನಡ
ko 한국의
lv latviešu valoda
mk македонски
ml മലയാളം
mr मराठी
ms Melayu
my မြန်မာ
nl nederlands
no norsk
pl polski
pt português
pt-BR português brasileiro
ro român
ru русский
sk slovenčina (Slovak)
sl slovenščina (Slovene)
sv svenska
sq shqiptar
sr српски
ta தமிழ்
te తెలుగు
th ไทย
tr Türk
uk українська
ur اردو
vi việt
zh-cn 簡體中文
zh-tw 正體中文

+ 2
- 1
frappe/defaults.py Переглянути файл

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

def add_default(key, value, parent, parenttype=None):
d = frappe.get_doc({


+ 5
- 1
frappe/desk/desk_page.py Переглянути файл

@@ -13,7 +13,11 @@ def get(name):
page = frappe.get_doc('Page', name)
if page.is_permitted():
page.load_assets()
return page
docs = frappe._dict(page.as_dict())
if getattr(page, '_dynamic_page', None):
docs['_dynamic_page'] = 1

return docs
else:
frappe.response['403'] = 1
raise frappe.PermissionError, 'No read permission for Page %s' % \


+ 12
- 7
frappe/desk/doctype/bulk_update/bulk_update.json Переглянути файл

@@ -14,6 +14,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "document_type",
"fieldtype": "Link",
"hidden": 0,
@@ -31,7 +32,7 @@
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -40,6 +41,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "field",
"fieldtype": "Select",
"hidden": 0,
@@ -56,7 +58,7 @@
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -65,6 +67,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "update_value",
"fieldtype": "Small Text",
"hidden": 0,
@@ -81,15 +84,16 @@
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"description": "SQL Conditions. Example: status=\"Open\"",
"fieldname": "condition",
"fieldtype": "Small Text",
@@ -114,8 +118,9 @@
},
{
"allow_on_submit": 0,
"bold": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"default": "500",
"description": "Max 500 records at a time",
"fieldname": "limit",
@@ -150,7 +155,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2016-07-15 06:24:42.575613",
"modified": "2016-09-23 05:10:19.377701",
"modified_by": "Administrator",
"module": "Desk",
"name": "Bulk Update",
@@ -178,7 +183,7 @@
"write": 1
}
],
"quick_entry": 0,
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",


+ 6
- 1
frappe/desk/doctype/bulk_update/bulk_update.py Переглянути файл

@@ -29,7 +29,12 @@ def update(doctype, field, value, condition='', limit=500):
for i, d in enumerate(items):
doc = frappe.get_doc(doctype, d)
doc.set(field, value)
doc.save()

try:
doc.save()
except Exception, e:
frappe.msgprint(_("Validation failed for {0}").format(frappe.bold(doc.name)))
raise e

frappe.publish_progress(float(i)*100/n,
title = _('Updating Records'), doctype='Bulk Update', docname='Bulk Update')


+ 5
- 0
frappe/desk/doctype/desktop_icon/desktop_icon.py Переглянути файл

@@ -5,6 +5,7 @@
from __future__ import unicode_literals

import frappe
from frappe import _
import json
import random
from frappe.model.document import Document
@@ -81,6 +82,10 @@ def get_desktop_icons(user=None):
# sort by idx
user_icons.sort(lambda a, b: 1 if a.idx > b.idx else -1)

# translate
for d in user_icons:
if d.label: d.label = _(d.label)

frappe.cache().hset('desktop_icons', user, user_icons)

return user_icons


+ 4
- 4
frappe/desk/doctype/event/event.py Переглянути файл

@@ -4,7 +4,8 @@
from __future__ import unicode_literals
import frappe

from frappe.utils import getdate, cint, add_months, date_diff, add_days, nowdate, get_datetime_str, cstr
from frappe.utils import (getdate, cint, add_months, date_diff, add_days,
nowdate, get_datetime_str, cstr, get_datetime)
from frappe.model.document import Document
from frappe.utils.user import get_enabled_system_users

@@ -12,15 +13,14 @@ weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday",

class Event(Document):
def validate(self):
if self.starts_on and self.ends_on and self.starts_on > self.ends_on:
if self.starts_on and self.ends_on and get_datetime(self.starts_on) > get_datetime(self.ends_on):
frappe.msgprint(frappe._("Event end must be after start"), raise_exception=True)

if self.starts_on == self.ends_on:
# this scenario doesn't make sense i.e. it starts and ends at the same second!
self.ends_on = None

if self.starts_on and self.ends_on and int(date_diff(self.ends_on.split(" ")[0], self.starts_on.split(" ")[0])) > 0 \
and self.repeat_on == "Every Day":
if getdate(self.starts_on) == getdate(self.ends_on) and self.repeat_on == "Every Day":
frappe.msgprint(frappe._("Every day events should finish on the same day."), raise_exception=True)

def get_permission_query_conditions(user):


+ 3
- 0
frappe/desk/doctype/event/event_list.js Переглянути файл

@@ -0,0 +1,3 @@
frappe.listview_settings['Event'] = {
add_fields: ["starts_on", "ends_on"]
}

+ 3
- 5
frappe/desk/doctype/note/note.js Переглянути файл

@@ -26,11 +26,9 @@ frappe.ui.form.on("Note", {
// hide all other fields
$.each(frm.fields_dict, function(fieldname, field) {

if(fieldname !== "content"
&& !in_list(["Section Break", "Column Break"], field.df.fieldtype)) {
frm.set_df_property(fieldname, "hidden", editable ? 0: 1);
}

if(fieldname !== "content") {
frm.set_df_property(fieldname, "hidden", editable ? 0: 1);
}
})

// no label, description for content either


+ 113
- 3
frappe/desk/doctype/note/note.json Переглянути файл

@@ -5,7 +5,7 @@
"beta": 0,
"creation": "2013-05-24 13:41:00",
"custom": 0,
"description": "Note is a free page where users can share documents / notes",
"description": "",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
@@ -15,6 +15,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 0,
@@ -39,7 +40,8 @@
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"description": "Everyone can read",
"columns": 0,
"description": "",
"fieldname": "public",
"fieldtype": "Check",
"hidden": 0,
@@ -64,6 +66,61 @@
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"depends_on": "public",
"fieldname": "notify_on_login",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Notify users with a popup when they log in",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.notify_on_login && doc.public",
"fieldname": "expire_notification_on",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Expire Notification On",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"description": "Help: To link to another record in the system, use \"#Form/Note/[Note Name]\" as the Link URL. (don't use \"http://\")",
"fieldname": "content",
"fieldtype": "Text Editor",
@@ -84,6 +141,59 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "seen_by_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Seen By",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "seen_by",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Seen By Table",
"length": 0,
"no_copy": 0,
"options": "Note Seen By",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
@@ -97,7 +207,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-07-25 05:24:24.137761",
"modified": "2016-08-30 00:28:57.094889",
"modified_by": "Administrator",
"module": "Desk",
"name": "Note",


+ 13
- 0
frappe/desk/doctype/note/note.py Переглянути файл

@@ -11,10 +11,23 @@ class Note(Document):
import re
self.name = re.sub("[%'\"#*?`]", "", self.title.strip())

def validate(self):
if self.notify_on_login and not self.expire_notification_on:

# expire this notification in a week (default)
self.expire_notification_on = frappe.utils.add_days(self.creation, 7)

def before_print(self):
self.print_heading = self.name
self.sub_heading = ""

@frappe.whitelist()
def mark_as_seen(note):
note = frappe.get_doc('Note', note)
if frappe.session.user not in [d.user for d in note.seen_by]:
note.append('seen_by', {'user': frappe.session.user})
note.save()

def get_permission_query_conditions(user):
if not user: user = frappe.session.user



frappe/docs/user/fr/guides/integration/__init__.py → frappe/desk/doctype/note_seen_by/__init__.py Переглянути файл


+ 64
- 0
frappe/desk/doctype/note_seen_by/note_seen_by.json Переглянути файл

@@ -0,0 +1,64 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-08-29 05:29:16.726172",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-08-29 06:02:41.531341",
"modified_by": "Administrator",
"module": "Desk",
"name": "Note Seen By",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
}

+ 10
- 0
frappe/desk/doctype/note_seen_by/note_seen_by.py Переглянути файл

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

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

class NoteSeenBy(Document):
pass

+ 3
- 2
frappe/desk/form/meta.py Переглянути файл

@@ -79,7 +79,8 @@ class FormMeta(Meta):
def _add_code(self, path, fieldname):
js = get_js(path)
if js:
self.set(fieldname, (self.get(fieldname) or "") + "\n\n" + js)
self.set(fieldname, (self.get(fieldname) or "")
+ "\n\n/* Adding {0} */\n\n".format(path) + js)

def add_html_templates(self, path):
if self.custom:
@@ -103,7 +104,7 @@ class FormMeta(Meta):
custom = frappe.db.get_value("Custom Script", {"dt": self.name,
"script_type": "Client"}, "script") or ""

self.set("__js", (self.get('__js') or '') + "\n\n" + custom)
self.set("__js", (self.get('__js') or '') + "\n\n/* Appending Custom Script */\n\n" + custom)

def add_search_fields(self):
"""add search fields found in the doctypes indicated by link fields' options"""


+ 4
- 0
frappe/desk/page/backups/backups.js Переглянути файл

@@ -5,6 +5,10 @@ frappe.pages['backups'].on_page_load = function(wrapper) {
single_column: true
});

page.add_inner_button(__("Set Number of Backups"), function() {
frappe.set_route('Form', 'System Settings');
});

frappe.breadcrumbs.add("Setup");

$(frappe.render_template("backups")).appendTo(page.body.addClass("no-border"));


+ 34
- 1
frappe/desk/page/backups/backups.py Переглянути файл

@@ -1,5 +1,6 @@
import os
from frappe.utils import get_site_path
import frappe
from frappe.utils import get_site_path, cint
from frappe.utils.data import convert_utc_to_user_timezone
import datetime

@@ -17,9 +18,41 @@ def get_context(context):

path = get_site_path('private', 'backups')
files = [x for x in os.listdir(path) if os.path.isfile(os.path.join(path, x))]
backup_limit = get_scheduled_backup_limit()

if len(files) > backup_limit:
cleanup_old_backups(path, files, backup_limit)

files = [('/backups/' + _file,
get_time(os.path.join(path, _file)),
get_size(os.path.join(path, _file))) for _file in files]
files.sort(key=lambda x: x[1], reverse=True)

return {"files": files}

def get_scheduled_backup_limit():
backup_limit = frappe.db.get_singles_value('System Settings', 'backup_limit')
return cint(backup_limit)

def cleanup_old_backups(site_path, files, limit):
backup_paths = []
for f in files:
_path = os.path.abspath(os.path.join(site_path, f))
backup_paths.append(_path)

backup_paths = sorted(backup_paths, key=os.path.getctime)
files_to_delete = len(backup_paths) - limit

for idx in range(0, files_to_delete):
f = os.path.basename(backup_paths[idx])
files.remove(f)

os.remove(backup_paths[idx])

def delete_downloadable_backups():
path = get_site_path('private', 'backups')
files = [x for x in os.listdir(path) if os.path.isfile(os.path.join(path, x))]
backup_limit = get_scheduled_backup_limit()

if len(files) > backup_limit:
cleanup_old_backups(path, files, backup_limit)

Деякі файли не було показано, через те що забагато файлів було змінено

Завантаження…
Відмінити
Зберегти