Bläddra i källkod

hooks.txt -> hooks.py

version-14
Rushabh Mehta 11 år sedan
förälder
incheckning
343330fe6b
18 ändrade filer med 351 tillägg och 179 borttagningar
  1. +22
    -8
      frappe/__init__.py
  2. +5
    -13
      frappe/cli.py
  3. +73
    -0
      frappe/hooks.py
  4. +0
    -55
      frappe/hooks.txt
  5. +4
    -1
      frappe/model/db_query.py
  6. +3
    -2
      frappe/model/document.py
  7. +1
    -1
      frappe/permissions.py
  8. +6
    -0
      frappe/public/css/desk.css
  9. +3
    -3
      frappe/public/js/frappe/dom.js
  10. +4
    -2
      frappe/public/js/frappe/form/grid.js
  11. +2
    -0
      frappe/public/js/frappe/request.js
  12. +21
    -21
      frappe/public/js/frappe/views/moduleview.js
  13. +1
    -1
      frappe/test_runner.py
  14. +11
    -2
      frappe/tests/test_client_login.py
  15. +16
    -0
      frappe/tests/test_hooks.py
  16. +102
    -28
      frappe/utils/boilerplate.py
  17. +27
    -30
      frappe/utils/scheduler.py
  18. +50
    -12
      frappe/utils/sel.py

+ 22
- 8
frappe/__init__.py Visa fil

@@ -391,24 +391,38 @@ def get_installed_apps():
installed = json.loads(db.get_global("installed_apps") or "[]")
return installed

def get_hooks(hook=None, app_name=None):
def get_hooks(hook=None, default=None, app_name=None):
def load_app_hooks(app_name=None):
hooks = {}
for app in [app_name] if app_name else get_installed_apps():
if app=="webnotes": app="frappe"
for item in get_file_items(get_pymodule_path(app, "hooks.txt")):
key, value = item.split("=", 1)
key, value = key.strip(), value.strip()
hooks.setdefault(key, [])
hooks[key].append(value)
app = "frappe" if app=="webnotes" else app
app_hooks = get_module(app + ".hooks")
for key in dir(app_hooks):
if not key.startswith("_"):
append_hook(hooks, key, getattr(app_hooks, key))
return hooks

def append_hook(target, key, value):
if isinstance(value, dict):
target.setdefault(key, {})
for inkey in value:
append_hook(target[key], inkey, value[inkey])
else:
append_to_list(target, key, value)

def append_to_list(target, key, value):
target.setdefault(key, [])
if not isinstance(value, list):
value = [value]
target[key].extend(value)

if app_name:
hooks = _dict(load_app_hooks(app_name))
else:
hooks = _dict(cache().get_value("app_hooks", load_app_hooks))

if hook:
return hooks.get(hook) or []
return hooks.get(hook) or default or []
else:
return hooks



+ 5
- 13
frappe/cli.py Visa fil

@@ -5,7 +5,6 @@

from __future__ import unicode_literals
import os
import time
import subprocess
import frappe

@@ -288,6 +287,9 @@ def install(db_name, root_login="root", root_password=None, source_sql=None,
admin_password = admin_password, verbose=verbose, force=force, site_config=site_config, reinstall=reinstall)
make_site_dirs()
install_app("frappe", verbose=verbose, set_as_patched=not source_sql)
if frappe.conf.get("install_apps"):
for app in frappe.conf.install_apps:
install_app(app, verbose=verbose, set_as_patched=not source_sql)
frappe.destroy()

@cmd
@@ -707,16 +709,8 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), with
import frappe.test_runner
from frappe.utils import sel

