Bläddra i källkod

Merge branch 'v6-wip' into develop

version-14
Anand Doshi 10 år sedan
förälder
incheckning
bd4c3aec06
100 ändrade filer med 8385 tillägg och 297 borttagningar
  1. +4
    -2
      .travis.yml
  2. +2
    -0
      frappe/__init__.py
  3. +1
    -1
      frappe/__version__.py
  4. +2
    -2
      frappe/api.py
  5. +26
    -1
      frappe/app.py
  6. +219
    -0
      frappe/async.py
  7. +1
    -1
      frappe/boot.py
  8. +2
    -1
      frappe/build.py
  9. +11
    -3
      frappe/celery_app.py
  10. +1
    -0
      frappe/change_log/current/assign_to_myself.md
  11. +40
    -1
      frappe/commands.py
  12. +0
    -0
      frappe/core/doctype/async_task/__init__.py
  13. +142
    -0
      frappe/core/doctype/async_task/async_task.json
  14. +10
    -0
      frappe/core/doctype/async_task/async_task.py
  15. +12
    -0
      frappe/core/doctype/async_task/test_async_task.py
  16. +2
    -2
      frappe/core/doctype/communication/communication.json
  17. +4
    -3
      frappe/core/doctype/doctype/doctype.json
  18. +1
    -1
      frappe/core/doctype/doctype/doctype.py
  19. +2
    -2
      frappe/core/doctype/version/version.json
  20. +2
    -1
      frappe/core/page/data_import_tool/importer.py
  21. +50
    -23
      frappe/core/page/desktop/desktop.js
  22. +25
    -0
      frappe/core/page/desktop/desktop_list_view.html
  23. +2
    -2
      frappe/core/page/desktop/desktop_module_icon.html
  24. +39
    -0
      frappe/desk/doctype/note/note.js
  25. +2
    -2
      frappe/desk/doctype/note/note.json
  26. +20
    -0
      frappe/desk/doctype/todo/todo_list.js
  27. +12
    -3
      frappe/desk/form/meta.py
  28. +4
    -8
      frappe/desk/moduleview.py
  29. +4
    -28
      frappe/desk/page/messages/messages.js
  30. +3
    -2
      frappe/desk/page/messages/messages_row.html
  31. +2
    -2
      frappe/email/doctype/email_account/email_account.json
  32. +2
    -7
      frappe/email/doctype/standard_reply/standard_reply.json
  33. +1
    -0
      frappe/geo/country_info.json
  34. +2
    -2
      frappe/geo/doctype/country/country.json
  35. +20
    -1
      frappe/handler.py
  36. +5
    -1
      frappe/hooks.py
  37. +62
    -3
      frappe/installer.py
  38. +4
    -4
      frappe/model/base_document.py
  39. +1
    -1
      frappe/model/db_query.py
  40. +4
    -0
      frappe/patches.txt
  41. +0
    -0
      frappe/patches/v6_0/__init__.py
  42. +7
    -0
      frappe/patches/v6_0/document_type_rename.py
  43. +7
    -0
      frappe/patches/v6_0/fix_ghana_currency.py
  44. +6
    -0
      frappe/patches/v6_0/make_task_log_folder.py
  45. +4
    -1
      frappe/public/build.json
  46. +4
    -0
      frappe/public/css/common.css
  47. +4
    -0
      frappe/public/css/desk.css
  48. +12
    -0
      frappe/public/css/desktop.css
  49. +4
    -0
      frappe/public/css/form.css
  50. +36
    -10
      frappe/public/css/mobile.css
  51. +4
    -0
      frappe/public/css/website.css
  52. +0
    -4
      frappe/public/js/frappe/desk.js
  53. +7
    -1
      frappe/public/js/frappe/form/control.js
  54. +1
    -1
      frappe/public/js/frappe/form/dashboard.js
  55. +11
    -0
      frappe/public/js/frappe/form/footer/assign_to.js
  56. +6
    -1
      frappe/public/js/frappe/form/footer/timeline.js
  57. +9
    -13
      frappe/public/js/frappe/form/layout.js
  58. +38
    -22
      frappe/public/js/frappe/form/save.js
  59. +3
    -0
      frappe/public/js/frappe/form/toolbar.js
  60. +5
    -0
      frappe/public/js/frappe/list/doclistview.js
  61. +2
    -4
      frappe/public/js/frappe/list/list_item_row.html
  62. +1
    -1
      frappe/public/js/frappe/list/list_item_row_head.html
  63. +3
    -1
      frappe/public/js/frappe/list/list_sidebar.html
  64. +13
    -1
      frappe/public/js/frappe/list/list_sidebar.js
  65. +1
    -1
      frappe/public/js/frappe/misc/user.js
  66. +12
    -1
      frappe/public/js/frappe/request.js
  67. +114
    -0
      frappe/public/js/frappe/socket.js
  68. +12
    -10
      frappe/public/js/frappe/ui/dialog.js
  69. +8
    -0
      frappe/public/js/frappe/ui/messages.js
  70. +15
    -0
      frappe/public/js/frappe/ui/page.js
  71. +2
    -2
      frappe/public/js/frappe/ui/toolbar/about.js
  72. +0
    -1
      frappe/public/js/frappe/ui/toolbar/toolbar.js
  73. +5
    -0
      frappe/public/js/frappe/upload.js
  74. +18
    -4
      frappe/public/js/frappe/views/communication.js
  75. +1
    -1
      frappe/public/js/frappe/views/module/module_section.html
  76. +12
    -4
      frappe/public/js/frappe/views/reports/reportview.js
  77. +7
    -2
      frappe/public/js/legacy/form.js
  78. +7000
    -0
      frappe/public/js/lib/socket.io.min.js
  79. +5
    -0
      frappe/public/less/common.less
  80. +18
    -0
      frappe/public/less/desktop.less
  81. +5
    -0
      frappe/public/less/form.less
  82. +43
    -12
      frappe/public/less/mobile.less
  83. +2
    -0
      frappe/public/less/variables.less
  84. +4
    -1
      frappe/sessions.py
  85. +58
    -3
      frappe/tasks.py
  86. +17
    -0
      frappe/tests/test_async.py
  87. +5
    -5
      frappe/tests/test_data_import.py
  88. +1
    -0
      frappe/utils/__init__.py
  89. +1
    -2
      frappe/utils/backups.py
  90. +4
    -2
      frappe/utils/install.py
  91. +7
    -4
      frappe/utils/response.py
  92. +2
    -2
      frappe/website/doctype/blog_category/blog_category.json
  93. +2
    -2
      frappe/website/doctype/blogger/blogger.json
  94. +2
    -2
      frappe/website/doctype/web_form/web_form.json
  95. +2
    -2
      frappe/website/doctype/web_page/web_page.json
  96. +2
    -2
      frappe/website/doctype/website_slideshow/website_slideshow.json
  97. +59
    -59
      frappe/workflow/doctype/workflow_document_state/workflow_document_state.json
  98. +2
    -2
      frappe/workflow/doctype/workflow_state/workflow_state.json
  99. +1
    -0
      requirements.txt
  100. +1
    -1
      setup.py

+ 4
- 2
.travis.yml Visa fil

@@ -15,7 +15,6 @@ install:
- wget https://raw.githubusercontent.com/frappe/bench/master/install_scripts/setup_frappe.sh
- sudo bash setup_frappe.sh --skip-setup-bench --mysql-root-password travis
- sudo pip install --upgrade pip
- sudo service redis-server start
- rm $TRAVIS_BUILD_DIR/.git/shallow
- cd ~/ && bench init frappe-bench --frappe-path $TRAVIS_BUILD_DIR
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
@@ -23,10 +22,13 @@ install:
script:
- cd ~/frappe-bench
- bench use test_site
- bench setup redis-cache
- bench setup redis-async-broker
- bench setup procfile --with-celery-broker
- bench reinstall
- bench build
- bench build-website
- bench serve &
- bench start &
- sleep 10
- bench --verbose run-tests --driver Firefox



+ 2
- 0
frappe/__init__.py Visa fil

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

from werkzeug.local import Local, release_local
from functools import wraps
import os, importlib, inspect, logging, json

# public
@@ -14,6 +15,7 @@ from frappe.__version__ import __version__
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template


local = Local()

class _dict(dict):


+ 1
- 1
frappe/__version__.py Visa fil

@@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = "5.4.2"
__version__ = "6.0.0"

+ 2
- 2
frappe/api.py Visa fil

@@ -58,13 +58,13 @@ def handle():
if frappe.local.request.method=="GET":
if not doc.has_permission("read"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
doc.run_method(method, **frappe.local.form_dict)
frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})

if frappe.local.request.method=="POST":
if not doc.has_permission("write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)

doc.run_method(method, **frappe.local.form_dict)
frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})
frappe.db.commit()

else:


+ 26
- 1
frappe/app.py Visa fil

@@ -12,6 +12,8 @@ from werkzeug.local import LocalManager
from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.contrib.profiler import ProfilerMiddleware
from werkzeug.wsgi import SharedDataMiddleware
from werkzeug.serving import run_with_reloader


import mimetypes
import frappe
@@ -20,9 +22,10 @@ import frappe.auth
import frappe.api
import frappe.utils.response
import frappe.website.render
from frappe.utils import get_site_name
from frappe.utils import get_site_name, get_site_path
from frappe.middlewares import StaticDataMiddleware


local_manager = LocalManager([frappe.local])

_site = None
@@ -30,6 +33,21 @@ _sites_path = os.environ.get("SITES_PATH", ".")

logger = frappe.get_logger()

class RequestContext(object):

def __init__(self, environ):
self.request = Request(environ)

def __enter__(self):
frappe.local.request = self.request
init_site(self.request)
make_form_dict(self.request)
frappe.local.http_request = frappe.auth.HTTPRequest()

def __exit__(self, type, value, traceback):
frappe.destroy()


@Request.application
def application(request):
frappe.local.request = request
@@ -135,6 +153,8 @@ def make_form_dict(request):
frappe.local.form_dict.pop("_")

application = local_manager.make_middleware(application)
application.debug = True


def serve(port=8000, profile=False, site=None, sites_path='.'):
global application, _site, _sites_path
@@ -155,5 +175,10 @@ def serve(port=8000, profile=False, site=None, sites_path='.'):
b'/files': os.path.abspath(sites_path).encode("utf-8")
})

application.debug = True
application.config = {
'SERVER_NAME': 'localhost:8000'
}

run_simple('0.0.0.0', int(port), application, use_reloader=True,
use_debugger=True, use_evalex=True, threaded=True)

+ 219
- 0
frappe/async.py Visa fil

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

from __future__ import unicode_literals


import frappe
import os
import time
import redis
from functools import wraps
from frappe.utils import get_site_path
import json
from frappe import conf

END_LINE = '<!-- frappe: end-file -->'
TASK_LOG_MAX_AGE = 86400 # 1 day in seconds
redis_server = None


def handler(f):
cmd = f.__module__ + '.' + f.__name__

def _run(args, set_in_response=True):
from frappe.tasks import run_async_task
from frappe.handler import execute_cmd
if frappe.conf.no_async:
return execute_cmd(cmd, async=True)
args = frappe._dict(args)
task = run_async_task.delay(frappe.local.site,
(frappe.session and frappe.session.user) or 'Administrator', cmd, args)
if set_in_response:
frappe.local.response['task_id'] = task.id
return task.id

@wraps(f)
def queue(*args, **kwargs):
from frappe.tasks import run_async_task
from frappe.handler import execute_cmd
if frappe.conf.no_async:
return execute_cmd(cmd, async=True)
task = run_async_task.delay(frappe.local.site,
(frappe.session and frappe.session.user) or 'Administrator', cmd,
frappe.local.form_dict)
frappe.local.response['task_id'] = task.id
return {
"status": "queued",
"task_id": task.id
}
queue.async = True
queue.queue = f
queue.run = _run
frappe.whitelisted.append(f)
frappe.whitelisted.append(queue)
return queue


