Просмотр исходного кода

hooks.txt -> hooks.py

version-14
Rushabh Mehta 11 лет назад
Родитель
Сommit
343330fe6b
18 измененных файлов: 351 добавлений и 179 удалений
  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 Просмотреть файл

@@ -391,24 +391,38 @@ def get_installed_apps():
installed = json.loads(db.get_global("installed_apps") or "[]") installed = json.loads(db.get_global("installed_apps") or "[]")
return installed 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): def load_app_hooks(app_name=None):
hooks = {} hooks = {}
for app in [app_name] if app_name else get_installed_apps(): 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 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: if app_name:
hooks = _dict(load_app_hooks(app_name)) hooks = _dict(load_app_hooks(app_name))
else: else:
hooks = _dict(cache().get_value("app_hooks", load_app_hooks)) hooks = _dict(cache().get_value("app_hooks", load_app_hooks))


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




+ 5
- 13
frappe/cli.py Просмотреть файл

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


from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import time
import subprocess import subprocess
import frappe 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) admin_password = admin_password, verbose=verbose, force=force, site_config=site_config, reinstall=reinstall)
make_site_dirs() make_site_dirs()
install_app("frappe", verbose=verbose, set_as_patched=not source_sql) 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() frappe.destroy()


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


frappe.local.localhost = "http://localhost:8888"
if not without_serve: 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 ret = 1
try: try:
@@ -726,9 +720,7 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), with
ret = 0 ret = 0
finally: finally:
if not without_serve: if not without_serve:
pipe.terminate()

sel.close()
sel.close()


return ret return ret




+ 73
- 0
frappe/hooks.py Просмотреть файл

@@ -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 Просмотреть файл

@@ -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 Просмотреть файл

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

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


def get_permission_query_conditions(self): 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: if condition_methods:
conditions = [] conditions = []
for method in condition_methods: for method in condition_methods:


+ 3
- 2
frappe/model/document.py Просмотреть файл

@@ -423,8 +423,9 @@ class Document(BaseDocument):
def composer(self, *args, **kwargs): def composer(self, *args, **kwargs):
hooks = [] hooks = []
method = f.__name__ 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)) hooks.append(frappe.get_attr(handler))


composed = compose(f, *hooks) composed = compose(f, *hooks)


+ 1
- 1
frappe/permissions.py Просмотреть файл

@@ -116,7 +116,7 @@ def has_controller_permissions(doc):
else: else:
doc = frappe.get_doc(doc.doctype, doc.name) 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): if not frappe.call(frappe.get_attr(method), doc=doc):
return False return False




+ 6
- 0
frappe/public/css/desk.css Просмотреть файл

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


/* form grid */ /* form grid */


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

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


+ 3
- 3
frappe/public/js/frappe/dom.js Просмотреть файл

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


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


+ 4
- 2
frappe/public/js/frappe/form/grid.js Просмотреть файл

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