frappe.local.localhost = "http://localhost:8888"
if not without_serve:
pipe = subprocess.Popen(["frappe", frappe.local.site, "--serve", "--port", "8888"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while not pipe.stderr.readline():
time.sleep(0.5)
if verbose:
print "Test server started"

sel.start(verbose)
sel.start(verbose)

ret = 1
try:
@@ -726,9 +720,7 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), with
ret = 0
finally:
if not without_serve:
pipe.terminate()

sel.close()
sel.close()

return ret



+ 73
- 0
frappe/hooks.py Visa fil

@@ -0,0 +1,73 @@
app_name = "frappe"
app_title = "Frappe Framework"
app_publisher = "Web Notes Technologies Pvt. Ltd. and Contributors"
app_description = "Full Stack Web Application Framwork in Python"
app_icon = "assets/frappe/images/frappe.svg"
app_version = "4.0.0-wip"
app_color = "#3498db"

before_install = "frappe.utils.install.before_install"
after_install = "frappe.utils.install.after_install"

# website
app_include_js = "assets/js/frappe.min.js"
app_include_css = [
"assets/frappe/css/splash.css",
"assets/css/frappe.css"
]
web_include_js = [
"assets/js/frappe-web.min.js",
"website_script.js"
]
web_include_css = [
"assets/css/frappe-web.css",
"style_settings.css"
]

website_clear_cache = "frappe.templates.generators.website_group.clear_cache"

write_file_keys = ["file_url", "file_name"]

notification_config = "frappe.core.notifications.get_notification_config"

before_tests = "frappe.utils.install.before_tests"

# permissions

permission_query_conditions = {
"Event": "frappe.core.doctype.event.event.get_permission_query_conditions",
"ToDo": "frappe.core.doctype.todo.todo.get_permission_query_conditions"
}

has_permission = {
"Event": "frappe.core.doctype.event.event.has_permission",
"ToDo": "frappe.core.doctype.todo.todo.has_permission"
}

# bean

doc_events = {
"*": {
"on_update": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications",
"on_cancel": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications",
"on_trash": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications"
},
"User Vote": {
"after_insert": "frappe.templates.generators.website_group.clear_cache_on_doc_event"
},
"Website Route Permission": {
"on_update": "frappe.templates.generators.website_group.clear_cache_on_doc_event"
}
}

scheduler_events = {
"all": ["frappe.utils.email_lib.bulk.flush"],
"daily": [
"frappe.utils.email_lib.bulk.clear_outbox",
"frappe.core.doctype.notification_count.notification_count.delete_event_notification_count",
"frappe.core.doctype.event.event.send_event_digest",
],
"hourly": [
"frappe.templates.generators.website_group.clear_event_cache"
]
}

+ 0
- 55
frappe/hooks.txt Visa fil

@@ -1,55 +0,0 @@
app_name = frappe
app_title = Frappe Framework
app_publisher = Web Notes Technologies Pvt. Ltd. and Contributors
app_description = Full Stack Web Application Framwork in Python
app_icon = assets/frappe/images/frappe.svg
app_version = 4.0.0-wip
app_color = #3498db

before_install = frappe.utils.install.before_install
after_install = frappe.utils.install.after_install

# website
app_include_js = assets/js/frappe.min.js
app_include_css = assets/frappe/css/splash.css
app_include_css = assets/css/frappe.css
web_include_js = assets/js/frappe-web.min.js
web_include_css = assets/css/frappe-web.css
web_include_js = website_script.js
web_include_css = style_settings.css

website_clear_cache = frappe.templates.generators.website_group.clear_cache

get_desktop_icons = frappe.manage.get_desktop_icons
notification_config = frappe.core.notifications.get_notification_config

scheduler_event:all = frappe.utils.email_lib.bulk.flush
scheduler_event:daily = frappe.utils.email_lib.bulk.clear_outbox
scheduler_event:daily = frappe.core.doctype.notification_count.notification_count.delete_event_notification_count
scheduler_event:daily = frappe.core.doctype.event.event.send_event_digest
scheduler_event:hourly = frappe.templates.generators.website_group.clear_event_cache

before_tests = frappe.utils.install.before_tests

# TODO
# on_session_creation = frappe.auth.notify_administrator_login

# permissions

permission_query_conditions:Event = frappe.core.doctype.event.event.get_permission_query_conditions
has_permission:Event = frappe.core.doctype.event.event.has_permission

permission_query_conditions:ToDo = frappe.core.doctype.todo.todo.get_permission_query_conditions
has_permission:ToDo = frappe.core.doctype.todo.todo.has_permission

# bean

doc_event:User Vote:after_insert = frappe.templates.generators.website_group.clear_cache_on_doc_event
doc_event:Website Route Permission:on_update = frappe.templates.generators.website_group.clear_cache_on_doc_event

doc_event:*:on_update = frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications
doc_event:*:on_cancel = frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications
doc_event:*:on_trash = frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications

write_file_keys = file_url
write_file_keys = file_name

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

@@ -21,6 +21,9 @@ class DatabaseQuery(object):
def execute(self, query=None, filters=None, fields=None, docstatus=None,
group_by=None, order_by=None, limit_start=0, limit_page_length=20,
as_list=False, with_childnames=False, debug=False, ignore_permissions=False):
if not frappe.has_permission(self.doctype, "read"):
raise frappe.PermissionError

if fields:
self.fields = fields
self.filters = filters or []
@@ -254,7 +257,7 @@ class DatabaseQuery(object):
return conditions

def get_permission_query_conditions(self):
condition_methods = frappe.get_hooks("permission_query_conditions:" + self.doctype)
condition_methods = frappe.get_hooks("permission_query_conditions", {}).get(self.doctype, [])
if condition_methods:
conditions = []
for method in condition_methods:


+ 3
- 2
frappe/model/document.py Visa fil

@@ -423,8 +423,9 @@ class Document(BaseDocument):
def composer(self, *args, **kwargs):
hooks = []
method = f.__name__
for handler in frappe.get_hooks("doc_event:" + self.doctype + ":" + method) \
+ frappe.get_hooks("doc_event:*:" + method):
doc_events = frappe.get_hooks("doc_events")
for handler in doc_events.get(self.doctype, {}).get(method, []) \
+ doc_events.get("*", {}).get(method, []):
hooks.append(frappe.get_attr(handler))

composed = compose(f, *hooks)


+ 1
- 1
frappe/permissions.py Visa fil

@@ -116,7 +116,7 @@ def has_controller_permissions(doc):
else:
doc = frappe.get_doc(doc.doctype, doc.name)

for method in frappe.get_hooks("has_permission:" + doc.doctype):
for method in frappe.get_hooks("has_permission").get(doc.doctype, []):
if not frappe.call(frappe.get_attr(method), doc=doc):
return False



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

@@ -246,6 +246,12 @@ ul.linked-with-list li {

/* form grid */

.form-grid {
border: 2px solid #c7c7c7;
margin-bottom: 15px;
border-radius: 3px;
}

.grid-heading-row {
padding: 8px;
border-bottom: 1px solid #dddddd;


+ 3
- 3
frappe/public/js/frappe/dom.js Visa fil

@@ -127,15 +127,15 @@ var pending_req = 0
frappe.set_loading = function() {
pending_req++;
//$('#spinner').css('visibility', 'visible');
$('body').css('cursor', 'progress');
$('body').css('cursor', 'progress').attr("data-ajax-state", "running");
NProgress.start();
$("body");
}

frappe.done_loading = function() {
pending_req--;
if(!pending_req){
$('body').css('cursor', 'default');
//$('#spinner').css('visibility', 'hidden');
$('body').css('cursor', 'default').attr("data-ajax-state", "complete");
NProgress.done();
} else {
NProgress.inc();


+ 4
- 2
frappe/public/js/frappe/form/grid.js Visa fil

@@ -11,7 +11,7 @@ frappe.ui.form.Grid = Class.extend({
var me = this;

this.wrapper = $('<div>\
<div class="" style="border: 2px solid #c7c7c7; margin-bottom: 15px; border-radius: 3px;">\
<div class="form-grid">\
<div class="grid-heading-row" style="font-size: 15px; background-color: #f9f9f9;"></div>\
<div class="panel-body">\
<div class="rows"></div>\
@@ -22,7 +22,9 @@ frappe.ui.form.Grid = Class.extend({
</div>\
</div>\
</div>\
</div>').appendTo(this.parent);
</div>')
.appendTo(this.parent)
.attr("data-fieldname", this.df.fieldname);

$(this.wrapper).find(".grid-add-row").click(function() {
me.add_new_row(null, null, true);


+ 2
- 0
frappe/public/js/frappe/request.js Visa fil

@@ -136,9 +136,11 @@ frappe.request.prepare = function(opts) {
console.log(opts)
throw "Incomplete Request";
}

}

frappe.request.cleanup = function(opts, r) {

// stop button indicator
if(opts.btn) $(opts.btn).done_working();



+ 21
- 21
frappe/public/js/frappe/views/moduleview.js Visa fil

@@ -194,7 +194,7 @@ frappe.views.moduleview.ModuleView = Class.extend({
$list_item = $($r('<li class="list-group-item">\
<div class="row">\
<div class="col-sm-6 list-item-name">\
<a><i class="%(icon)s icon-fixed-width"></i> %(label)s</a></div>\
<a data-label="%(label)s"><i class="%(icon)s icon-fixed-width"></i> %(label)s</a></div>\
<div class="col-sm-6 text-muted list-item-description">%(description)s</div>\
</div>\
</li>', item)).appendTo($list);
@@ -205,32 +205,32 @@ frappe.views.moduleview.ModuleView = Class.extend({
$list_item.find(".list-item-name").removeClass("col-sm-6").addClass("col-sm-12");
}

$list_item.find("a")
.on("click", function() {
if(item.route) {
frappe.set_route(item.route);
} else if(item.onclick) {
if(item.onclick) {
$list_item.find("a")
.on("click", function() {
var fn = eval(item.onclick);
if(typeof(fn)==="function") {
fn();
}
} else if(item.type==="doctype") {
frappe.set_route("List", item.name)
}
else if(item.type==="page") {
frappe.set_route(item.route || item.link || item.name);
}
else if(item.type==="report") {
if(item.is_query_report) {
frappe.set_route("query-report", item.name);
} else {
frappe.set_route("Report", item.doctype, item.name);
}
});
} else {
var route = item.route;
if(item.type==="doctype") {
route = "List/" + encodeURIComponent(item.name);
} else if(item.type==="page") {
route = item.route || item.link || item.name;
} else if(item.type==="report") {
if(item.is_query_report) {
route = "query-report/" + encodeURIComponent(item.name);
} else {
return;
route = "Report/" + encodeURIComponent(item.doctype) + "/" + encodeURIComponent(item.name);
}
return false;
});
}

$list_item.find("a")
.attr("href", "#" + route)
}


var show_count = (item.type==="doctype" || (item.type==="page" && item.doctype)) && !item.hide_count
if(show_count) {


+ 1
- 1
frappe/test_runner.py Visa fil

@@ -25,7 +25,7 @@ def main(app=None, module=None, doctype=None, verbose=False, tests=()):

if verbose:
print 'Running "before_tests" hooks'
for fn in frappe.get_hooks("before_tests", app):
for fn in frappe.get_hooks("before_tests", app_name=app):
frappe.get_attr(fn)()

if doctype:


+ 11
- 2
frappe/tests/test_client_login.py Visa fil

@@ -15,6 +15,15 @@ class TestLogin(unittest.TestCase):
sel.module("ToDo")
sel.find('.appframe-iconbar .icon-plus')[0].click()
sel.wait_for_page("Form/ToDo")
sel.set_field_input("description", "test description")
sel.set_field("description", "test description")
sel.primary_action()
sel.wait_for_state("clean")
self.assertTrue(sel.wait_for_state("clean"))

# def test_material_request(self):
# sel.new_doc("Stock", "Material Request")
# sel.add_child("indent_details")
# sel.set_field("item_code", "_Test Item")
# sel.set_field("schedule_date", "10-10-2014")
# sel.primary_action()
# sel.wait_for_state("clean")


+ 16
- 0
frappe/tests/test_hooks.py Visa fil

@@ -0,0 +1,16 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
import unittest
import frappe

class TestHooks(unittest.TestCase):
def test_hooks(self):
hooks = frappe.get_hooks()
self.assertTrue(isinstance(hooks.get("app_name"), list))
self.assertTrue(isinstance(hooks.get("doc_events"), dict))
self.assertTrue(isinstance(hooks.get("doc_events").get("*"), dict))
self.assertTrue(isinstance(hooks.get("doc_events").get("*"), dict))
self.assertTrue("frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications" in
hooks.get("doc_events").get("*").get("on_update"))

+ 102
- 28
frappe/utils/boilerplate.py Visa fil

@@ -1,5 +1,5 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# MIT License. See license.txt

from __future__ import unicode_literals

@@ -10,9 +10,9 @@ def make_boilerplate(dest):
if not os.path.exists(dest):
print "Destination directory does not exist"
return
hooks = frappe._dict()
for key in ("App Name", "App Title", "App Description", "App Publisher",
for key in ("App Name", "App Title", "App Description", "App Publisher",
"App Icon", "App Color", "App Email", "App URL", "App License"):
hook_key = key.lower().replace(" ", "_")
hook_val = None
@@ -21,29 +21,29 @@ def make_boilerplate(dest):
if hook_key=="app_name" and hook_val.lower().replace(" ", "_") != hook_val:
print "App Name must be all lowercase and without spaces"
hook_val = ""
hooks[hook_key] = hook_val
frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, hooks.app_name))
frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates"))
frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
"statics"))
frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
"pages"))
frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
"generators"))
frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "config"))
# init files
touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "__init__.py"))
touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, hooks.app_name, "__init__.py"))
touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "templates", "__init__.py"))
touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
"pages", "__init__.py"))
touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
"generators", "__init__.py"))
touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "config", "__init__.py"))
with open(os.path.join(dest, hooks.app_name, "MANIFEST.in"), "w") as f:
f.write(manifest_template.format(**hooks))