def run_async_task(method, args, reference_doctype=None, reference_name=None, set_in_response=True):
if frappe.local.request and frappe.local.request.method == "GET":
frappe.throw("Cannot run task in a GET request")
task_id = method.run(args, set_in_response=set_in_response)
task = frappe.new_doc("Async Task")
task.celery_task_id = task_id
task.status = "Queued"
task.reference_doctype = reference_doctype
task.reference_name = reference_name
task.save()
return task_id


@frappe.whitelist()
def get_pending_tasks_for_doc(doctype, docname):
return frappe.db.sql_list("select name from `tabAsync Task` where status in ('Queued', 'Running') and reference_doctype='%s' and reference_name='%s'" % (doctype, docname))


@handler
def ping():
from time import sleep
sleep(6)
return "pong"


@frappe.whitelist()
def get_task_status(task_id):
from frappe.celery_app import get_celery
c = get_celery()
a = c.AsyncResult(task_id)
frappe.local.response['response'] = a.result
return {
"state": a.state,
"progress": 0
}


def set_task_status(task_id, status, response=None):
frappe.db.set_value("Async Task", task_id, "status", status)
if not response:
response = {}
response.update({
"status": status,
"task_id": task_id
})
emit_via_redis("task_status_change", response, room="task:" + task_id)


def remove_old_task_logs():
logs_path = get_site_path('task-logs')

def full_path(_file):
return os.path.join(logs_path, _file)

files_to_remove = [full_path(_file) for _file in os.listdir(logs_path)]
files_to_remove = [_file for _file in files_to_remove if is_file_old(_file) and os.path.isfile(_file)]
for _file in files_to_remove:
os.remove(_file)


def is_file_old(file_path):
return ((time.time() - os.stat(file_path).st_mtime) > TASK_LOG_MAX_AGE)


def emit_via_redis(event, message, room=None):
r = get_redis_server()
try:
r.publish('events', json.dumps({'event': event, 'message': message, 'room': room}))
except redis.exceptions.ConnectionError:
pass


def put_log(line_no, line, task_id=None):
r = get_redis_server()
if not task_id:
task_id = frappe.local.task_id
task_progress_room = "task_progress:" + frappe.local.task_id
task_log_key = "task_log:" + task_id
emit_via_redis('task_progress', {
"message": {
"lines": {line_no: line}
},
"task_id": task_id
}, room=task_progress_room)
r.hset(task_log_key, line_no, line)
r.expire(task_log_key, 3600)


def get_redis_server():
"""Returns memcache connection."""
global redis_server
if not redis_server:
from redis import Redis
redis_server = Redis.from_url(conf.get("async_redis_server") or "redis://localhost:12311")
return redis_server


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

def write(self, data):
ret = super(FileAndRedisStream, self).write(data)
if frappe.local.task_id:
put_log(self.count, data, task_id=frappe.local.task_id)
self.count += 1
return ret


def get_std_streams(task_id):
stdout = FileAndRedisStream(get_task_log_file_path(task_id, 'stdout'), 'w')
# stderr = FileAndRedisStream(get_task_log_file_path(task_id, 'stderr'), 'w')
return stdout, stdout


def get_task_log_file_path(task_id, stream_type):
logs_dir = frappe.utils.get_site_path('task-logs')
return os.path.join(logs_dir, task_id + '.' + stream_type)


@frappe.whitelist(allow_guest=True)
def can_subscribe_doc(doctype, docname, sid):
from frappe.sessions import Session
from frappe.exceptions import PermissionError
session = Session(None).get_session_data()
if not frappe.has_permission(user=session.user, doctype=doctype, doc=docname, ptype='read'):
raise PermissionError()
return True

@frappe.whitelist(allow_guest=True)
def get_user_info(sid):
from frappe.sessions import Session
session = Session(None).get_session_data()
return {
'user': session.user,
}

def new_comment(doc, event):
if not doc.comment_doctype:
return
if doc.comment_doctype == 'Message':
if doc.comment_docname == frappe.session.user:
message = doc.as_dict()
message['broadcast'] = True
emit_via_redis('new_message', message, room=get_site_room())
else:
emit_via_redis('new_message', doc.as_dict(), room=get_user_room(doc.comment_docname))
else:
emit_via_redis('new_comment', doc.as_dict(), room=get_doc_room(doc.comment_doctype, doc.comment_docname))

def get_doc_room(doctype, docname):
return ''.join([frappe.local.site, ':doc:', doctype, '/', docname])

def get_user_room(user):
return ''.join([frappe.local.site, ':user:', user])

def get_site_room():
return ''.join([frappe.local.site, ':all'])


+ 1
- 1
frappe/boot.py Visa fil

@@ -69,7 +69,7 @@ def get_bootinfo():
bootinfo['versions'] = {k: v['version'] for k, v in get_versions().items()}

bootinfo.error_report_email = frappe.get_hooks("error_report_email")
bootinfo.default_background_image = get_url("/assets/frappe/images/ui/into-the-dawn.jpg")
bootinfo.default_background_image = "/assets/frappe/images/ui/into-the-dawn.jpg"
bootinfo.calendars = sorted(frappe.get_hooks("calendars"))

return bootinfo


+ 2
- 1
frappe/build.py Visa fil

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

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


+ 11
- 3
frappe/celery_app.py Visa fil

@@ -17,9 +17,10 @@ SITES_PATH = os.environ.get('SITES_PATH', '.')

# defaults
DEFAULT_CELERY_BROKER = "redis://localhost"
DEFAULT_CELERY_BACKEND = None
DEFAULT_CELERY_BACKEND = "redis://localhost"
DEFAULT_SCHEDULER_INTERVAL = 300
LONGJOBS_PREFIX = "longjobs@"
ASYNC_TASKS_PREFIX = "async@"

_app = None
def get_celery():
@@ -29,7 +30,7 @@ def get_celery():
_app = Celery('frappe',
broker=conf.celery_broker or DEFAULT_CELERY_BROKER,
backend=conf.celery_result_backend or DEFAULT_CELERY_BACKEND)
backend=conf.async_redis_server or DEFAULT_CELERY_BACKEND)
setup_celery(_app, conf)
@@ -41,9 +42,11 @@ def setup_celery(app, conf):
app.conf.CELERY_TASK_SERIALIZER = 'json'
app.conf.CELERY_ACCEPT_CONTENT = ['json']
app.conf.CELERY_TIMEZONE = 'UTC'
app.conf.CELERY_RESULT_SERIALIZER = 'json'
app.CELERY_TASK_RESULT_EXPIRES = timedelta(0, 3600)
if conf.celery_queue_per_site:
app.conf.CELERY_ROUTES = (SiteRouter(),)
app.conf.CELERY_ROUTES = (SiteRouter(), AsyncTaskRouter())
app.conf.CELERYBEAT_SCHEDULE = get_beat_schedule(conf)

@@ -61,6 +64,11 @@ class SiteRouter(object):
return get_queue(frappe.local.site)
return None

class AsyncTaskRouter(object):
def route_for_task(self, task, args=None, kwargs=None):
if task == "frappe.tasks.run_async_task" and hasattr(frappe.local, 'site'):
return get_queue(frappe.local.site, ASYNC_TASKS_PREFIX)
def get_queue(site, prefix=None):
return {'queue': "{}{}".format(prefix or "", site)}


+ 1
- 0
frappe/change_log/current/assign_to_myself.md Visa fil

@@ -0,0 +1 @@
- You can now quickly assign a document to yourself by clicking on "Assign to me"

+ 40
- 1
frappe/commands.py Visa fil

@@ -163,6 +163,16 @@ def install_app(context, app):
finally:
frappe.destroy()

@click.command('list-apps')
@pass_context
def list_apps(context):
"Reinstall site ie. wipe all data and start over"
site = get_single_site(context)
frappe.init(site=site)
frappe.connect()
print "\n".join(frappe.get_installed_apps())
frappe.destroy()

@click.command('add-system-manager')
@click.argument('email')
@click.option('--first-name')
@@ -616,7 +626,7 @@ def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None
site = get_single_site(context)
frappe.init(site=site)

if frappe.conf.run_selenium_tests:
if frappe.conf.run_selenium_tests and False:
sel.start(context.verbose, driver)

try:
@@ -738,6 +748,20 @@ def remove_from_installed_apps(context, app):
finally:
frappe.destroy()

@click.command('uninstall-app')
@click.argument('app')
@click.option('--dry-run', help='List all doctypes that will be deleted', is_flag=True, default=False)
@pass_context
def uninstall(context, app, dry_run=False):
from frappe.installer import remove_app
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
remove_app(app, dry_run)
finally:
frappe.destroy()

def move(dest_dir, site):
import os
if not os.path.isdir(dest_dir):
@@ -759,6 +783,18 @@ def move(dest_dir, site):
frappe.destroy()
return final_new_path


@click.command('set-config')
@click.argument('key')
@click.argument('value')
@pass_context
def set_config(context, key, value):
from frappe.installer import update_site_config
for site in context.sites:
frappe.init(site=site)
update_site_config(key, value)
frappe.destroy()

