@@ -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 | |||
import frappe | |||
@@ -17,7 +17,7 @@ class Page(Document): | |||
self.name = self.page_name.lower().replace('"','').replace("'",'').\ | |||
replace(' ', '-')[:20] | |||
if frappe.db.exists('Page',self.name): | |||
cnt = frappe.db.sql("""select name from tabPage | |||
cnt = frappe.db.sql("""select name from tabPage | |||
where name like "%s-%%" order by name desc limit 1""" % self.name) | |||
if cnt: | |||
cnt = cint(cnt[0][0].split('-')[-1]) + 1 | |||
@@ -34,29 +34,29 @@ class Page(Document): | |||
from frappe import conf | |||
from frappe.core.doctype.doctype.doctype import make_module_and_roles | |||
make_module_and_roles(self, "roles") | |||
if not frappe.flags.in_import and getattr(conf,'developer_mode', 0) and self.standard=='Yes': | |||
from frappe.modules.export_file import export_to_files | |||
from frappe.modules import get_module_path, scrub | |||
import os | |||
export_to_files(record_list=[['Page', self.name]]) | |||
# write files | |||
path = os.path.join(get_module_path(self.module), 'page', scrub(self.name), scrub(self.name)) | |||
# js | |||
if not os.path.exists(path + '.js'): | |||
with open(path + '.js', 'w') as f: | |||
f.write("""frappe.pages['%s'].onload = function(wrapper) { | |||
f.write("""frappe.pages['%s'].onload = function(wrapper) { | |||
frappe.ui.make_app_page({ | |||
parent: wrapper, | |||
title: '%s', | |||
single_column: true | |||
}); | |||
}); | |||
}""" % (self.name, self.title)) | |||
def as_dict(self): | |||
d = super(Page, self).as_dict() | |||
def as_dict(self, no_nulls=False): | |||
d = super(Page, self).as_dict(no_nulls=no_nulls) | |||
for key in ("script", "style", "content"): | |||
d[key] = self.get(key) | |||
return d | |||
@@ -64,7 +64,7 @@ class Page(Document): | |||
def load_assets(self): | |||
from frappe.modules import get_module_path, scrub | |||
import os | |||
path = os.path.join(get_module_path(self.module), 'page', scrub(self.name)) | |||
# script | |||
@@ -78,14 +78,13 @@ class Page(Document): | |||
if os.path.exists(fpath): | |||
with open(fpath, 'r') as f: | |||
self.style = f.read() | |||
# html | |||
fpath = os.path.join(path, scrub(self.name) + '.html') | |||
if os.path.exists(fpath): | |||
with open(fpath, 'r') as f: | |||
self.content = f.read() | |||
if frappe.lang != 'en': | |||
from frappe.translate import get_lang_js | |||
self.script += get_lang_js("page", self.name) | |||
@@ -54,7 +54,7 @@ def export_json(doctype, name, path): | |||
d.set("parent", None) | |||
d.set("name", None) | |||
d.set("__islocal", 1) | |||
outfile.write(json.dumps([d], default=json_handler, indent=1, sort_keys=True)) | |||
outfile.write(json.dumps(doc, default=json_handler, indent=1, sort_keys=True)) | |||
@frappe.whitelist() | |||
def export_fixture(doctype, name, app): | |||
@@ -138,12 +138,18 @@ class BaseDocument(object): | |||
def is_new(self): | |||
return self.get("__islocal") | |||
def as_dict(self): | |||
def as_dict(self, no_nulls=False): | |||
doc = self.get_valid_dict() | |||
doc["doctype"] = self.doctype | |||
for df in self.meta.get_table_fields(): | |||
children = self.get(df.fieldname) or [] | |||
doc[df.fieldname] = [d.as_dict() for d in children] | |||
doc[df.fieldname] = [d.as_dict(no_nulls=no_nulls) for d in children] | |||
if no_nulls: | |||
for k in doc.keys(): | |||
if doc[k] is None: | |||
del doc[k] | |||
return doc | |||
def get_table_field_doctype(self, fieldname): | |||
@@ -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 | |||
import json | |||
@@ -22,7 +22,7 @@ from werkzeug.exceptions import NotFound, Forbidden | |||
def report_error(status_code): | |||
if status_code!=404 or frappe.conf.logging: | |||
frappe.errprint(frappe.utils.get_traceback()) | |||
response = build_response("json") | |||
response.status_code = status_code | |||
return response | |||
@@ -30,7 +30,7 @@ def report_error(status_code): | |||
def build_response(response_type=None): | |||
if "docs" in frappe.local.response and not frappe.local.response.docs: | |||
del frappe.local.response["docs"] | |||
response_type_map = { | |||
'csv': as_csv, | |||
'download': as_raw, | |||
@@ -38,9 +38,9 @@ def build_response(response_type=None): | |||
'page': as_page, | |||
'redirect': redirect | |||
} | |||
return response_type_map[frappe.response.get('type') or response_type]() | |||
def as_csv(): | |||
response = Response() | |||
response.headers["Content-Type"] = "text/csv; charset: utf-8" | |||
@@ -62,7 +62,7 @@ def as_json(): | |||
response = gzip(json.dumps(frappe.local.response, default=json_handler, separators=(',',':')), | |||
response=response) | |||
return response | |||
def make_logs(): | |||
"""make strings for msgprint and errprint""" | |||
if frappe.error_log: | |||
@@ -72,21 +72,21 @@ def make_logs(): | |||
if frappe.local.message_log: | |||
frappe.response['_server_messages'] = json.dumps([frappe.utils.cstr(d) for | |||
d in frappe.local.message_log]) | |||
if frappe.debug_log and frappe.conf.get("logging") or False: | |||
frappe.response['_debug_messages'] = json.dumps(frappe.local.debug_log) | |||
def gzip(data, response): | |||
data = data.encode('utf-8') | |||
orig_len = len(data) | |||
if accept_gzip() and orig_len>512: | |||
data = compressBuf(data) | |||
response.headers["Content-Encoding"] = "gzip" | |||
response.headers["Content-Length"] = str(len(data)) | |||
response.data = data | |||
return response | |||
def accept_gzip(): | |||
if "gzip" in frappe.get_request_header("HTTP_ACCEPT_ENCODING", ""): | |||
return True | |||
@@ -97,7 +97,7 @@ def compressBuf(buf): | |||
zfile.write(buf) | |||
zfile.close() | |||
return zbuf.getvalue() | |||
def json_handler(obj): | |||
"""serialize non-serializable data for json""" | |||
# serialize date | |||
@@ -105,20 +105,22 @@ def json_handler(obj): | |||
return unicode(obj) | |||
elif isinstance(obj, LocalProxy): | |||
return unicode(obj) | |||
elif isinstance(obj, frappe.model.document.Document): | |||
return obj.as_dict() | |||
elif isinstance(obj, frappe.model.document.BaseDocument): | |||
doc = obj.as_dict(no_nulls=True) | |||
return doc | |||
else: | |||
raise TypeError, """Object of type %s with value of %s is not JSON serializable""" % \ | |||
(type(obj), repr(obj)) | |||
def as_page(): | |||
"""print web page""" | |||
from frappe.website.render import render | |||
return render(frappe.response['page_name'], http_status_code=frappe.response.get("http_status_code")) | |||
def redirect(): | |||
return werkzeug.utils.redirect(frappe.response.location) | |||
def download_backup(path): | |||
try: | |||
frappe.only_for(("System Manager", "Administrator")) | |||
@@ -145,7 +147,7 @@ def send_private_file(path): | |||
response = Response(wrap_file(frappe.local.request.environ, f)) | |||
response.headers.add('Content-Disposition', 'attachment', filename=filename) | |||
response.headers['Content-Type'] = mimetypes.guess_type(filename)[0] or 'application/octet-stream' | |||
return response | |||
def handle_session_stopped(): | |||
@@ -9,8 +9,8 @@ $.extend(frappe, { | |||
if(frappe._assets_loaded.indexOf(url)!==-1) return; | |||
$.ajax({ | |||
url: url, | |||
async: false, | |||
dataType: "text", | |||
async: false, | |||
dataType: "text", | |||
success: function(data) { | |||
if(url.split(".").splice(-1) == "js") { | |||
var el = document.createElement('script'); | |||
@@ -58,13 +58,13 @@ $.extend(frappe, { | |||
if(opts.btn) { | |||
$(opts.btn).prop("disabled", true); | |||
} | |||
if(opts.msg) { | |||
$(opts.msg).toggle(false); | |||
} | |||
if(!opts.args) opts.args = {}; | |||
// get or post? | |||
if(!opts.args._type) { | |||
opts.args._type = opts.type || "GET"; | |||
@@ -82,17 +82,17 @@ $.extend(frappe, { | |||
} | |||
}); | |||
if(!opts.no_spinner) { | |||
if(!opts.no_spinner) { | |||
NProgress.start(); | |||
} | |||
}, | |||
process_response: function(opts, data) { | |||
if(!opts.no_spinner) NProgress.done(); | |||
if(opts.btn) { | |||
$(opts.btn).prop("disabled", false); | |||
} | |||
if(data.exc) { | |||
if(opts.btn) { | |||
$(opts.btn).addClass("btn-danger"); | |||
@@ -145,7 +145,7 @@ $.extend(frappe, { | |||
</div>\ | |||
</div>\ | |||
</div>').appendTo(document.body); | |||
return modal; | |||
}, | |||
msgprint: function(html, title) { | |||
@@ -189,19 +189,19 @@ $.extend(frappe, { | |||
if(frappe.supports_pjax()) { | |||
// hack for chrome's onload popstate call | |||
window.initial_href = window.location.href | |||
$(document).on("click", "#wrap a", frappe.handle_click); | |||
$(window).on("popstate", function(event) { | |||
// hack for chrome's onload popstate call | |||
if(window.initial_href==location.href && window.previous_href==undefined) { | |||
frappe.set_force_reload(true); | |||
return; | |||
} | |||
window.previous_href = location.href; | |||
var state = event.originalEvent.state; | |||
if(state) { | |||
frappe.render_json(state); | |||
} else { | |||
@@ -213,7 +213,7 @@ $.extend(frappe, { | |||
handle_click: function(event) { | |||
// taken from jquery pjax | |||
var link = event.currentTarget | |||
if (link.tagName.toUpperCase() !== 'A') | |||
throw "using pjax requires an anchor element" | |||
@@ -221,7 +221,7 @@ $.extend(frappe, { | |||
// links in a new tab as normal. | |||
if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) | |||
return | |||
// Ignore cross origin links | |||
if ( location.protocol !== link.protocol || location.hostname !== link.hostname ) | |||
return | |||
@@ -230,15 +230,15 @@ $.extend(frappe, { | |||
if (link.hash && link.href.replace(link.hash, '') === | |||
location.href.replace(location.hash, '')) | |||
return | |||
// Ignore empty anchor "foo.html#" | |||
if (link.href === location.href + '#') | |||
return | |||
// our custom logic | |||
if (link.href.indexOf("cmd=")!==-1 || link.hasAttribute("no-pjax")) | |||
return | |||
event.preventDefault() | |||
frappe.load_via_ajax(link.href); | |||
@@ -247,12 +247,12 @@ $.extend(frappe, { | |||
// console.log("calling ajax"); | |||
window.previous_href = href; | |||
history.pushState(null, null, href); | |||
//NProgress.start(); | |||
$.ajax({ url: href, cache: false }).done(function(data) { | |||
history.replaceState(data, data.title, href); | |||
$("html, body").animate({ scrollTop: 0 }, "slow"); | |||
frappe.render_json(data); | |||
frappe.render_json(data); | |||
}).always(function() { | |||
//NProgress.done(); | |||
}).fail(function(xhr, status, error) { | |||
@@ -289,17 +289,17 @@ $.extend(frappe, { | |||
} | |||
}); | |||
if(data.title) $("title").html(data.title); | |||
// change id of current page | |||
$(".page-container").attr("id", "page-" + data.path); | |||
window.ga && ga('send', 'pageview', location.pathname); | |||
$(document).trigger("page-change"); | |||
} | |||
}, | |||
set_force_reload: function(reload) { | |||
// learned this from twitter's implementation | |||
window.history.replaceState({"reload": reload}, | |||
window.history.replaceState({"reload": reload}, | |||
window.document.title, location.href); | |||
}, | |||
supports_pjax: function() { | |||
@@ -345,19 +345,19 @@ $.extend(frappe, { | |||
$("[data-html-block='breadcrumbs'] .breadcrumb").toggleClass("hidden", | |||
!$("[data-html-block='breadcrumbs']").text().trim() || | |||
$("[data-html-block='breadcrumbs']").text().trim()==$("[data-html-block='header']").text().trim()); | |||
// to show full content width, when no sidebar content | |||
var sidebar_has_content = !!$("[data-html-block='sidebar']").html().trim(); | |||
$(".page-sidebar, .toggle-sidebar").toggleClass("hidden", !sidebar_has_content); | |||
$(".page-sidebar").toggleClass("col-sm-push-9", sidebar_has_content); | |||
$(".page-content").toggleClass("col-sm-12", !sidebar_has_content); | |||
$(".page-content").toggleClass("col-sm-9 col-sm-pull-3", sidebar_has_content); | |||
// var sidebar_has_content = !!$("[data-html-block='sidebar']").html().trim(); | |||
// $(".page-sidebar, .toggle-sidebar").toggleClass("hidden", !sidebar_has_content); | |||
// $(".page-sidebar").toggleClass("col-sm-push-9", sidebar_has_content); | |||
// $(".page-content").toggleClass("col-sm-12", !sidebar_has_content); | |||
// $(".page-content").toggleClass("col-sm-9 col-sm-pull-3", sidebar_has_content); | |||
// if everything in the sub-header is hidden, hide the sub-header | |||
var hide_sub_header = $(".page-sub-header .row").children().length === $(".page-sub-header .row").find(".hidden").length; | |||
$(".page-sub-header").toggleClass("hidden", hide_sub_header); | |||
// collapse sidebar in mobile view on page change | |||
if(!$(".page-sidebar").hasClass("hidden-xs")) { | |||
$(".toggle-sidebar").trigger("click"); | |||
@@ -388,7 +388,7 @@ function get_url_arg(name) { | |||
if(results == null) | |||
return ""; | |||
else | |||
return decodeURIComponent(results[1]); | |||
return decodeURIComponent(results[1]); | |||
} | |||
function make_query_string(obj) { | |||
@@ -468,7 +468,7 @@ function remove_script_and_style(txt) { | |||
} | |||
function is_html(txt) { | |||
if(txt.indexOf("<br>")==-1 && txt.indexOf("<p")==-1 | |||
if(txt.indexOf("<br>")==-1 && txt.indexOf("<p")==-1 | |||
&& txt.indexOf("<img")==-1 && txt.indexOf("<div")==-1) { | |||
return false; | |||
} | |||
@@ -491,21 +491,21 @@ $(document).ready(function() { | |||
window.logged_in = getCookie("sid") && getCookie("sid")!=="Guest"; | |||
$("#website-login").toggleClass("hide", logged_in ? true : false); | |||
$("#website-post-login").toggleClass("hide", logged_in ? false : true); | |||
$(".toggle-sidebar").on("click", function() { | |||
$(".page-sidebar").toggleClass("hidden-xs"); | |||
$(".toggle-sidebar i").toggleClass("icon-rotate-180"); | |||
}); | |||
// switch to app link | |||
if(getCookie("system_user")==="yes") { | |||
$("#website-post-login .dropdown-menu").append('<li class="divider"></li>\ | |||
<li><a href="/desk" no-pjax><i class="icon-fixed-width icon-th-large"></i> Switch To Desk</a></li>'); | |||
} | |||
frappe.render_user(); | |||
frappe.setup_push_state() | |||
$(document).trigger("page-change"); | |||
}); | |||
@@ -42,7 +42,9 @@ def build_sitemap_options(path): | |||
if not sitemap_options.no_sidebar: | |||
sitemap_options.children = get_route_children(sitemap_options.pathname, home_page) | |||
if not sitemap_options.children: | |||
if not sitemap_options.children and sitemap_options.parent_website_route \ | |||
and sitemap_options.parent_website_route!=home_page: | |||
sitemap_options.children = get_route_children(sitemap_options.parent_website_route, home_page) | |||
# determine templates to be used | |||
@@ -80,4 +82,4 @@ def get_route_children(pathname, home_page=None): | |||
children = [frappe.get_doc("Website Route", pathname)] + children | |||
return children | |||
return children |
@@ -1,5 +1,5 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# MIT License. See license.txt | |||
# metadata | |||
@@ -20,14 +20,14 @@ def get_meta(doctype, cached=True): | |||
if frappe.local.lang != 'en': | |||
meta.set("__messages", frappe.get_lang_dict("doctype", doctype)) | |||
return meta | |||
class FormMeta(Meta): | |||
def __init__(self, doctype): | |||
super(FormMeta, self).__init__(doctype) | |||
self.load_assets() | |||
def load_assets(self): | |||
self.expand_selects() | |||
self.add_search_fields() | |||
@@ -37,52 +37,52 @@ class FormMeta(Meta): | |||
self.add_code() | |||
self.load_print_formats() | |||
self.load_workflows() | |||
def as_dict(self): | |||
d = super(FormMeta, self).as_dict() | |||
def as_dict(self, no_nulls=False): | |||
d = super(FormMeta, self).as_dict(no_nulls=no_nulls) | |||
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", "__linked_with", "__messages"): | |||
d[k] = self.get(k) | |||
for i, df in enumerate(d.get("fields")): | |||
for k in ("link_doctype", "search_fields"): | |||
df[k] = self.get("fields")[i].get(k) | |||
return d | |||
def add_code(self): | |||
path = os.path.join(get_module_path(self.module), 'doctype', scrub(self.name)) | |||
def _get_path(fname): | |||
return os.path.join(path, scrub(fname)) | |||
self._add_code(_get_path(self.name + '.js'), '__js') | |||
self._add_code(_get_path(self.name + '.css'), "__css") | |||
self._add_code(_get_path(self.name + '_list.js'), '__list_js') | |||
self._add_code(_get_path(self.name + '_calendar.js'), '__calendar_js') | |||
self._add_code(_get_path(self.name + '_map.js'), '__map_js') | |||
self.add_custom_script() | |||
self.add_code_via_hook("doctype_js", "__js") | |||
def _add_code(self, path, fieldname): | |||
js = frappe.read_file(path) | |||
if js: | |||
self.set(fieldname, (self.get(fieldname) or "") + "\n\n" + render_jinja(js)) | |||
def add_code_via_hook(self, hook, fieldname): | |||
hook = "{}:{}".format(hook, self.name) | |||
for app_name in frappe.get_installed_apps(): | |||
for file in frappe.get_hooks(hook, app_name=app_name): | |||
path = frappe.get_app_path(app_name, *file.strip("/").split("/")) | |||
self._add_code(path, fieldname) | |||
def add_custom_script(self): | |||
"""embed all require files""" | |||
# custom script | |||
custom = frappe.db.get_value("Custom Script", {"dt": self.name, | |||
custom = frappe.db.get_value("Custom Script", {"dt": self.name, | |||
"script_type": "Client"}, "script") or "" | |||
self.set("__js", (self.get('__js') or '') + "\n\n" + custom) | |||
def render_jinja(content): | |||
if "{% include" in content: | |||
content = frappe.get_jenv().from_string(content).render() | |||
@@ -92,9 +92,9 @@ class FormMeta(Meta): | |||
for df in self.get("fields", {"fieldtype": "Select"}): | |||
if df.options and df.options.startswith("link:"): | |||
df.link_doctype = df.options.split("\n")[0][5:] | |||
df.options = '\n'.join([''] + [o.name for o in frappe.db.sql("""select | |||
df.options = '\n'.join([''] + [o.name for o in frappe.db.sql("""select | |||
name from `tab%s` where docstatus<2 order by name asc""" % df.link_doctype, as_dict=1)]) | |||
def add_search_fields(self): | |||
"""add search fields found in the doctypes indicated by link fields' options""" | |||
for df in self.get("fields", {"fieldtype": "Link", "options":["!=", "[Select]"]}): | |||
@@ -114,7 +114,7 @@ class FormMeta(Meta): | |||
links = dict(links) | |||
if not links: | |||
if not links: | |||
return {} | |||
ret = {} | |||
@@ -122,21 +122,21 @@ class FormMeta(Meta): | |||
for dt in links: | |||
ret[dt] = { "fieldname": links[dt] } | |||
for grand_parent, options in frappe.db.sql("""select parent, options from tabDocField | |||
where fieldtype="Table" | |||
and options in (select name from tabDocType | |||
for grand_parent, options in frappe.db.sql("""select parent, options from tabDocField | |||
where fieldtype="Table" | |||
and options in (select name from tabDocType | |||
where istable=1 and name in (%s))""" % ", ".join(["%s"] * len(links)) ,tuple(links)): | |||
ret[grand_parent] = {"child_doctype": options, "fieldname": links[options] } | |||
if options in ret: | |||
del ret[options] | |||
self.set("__linked_with", ret) | |||
def load_print_formats(self): | |||
frappe.response.docs.extend(frappe.db.sql("""select * FROM `tabPrint Format` | |||
WHERE doc_type=%s AND docstatus<2""", (self.name,), as_dict=1, update={"doctype":"Print Format"})) | |||
def load_workflows(self): | |||
# get active workflow | |||
workflow_name = get_workflow_name(self.name) | |||
@@ -144,14 +144,13 @@ class FormMeta(Meta): | |||
if workflow_name and frappe.db.exists("Workflow", workflow_name): | |||
workflow = frappe.get_doc("Workflow", workflow_name) | |||
frappe.response.docs.append(workflow) | |||
for d in workflow.get("workflow_document_states"): | |||
frappe.response.docs.append(frappe.get_doc("Workflow State", d.state)) | |||
def render_jinja(content): | |||
if "{% include" in content: | |||
content = frappe.get_jenv().from_string(content).render() | |||
return content | |||