@@ -64,17 +64,17 @@ def make_boilerplate(dest):
with open(os.path.join(dest, hooks.app_name, hooks.app_name, "modules.txt"), "w") as f:
f.write(hooks.app_name)

with open(os.path.join(dest, hooks.app_name, hooks.app_name, "hooks.txt"), "w") as f:
with open(os.path.join(dest, hooks.app_name, hooks.app_name, "hooks.py"), "w") as f:
f.write(hooks_template.format(**hooks))

touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "patches.txt"))

with open(os.path.join(dest, hooks.app_name, hooks.app_name, "config", "desktop.py"), "w") as f:
f.write(desktop_template.format(**hooks))




manifest_template = """include MANIFEST.in
include requirements.txt
include *.json
@@ -93,24 +93,98 @@ recursive-include {app_name} *.py
recursive-include {app_name} *.svg
recursive-include {app_name} *.txt
recursive-exclude {app_name} *.pyc"""
hooks_template = """app_name = {app_name}
app_title = {app_title}
app_publisher = {app_publisher}
app_description = {app_description}
app_icon = {app_icon}
app_color = {app_color}
app_email = {app_email}
app_url = {app_url}
app_version = 0.0.1

hooks_template = """app_name = "{app_name}"
app_title = "{app_title}"
app_publisher = "{app_publisher}"
app_description = "{app_description}"
app_icon = "{app_icon}"
app_color = "{app_color}"
app_email = "{app_email}"
app_url = "{app_url}"
app_version = "0.0.1"

