@@ -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 | |||
@@ -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,2 +1,2 @@ | |||
from __future__ import unicode_literals | |||
__version__ = "5.4.2" | |||
__version__ = "6.0.0" |
@@ -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: | |||
@@ -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) |
@@ -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']) | |||
@@ -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 | |||
@@ -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)) | |||
@@ -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)} | |||
@@ -0,0 +1 @@ | |||
- You can now quickly assign a document to yourself by clicking on "Assign to me" |
@@ -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 +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" | |||
} |
@@ -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 |
@@ -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 |
@@ -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", | |||
@@ -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", | |||
@@ -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] | |||
@@ -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", | |||
@@ -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""" | |||
@@ -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); | |||
} | |||
} | |||
} | |||
} | |||
@@ -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> |
@@ -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> --> | |||
@@ -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; | |||
} | |||
}); |
@@ -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", | |||
@@ -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"], | |||
} |
@@ -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 | |||
@@ -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.""" | |||
@@ -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({ | |||
}); | |||
@@ -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> | |||
@@ -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", | |||
@@ -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, | |||
@@ -911,6 +911,7 @@ | |||
}, | |||
"Ghana": { | |||
"code": "gh", | |||
"currency": "GHS", | |||
"currency_fraction": "Pesewa", | |||
"currency_fraction_units": 100, | |||
"currency_symbol": "\u20b5", | |||
@@ -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", | |||
@@ -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 | |||
} |
@@ -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", | |||
] | |||
} | |||
@@ -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') | |||
@@ -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] | |||
@@ -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("\\", "\\\\") | |||
@@ -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 +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'""") |
@@ -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"])) |
@@ -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) |
@@ -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", | |||
@@ -215,3 +215,7 @@ a.no-decoration:active { | |||
text-align: center; | |||
} | |||
} | |||
.grayscale { | |||
-webkit-filter: grayscale(100%); | |||
filter: grayscale(100%); | |||
} |
@@ -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; | |||
@@ -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; | |||
} |
@@ -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; | |||
@@ -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; | |||
@@ -215,6 +215,10 @@ a.no-decoration:active { | |||
text-align: center; | |||
} | |||
} | |||
.grayscale { | |||
-webkit-filter: grayscale(100%); | |||
filter: grayscale(100%); | |||
} | |||
html { | |||
min-height: 100%; | |||
} | |||
@@ -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 + ")"); | |||
} | |||
} | |||
@@ -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) { | |||
@@ -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) | |||
@@ -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; | |||
@@ -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); | |||
} | |||
} | |||
@@ -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) { | |||
@@ -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(); | |||
} | |||
}; | |||
@@ -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() { | |||
@@ -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) { | |||
@@ -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,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 { %} | |||
@@ -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> | |||
{% } %} | |||
@@ -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({ | |||
@@ -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;\ | |||
@@ -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, | |||
@@ -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); |
@@ -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"); | |||
}, | |||
@@ -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, | |||
@@ -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() { | |||
@@ -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; | |||
@@ -126,7 +126,6 @@ frappe.ui.toolbar.update_notifications = function() { | |||
frappe.views.show_open_count_list(this); | |||
} | |||
} | |||
return false; | |||
}); | |||
$(".navbar-new-comments") | |||
@@ -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 | |||
}); | |||
} | |||
@@ -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 || "") | |||
@@ -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) { %} | |||
@@ -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); | |||
@@ -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) { | |||
@@ -232,3 +232,8 @@ a.no-decoration& { | |||
text-align: center; | |||
} | |||
} | |||
.grayscale { | |||
-webkit-filter: grayscale(100%); | |||
filter: grayscale(100%); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
@@ -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 { | |||
@@ -32,3 +32,5 @@ | |||
@label-info-bg: #E8DDFF; | |||
@label-warning-bg: #FFE6BF; | |||
@label-danger-bg: #FFDCDC; | |||
@checkbox-color: #3b99fc; |
@@ -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 | |||
@@ -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: | |||
@@ -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") |
@@ -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]) |
@@ -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 | |||
@@ -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() | |||
@@ -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() | |||
@@ -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""" | |||
@@ -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", | |||
@@ -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", | |||
@@ -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", | |||
@@ -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", | |||
@@ -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", | |||
@@ -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": [] | |||
} | |||
} |
@@ -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", | |||
@@ -28,4 +28,5 @@ html2text | |||
email_reply_parser | |||
click | |||
num2words | |||
gevent-socketio | |||
watchdog==0.8.0 |
@@ -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() | |||