@click.command('drop-site')
@click.argument('site')
@click.option('--root-login', default='root')
@@ -797,6 +833,7 @@ commands = [
restore,
reinstall,
install_app,
list_apps,
add_system_manager,
migrate,
run_patch,
@@ -836,5 +873,7 @@ commands = [
_use,
backup,
remove_from_installed_apps,
uninstall,
drop_site,
set_config,
]

+ 0
- 0
frappe/core/doctype/async_task/__init__.py Visa fil


+ 142
- 0
frappe/core/doctype/async_task/async_task.json Visa fil

@@ -0,0 +1,142 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:celery_task_id",
"creation": "2015-07-03 11:28:03.496346",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"fields": [
{
"allow_on_submit": 0,
"fieldname": "celery_task_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Celery Task ID",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Status",
"no_copy": 0,
"options": "\nQueued\nRunning\nFinished\nFailed\n",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "stdout",
"fieldtype": "Long Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "stdout",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"fieldname": "stderr",
"fieldtype": "Long Text",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "stderr",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"label": "Reference DocType",
"options": "DocType",
"permlevel": 0,
"precision": ""
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"label": "Reference Doc",
"options": "reference_doctype",
"permlevel": 0,
"precision": ""
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-07-28 16:18:11.344349",
"modified_by": "Administrator",
"module": "Core",
"name": "Async Task",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC"
}

+ 10
- 0
frappe/core/doctype/async_task/async_task.py Visa fil

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

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

class AsyncTask(Document):
pass

+ 12
- 0
frappe/core/doctype/async_task/test_async_task.py Visa fil

@@ -0,0 +1,12 @@
# -*- 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('Async Task')

class TestAsyncTask(unittest.TestCase):
pass

+ 2
- 2
frappe/core/doctype/communication/communication.json Visa fil

@@ -5,7 +5,7 @@
"description": "Keep a track of all communications",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Setup",
"fields": [
{
"default": "COMM-",
@@ -198,7 +198,7 @@
"idx": 1,
"in_dialog": 0,
"issingle": 0,
"modified": "2015-07-28 07:28:11.457131",
"modified": "2015-07-28 17:18:11.664740",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",


+ 4
- 3
frappe/core/doctype/doctype/doctype.json Visa fil

@@ -7,6 +7,7 @@
"description": "DocType is a Table / Form in the application.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"fields": [
{
"fieldname": "sb0",
@@ -65,7 +66,7 @@
"label": "Document Type",
"oldfieldname": "document_type",
"oldfieldtype": "Select",
"options": "\nMaster\nTransaction\nSystem\nOther",
"options": "\nDocument\nSetup\nSystem\nOther",
"permlevel": 0
},
{
@@ -108,7 +109,7 @@
"oldfieldtype": "Table",
"options": "DocField",
"permlevel": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0
},
{
@@ -337,7 +338,7 @@
"idx": 1,
"issingle": 0,
"istable": 0,
"modified": "2015-03-03 10:40:45.768116",
"modified": "2015-07-28 16:18:11.925264",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",


+ 1
- 1
frappe/core/doctype/doctype/doctype.py Visa fil

@@ -87,7 +87,7 @@ class DocType(Document):

if autoname and (not autoname.startswith('field:')) \
and (not autoname.startswith('eval:')) \
and (not autoname in ('Prompt', 'hash')) \
and (not autoname.lower() in ('prompt', 'hash')) \
and (not autoname.startswith('naming_series:')):

prefix = autoname.split('.')[0]


+ 2
- 2
frappe/core/doctype/version/version.json Visa fil

@@ -3,7 +3,7 @@
"creation": "2014-02-20 17:22:37",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Setup",
"fields": [
{
"fieldname": "ref_doctype",
@@ -33,7 +33,7 @@
],
"icon": "icon-copy",
"idx": 1,
"modified": "2014-08-05 01:23:37.541856",
"modified": "2015-07-28 16:18:12.706419",
"modified_by": "Administrator",
"module": "Core",
"name": "Version",


+ 2
- 1
frappe/core/page/data_import_tool/importer.py Visa fil

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

import frappe, json
import frappe.permissions
import frappe.async

from frappe import _

@@ -14,7 +15,7 @@ from frappe.utils.dateutils import parse_date
from frappe.utils import cint, cstr, flt
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys

@frappe.whitelist()
@frappe.async.handler
def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=None,
ignore_links=False, pre_process=None):
"""upload data"""


+ 50
- 23
frappe/core/page/desktop/desktop.js Visa fil

@@ -2,10 +2,18 @@ frappe.provide('frappe.desktop');

frappe.pages['desktop'].on_page_load = function(wrapper) {
// load desktop
frappe.desktop.set_background();
if(!frappe.list_desktop) {
frappe.desktop.set_background();
}
frappe.desktop.refresh(wrapper);
};

frappe.pages['desktop'].on_page_show = function(wrapper) {
if(frappe.list_desktop) {
$("body").attr("data-route", "list-desktop");
}
};

$.extend(frappe.desktop, {
refresh: function(wrapper) {
if (wrapper) {
@@ -20,7 +28,10 @@ $.extend(frappe.desktop, {
var me = this;
frappe.utils.set_title("Desktop");

this.wrapper.html(frappe.render_template("desktop_icon_grid", {
var template = frappe.list_desktop ? "desktop_list_view" : "desktop_icon_grid";


this.wrapper.html(frappe.render_template(template, {
// all visible icons
desktop_items: this.get_desktop_items(),

@@ -28,7 +39,7 @@ $.extend(frappe.desktop, {
user_desktop_items: this.get_user_desktop_items(),
}));

this.setup_icon_click();
this.setup_module_click();

// notifications
this.show_pending_notifications();
@@ -96,29 +107,40 @@ $.extend(frappe.desktop, {
return out;
},

setup_icon_click: function() {
this.wrapper.on("click", ".app-icon", function() {
var parent = $(this).parent();
var link = parent.attr("data-link");
if(link) {
if(link.substr(0, 1)==="/" || link.substr(0, 4)==="http") {
window.open(link, "_blank");
} else {
frappe.set_route(link);
}
return false;
setup_module_click: function() {
var me = this;

if(frappe.list_desktop) {
this.wrapper.on("click", ".desktop-list-item", function() {
me.open_module($(this));
});
} else {
this.wrapper.on("click", ".app-icon", function() {
me.open_module($(this).parent());
});
}
},

open_module: function(parent) {
var link = parent.attr("data-link");
if(link) {
if(link.substr(0, 1)==="/" || link.substr(0, 4)==="http") {
window.open(link, "_blank");
} else {
module = frappe.get_module(parent.attr("data-name"));
if (module && module.onclick) {
module.onclick();
return false;
}
frappe.set_route(link);
}
});
return false;
} else {
module = frappe.get_module(parent.attr("data-name"));
if (module && module.onclick) {
module.onclick();
return false;
}
}
},

make_sortable: function() {
if (frappe.dom.is_touchscreen()) {
if (frappe.dom.is_touchscreen() || frappe.list_desktop) {
return;
}

@@ -215,10 +237,15 @@ $.extend(frappe.desktop, {
sum = frappe.boot.notification_info.open_count_module[module];
}
if (frappe.modules[module]) {
var notifier = $("#module-count-" + frappe.get_module(module)._id);
var notifier = $(".module-count-" + frappe.get_module(module)._id);
if(notifier.length) {
notifier.toggle(sum ? true : false);
notifier.find(".circle-text").html(sum || "");
var circle = notifier.find(".circle-text");
if(circle.length) {
circle.html(sum || "");
} else {
notifier.html(sum);
}
}
}
}


+ 25
- 0
frappe/core/page/desktop/desktop_list_view.html Visa fil

@@ -0,0 +1,25 @@
<div class="container page-body">
<div class="row">
<div class="layout-main-section">
<div class="page-content desktop-list" style="margin-top: 40px;">
{% for (var i=0, l=desktop_items.length; i < l; i++) {
var module = frappe.get_module(desktop_items[i]);
if (!module || (user_desktop_items.indexOf(module.name)===-1 && !module.force_show)
|| frappe.user.is_module_blocked(module.name)) { continue; }
%}
<div class="desktop-list-item" id="module-icon-{%= module._id %}"
data-name="{%= module.name %}" data-link="{%= module.link %}"
title="{%= module._label %}">
<h4>
<i class="{{ module.icon }} text-muted"
style="font-size: 20px; margin-right: 15px;"></i>
{{ module._label }}
</h4>
<span class="open-notification module-count-{{ module._id }}"
style="display: none;"></span>
</div>
{% } %}
</div>
</div>
</div>
</div>

+ 2
- 2
frappe/core/page/desktop/desktop_module_icon.html Visa fil

@@ -1,8 +1,8 @@
<div id="module-icon-{%= _id %}" class="case-wrapper"
<div class="case-wrapper"
data-name="{%= name %}" data-link="{%= link %}" title="{%= _label %}">
{%= app_icon %}
<div class="case-label text-ellipsis">
<div class="circle" id="module-count-{%= _id %}" style="display: none;">
<div class="circle module-count-{%= _id %}" style="display: none;">
<span class="circle-text"></span>
</div>
<!-- <span id="module-count-{%= _id %}" class="octicon octicon-primitive-dot circle" style="display: None"></span> -->


+ 39
- 0
frappe/desk/doctype/note/note.js Visa fil

@@ -0,0 +1,39 @@
frappe.ui.form.on("Note", {
refresh: function(frm) {
if(frm.doc.__islocal) {
frm.events.set_editable(frm, true);
} else {
// toggle edit
frm.add_custom_button("Edit", function() {
frm.events.set_editable(frm, !frm.is_note_editable);
})
frm.events.set_editable(frm, false);
}
},
set_editable: function(frm, editable) {
// hide all fields other than content

// no permission
if(editable && !frm.perm[0].write) return;

// content read_only
frm.set_df_property("content", "read_only", editable ? 0: 1);

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

})

// no label, description for content either
frm.get_field("content").toggle_label(editable);
frm.get_field("content").toggle_description(editable);

// set flag for toggle
frm.is_note_editable = editable;
}
});

+ 2
- 2
frappe/desk/doctype/note/note.json Visa fil

@@ -4,7 +4,7 @@
"description": "Note is a free page where users can share documents / notes",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Transaction",
"document_type": "Document",
"fields": [
{
"fieldname": "title",
@@ -35,7 +35,7 @@
],
"icon": "icon-file-text",
"idx": 1,
"modified": "2015-02-06 00:44:06.475116",
"modified": "2015-07-28 16:18:12.301520",
"modified_by": "Administrator",
"module": "Desk",
"name": "Note",


+ 20
- 0
frappe/desk/doctype/todo/todo_list.js Visa fil

@@ -5,6 +5,26 @@ frappe.listview_settings['ToDo'] = {
"status": "Open"
};
me.page.set_title(__("To Do"));

},
refresh: function(me) {
// override assigned to me by owner
me.page.sidebar.find(".assigned-to-me a").off("click").on("click", function() {
var assign_filter = me.filter_list.get_filter("assigned_by");
assign_filter && assign_filter.remove(true);

me.filter_list.add_filter(me.doctype, "owner", '=', user);
me.run();
});

// add assigned by me
me.page.add_sidebar_item(__("Assigned By Me"), function() {
var assign_filter = me.filter_list.get_filter("owner");
assign_filter && assign_filter.remove(true);

me.filter_list.add_filter(me.doctype, "assigned_by", '=', user);
me.run();
}, ".assigned-to-me");
},
add_fields: ["reference_type", "reference_name"],
}

+ 12
- 3
frappe/desk/form/meta.py Visa fil

@@ -121,7 +121,14 @@ class FormMeta(Meta):
df.search_fields = map(lambda sf: sf.strip(), search_fields.split(","))

def add_linked_with(self):
"""add list of doctypes this doctype is 'linked' with"""
"""add list of doctypes this doctype is 'linked' with.

Example, for Customer:

{"Address": {"fieldname": "customer"}..}
"""

# find fields where this doctype is linked
links = frappe.db.sql("""select parent, fieldname from tabDocField
where (fieldtype="Link" and options=%s)
or (fieldtype="Select" and options=%s)""", (self.name, "link:"+ self.name))
@@ -137,15 +144,17 @@ class FormMeta(Meta):
ret[dt] = { "fieldname": links[dt] }

if links:
for grand_parent, options in frappe.db.sql("""select parent, options from tabDocField
# find out if linked in a child table
for parent, options in frappe.db.sql("""select parent, options from tabDocField
where fieldtype="Table"
and options in (select name from tabDocType
where istable=1 and name in (%s))""" % ", ".join(["%s"] * len(links)) ,tuple(links)):

ret[grand_parent] = {"child_doctype": options, "fieldname": links[options] }
ret[parent] = {"child_doctype": options, "fieldname": links[options] }
if options in ret:
del ret[options]

# find links of parents
links = frappe.db.sql("""select dt from `tabCustom Field`
where (fieldtype="Table" and options=%s)""", (self.name))
links += frappe.db.sql("""select parent from tabDocField


+ 4
- 8
frappe/desk/moduleview.py Visa fil

@@ -59,10 +59,10 @@ def build_standard_config(module, doctype_info):
data = []

add_section(data, _("Documents"), "icon-star",
[d for d in doctype_info if in_document_section(d)])
[d for d in doctype_info if d.document_type in ("Document", "Transaction")])

add_section(data, _("Setup"), "icon-cog",
[d for d in doctype_info if not in_document_section(d)])
[d for d in doctype_info if d.document_type in ("Master", "Setup", "")])

add_section(data, _("Standard Reports"), "icon-list",
get_report_list(module, is_standard="Yes"))
@@ -82,14 +82,10 @@ def add_section(data, label, icon, items):
def add_custom_doctypes(data, doctype_info):
"""Adds Custom DocTypes to modules setup via `config/desktop.py`."""
add_section(data, _("Documents"), "icon-star",
[d for d in doctype_info if (d.custom and in_document_section(d))])
[d for d in doctype_info if (d.custom and d.document_type in ("Document", "Transaction"))])

add_section(data, _("Setup"), "icon-cog",
[d for d in doctype_info if (d.custom and not in_document_section(d))])

def in_document_section(d):
"""Returns True if `document_type` property is one of `Master`, `Transaction` or not set."""
return d.document_type in ("Transaction", "Master", "")
[d for d in doctype_info if (d.custom and d.document_type in ("Setup", "Master", ""))])

def get_doctype_info(module):
"""Returns list of non child DocTypes for given module."""


+ 4
- 28
frappe/desk/page/messages/messages.js Visa fil

@@ -37,7 +37,6 @@ frappe.desk.pages.Messages = Class.extend({

make: function() {
this.make_sidebar();
this.set_next_refresh();
},

make_sidebar: function() {
@@ -135,6 +134,9 @@ frappe.desk.pages.Messages = Class.extend({
hide_refresh: true,
freeze: false,
render_row: function(wrapper, data) {
if(data.parenttype==="Assignment" || data.comment_type==="Shared") {
data.is_system_message = 1;
}
var row = $(frappe.render_template("messages_row", {
data: data
})).appendTo(wrapper);
@@ -155,31 +157,7 @@ frappe.desk.pages.Messages = Class.extend({
});
},

refresh: function() {
// check for updates every 5 seconds if page is active
this.set_next_refresh();

if(!frappe.session_alive) {
// not in session
return;
}

if(frappe.get_route()[0]!="messages") {
// not on messages page
return;
}

if (this.list) {
this.list.run();
}
},

set_next_refresh: function() {
// 30 seconds
setTimeout("frappe.desk.pages.messages.refresh()", 30000);
},

////
refresh: function() {},

get_contact: function() {
var route = location.hash;
@@ -195,5 +173,3 @@ frappe.desk.pages.Messages = Class.extend({


});



+ 3
- 2
frappe/desk/page/messages/messages_row.html Visa fil

@@ -6,10 +6,11 @@
<div class="media">
<div class="pull-left hidden-xs">
<span class="avatar avatar-small" title="{%= frappe.user.full_name(data.owner) %} ">
<img class="media-object" src="{%= frappe.user.image(data.owner) %}">
<img class="media-object {{ data.is_system_message ? "grayscale" : "" }}"
src="{%= frappe.user.image(data.owner) %}">
</span>
</div>
<div class="media-body">
<div class="media-body {{ data.is_system_message ? "text-muted" : "" }}">
{%= data.comment %}
</div>
</div>


+ 2
- 2
frappe/email/doctype/email_account/email_account.json Visa fil

@@ -7,7 +7,7 @@
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Setup",
"fields": [
{
"fieldname": "email_settings",
@@ -429,7 +429,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-07-16 10:11:06.466258",
"modified": "2015-07-28 16:18:12.116327",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Account",


+ 2
- 7
frappe/email/doctype/standard_reply/standard_reply.json Visa fil

@@ -4,7 +4,7 @@
"creation": "2014-06-19 05:20:26.331041",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Transaction",
"document_type": "Document",
"fields": [
{
"fieldname": "subject",
@@ -33,18 +33,13 @@
}
],
"icon": "icon-comment",
"modified": "2015-02-05 05:11:46.922356",
"modified": "2015-07-28 16:18:12.432775",
"modified_by": "Administrator",
"module": "Email",
"name": "Standard Reply",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"permlevel": 0,
"read": 1,
"role": "All"
},
{
"apply_user_permissions": 1,
"create": 1,


+ 1
- 0
frappe/geo/country_info.json Visa fil

@@ -911,6 +911,7 @@
},
"Ghana": {
"code": "gh",
"currency": "GHS",
"currency_fraction": "Pesewa",
"currency_fraction_units": 100,
"currency_symbol": "\u20b5",


+ 2
- 2
frappe/geo/doctype/country/country.json Visa fil

@@ -5,7 +5,7 @@
"creation": "2013-01-19 10:23:30",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Setup",
"fields": [
{
"fieldname": "country_name",
@@ -42,7 +42,7 @@
"icon": "icon-globe",
"idx": 1,
"in_create": 0,
"modified": "2015-02-05 05:11:36.234753",
"modified": "2015-07-28 16:18:11.855617",
"modified_by": "Administrator",
"module": "Geo",
"name": "Country",


+ 20
- 1
frappe/handler.py Visa fil

@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe
from frappe import _
import frappe.utils
import frappe.async
import frappe.sessions
import frappe.utils.file_manager
import frappe.desk.form.run_method
@@ -18,6 +19,10 @@ def version():
def ping():
return "pong"

@frappe.async.handler
def async_ping():
return "pong"

@frappe.whitelist()
def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None):
frappe.desk.form.run_method.runserverobj(method, docs=docs, dt=dt, dn=dn, arg=arg, args=args)
@@ -70,7 +75,7 @@ def handle():

return build_response("json")

def execute_cmd(cmd):
def execute_cmd(cmd, async=False):
"""execute a request as python module"""
for hook in frappe.get_hooks("override_whitelisted_methods", {}).get(cmd, []):
# override using the first hook
@@ -78,6 +83,8 @@ def execute_cmd(cmd):
break

method = get_attr(cmd)
if async:
method = method.queue

# check if whitelisted
if frappe.session['user'] == 'Guest':
@@ -103,3 +110,15 @@ def get_attr(cmd):
method = globals()[cmd]
frappe.log("method:" + cmd)
return method


@frappe.whitelist()
def get_async_task_status(task_id):
from frappe.celery_app import get_celery
c = get_celery()
a = c.AsyncResult(task_id)
frappe.local.response['response'] = a.result
return {
"state": a.state,
"progress": 0
}

+ 5
- 1
frappe/hooks.py Visa fil

@@ -26,7 +26,7 @@ to ERPNext.
"""

app_icon = "octicon octicon-circuit-board"
app_version = "5.4.2"
app_version = "6.0.0"
app_color = "orange"
github_link = "https://github.com/frappe/frappe"

@@ -131,6 +131,9 @@ doc_events = {
"frappe.email.doctype.email_alert.email_alert.trigger_email_alerts"
],
"on_trash": "frappe.desk.notifications.clear_doctype_notifications"
},
"Comment": {
"after_insert": "frappe.async.new_comment"
}
}

@@ -147,6 +150,7 @@ scheduler_events = {
"frappe.desk.doctype.event.event.send_event_digest",
"frappe.sessions.clear_expired_sessions",
"frappe.email.doctype.email_alert.email_alert.trigger_daily_alerts",
"frappe.async.remove_old_task_logs",
]
}



+ 62
- 3
frappe/installer.py Visa fil

@@ -151,6 +151,36 @@ def remove_from_installed_apps(app_name):
if frappe.flags.in_install:
post_install()

def remove_app(app_name, dry_run=False):
"""Delete app and all linked to the app's module with the app."""

if not dry_run:
confirm = raw_input("All doctypes (including custom), modules related to this app will be deleted. Are you sure you want to continue (y/n) ? ")
if confirm!="y":
return

from frappe.utils.backups import scheduled_backup
print "Backing up..."
scheduled_backup(ignore_files=True)

# remove modules, doctypes, roles
for module_name in frappe.get_module_list(app_name):
for doctype in frappe.get_list("DocType", filters={"module": module_name},
fields=["name", "issingle"]):
print "removing {0}...".format(doctype.name)
# drop table

if not dry_run:
if not doctype.issingle:
frappe.db.sql("drop table `tab{0}`".format(doctype.name))
frappe.delete_doc("DocType", doctype.name)

print "removing Module {0}...".format(module_name)
if not dry_run:
frappe.delete_doc("Module Def", module_name)

remove_from_installed_apps(app_name)

def post_install(rebuild_website=False):
if rebuild_website:
render.clear_cache()
@@ -188,7 +218,7 @@ def make_conf(db_name=None, db_password=None, site_config=None):

def make_site_config(db_name=None, db_password=None, site_config=None):
frappe.create_folder(os.path.join(frappe.local.site_path))
site_file = os.path.join(frappe.local.site_path, "site_config.json")
site_file = get_site_config_path()

if not os.path.exists(site_file):
if not (site_config and isinstance(site_config, dict)):
@@ -197,6 +227,34 @@ def make_site_config(db_name=None, db_password=None, site_config=None):
with open(site_file, "w") as f:
f.write(json.dumps(site_config, indent=1, sort_keys=True))

def update_site_config(key, value):
"""Update a value in site_config"""
with open(get_site_config_path(), "r") as f:
site_config = json.loads(f.read())

# int
try:
value = int(value)
except ValueError:
pass

# boolean
if value in ("False", "True"):
value = eval(value)

# remove key if value is None
if value == "None":
if key in site_config:
del site_config[key]
else:
site_config[key] = value

with open(get_site_config_path(), "w") as f:
f.write(json.dumps(site_config, indent=1, sort_keys=True))

def get_site_config_path():
return os.path.join(frappe.local.site_path, "site_config.json")

def get_conf_params(db_name=None, db_password=None):
if not db_name:
db_name = raw_input("Database Name: ")
@@ -214,7 +272,8 @@ def make_site_dirs():
site_private_path = os.path.join(frappe.local.site_path, 'private')
for dir_path in (
os.path.join(site_private_path, 'backups'),
os.path.join(site_public_path, 'files')):
os.path.join(site_public_path, 'files'),
os.path.join(frappe.local.site_path, 'task-logs')):
if not os.path.exists(dir_path):
os.makedirs(dir_path)
locks_dir = frappe.get_site_path('locks')
@@ -227,7 +286,7 @@ def add_module_defs(app):
d = frappe.new_doc("Module Def")
d.app_name = app
d.module_name = module
d.save()
d.save(ignore_permissions=True)

def remove_missing_apps():
apps = ('frappe_subscription', 'shopping_cart')


+ 4
- 4
frappe/model/base_document.py Visa fil

@@ -184,7 +184,7 @@ class BaseDocument(object):

elif df.fieldtype in ("Datetime", "Date") and d[fieldname]=="":
d[fieldname] = None
elif df.get("unique") and cstr(d[fieldname]).strip()=="":
# unique empty field should be set to None
d[fieldname] = None
@@ -270,11 +270,11 @@ class BaseDocument(object):
self.name = None
self.db_insert()
return
type, value, traceback = sys.exc_info()
frappe.msgprint(_("Duplicate name {0} {1}").format(self.doctype, self.name))
raise frappe.DuplicateEntryError, (self.doctype, self.name, e), traceback
elif "Duplicate" in cstr(e.args[1]):
# unique constraint
self.show_unique_validation_message(e)
@@ -303,7 +303,7 @@ class BaseDocument(object):
self.show_unique_validation_message(e)
else:
raise
def show_unique_validation_message(self, e):
type, value, traceback = sys.exc_info()
fieldname = str(e).split("'")[-2]


+ 1
- 1
frappe/model/db_query.py Visa fil

@@ -238,7 +238,7 @@ class DatabaseQuery(object):

elif f[2] == "like" or (isinstance(f[3], basestring) and
(not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])):
if f[2] == "like":
if f[2] == "like" and isinstance(f[3], basestring):
# because "like" uses backslash (\) for escaping
f[3] = f[3].replace("\\", "\\\\")



+ 4
- 0
frappe/patches.txt Visa fil

@@ -85,3 +85,7 @@ execute:frappe.permissions.reset_perms("DocType")
execute:frappe.db.sql("delete from `tabProperty Setter` where `property` = 'idx'")
frappe.patches.v5_2.change_checks_to_not_null
frappe.patches.v5_3.rename_chinese_languages

frappe.patches.v6_0.make_task_log_folder
frappe.patches.v6_0.document_type_rename
frappe.patches.v6_0.fix_ghana_currency

+ 0
- 0
frappe/patches/v6_0/__init__.py Visa fil


+ 7
- 0
frappe/patches/v6_0/document_type_rename.py Visa fil

@@ -0,0 +1,7 @@
import frappe

def execute():
frappe.db.sql("""update tabDocType set document_type='Document'
where document_type='Transaction'""")
frappe.db.sql("""update tabDocType set document_type='Setup'
where document_type='Master'""")

+ 7
- 0
frappe/patches/v6_0/fix_ghana_currency.py Visa fil

@@ -0,0 +1,7 @@

def execute():
from frappe.geo.country_info import get_all
import frappe.utils.install

countries = get_all()
frappe.utils.install.add_country_and_currency("Ghana", frappe._dict(countries["Ghana"]))

+ 6
- 0
frappe/patches/v6_0/make_task_log_folder.py Visa fil

@@ -0,0 +1,6 @@
import frappe.utils, os

def execute():
path = frappe.utils.get_site_path('task-logs')
if not os.path.exists(path):
os.makedirs(path)

+ 4
- 1
frappe/public/build.json Visa fil

@@ -15,7 +15,8 @@
"public/js/lib/moment/moment.min.js",
"public/js/lib/highlight.pack.js",
"public/js/frappe/class.js",
"website/js/website.js"
"website/js/website.js",
"public/js/lib/socket.io.min.js"
],
"js/editor.min.js": [
"public/js/lib/jquery/jquery.hotkeys.js",
@@ -49,6 +50,7 @@
"public/js/lib/nprogress.js",
"public/js/lib/moment/moment-with-locales.min.js",
"public/js/lib/moment/moment-timezone-with-data.min.js",
"public/js/lib/socket.io.min.js",

"public/js/frappe/provide.js",
"public/js/frappe/class.js",
@@ -61,6 +63,7 @@
"public/js/frappe/ui/messages.js",

"public/js/frappe/request.js",
"public/js/frappe/socket.js",
"public/js/frappe/router.js",
"public/js/frappe/defaults.js",
"public/js/lib/microtemplate.js",


+ 4
- 0
frappe/public/css/common.css Visa fil

@@ -215,3 +215,7 @@ a.no-decoration:active {
text-align: center;
}
}
.grayscale {
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
}

+ 4
- 0
frappe/public/css/desk.css Visa fil

@@ -215,6 +215,10 @@ a.no-decoration:active {
text-align: center;
}
}
.grayscale {
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
}
.nav-pills a,
.nav-pills a:hover {
border-bottom: none;


+ 12
- 0
frappe/public/css/desktop.css Visa fil

@@ -161,3 +161,15 @@ body[data-route="desktop"] .navbar-default {
margin-top: 3px;
margin-bottom: 3px;
}
.desktop-list-item {
padding: 10px 15px;
border-bottom: 1px solid #d1d8dd;
cursor: pointer;
}
.desktop-list-item:hover,
.desktop-list-item:focus {
background-color: #f7fafc;
}
.desktop-list-item h4 {
display: inline-block;
}

+ 4
- 0
frappe/public/css/form.css Visa fil

@@ -157,6 +157,10 @@ select.form-control {
-moz-appearance: none;
appearance: none;
}
.form-control.bold {
font-weight: bold;
background-color: #fffce7;
}
.form-headline .alert {
font-size: 12px;
border-color: #d1d8dd;


+ 36
- 10
frappe/public/css/mobile.css Visa fil

@@ -166,6 +166,9 @@ body {
.timeline .timeline-item:last-child {
border-bottom: none;
}
.list-row {
padding: 13px 15px !important;
}
.doclist-row {
position: relative;
padding-right: 10px;
@@ -192,6 +195,15 @@ body {
.doclist-row .list-row-right {
float: right;
}
.doclist-row .list-row-right .list-row-indicator {
top: 4px;
}
.doclist-row .list-row-right .list-row-indicator .indicator::before,
.doclist-row .list-row-right .list-row-indicator .indicator::after {
height: 12px;
width: 12px;
border-radius: 12px;
}
.doclist-row .list-row-right.no-right-column {
position: absolute;
top: 0;
@@ -199,15 +211,6 @@ body {
left: -10px;
width: 100%;
}
.doclist-row .list-row-right.no-right-column .list-row-indicator {
top: 5px;
}
.doclist-row .list-row-right.no-right-column .list-row-indicator .indicator::before,
.doclist-row .list-row-right.no-right-column .list-row-indicator .indicator::after {
height: 14px;
width: 14px;
border-radius: 14px;
}
body[data-route^="messages"] .navbar-center {
display: block !important;
position: absolute;
@@ -258,6 +261,26 @@ body {
}
}
@media (max-width: 991px) {
input[type='checkbox'] {
-webkit-appearance: none;
width: 12px;
height: 12px;
background: white;
border-radius: 6px;
border: 1px solid #d1d8dd;
display: inline-block;
}
input[type='checkbox']:checked {
background: #3b99fc;
border-color: #3b99fc;
}
input.list-select-all {
margin-top: 0px;
}
.input-area input[type='checkbox'] {
margin-top: 2px;
margin-left: -23px;
}
.intro-area,
.footnote-area {
padding: 15px 0px;
@@ -269,7 +292,7 @@ body {
position: relative;
}
.page-head .page-title h1 {
font-size: 18px;
font-size: 22px;
margin-top: 22px;
}
body[data-route^="Form"] .page-title h1 {
@@ -304,6 +327,9 @@ body {
.module-item {
padding: 7px 0px !important;
}
.module-item h4 {
font-weight: normal;
}
#navbar-breadcrumbs {
margin: 0px;
display: inline-block;


+ 4
- 0
frappe/public/css/website.css Visa fil

@@ -215,6 +215,10 @@ a.no-decoration:active {
text-align: center;
}
}
.grayscale {
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
}
html {
min-height: 100%;
}


+ 0
- 4
frappe/public/js/frappe/desk.js Visa fil

@@ -126,10 +126,6 @@ frappe.Application = Class.extend({

if(frappe.get_route()[0] != "messages") {
if(r.message.new_messages.length) {
$.each(r.message.new_messages, function(i, m) {
frappe.utils.notify(__("Message from {0}", [m.comment_by_fullname]),
m.comment);
});
frappe.utils.set_title_prefix("(" + r.message.new_messages.length + ")");
}
}


+ 7
- 1
frappe/public/js/frappe/form/control.js Visa fil

@@ -190,6 +190,12 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({
</div>').appendTo(this.parent);
}
},
toggle_label: function(show) {
this.$wrapper.find(".control-label").toggleClass("hide", !show);
},
toggle_description: function(show) {
this.$wrapper.find(".help-box").toggleClass("hide", !show);
},
set_input_areas: function() {
if(this.only_input) {
this.input_area = this.wrapper;
@@ -647,7 +653,7 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({
this.input = this.$input.get(0);
this.set_input_attributes();
this.has_input = true;
this.$wrapper.find(".control-label").addClass("hide");
this.toggle_label(false);
},
onclick: function() {
if(this.frm && this.frm.doc) {


+ 1
- 1
frappe/public/js/frappe/form/dashboard.js Visa fil

@@ -42,7 +42,7 @@ frappe.ui.form.Dashboard = Class.extend({
var badge = $(repl('<div class="col-md-4">\
<div class="alert-badge">\
<a class="badge-link grey">%(label)s</a>\
<span class="badge">-</span>\
<span class="badge" style="margin-left: 10px;">-</span>\
</div></div>', {label:label, icon: frappe.boot.doctype_icons[doctype]}))
.appendTo(this.body)



+ 11
- 0
frappe/public/js/frappe/form/footer/assign_to.js Visa fil

@@ -92,6 +92,7 @@ frappe.ui.form.AssignTo = Class.extend({
me.dialog = new frappe.ui.Dialog({
title: __('Add to To Do'),
fields: [
{fieldtype:'Check', fieldname:'myself', label:__("Assign to me"), "default":0},
{fieldtype:'Link', fieldname:'assign_to', options:'User',
label:__("Assign To"),
description:__("Add to To Do List Of"), reqd:true},
@@ -115,6 +116,16 @@ frappe.ui.form.AssignTo = Class.extend({
}

me.dialog.show();

me.dialog.get_input("myself").on("click", function() {
if($(this).prop("checked")) {
me.dialog.set_value("assign_to", user);
me.dialog.set_value("notify", 0);
} else {
me.dialog.set_value("assign_to", "");
me.dialog.set_value("notify", 1);
}
});
},
add_assignment: function() {
var me = this;


+ 6
- 1
frappe/public/js/frappe/form/footer/timeline.js Visa fil

@@ -227,9 +227,14 @@ frappe.ui.form.Comments = Class.extend({
btn: btn,
callback: function(r) {
if(!r.exc) {
var comment_exists = !!$.map(me.get_comments(), function(x) {
return x.name == r.message.name? true : undefined}).length;
me.input.val("");
if (comment_exists) {
return;
}
me.frm.get_docinfo().comments =
me.get_comments().concat([r.message]);
me.input.val("");
me.refresh(true);
}
}


+ 9
- 13
frappe/public/js/frappe/form/layout.js Visa fil

@@ -44,24 +44,20 @@ frappe.ui.form.Layout = Class.extend({
$(this.frm.wrapper).trigger("refresh-fields");
}

if (this.frm) {
// show empty form notification
setTimeout(function() {
me.page.find(".empty-form-alert").remove();
if(!(me.page.find(".frappe-control:visible").length)) {
$('<div class="empty-form-alert text-muted" style="padding: 15px;">'
+__("This form does not have any input")+'</div>')
.appendTo(me.page);
}
}, 100);
}

// dependent fields
this.refresh_dependency();

// refresh sections
this.refresh_sections();
},
show_empty_form_message: function() {
this.wrapper.find(".empty-form-alert").remove();
if(!(this.wrapper.find(".frappe-control:visible").length)) {
$('<div class="empty-form-alert text-muted" style="padding: 15px;">'
+__("This form does not have any input")+'</div>')
.appendTo(this.page);
}
},
attach_doc_and_docfields: function(refresh) {
var me = this;
for(var i=0, l=this.fields_list.length; i<l; i++) {
@@ -85,7 +81,7 @@ frappe.ui.form.Layout = Class.extend({

this.section = null;
this.column = null;
if(this.fields[0] && this.fields[0].fieldtype!="Section Break") {
if((this.fields[0] && this.fields[0].fieldtype!="Section Break") || !this.fields.length) {
this.make_section();
}
$.each(this.fields, function(i, df) {


+ 38
- 22
frappe/public/js/frappe/form/save.js Visa fil

@@ -18,21 +18,23 @@ frappe.ui.form.save = function(frm, action, callback, btn) {
var freeze_message = working_label ? __(working_label) : "";

var save = function() {
check_name();
if(check_mandatory()) {
_call({
method: "frappe.desk.form.save.savedocs",
args: { doc: frm.doc, action:action},
callback: function(r) {
$(document).trigger("save", [frm.doc]);
callback(r);
},
btn: btn,
freeze_message: freeze_message
});
} else {
$(btn).prop("disabled", false);
}
check_name(function() {
if(check_mandatory()) {
_call({
method: "frappe.desk.form.save.savedocs",
args: { doc: frm.doc, action:action},
callback: function(r) {
$(document).trigger("save", [frm.doc]);
callback(r);
},
btn: btn,
freeze_message: freeze_message
});
} else {
$(btn).prop("disabled", false);
}
});

};

var cancel = function() {
@@ -63,19 +65,33 @@ frappe.ui.form.save = function(frm, action, callback, btn) {
});
};

var check_name = function() {
var check_name = function(callback) {
var doc = frm.doc;
var meta = locals.DocType[doc.doctype];
if(doc.__islocal && (meta && meta.autoname
&& meta.autoname.toLowerCase()=='prompt')) {
var newname = prompt('Enter the name of the new '+ doc.doctype, '');
if(newname) {
doc.__newname = strip(newname);
} else {
msgprint(__("Name is required"));
var d = frappe.prompt(__("Name"), function(values) {
var newname = values.value;
if(newname) {
doc.__newname = strip(newname);
} else {
msgprint(__("Name is required"));
throw "name required";
}

callback();

}, __('Enter the name of the new {0}', [doc.doctype]), __("Create"));

if(doc.__newname) {
d.set_value("value", doc.__newname);
}

d.onhide = function() {
$(btn).prop("disabled", false);
throw "name required";
}
} else {
callback();
}
};



+ 3
- 0
frappe/public/js/frappe/form/toolbar.js Visa fil

@@ -220,6 +220,9 @@ frappe.ui.form.Toolbar = Class.extend({
} else {
var click = {
"Save": function() {
if(!frappe.dom.is_touchscreen() && Math.random() < 0.25) {
show_alert(__("ProTip: You can also use Ctrl+S to Save"));
}
me.frm.save('Save', null, this);
},
"Submit": function() {


+ 5
- 0
frappe/public/js/frappe/list/doclistview.js Visa fil

@@ -216,6 +216,11 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
refresh: function() {
var me = this;
this.init_stats();

if(this.listview.settings.refresh) {
this.listview.settings.refresh(this);
}

if(frappe.route_options) {
me.set_route_options();
} else if(me.dirty) {


+ 2
- 4
frappe/public/js/frappe/list/list_item_row.html Visa fil

@@ -1,5 +1,5 @@
<div class="row doclist-row {% if (data._checkbox) { %} has-checkbox {% } %}">
<div class="{% if(right_column) { %} col-xs-12 {% } else { %} col-xs-10 {% } %}
<div class="col-xs-10
{% if (list.meta.title_field) { %}
col-sm-8
{% } else { %}
@@ -13,8 +13,7 @@
{% if (list.meta.title_field) {
var is_different = data.name !== data[list.meta.title_field];
%}
<div class="list-col col-sm-2 col-xs-10 text-right text-ellipsis rtl list-row-id
{% if (!is_different) { %} hidden-xs {% } %}">
<div class="list-col col-sm-2 hidden-xs text-right text-ellipsis rtl list-row-id">
{% if (is_different) { %}
<a class="text-muted list-value" href="#Form/{%= data._doctype_encoded %}/{%= data._name_encoded %}">
{%= data.name %}</a>
@@ -24,7 +23,6 @@

<!-- comment -->
<div class="list-col col-sm-2 col-xs-2
{% if(!right_column) { %} no-right-column {% } %}
text-right list-row-right">
<div class="visible-xs pull-right list-row-indicator">{%= list.get_indicator_dot(data) %}</div>
<div class="hidden-xs pull-right">


+ 1
- 1
frappe/public/js/frappe/list/list_item_row_head.html Visa fil

@@ -1,6 +1,6 @@
<div class="list-row list-row-head">
<div class="row doclist-row">
<div class="col-xs-12
<div class="col-xs-10
{% if (list.meta.title_field) { %}
col-sm-8
{% } else { %}


+ 3
- 1
frappe/public/js/frappe/list/list_sidebar.html Visa fil

@@ -6,7 +6,9 @@
<li><a href="#Report/{%= doctype %}">{%= __("Report") %}</a></li>
<li class="hide calendar-link"><a href="#Calendar/{%= doctype %}">{%= __("Calendar") %}</a></li>
<li class="hide gantt-link"><a href="#Gantt/{%= doctype %}">{%= __("Gantt") %}</a></li>
<li><a onclick="cur_list.assigned_to_me()">{%= __("Assigned To Me") %}</a></li>
<li class="assigned-to-me">
<a>{%= __("Assigned To Me") %}</a>
</li>
{% if(frappe.help.has_help(doctype)) { %}
<li><a class="help-link" data-doctype="{{ doctype }}">{{ __("Help") }}</a></li>
{% } %}


+ 13
- 1
frappe/public/js/frappe/list/list_sidebar.js Visa fil

@@ -12,8 +12,8 @@ frappe.provide('frappe.views');
frappe.views.ListSidebar = Class.extend({
init: function(opts) {
$.extend(this, opts);
this.get_stats();
this.make();
this.get_stats();
},
make: function() {
var sidebar_content = frappe.render_template("list_sidebar", {doctype: this.doclistview.doctype});
@@ -25,10 +25,22 @@ frappe.views.ListSidebar = Class.extend({

this.sidebar = this.page_sidebar.add(this.offcanvas_list_sidebar);

this.setup_assigned_to_me();

if(frappe.views.calendar[this.doctype]) {
this.sidebar.find(".calendar-link, .gantt-link").removeClass("hide");
}
},
setup_assigned_to_me: function() {
var me = this;
this.page.sidebar.find(".assigned-to-me a").on("click", function() {
me.doclistview.assigned_to_me();
});

this.offcanvas_list_sidebar.find(".assigned-to-me a").on("click", function() {
me.doclistview.assigned_to_me();
});
},
get_stats: function() {
var me = this
return frappe.call({


+ 1
- 1
frappe/public/js/frappe/misc/user.js Visa fil

@@ -62,7 +62,7 @@ frappe.get_gravatar = function(email_id) {
frappe.ui.set_user_background = function(src, selector, style) {
if(!selector) selector = "#page-desktop";
if(!style) style = "Fill Screen";
if(!src) src = frappe.boot.default_background_image;
if(!src) src = frappe.urllib.get_full_url(frappe.boot.default_background_image);

frappe.dom.set_style(repl('%(selector)s { \
background: url("%(src)s") center center;\


+ 12
- 1
frappe/public/js/frappe/request.js Visa fil

@@ -28,10 +28,21 @@ frappe.call = function(opts) {
args.cmd = opts.method;
}

var callback = function(data, xhr) {
if(data.task_id) {
// async call, subscribe
frappe.socket.subscribe(data.task_id, opts);
}
else {
// ajax
return opts.callback(data, xhr);
}
}

return frappe.request.call({
type: opts.type || "POST",
args: args,
success: opts.callback,
success: callback,
error: opts.error,
always: opts.always,
btn: opts.btn,


+ 114
- 0
frappe/public/js/frappe/socket.js Visa fil

@@ -0,0 +1,114 @@
frappe.socket = {
open_tasks: {},
init: function() {
if (frappe.boot.no_async) {
return;
}
var socketio_server = frappe.boot.dev_server? '//' + document.domain + ':3000' : '//' + document.domain + ':' + window.location.port;
frappe.socket.socket = io.connect(socketio_server);
frappe.socket.socket.on('msgprint', function(message) {
frappe.msgprint(message)
});

frappe.socket.setup_listeners();
frappe.socket.setup_reconnect();
$(document).on('form-load', function(e, frm) {
frappe.socket.doc_subscribe(frm.doctype, frm.docname);
});

$(document).on('form-unload', function(e, frm) {
frappe.socket.doc_unsubscribe(frm.doctype, frm.docname);
});
},
subscribe: function(task_id, opts) {
frappe.socket.socket.emit('task_subscribe', task_id);
frappe.socket.socket.emit('progress_subscribe', task_id);

frappe.socket.open_tasks[task_id] = opts;
},
doc_subscribe: function(doctype, docname) {
frappe.socket.socket.emit('doc_subscribe', doctype, docname);
frappe.socket.open_doc = {doctype: doctype, docname: docname};
},
doc_unsubscribe: function(doctype, docname) {
frappe.socket.socket.emit('doc_unsubscribe', doctype, docname);
frappe.socket.open_doc = null;
},
setup_listeners: function() {
frappe.socket.socket.on('task_status_change', function(data) {
if(data.status==="Running") {
frappe.socket.process_response(data, "running");
} else {
// failed or finished
frappe.socket.process_response(data, "callback");
// delete frappe.socket.open_tasks[data.task_id];
}
});
frappe.socket.socket.on('task_progress', function(data) {
frappe.socket.process_response(data, "progress");
});
frappe.socket.socket.on('new_comment', function(comment) {
if (frappe.model.docinfo[comment.comment_doctype] && frappe.model.docinfo[comment.comment_doctype][comment.comment_docname]) {
var comments = frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments
var comment_exists = !!$.map(comments, function(x) { return x.name == comment.name? true : undefined}).length
if (!comment_exists) {
frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments = comments.concat([comment]);
}
}
if (cur_frm.doctype === comment.comment_doctype && cur_frm.docname === comment.comment_docname) {
cur_frm.comments.refresh();
}
});
frappe.socket.socket.on('new_message', function(comment) {
frappe.utils.notify(__("Message from {0}", [comment.comment_by_fullname]), comment.comment);
if ($(cur_page.page).data('page-route') === 'messages') {
var current_contact = $(cur_page.page).find('[data-contact]').data('contact');
var on_broadcast_page = current_contact === user;
if (current_contact == comment.owner || (on_broadcast_page && comment.broadcast)) {
var $row = $('<div class="list-row"/>');
frappe.desk.pages.messages.list.data.unshift(comment);
frappe.desk.pages.messages.list.render_row($row, comment);
frappe.desk.pages.messages.list.parent.prepend($row);
}
}
else {
}
});
},
setup_reconnect: function() {
// subscribe again to open_tasks
frappe.socket.socket.on("connect", function() {
$.each(frappe.socket.open_tasks, function(task_id, opts) {
frappe.socket.subscribe(task_id, opts);
});
});

if(frappe.socket.open_doc) {
frappe.socket.doc_subscribe(frappe.socket.open_doc.doctype, frappe.socket.open_doc.docname);
}
},
process_response: function(data, method) {
if(!data) {
return;
}

// success
if(data) {
var opts = frappe.socket.open_tasks[data.task_id];
if(opts[method]) opts[method](data);
}

// always
frappe.request.cleanup(opts, data);
if(opts.always) {
opts.always(data);
}

// error
if(data.status_code && data.status_code > 400 && opts.error) {
opts.error(data);
}
}
}

$(frappe.socket.init);

+ 12
- 10
frappe/public/js/frappe/ui/dialog.js Visa fil

@@ -48,19 +48,21 @@ frappe.ui.Dialog = frappe.ui.FieldGroup.extend({
me.display = true;
cur_dialog = me;
frappe.ui.open_dialogs.push(me);
var first = $(me.body).find('.modal-content :input:first');
if(first.length && first.attr("data-fieldtype")!="Date") {
try {
first.get(0).focus();
} catch(e) {
console.log("Dialog: unable to focus on first input: " + e);
}
}
me.focus_on_first_input();
me.on_page_show && me.on_page_show();
})

});

},
focus_on_first_input: function() {
var first = $(this.body).find(':input:first');
if(first.length && first.attr("data-fieldtype")!="Date") {
try {
first.get(0).focus();
} catch(e) {
console.log("Dialog: unable to focus on first input: " + e);
}
}
},
get_primary_btn: function() {
return this.$wrapper.find(".modal-header .btn-primary");
},


+ 8
- 0
frappe/public/js/frappe/ui/messages.js Visa fil

@@ -44,6 +44,14 @@ frappe.confirm = function(message, ifyes, ifno) {
}

frappe.prompt = function(fields, callback, title, primary_label) {
if (typeof fields === "string") {
fields = [{
label: fields,
fieldname: "value",
fieldtype: "Data",
reqd: 1
}];
}
if(!$.isArray(fields)) fields = [fields];
var d = new frappe.ui.Dialog({
fields: fields,


+ 15
- 0
frappe/public/js/frappe/ui/page.js Visa fil

@@ -232,6 +232,21 @@ frappe.ui.Page = Class.extend({
.on("click", action).appendTo(this.inner_toolbar.removeClass("hide"))
},

//-- Sidebar --//

add_sidebar_item: function(label, action, insert_after) {
var parent = this.sidebar.find(".sidebar-menu.standard-actions");
var li = $('<li>');
var link = $('<a>').html(label).on("click", action).appendTo(li);

if(insert_after) {
li.insertAfter(parent.find(insert_after));
} else {
li.appendTo(parent);
}
return link;
},

//---//

clear_user_actions: function() {


+ 2
- 2
frappe/public/js/frappe/ui/toolbar/about.js Visa fil

@@ -33,8 +33,8 @@ frappe.ui.misc.about = function() {
var $wrap = $("#about-app-versions").empty();
$.each(keys(versions).sort(), function(i, key) {
var v = versions[key];
$($.format('<p><b>{0}:</b> v{1}<br><span class="text-muted">{2}</span></p>',
[v.title, v.version, v.description])).appendTo($wrap);
$($.format('<p><b>{0}:</b> v{1}<br></p>',
[v.title, v.version])).appendTo($wrap);
});

frappe.versions = versions;


+ 0
- 1
frappe/public/js/frappe/ui/toolbar/toolbar.js Visa fil

@@ -126,7 +126,6 @@ frappe.ui.toolbar.update_notifications = function() {
frappe.views.show_open_count_list(this);
}
}
return false;
});

$(".navbar-new-comments")


+ 5
- 0
frappe/public/js/frappe/upload.js Visa fil

@@ -93,6 +93,11 @@ frappe.upload = {
opts.callback(attachment, r);
$(document).trigger("upload_complete", attachment);
},
error: function(r) {
// if no onerror, assume callback will handle errors
opts.onerror ? opts.onerror(r) : opts.callback(null, null, r);
return;
},
btn: opts.btn
});
}


+ 18
- 4
frappe/public/js/frappe/views/communication.js Visa fil

@@ -130,11 +130,25 @@ frappe.views.CommunicationComposer = Class.extend({
this.dialog.get_input("standard_reply").on("change", function() {
var standard_reply = $(this).val();
var prepend_reply = function() {
if(me.reply_added===standard_reply) {
return;
}
var content_field = me.dialog.fields_dict.content;
var content = content_field.get_value() || "";
content_field.set_input(
frappe.standard_replies[standard_reply]
+ "<br><br>" + content);

parts = content.split('<!-- salutation-ends -->');

if(parts.length===2) {
content = [parts[0], frappe.standard_replies[standard_reply],
"<br>", parts[1]];
} else {
content = [frappe.standard_replies[standard_reply],
"<br>", content];
}

content_field.set_input(content.join(''));

me.reply_added = standard_reply;
}
if(frappe.standard_replies[standard_reply]) {
prepend_reply();
@@ -369,7 +383,7 @@ frappe.views.CommunicationComposer = Class.extend({

if(this.real_name) {
this.message = '<p>'+__('Dear') +' '
+ this.real_name + ",</p>" + (this.message || "");
+ this.real_name + ",</p><!-- salutation-ends --><br>" + (this.message || "");
}

var reply = (this.message || "")


+ 1
- 1
frappe/public/js/frappe/views/module/module_section.html Visa fil

@@ -7,7 +7,7 @@
{% if (item.type==="doctype") { %}
<span class="open-notification hide" data-doctype="{%= item.name %}"></span>
{% } %}
<p class="text-muted small module-item-description">{%= item.description %}</p>
<p class="text-muted small module-item-description hidden-xs">{%= item.description %}</p>
</div>
<div class="col-xs-4 text-muted text-right small" style="padding-top: 5px;">
{% if (item.last_modified) { %}


+ 12
- 4
frappe/public/js/frappe/views/reports/reportview.js Visa fil

@@ -97,9 +97,9 @@ frappe.views.ReportView = frappe.ui.Listing.extend({
parent: this.page.main,
start: 0,
show_filters: true,
new_doctype: this.doctype,
allow_delete: true,
});
this.make_new_and_refresh();
this.make_delete();
this.make_column_picker();
this.make_sorter();
@@ -109,8 +109,16 @@ frappe.views.ReportView = frappe.ui.Listing.extend({
this.make_save();
this.make_user_permissions();
this.set_tag_and_status_filter();
this.page.add_menu_item(__("Refresh"), function() {
me.refresh();
},

make_new_and_refresh: function() {
var me = this;
this.page.set_primary_action(__("Refresh"), function() {
me.run();
});

this.page.add_menu_item(__("New {0}", [this.doctype]), function() {
new_doc(me.doctype);
}, true);
},

@@ -244,7 +252,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({

if(docfield.fieldtype==="Link" && docfield.fieldname!=="name") {
docfield.link_onclick =
repl('frappe.container.page.reportview.set_filter("%(fieldname)s", "%(value)s").page.reportview.run()',
repl('frappe.container.page.reportview.set_filter("%(fieldname)s", "%(value)s").run()',
{fieldname:docfield.fieldname, value:value});
}
return frappe.format(value, docfield, {for_print: for_print}, dataContext);


+ 7
- 2
frappe/public/js/legacy/form.js Visa fil

@@ -273,7 +273,7 @@ _f.Frm.prototype.rename_notify = function(dt, old, name) {
return;

// cleanup
if(this && this.opendocs[old]) {
if(this && this.opendocs[old] && frappe.meta.docfield_copy[dt]) {
// delete docfield copy
frappe.meta.docfield_copy[dt][name] = frappe.meta.docfield_copy[dt][old];
delete frappe.meta.docfield_copy[dt][old];
@@ -391,8 +391,13 @@ _f.Frm.prototype.refresh = function(docname) {
// load the record for the first time, if not loaded (call 'onload')
cur_frm.cscript.is_onload = false;
if(!this.opendocs[this.docname]) {
var me = this;
cur_frm.cscript.is_onload = true;
this.setnewdoc();
$(document).trigger("form-load", [this]);
$(this.page.wrapper).on('hide', function(e) {
$(document).trigger("form-unload", [me]);
})
} else {
this.render_form(is_a_different_doc);
}
@@ -442,13 +447,13 @@ _f.Frm.prototype.render_form = function(is_a_different_doc) {
first.focus();
}
}

} else {
this.refresh_header(is_a_different_doc);
}

$(cur_frm.wrapper).trigger('render_complete');

this.layout.show_empty_form_message();
}

_f.Frm.prototype.refresh_field = function(fname) {


+ 7000
- 0
frappe/public/js/lib/socket.io.min.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 5
- 0
frappe/public/less/common.less Visa fil

@@ -232,3 +232,8 @@ a.no-decoration& {
text-align: center;
}
}

.grayscale {
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
}

+ 18
- 0
frappe/public/less/desktop.less Visa fil

@@ -200,3 +200,21 @@ body[data-route=""] .navbar-default, body[data-route="desktop"] .navbar-default
margin-bottom: 3px;
}
}

.desktop-list {

}

.desktop-list-item& {
padding: 10px 15px;
border-bottom: 1px solid @border-color;
cursor: pointer;

&:hover, &:focus {
background-color: @panel-bg;
}

h4 {
display: inline-block;
}
}

+ 5
- 0
frappe/public/less/form.less Visa fil

@@ -205,6 +205,11 @@ select.form-control {
appearance: none;
}

.form-control.bold {
font-weight: bold;
background-color: @light-yellow;
}

.form-headline .alert {
font-size: @text-medium;
border-color: @border-color;


+ 43
- 12
frappe/public/less/mobile.less Visa fil

@@ -157,6 +157,10 @@
}

// listviews
.list-row {
padding: 13px 15px !important;
}

.doclist-row& {
position: relative;
padding-right: 10px;
@@ -187,6 +191,19 @@

.list-row-right {
float: right;

.list-row-indicator {
top: 4px;

// bigger indicators for list
.indicator::before, .indicator::after {
height: 12px;
width: 12px;
border-radius: 12px;
}

}

}

.list-row-right.no-right-column {
@@ -196,17 +213,6 @@
left: -10px;
width: 100%;

.list-row-indicator {
top: 5px;

// bigger indicators for list
.indicator::before, .indicator::after {
height: 14px;
width: 14px;
border-radius: 14px;
}

}
}
}

@@ -273,6 +279,27 @@
}

@media(max-width: 991px) {
input[type='checkbox'] {
-webkit-appearance:none;
width: 12px;
height: 12px;
background: white;
border-radius: 6px;
border: 1px solid @border-color;
display: inline-block;
}
input[type='checkbox']:checked {
background: @checkbox-color;
border-color: @checkbox-color;
}
input.list-select-all {
margin-top: 0px;
}
.input-area input[type='checkbox'] {
margin-top: 2px;
margin-left: -23px;
}

.intro-area,
.footnote-area {
padding: 15px 0px;
@@ -288,7 +315,7 @@

.page-head {
.page-title h1 {
font-size: 18px;
font-size: 22px;
margin-top: 22px;
}
}
@@ -334,6 +361,10 @@

.module-item {
padding: 7px 0px !important;

h4 {
font-weight: normal;
}
}

#navbar-breadcrumbs {


+ 2
- 0
frappe/public/less/variables.less Visa fil

@@ -32,3 +32,5 @@
@label-info-bg: #E8DDFF;
@label-warning-bg: #FFE6BF;
@label-danger-bg: #FFDCDC;

@checkbox-color: #3b99fc;

+ 4
- 1
frappe/sessions.py Visa fil

@@ -17,6 +17,7 @@ import frappe.defaults
import frappe.translate
from frappe.utils.change_log import get_change_log
import redis
import os
from urllib import unquote

@frappe.whitelist()
@@ -124,6 +125,8 @@ def get():
frappe.get_attr(hook)(bootinfo=bootinfo)

bootinfo["lang"] = frappe.translate.get_user_lang()
bootinfo["dev_server"] = os.environ.get('DEV_SERVER', False)
bootinfo["no_async"] = frappe.conf.no_async
return bootinfo

class Session:
@@ -163,7 +166,7 @@ class Session:
"full_name": self.full_name,
"user_type": self.user_type,
"device": self.device,
"session_country": get_geo_ip_country(frappe.local.request_ip)
"session_country": get_geo_ip_country(frappe.local.request_ip) if frappe.local.request_ip else None
})

# insert session


+ 58
- 3
frappe/tasks.py Visa fil

@@ -4,9 +4,13 @@
from __future__ import unicode_literals
import frappe
from frappe.utils.scheduler import enqueue_events
from frappe.celery_app import get_celery, celery_task, task_logger, LONGJOBS_PREFIX
from frappe.celery_app import get_celery, celery_task, task_logger, LONGJOBS_PREFIX, ASYNC_TASKS_PREFIX
from frappe.utils import get_sites
from frappe.utils.file_lock import create_lock, delete_lock
from frappe.handler import execute_cmd
from frappe.async import set_task_status, END_LINE, get_std_streams
import frappe.utils.response
import sys
import time
import MySQLdb

@@ -14,7 +18,7 @@ import MySQLdb
def sync_queues():
"""notifies workers to monitor newly added sites"""
app = get_celery()
shortjob_workers, longjob_workers = get_workers(app)
shortjob_workers, longjob_workers, async_tasks_workers = get_workers(app)

if shortjob_workers:
for worker in shortjob_workers:
@@ -24,18 +28,25 @@ def sync_queues():
for worker in longjob_workers:
sync_worker(app, worker, prefix=LONGJOBS_PREFIX)

if async_tasks_workers:
for worker in async_tasks_workers:
sync_worker(app, worker, prefix=ASYNC_TASKS_PREFIX)

def get_workers(app):
longjob_workers = []
shortjob_workers = []
async_tasks_workers = []

active_queues = app.control.inspect().active_queues()
for worker in active_queues:
if worker.startswith(LONGJOBS_PREFIX):
longjob_workers.append(worker)
elif worker.startswith(ASYNC_TASKS_PREFIX):
async_tasks_workers.append(worker)
else:
shortjob_workers.append(worker)

return shortjob_workers, longjob_workers
return shortjob_workers, longjob_workers, async_tasks_workers

def sync_worker(app, worker, prefix=''):
active_queues = set(get_active_queues(app, worker))
@@ -125,6 +136,50 @@ def pull_from_email_account(site, email_account):
finally:
frappe.destroy()

@celery_task(bind=True)
def run_async_task(self, site, user, cmd, form_dict):
ret = {}
frappe.init(site)
frappe.connect()
sys.stdout, sys.stderr = get_std_streams(self.request.id)
frappe.local.stdout, frappe.local.stderr = sys.stdout, sys.stderr
frappe.local.task_id = self.request.id
frappe.cache()
try:
set_task_status(self.request.id, "Running")
frappe.db.commit()
frappe.set_user(user)
# sleep(60)
frappe.local.form_dict = frappe._dict(form_dict)
execute_cmd(cmd, async=True)
ret = frappe.local.response
except Exception, e:
frappe.db.rollback()
if not frappe.flags.in_test:
frappe.db.commit()

ret = frappe.local.response
http_status_code = getattr(e, "http_status_code", 500)
ret['status_code'] = http_status_code
frappe.errprint(frappe.get_traceback())
frappe.utils.response.make_logs()
set_task_status(self.request.id, "Failed", response=ret)
task_logger.error('Exception in running {}: {}'.format(cmd, ret['exc']))
else:
set_task_status(self.request.id, "Finished", response=ret)
if not frappe.flags.in_test:
frappe.db.commit()
finally:
sys.stdout.write('\n' + END_LINE)
sys.stderr.write('\n' + END_LINE)
if not frappe.flags.in_test:
frappe.destroy()
sys.stdout.close()
sys.stderr.close()
sys.stdout, sys.stderr = 1, 0
return ret


@celery_task()
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False):
try:


+ 17
- 0
frappe/tests/test_async.py Visa fil

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
import unittest
import frappe

from frappe.tasks import run_async_task

class TestAsync(unittest.TestCase):
def test_response(self):
result = run_async_task.delay(frappe.local.site, 'Administrator', 'async_ping',
frappe._dict())
result = result.get()
self.assertEquals(result.get("message"), "pong")

+ 5
- 5
frappe/tests/test_data_import.py Visa fil

@@ -35,7 +35,7 @@ class TestDataImport(unittest.TestCase):
exporter.get_template("Blog Category", all_doctypes="No", with_data="No")
content = read_csv_content(frappe.response.result)
content.append(["", "", "test-category", "Test Cateogry"])
importer.upload(content)
importer.upload.queue(content)
self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "Test Category")

# export with data
@@ -44,7 +44,7 @@ class TestDataImport(unittest.TestCase):

# overwrite
content[-1][3] = "New Title"
importer.upload(content, overwrite=True)
importer.upload.queue(content, overwrite=True)
self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "New Title")

def test_import_only_children(self):
@@ -57,7 +57,7 @@ class TestDataImport(unittest.TestCase):
exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No")
content = read_csv_content(frappe.response.result)
content.append(["", "test_import_userrole@example.com", "Blogger"])
importer.upload(content)
importer.upload.queue(content)

user = frappe.get_doc("User", user_email)
self.assertEquals(len(user.get("user_roles")), 1)
@@ -67,7 +67,7 @@ class TestDataImport(unittest.TestCase):
exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No")
content = read_csv_content(frappe.response.result)
content.append(["", "test_import_userrole@example.com", "Website Manager"])
importer.upload(content, overwrite=True)
importer.upload.queue(content, overwrite=True)

user = frappe.get_doc("User", user_email)
self.assertEquals(len(user.get("user_roles")), 1)
@@ -81,7 +81,7 @@ class TestDataImport(unittest.TestCase):
content[-1][3] = "Private"
content[-1][4] = "2014-01-01 10:00:00.000000"
content[-1][content[15].index("role")] = "System Manager"
importer.upload(content)
importer.upload.queue(content)

ev = frappe.get_doc("Event", {"subject":"__Test Event"})
self.assertTrue("System Manager" in [d.role for d in ev.roles])

+ 1
- 0
frappe/utils/__init__.py Visa fil

@@ -400,3 +400,4 @@ def get_request_session(max_retries=3):
session.mount("http://", requests.adapters.HTTPAdapter(max_retries=Retry(total=5, status_forcelist=[500])))
session.mount("https://", requests.adapters.HTTPAdapter(max_retries=Retry(total=5, status_forcelist=[500])))
return session


+ 1
- 2
frappe/utils/backups.py Visa fil

@@ -41,7 +41,7 @@ class BackupGenerator:
last_db, last_file = self.get_recent_backup(older_than)
else:
last_db, last_file = False, False
if not (self.backup_path_files and self.backup_path_db):
self.set_backup_file_name()
if not (last_db and last_file):
@@ -219,4 +219,3 @@ if __name__ == "__main__":

if cmd == "delete_temp_backups":
delete_temp_backups()


+ 4
- 2
frappe/utils/install.py Visa fil

@@ -106,7 +106,8 @@ def add_country_and_currency(name, country):
"country_name": name,
"code": country.code,
"date_format": country.date_format or "dd-mm-yyyy",
"time_zones": "\n".join(country.timezones or [])
"time_zones": "\n".join(country.timezones or []),
"docstatus": 0
}).db_insert()

if country.currency and not frappe.db.exists("Currency", country.currency):
@@ -116,6 +117,7 @@ def add_country_and_currency(name, country):
"fraction": country.currency_fraction,
"symbol": country.currency_symbol,
"fraction_units": country.currency_fraction_units,
"number_format": country.number_format
"number_format": country.number_format,
"docstatus": 0
}).db_insert()


+ 7
- 4
frappe/utils/response.py Visa fil

@@ -64,18 +64,21 @@ def as_json():
response.data = json.dumps(frappe.local.response, default=json_handler, separators=(',',':'))
return response

def make_logs():
def make_logs(response = None):
"""make strings for msgprint and errprint"""
if not response:
response = frappe.local.response

if frappe.error_log:
# frappe.response['exc'] = json.dumps("\n".join([cstr(d) for d in frappe.error_log]))
frappe.response['exc'] = json.dumps([frappe.utils.cstr(d) for d in frappe.local.error_log])
response['exc'] = json.dumps([frappe.utils.cstr(d) for d in frappe.local.error_log])

if frappe.local.message_log:
frappe.response['_server_messages'] = json.dumps([frappe.utils.cstr(d) for
response['_server_messages'] = json.dumps([frappe.utils.cstr(d) for
d in frappe.local.message_log])

if frappe.debug_log and frappe.conf.get("logging") or False:
frappe.response['_debug_messages'] = json.dumps(frappe.local.debug_log)
response['_debug_messages'] = json.dumps(frappe.local.debug_log)

def json_handler(obj):
"""serialize non-serializable data for json"""


+ 2
- 2
frappe/website/doctype/blog_category/blog_category.json Visa fil

@@ -4,7 +4,7 @@
"creation": "2013-03-08 09:41:11",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Setup",
"fields": [
{
"fieldname": "category_name",
@@ -49,7 +49,7 @@
],
"icon": "icon-tag",
"idx": 1,
"modified": "2015-02-05 05:11:34.877605",
"modified": "2015-07-28 16:18:11.486847",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Category",


+ 2
- 2
frappe/website/doctype/blogger/blogger.json Visa fil

@@ -5,7 +5,7 @@
"description": "User ID of a Blogger",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Setup",
"fields": [
{
"fieldname": "disabled",
@@ -61,7 +61,7 @@
"icon": "icon-user",
"idx": 1,
"max_attachments": 1,
"modified": "2015-02-19 09:29:25.836280",
"modified": "2015-07-28 16:18:11.567110",
"modified_by": "Administrator",
"module": "Website",
"name": "Blogger",


+ 2
- 2
frappe/website/doctype/web_form/web_form.json Visa fil

@@ -7,7 +7,7 @@
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Transaction",
"document_type": "Document",
"fields": [
{
"allow_on_submit": 0,
@@ -230,7 +230,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"modified": "2015-02-05 05:11:48.897157",
"modified": "2015-07-28 16:18:12.772231",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Form",


+ 2
- 2
frappe/website/doctype/web_page/web_page.json Visa fil

@@ -3,7 +3,7 @@
"description": "Page to show on the website\n",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Transaction",
"document_type": "Document",
"fields": [
{
"fieldname": "section_title",
@@ -200,7 +200,7 @@
"icon": "icon-file-alt",
"idx": 1,
"max_attachments": 20,
"modified": "2015-07-22 12:38:08.696692",
"modified": "2015-07-28 16:18:12.887565",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Page",


+ 2
- 2
frappe/website/doctype/website_slideshow/website_slideshow.json Visa fil

@@ -4,7 +4,7 @@
"description": "Slideshow like display for the website",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Transaction",
"document_type": "Document",
"fields": [
{
"fieldname": "slideshow_name",
@@ -42,7 +42,7 @@
"icon": "icon-play",
"idx": 1,
"max_attachments": 10,
"modified": "2015-02-20 05:04:19.614170",
"modified": "2015-07-28 16:18:13.013029",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Slideshow",


+ 59
- 59
frappe/workflow/doctype/workflow_document_state/workflow_document_state.json Visa fil

@@ -1,75 +1,75 @@
{
"allow_import": 1,
"creation": "2013-02-22 01:27:36",
"description": "Represents the states allowed in one document and role assigned to change the state.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"allow_import": 1,
"creation": "2013-02-22 01:27:36",
"description": "Represents the states allowed in one document and role assigned to change the state.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"fields": [
{
"fieldname": "state",
"fieldtype": "Link",
"in_list_view": 1,
"label": "State",
"options": "Workflow State",
"permlevel": 0,
"print_width": "160px",
"reqd": 1,
"fieldname": "state",
"fieldtype": "Link",
"in_list_view": 1,
"label": "State",
"options": "Workflow State",
"permlevel": 0,
"print_width": "160px",
"reqd": 1,
"width": "160px"
},
},
{
"description": "0 - Draft; 1 - Submitted; 2 - Cancelled",
"fieldname": "doc_status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Doc Status",
"options": "0\n1\n2",
"permlevel": 0,
"print_width": "80px",
"description": "0 - Draft; 1 - Submitted; 2 - Cancelled",
"fieldname": "doc_status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Doc Status",
"options": "0\n1\n2",
"permlevel": 0,
"print_width": "80px",
"width": "80px"
},
},
{
"fieldname": "update_field",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Update Field",
"fieldname": "update_field",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Update Field",
"permlevel": 0
},
},
{
"fieldname": "update_value",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Update Value",
"fieldname": "update_value",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Update Value",
"permlevel": 0
},
},
{
"fieldname": "allow_edit",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Only Allow Edit For",
"options": "Role",
"permlevel": 0,
"print_width": "160px",
"reqd": 1,
"fieldname": "allow_edit",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Only Allow Edit For",
"options": "Role",
"permlevel": 0,
"print_width": "160px",
"reqd": 1,
"width": "160px"
},
},
{
"fieldname": "message",
"fieldtype": "Text",
"in_list_view": 1,
"label": "Message",
"permlevel": 0,
"print_width": "160px",
"reqd": 0,
"fieldname": "message",
"fieldtype": "Text",
"in_list_view": 1,
"label": "Message",
"permlevel": 0,
"print_width": "160px",
"reqd": 0,
"width": "160px"
}
],
"idx": 1,
"istable": 1,
"modified": "2014-05-27 06:35:01.070158",
"modified_by": "Administrator",
"module": "Workflow",
"name": "Workflow Document State",
"owner": "Administrator",
],
"idx": 1,
"istable": 1,
"modified": "2015-07-28 16:18:13.257862",
"modified_by": "Administrator",
"module": "Workflow",
"name": "Workflow Document State",
"owner": "Administrator",
"permissions": []
}
}

+ 2
- 2
frappe/workflow/doctype/workflow_state/workflow_state.json Visa fil

@@ -5,7 +5,7 @@
"description": "Workflow state represents the current state of a document.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Master",
"document_type": "Setup",
"fields": [
{
"fieldname": "workflow_state_name",
@@ -36,7 +36,7 @@
],
"icon": "icon-flag",
"idx": 1,
"modified": "2015-05-27 02:51:01.978973",
"modified": "2015-07-28 16:18:13.320514",
"modified_by": "Administrator",
"module": "Workflow",
"name": "Workflow State",


+ 1
- 0
requirements.txt Visa fil

@@ -28,4 +28,5 @@ html2text
email_reply_parser
click
num2words
gevent-socketio
watchdog==0.8.0

+ 1
- 1
setup.py Visa fil

@@ -1,6 +1,6 @@
from setuptools import setup, find_packages

version = "5.4.2"
version = "6.0.0"

with open("requirements.txt", "r") as f:
install_requires = f.readlines()


Vissa filer visades inte eftersom för många filer har ändrats

Laddar…
Avbryt
Spara