# Includes in <head>
# ------------------

# include js, css files in header of desk.html
# app_include_css = "/assets/{app_name}/{app_name}.css"
# app_include_js = "/assets/{app_name}/{app_name}.js"

# include js, css files in header of web template
# web_include_css = "/assets/{app_name}/{app_name}.css"
# web_include_js = "/assets/{app_name}/{app_name}.js"

# Installation
# ------------

# before_install = "{app_name}.install.before_install"
# after_install = "{app_name}.install.after_install"

# Desk Notifications
# ------------------
# See frappe.core.notifications.get_notification_config

# notification_config = "{app_name}.notifications.get_notification_config"

# Permissions
# -----------
# Permissions evaluated in scripted ways

# permission_query_conditions = {
# "Event": "frappe.core.doctype.event.event.get_permission_query_conditions",
# }
#
# has_permission = {
# "Event": "frappe.core.doctype.event.event.has_permission",
# }

# Document Events
# ---------------
# Hook on document methods and events

# doc_events = {
# "*": {
# "on_update": "method",
# "on_cancel": "method",
# "on_trash": "method"
# }
# }

# Scheduled Tasks
# ---------------

# scheduler_events = {
# "all": [
# "{app_name}.tasks.all"
# ],
# "daily": [
# "{app_name}.tasks.daily"
# ],
# "hourly": [
# "{app_name}.tasks.hourly"
# ],
# "weekly": [
# "{app_name}.tasks.weekly"
# ]
# "monthly": [
# "{app_name}.tasks.monthly"
# ]
# }

