@@ -15,7 +15,6 @@ install: | |||||
- wget https://raw.githubusercontent.com/frappe/bench/master/install_scripts/setup_frappe.sh | - 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 bash setup_frappe.sh --skip-setup-bench --mysql-root-password travis | ||||
- sudo pip install --upgrade pip | - sudo pip install --upgrade pip | ||||
- sudo service redis-server start | |||||
- rm $TRAVIS_BUILD_DIR/.git/shallow | - rm $TRAVIS_BUILD_DIR/.git/shallow | ||||
- cd ~/ && bench init frappe-bench --frappe-path $TRAVIS_BUILD_DIR | - cd ~/ && bench init frappe-bench --frappe-path $TRAVIS_BUILD_DIR | ||||
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ | - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ | ||||
@@ -23,10 +22,13 @@ install: | |||||
script: | script: | ||||
- cd ~/frappe-bench | - cd ~/frappe-bench | ||||
- bench use test_site | - bench use test_site | ||||
- bench setup redis-cache | |||||
- bench setup redis-async-broker | |||||
- bench setup procfile --with-celery-broker | |||||
- bench reinstall | - bench reinstall | ||||
- bench build | - bench build | ||||
- bench build-website | - bench build-website | ||||
- bench serve & | |||||
- bench start & | |||||
- sleep 10 | - sleep 10 | ||||
- bench --verbose run-tests --driver Firefox | - bench --verbose run-tests --driver Firefox | ||||
@@ -7,6 +7,7 @@ globals attached to frappe module | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
from werkzeug.local import Local, release_local | from werkzeug.local import Local, release_local | ||||
from functools import wraps | |||||
import os, importlib, inspect, logging, json | import os, importlib, inspect, logging, json | ||||
# public | # public | ||||
@@ -14,6 +15,7 @@ from frappe.__version__ import __version__ | |||||
from .exceptions import * | from .exceptions import * | ||||
from .utils.jinja import get_jenv, get_template, render_template | from .utils.jinja import get_jenv, get_template, render_template | ||||
local = Local() | local = Local() | ||||
class _dict(dict): | class _dict(dict): | ||||
@@ -1,2 +1,2 @@ | |||||
from __future__ import unicode_literals | 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 frappe.local.request.method=="GET": | ||||
if not doc.has_permission("read"): | if not doc.has_permission("read"): | ||||
frappe.throw(_("Not permitted"), frappe.PermissionError) | 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 frappe.local.request.method=="POST": | ||||
if not doc.has_permission("write"): | if not doc.has_permission("write"): | ||||
frappe.throw(_("Not permitted"), frappe.PermissionError) | 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() | frappe.db.commit() | ||||
else: | else: | ||||
@@ -12,6 +12,8 @@ from werkzeug.local import LocalManager | |||||
from werkzeug.exceptions import HTTPException, NotFound | from werkzeug.exceptions import HTTPException, NotFound | ||||
from werkzeug.contrib.profiler import ProfilerMiddleware | from werkzeug.contrib.profiler import ProfilerMiddleware | ||||
from werkzeug.wsgi import SharedDataMiddleware | from werkzeug.wsgi import SharedDataMiddleware | ||||
from werkzeug.serving import run_with_reloader | |||||
import mimetypes | import mimetypes | ||||
import frappe | import frappe | ||||
@@ -20,9 +22,10 @@ import frappe.auth | |||||
import frappe.api | import frappe.api | ||||
import frappe.utils.response | import frappe.utils.response | ||||
import frappe.website.render | 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 | from frappe.middlewares import StaticDataMiddleware | ||||
local_manager = LocalManager([frappe.local]) | local_manager = LocalManager([frappe.local]) | ||||
_site = None | _site = None | ||||
@@ -30,6 +33,21 @@ _sites_path = os.environ.get("SITES_PATH", ".") | |||||
logger = frappe.get_logger() | 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 | @Request.application | ||||
def application(request): | def application(request): | ||||
frappe.local.request = request | frappe.local.request = request | ||||
@@ -135,6 +153,8 @@ def make_form_dict(request): | |||||
frappe.local.form_dict.pop("_") | frappe.local.form_dict.pop("_") | ||||
application = local_manager.make_middleware(application) | application = local_manager.make_middleware(application) | ||||
application.debug = True | |||||
def serve(port=8000, profile=False, site=None, sites_path='.'): | def serve(port=8000, profile=False, site=None, sites_path='.'): | ||||
global application, _site, _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") | 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, | run_simple('0.0.0.0', int(port), application, use_reloader=True, | ||||
use_debugger=True, use_evalex=True, threaded=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['versions'] = {k: v['version'] for k, v in get_versions().items()} | ||||
bootinfo.error_report_email = frappe.get_hooks("error_report_email") | 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")) | bootinfo.calendars = sorted(frappe.get_hooks("calendars")) | ||||
return bootinfo | return bootinfo | ||||
@@ -127,7 +127,8 @@ def pack(target, sources, no_compress, verbose): | |||||
tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO() | tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO() | ||||
jsm.minify(tmpin, tmpout) | jsm.minify(tmpin, tmpout) | ||||
minified = tmpout.getvalue() | minified = tmpout.getvalue() | ||||
outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';' | |||||
if minified: | |||||
outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';' | |||||
if verbose: | if verbose: | ||||
print "{0}: {1}k".format(f, int(len(minified) / 1024)) | print "{0}: {1}k".format(f, int(len(minified) / 1024)) | ||||
@@ -17,9 +17,10 @@ SITES_PATH = os.environ.get('SITES_PATH', '.') | |||||
# defaults | # defaults | ||||
DEFAULT_CELERY_BROKER = "redis://localhost" | DEFAULT_CELERY_BROKER = "redis://localhost" | ||||
DEFAULT_CELERY_BACKEND = None | |||||
DEFAULT_CELERY_BACKEND = "redis://localhost" | |||||
DEFAULT_SCHEDULER_INTERVAL = 300 | DEFAULT_SCHEDULER_INTERVAL = 300 | ||||
LONGJOBS_PREFIX = "longjobs@" | LONGJOBS_PREFIX = "longjobs@" | ||||
ASYNC_TASKS_PREFIX = "async@" | |||||
_app = None | _app = None | ||||
def get_celery(): | def get_celery(): | ||||
@@ -29,7 +30,7 @@ def get_celery(): | |||||
_app = Celery('frappe', | _app = Celery('frappe', | ||||
broker=conf.celery_broker or DEFAULT_CELERY_BROKER, | 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) | setup_celery(_app, conf) | ||||
@@ -41,9 +42,11 @@ def setup_celery(app, conf): | |||||
app.conf.CELERY_TASK_SERIALIZER = 'json' | app.conf.CELERY_TASK_SERIALIZER = 'json' | ||||
app.conf.CELERY_ACCEPT_CONTENT = ['json'] | app.conf.CELERY_ACCEPT_CONTENT = ['json'] | ||||
app.conf.CELERY_TIMEZONE = 'UTC' | 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: | 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) | app.conf.CELERYBEAT_SCHEDULE = get_beat_schedule(conf) | ||||
@@ -61,6 +64,11 @@ class SiteRouter(object): | |||||
return get_queue(frappe.local.site) | return get_queue(frappe.local.site) | ||||
return None | 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): | def get_queue(site, prefix=None): | ||||
return {'queue': "{}{}".format(prefix or "", site)} | 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: | finally: | ||||
frappe.destroy() | 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.command('add-system-manager') | ||||
@click.argument('email') | @click.argument('email') | ||||
@click.option('--first-name') | @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) | site = get_single_site(context) | ||||
frappe.init(site=site) | frappe.init(site=site) | ||||
if frappe.conf.run_selenium_tests: | |||||
if frappe.conf.run_selenium_tests and False: | |||||
sel.start(context.verbose, driver) | sel.start(context.verbose, driver) | ||||
try: | try: | ||||
@@ -738,6 +748,20 @@ def remove_from_installed_apps(context, app): | |||||
finally: | finally: | ||||
frappe.destroy() | 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): | def move(dest_dir, site): | ||||
import os | import os | ||||
if not os.path.isdir(dest_dir): | if not os.path.isdir(dest_dir): | ||||
@@ -759,6 +783,18 @@ def move(dest_dir, site): | |||||
frappe.destroy() | frappe.destroy() | ||||
return final_new_path | 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.command('drop-site') | ||||
@click.argument('site') | @click.argument('site') | ||||
@click.option('--root-login', default='root') | @click.option('--root-login', default='root') | ||||
@@ -797,6 +833,7 @@ commands = [ | |||||
restore, | restore, | ||||
reinstall, | reinstall, | ||||
install_app, | install_app, | ||||
list_apps, | |||||
add_system_manager, | add_system_manager, | ||||
migrate, | migrate, | ||||
run_patch, | run_patch, | ||||
@@ -836,5 +873,7 @@ commands = [ | |||||
_use, | _use, | ||||
backup, | backup, | ||||
remove_from_installed_apps, | remove_from_installed_apps, | ||||
uninstall, | |||||
drop_site, | 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", | "description": "Keep a track of all communications", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Master", | |||||
"document_type": "Setup", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"default": "COMM-", | "default": "COMM-", | ||||
@@ -198,7 +198,7 @@ | |||||
"idx": 1, | "idx": 1, | ||||
"in_dialog": 0, | "in_dialog": 0, | ||||
"issingle": 0, | "issingle": 0, | ||||
"modified": "2015-07-28 07:28:11.457131", | |||||
"modified": "2015-07-28 17:18:11.664740", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Communication", | "name": "Communication", | ||||
@@ -7,6 +7,7 @@ | |||||
"description": "DocType is a Table / Form in the application.", | "description": "DocType is a Table / Form in the application.", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Document", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "sb0", | "fieldname": "sb0", | ||||
@@ -65,7 +66,7 @@ | |||||
"label": "Document Type", | "label": "Document Type", | ||||
"oldfieldname": "document_type", | "oldfieldname": "document_type", | ||||
"oldfieldtype": "Select", | "oldfieldtype": "Select", | ||||
"options": "\nMaster\nTransaction\nSystem\nOther", | |||||
"options": "\nDocument\nSetup\nSystem\nOther", | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
@@ -108,7 +109,7 @@ | |||||
"oldfieldtype": "Table", | "oldfieldtype": "Table", | ||||
"options": "DocField", | "options": "DocField", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"reqd": 1, | |||||
"reqd": 0, | |||||
"search_index": 0 | "search_index": 0 | ||||
}, | }, | ||||
{ | { | ||||
@@ -337,7 +338,7 @@ | |||||
"idx": 1, | "idx": 1, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"modified": "2015-03-03 10:40:45.768116", | |||||
"modified": "2015-07-28 16:18:11.925264", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "DocType", | "name": "DocType", | ||||
@@ -87,7 +87,7 @@ class DocType(Document): | |||||
if autoname and (not autoname.startswith('field:')) \ | if autoname and (not autoname.startswith('field:')) \ | ||||
and (not autoname.startswith('eval:')) \ | and (not autoname.startswith('eval:')) \ | ||||
and (not autoname in ('Prompt', 'hash')) \ | |||||
and (not autoname.lower() in ('prompt', 'hash')) \ | |||||
and (not autoname.startswith('naming_series:')): | and (not autoname.startswith('naming_series:')): | ||||
prefix = autoname.split('.')[0] | prefix = autoname.split('.')[0] | ||||
@@ -3,7 +3,7 @@ | |||||
"creation": "2014-02-20 17:22:37", | "creation": "2014-02-20 17:22:37", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Master", | |||||
"document_type": "Setup", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "ref_doctype", | "fieldname": "ref_doctype", | ||||
@@ -33,7 +33,7 @@ | |||||
], | ], | ||||
"icon": "icon-copy", | "icon": "icon-copy", | ||||
"idx": 1, | "idx": 1, | ||||
"modified": "2014-08-05 01:23:37.541856", | |||||
"modified": "2015-07-28 16:18:12.706419", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Version", | "name": "Version", | ||||
@@ -5,6 +5,7 @@ from __future__ import unicode_literals | |||||
import frappe, json | import frappe, json | ||||
import frappe.permissions | import frappe.permissions | ||||
import frappe.async | |||||
from frappe import _ | from frappe import _ | ||||
@@ -14,7 +15,7 @@ from frappe.utils.dateutils import parse_date | |||||
from frappe.utils import cint, cstr, flt | from frappe.utils import cint, cstr, flt | ||||
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys | 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, | def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=None, | ||||
ignore_links=False, pre_process=None): | ignore_links=False, pre_process=None): | ||||
"""upload data""" | """upload data""" | ||||
@@ -2,10 +2,18 @@ frappe.provide('frappe.desktop'); | |||||
frappe.pages['desktop'].on_page_load = function(wrapper) { | frappe.pages['desktop'].on_page_load = function(wrapper) { | ||||
// load desktop | // load desktop | ||||
frappe.desktop.set_background(); | |||||
if(!frappe.list_desktop) { | |||||
frappe.desktop.set_background(); | |||||
} | |||||
frappe.desktop.refresh(wrapper); | 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, { | $.extend(frappe.desktop, { | ||||
refresh: function(wrapper) { | refresh: function(wrapper) { | ||||
if (wrapper) { | if (wrapper) { | ||||
@@ -20,7 +28,10 @@ $.extend(frappe.desktop, { | |||||
var me = this; | var me = this; | ||||
frappe.utils.set_title("Desktop"); | 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 | // all visible icons | ||||
desktop_items: this.get_desktop_items(), | desktop_items: this.get_desktop_items(), | ||||
@@ -28,7 +39,7 @@ $.extend(frappe.desktop, { | |||||
user_desktop_items: this.get_user_desktop_items(), | user_desktop_items: this.get_user_desktop_items(), | ||||
})); | })); | ||||
this.setup_icon_click(); | |||||
this.setup_module_click(); | |||||
// notifications | // notifications | ||||
this.show_pending_notifications(); | this.show_pending_notifications(); | ||||
@@ -96,29 +107,40 @@ $.extend(frappe.desktop, { | |||||
return out; | 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 { | } 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() { | make_sortable: function() { | ||||
if (frappe.dom.is_touchscreen()) { | |||||
if (frappe.dom.is_touchscreen() || frappe.list_desktop) { | |||||
return; | return; | ||||
} | } | ||||
@@ -215,10 +237,15 @@ $.extend(frappe.desktop, { | |||||
sum = frappe.boot.notification_info.open_count_module[module]; | sum = frappe.boot.notification_info.open_count_module[module]; | ||||
} | } | ||||
if (frappe.modules[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) { | if(notifier.length) { | ||||
notifier.toggle(sum ? true : false); | 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 %}"> | data-name="{%= name %}" data-link="{%= link %}" title="{%= _label %}"> | ||||
{%= app_icon %} | {%= app_icon %} | ||||
<div class="case-label text-ellipsis"> | <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> | <span class="circle-text"></span> | ||||
</div> | </div> | ||||
<!-- <span id="module-count-{%= _id %}" class="octicon octicon-primitive-dot circle" style="display: None"></span> --> | <!-- <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", | "description": "Note is a free page where users can share documents / notes", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Transaction", | |||||
"document_type": "Document", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "title", | "fieldname": "title", | ||||
@@ -35,7 +35,7 @@ | |||||
], | ], | ||||
"icon": "icon-file-text", | "icon": "icon-file-text", | ||||
"idx": 1, | "idx": 1, | ||||
"modified": "2015-02-06 00:44:06.475116", | |||||
"modified": "2015-07-28 16:18:12.301520", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Desk", | "module": "Desk", | ||||
"name": "Note", | "name": "Note", | ||||
@@ -5,6 +5,26 @@ frappe.listview_settings['ToDo'] = { | |||||
"status": "Open" | "status": "Open" | ||||
}; | }; | ||||
me.page.set_title(__("To Do")); | 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"], | add_fields: ["reference_type", "reference_name"], | ||||
} | } |
@@ -121,7 +121,14 @@ class FormMeta(Meta): | |||||
df.search_fields = map(lambda sf: sf.strip(), search_fields.split(",")) | df.search_fields = map(lambda sf: sf.strip(), search_fields.split(",")) | ||||
def add_linked_with(self): | 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 | links = frappe.db.sql("""select parent, fieldname from tabDocField | ||||
where (fieldtype="Link" and options=%s) | where (fieldtype="Link" and options=%s) | ||||
or (fieldtype="Select" and options=%s)""", (self.name, "link:"+ self.name)) | or (fieldtype="Select" and options=%s)""", (self.name, "link:"+ self.name)) | ||||
@@ -137,15 +144,17 @@ class FormMeta(Meta): | |||||
ret[dt] = { "fieldname": links[dt] } | ret[dt] = { "fieldname": links[dt] } | ||||
if links: | 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" | where fieldtype="Table" | ||||
and options in (select name from tabDocType | and options in (select name from tabDocType | ||||
where istable=1 and name in (%s))""" % ", ".join(["%s"] * len(links)) ,tuple(links)): | 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: | if options in ret: | ||||
del ret[options] | del ret[options] | ||||
# find links of parents | |||||
links = frappe.db.sql("""select dt from `tabCustom Field` | links = frappe.db.sql("""select dt from `tabCustom Field` | ||||
where (fieldtype="Table" and options=%s)""", (self.name)) | where (fieldtype="Table" and options=%s)""", (self.name)) | ||||
links += frappe.db.sql("""select parent from tabDocField | links += frappe.db.sql("""select parent from tabDocField | ||||
@@ -59,10 +59,10 @@ def build_standard_config(module, doctype_info): | |||||
data = [] | data = [] | ||||
add_section(data, _("Documents"), "icon-star", | 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", | 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", | add_section(data, _("Standard Reports"), "icon-list", | ||||
get_report_list(module, is_standard="Yes")) | 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): | def add_custom_doctypes(data, doctype_info): | ||||
"""Adds Custom DocTypes to modules setup via `config/desktop.py`.""" | """Adds Custom DocTypes to modules setup via `config/desktop.py`.""" | ||||
add_section(data, _("Documents"), "icon-star", | 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", | 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): | def get_doctype_info(module): | ||||
"""Returns list of non child DocTypes for given module.""" | """Returns list of non child DocTypes for given module.""" | ||||
@@ -37,7 +37,6 @@ frappe.desk.pages.Messages = Class.extend({ | |||||
make: function() { | make: function() { | ||||
this.make_sidebar(); | this.make_sidebar(); | ||||
this.set_next_refresh(); | |||||
}, | }, | ||||
make_sidebar: function() { | make_sidebar: function() { | ||||
@@ -135,6 +134,9 @@ frappe.desk.pages.Messages = Class.extend({ | |||||
hide_refresh: true, | hide_refresh: true, | ||||
freeze: false, | freeze: false, | ||||
render_row: function(wrapper, data) { | 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", { | var row = $(frappe.render_template("messages_row", { | ||||
data: data | data: data | ||||
})).appendTo(wrapper); | })).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() { | get_contact: function() { | ||||
var route = location.hash; | var route = location.hash; | ||||
@@ -195,5 +173,3 @@ frappe.desk.pages.Messages = Class.extend({ | |||||
}); | }); | ||||
@@ -6,10 +6,11 @@ | |||||
<div class="media"> | <div class="media"> | ||||
<div class="pull-left hidden-xs"> | <div class="pull-left hidden-xs"> | ||||
<span class="avatar avatar-small" title="{%= frappe.user.full_name(data.owner) %} "> | <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> | </span> | ||||
</div> | </div> | ||||
<div class="media-body"> | |||||
<div class="media-body {{ data.is_system_message ? "text-muted" : "" }}"> | |||||
{%= data.comment %} | {%= data.comment %} | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -7,7 +7,7 @@ | |||||
"custom": 0, | "custom": 0, | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Master", | |||||
"document_type": "Setup", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "email_settings", | "fieldname": "email_settings", | ||||
@@ -429,7 +429,7 @@ | |||||
"is_submittable": 0, | "is_submittable": 0, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"modified": "2015-07-16 10:11:06.466258", | |||||
"modified": "2015-07-28 16:18:12.116327", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Email", | "module": "Email", | ||||
"name": "Email Account", | "name": "Email Account", | ||||
@@ -4,7 +4,7 @@ | |||||
"creation": "2014-06-19 05:20:26.331041", | "creation": "2014-06-19 05:20:26.331041", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Transaction", | |||||
"document_type": "Document", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "subject", | "fieldname": "subject", | ||||
@@ -33,18 +33,13 @@ | |||||
} | } | ||||
], | ], | ||||
"icon": "icon-comment", | "icon": "icon-comment", | ||||
"modified": "2015-02-05 05:11:46.922356", | |||||
"modified": "2015-07-28 16:18:12.432775", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Email", | "module": "Email", | ||||
"name": "Standard Reply", | "name": "Standard Reply", | ||||
"name_case": "", | "name_case": "", | ||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [ | "permissions": [ | ||||
{ | |||||
"permlevel": 0, | |||||
"read": 1, | |||||
"role": "All" | |||||
}, | |||||
{ | { | ||||
"apply_user_permissions": 1, | "apply_user_permissions": 1, | ||||
"create": 1, | "create": 1, | ||||
@@ -911,6 +911,7 @@ | |||||
}, | }, | ||||
"Ghana": { | "Ghana": { | ||||
"code": "gh", | "code": "gh", | ||||
"currency": "GHS", | |||||
"currency_fraction": "Pesewa", | "currency_fraction": "Pesewa", | ||||
"currency_fraction_units": 100, | "currency_fraction_units": 100, | ||||
"currency_symbol": "\u20b5", | "currency_symbol": "\u20b5", | ||||
@@ -5,7 +5,7 @@ | |||||
"creation": "2013-01-19 10:23:30", | "creation": "2013-01-19 10:23:30", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Master", | |||||
"document_type": "Setup", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "country_name", | "fieldname": "country_name", | ||||
@@ -42,7 +42,7 @@ | |||||
"icon": "icon-globe", | "icon": "icon-globe", | ||||
"idx": 1, | "idx": 1, | ||||
"in_create": 0, | "in_create": 0, | ||||
"modified": "2015-02-05 05:11:36.234753", | |||||
"modified": "2015-07-28 16:18:11.855617", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Geo", | "module": "Geo", | ||||
"name": "Country", | "name": "Country", | ||||
@@ -5,6 +5,7 @@ from __future__ import unicode_literals | |||||
import frappe | import frappe | ||||
from frappe import _ | from frappe import _ | ||||
import frappe.utils | import frappe.utils | ||||
import frappe.async | |||||
import frappe.sessions | import frappe.sessions | ||||
import frappe.utils.file_manager | import frappe.utils.file_manager | ||||
import frappe.desk.form.run_method | import frappe.desk.form.run_method | ||||
@@ -18,6 +19,10 @@ def version(): | |||||
def ping(): | def ping(): | ||||
return "pong" | return "pong" | ||||
@frappe.async.handler | |||||
def async_ping(): | |||||
return "pong" | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None): | 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) | 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") | return build_response("json") | ||||
def execute_cmd(cmd): | |||||
def execute_cmd(cmd, async=False): | |||||
"""execute a request as python module""" | """execute a request as python module""" | ||||
for hook in frappe.get_hooks("override_whitelisted_methods", {}).get(cmd, []): | for hook in frappe.get_hooks("override_whitelisted_methods", {}).get(cmd, []): | ||||
# override using the first hook | # override using the first hook | ||||
@@ -78,6 +83,8 @@ def execute_cmd(cmd): | |||||
break | break | ||||
method = get_attr(cmd) | method = get_attr(cmd) | ||||
if async: | |||||
method = method.queue | |||||
# check if whitelisted | # check if whitelisted | ||||
if frappe.session['user'] == 'Guest': | if frappe.session['user'] == 'Guest': | ||||
@@ -103,3 +110,15 @@ def get_attr(cmd): | |||||
method = globals()[cmd] | method = globals()[cmd] | ||||
frappe.log("method:" + cmd) | frappe.log("method:" + cmd) | ||||
return method | 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_icon = "octicon octicon-circuit-board" | ||||
app_version = "5.4.2" | |||||
app_version = "6.0.0" | |||||
app_color = "orange" | app_color = "orange" | ||||
github_link = "https://github.com/frappe/frappe" | github_link = "https://github.com/frappe/frappe" | ||||
@@ -131,6 +131,9 @@ doc_events = { | |||||
"frappe.email.doctype.email_alert.email_alert.trigger_email_alerts" | "frappe.email.doctype.email_alert.email_alert.trigger_email_alerts" | ||||
], | ], | ||||
"on_trash": "frappe.desk.notifications.clear_doctype_notifications" | "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.desk.doctype.event.event.send_event_digest", | ||||
"frappe.sessions.clear_expired_sessions", | "frappe.sessions.clear_expired_sessions", | ||||
"frappe.email.doctype.email_alert.email_alert.trigger_daily_alerts", | "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: | if frappe.flags.in_install: | ||||
post_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): | def post_install(rebuild_website=False): | ||||
if rebuild_website: | if rebuild_website: | ||||
render.clear_cache() | 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): | def make_site_config(db_name=None, db_password=None, site_config=None): | ||||
frappe.create_folder(os.path.join(frappe.local.site_path)) | 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 os.path.exists(site_file): | ||||
if not (site_config and isinstance(site_config, dict)): | 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: | with open(site_file, "w") as f: | ||||
f.write(json.dumps(site_config, indent=1, sort_keys=True)) | 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): | def get_conf_params(db_name=None, db_password=None): | ||||
if not db_name: | if not db_name: | ||||
db_name = raw_input("Database 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') | site_private_path = os.path.join(frappe.local.site_path, 'private') | ||||
for dir_path in ( | for dir_path in ( | ||||
os.path.join(site_private_path, 'backups'), | 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): | if not os.path.exists(dir_path): | ||||
os.makedirs(dir_path) | os.makedirs(dir_path) | ||||
locks_dir = frappe.get_site_path('locks') | locks_dir = frappe.get_site_path('locks') | ||||
@@ -227,7 +286,7 @@ def add_module_defs(app): | |||||
d = frappe.new_doc("Module Def") | d = frappe.new_doc("Module Def") | ||||
d.app_name = app | d.app_name = app | ||||
d.module_name = module | d.module_name = module | ||||
d.save() | |||||
d.save(ignore_permissions=True) | |||||
def remove_missing_apps(): | def remove_missing_apps(): | ||||
apps = ('frappe_subscription', 'shopping_cart') | apps = ('frappe_subscription', 'shopping_cart') | ||||
@@ -184,7 +184,7 @@ class BaseDocument(object): | |||||
elif df.fieldtype in ("Datetime", "Date") and d[fieldname]=="": | elif df.fieldtype in ("Datetime", "Date") and d[fieldname]=="": | ||||
d[fieldname] = None | d[fieldname] = None | ||||
elif df.get("unique") and cstr(d[fieldname]).strip()=="": | elif df.get("unique") and cstr(d[fieldname]).strip()=="": | ||||
# unique empty field should be set to None | # unique empty field should be set to None | ||||
d[fieldname] = None | d[fieldname] = None | ||||
@@ -270,11 +270,11 @@ class BaseDocument(object): | |||||
self.name = None | self.name = None | ||||
self.db_insert() | self.db_insert() | ||||
return | return | ||||
type, value, traceback = sys.exc_info() | type, value, traceback = sys.exc_info() | ||||
frappe.msgprint(_("Duplicate name {0} {1}").format(self.doctype, self.name)) | frappe.msgprint(_("Duplicate name {0} {1}").format(self.doctype, self.name)) | ||||
raise frappe.DuplicateEntryError, (self.doctype, self.name, e), traceback | raise frappe.DuplicateEntryError, (self.doctype, self.name, e), traceback | ||||
elif "Duplicate" in cstr(e.args[1]): | elif "Duplicate" in cstr(e.args[1]): | ||||
# unique constraint | # unique constraint | ||||
self.show_unique_validation_message(e) | self.show_unique_validation_message(e) | ||||
@@ -303,7 +303,7 @@ class BaseDocument(object): | |||||
self.show_unique_validation_message(e) | self.show_unique_validation_message(e) | ||||
else: | else: | ||||
raise | raise | ||||
def show_unique_validation_message(self, e): | def show_unique_validation_message(self, e): | ||||
type, value, traceback = sys.exc_info() | type, value, traceback = sys.exc_info() | ||||
fieldname = str(e).split("'")[-2] | fieldname = str(e).split("'")[-2] | ||||
@@ -238,7 +238,7 @@ class DatabaseQuery(object): | |||||
elif f[2] == "like" or (isinstance(f[3], basestring) and | elif f[2] == "like" or (isinstance(f[3], basestring) and | ||||
(not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])): | (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 | # because "like" uses backslash (\) for escaping | ||||
f[3] = f[3].replace("\\", "\\\\") | 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'") | execute:frappe.db.sql("delete from `tabProperty Setter` where `property` = 'idx'") | ||||
frappe.patches.v5_2.change_checks_to_not_null | frappe.patches.v5_2.change_checks_to_not_null | ||||
frappe.patches.v5_3.rename_chinese_languages | 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/moment/moment.min.js", | ||||
"public/js/lib/highlight.pack.js", | "public/js/lib/highlight.pack.js", | ||||
"public/js/frappe/class.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": [ | "js/editor.min.js": [ | ||||
"public/js/lib/jquery/jquery.hotkeys.js", | "public/js/lib/jquery/jquery.hotkeys.js", | ||||
@@ -49,6 +50,7 @@ | |||||
"public/js/lib/nprogress.js", | "public/js/lib/nprogress.js", | ||||
"public/js/lib/moment/moment-with-locales.min.js", | "public/js/lib/moment/moment-with-locales.min.js", | ||||
"public/js/lib/moment/moment-timezone-with-data.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/provide.js", | ||||
"public/js/frappe/class.js", | "public/js/frappe/class.js", | ||||
@@ -61,6 +63,7 @@ | |||||
"public/js/frappe/ui/messages.js", | "public/js/frappe/ui/messages.js", | ||||
"public/js/frappe/request.js", | "public/js/frappe/request.js", | ||||
"public/js/frappe/socket.js", | |||||
"public/js/frappe/router.js", | "public/js/frappe/router.js", | ||||
"public/js/frappe/defaults.js", | "public/js/frappe/defaults.js", | ||||
"public/js/lib/microtemplate.js", | "public/js/lib/microtemplate.js", | ||||
@@ -215,3 +215,7 @@ a.no-decoration:active { | |||||
text-align: center; | text-align: center; | ||||
} | } | ||||
} | } | ||||
.grayscale { | |||||
-webkit-filter: grayscale(100%); | |||||
filter: grayscale(100%); | |||||
} |
@@ -215,6 +215,10 @@ a.no-decoration:active { | |||||
text-align: center; | text-align: center; | ||||
} | } | ||||
} | } | ||||
.grayscale { | |||||
-webkit-filter: grayscale(100%); | |||||
filter: grayscale(100%); | |||||
} | |||||
.nav-pills a, | .nav-pills a, | ||||
.nav-pills a:hover { | .nav-pills a:hover { | ||||
border-bottom: none; | border-bottom: none; | ||||
@@ -161,3 +161,15 @@ body[data-route="desktop"] .navbar-default { | |||||
margin-top: 3px; | margin-top: 3px; | ||||
margin-bottom: 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; | -moz-appearance: none; | ||||
appearance: none; | appearance: none; | ||||
} | } | ||||
.form-control.bold { | |||||
font-weight: bold; | |||||
background-color: #fffce7; | |||||
} | |||||
.form-headline .alert { | .form-headline .alert { | ||||
font-size: 12px; | font-size: 12px; | ||||
border-color: #d1d8dd; | border-color: #d1d8dd; | ||||
@@ -166,6 +166,9 @@ body { | |||||
.timeline .timeline-item:last-child { | .timeline .timeline-item:last-child { | ||||
border-bottom: none; | border-bottom: none; | ||||
} | } | ||||
.list-row { | |||||
padding: 13px 15px !important; | |||||
} | |||||
.doclist-row { | .doclist-row { | ||||
position: relative; | position: relative; | ||||
padding-right: 10px; | padding-right: 10px; | ||||
@@ -192,6 +195,15 @@ body { | |||||
.doclist-row .list-row-right { | .doclist-row .list-row-right { | ||||
float: 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 { | .doclist-row .list-row-right.no-right-column { | ||||
position: absolute; | position: absolute; | ||||
top: 0; | top: 0; | ||||
@@ -199,15 +211,6 @@ body { | |||||
left: -10px; | left: -10px; | ||||
width: 100%; | 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 { | body[data-route^="messages"] .navbar-center { | ||||
display: block !important; | display: block !important; | ||||
position: absolute; | position: absolute; | ||||
@@ -258,6 +261,26 @@ body { | |||||
} | } | ||||
} | } | ||||
@media (max-width: 991px) { | @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, | .intro-area, | ||||
.footnote-area { | .footnote-area { | ||||
padding: 15px 0px; | padding: 15px 0px; | ||||
@@ -269,7 +292,7 @@ body { | |||||
position: relative; | position: relative; | ||||
} | } | ||||
.page-head .page-title h1 { | .page-head .page-title h1 { | ||||
font-size: 18px; | |||||
font-size: 22px; | |||||
margin-top: 22px; | margin-top: 22px; | ||||
} | } | ||||
body[data-route^="Form"] .page-title h1 { | body[data-route^="Form"] .page-title h1 { | ||||
@@ -304,6 +327,9 @@ body { | |||||
.module-item { | .module-item { | ||||
padding: 7px 0px !important; | padding: 7px 0px !important; | ||||
} | } | ||||
.module-item h4 { | |||||
font-weight: normal; | |||||
} | |||||
#navbar-breadcrumbs { | #navbar-breadcrumbs { | ||||
margin: 0px; | margin: 0px; | ||||
display: inline-block; | display: inline-block; | ||||
@@ -215,6 +215,10 @@ a.no-decoration:active { | |||||
text-align: center; | text-align: center; | ||||
} | } | ||||
} | } | ||||
.grayscale { | |||||
-webkit-filter: grayscale(100%); | |||||
filter: grayscale(100%); | |||||
} | |||||
html { | html { | ||||
min-height: 100%; | min-height: 100%; | ||||
} | } | ||||
@@ -126,10 +126,6 @@ frappe.Application = Class.extend({ | |||||
if(frappe.get_route()[0] != "messages") { | if(frappe.get_route()[0] != "messages") { | ||||
if(r.message.new_messages.length) { | 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 + ")"); | 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); | </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() { | set_input_areas: function() { | ||||
if(this.only_input) { | if(this.only_input) { | ||||
this.input_area = this.wrapper; | 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.input = this.$input.get(0); | ||||
this.set_input_attributes(); | this.set_input_attributes(); | ||||
this.has_input = true; | this.has_input = true; | ||||
this.$wrapper.find(".control-label").addClass("hide"); | |||||
this.toggle_label(false); | |||||
}, | }, | ||||
onclick: function() { | onclick: function() { | ||||
if(this.frm && this.frm.doc) { | if(this.frm && this.frm.doc) { | ||||
@@ -42,7 +42,7 @@ frappe.ui.form.Dashboard = Class.extend({ | |||||
var badge = $(repl('<div class="col-md-4">\ | var badge = $(repl('<div class="col-md-4">\ | ||||
<div class="alert-badge">\ | <div class="alert-badge">\ | ||||
<a class="badge-link grey">%(label)s</a>\ | <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]})) | </div></div>', {label:label, icon: frappe.boot.doctype_icons[doctype]})) | ||||
.appendTo(this.body) | .appendTo(this.body) | ||||
@@ -92,6 +92,7 @@ frappe.ui.form.AssignTo = Class.extend({ | |||||
me.dialog = new frappe.ui.Dialog({ | me.dialog = new frappe.ui.Dialog({ | ||||
title: __('Add to To Do'), | title: __('Add to To Do'), | ||||
fields: [ | fields: [ | ||||
{fieldtype:'Check', fieldname:'myself', label:__("Assign to me"), "default":0}, | |||||
{fieldtype:'Link', fieldname:'assign_to', options:'User', | {fieldtype:'Link', fieldname:'assign_to', options:'User', | ||||
label:__("Assign To"), | label:__("Assign To"), | ||||
description:__("Add to To Do List Of"), reqd:true}, | description:__("Add to To Do List Of"), reqd:true}, | ||||
@@ -115,6 +116,16 @@ frappe.ui.form.AssignTo = Class.extend({ | |||||
} | } | ||||
me.dialog.show(); | 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() { | add_assignment: function() { | ||||
var me = this; | var me = this; | ||||
@@ -227,9 +227,14 @@ frappe.ui.form.Comments = Class.extend({ | |||||
btn: btn, | btn: btn, | ||||
callback: function(r) { | callback: function(r) { | ||||
if(!r.exc) { | 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.frm.get_docinfo().comments = | ||||
me.get_comments().concat([r.message]); | me.get_comments().concat([r.message]); | ||||
me.input.val(""); | |||||
me.refresh(true); | me.refresh(true); | ||||
} | } | ||||
} | } | ||||
@@ -44,24 +44,20 @@ frappe.ui.form.Layout = Class.extend({ | |||||
$(this.frm.wrapper).trigger("refresh-fields"); | $(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 | // dependent fields | ||||
this.refresh_dependency(); | this.refresh_dependency(); | ||||
// refresh sections | // refresh sections | ||||
this.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) { | attach_doc_and_docfields: function(refresh) { | ||||
var me = this; | var me = this; | ||||
for(var i=0, l=this.fields_list.length; i<l; i++) { | 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.section = null; | ||||
this.column = 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(); | this.make_section(); | ||||
} | } | ||||
$.each(this.fields, function(i, df) { | $.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 freeze_message = working_label ? __(working_label) : ""; | ||||
var save = function() { | 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() { | 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 doc = frm.doc; | ||||
var meta = locals.DocType[doc.doctype]; | var meta = locals.DocType[doc.doctype]; | ||||
if(doc.__islocal && (meta && meta.autoname | if(doc.__islocal && (meta && meta.autoname | ||||
&& meta.autoname.toLowerCase()=='prompt')) { | && 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); | $(btn).prop("disabled", false); | ||||
throw "name required"; | |||||
} | } | ||||
} else { | |||||
callback(); | |||||
} | } | ||||
}; | }; | ||||
@@ -220,6 +220,9 @@ frappe.ui.form.Toolbar = Class.extend({ | |||||
} else { | } else { | ||||
var click = { | var click = { | ||||
"Save": function() { | "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); | me.frm.save('Save', null, this); | ||||
}, | }, | ||||
"Submit": function() { | "Submit": function() { | ||||
@@ -216,6 +216,11 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
refresh: function() { | refresh: function() { | ||||
var me = this; | var me = this; | ||||
this.init_stats(); | this.init_stats(); | ||||
if(this.listview.settings.refresh) { | |||||
this.listview.settings.refresh(this); | |||||
} | |||||
if(frappe.route_options) { | if(frappe.route_options) { | ||||
me.set_route_options(); | me.set_route_options(); | ||||
} else if(me.dirty) { | } else if(me.dirty) { | ||||
@@ -1,5 +1,5 @@ | |||||
<div class="row doclist-row {% if (data._checkbox) { %} has-checkbox {% } %}"> | <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) { %} | {% if (list.meta.title_field) { %} | ||||
col-sm-8 | col-sm-8 | ||||
{% } else { %} | {% } else { %} | ||||
@@ -13,8 +13,7 @@ | |||||
{% if (list.meta.title_field) { | {% if (list.meta.title_field) { | ||||
var is_different = data.name !== data[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) { %} | {% if (is_different) { %} | ||||
<a class="text-muted list-value" href="#Form/{%= data._doctype_encoded %}/{%= data._name_encoded %}"> | <a class="text-muted list-value" href="#Form/{%= data._doctype_encoded %}/{%= data._name_encoded %}"> | ||||
{%= data.name %}</a> | {%= data.name %}</a> | ||||
@@ -24,7 +23,6 @@ | |||||
<!-- comment --> | <!-- comment --> | ||||
<div class="list-col col-sm-2 col-xs-2 | <div class="list-col col-sm-2 col-xs-2 | ||||
{% if(!right_column) { %} no-right-column {% } %} | |||||
text-right list-row-right"> | text-right list-row-right"> | ||||
<div class="visible-xs pull-right list-row-indicator">{%= list.get_indicator_dot(data) %}</div> | <div class="visible-xs pull-right list-row-indicator">{%= list.get_indicator_dot(data) %}</div> | ||||
<div class="hidden-xs pull-right"> | <div class="hidden-xs pull-right"> | ||||
@@ -1,6 +1,6 @@ | |||||
<div class="list-row list-row-head"> | <div class="list-row list-row-head"> | ||||
<div class="row doclist-row"> | <div class="row doclist-row"> | ||||
<div class="col-xs-12 | |||||
<div class="col-xs-10 | |||||
{% if (list.meta.title_field) { %} | {% if (list.meta.title_field) { %} | ||||
col-sm-8 | col-sm-8 | ||||
{% } else { %} | {% } else { %} | ||||
@@ -6,7 +6,9 @@ | |||||
<li><a href="#Report/{%= doctype %}">{%= __("Report") %}</a></li> | <li><a href="#Report/{%= doctype %}">{%= __("Report") %}</a></li> | ||||
<li class="hide calendar-link"><a href="#Calendar/{%= doctype %}">{%= __("Calendar") %}</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 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)) { %} | {% if(frappe.help.has_help(doctype)) { %} | ||||
<li><a class="help-link" data-doctype="{{ doctype }}">{{ __("Help") }}</a></li> | <li><a class="help-link" data-doctype="{{ doctype }}">{{ __("Help") }}</a></li> | ||||
{% } %} | {% } %} | ||||
@@ -12,8 +12,8 @@ frappe.provide('frappe.views'); | |||||
frappe.views.ListSidebar = Class.extend({ | frappe.views.ListSidebar = Class.extend({ | ||||
init: function(opts) { | init: function(opts) { | ||||
$.extend(this, opts); | $.extend(this, opts); | ||||
this.get_stats(); | |||||
this.make(); | this.make(); | ||||
this.get_stats(); | |||||
}, | }, | ||||
make: function() { | make: function() { | ||||
var sidebar_content = frappe.render_template("list_sidebar", {doctype: this.doclistview.doctype}); | 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.sidebar = this.page_sidebar.add(this.offcanvas_list_sidebar); | ||||
this.setup_assigned_to_me(); | |||||
if(frappe.views.calendar[this.doctype]) { | if(frappe.views.calendar[this.doctype]) { | ||||
this.sidebar.find(".calendar-link, .gantt-link").removeClass("hide"); | 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() { | get_stats: function() { | ||||
var me = this | var me = this | ||||
return frappe.call({ | return frappe.call({ | ||||
@@ -62,7 +62,7 @@ frappe.get_gravatar = function(email_id) { | |||||
frappe.ui.set_user_background = function(src, selector, style) { | frappe.ui.set_user_background = function(src, selector, style) { | ||||
if(!selector) selector = "#page-desktop"; | if(!selector) selector = "#page-desktop"; | ||||
if(!style) style = "Fill Screen"; | 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 { \ | frappe.dom.set_style(repl('%(selector)s { \ | ||||
background: url("%(src)s") center center;\ | background: url("%(src)s") center center;\ | ||||
@@ -28,10 +28,21 @@ frappe.call = function(opts) { | |||||
args.cmd = opts.method; | 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({ | return frappe.request.call({ | ||||
type: opts.type || "POST", | type: opts.type || "POST", | ||||
args: args, | args: args, | ||||
success: opts.callback, | |||||
success: callback, | |||||
error: opts.error, | error: opts.error, | ||||
always: opts.always, | always: opts.always, | ||||
btn: opts.btn, | 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; | me.display = true; | ||||
cur_dialog = me; | cur_dialog = me; | ||||
frappe.ui.open_dialogs.push(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(); | 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() { | get_primary_btn: function() { | ||||
return this.$wrapper.find(".modal-header .btn-primary"); | 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) { | 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]; | if(!$.isArray(fields)) fields = [fields]; | ||||
var d = new frappe.ui.Dialog({ | var d = new frappe.ui.Dialog({ | ||||
fields: fields, | fields: fields, | ||||
@@ -232,6 +232,21 @@ frappe.ui.Page = Class.extend({ | |||||
.on("click", action).appendTo(this.inner_toolbar.removeClass("hide")) | .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() { | clear_user_actions: function() { | ||||
@@ -33,8 +33,8 @@ frappe.ui.misc.about = function() { | |||||
var $wrap = $("#about-app-versions").empty(); | var $wrap = $("#about-app-versions").empty(); | ||||
$.each(keys(versions).sort(), function(i, key) { | $.each(keys(versions).sort(), function(i, key) { | ||||
var v = versions[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; | frappe.versions = versions; | ||||
@@ -126,7 +126,6 @@ frappe.ui.toolbar.update_notifications = function() { | |||||
frappe.views.show_open_count_list(this); | frappe.views.show_open_count_list(this); | ||||
} | } | ||||
} | } | ||||
return false; | |||||
}); | }); | ||||
$(".navbar-new-comments") | $(".navbar-new-comments") | ||||
@@ -93,6 +93,11 @@ frappe.upload = { | |||||
opts.callback(attachment, r); | opts.callback(attachment, r); | ||||
$(document).trigger("upload_complete", attachment); | $(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 | btn: opts.btn | ||||
}); | }); | ||||
} | } | ||||
@@ -130,11 +130,25 @@ frappe.views.CommunicationComposer = Class.extend({ | |||||
this.dialog.get_input("standard_reply").on("change", function() { | this.dialog.get_input("standard_reply").on("change", function() { | ||||
var standard_reply = $(this).val(); | var standard_reply = $(this).val(); | ||||
var prepend_reply = function() { | var prepend_reply = function() { | ||||
if(me.reply_added===standard_reply) { | |||||
return; | |||||
} | |||||
var content_field = me.dialog.fields_dict.content; | var content_field = me.dialog.fields_dict.content; | ||||
var content = content_field.get_value() || ""; | 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]) { | if(frappe.standard_replies[standard_reply]) { | ||||
prepend_reply(); | prepend_reply(); | ||||
@@ -369,7 +383,7 @@ frappe.views.CommunicationComposer = Class.extend({ | |||||
if(this.real_name) { | if(this.real_name) { | ||||
this.message = '<p>'+__('Dear') +' ' | this.message = '<p>'+__('Dear') +' ' | ||||
+ this.real_name + ",</p>" + (this.message || ""); | |||||
+ this.real_name + ",</p><!-- salutation-ends --><br>" + (this.message || ""); | |||||
} | } | ||||
var reply = (this.message || "") | var reply = (this.message || "") | ||||
@@ -7,7 +7,7 @@ | |||||
{% if (item.type==="doctype") { %} | {% if (item.type==="doctype") { %} | ||||
<span class="open-notification hide" data-doctype="{%= item.name %}"></span> | <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> | ||||
<div class="col-xs-4 text-muted text-right small" style="padding-top: 5px;"> | <div class="col-xs-4 text-muted text-right small" style="padding-top: 5px;"> | ||||
{% if (item.last_modified) { %} | {% if (item.last_modified) { %} | ||||
@@ -97,9 +97,9 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||||
parent: this.page.main, | parent: this.page.main, | ||||
start: 0, | start: 0, | ||||
show_filters: true, | show_filters: true, | ||||
new_doctype: this.doctype, | |||||
allow_delete: true, | allow_delete: true, | ||||
}); | }); | ||||
this.make_new_and_refresh(); | |||||
this.make_delete(); | this.make_delete(); | ||||
this.make_column_picker(); | this.make_column_picker(); | ||||
this.make_sorter(); | this.make_sorter(); | ||||
@@ -109,8 +109,16 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||||
this.make_save(); | this.make_save(); | ||||
this.make_user_permissions(); | this.make_user_permissions(); | ||||
this.set_tag_and_status_filter(); | 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); | }, true); | ||||
}, | }, | ||||
@@ -244,7 +252,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||||
if(docfield.fieldtype==="Link" && docfield.fieldname!=="name") { | if(docfield.fieldtype==="Link" && docfield.fieldname!=="name") { | ||||
docfield.link_onclick = | 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}); | {fieldname:docfield.fieldname, value:value}); | ||||
} | } | ||||
return frappe.format(value, docfield, {for_print: for_print}, dataContext); | return frappe.format(value, docfield, {for_print: for_print}, dataContext); | ||||
@@ -273,7 +273,7 @@ _f.Frm.prototype.rename_notify = function(dt, old, name) { | |||||
return; | return; | ||||
// cleanup | // cleanup | ||||
if(this && this.opendocs[old]) { | |||||
if(this && this.opendocs[old] && frappe.meta.docfield_copy[dt]) { | |||||
// delete docfield copy | // delete docfield copy | ||||
frappe.meta.docfield_copy[dt][name] = frappe.meta.docfield_copy[dt][old]; | frappe.meta.docfield_copy[dt][name] = frappe.meta.docfield_copy[dt][old]; | ||||
delete 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') | // load the record for the first time, if not loaded (call 'onload') | ||||
cur_frm.cscript.is_onload = false; | cur_frm.cscript.is_onload = false; | ||||
if(!this.opendocs[this.docname]) { | if(!this.opendocs[this.docname]) { | ||||
var me = this; | |||||
cur_frm.cscript.is_onload = true; | cur_frm.cscript.is_onload = true; | ||||
this.setnewdoc(); | this.setnewdoc(); | ||||
$(document).trigger("form-load", [this]); | |||||
$(this.page.wrapper).on('hide', function(e) { | |||||
$(document).trigger("form-unload", [me]); | |||||
}) | |||||
} else { | } else { | ||||
this.render_form(is_a_different_doc); | this.render_form(is_a_different_doc); | ||||
} | } | ||||
@@ -442,13 +447,13 @@ _f.Frm.prototype.render_form = function(is_a_different_doc) { | |||||
first.focus(); | first.focus(); | ||||
} | } | ||||
} | } | ||||
} else { | } else { | ||||
this.refresh_header(is_a_different_doc); | this.refresh_header(is_a_different_doc); | ||||
} | } | ||||
$(cur_frm.wrapper).trigger('render_complete'); | $(cur_frm.wrapper).trigger('render_complete'); | ||||
this.layout.show_empty_form_message(); | |||||
} | } | ||||
_f.Frm.prototype.refresh_field = function(fname) { | _f.Frm.prototype.refresh_field = function(fname) { | ||||
@@ -232,3 +232,8 @@ a.no-decoration& { | |||||
text-align: center; | 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; | 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; | appearance: none; | ||||
} | } | ||||
.form-control.bold { | |||||
font-weight: bold; | |||||
background-color: @light-yellow; | |||||
} | |||||
.form-headline .alert { | .form-headline .alert { | ||||
font-size: @text-medium; | font-size: @text-medium; | ||||
border-color: @border-color; | border-color: @border-color; | ||||
@@ -157,6 +157,10 @@ | |||||
} | } | ||||
// listviews | // listviews | ||||
.list-row { | |||||
padding: 13px 15px !important; | |||||
} | |||||
.doclist-row& { | .doclist-row& { | ||||
position: relative; | position: relative; | ||||
padding-right: 10px; | padding-right: 10px; | ||||
@@ -187,6 +191,19 @@ | |||||
.list-row-right { | .list-row-right { | ||||
float: 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 { | .list-row-right.no-right-column { | ||||
@@ -196,17 +213,6 @@ | |||||
left: -10px; | left: -10px; | ||||
width: 100%; | 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) { | @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, | .intro-area, | ||||
.footnote-area { | .footnote-area { | ||||
padding: 15px 0px; | padding: 15px 0px; | ||||
@@ -288,7 +315,7 @@ | |||||
.page-head { | .page-head { | ||||
.page-title h1 { | .page-title h1 { | ||||
font-size: 18px; | |||||
font-size: 22px; | |||||
margin-top: 22px; | margin-top: 22px; | ||||
} | } | ||||
} | } | ||||
@@ -334,6 +361,10 @@ | |||||
.module-item { | .module-item { | ||||
padding: 7px 0px !important; | padding: 7px 0px !important; | ||||
h4 { | |||||
font-weight: normal; | |||||
} | |||||
} | } | ||||
#navbar-breadcrumbs { | #navbar-breadcrumbs { | ||||
@@ -32,3 +32,5 @@ | |||||
@label-info-bg: #E8DDFF; | @label-info-bg: #E8DDFF; | ||||
@label-warning-bg: #FFE6BF; | @label-warning-bg: #FFE6BF; | ||||
@label-danger-bg: #FFDCDC; | @label-danger-bg: #FFDCDC; | ||||
@checkbox-color: #3b99fc; |
@@ -17,6 +17,7 @@ import frappe.defaults | |||||
import frappe.translate | import frappe.translate | ||||
from frappe.utils.change_log import get_change_log | from frappe.utils.change_log import get_change_log | ||||
import redis | import redis | ||||
import os | |||||
from urllib import unquote | from urllib import unquote | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
@@ -124,6 +125,8 @@ def get(): | |||||
frappe.get_attr(hook)(bootinfo=bootinfo) | frappe.get_attr(hook)(bootinfo=bootinfo) | ||||
bootinfo["lang"] = frappe.translate.get_user_lang() | 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 | return bootinfo | ||||
class Session: | class Session: | ||||
@@ -163,7 +166,7 @@ class Session: | |||||
"full_name": self.full_name, | "full_name": self.full_name, | ||||
"user_type": self.user_type, | "user_type": self.user_type, | ||||
"device": self.device, | "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 | # insert session | ||||
@@ -4,9 +4,13 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
from frappe.utils.scheduler import enqueue_events | 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 import get_sites | ||||
from frappe.utils.file_lock import create_lock, delete_lock | 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 time | ||||
import MySQLdb | import MySQLdb | ||||
@@ -14,7 +18,7 @@ import MySQLdb | |||||
def sync_queues(): | def sync_queues(): | ||||
"""notifies workers to monitor newly added sites""" | """notifies workers to monitor newly added sites""" | ||||
app = get_celery() | app = get_celery() | ||||
shortjob_workers, longjob_workers = get_workers(app) | |||||
shortjob_workers, longjob_workers, async_tasks_workers = get_workers(app) | |||||
if shortjob_workers: | if shortjob_workers: | ||||
for worker in shortjob_workers: | for worker in shortjob_workers: | ||||
@@ -24,18 +28,25 @@ def sync_queues(): | |||||
for worker in longjob_workers: | for worker in longjob_workers: | ||||
sync_worker(app, worker, prefix=LONGJOBS_PREFIX) | 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): | def get_workers(app): | ||||
longjob_workers = [] | longjob_workers = [] | ||||
shortjob_workers = [] | shortjob_workers = [] | ||||
async_tasks_workers = [] | |||||
active_queues = app.control.inspect().active_queues() | active_queues = app.control.inspect().active_queues() | ||||
for worker in active_queues: | for worker in active_queues: | ||||
if worker.startswith(LONGJOBS_PREFIX): | if worker.startswith(LONGJOBS_PREFIX): | ||||
longjob_workers.append(worker) | longjob_workers.append(worker) | ||||
elif worker.startswith(ASYNC_TASKS_PREFIX): | |||||
async_tasks_workers.append(worker) | |||||
else: | else: | ||||
shortjob_workers.append(worker) | shortjob_workers.append(worker) | ||||
return shortjob_workers, longjob_workers | |||||
return shortjob_workers, longjob_workers, async_tasks_workers | |||||
def sync_worker(app, worker, prefix=''): | def sync_worker(app, worker, prefix=''): | ||||
active_queues = set(get_active_queues(app, worker)) | active_queues = set(get_active_queues(app, worker)) | ||||
@@ -125,6 +136,50 @@ def pull_from_email_account(site, email_account): | |||||
finally: | finally: | ||||
frappe.destroy() | 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() | @celery_task() | ||||
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | ||||
try: | 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") | exporter.get_template("Blog Category", all_doctypes="No", with_data="No") | ||||
content = read_csv_content(frappe.response.result) | content = read_csv_content(frappe.response.result) | ||||
content.append(["", "", "test-category", "Test Cateogry"]) | 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") | self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "Test Category") | ||||
# export with data | # export with data | ||||
@@ -44,7 +44,7 @@ class TestDataImport(unittest.TestCase): | |||||
# overwrite | # overwrite | ||||
content[-1][3] = "New Title" | 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") | self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "New Title") | ||||
def test_import_only_children(self): | 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") | exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No") | ||||
content = read_csv_content(frappe.response.result) | content = read_csv_content(frappe.response.result) | ||||
content.append(["", "test_import_userrole@example.com", "Blogger"]) | content.append(["", "test_import_userrole@example.com", "Blogger"]) | ||||
importer.upload(content) | |||||
importer.upload.queue(content) | |||||
user = frappe.get_doc("User", user_email) | user = frappe.get_doc("User", user_email) | ||||
self.assertEquals(len(user.get("user_roles")), 1) | 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") | exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No") | ||||
content = read_csv_content(frappe.response.result) | content = read_csv_content(frappe.response.result) | ||||
content.append(["", "test_import_userrole@example.com", "Website Manager"]) | 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) | user = frappe.get_doc("User", user_email) | ||||
self.assertEquals(len(user.get("user_roles")), 1) | self.assertEquals(len(user.get("user_roles")), 1) | ||||
@@ -81,7 +81,7 @@ class TestDataImport(unittest.TestCase): | |||||
content[-1][3] = "Private" | content[-1][3] = "Private" | ||||
content[-1][4] = "2014-01-01 10:00:00.000000" | content[-1][4] = "2014-01-01 10:00:00.000000" | ||||
content[-1][content[15].index("role")] = "System Manager" | content[-1][content[15].index("role")] = "System Manager" | ||||
importer.upload(content) | |||||
importer.upload.queue(content) | |||||
ev = frappe.get_doc("Event", {"subject":"__Test Event"}) | ev = frappe.get_doc("Event", {"subject":"__Test Event"}) | ||||
self.assertTrue("System Manager" in [d.role for d in ev.roles]) | 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("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]))) | session.mount("https://", requests.adapters.HTTPAdapter(max_retries=Retry(total=5, status_forcelist=[500]))) | ||||
return session | return session | ||||
@@ -41,7 +41,7 @@ class BackupGenerator: | |||||
last_db, last_file = self.get_recent_backup(older_than) | last_db, last_file = self.get_recent_backup(older_than) | ||||
else: | else: | ||||
last_db, last_file = False, False | last_db, last_file = False, False | ||||
if not (self.backup_path_files and self.backup_path_db): | if not (self.backup_path_files and self.backup_path_db): | ||||
self.set_backup_file_name() | self.set_backup_file_name() | ||||
if not (last_db and last_file): | if not (last_db and last_file): | ||||
@@ -219,4 +219,3 @@ if __name__ == "__main__": | |||||
if cmd == "delete_temp_backups": | if cmd == "delete_temp_backups": | ||||
delete_temp_backups() | delete_temp_backups() | ||||
@@ -106,7 +106,8 @@ def add_country_and_currency(name, country): | |||||
"country_name": name, | "country_name": name, | ||||
"code": country.code, | "code": country.code, | ||||
"date_format": country.date_format or "dd-mm-yyyy", | "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() | }).db_insert() | ||||
if country.currency and not frappe.db.exists("Currency", country.currency): | 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, | "fraction": country.currency_fraction, | ||||
"symbol": country.currency_symbol, | "symbol": country.currency_symbol, | ||||
"fraction_units": country.currency_fraction_units, | "fraction_units": country.currency_fraction_units, | ||||
"number_format": country.number_format | |||||
"number_format": country.number_format, | |||||
"docstatus": 0 | |||||
}).db_insert() | }).db_insert() | ||||
@@ -64,18 +64,21 @@ def as_json(): | |||||
response.data = json.dumps(frappe.local.response, default=json_handler, separators=(',',':')) | response.data = json.dumps(frappe.local.response, default=json_handler, separators=(',',':')) | ||||
return response | return response | ||||
def make_logs(): | |||||
def make_logs(response = None): | |||||
"""make strings for msgprint and errprint""" | """make strings for msgprint and errprint""" | ||||
if not response: | |||||
response = frappe.local.response | |||||
if frappe.error_log: | if frappe.error_log: | ||||
# frappe.response['exc'] = json.dumps("\n".join([cstr(d) for d in 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: | 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]) | d in frappe.local.message_log]) | ||||
if frappe.debug_log and frappe.conf.get("logging") or False: | 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): | def json_handler(obj): | ||||
"""serialize non-serializable data for json""" | """serialize non-serializable data for json""" | ||||
@@ -4,7 +4,7 @@ | |||||
"creation": "2013-03-08 09:41:11", | "creation": "2013-03-08 09:41:11", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Master", | |||||
"document_type": "Setup", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "category_name", | "fieldname": "category_name", | ||||
@@ -49,7 +49,7 @@ | |||||
], | ], | ||||
"icon": "icon-tag", | "icon": "icon-tag", | ||||
"idx": 1, | "idx": 1, | ||||
"modified": "2015-02-05 05:11:34.877605", | |||||
"modified": "2015-07-28 16:18:11.486847", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Blog Category", | "name": "Blog Category", | ||||
@@ -5,7 +5,7 @@ | |||||
"description": "User ID of a Blogger", | "description": "User ID of a Blogger", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Master", | |||||
"document_type": "Setup", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "disabled", | "fieldname": "disabled", | ||||
@@ -61,7 +61,7 @@ | |||||
"icon": "icon-user", | "icon": "icon-user", | ||||
"idx": 1, | "idx": 1, | ||||
"max_attachments": 1, | "max_attachments": 1, | ||||
"modified": "2015-02-19 09:29:25.836280", | |||||
"modified": "2015-07-28 16:18:11.567110", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Blogger", | "name": "Blogger", | ||||
@@ -7,7 +7,7 @@ | |||||
"custom": 0, | "custom": 0, | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Transaction", | |||||
"document_type": "Document", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"allow_on_submit": 0, | "allow_on_submit": 0, | ||||
@@ -230,7 +230,7 @@ | |||||
"is_submittable": 0, | "is_submittable": 0, | ||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"modified": "2015-02-05 05:11:48.897157", | |||||
"modified": "2015-07-28 16:18:12.772231", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Web Form", | "name": "Web Form", | ||||
@@ -3,7 +3,7 @@ | |||||
"description": "Page to show on the website\n", | "description": "Page to show on the website\n", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Transaction", | |||||
"document_type": "Document", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "section_title", | "fieldname": "section_title", | ||||
@@ -200,7 +200,7 @@ | |||||
"icon": "icon-file-alt", | "icon": "icon-file-alt", | ||||
"idx": 1, | "idx": 1, | ||||
"max_attachments": 20, | "max_attachments": 20, | ||||
"modified": "2015-07-22 12:38:08.696692", | |||||
"modified": "2015-07-28 16:18:12.887565", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Web Page", | "name": "Web Page", | ||||
@@ -4,7 +4,7 @@ | |||||
"description": "Slideshow like display for the website", | "description": "Slideshow like display for the website", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Transaction", | |||||
"document_type": "Document", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "slideshow_name", | "fieldname": "slideshow_name", | ||||
@@ -42,7 +42,7 @@ | |||||
"icon": "icon-play", | "icon": "icon-play", | ||||
"idx": 1, | "idx": 1, | ||||
"max_attachments": 10, | "max_attachments": 10, | ||||
"modified": "2015-02-20 05:04:19.614170", | |||||
"modified": "2015-07-28 16:18:13.013029", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Website Slideshow", | "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": [ | "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" | "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" | "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 | "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 | "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" | "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" | "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": [] | "permissions": [] | ||||
} | |||||
} |
@@ -5,7 +5,7 @@ | |||||
"description": "Workflow state represents the current state of a document.", | "description": "Workflow state represents the current state of a document.", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"document_type": "Master", | |||||
"document_type": "Setup", | |||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "workflow_state_name", | "fieldname": "workflow_state_name", | ||||
@@ -36,7 +36,7 @@ | |||||
], | ], | ||||
"icon": "icon-flag", | "icon": "icon-flag", | ||||
"idx": 1, | "idx": 1, | ||||
"modified": "2015-05-27 02:51:01.978973", | |||||
"modified": "2015-07-28 16:18:13.320514", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Workflow", | "module": "Workflow", | ||||
"name": "Workflow State", | "name": "Workflow State", | ||||
@@ -28,4 +28,5 @@ html2text | |||||
email_reply_parser | email_reply_parser | ||||
click | click | ||||
num2words | num2words | ||||
gevent-socketio | |||||
watchdog==0.8.0 | watchdog==0.8.0 |
@@ -1,6 +1,6 @@ | |||||
from setuptools import setup, find_packages | from setuptools import setup, find_packages | ||||
version = "5.4.2" | |||||
version = "6.0.0" | |||||
with open("requirements.txt", "r") as f: | with open("requirements.txt", "r") as f: | ||||
install_requires = f.readlines() | install_requires = f.readlines() | ||||