this.wrapper = $('<div>\ 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="grid-heading-row" style="font-size: 15px; background-color: #f9f9f9;"></div>\
<div class="panel-body">\ <div class="panel-body">\
<div class="rows"></div>\ <div class="rows"></div>\
@@ -22,7 +22,9 @@ frappe.ui.form.Grid = Class.extend({
</div>\ </div>\
</div>\ </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() { $(this.wrapper).find(".grid-add-row").click(function() {
me.add_new_row(null, null, true); me.add_new_row(null, null, true);


+ 2
- 0
frappe/public/js/frappe/request.js Просмотреть файл

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

} }


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

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




+ 21
- 21
frappe/public/js/frappe/views/moduleview.js Просмотреть файл

@@ -194,7 +194,7 @@ frappe.views.moduleview.ModuleView = Class.extend({
$list_item = $($r('<li class="list-group-item">\ $list_item = $($r('<li class="list-group-item">\
<div class="row">\ <div class="row">\
<div class="col-sm-6 list-item-name">\ <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 class="col-sm-6 text-muted list-item-description">%(description)s</div>\
</div>\ </div>\
</li>', item)).appendTo($list); </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(".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); var fn = eval(item.onclick);
if(typeof(fn)==="function") { if(typeof(fn)==="function") {
fn(); 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 { } 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 var show_count = (item.type==="doctype" || (item.type==="page" && item.doctype)) && !item.hide_count
if(show_count) { if(show_count) {


+ 1
- 1
frappe/test_runner.py Просмотреть файл

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


if verbose: if verbose:
print 'Running "before_tests" hooks' 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)() frappe.get_attr(fn)()


if doctype: if doctype:


+ 11
- 2
frappe/tests/test_client_login.py Просмотреть файл

@@ -15,6 +15,15 @@ class TestLogin(unittest.TestCase):
sel.module("ToDo") sel.module("ToDo")
sel.find('.appframe-iconbar .icon-plus')[0].click() sel.find('.appframe-iconbar .icon-plus')[0].click()
sel.wait_for_page("Form/ToDo") sel.wait_for_page("Form/ToDo")
sel.set_field_input("description", "test description")
sel.set_field("description", "test description")
sel.primary_action() 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 Просмотреть файл

@@ -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 Просмотреть файл

@@ -1,5 +1,5 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # 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 from __future__ import unicode_literals


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


touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "patches.txt")) 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: with open(os.path.join(dest, hooks.app_name, hooks.app_name, "config", "desktop.py"), "w") as f:
f.write(desktop_template.format(**hooks)) f.write(desktop_template.format(**hooks))





manifest_template = """include MANIFEST.in manifest_template = """include MANIFEST.in
include requirements.txt include requirements.txt
include *.json include *.json
@@ -93,24 +93,98 @@ recursive-include {app_name} *.py
recursive-include {app_name} *.svg recursive-include {app_name} *.svg
recursive-include {app_name} *.txt recursive-include {app_name} *.txt
recursive-exclude {app_name} *.pyc""" 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 _ desktop_template = """from frappe import _


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


+ 27
- 30
frappe/utils/scheduler.py Просмотреть файл

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


trigger(site, "all") and _log("all") trigger(site, "all") and _log("all")
return out return out
def trigger(site, event, now=False): def trigger(site, event, now=False):
"""trigger method in startup.schedule_handler""" """trigger method in startup.schedule_handler"""
from frappe.tasks import scheduler_task 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 check_lock(handler):
if not now: if not now:
scheduler_task.delay(site=site, event=event, handler=handler) scheduler_task.delay(site=site, event=event, handler=handler)
@@ -85,36 +85,36 @@ def trigger(site, event, now=False):
else: else:
create_lock(handler) create_lock(handler)
scheduler_task(site=site, event=event, handler=handler, now=True) scheduler_task(site=site, event=event, handler=handler, now=True)
def log(method, message=None): def log(method, message=None):
"""log error in patch_log""" """log error in patch_log"""
message = frappe.utils.cstr(message) + "\n" if message else "" message = frappe.utils.cstr(message) + "\n" if message else ""
message += frappe.get_traceback() message += frappe.get_traceback()
if not (frappe.db and frappe.db._conn): if not (frappe.db and frappe.db._conn):
frappe.connect() frappe.connect()


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


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


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

+ 50
- 12
frappe/utils/sel.py Просмотреть файл

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


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


def start(_verbose=None): def start(_verbose=None):
global driver, verbose global driver, verbose
driver = webdriver.PhantomJS()
verbose = _verbose 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): def get(url):
driver.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: if logged_in:
return return
host = _host
get(host + "/login") get(host + "/login")
wait("#login_email") wait("#login_email")
set_input("#login_email", "Administrator") set_input("#login_email", "Administrator")
set_input("#login_password", "admin" + Keys.RETURN) set_input("#login_password", "admin" + Keys.RETURN)
wait("#page-desktop")
wait(wait_for_id)
logged_in = True logged_in = True




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


# desktop # desktop
@@ -56,26 +69,49 @@ def module(module_name):
m[0].click() m[0].click()
wait_for_page(page) 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): def find(selector, everywhere=False):
if cur_route and not everywhere: if cur_route and not everywhere:
selector = cur_route + " " + selector selector = cur_route + " " + selector
return driver.find_elements_by_css_selector(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(): def primary_action():
find(".appframe-titlebar .btn-primary")[0].click() 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): def wait_for_page(name):
global cur_route global cur_route
cur_route = None cur_route = None
route = '[data-page-route="{0}"]'.format(name) route = '[data-page-route="{0}"]'.format(name)
wait(route)
elem = wait(route)
wait_for_ajax()
cur_route = route cur_route = route
return elem


def wait_for_state(state): 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): def wait(selector, everywhere=False):
if cur_route and not everywhere: if cur_route and not everywhere:
@@ -92,9 +128,11 @@ def wait(selector, everywhere=False):


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


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

Загрузка…
Отмена
Сохранить