# Testing
# -------

# before_tests = "{app_name}.install.before_tests"

"""

desktop_template = """from frappe import _

data = {{
"{app_title}": {{
"color": "{app_color}",
"icon": "{app_icon}",
"color": "{app_color}",
"icon": "{app_icon}",
"label": _("{app_title}")
}}
}}


+ 27
- 30
frappe/utils/scheduler.py Visa fil

@@ -1,5 +1,5 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# MIT License. See license.txt
"""
Events:
always
@@ -20,7 +20,7 @@ DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
def enqueue_events(site):
if is_scheduler_disabled():
return
# lock before queuing begins
try:
lock = create_lock('scheduler')
@@ -28,31 +28,31 @@ def enqueue_events(site):
return
except LockTimeoutError:
return
nowtime = frappe.utils.now_datetime()
last = frappe.db.get_global('scheduler_last_event')
# set scheduler last event
frappe.db.begin()
frappe.db.set_global('scheduler_last_event', nowtime.strftime(DATETIME_FORMAT))
frappe.db.commit()
out = []
if last:
last = datetime.strptime(last, DATETIME_FORMAT)
out = enqueue_applicable_events(site, nowtime, last)
delete_lock('scheduler')
return '\n'.join(out)
def enqueue_applicable_events(site, nowtime, last):
nowtime_str = nowtime.strftime(DATETIME_FORMAT)
out = []
def _log(event):
out.append("{time} - {event} - queued".format(time=nowtime_str, event=event))
if nowtime.day != last.day:
# if first task of the day execute daily tasks
trigger(site, "daily") and _log("daily")
@@ -61,23 +61,23 @@ def enqueue_applicable_events(site, nowtime, last):
if nowtime.month != last.month:
trigger(site, "monthly") and _log("monthly")
trigger(site, "monthly_long") and _log("monthly_long")
if nowtime.weekday()==0:
trigger(site, "weekly") and _log("weekly")
trigger(site, "weekly_long") and _log("weekly_long")
if nowtime.hour != last.hour:
trigger(site, "hourly") and _log("hourly")

trigger(site, "all") and _log("all")
return out
def trigger(site, event, now=False):
"""trigger method in startup.schedule_handler"""
from frappe.tasks import scheduler_task
for handler in frappe.get_hooks("scheduler_event:{}".format(event)):
for handler in frappe.get_hooks("scheduler_events").get(event, []):
if not check_lock(handler):
if not now:
scheduler_task.delay(site=site, event=event, handler=handler)
@@ -85,36 +85,36 @@ def trigger(site, event, now=False):
else:
create_lock(handler)
scheduler_task(site=site, event=event, handler=handler, now=True)
def log(method, message=None):
"""log error in patch_log"""
message = frappe.utils.cstr(message) + "\n" if message else ""
message += frappe.get_traceback()
if not (frappe.db and frappe.db._conn):
frappe.connect()

frappe.db.rollback()
frappe.db.begin()
d = frappe.get_doc("Scheduler Log")
d.method = method
d.error = message
d.save()

frappe.db.commit()
return message
def is_scheduler_disabled():
return frappe.utils.cint(frappe.db.get_global("disable_scheduler"))

def enable_scheduler():
frappe.db.set_global("disable_scheduler", 0)
def disable_scheduler():
frappe.db.set_global("disable_scheduler", 1)
def get_errors(from_date, to_date, limit):
errors = frappe.db.sql("""select modified, method, error from `tabScheduler Log`
where date(modified) between %s and %s
@@ -122,23 +122,20 @@ def get_errors(from_date, to_date, limit):
order by modified limit %s""", (from_date, to_date, limit), as_dict=True)
return ["""<p>Time: {modified}</p><pre><code>Method: {method}\n{error}</code></pre>""".format(**e)
for e in errors]
def get_error_report(from_date=None, to_date=None, limit=10):
from frappe.utils import get_url, now_datetime, add_days
if not from_date:
from_date = add_days(now_datetime().date(), -1)
if not to_date:
to_date = add_days(now_datetime().date(), -1)
errors = get_errors(from_date, to_date, limit)
if errors:
return 1, """<h4>Scheduler Failed Events (max {limit}):</h4>
<p>URL: <a href="{url}" target="_blank">{url}</a></p><hr>{errors}""".format(
limit=limit, url=get_url(), errors="<hr>".join(errors))
else:
return 0, "<p>Scheduler didn't encounter any problems.</p>"
if __name__=='__main__':
execute()

+ 50
- 12
frappe/utils/sel.py Visa fil

@@ -9,7 +9,11 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from urllib import unquote
import time, frappe, subprocess

host = "http://localhost:8888"
pipe = None
driver = None
verbose = None
host = None
@@ -18,26 +22,35 @@ cur_route = False

def start(_verbose=None):
global driver, verbose
driver = webdriver.PhantomJS()
verbose = _verbose

start_test_server(verbose)
driver = webdriver.PhantomJS()

def start_test_server(verbose):
global pipe
pipe = subprocess.Popen(["frappe", "--serve", "--port", "8888"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while not pipe.stderr.readline():
time.sleep(0.5)
if verbose:
print "Test server started"

def get(url):
driver.get(url)

def login(_host):
global host, logged_in
def login(wait_for_id="#page-desktop"):
global logged_in
if logged_in:
return
host = _host
get(host + "/login")
wait("#login_email")
set_input("#login_email", "Administrator")
set_input("#login_password", "admin" + Keys.RETURN)
wait("#page-desktop")
wait(wait_for_id)
logged_in = True


def module(module_name):
def go_to_module(module_name, item=None):
global cur_route

# desktop
@@ -56,26 +69,49 @@ def module(module_name):
m[0].click()
wait_for_page(page)

if item:
elem = find('[data-label="{0}"]'.format(item))[0]
elem.click()
page = unquote(elem.get_attribute("href").split("#", 1)[1])
wait_for_page(page)

def new_doc(module, doctype):
go_to_module(module, doctype)
find('.appframe-iconbar .icon-plus')[0].click()
wait_for_page("Form/" + doctype)

def add_child(fieldname):
find('[data-fieldname="{0}"] .grid-add-row'.format(fieldname))[0].click()
wait('[data-fieldname="{0}"] .form-grid'.format(fieldname))

def find(selector, everywhere=False):
if cur_route and not everywhere:
selector = cur_route + " " + selector
return driver.find_elements_by_css_selector(selector)

def set_field_input(fieldname, value):
set_input('[data-fieldname="{0}"]'.format(fieldname), value)
def set_field(fieldname, value):
set_input('input[data-fieldname="{0}"]'.format(fieldname), value + Keys.TAB)
wait_for_ajax()
time.sleep(0.5)

def primary_action():
find(".appframe-titlebar .btn-primary")[0].click()
wait_for_ajax()

def wait_for_ajax():
wait('body[data-ajax-state="complete"]', True)

def wait_for_page(name):
global cur_route
cur_route = None
route = '[data-page-route="{0}"]'.format(name)
wait(route)
elem = wait(route)
wait_for_ajax()
cur_route = route
return elem

def wait_for_state(state):
wait(cur_route + '[data-state="{0}"]'.format(state), True)
return wait(cur_route + '[data-state="{0}"]'.format(state), True)

def wait(selector, everywhere=False):
if cur_route and not everywhere:
@@ -92,9 +128,11 @@ def wait(selector, everywhere=False):

def set_input(selector, text):
elem = find(selector)[0]
elem.clear()
elem.send_keys(text)

def close():
global driver
global driver, pipe
driver.quit()
driver = None
pipe.kill()
driver = pipe = None

Laddar…
Avbryt
Spara