Ver código fonte

Merge pull request #651 from frappe/wip-4.1

Wip 4.1
version-14
Anand Doshi 11 anos atrás
pai
commit
1804f7133d
90 arquivos alterados com 1489 adições e 1796 exclusões
  1. +1
    -1
      frappe/__init__.py
  2. +25
    -18
      frappe/build.py
  3. +10
    -17
      frappe/cli.py
  4. +1
    -0
      frappe/core/doctype/communication/communication.py
  5. +4
    -0
      frappe/core/doctype/doctype/doctype.py
  6. +79
    -62
      frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json
  7. +3
    -0
      frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py
  8. +1
    -1
      frappe/core/page/desktop/desktop.css
  9. +12
    -2
      frappe/core/page/permission_manager/permission_manager.js
  10. +5
    -1
      frappe/core/page/user_permissions/user_permissions.js
  11. +1
    -0
      frappe/exceptions.py
  12. +23
    -22
      frappe/hooks.py
  13. +8
    -6
      frappe/installer.py
  14. +1
    -1
      frappe/model/base_document.py
  15. +1
    -1
      frappe/model/mapper.py
  16. +27
    -27
      frappe/model/sync.py
  17. +18
    -5
      frappe/modules/__init__.py
  18. +3
    -0
      frappe/patches.txt
  19. +2
    -2
      frappe/patches/v4_0/rename_sitemap_to_route.py
  20. +21
    -20
      frappe/patches/v4_0/set_website_route_idx.py
  21. +3
    -3
      frappe/patches/v4_0/website_sitemap_hierarchy.py
  22. +3
    -1
      frappe/public/build.json
  23. +17
    -0
      frappe/public/html/print_template.html
  24. +47
    -76
      frappe/public/js/frappe/form/grid.js
  25. +0
    -1
      frappe/public/js/frappe/misc/user.js
  26. +1
    -1
      frappe/public/js/frappe/misc/utils.js
  27. +4
    -3
      frappe/public/js/frappe/provide.js
  28. +1
    -1
      frappe/public/js/frappe/request.js
  29. +8
    -1
      frappe/public/js/frappe/views/doclistview.js
  30. +33
    -1
      frappe/public/js/frappe/views/query_report.js
  31. +2
    -0
      frappe/public/js/legacy/datatype.js
  32. +34
    -0
      frappe/public/js/lib/microtemplate.js
  33. +6
    -9
      frappe/templates/base.html
  34. +1
    -1
      frappe/templates/emails/new_message.html
  35. +3
    -3
      frappe/templates/emails/new_user.html
  36. +3
    -260
      frappe/templates/emails/standard.html
  37. +41
    -0
      frappe/templates/form_grid/fields.html
  38. +0
    -2
      frappe/templates/generators/blog_category.py
  39. +0
    -81
      frappe/templates/generators/blog_post.py
  40. +0
    -44
      frappe/templates/generators/web_page.py
  41. +0
    -240
      frappe/templates/generators/website_group.py
  42. +1
    -2
      frappe/templates/includes/blog.js
  43. +6
    -1
      frappe/templates/includes/comments.html
  44. +1
    -1
      frappe/templates/includes/comments.py
  45. +1
    -0
      frappe/templates/includes/login.js
  46. +5
    -0
      frappe/templates/includes/meta_block.html
  47. +1
    -2
      frappe/templates/includes/navbar.html
  48. +3
    -3
      frappe/templates/includes/sidebar.html
  49. +1
    -1
      frappe/templates/includes/static_index.html
  50. +2
    -2
      frappe/templates/pages/404.py
  51. +1
    -0
      frappe/templates/pages/blog.py
  52. +2
    -2
      frappe/templates/pages/login.html
  53. +10
    -12
      frappe/templates/pages/sitemap.py
  54. +19
    -19
      frappe/templates/website_group/forum.py
  55. +2
    -2
      frappe/templates/website_group/post.py
  56. +21
    -21
      frappe/templates/website_group/settings.py
  57. +6
    -0
      frappe/utils/boilerplate.py
  58. +2
    -1
      frappe/utils/email_lib/bulk.py
  59. +12
    -16
      frappe/utils/email_lib/email_body.py
  60. +6
    -2
      frappe/utils/fixtures.py
  61. +0
    -5
      frappe/website/__init__.py
  62. +7
    -3
      frappe/website/context.py
  63. +9
    -3
      frappe/website/doctype/blog_category/blog_category.py
  64. +3
    -2
      frappe/website/doctype/blog_post/blog_post.json
  65. +80
    -6
      frappe/website/doctype/blog_post/blog_post.py
  66. +1
    -1
      frappe/website/doctype/post/post.py
  67. +16
    -38
      frappe/website/doctype/web_page/test_web_page.py
  68. +1
    -0
      frappe/website/doctype/web_page/web_page.js
  69. +133
    -126
      frappe/website/doctype/web_page/web_page.json
  70. +32
    -3
      frappe/website/doctype/web_page/web_page.py
  71. +238
    -5
      frappe/website/doctype/website_group/website_group.py
  72. +14
    -7
      frappe/website/doctype/website_route/website_route.json
  73. +34
    -82
      frappe/website/doctype/website_route/website_route.py
  74. +0
    -1
      frappe/website/doctype/website_slideshow/website_slideshow.py
  75. +0
    -0
      frappe/website/doctype/website_template/__init__.py
  76. +0
    -130
      frappe/website/doctype/website_template/website_template.json
  77. +0
    -142
      frappe/website/doctype/website_template/website_template.py
  78. +14
    -0
      frappe/website/js/website.js
  79. +10
    -92
      frappe/website/page/sitemap_browser/sitemap_browser.js
  80. +3
    -1
      frappe/website/render.py
  81. +16
    -11
      frappe/website/sitemap.py
  82. +89
    -50
      frappe/website/statics.py
  83. +116
    -0
      frappe/website/sync.py
  84. +22
    -2
      frappe/website/template.py
  85. +6
    -0
      frappe/website/utils.py
  86. +62
    -74
      frappe/website/website_generator.py
  87. +14
    -2
      frappe/widgets/form/meta.py
  88. +12
    -7
      frappe/widgets/query_report.py
  89. +2
    -0
      frappe/widgets/reportview.py
  90. +0
    -2
      requirements.txt

+ 1
- 1
frappe/__init__.py Ver arquivo

@@ -377,7 +377,7 @@ def get_module(modulename):
return importlib.import_module(modulename) return importlib.import_module(modulename)


def scrub(txt): def scrub(txt):
return txt.replace(' ','_').replace('-', '_').replace('/', '_').lower()
return txt.replace(' ','_').replace('-', '_').lower()


def unscrub(txt): def unscrub(txt):
return txt.replace('_',' ').replace('-', ' ').title() return txt.replace('_',' ').replace('-', ' ').title()


+ 25
- 18
frappe/build.py Ver arquivo

@@ -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
from frappe.utils.minify import JavascriptMinify from frappe.utils.minify import JavascriptMinify
@@ -16,7 +16,7 @@ def bundle(no_compress, make_copy=False):
# build js files # build js files
make_asset_dirs(make_copy=make_copy) make_asset_dirs(make_copy=make_copy)
build(no_compress) build(no_compress)
def watch(no_compress): def watch(no_compress):
"""watch and rebuild if necessary""" """watch and rebuild if necessary"""
import time import time
@@ -25,18 +25,18 @@ def watch(no_compress):
while True: while True:
if files_dirty(): if files_dirty():
build(no_compress=True) build(no_compress=True)
time.sleep(3) time.sleep(3)


def make_asset_dirs(make_copy=False): def make_asset_dirs(make_copy=False):
assets_path = os.path.join(frappe.local.sites_path, "assets") assets_path = os.path.join(frappe.local.sites_path, "assets")
for dir_path in [ for dir_path in [
os.path.join(assets_path, 'js'),
os.path.join(assets_path, 'js'),
os.path.join(assets_path, 'css')]: os.path.join(assets_path, 'css')]:
if not os.path.exists(dir_path): if not os.path.exists(dir_path):
os.makedirs(dir_path) os.makedirs(dir_path)
# symlink app/public > assets/app # symlink app/public > assets/app
for app_name in frappe.get_all_apps(True): for app_name in frappe.get_all_apps(True):
pymodule = frappe.get_module(app_name) pymodule = frappe.get_module(app_name)
@@ -53,7 +53,7 @@ def build(no_compress=False):
assets_path = os.path.join(frappe.local.sites_path, "assets") assets_path = os.path.join(frappe.local.sites_path, "assets")


for target, sources in get_build_maps().iteritems(): for target, sources in get_build_maps().iteritems():
pack(os.path.join(assets_path, target), sources, no_compress)
pack(os.path.join(assets_path, target), sources, no_compress)


shutil.copy(os.path.join(os.path.dirname(os.path.abspath(frappe.__file__)), 'data', 'languages.txt'), frappe.local.sites_path) shutil.copy(os.path.join(os.path.dirname(os.path.abspath(frappe.__file__)), 'data', 'languages.txt'), frappe.local.sites_path)
# reset_app_html() # reset_app_html()
@@ -79,39 +79,46 @@ def get_build_maps():
else: else:
s = os.path.join(app_path, source) s = os.path.join(app_path, source)
source_paths.append(s) source_paths.append(s)
build_maps[target] = source_paths build_maps[target] = source_paths
except Exception, e: except Exception, e:
print path print path
raise raise
return build_maps return build_maps


timestamps = {} timestamps = {}


def pack(target, sources, no_compress): def pack(target, sources, no_compress):
from cStringIO import StringIO from cStringIO import StringIO
outtype, outtxt = target.split(".")[-1], '' outtype, outtxt = target.split(".")[-1], ''
jsm = JavascriptMinify() jsm = JavascriptMinify()
for f in sources: for f in sources:
suffix = None suffix = None
if ':' in f: f, suffix = f.split(':') if ':' in f: f, suffix = f.split(':')
if not os.path.exists(f) or os.path.isdir(f): continue if not os.path.exists(f) or os.path.isdir(f): continue
timestamps[f] = os.path.getmtime(f) timestamps[f] = os.path.getmtime(f)
try: try:
with open(f, 'r') as sourcefile:
with open(f, 'r') as sourcefile:
data = unicode(sourcefile.read(), 'utf-8', errors='ignore') data = unicode(sourcefile.read(), 'utf-8', errors='ignore')
if outtype=="js" and (not no_compress) and suffix!="concat" and (".min." not in f):

extn = f.rsplit(".", 1)[1]

if outtype=="js" and extn=="js" and (not no_compress) and suffix!="concat" and (".min." not in f):
tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO() tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO()
jsm.minify(tmpin, tmpout) jsm.minify(tmpin, tmpout)
outtxt += unicode(tmpout.getvalue() or '', 'utf-8').strip('\n') + ';' outtxt += unicode(tmpout.getvalue() or '', 'utf-8').strip('\n') + ';'
elif outtype=="js" and extn=="html":
# add to frappe.templates
content = data.replace("\n", " ").replace("'", "\'")
outtxt += """frappe.templates["{key}"] = '{content}';\n""".format(\
key=f.rsplit("/", 1)[1][:-5], content=content)
else: else:
outtxt += ('\n/*\n *\t%s\n */' % f) outtxt += ('\n/*\n *\t%s\n */' % f)
outtxt += '\n' + data + '\n' outtxt += '\n' + data + '\n'
except Exception, e: except Exception, e:
print "--Error in:" + f + "--" print "--Error in:" + f + "--"
print frappe.get_traceback() print frappe.get_traceback()
@@ -119,10 +126,10 @@ def pack(target, sources, no_compress):
if not no_compress and outtype == 'css': if not no_compress and outtype == 'css':
pass pass
#outtxt = cssmin(outtxt) #outtxt = cssmin(outtxt)
with open(target, 'w') as f: with open(target, 'w') as f:
f.write(outtxt.encode("utf-8")) f.write(outtxt.encode("utf-8"))
print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024))) print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024)))


def files_dirty(): def files_dirty():
@@ -135,4 +142,4 @@ def files_dirty():
return True return True
else: else:
return False return False

+ 10
- 17
frappe/cli.py Ver arquivo

@@ -230,7 +230,7 @@ def setup_utilities(parser):
# clear # clear
parser.add_argument("--clear_web", default=False, action="store_true", parser.add_argument("--clear_web", default=False, action="store_true",
help="Clear website cache") help="Clear website cache")
parser.add_argument("--build_sitemap", default=False, action="store_true",
parser.add_argument("--build_website", default=False, action="store_true",
help="Build Website Route") help="Build Website Route")
parser.add_argument("--sync_statics", default=False, action="store_true", parser.add_argument("--sync_statics", default=False, action="store_true",
help="Sync files from templates/statics to Web Pages") help="Sync files from templates/statics to Web Pages")
@@ -352,7 +352,7 @@ def add_to_installed_apps(*apps):
all_apps = frappe.get_all_apps(with_frappe=True) all_apps = frappe.get_all_apps(with_frappe=True)
for each in apps: for each in apps:
if each in all_apps: if each in all_apps:
add_to_installed_apps(each, rebuild_sitemap=False)
add_to_installed_apps(each, rebuild_website=False)
frappe.destroy() frappe.destroy()


@cmd @cmd
@@ -402,13 +402,11 @@ def update(remote=None, branch=None, reload_gunicorn=False):
subprocess.check_output("killall -HUP gunicorn".split()) subprocess.check_output("killall -HUP gunicorn".split())


@cmd @cmd
def latest(rebuild_website_config=True, quiet=False):
def latest(rebuild_website=True, quiet=False):
import frappe.modules.patch_handler import frappe.modules.patch_handler
import frappe.model.sync import frappe.model.sync
from frappe.website import rebuild_config
from frappe.utils.fixtures import sync_fixtures from frappe.utils.fixtures import sync_fixtures
import frappe.translate import frappe.translate
from frappe.website import statics


verbose = not quiet verbose = not quiet


@@ -419,17 +417,11 @@ def latest(rebuild_website_config=True, quiet=False):
frappe.modules.patch_handler.run_all() frappe.modules.patch_handler.run_all()
# sync # sync
frappe.model.sync.sync_all(verbose=verbose) frappe.model.sync.sync_all(verbose=verbose)
sync_fixtures()

sync = statics.sync()
sync.start()
sync.start(rebuild=True)
# build website config if any changes in templates etc.
if rebuild_website_config:
rebuild_config()

frappe.translate.clear_cache() frappe.translate.clear_cache()
sync_fixtures()


if rebuild_website:
build_website()
finally: finally:
frappe.destroy() frappe.destroy()


@@ -561,10 +553,11 @@ def clear_all_sessions():
frappe.destroy() frappe.destroy()


@cmd @cmd
def build_sitemap():
from frappe.website import rebuild_config
def build_website():
import frappe.website.sync
frappe.connect() frappe.connect()
rebuild_config()
frappe.website.sync.sync()
frappe.db.commit()
frappe.destroy() frappe.destroy()


@cmd @cmd


+ 1
- 0
frappe/core/doctype/communication/communication.py Ver arquivo

@@ -110,6 +110,7 @@ def get_customer_supplier(args=None):
def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', send_me_a_copy=False): def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', send_me_a_copy=False):
footer = None footer = None



if sent_via: if sent_via:
if hasattr(sent_via, "get_sender"): if hasattr(sent_via, "get_sender"):
d.sender = sent_via.get_sender(d) or d.sender d.sender = sent_via.get_sender(d) or d.sender


+ 4
- 0
frappe/core/doctype/doctype/doctype.py Ver arquivo

@@ -13,6 +13,10 @@ from frappe.model.document import Document
from frappe.model.db_schema import type_map from frappe.model.db_schema import type_map
from frappe.core.doctype.property_setter.property_setter import make_property_setter from frappe.core.doctype.property_setter.property_setter import make_property_setter


form_grid_templates = {
"fields": "templates/form_grid/fields.html"
}

class DocType(Document): class DocType(Document):
def validate(self): def validate(self):
if not frappe.conf.get("developer_mode"): if not frappe.conf.get("developer_mode"):


+ 79
- 62
frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json Ver arquivo

@@ -1,88 +1,105 @@
{ {
"allow_copy": 1,
"creation": "2014-03-03 19:48:01.000000",
"description": "Email Settings for Outgoing and Incoming Emails.",
"docstatus": 0,
"doctype": "DocType",
"allow_copy": 1,
"creation": "2014-03-03 19:48:01",
"description": "Email Settings for Outgoing and Incoming Emails.",
"docstatus": 0,
"doctype": "DocType",
"fields": [ "fields": [
{ {
"description": "SMTP Server (e.g. smtp.gmail.com)",
"fieldname": "mail_server",
"fieldtype": "Data",
"label": "Outgoing Mail Server",
"description": "SMTP Server (e.g. smtp.gmail.com)",
"fieldname": "mail_server",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Outgoing Mail Server",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "<a href=\"https://en.wikipedia.org/wiki/Transport_Layer_Security\" target=\"_blank\">[?]</a>",
"fieldname": "use_ssl",
"fieldtype": "Check",
"label": "Use TLS",
"description": "<a href=\"https://en.wikipedia.org/wiki/Transport_Layer_Security\" target=\"_blank\">[?]</a>",
"fieldname": "use_ssl",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Use TLS",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "If non standard port (e.g. 587)",
"fieldname": "mail_port",
"fieldtype": "Int",
"label": "Port",
"description": "If non standard port (e.g. 587)",
"fieldname": "mail_port",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Port",
"permlevel": 0 "permlevel": 0
},
},
{ {
"fieldname": "cb0",
"fieldtype": "Column Break",
"fieldname": "cb0",
"fieldtype": "Column Break",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "Set Login and Password if authentication is required.",
"fieldname": "mail_login",
"fieldtype": "Data",
"label": "Login Id",
"description": "Set Login and Password if authentication is required.",
"fieldname": "mail_login",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Login Id",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "Check this if you want to send emails as this id only (in case of restriction by your email provider).",
"fieldname": "always_use_login_id_as_sender",
"fieldtype": "Check",
"label": "Always use above Login Id as sender",
"description": "Check this if you want to send emails as this id only (in case of restriction by your email provider).",
"fieldname": "always_use_login_id_as_sender",
"fieldtype": "Check",
"label": "Always use above Login Id as sender",
"permlevel": 0 "permlevel": 0
},
},
{ {
"fieldname": "mail_password",
"fieldtype": "Password",
"label": "Mail Password",
"fieldname": "mail_password",
"fieldtype": "Password",
"label": "Mail Password",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "System generated mails will be sent from this email id.",
"fieldname": "auto_email_id",
"fieldtype": "Data",
"label": "Auto Email Id",
"description": "System generated mails will be sent from this email id.",
"fieldname": "auto_email_id",
"fieldtype": "Data",
"label": "Auto Email Id",
"permlevel": 0 "permlevel": 0
},
},
{ {
"default": "1",
"description": "If checked, an email with an attached HTML format will be added to part of the EMail body as well as attachment. To only send as attachment, uncheck this.",
"fieldname": "send_print_in_body_and_attachment",
"fieldtype": "Check",
"label": "Send Print in Body and Attachment",
"default": "1",
"description": "If checked, an email with an attached HTML format will be added to part of the EMail body as well as attachment. To only send as attachment, uncheck this.",
"fieldname": "send_print_in_body_and_attachment",
"fieldtype": "Check",
"label": "Send Print in Body and Attachment",
"permlevel": 0 "permlevel": 0
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"permlevel": 0
},
{
"default": "<div style=\"padding: 7px; text-align: right; color: #888\"><small>Sent via \n\t<a style=\"color: #888\" href=\"http://frappe.io\">Frappe</a></div>",
"fieldname": "footer",
"fieldtype": "Text Editor",
"label": "Email Footer",
"permlevel": 0,
"reqd": 0
} }
],
"icon": "icon-cog",
"idx": 1,
"in_create": 1,
"issingle": 1,
"modified": "2014-03-03 20:20:09.000000",
"modified_by": "Administrator",
"module": "Core",
"name": "Outgoing Email Settings",
"owner": "Administrator",
],
"icon": "icon-cog",
"idx": 1,
"in_create": 1,
"issingle": 1,
"modified": "2014-06-26 02:15:27.070400",
"modified_by": "Administrator",
"module": "Core",
"name": "Outgoing Email Settings",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"create": 1,
"permlevel": 0,
"read": 1,
"role": "System Manager",
"create": 1,
"permlevel": 0,
"read": 1,
"role": "System Manager",
"write": 1 "write": 1
} }
] ]
}
}

+ 3
- 0
frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py Ver arquivo

@@ -23,3 +23,6 @@ class OutgoingEmailSettings(Document):


# exceptions are handled in session connect # exceptions are handled in session connect
sess = smtpserver.sess sess = smtpserver.sess

def get_mail_footer():
return frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "footer") or ""

+ 1
- 1
frappe/core/page/desktop/desktop.css Ver arquivo

@@ -20,7 +20,7 @@


@media (max-width: 768px) { @media (max-width: 768px) {
.case-wrapper { .case-wrapper {
margin: 12px;
margin: 9px;
width: 70px; width: 70px;
height: 80px; height: 80px;
} }


+ 12
- 2
frappe/core/page/permission_manager/permission_manager.js Ver arquivo

@@ -35,6 +35,7 @@ frappe.PermissionEngine = Class.extend({
me.setup_appframe(); me.setup_appframe();
} }
}); });

}, },
setup_appframe: function() { setup_appframe: function() {
var me = this; var me = this;
@@ -192,8 +193,8 @@ frappe.PermissionEngine = Class.extend({
} }


var checkbox = $("<div class='col-md-4'><div class='checkbox'>\ var checkbox = $("<div class='col-md-4'><div class='checkbox'>\
<label><input type='checkbox'>"+__(label)+"</input></label>\
</div></div>").appendTo(cell)
<label><input type='checkbox'>"+__(label)+"</input></label>"
+ (d.help || "") + "</div></div>").appendTo(cell)
.attr("data-fieldname", fieldname) .attr("data-fieldname", fieldname)
.css("text-transform", "capitalize"); .css("text-transform", "capitalize");


@@ -214,9 +215,12 @@ frappe.PermissionEngine = Class.extend({
me.set_show_users(role_cell, d.role); me.set_show_users(role_cell, d.role);


if (d.permlevel===0) { if (d.permlevel===0) {
d.help = '<div style="margin-left: 20px;">\
<a class="show-user-permissions small">Show User Pemissions</a></div>';
add_check(role_cell, d, "apply_user_permissions") add_check(role_cell, d, "apply_user_permissions")
.removeClass("col-md-4") .removeClass("col-md-4")
.css({"margin-top": "15px"}); .css({"margin-top": "15px"});
d.help = "";
} }


var cell = add_cell(row, d, "permlevel"); var cell = add_cell(row, d, "permlevel");
@@ -290,6 +294,12 @@ frappe.PermissionEngine = Class.extend({
}, },
add_check_events: function() { add_check_events: function() {
var me = this; var me = this;

this.body.on("click", ".show-user-permissions", function() {
frappe.route_options = { doctype: me.get_doctype() || "" };
frappe.set_route("user-permissions");
});

this.body.on("click", "input[type='checkbox']", function() { this.body.on("click", "input[type='checkbox']", function() {
var chk = $(this); var chk = $(this);
var args = { var args = {


+ 5
- 1
frappe/core/page/user_permissions/user_permissions.js Ver arquivo

@@ -2,7 +2,7 @@ frappe.pages['user-permissions'].onload = function(wrapper) {
frappe.ui.make_app_page({ frappe.ui.make_app_page({
parent: wrapper, parent: wrapper,
title: "User Permissions Manager", title: "User Permissions Manager",
icon: "icon-user",
icon: "icon-shield",
single_column: true single_column: true
}); });
$(wrapper).find(".layout-main").html("<div class='user-settings' style='min-height: 200px;'></div>\ $(wrapper).find(".layout-main").html("<div class='user-settings' style='min-height: 200px;'></div>\
@@ -45,6 +45,10 @@ frappe.UserPermissions = Class.extend({
}, },
make: function() { make: function() {
var me = this; var me = this;
this.wrapper.appframe.add_primary_action("Role Permissions", function() {
frappe.route_options = { doctype: me.get_doctype() || "" };
frappe.set_route("permission-manager");
}, "icon-lock");
return frappe.call({ return frappe.call({
module:"frappe.core", module:"frappe.core",
page:"user_permissions", page:"user_permissions",


+ 1
- 0
frappe/exceptions.py Ver arquivo

@@ -44,3 +44,4 @@ class DocstatusTransitionError(ValidationError): pass
class TimestampMismatchError(ValidationError): pass class TimestampMismatchError(ValidationError): pass
class EmptyTableError(ValidationError): pass class EmptyTableError(ValidationError): pass
class LinkExistsError(ValidationError): pass class LinkExistsError(ValidationError): pass
class InvalidEmailAddressError(ValidationError): pass

+ 23
- 22
frappe/hooks.py Ver arquivo

@@ -26,7 +26,7 @@ web_include_css = [
"style_settings.css" "style_settings.css"
] ]


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


write_file_keys = ["file_url", "file_name"] write_file_keys = ["file_url", "file_name"]


@@ -34,34 +34,33 @@ notification_config = "frappe.core.notifications.get_notification_config"


before_tests = "frappe.utils.install.before_tests" before_tests = "frappe.utils.install.before_tests"


website_generators = ["Web Page", "Blog Post", "Website Group", "Blog Category"]

# permissions # permissions


permission_query_conditions = { permission_query_conditions = {
"Event": "frappe.core.doctype.event.event.get_permission_query_conditions",
"ToDo": "frappe.core.doctype.todo.todo.get_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 = { has_permission = {
"Event": "frappe.core.doctype.event.event.has_permission",
"ToDo": "frappe.core.doctype.todo.todo.has_permission"
}

# bean

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


doc_events = { 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"
}
"*": {
"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.website.doctype.website_group.website_group.clear_cache_on_doc_event"
},
"Website Route Permission": {
"on_update": "frappe.website.doctype.website_group.website_group.clear_cache_on_doc_event"
} }
}


scheduler_events = { scheduler_events = {
"all": ["frappe.utils.email_lib.bulk.flush"], "all": ["frappe.utils.email_lib.bulk.flush"],
@@ -72,6 +71,8 @@ scheduler_events = {
"frappe.sessions.clear_expired_sessions", "frappe.sessions.clear_expired_sessions",
], ],
"hourly": [ "hourly": [
"frappe.templates.generators.website_group.clear_event_cache"
"frappe.website.doctype.website_group.website_group.clear_event_cache"
] ]
} }

mail_footer = "frappe.core.doctype.outgoing_email_settings.outgoing_email_settings.get_mail_footer"

+ 8
- 6
frappe/installer.py Ver arquivo

@@ -10,8 +10,8 @@ import os, json
import frappe import frappe
import frappe.database import frappe.database
import getpass import getpass
from frappe import _
from frappe.model.db_schema import DbManager from frappe.model.db_schema import DbManager
import frappe.website.sync
from frappe.model.sync import sync_for from frappe.model.sync import sync_for
from frappe.utils.fixtures import sync_fixtures from frappe.utils.fixtures import sync_fixtures


@@ -116,20 +116,22 @@ def install_app(name, verbose=False, set_as_patched=True):
for after_install in app_hooks.after_install or []: for after_install in app_hooks.after_install or []:
frappe.get_attr(after_install)() frappe.get_attr(after_install)()


sync_fixtures()
print "Installing Fixtures..."
sync_fixtures(name)


frappe.flags.in_install_app = False frappe.flags.in_install_app = False


def add_to_installed_apps(app_name, rebuild_sitemap=True):
def add_to_installed_apps(app_name, rebuild_website=True):
installed_apps = frappe.get_installed_apps() installed_apps = frappe.get_installed_apps()
if not app_name in installed_apps: if not app_name in installed_apps:
installed_apps.append(app_name) installed_apps.append(app_name)
frappe.db.set_global("installed_apps", json.dumps(installed_apps)) frappe.db.set_global("installed_apps", json.dumps(installed_apps))
frappe.db.commit() frappe.db.commit()


if rebuild_sitemap:
from frappe.website.doctype.website_template.website_template import rebuild_website_template
rebuild_website_template()
if rebuild_website:
frappe.website.sync.sync()

frappe.db.commit()


frappe.clear_cache() frappe.clear_cache()




+ 1
- 1
frappe/model/base_document.py Ver arquivo

@@ -292,7 +292,7 @@ class BaseDocument(object):
return return


for df in self.meta.get_select_fields(): for df in self.meta.get_select_fields():
if not (self.get(df.fieldname) and df.options):
if df.fieldname=="naming_series" or not (self.get(df.fieldname) and df.options):
continue continue


options = (df.options or "").split("\n") options = (df.options or "").split("\n")


+ 1
- 1
frappe/model/mapper.py Ver arquivo

@@ -22,7 +22,7 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None,
elif isinstance(target_doc, basestring): elif isinstance(target_doc, basestring):
target_doc = frappe.get_doc(json.loads(target_doc)) target_doc = frappe.get_doc(json.loads(target_doc))


if not target_doc.has_permission("create"):
if not ignore_permissions and not target_doc.has_permission("create"):
target_doc.raise_no_permission_to("create") target_doc.raise_no_permission_to("create")


map_doc(source_doc, target_doc, table_maps[source_doc.doctype]) map_doc(source_doc, target_doc, table_maps[source_doc.doctype])


+ 27
- 27
frappe/model/sync.py Ver arquivo

@@ -9,7 +9,6 @@ from __future__ import unicode_literals
import frappe import frappe
import os, sys import os, sys
from frappe.modules.import_file import import_file_by_path from frappe.modules.import_file import import_file_by_path
from frappe.utils import get_path, cstr
from frappe.modules.patch_handler import block_user from frappe.modules.patch_handler import block_user


def sync_all(force=0, verbose=False): def sync_all(force=0, verbose=False):
@@ -23,40 +22,41 @@ def sync_all(force=0, verbose=False):
frappe.clear_cache() frappe.clear_cache()


def sync_for(app_name, force=0, sync_everything = False, verbose=False): def sync_for(app_name, force=0, sync_everything = False, verbose=False):
files = []
for module_name in frappe.local.app_modules.get(app_name) or []: for module_name in frappe.local.app_modules.get(app_name) or []:
folder = os.path.dirname(frappe.get_module(app_name + "." + module_name).__file__) folder = os.path.dirname(frappe.get_module(app_name + "." + module_name).__file__)
walk_and_sync(folder, force, sync_everything, verbose=verbose)
files += get_doc_files(folder, force, sync_everything, verbose=verbose)


def walk_and_sync(start_path, force=0, sync_everything = False, verbose=False):
"""walk and sync all doctypes and pages"""

modules = []

document_type = ['doctype', 'page', 'report', 'print_format']
l = len(files)
if l:
for i, doc_path in enumerate(files):
if import_file_by_path(doc_path, force=force) and verbose:
complete = int(float(i+1) / l * 40)
sys.stdout.write("\rSyncing {0}: [{1}{2}]".format(app_name, "="*complete, " "*(40-complete)))
sys.stdout.flush()
#print module_name + ' | ' + doctype + ' | ' + name


for path, folders, files in os.walk(start_path):
# sort folders so that doctypes are synced before pages or reports
frappe.db.commit()


for dontwalk in (".git", "locale", "public"):
if dontwalk in folders:
folders.remove(dontwalk)
print ""


folders.sort()


if sync_everything or (os.path.basename(os.path.dirname(path)) in document_type):
for f in files:
f = cstr(f)
if f.endswith(".json"):
doc_name = f.split(".json")[0]
if doc_name == os.path.basename(path):
def get_doc_files(start_path, force=0, sync_everything = False, verbose=False):
"""walk and sync all doctypes and pages"""


module_name = path.split(os.sep)[-3]
doctype = path.split(os.sep)[-2]
name = path.split(os.sep)[-1]
out = []
document_type = ['doctype', 'page', 'report', 'print_format']
for doctype in document_type:
doctype_path = os.path.join(start_path, doctype)
if os.path.exists(doctype_path):


if import_file_by_path(os.path.join(path, f), force=force) and verbose:
print module_name + ' | ' + doctype + ' | ' + name
# Note: sorted is a hack because custom* and doc* need
# be synced first


frappe.db.commit()
for docname in sorted(os.listdir(doctype_path)):
if os.path.isdir(os.path.join(doctype_path, docname)):
doc_path = os.path.join(doctype_path, docname, docname) + ".json"
if os.path.exists(doc_path):
out.append(doc_path)


return modules
return out

+ 18
- 5
frappe/modules/__init__.py Ver arquivo

@@ -44,13 +44,26 @@ def export_doc(doctype, name, module=None):
def get_doctype_module(doctype): def get_doctype_module(doctype):
return frappe.db.get_value('DocType', doctype, 'module') or "core" return frappe.db.get_value('DocType', doctype, 'module') or "core"


doctype_python_modules = {}
def load_doctype_module(doctype, module=None, prefix=""): def load_doctype_module(doctype, module=None, prefix=""):
if not module: if not module:
module = get_doctype_module(doctype) module = get_doctype_module(doctype)
return frappe.get_module(get_module_name(doctype, module, prefix))


def get_module_name(doctype, module, prefix=""):
from frappe.modules import scrub
app = get_module_app(module)

key = (app, doctype, prefix)

if key not in doctype_python_modules:
doctype_python_modules[key] = frappe.get_module(get_module_name(doctype, module, prefix))

return doctype_python_modules[key]

def get_module_name(doctype, module, prefix="", app=None):
return '{app}.{module}.doctype.{doctype}.{prefix}{doctype}'.format(\ return '{app}.{module}.doctype.{doctype}.{prefix}{doctype}'.format(\
app = scrub(frappe.local.module_app[scrub(module)]),
module = scrub(module), doctype = scrub(doctype), prefix=prefix)
app = scrub(app or get_module_app(module)),
module = scrub(module),
doctype = scrub(doctype),
prefix=prefix)

def get_module_app(module):
return frappe.local.module_app[scrub(module)]

+ 3
- 0
frappe/patches.txt Ver arquivo

@@ -41,4 +41,7 @@ frappe.patches.v4_0.fix_attach_field_file_url
execute:frappe.reset_perms("User") #2014-06-13 execute:frappe.reset_perms("User") #2014-06-13
execute:frappe.db.sql("""delete from `tabUserRole` where ifnull(parentfield, '')=''""") #2014-06-17 execute:frappe.db.sql("""delete from `tabUserRole` where ifnull(parentfield, '')=''""") #2014-06-17
frappe.patches.v4_0.remove_user_owner_custom_field frappe.patches.v4_0.remove_user_owner_custom_field
execute:frappe.delete_doc("DocType", "Website Template")
execute:frappe.reload_doc('website', 'doctype', 'website_route') #2014-06-17
execute:frappe.db.sql("""update `tabProperty Setter` set property_type='Text' where property in ('options', 'default')""") #2014-06-20 execute:frappe.db.sql("""update `tabProperty Setter` set property_type='Text' where property in ('options', 'default')""") #2014-06-20


+ 2
- 2
frappe/patches/v4_0/rename_sitemap_to_route.py Ver arquivo

@@ -17,11 +17,11 @@ def execute():
frappe.reload_doc("website", "doctype", frappe.scrub(d)) frappe.reload_doc("website", "doctype", frappe.scrub(d))
rename_field_if_exists(d, "parent_website_sitemap", "parent_website_route") rename_field_if_exists(d, "parent_website_sitemap", "parent_website_route")


frappe.reload_doc("website", "doctype", "website_template")
#frappe.reload_doc("website", "doctype", "website_template")
frappe.reload_doc("website", "doctype", "website_route") frappe.reload_doc("website", "doctype", "website_route")
frappe.reload_doc("website", "doctype", "website_route_permission") frappe.reload_doc("website", "doctype", "website_route_permission")


rename_field_if_exists("Website Route", "website_sitemap_config", "website_template")
#rename_field_if_exists("Website Route", "website_sitemap_config", "website_template")
rename_field_if_exists("Website Route Permission", "website_sitemap", "website_route") rename_field_if_exists("Website Route Permission", "website_sitemap", "website_route")


for d in ("blog_category", "blog_post", "web_page", "website_route", "website_group", "post", "user_vote"): for d in ("blog_category", "blog_post", "web_page", "website_route", "website_group", "post", "user_vote"):


+ 21
- 20
frappe/patches/v4_0/set_website_route_idx.py Ver arquivo

@@ -1,23 +1,24 @@
import frappe import frappe


def execute(): def execute():
from frappe.website.doctype.website_template.website_template import \
get_pages_and_generators, get_template_controller

frappe.reload_doc("website", "doctype", "website_template")
frappe.reload_doc("website", "doctype", "website_route")

for app in frappe.get_installed_apps():
pages, generators = get_pages_and_generators(app)
for g in generators:
doctype = frappe.get_attr(get_template_controller(app, g["path"], g["fname"]) + ".doctype")
module = frappe.db.get_value("DocType", doctype, "module")
frappe.reload_doc(frappe.scrub(module), "doctype", frappe.scrub(doctype))
frappe.db.sql("""update `tabBlog Category` set `title`=`name` where ifnull(`title`, '')=''""")
frappe.db.sql("""update `tabWebsite Route` set idx=null""")
for doctype in ["Blog Category", "Blog Post", "Web Page", "Website Group"]:
frappe.db.sql("""update `tab{}` set idx=null""".format(doctype))

from frappe.website.doctype.website_template.website_template import rebuild_website_template
rebuild_website_template()
pass
# from frappe.website.doctype.website_template.website_template import \
# get_pages_and_generators, get_template_controller
#
# frappe.reload_doc("website", "doctype", "website_template")
# frappe.reload_doc("website", "doctype", "website_route")
#
# for app in frappe.get_installed_apps():
# pages, generators = get_pages_and_generators(app)
# for g in generators:
# doctype = frappe.get_attr(get_template_controller(app, g["path"], g["fname"]) + ".doctype")
# module = frappe.db.get_value("DocType", doctype, "module")
# frappe.reload_doc(frappe.scrub(module), "doctype", frappe.scrub(doctype))
#
# frappe.db.sql("""update `tabBlog Category` set `title`=`name` where ifnull(`title`, '')=''""")
# frappe.db.sql("""update `tabWebsite Route` set idx=null""")
# for doctype in ["Blog Category", "Blog Post", "Web Page", "Website Group"]:
# frappe.db.sql("""update `tab{}` set idx=null""".format(doctype))
#
# from frappe.website.doctype.website_template.website_template import rebuild_website_template
# rebuild_website_template()

+ 3
- 3
frappe/patches/v4_0/website_sitemap_hierarchy.py Ver arquivo

@@ -6,9 +6,9 @@ from __future__ import unicode_literals
import frappe import frappe


def execute(): def execute():
frappe.db.sql("""update `tabWebsite Route` ws set ref_doctype=(select wsc.ref_doctype
from `tabWebsite Template` wsc where wsc.name=ws.website_template)
where ifnull(page_or_generator, '')!='Page'""")
# frappe.db.sql("""update `tabWebsite Route` ws set ref_doctype=(select wsc.ref_doctype
# from `tabWebsite Template` wsc where wsc.name=ws.website_template)
# where ifnull(page_or_generator, '')!='Page'""")


frappe.reload_doc("website", "doctype", "website_settings") frappe.reload_doc("website", "doctype", "website_settings")




+ 3
- 1
frappe/public/build.json Ver arquivo

@@ -32,7 +32,6 @@


"public/css/tag-it.css", "public/css/tag-it.css",
"public/css/bootstrap.css", "public/css/bootstrap.css",
"public/css/bootstrap-responsive.css",
"public/css/font-awesome.css", "public/css/font-awesome.css",
"public/css/desk.css", "public/css/desk.css",
"public/css/appframe.css", "public/css/appframe.css",
@@ -67,6 +66,9 @@
"public/js/frappe/router.js", "public/js/frappe/router.js",
"public/js/frappe/desk.js", "public/js/frappe/desk.js",
"public/js/frappe/defaults.js", "public/js/frappe/defaults.js",
"public/js/lib/microtemplate.js",

"public/html/print_template.html",


"public/js/legacy/globals.js", "public/js/legacy/globals.js",
"public/js/legacy/datatype.js", "public/js/legacy/datatype.js",


+ 17
- 0
frappe/public/html/print_template.html Ver arquivo

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>{%= title %}</title>
<link href="{%= frappe.urllib.get_base_url() %}/assets/frappe/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<div class="container">
{%= content %}
</div>
</body>
</html>

+ 47
- 76
frappe/public/js/frappe/form/grid.js Ver arquivo

@@ -6,6 +6,11 @@ frappe.ui.form.Grid = Class.extend({
$.extend(this, opts); $.extend(this, opts);
this.fieldinfo = {}; this.fieldinfo = {};
this.doctype = this.df.options; this.doctype = this.df.options;
this.template = null;
if(this.frm.meta.__form_grid_templates
&& this.frm.meta.__form_grid_templates[this.df.fieldname]) {
this.template = this.frm.meta.__form_grid_templates[this.df.fieldname];
}
this.is_grid = true; this.is_grid = true;
}, },
make: function() { make: function() {
@@ -61,7 +66,7 @@ frappe.ui.form.Grid = Class.extend({


if(!force && this.data_rows_are_same(data)) { if(!force && this.data_rows_are_same(data)) {
// soft refresh // soft refresh
this.header_row.refresh();
this.header_row && this.header_row.refresh();
for(var i in this.grid_rows) { for(var i in this.grid_rows) {
this.grid_rows[i].refresh(); this.grid_rows[i].refresh();
} }
@@ -261,38 +266,27 @@ frappe.ui.form.GridRow = Class.extend({
}, },
make_static_display: function() { make_static_display: function() {
var me = this; var me = this;
this.make_static_display_template();
this.row.empty(); this.row.empty();
$('<div class="col col-xs-1 row-index">' + (this.doc ? this.doc.idx : "#")+ '</div>')
$('<div class="col-xs-1 row-index">' + (this.doc ? this.doc.idx : "#")+ '</div>')
.appendTo(this.row); .appendTo(this.row);


for(var ci in this.static_display_template) {
var df = this.static_display_template[ci][0];
var colsize = this.static_display_template[ci][1];
var txt = this.doc ?
frappe.format(this.doc[df.fieldname], df, null, this.doc) :
__(df.label);
if(this.doc && df.fieldtype === "Select") {
txt = __(txt);
}
var add_class = (["Text", "Small Text"].indexOf(df.fieldtype)===-1) ?
" grid-overflow-ellipsis" : " grid-overflow-no-ellipsis";
add_class += (["Int", "Currency", "Float"].indexOf(df.fieldtype)!==-1) ?
" text-right": "";

$col = $('<div class="col col-xs-'+colsize+add_class+'"></div>')
.html(txt)
.attr("data-fieldname", df.fieldname)
.data("df", df)
.appendTo(this.row)
if(!this.doc) $col.css({"font-weight":"bold"})
if(this.grid.template) {
$('<div class="col-xs-10">').appendTo(this.row)
.html(frappe.render(this.grid.template, {doc:this.doc, frm:this.frm, grid_row: this}));
} else {
this.add_visible_columns();
} }


// TODO find a better solution
// append button column
this.add_buttons();

$(this.frm.wrapper).trigger("grid-row-render", [this]);
},

add_buttons: function() {
var me = this;
if(this.doc && this.grid.is_editable()) { if(this.doc && this.grid.is_editable()) {
if(!this.grid.$row_actions) { if(!this.grid.$row_actions) {
this.grid.$row_actions = $('<div class="col-md-1 pull-right" \
this.grid.$row_actions = $('<div class="col-xs-1 pull-right" \
style="text-align: right; padding-right: 5px;">\ style="text-align: right; padding-right: 5px;">\
<span class="text-success grid-insert-row" style="padding: 4px;">\ <span class="text-success grid-insert-row" style="padding: 4px;">\
<i class="icon icon-plus-sign"></i></span>\ <i class="icon icon-plus-sign"></i></span>\
@@ -311,8 +305,34 @@ frappe.ui.form.GridRow = Class.extend({
} }
} }


$(this.frm.wrapper).trigger("grid-row-render", [this]);
}, },

add_visible_columns: function() {
this.make_static_display_template();
for(var ci in this.static_display_template) {
var df = this.static_display_template[ci][0];
var colsize = this.static_display_template[ci][1];
var txt = this.doc ?
frappe.format(this.doc[df.fieldname], df, null, this.doc) :
__(df.label);
if(this.doc && df.fieldtype === "Select") {
txt = __(txt);
}
var add_class = (["Text", "Small Text"].indexOf(df.fieldtype)===-1) ?
" grid-overflow-ellipsis" : " grid-overflow-no-ellipsis";
add_class += (["Int", "Currency", "Float"].indexOf(df.fieldtype)!==-1) ?
" text-right": "";

$col = $('<div class="col col-xs-'+colsize+add_class+'"></div>')
.html(txt)
.attr("data-fieldname", df.fieldname)
.data("df", df)
.appendTo(this.row)
if(!this.doc) $col.css({"font-weight":"bold"})
}

},

make_static_display_template: function() { make_static_display_template: function() {
if(this.static_display_template) return; if(this.static_display_template) return;


@@ -443,55 +463,6 @@ frappe.ui.form.GridRow = Class.extend({
$.extend(me.fields_dict[fieldname], fi); $.extend(me.fields_dict[fieldname], fi);
}) })


// var me = this,
// make_row = function(label) {
// if(label)
// $('<div><h4 style="margin-bottom: 0px;"><b>'+ label +'</b></h4>\
// <hr style="margin-top: 10px;"></div>')
// .appendTo(me.form_area);
//
// var row = $('<div class="row">')
// .appendTo(me.form_area);
//
// var col_spans = 6;
// if(row.parents(".form-column:first").hasClass("col-md-6"))
// col_spans = 12;
//
// var col1 = $('<div class="col-md-'+col_spans+'"></div>').appendTo(row),
// col2 = $('<div class="col-md-'+col_spans+'"></div>').appendTo(row);
//
// return [col1, col2];
// },
// cols = make_row(),
// cnt = 0;
//
// $.each(me.docfields, function(ci, df) {
// if(!df.hidden) {
// if(df.fieldtype=="Section Break") {
// cols = make_row(df.label);
// cnt = 0;
// return;
// }
// var fieldwrapper = $('<div>')
// .appendTo(cols[cnt % 2])
// var fieldobj = make_field(df, me.parent_df.options,
// fieldwrapper.get(0), me.frm);
// fieldobj.docname = me.doc.name;
// fieldobj.refresh();
// fieldobj.input &&
// $(fieldobj.input).css({"max-height": "100px"});
//
// // set field properties
// // used for setting custom get queries in links
// if(me.grid.fieldinfo[df.fieldname])
// $.extend(fieldobj, me.grid.fieldinfo[df.fieldname]);
//
// me.fields.push(fieldobj);
// me.fields_dict[df.fieldname] = fieldobj;
// cnt++;
// }
// });

this.toggle_add_delete_button_display(this.wrapper.find(".panel:first")); this.toggle_add_delete_button_display(this.wrapper.find(".panel:first"));


this.grid.open_grid_row = this; this.grid.open_grid_row = this;


+ 0
- 1
frappe/public/js/frappe/misc/user.js Ver arquivo

@@ -41,7 +41,6 @@ frappe.ui.set_user_background = function(src, selector, style) {
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;\
background-attachment: fixed; \ background-attachment: fixed; \
background-size: 100%; \
%(style)s \ %(style)s \
}', {src:src, selector:selector, style: style==="Fill Screen" ? "background-size: cover;" : ""})); }', {src:src, selector:selector, style: style==="Fill Screen" ? "background-size: cover;" : ""}));
} }


+ 1
- 1
frappe/public/js/frappe/misc/utils.js Ver arquivo

@@ -247,5 +247,5 @@ frappe.utils = {
var dataURL = canvas.toDataURL("image/jpeg"); var dataURL = canvas.toDataURL("image/jpeg");
setTimeout(function() { callback(dataURL); }, 10 ); setTimeout(function() { callback(dataURL); }, 10 );
} }
}
},
}; };

+ 4
- 3
frappe/public/js/frappe/provide.js Ver arquivo

@@ -1,8 +1,8 @@
// 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


// provide a namespace // provide a namespace
if(!window.frappe)
if(!window.frappe)
window.frappe = {}; window.frappe = {};
frappe.provide = function(namespace) { frappe.provide = function(namespace) {
// docs: create a namespace // // docs: create a namespace //
@@ -22,4 +22,5 @@ frappe.provide("locals");
frappe.provide("frappe.settings"); frappe.provide("frappe.settings");
frappe.provide("frappe.utils"); frappe.provide("frappe.utils");
frappe.provide("frappe.ui"); frappe.provide("frappe.ui");
frappe.provide("frappe.modules");
frappe.provide("frappe.modules");
frappe.provide("frappe.templates");

+ 1
- 1
frappe/public/js/frappe/request.js Ver arquivo

@@ -71,6 +71,7 @@ frappe.request.call = function(opts) {
500: function() { 500: function() {
msgprint(__("Server Error: Please check your server logs or contact tech support.")) msgprint(__("Server Error: Please check your server logs or contact tech support."))
opts.error && opts.error(); opts.error && opts.error();

} }
}, },
async: opts.async async: opts.async
@@ -144,7 +145,6 @@ frappe.request.prepare = function(opts) {
} }


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();




+ 8
- 1
frappe/public/js/frappe/views/doclistview.js Ver arquivo

@@ -259,12 +259,19 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
this.appframe.add_icon_btn("2", "icon-shield", this.appframe.add_icon_btn("2", "icon-shield",
__("User Permissions Manager"), function() { __("User Permissions Manager"), function() {
frappe.route_options = { frappe.route_options = {
property: me.doctype
doctype: me.doctype
}; };
frappe.set_route("user-permissions"); frappe.set_route("user-permissions");
}); });
} }
if(in_list(user_roles, "System Manager")) { if(in_list(user_roles, "System Manager")) {
this.appframe.add_icon_btn("2", "icon-lock",
__("Role Permissions Manager"), function() {
frappe.route_options = {
doctype: me.doctype
};
frappe.set_route("permission-manager");
});
this.appframe.add_icon_btn("2", "icon-glass", __("Customize"), function() { this.appframe.add_icon_btn("2", "icon-glass", __("Customize"), function() {
frappe.set_route("Form", "Customize Form", { frappe.set_route("Form", "Customize Form", {
doctype: me.doctype doctype: me.doctype


+ 33
- 1
frappe/public/js/frappe/views/query_report.js Ver arquivo

@@ -107,8 +107,9 @@ frappe.views.QueryReport = Class.extend({
}, },
callback: function(r) { callback: function(r) {
me.appframe.set_title(__("Query Report")+": " + __(me.report_name)); me.appframe.set_title(__("Query Report")+": " + __(me.report_name));
frappe.dom.eval(r.message || "");
frappe.dom.eval(r.message.script || "");
me.setup_filters(); me.setup_filters();
me.setup_html_format(r.message.html_format);
me.refresh(); me.refresh();
} }
}); });
@@ -124,6 +125,37 @@ frappe.views.QueryReport = Class.extend({
this.wrapper.find(".no-report-area").html(msg).toggle(true); this.wrapper.find(".no-report-area").html(msg).toggle(true);
} }
}, },
setup_html_format: function(html_format) {
var me = this;
if(html_format) {
this.appframe.add_primary_action(__('Print'), function() {
if(!me.data) {
msgprint(__("Run the report first"));
return;
}

var data = [];
$.each(me.data, function(i, d) {
var newd = {}; data.push(newd);
$.each(d, function(k, v) {
newd[k.replace(/ /g, "_").toLowerCase()] = v; });
});

var content = frappe.render(html_format,
{data: data, filters:me.get_values(), report:me});

var html = frappe.render(frappe.templates.print_template, {
title: __(me.report_name), content: content
});

var w = window.open();
w.document.write(html);
w.document.close();

}, "icon-print");

}
},
setup_filters: function() { setup_filters: function() {
this.clear_filters(); this.clear_filters();
var me = this; var me = this;


+ 2
- 0
frappe/public/js/legacy/datatype.js Ver arquivo

@@ -6,6 +6,8 @@ frappe.utils.full_name = function(fn, ln) {
} }


function fmt_money(v, format){ function fmt_money(v, format){
// deprecated!
// for backward compatibility
return format_number(v, format); return format_number(v, format);
} }




+ 34
- 0
frappe/public/js/lib/microtemplate.js Ver arquivo

@@ -0,0 +1,34 @@
// Simple JavaScript Templating
// Adapted from John Resig - http://ejohn.org/ - MIT Licensed

frappe.template = {compiled: {}, debug:{}};
frappe.template.compile = function(str) {
if(str.indexOf("'")!==-1) {
console.log("Warning: Single quotes (') may not work in templates");
}
if(!frappe.template.compiled[str]) {
fn_str = "var p=[],print=function(){p.push.apply(p,arguments)};" +

// Introduce the data as local variables using with(){}
"with(obj){p.push('" +

// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("{%").join("\t")
.replace(/((^|%})[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%}/g, "',$1,'")
.split("\t").join("');")
.split("%}").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');";

frappe.template.debug[str] = fn_str;
frappe.template.compiled[str] = new Function("obj", fn_str);
}

return frappe.template.compiled[str];
};
frappe.render = function(str, data) {
return frappe.template.compile(str)(data);
};

+ 6
- 9
frappe/templates/base.html Ver arquivo

@@ -14,12 +14,9 @@ Built on Frappe.io. Free and Open Source Framework for the Web. https://frappe.i
<link rel="icon" href="{{ favicon or "" }}" type="image/x-icon"> <link rel="icon" href="{{ favicon or "" }}" type="image/x-icon">
{%- block head_include %}{% endblock -%} {%- block head_include %}{% endblock -%}
{%- block head -%} {%- block head -%}

{%- if metatags -%}
{%- for name in metatags %}
<meta name="{{ name }}" content="{{ metatags[name]|striptags }}">
{%- endfor -%}
{%- endif -%}
{% if meta_block is defined %}
{{ meta_block }}
{% endif %}


{%- for link in web_include_css %} {%- for link in web_include_css %}
<link type="text/css" rel="stylesheet" href="{{ link }}"> <link type="text/css" rel="stylesheet" href="{{ link }}">
@@ -50,15 +47,15 @@ Built on Frappe.io. Free and Open Source Framework for the Web. https://frappe.i
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-sm-9 page-header-left"> <div class="col-sm-9 page-header-left">
<a class="visible-xs toggle-sidebar no-decoration pull-right">
<i class="icon-chevron-down"></i>
</a>
<div data-html-block="header"> <div data-html-block="header">
{%- if header is defined -%}{{ header }}{%- endif -%} {%- if header is defined -%}{{ header }}{%- endif -%}
</div> </div>
<div class="page-breadcrumbs" data-html-block="breadcrumbs"> <div class="page-breadcrumbs" data-html-block="breadcrumbs">
{%- if breadcrumbs is defined -%}{{ breadcrumbs }}{%- endif -%} {%- if breadcrumbs is defined -%}{{ breadcrumbs }}{%- endif -%}
</div> </div>
<!-- <a class="visible-xs toggle-sidebar no-decoration pull-right">
<i class="icon-chevron-down"></i>
</a> -->
</div> </div>
<div class="col-sm-3 text-right"> <div class="col-sm-3 text-right">
<div class="page-header-right"></div> <div class="page-header-right"></div>


+ 1
- 1
frappe/templates/emails/new_message.html Ver arquivo

@@ -2,4 +2,4 @@
<p>You have a new message from: <b>{{ from }}</b></p> <p>You have a new message from: <b>{{ from }}</b></p>
<p>{{ message }}</p> <p>{{ message }}</p>
<hr> <hr>
<p><a href="{{ link }}">Login and view in Browser</a></p>
<p><a href="{{ link }}">Login and view in Browser</a></p>

+ 3
- 3
frappe/templates/emails/new_user.html Ver arquivo

@@ -2,9 +2,9 @@
<p>Dear {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},</p> <p>Dear {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},</p>
<p>A new account has been created for you.</p> <p>A new account has been created for you.</p>
<p>Your login id is: <b>{{ user }}</b> <p>Your login id is: <b>{{ user }}</b>
<p>Click on the button below to complete your registration and set a new password.</p>
<p><a class="btn-primary" href="{{ link }}">Complete Registration</a></p>
<p>Click on the link below to complete your registration and set a new password.</p>
<p><b><a href="{{ link }}">Complete Registration</a></b></p>
<br> <br>
<p>You can also copy-paste this link in your browser <a href="{{ link }}">{{ link }}</a></p> <p>You can also copy-paste this link in your browser <a href="{{ link }}">{{ link }}</a></p>
<p>Thank you,<br> <p>Thank you,<br>
{{ user_fullname }}</p>
{{ user_fullname }}</p>

+ 3
- 260
frappe/templates/emails/standard.html Ver arquivo

@@ -4,273 +4,16 @@
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>{{ subject or "" }}</title> <title>{{ subject or "" }}</title>
<style>
/* -------------------------------------
GLOBAL
------------------------------------- */
img {
max-width: 100%;
}

body {
width: 100% !important;
height: 100%;
}

.wrapper {
background-color: #eee;
}

.wrapper * {
margin:0;
padding:0;
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
font-size: 100%;
line-height: 1.6;
}

/* -------------------------------------
ELEMENTS
------------------------------------- */
.wrapper a {
color: #348eda;
}

.btn-primary{
text-decoration:none;
color: #FFF !important;
background-color: #348eda;
border:solid #348eda;
border-width:10px 20px;
line-height:2;
font-weight:bold;
margin-right:10px;
text-align:center;
cursor:pointer;
display: inline-block;
border-radius: 25px;
}

.btn-secondary {
text-decoration:none;
color: #FFF !important;
background-color: #aaa;
border:solid #aaa;
border-width:10px 20px;
line-height:2;
font-weight:bold;
margin-right:10px;
text-align:center;
cursor:pointer;
display: inline-block;
border-radius: 25px;
}

.last {
margin-bottom: 0;
}

.first {
margin-top: 0;
}

.padding{
padding:10px 0;
}

.left-padding {
padding-left: 10px;
}

.breadcrumb {
list-style: none;
}

.breadcrumb > li {
display: inline-block;
margin-left: 0px;
margin-right: 5px;
}

/* -------------------------------------
BODY
------------------------------------- */
table.body-wrap {
width: 100%;
padding: 10px;
}

table.body-wrap .container{
border: 1px solid #f0f0f0;
}


/* -------------------------------------
FOOTER
------------------------------------- */
table.footer-wrap {
width: 100%;
clear:both!important;
}

.footer-wrap .container p {
font-size:12px;
color:#666;

}

table.footer-wrap a{
color: #999;
}


/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
.wrapper h1,
.wrapper h2,
.wrapper h3{
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
line-height: 1.1;
margin-top:15px;
margin-bottom:15px;
color:#000;
line-height: 1.2;
font-weight:200;
}

.wrapper h1 {
font-size: 36px;
}
.wrapper h2 {
font-size: 28px;
}
.wrapper h3 {
font-size: 22px;
}
.wrapper hr {
margin: 20px 0;
border-top: 1px solid #eee;
}
.wrapper p,
.wrapper ul,
.wrapper ol {
margin-bottom: 10px;
font-weight: normal;
font-size:14px;
}

.wrapper ul li,
.wrapper ol li {
margin-left:5px;
list-style-position: inside;
}

/* ---------------------------------------------------
RESPONSIVENESS
Nuke it from orbit. It's the only way to be sure.
------------------------------------------------------ */

/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display:block!important;
max-width:600px!important;
margin:0 auto!important; /* makes it centered */
clear:both!important;
}

/* Set the padding on the td rather than the div for Outlook compatibility */
.body-wrap .container{
padding:20px;
}

/* This should also be a block element, so that it will fill 100% of the .container */
.content {
max-width:600px;
margin:0 auto;
display:block;
}

/* Let's make sure tables in the content area are 100% wide */
.content table {
width: 100%;
}

a.no-decoration {
text-decoration: none;
color: inherit;
}

small, .small {
font-size: 85%;
}

.text-muted {
color: #999999;
}

pre, code {
font-family: monospace !important;
}

</style>
</head> </head>

<body> <body>

<div class="wrapper">

<!-- body --> <!-- body -->
<table class="body-wrap">
<tr>
<td></td>
<td class="container" bgcolor="#FFFFFF">

<!-- content -->
<div class="content">
<table>
<tr>
<td>
{{ content }}
</td>
</tr>
</table>
</div>
<!-- /content -->

</td>
<td></td>
</tr>
</table>
<!-- /body -->
<div style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;">{{ content }}</div>


<!-- footer --> <!-- footer -->
<table class="footer-wrap">
<tr>
<td></td>
<td class="container">

<!-- content -->
<div class="content">
<table>
<tr>
<td align="center">
{{ footer }}
</td>
</tr>
</table>
</div>
<!-- /content -->
<div style="margin-top: 30px; font-family: Helvetica, Arial, sans-serif; font-size: 11px;">
{{ footer }}</div>


</td>
<td></td>
</tr>
</table>
<!-- /footer --> <!-- /footer -->

</div>

<div class="print-html">{{ print_html or "" }}</div> <div class="print-html">{{ print_html or "" }}</div>

</body> </body>
</html> </html>

+ 41
- 0
frappe/templates/form_grid/fields.html Ver arquivo

@@ -0,0 +1,41 @@
{% if(doc) { %}
<div class="row">
<div class="col-sm-4">
<p>
{% if (doc.fieldtype==="Section Break") { %}<strong>{% } %}
{% if (doc.reqd) { %}<span class="text-danger">{% } %}
{% if (doc.hidden) { %}
<span class="text-muted"><i class="icon-eye-close"></i>{% } %}
{% if(doc.fieldtype==="Link") { %}<i class="icon-link"></i>{% } %}
{% if(doc.fieldtype==="Table") { %}<i class="icon-th"></i>{% } %}
{%= doc.label %}
{% if (doc.hidden) { %}</span>{% } %}
{% if (doc.reqd) { %}</span>{% } %}
{% if (doc.fieldtype=="Section Break") { %}</strong>{% } %}
</p>
{% if (doc.description) { %}<p class="text-muted small">{%= doc.description %}</p>{% } %}
</div>
<div class="col-sm-4">
{%= doc.fieldtype %}
<br><span class="small">{%= doc.fieldname %}</span>
</div>
<div class="col-sm-4">
{%= doc.options && doc.options.split("\n").join("<br>") || "" %}
{% if(doc["default"]) { %}
<br>{%= __("Default") %}: <strong>{%= doc["default"] %}</strong>
{% } %}
</div>
</div>
{% } else { %}
<div class="row">
<div class="col-sm-4">
{%= __("Label") %}
</div>
<div class="col-sm-4">
{%= __("Field Type") %}
</div>
<div class="col-sm-4">
{%= __("Options") %}
</div>
</div>
{% } %}

+ 0
- 2
frappe/templates/generators/blog_category.py Ver arquivo

@@ -1,2 +0,0 @@
doctype = "Blog Category"
no_cache = 1

+ 0
- 81
frappe/templates/generators/blog_post.py Ver arquivo

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

from __future__ import unicode_literals

import frappe
from frappe.utils import global_date_format, get_fullname, cint
from frappe.website.utils import find_first_image

doctype = "Blog Post"
condition_field = "published"
sort_by = "published_on"
sort_order = "desc"

def get_context(context):
blog_post = context.doc

# this is for double precaution. usually it wont reach this code if not published
if not cint(blog_post.published):
raise Exception, "This blog has not been published yet!"

# temp fields
blog_post.full_name = get_fullname(blog_post.owner)
blog_post.updated = global_date_format(blog_post.published_on)

if blog_post.blogger:
blog_post.blogger_info = frappe.get_doc("Blogger", blog_post.blogger).as_dict()

blog_post.description = blog_post.blog_intro or blog_post.content[:140]

blog_post.metatags = {
"name": blog_post.title,
"description": blog_post.description,
}

image = find_first_image(blog_post.content)
if image:
blog_post.metatags["image"] = image

blog_post.categories = frappe.db.sql_list("select name from `tabBlog Category` order by name")

blog_post.comment_list = frappe.db.sql("""\
select comment, comment_by_fullname, creation
from `tabComment` where comment_doctype="Blog Post"
and comment_docname=%s order by creation""", (blog_post.name,), as_dict=1) or []

return blog_post.__dict__

@frappe.whitelist(allow_guest=True)
def get_blog_list(start=0, by=None, category=None):
condition = ""
if by:
condition = " and t1.blogger='%s'" % by.replace("'", "\'")
if category:
condition += " and t1.blog_category='%s'" % category.replace("'", "\'")
query = """\
select
t1.title, t1.name, t3.name as page_name, t1.published_on as creation,
day(t1.published_on) as day, monthname(t1.published_on) as month,
year(t1.published_on) as year,
ifnull(t1.blog_intro, t1.content) as content,
t2.full_name, t2.avatar, t1.blogger,
(select count(name) from `tabComment` where
comment_doctype='Blog Post' and comment_docname=t1.name) as comments
from `tabBlog Post` t1, `tabBlogger` t2, `tabWebsite Route` t3
where ifnull(t1.published,0)=1
and t1.blogger = t2.name
and t3.docname = t1.name
and t3.ref_doctype = "Blog Post"
%(condition)s
order by published_on desc, name asc
limit %(start)s, 20""" % {"start": start, "condition": condition}

result = frappe.db.sql(query, as_dict=1)

# strip html tags from content
for res in result:
res['published'] = global_date_format(res['creation'])
res['content'] = res['content'][:140]

return result

+ 0
- 44
frappe/templates/generators/web_page.py Ver arquivo

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

from __future__ import unicode_literals
import frappe
from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
from frappe.website.utils import find_first_image

doctype = "Web Page"
condition_field = "published"

def get_context(context):
web_page = frappe._dict(context.doc.as_dict())
web_page.main_section = web_page.main_section or ""

if web_page.slideshow:
web_page.update(get_slideshow(web_page))

if web_page.enable_comments:
web_page.comment_list = frappe.db.sql("""select
comment, comment_by_fullname, creation
from `tabComment` where comment_doctype="Web Page"
and comment_docname=%s order by creation""", web_page.name, as_dict=1) or []

web_page.update({
"style": web_page.css or "",
"script": web_page.javascript or ""
})
web_page.update(context)

web_page.metatags = {
"name": web_page.title,
"description": web_page.description or web_page.main_section[:150]
}

image = find_first_image(web_page.main_section)
if image:
web_page.metatags["image"] = image


if not web_page.header:
web_page.header = web_page.title

return web_page

+ 0
- 240
frappe/templates/generators/website_group.py Ver arquivo

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

from __future__ import unicode_literals

import frappe
from frappe.website.permissions import get_access
from frappe.website.render import can_cache
from frappe.templates.website_group.forum import get_post_list_html

doctype = "Website Group"
no_cache = 1

def get_context(context):
group, view = guess_group_view(context)

try:
if not has_access(context.access, view):
raise frappe.PermissionError

return get_group_context(group, view, context)

except frappe.DoesNotExistError:
return {
"content": '<div class="alert alert-danger full-page">'
'The page you are looking for does not exist.</div>'
}
except frappe.PermissionError:
return {
"content": '<div class="alert alert-danger full-page">'
'You are not permitted to view this page.</div>'
}

def get_group_context(group, view, context):
cache_key = "website_group_context:{}:{}".format(group, view)

views = get_views(context.doc.group_type)
view = frappe._dict(views.get(view))

if can_cache(view.no_cache):
group_context = frappe.cache().get_value(cache_key)
if group_context:
return group_context

group_context = build_group_context(group, view, views, context)

if can_cache(view.get("no_cache")):
frappe.cache().set_value(cache_key, group_context)

return group_context

def build_group_context(group, view, views, context):
title = "{} - {}".format(context.doc.group_title, view.get("label"))

group_context = frappe._dict({
"group": context.doc,
"view": view,
"views": [v[1] for v in sorted(views.iteritems(), key=lambda (k, v): v.get("idx"))],
"title": title,
"pathname": context.pathname
})
group_context.update(build_view_context(group_context))

return group_context

def build_view_context(context):
from frappe.templates.website_group.post import get_post_context

if context.view.name in ("popular", "feed", "open", "closed", "upcoming", "past"):
context.post_list_html = get_post_list_html(context.group.name, context.view.name)

elif context.view.name == "edit":
context.post = frappe.get_doc("Post", frappe.form_dict.name).as_dict()

if context.post.assigned_to:
context.user = frappe.get_doc("User", context.post.assigned_to)

elif context.view.name == "settings":
context.users = frappe.db.sql("""select p.*, wsp.`read`, wsp.`write`, wsp.`admin`
from `tabUser` p, `tabWebsite Route Permission` wsp
where wsp.website_route=%s and wsp.user=p.name""", context.pathname, as_dict=True)

elif context.view.name == "post":
context.update(get_post_context(context))

return context

def guess_group_view(context):
group = context.docname
view = frappe.form_dict.view or get_default_view(context.doc.group_type)
return group, view

def get_default_view(group_type):
for view, opts in get_views(group_type).iteritems():
if opts.get("default"):
return view

def get_views(group_type=None):
if group_type:
group_views = frappe._dict(views[group_type])
else:
group_views = {}
for group_type in views:
group_views.update(views[group_type].copy())

group_views.update(common_views)

if group_type == "Forum":
group_views["post"]["upvote"] = True

return group_views

def has_access(access, view):
if view=="settings":
return access.get("admin")
elif view in ("add", "edit"):
return access.get("write")
else:
return access.get("read")

def clear_cache(path=None, website_group=None):
from frappe.templates.website_group.post import clear_post_cache
if path:
website_groups = [frappe.db.get_value("Website Route", path, "docname")]
elif website_group:
website_groups = [website_group]
else:
clear_post_cache()
website_groups = frappe.db.sql_list("""select name from `tabWebsite Group`""")

cache = frappe.cache()
all_views = get_views()
for group in website_groups:
for view in all_views:
cache.delete_value("website_group_context:{}:{}".format(group, view))

def clear_event_cache():
for group in frappe.db.sql_list("""select name from `tabWebsite Group` where group_type='Event'"""):
clear_cache(website_group=group)

def clear_cache_on_doc_event(doc, method, *args, **kwargs):
clear_cache(path=doc.website_route, website_group=doc.website_group)

def get_pathname(group):
return frappe.db.get_value("Website Route", {"ref_doctype": "Website Group",
"docname": group})

views = {
"Forum": {
"popular": {
"name": "popular",
"template_path": "templates/website_group/forum.html",
"label": "Popular",
"icon": "icon-heart",
"default": True,
"upvote": True,
"idx": 1
},
"feed": {
"name": "feed",
"template_path": "templates/website_group/forum.html",
"label": "Feed",
"icon": "icon-rss",
"upvote": True,
"idx": 2
}
},
"Tasks": {
"open": {
"name": "open",
"template_path": "templates/website_group/forum.html",
"label": "Open",
"icon": "icon-inbox",
"default": True,
"upvote": True,
"idx": 1
},
"closed": {
"name": "closed",
"template_path": "templates/website_group/forum.html",
"label": "Closed",
"icon": "icon-smile",
"idx": 2
}
},
"Events": {
"upcoming": {
"name": "upcoming",
"template_path": "templates/website_group/forum.html",
"label": "Upcoming",
"icon": "icon-calendar",
"default": True,
"idx": 1
},
"past": {
"name": "past",
"template_path": "templates/website_group/forum.html",
"label": "Past",
"icon": "icon-time",
"idx": 2
}
}
}

common_views = {
"post": {
"name": "post",
"template_path": "templates/website_group/post.html",
"label": "Post",
"icon": "icon-comments",
"hidden": True,
"no_cache": True,
"idx": 3
},
"edit": {
"name": "edit",
"template_path": "templates/website_group/edit_post.html",
"label": "Edit Post",
"icon": "icon-pencil",
"hidden": True,
"no_cache": True,
"idx": 4
},
"add": {
"name": "add",
"template_path": "templates/website_group/edit_post.html",
"label": "Add Post",
"icon": "icon-plus",
"hidden": True,
"idx": 5
},
"settings": {
"name": "settings",
"template_path": "templates/website_group/settings.html",
"label": "Settings",
"icon": "icon-cog",
"hidden": True,
"idx": 6
}
}

+ 1
- 2
frappe/templates/includes/blog.js Ver arquivo

@@ -8,9 +8,8 @@ var blog = {
get_list: function() { get_list: function() {
$.ajax({ $.ajax({
method: "GET", method: "GET",
url: "/",
url: "/api/method/frappe.website.doctype.blog_post.blog_post.get_blog_list",
data: { data: {
cmd: "frappe.templates.generators.blog_post.get_blog_list",
start: blog.start, start: blog.start,
by: get_url_arg("by"), by: get_url_arg("by"),
category: window.category || get_url_arg("category") category: window.category || get_url_arg("category")


+ 6
- 1
frappe/templates/includes/comments.html Ver arquivo

@@ -18,7 +18,7 @@
<fieldset> <fieldset>
<input class="form-control" name="comment_by_fullname" placeholder="Your Name" type="text"/><br> <input class="form-control" name="comment_by_fullname" placeholder="Your Name" type="text"/><br>
<input class="form-control" name="comment_by" <input class="form-control" name="comment_by"
placeholder="Your Email Id" type="text"/><br>
placeholder="Your Email Id" type="email"/><br>
<textarea class="form-control" name="comment" rows=10 <textarea class="form-control" name="comment" rows=10
placeholder="Comment"/> placeholder="Comment"/>
</textarea><br> </textarea><br>
@@ -62,6 +62,11 @@ $(document).ready(function() {
return false; return false;
} }


if (!valid_email(args.comment_by)) {
frappe.msgprint("Please enter a valid email address.");
return false;
}

frappe.call({ frappe.call({
btn: this, btn: this,
type: "POST", type: "POST",


+ 1
- 1
frappe/templates/includes/comments.py Ver arquivo

@@ -43,7 +43,7 @@ def add_comment(args=None):
ifnull(unsubscribed, 0)=0""", (comment.comment_doctype, comment.comment_docname))] ifnull(unsubscribed, 0)=0""", (comment.comment_doctype, comment.comment_docname))]


owner = frappe.db.get_value(comment.comment_doctype, comment.comment_docname, "owner") owner = frappe.db.get_value(comment.comment_doctype, comment.comment_docname, "owner")
recipients = commentors if owner=="Administrator" else list(set(commentors + [owner]))
recipients = list(set(commentors if owner=="Administrator" else (commentors + [owner])))




from frappe.utils.email_lib.bulk import send from frappe.utils.email_lib.bulk import send


+ 1
- 0
frappe/templates/includes/login.js Ver arquivo

@@ -124,5 +124,6 @@ frappe.ready(function() {
window.location.hash = "#login"; window.location.hash = "#login";
login.bind_events(); login.bind_events();
login.login(); login.login();
$(".form-signup, .form-forgot").removeClass("hide");
$(document).trigger('login_rendered'); $(document).trigger('login_rendered');
}); });

+ 5
- 0
frappe/templates/includes/meta_block.html Ver arquivo

@@ -0,0 +1,5 @@
{%- if metatags -%}
{%- for name in metatags %}
<meta name="{{ name }}" content="{{ metatags[name]|striptags }}" data-html-block="meta_block">
{%- endfor -%}
{%- endif -%}

+ 1
- 2
frappe/templates/includes/navbar.html Ver arquivo

@@ -39,7 +39,7 @@
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{%- for child in post_login -%} {%- for child in post_login -%}
<li data-label="{{ child.label }}"
<li {% if child.label %}data-label="{{ child.label }}" {% endif %}
{% if child.class %} class="{{ child.class }}" {% endif %}> {% if child.class %} class="{{ child.class }}" {% endif %}>


{%- if child.url -%} {%- if child.url -%}
@@ -47,7 +47,6 @@
{%- if child.icon -%} {%- if child.icon -%}
<i class="icon-fixed-width {{ child.icon }}"></i> <i class="icon-fixed-width {{ child.icon }}"></i>
{%- endif -%} {%- endif -%}

{{ child.label }} {{ child.label }}
</a> </a>
{%- endif -%} {%- endif -%}


+ 3
- 3
frappe/templates/includes/sidebar.html Ver arquivo

@@ -2,10 +2,10 @@
{% if children -%} {% if children -%}
{%- for child in children -%} {%- for child in children -%}
<div class="sidebar-item"> <div class="sidebar-item">
{% set is_parent = parents and child.name == parents[-1].name or (loop.first and child.name==pathname) %}
<i class="icon-fixed-width <i class="icon-fixed-width
{% if (child.lft != None) and (child.rgt - child.lft != 1) and (not loop.first) %}icon-chevron-right{% endif %}
{% if parents and child.name == parents[-1].name or (loop.first and child.name==pathname) %}icon-chevron-down{% endif %}"
style="margin-left: -17px; color: #ddd;"></i>
{% if (child.lft != None) and (child.rgt - child.lft != 1) and (not loop.first) %}icon-chevron-right{% endif %}"
style="margin-left: -15px; color: #ddd; {% if is_parent %}margin-left: -30px;{% endif %}"></i>
<a href="{{ child.name }}" class="no-decoration {% if child.name == pathname %}active{% endif %}"> <a href="{{ child.name }}" class="no-decoration {% if child.name == pathname %}active{% endif %}">
{{ child.page_title }} {{ child.page_title }}
{% if not child.public_read %} {% if not child.public_read %}


+ 1
- 1
frappe/templates/includes/static_index.html Ver arquivo

@@ -7,7 +7,7 @@
<div class="row"> <div class="row">
<div class="col-xs-1 text-right" style="padding-right: 0px;"><b>{{ loop.index }}.</b></div> <div class="col-xs-1 text-right" style="padding-right: 0px;"><b>{{ loop.index }}.</b></div>
<div class="col-xs-11"> <div class="col-xs-11">
<a href="{{ item.name }}">{{ item.page_title }}</a>
<a href="/{{ item.name }}">{{ item.page_title }}</a>
</div> </div>
</div> </div>
</li> </li>


+ 2
- 2
frappe/templates/pages/404.py Ver arquivo

@@ -1,4 +1,4 @@
# 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


no_sitemap = 1
no_sitemap = 1

+ 1
- 0
frappe/templates/pages/blog.py Ver arquivo

@@ -4,5 +4,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe


page_title = "Blog"
def get_context(context): def get_context(context):
return frappe.get_doc("Blog Settings", "Blog Settings").as_dict() return frappe.get_doc("Blog Settings", "Blog Settings").as_dict()

+ 2
- 2
frappe/templates/pages/login.html Ver arquivo

@@ -43,7 +43,7 @@


</form> </form>


<form class="form-signin form-signup" role="form">
<form class="form-signin form-signup hide" role="form">
<h2 class="form-signin-heading">{{ _("Sign Up") }}</h2> <h2 class="form-signin-heading">{{ _("Sign Up") }}</h2>
<input type="text" id="signup_fullname" <input type="text" id="signup_fullname"
class="form-control" placeholder="{{ _('Full Name') }}" required autofocus> class="form-control" placeholder="{{ _('Full Name') }}" required autofocus>
@@ -56,7 +56,7 @@


</form> </form>


<form class="form-signin form-forgot" role="form">
<form class="form-signin form-forgot hide" role="form">
<h2 class="form-signin-heading">{{ _("Forgot Password") }}</h2> <h2 class="form-signin-heading">{{ _("Forgot Password") }}</h2>
<input type="email" id="forgot_email" <input type="email" id="forgot_email"
class="form-control" placeholder="{{ _('Email Id') }}" required autofocus> class="form-control" placeholder="{{ _('Email Id') }}" required autofocus>


+ 10
- 12
frappe/templates/pages/sitemap.py Ver arquivo

@@ -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


@@ -15,16 +15,14 @@ def get_context(context):
"""generate the sitemap XML""" """generate the sitemap XML"""
host = get_request_site_address() host = get_request_site_address()
links = [] links = []
for l in frappe.db.sql("""select `tabWebsite Route`.page_name, `tabWebsite Route`.lastmod
from `tabWebsite Route`, `tabWebsite Template`
where
`tabWebsite Route`.website_template = `tabWebsite Template`.name
and ifnull(`tabWebsite Template`.no_sitemap, 0)=0""",
for l in frappe.db.sql("""select page_name, lastmod, controller
from `tabWebsite Route`""",
as_dict=True): as_dict=True):
links.append({
"loc": urllib.basejoin(host, urllib.quote(l.page_name.encode("utf-8"))),
"lastmod": l.lastmod
})
module = frappe.get_module(l.controller) if l.controller else None
if not getattr(module, "no_sitemap", False):
links.append({
"loc": urllib.basejoin(host, urllib.quote(l.page_name.encode("utf-8"))),
"lastmod": l.lastmod
})

return {"links":links} return {"links":links}

+ 19
- 19
frappe/templates/website_group/forum.py Ver arquivo

@@ -8,58 +8,58 @@ from frappe.website.permissions import get_access


@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def get_post_list_html(group, view, limit_start=0, limit_length=20): def get_post_list_html(group, view, limit_start=0, limit_length=20):
from frappe.templates.generators.website_group import get_views
from frappe.website.doctype.website_group.website_group import get_views
# verify permission for paging # verify permission for paging
if frappe.local.form_dict.cmd == "get_post_list_html": if frappe.local.form_dict.cmd == "get_post_list_html":
pathname = frappe.db.get_value("Website Route",
pathname = frappe.db.get_value("Website Route",
{"ref_doctype": "Website Group", "docname": group}) {"ref_doctype": "Website Group", "docname": group})
access = get_access(pathname) access = get_access(pathname)
if not access.get("read"): if not access.get("read"):
return frappe.PermissionError return frappe.PermissionError
conditions = "" conditions = ""
values = [group] values = [group]
group_type = frappe.db.get_value("Website Group", group, "group_type") group_type = frappe.db.get_value("Website Group", group, "group_type")
if group_type == "Events": if group_type == "Events":
# should show based on time upto precision of hour # should show based on time upto precision of hour
# because the current hour should also be in upcoming # because the current hour should also be in upcoming
values.append(now_datetime().replace(minute=0, second=0, microsecond=0)) values.append(now_datetime().replace(minute=0, second=0, microsecond=0))
if view in ("feed", "closed"): if view in ("feed", "closed"):
order_by = "p.creation desc" order_by = "p.creation desc"
if view == "closed": if view == "closed":
conditions += " and p.is_task=1 and p.status='Closed'" conditions += " and p.is_task=1 and p.status='Closed'"
elif view in ("popular", "open"): elif view in ("popular", "open"):
now = get_datetime_str(now_datetime()) now = get_datetime_str(now_datetime())
order_by = """(p.upvotes + post_reply_count - (timestampdiff(hour, p.creation, \"{}\") / 2)) desc,
order_by = """(p.upvotes + post_reply_count - (timestampdiff(hour, p.creation, \"{}\") / 2)) desc,
p.creation desc""".format(now) p.creation desc""".format(now)
if view == "open": if view == "open":
conditions += " and p.is_task=1 and p.status='Open'" conditions += " and p.is_task=1 and p.status='Open'"
elif view == "upcoming": elif view == "upcoming":
conditions += " and p.is_event=1 and p.event_datetime >= %s" conditions += " and p.is_event=1 and p.event_datetime >= %s"
order_by = "p.event_datetime asc" order_by = "p.event_datetime asc"
elif view == "past": elif view == "past":
conditions += " and p.is_event=1 and p.event_datetime < %s" conditions += " and p.is_event=1 and p.event_datetime < %s"
order_by = "p.event_datetime desc" order_by = "p.event_datetime desc"
values += [int(limit_start), int(limit_length)] values += [int(limit_start), int(limit_length)]
posts = frappe.db.sql("""select p.*, pr.user_image, pr.first_name, pr.last_name, posts = frappe.db.sql("""select p.*, pr.user_image, pr.first_name, pr.last_name,
(select count(pc.name) from `tabPost` pc where pc.parent_post=p.name) as post_reply_count (select count(pc.name) from `tabPost` pc where pc.parent_post=p.name) as post_reply_count
from `tabPost` p, `tabUser` pr from `tabPost` p, `tabUser` pr
where p.website_group = %s and pr.name = p.owner and ifnull(p.parent_post, '')=''
where p.website_group = %s and pr.name = p.owner and ifnull(p.parent_post, '')=''
{conditions} order by {order_by} limit %s, %s""".format(conditions=conditions, order_by=order_by), {conditions} order by {order_by} limit %s, %s""".format(conditions=conditions, order_by=order_by),
tuple(values), as_dict=True, debug=True) tuple(values), as_dict=True, debug=True)
context = { "posts": posts, "limit_start": limit_start, "view": get_views(group_type)[view] } context = { "posts": posts, "limit_start": limit_start, "view": get_views(group_type)[view] }
return frappe.get_template("templates/includes/post_list.html").render(context) return frappe.get_template("templates/includes/post_list.html").render(context)

+ 2
- 2
frappe/templates/website_group/post.py Ver arquivo

@@ -7,7 +7,7 @@ from frappe import _
from frappe.utils import get_fullname from frappe.utils import get_fullname
from frappe.website.permissions import get_access from frappe.website.permissions import get_access
from frappe.utils.file_manager import save_file from frappe.utils.file_manager import save_file
from frappe.templates.generators.website_group import get_pathname
from frappe.website.doctype.website_group.website_group import get_pathname


def get_post_context(context): def get_post_context(context):
post = frappe.get_doc("Post", frappe.form_dict.name) post = frappe.get_doc("Post", frappe.form_dict.name)
@@ -130,7 +130,7 @@ def save_post(post, content, picture=None, picture_name=None, title=None,
return post.parent_post or post.name return post.parent_post or post.name


def process_picture(post, picture_name, picture): def process_picture(post, picture_name, picture):
from frappe.templates.generators.website_group import clear_cache
from frappe.website.doctype.website_group.website_group import clear_cache


post.picture_url = save_file(picture_name, picture, "Post", post.name, decode=True).file_url post.picture_url = save_file(picture_name, picture, "Post", post.name, decode=True).file_url
frappe.db.set_value("Post", post.name, "picture_url", post.picture_url) frappe.db.set_value("Post", post.name, "picture_url", post.picture_url)


+ 21
- 21
frappe/templates/website_group/settings.py Ver arquivo

@@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.website.permissions import get_access, clear_permissions from frappe.website.permissions import get_access, clear_permissions
from frappe.templates.generators.website_group import get_pathname
from frappe.website.doctype.website_group.website_group import get_pathname
from frappe.utils.email_lib.bulk import send from frappe.utils.email_lib.bulk import send


@frappe.whitelist() @frappe.whitelist()
@@ -12,17 +12,17 @@ def suggest_user(term, group):
pathname = get_pathname(group) pathname = get_pathname(group)
if not get_access(pathname).get("admin"): if not get_access(pathname).get("admin"):
raise frappe.PermissionError raise frappe.PermissionError
users = frappe.db.sql("""select pr.name, pr.first_name, pr.last_name,
users = frappe.db.sql("""select pr.name, pr.first_name, pr.last_name,
pr.user_image, pr.location pr.user_image, pr.location
from `tabUser` pr
from `tabUser` pr
where (pr.first_name like %(term)s or pr.last_name like %(term)s) where (pr.first_name like %(term)s or pr.last_name like %(term)s)
and pr.user_type = "Website User" and pr.user_type = "Website User"
and pr.user_image is not null and pr.enabled=1 and pr.user_image is not null and pr.enabled=1
and not exists(select wsp.name from `tabWebsite Route Permission` wsp
where wsp.website_route=%(group)s and wsp.user=pr.name)""",
and not exists(select wsp.name from `tabWebsite Route Permission` wsp
where wsp.website_route=%(group)s and wsp.user=pr.name)""",
{"term": "%{}%".format(term), "group": pathname}, as_dict=True) {"term": "%{}%".format(term), "group": pathname}, as_dict=True)
template = frappe.get_template("templates/includes/user_display.html") template = frappe.get_template("templates/includes/user_display.html")
return [{ return [{
"value": "{} {}".format(pr.first_name or "", pr.last_name or ""), "value": "{} {}".format(pr.first_name or "", pr.last_name or ""),
@@ -35,7 +35,7 @@ def add_sitemap_permission(group, user):
pathname = get_pathname(group) pathname = get_pathname(group)
if not get_access(pathname).get("admin"): if not get_access(pathname).get("admin"):
raise frappe.PermissionError raise frappe.PermissionError
permission = frappe.get_doc({ permission = frappe.get_doc({
"doctype": "Website Route Permission", "doctype": "Website Route Permission",
"website_route": pathname, "website_route": pathname,
@@ -43,11 +43,11 @@ def add_sitemap_permission(group, user):
"read": 1 "read": 1
}) })
permission.insert(ignore_permissions=True) permission.insert(ignore_permissions=True)
user = permission.as_dict() user = permission.as_dict()
user.update(frappe.db.get_value("User", user.user,
user.update(frappe.db.get_value("User", user.user,
["name", "first_name", "last_name", "user_image", "location"], as_dict=True)) ["name", "first_name", "last_name", "user_image", "location"], as_dict=True))
return frappe.get_template("templates/includes/sitemap_permission.html").render({ return frappe.get_template("templates/includes/sitemap_permission.html").render({
"user": user "user": user
}) })
@@ -57,18 +57,18 @@ def update_permission(group, user, perm, value):
pathname = get_pathname(group) pathname = get_pathname(group)
if not get_access(pathname).get("admin"): if not get_access(pathname).get("admin"):
raise frappe.PermissionError raise frappe.PermissionError
permission = frappe.get_doc("Website Route Permission", {"website_route": pathname, "user": user}) permission = frappe.get_doc("Website Route Permission", {"website_route": pathname, "user": user})
permission.set(perm, int(value)) permission.set(perm, int(value))
permission.save(ignore_permissions=True) permission.save(ignore_permissions=True)
# send email # send email
if perm=="admin" and int(value): if perm=="admin" and int(value):
group_title = frappe.db.get_value("Website Route", pathname, "page_title") group_title = frappe.db.get_value("Website Route", pathname, "page_title")
subject = "You have been made Administrator of Group " + group_title subject = "You have been made Administrator of Group " + group_title
send(recipients=[user],
send(recipients=[user],
subject= subject, add_unsubscribe_link=False, subject= subject, add_unsubscribe_link=False,
message="""<h3>Group Notification<h3>\ message="""<h3>Group Notification<h3>\
<p>%s</p>\ <p>%s</p>\
@@ -82,15 +82,15 @@ def update_description(group, description):
group = frappe.get_doc("Website Group", group) group = frappe.get_doc("Website Group", group)
group.group_description = description group.group_description = description
group.save(ignore_permissions=True) group.save(ignore_permissions=True)
@frappe.whitelist() @frappe.whitelist()
def add_website_group(group, new_group, public_read, public_write, group_type="Forum"): def add_website_group(group, new_group, public_read, public_write, group_type="Forum"):
if not get_access(get_pathname(group)).get("admin"): if not get_access(get_pathname(group)).get("admin"):
raise frappe.PermissionError raise frappe.PermissionError
parent_website_route = frappe.db.get_value("Website Route",
parent_website_route = frappe.db.get_value("Website Route",
{"ref_doctype": "Website Group", "docname": group}) {"ref_doctype": "Website Group", "docname": group})
frappe.get_doc({ frappe.get_doc({
"doctype": "Website Group", "doctype": "Website Group",
"group_name": group + "-" + new_group, "group_name": group + "-" + new_group,
@@ -99,4 +99,4 @@ def add_website_group(group, new_group, public_read, public_write, group_type="F
"group_type": group_type, "group_type": group_type,
"public_read": int(public_read), "public_read": int(public_read),
"public_write": int(public_write) "public_write": int(public_write)
}).insert(ignore_permissions=True)
}).insert(ignore_permissions=True)

+ 6
- 0
frappe/utils/boilerplate.py Ver arquivo

@@ -119,6 +119,12 @@ app_version = "0.0.1"
# "Role": "home_page" # "Role": "home_page"
# }} # }}


# Generators
# ----------

# automatically create page for each record of this doctype
# website_generators = ["Web Page"]

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




+ 2
- 1
frappe/utils/email_lib/bulk.py Ver arquivo

@@ -16,6 +16,7 @@ class BulkLimitCrossedError(frappe.ValidationError): pass
def send(recipients=None, sender=None, doctype='User', email_field='email', def send(recipients=None, sender=None, doctype='User', email_field='email',
subject='[No Subject]', message='[No Content]', ref_doctype=None, ref_docname=None, subject='[No Subject]', message='[No Content]', ref_doctype=None, ref_docname=None,
add_unsubscribe_link=True): add_unsubscribe_link=True):

def is_unsubscribed(rdata): def is_unsubscribed(rdata):
if not rdata: if not rdata:
return 1 return 1
@@ -81,7 +82,7 @@ def add(email, sender, subject, formatted, text_content=None,
try: try:
e.message = get_email(email, sender=e.sender, formatted=formatted, subject=subject, e.message = get_email(email, sender=e.sender, formatted=formatted, subject=subject,
text_content=text_content).as_string() text_content=text_content).as_string()
except frappe.ValidationError:
except frappe.InvalidEmailAddressError:
# bad email id - don't add to queue # bad email id - don't add to queue
return return




+ 12
- 16
frappe/utils/email_lib/email_body.py Ver arquivo

@@ -4,15 +4,16 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import msgprint, throw, _ from frappe import msgprint, throw, _
from frappe.utils import scrub_urls, cstr
from frappe.utils import scrub_urls
import email.utils import email.utils
from markdown2 import markdown



def get_email(recipients, sender='', msg='', subject='[No Subject]', def get_email(recipients, sender='', msg='', subject='[No Subject]',
text_content = None, footer=None, print_html=None, formatted=None): text_content = None, footer=None, print_html=None, formatted=None):
"""send an html email as multipart with attachments and all""" """send an html email as multipart with attachments and all"""
emailobj = EMail(sender, recipients, subject) emailobj = EMail(sender, recipients, subject)
if (not '<br>' in msg) and (not '<p>' in msg) and (not '<div' in msg):
msg = msg.replace('\n', '<br>')
msg = markdown(msg)
emailobj.set_html(msg, text_content, footer=footer, print_html=print_html, formatted=formatted) emailobj.set_html(msg, text_content, footer=footer, print_html=print_html, formatted=formatted)


return emailobj return emailobj
@@ -151,17 +152,18 @@ class EMail:
def _validate(email): def _validate(email):
"""validate an email field""" """validate an email field"""
if email and not validate_email_add(email): if email and not validate_email_add(email):
throw(_("{0} is not a valid email id").format(email))
throw(_("{0} is not a valid email id").format(email), frappe.InvalidEmailAddressError)
return email return email


if not self.sender: if not self.sender:
self.sender = frappe.db.get_value('Outgoing Email Settings', None, self.sender = frappe.db.get_value('Outgoing Email Settings', None,
'auto_email_id') or frappe.conf.get('auto_email_id') or None 'auto_email_id') or frappe.conf.get('auto_email_id') or None
if not self.sender: if not self.sender:
msgprint(_("Please specify 'Auto Email Id' in Setup > Outgoing Email Settings"))
msg = _("Please specify 'Auto Email Id' in Setup > Outgoing Email Settings")
msgprint(msg)
if not "expires_on" in frappe.conf: if not "expires_on" in frappe.conf:
msgprint(_("Alternatively, you can also specify 'auto_email_id' in site_config.json")) msgprint(_("Alternatively, you can also specify 'auto_email_id' in site_config.json"))
raise frappe.ValidationError
raise frappe.ValidationError, msg


self.sender = _validate(self.sender) self.sender = _validate(self.sender)
self.reply_to = _validate(self.reply_to) self.reply_to = _validate(self.reply_to)
@@ -189,7 +191,6 @@ class EMail:


def get_formatted_html(subject, message, footer=None, print_html=None): def get_formatted_html(subject, message, footer=None, print_html=None):
# imported here to avoid cyclic import # imported here to avoid cyclic import
import inlinestyler.utils


message = scrub_urls(message) message = scrub_urls(message)
rendered_email = frappe.get_template("templates/emails/standard.html").render({ rendered_email = frappe.get_template("templates/emails/standard.html").render({
@@ -200,22 +201,17 @@ def get_formatted_html(subject, message, footer=None, print_html=None):
"subject": subject "subject": subject
}) })


# if in a test case, do not inline css
if frappe.local.flags.in_test:
return rendered_email

return cstr(inlinestyler.utils.inline_css(rendered_email))
return rendered_email


def get_footer(footer=None): def get_footer(footer=None):
"""append a footer (signature)""" """append a footer (signature)"""
footer = footer or "" footer = footer or ""


# control panel
footer += frappe.db.get_default('mail_footer') or ''

# hooks # hooks
for f in frappe.get_hooks("mail_footer"): for f in frappe.get_hooks("mail_footer"):
footer += frappe.get_attr(f)
# mail_footer could be a function that returns a value
mail_footer = frappe.get_attr(f)
footer += (mail_footer if isinstance(mail_footer, basestring) else mail_footer())


footer += "<!--unsubscribe link here-->" footer += "<!--unsubscribe link here-->"




+ 6
- 2
frappe/utils/fixtures.py Ver arquivo

@@ -6,8 +6,12 @@ from __future__ import unicode_literals
import frappe, os import frappe, os
from frappe.core.page.data_import_tool.data_import_tool import import_doc, export_fixture, export_csv from frappe.core.page.data_import_tool.data_import_tool import import_doc, export_fixture, export_csv


def sync_fixtures():
for app in frappe.get_installed_apps():
def sync_fixtures(app=None):
if app:
apps = [app]
else:
apps = frappe.get_installed_apps()
for app in apps:
if os.path.exists(frappe.get_app_path(app, "fixtures")): if os.path.exists(frappe.get_app_path(app, "fixtures")):
for fname in os.listdir(frappe.get_app_path(app, "fixtures")): for fname in os.listdir(frappe.get_app_path(app, "fixtures")):
if fname.endswith(".json") or fname.endswith(".csv"): if fname.endswith(".json") or fname.endswith(".csv"):


+ 0
- 5
frappe/website/__init__.py Ver arquivo

@@ -1,7 +1,2 @@
# 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 frappe.website.doctype.website_template.website_template \
import rebuild_website_template as rebuild_config

+ 7
- 3
frappe/website/context.py Ver arquivo

@@ -53,9 +53,13 @@ def build_context(sitemap_options):


# provide doc # provide doc
if context.doctype and context.docname: if context.doctype and context.docname:
context.doc = frappe.get_doc(context.doctype, context.docname)
doc = frappe.get_doc(context.doctype, context.docname)
context.doc = doc
context.update(doc.as_dict())
if hasattr(context.doc, "get_context"):
context.update(context.doc.get_context(context) or {})


if context.controller:
elif context.controller:
module = frappe.get_module(context.controller) module = frappe.get_module(context.controller)


if module and hasattr(module, "get_context"): if module and hasattr(module, "get_context"):
@@ -63,7 +67,7 @@ def build_context(sitemap_options):


add_metatags(context) add_metatags(context)


if context.get("base_template_path") != context.get("template_path") and not context.get("rendered"):
if context.get("base_template_path") != context.get("template") and not context.get("rendered"):
context.data = render_blocks(context) context.data = render_blocks(context)


return context return context


+ 9
- 3
frappe/website/doctype/blog_category/blog_category.py Ver arquivo

@@ -6,15 +6,21 @@ import frappe
from frappe.website.website_generator import WebsiteGenerator from frappe.website.website_generator import WebsiteGenerator
from frappe.website.render import clear_cache from frappe.website.render import clear_cache


template = "templates/generators/blog_category.html"
no_cache = True

class BlogCategory(WebsiteGenerator): class BlogCategory(WebsiteGenerator):
def autoname(self): def autoname(self):
# to override autoname of WebsiteGenerator # to override autoname of WebsiteGenerator
self.name = self.category_name self.name = self.category_name
def get_page_title(self): def get_page_title(self):
return self.title or self.name return self.title or self.name
def on_update(self): def on_update(self):
WebsiteGenerator.on_update(self) WebsiteGenerator.on_update(self)
clear_cache() clear_cache()

def get_parent_website_route(self):
parent_website_sitemap = super(BlogCategory, self).get_parent_website_route()
return parent_website_sitemap or "blog"

+ 3
- 2
frappe/website/doctype/blog_post/blog_post.json Ver arquivo

@@ -44,7 +44,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Blog Category", "label": "Blog Category",
"options": "Blog Category", "options": "Blog Category",
"permlevel": 0
"permlevel": 0,
"reqd": 1
}, },
{ {
"fieldname": "parent_website_route", "fieldname": "parent_website_route",
@@ -95,7 +96,7 @@
"icon": "icon-quote-left", "icon": "icon-quote-left",
"idx": 1, "idx": 1,
"max_attachments": 5, "max_attachments": 5,
"modified": "2014-05-27 03:49:07.888408",
"modified": "2014-06-27 05:08:37.936947",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Website", "module": "Website",
"name": "Blog Post", "name": "Blog Post",


+ 80
- 6
frappe/website/doctype/blog_post/blog_post.py Ver arquivo

@@ -7,19 +7,22 @@ import frappe, re


from frappe.website.website_generator import WebsiteGenerator from frappe.website.website_generator import WebsiteGenerator
from frappe.website.render import clear_cache from frappe.website.render import clear_cache
from frappe import _
from frappe.utils import today
from frappe.utils import today, cint, global_date_format, get_fullname
from frappe.website.utils import find_first_image, get_comment_list

order_by = "`tabBlog Post`.published_on desc"
condition_field = "published"
template = "templates/generators/blog_post.html"


class BlogPost(WebsiteGenerator): class BlogPost(WebsiteGenerator):
save_versions = True save_versions = True

def get_page_title(self): def get_page_title(self):
return self.title return self.title


def validate(self): def validate(self):
if not self.blog_intro: if not self.blog_intro:
self.blog_intro = self.content[:140] self.blog_intro = self.content[:140]
re.sub("\<[^>]*\>", "", self.blog_intro)
self.blog_intro = re.sub("\<[^>]*\>", "", self.blog_intro)


if self.blog_intro: if self.blog_intro:
self.blog_intro = self.blog_intro[:140] self.blog_intro = self.blog_intro[:140]
@@ -27,22 +30,93 @@ class BlogPost(WebsiteGenerator):
if self.published and not self.published_on: if self.published and not self.published_on:
self.published_on = today() self.published_on = today()


self.parent_website_route = frappe.db.get_value("Website Route",
{"ref_doctype": "Blog Category", "docname": self.blog_category})
# make sure route for category exists
self.parent_website_route = self.get_category_route()
if not self.parent_website_route:
frappe.get_doc("Blog Category", self.blog_category).save(ignore_permissions=True)
self.parent_website_route = self.get_category_route()


# update posts # update posts
frappe.db.sql("""update tabBlogger set posts=(select count(*) from `tabBlog Post` frappe.db.sql("""update tabBlogger set posts=(select count(*) from `tabBlog Post`
where ifnull(blogger,'')=tabBlogger.name) where ifnull(blogger,'')=tabBlogger.name)
where name=%s""", (self.blogger,)) where name=%s""", (self.blogger,))


def get_category_route(self):
return frappe.db.get_value("Website Route",
{"ref_doctype": "Blog Category", "docname": self.blog_category})


def on_update(self): def on_update(self):
WebsiteGenerator.on_update(self) WebsiteGenerator.on_update(self)
clear_cache("writers") clear_cache("writers")


def get_context(self, context):
# this is for double precaution. usually it wont reach this code if not published
if not cint(self.published):
raise Exception, "This blog has not been published yet!"

# temp fields
context.full_name = get_fullname(self.owner)
context.updated = global_date_format(self.published_on)

if self.blogger:
context.blogger_info = frappe.get_doc("Blogger", self.blogger).as_dict()

context.description = self.blog_intro or self.content[:140]

context.metatags = {
"name": self.title,
"description": context.description,
}

image = find_first_image(self.content)
if image:
context.metatags["image"] = image

context.categories = frappe.db.sql_list("""select name from
`tabBlog Category` order by name""")

context.comment_list = get_comment_list(self.doctype, self.name)

return context

def clear_blog_cache(): def clear_blog_cache():
for blog in frappe.db.sql_list("""select page_name from for blog in frappe.db.sql_list("""select page_name from
`tabBlog Post` where ifnull(published,0)=1"""): `tabBlog Post` where ifnull(published,0)=1"""):
clear_cache(blog) clear_cache(blog)


clear_cache("writers") clear_cache("writers")

@frappe.whitelist(allow_guest=True)
def get_blog_list(start=0, by=None, category=None):
condition = ""
if by:
condition = " and t1.blogger='%s'" % by.replace("'", "\'")
if category:
condition += " and t1.blog_category='%s'" % category.replace("'", "\'")
query = """\
select
t1.title, t1.name, t3.name as page_name, t1.published_on as creation,
day(t1.published_on) as day, monthname(t1.published_on) as month,
year(t1.published_on) as year,
ifnull(t1.blog_intro, t1.content) as content,
t2.full_name, t2.avatar, t1.blogger,
(select count(name) from `tabComment` where
comment_doctype='Blog Post' and comment_docname=t1.name) as comments
from `tabBlog Post` t1, `tabBlogger` t2, `tabWebsite Route` t3
where ifnull(t1.published,0)=1
and t1.blogger = t2.name
and t3.docname = t1.name
and t3.ref_doctype = "Blog Post"
%(condition)s
order by published_on desc, name asc
limit %(start)s, 20""" % {"start": start, "condition": condition}

result = frappe.db.sql(query, as_dict=1)

# strip html tags from content
for res in result:
res['published'] = global_date_format(res['creation'])
res['content'] = res['content'][:140]

return result


+ 1
- 1
frappe/website/doctype/post/post.py Ver arquivo

@@ -39,7 +39,7 @@ class Post(Document):


def on_update(self): def on_update(self):
from frappe.templates.website_group.post import clear_post_cache from frappe.templates.website_group.post import clear_post_cache
from frappe.templates.generators.website_group import clear_cache
from frappe.website.doctype.website_group.website_group import clear_cache


clear_cache(website_group=self.website_group) clear_cache(website_group=self.website_group)
clear_post_cache(self.parent_post or self.name) clear_post_cache(self.parent_post or self.name)


+ 16
- 38
frappe/website/doctype/web_page/test_web_page.py Ver arquivo

@@ -5,56 +5,34 @@ import frappe
test_records = frappe.get_test_records('Web Page') test_records = frappe.get_test_records('Web Page')


class TestWebPage(unittest.TestCase): class TestWebPage(unittest.TestCase):
def setUp(self):
frappe.db.sql("delete from `tabWeb Page`")
frappe.db.sql("delete from `tabWebsite Route` where ref_doctype='Web Page'")
for t in test_records:
frappe.get_doc(t).insert()

def test_check_sitemap(self): def test_check_sitemap(self):
self.assertEquals(frappe.db.get_value("Website Route",
self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-1"}), "test-web-page-1") {"ref_doctype":"Web Page", "docname": "test-web-page-1"}), "test-web-page-1")
self.assertEquals(frappe.db.get_value("Website Route",
self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-2"}), "test-web-page-1/test-web-page-2") {"ref_doctype":"Web Page", "docname": "test-web-page-2"}), "test-web-page-1/test-web-page-2")


self.assertEquals(frappe.db.get_value("Website Route",
self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-3"}), "test-web-page-1/test-web-page-3") {"ref_doctype":"Web Page", "docname": "test-web-page-3"}), "test-web-page-1/test-web-page-3")
def test_check_idx(self):
self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-2"}, 'idx'), 0)
self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-3"}, 'idx'), 1)

self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-5"}, 'idx'), 2)

def test_check_rename(self): def test_check_rename(self):
web_page = frappe.get_doc("Web Page", "test-web-page-1") web_page = frappe.get_doc("Web Page", "test-web-page-1")
web_page.parent_website_route = "test-web-page-4" web_page.parent_website_route = "test-web-page-4"
web_page.save() web_page.save()


self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-2"}),
self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-2"}),
"test-web-page-4/test-web-page-1/test-web-page-2") "test-web-page-4/test-web-page-1/test-web-page-2")

web_page.parent_website_route = "" web_page.parent_website_route = ""
web_page.save() web_page.save()


self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-2"}),
self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-2"}),
"test-web-page-1/test-web-page-2") "test-web-page-1/test-web-page-2")
def test_check_move(self):
web_page = frappe.get_doc("Web Page", "test-web-page-3")
web_page.parent_website_route = "test-web-page-4"
web_page.save()
self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-2"}, 'idx'), 0)
self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-3"}, 'idx'), 0)

self.assertEquals(frappe.db.get_value("Website Route",
{"ref_doctype":"Web Page", "docname": "test-web-page-5"}, 'idx'), 1)
web_page = frappe.get_doc("Web Page", "test-web-page-3")
web_page.parent_website_route = "test-web-page-1"
web_page.save()

+ 1
- 0
frappe/website/doctype/web_page/web_page.js Ver arquivo

@@ -18,6 +18,7 @@ $.extend(cur_frm.cscript, {
}, },
refresh: function(doc) { refresh: function(doc) {
cur_frm.cscript.layout(doc); cur_frm.cscript.layout(doc);
cur_frm.set_intro("");
if (!doc.__islocal && doc.published) { if (!doc.__islocal && doc.published) {
cur_frm.set_intro(__("Published on website at: {0}", cur_frm.set_intro(__("Published on website at: {0}",
[repl('<a href="/%(website_route)s" target="_blank">/%(website_route)s</a>', doc.__onload)])); [repl('<a href="/%(website_route)s" target="_blank">/%(website_route)s</a>', doc.__onload)]));


+ 133
- 126
frappe/website/doctype/web_page/web_page.json Ver arquivo

@@ -1,173 +1,180 @@
{ {
"allow_attach": 1,
"creation": "2013-03-28 10:35:30",
"description": "Page to show on the website\n",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Transaction",
"allow_attach": 1,
"creation": "2013-03-28 10:35:30",
"description": "Page to show on the website\n",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Transaction",
"fields": [ "fields": [
{ {
"fieldname": "section_title",
"fieldtype": "Section Break",
"label": "Title",
"fieldname": "section_title",
"fieldtype": "Section Break",
"label": "Title",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "Title / headline of your page",
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"permlevel": 0,
"description": "Title / headline of your page",
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"permlevel": 0,
"reqd": 1 "reqd": 1
},
{
"description": "Page url name (auto-generated)",
"fieldname": "page_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Page Name",
"permlevel": 0,
},
{
"description": "Page url name (auto-generated)",
"fieldname": "page_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Page Name",
"permlevel": 0,
"read_only": 0 "read_only": 0
},
},
{ {
"fieldname": "parent_website_route",
"fieldtype": "Link",
"label": "Parent Website Page",
"options": "Website Route",
"fieldname": "parent_website_route",
"fieldtype": "Link",
"label": "Parent Website Page",
"options": "Website Route",
"permlevel": 0 "permlevel": 0
},
},
{ {
"fieldname": "published",
"fieldtype": "Check",
"label": "Published",
"fieldname": "published",
"fieldtype": "Check",
"label": "Published",
"permlevel": 0 "permlevel": 0
},
},
{ {
"fieldname": "cb1",
"fieldtype": "Column Break",
"permlevel": 0,
"description": "0 is highest",
"fieldname": "idx",
"fieldtype": "Int",
"label": "Priority",
"permlevel": 0
},
{
"fieldname": "cb1",
"fieldtype": "Column Break",
"permlevel": 0,
"width": "50%" "width": "50%"
},
},
{ {
"description": "Description for page header.",
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description",
"description": "Description for page header.",
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "Page content",
"fieldname": "sb1",
"fieldtype": "Section Break",
"label": "Content",
"description": "Page content",
"fieldname": "sb1",
"fieldtype": "Section Break",
"label": "Content",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "Begin this page with a slideshow of images",
"fieldname": "slideshow",
"fieldtype": "Link",
"label": "Slideshow",
"options": "Website Slideshow",
"description": "Begin this page with a slideshow of images",
"fieldname": "slideshow",
"fieldtype": "Link",
"label": "Slideshow",
"options": "Website Slideshow",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "Content in markdown format that appears on the main side of your page",
"fieldname": "main_section",
"fieldtype": "Text Editor",
"label": "Main Section",
"description": "Content in markdown format that appears on the main side of your page",
"fieldname": "main_section",
"fieldtype": "Text Editor",
"label": "Main Section",
"permlevel": 0 "permlevel": 0
},
},
{ {
"depends_on": "eval:!doc.__islocal",
"description": "Link to other pages in the side bar and next section",
"fieldname": "sb2",
"fieldtype": "Section Break",
"label": "More",
"depends_on": "eval:!doc.__islocal",
"description": "Link to other pages in the side bar and next section",
"fieldname": "sb2",
"fieldtype": "Section Break",
"label": "More",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "HTML for header section. Optional",
"fieldname": "header",
"fieldtype": "Text",
"label": "Header",
"description": "HTML for header section. Optional",
"fieldname": "header",
"fieldtype": "Text",
"label": "Header",
"permlevel": 0 "permlevel": 0
},
},
{ {
"fieldname": "enable_comments",
"fieldtype": "Check",
"label": "Enable Comments",
"fieldname": "enable_comments",
"fieldtype": "Check",
"label": "Enable Comments",
"permlevel": 0 "permlevel": 0
},
},
{ {
"fieldname": "text_align",
"fieldtype": "Select",
"label": "Text Align",
"options": "Left\nCenter\nRight",
"fieldname": "text_align",
"fieldtype": "Select",
"label": "Text Align",
"options": "Left\nCenter\nRight",
"permlevel": 0 "permlevel": 0
},
},
{ {
"fieldname": "custom_javascript",
"fieldtype": "Section Break",
"label": "Custom Javascript",
"fieldname": "custom_javascript",
"fieldtype": "Section Break",
"label": "Custom Javascript",
"permlevel": 0 "permlevel": 0
},
},
{ {
"description": "Add code as &lt;script&gt;",
"fieldname": "insert_code",
"fieldtype": "Check",
"label": "Insert Code",
"description": "Add code as &lt;script&gt;",
"fieldname": "insert_code",
"fieldtype": "Check",
"label": "Insert Code",
"permlevel": 0 "permlevel": 0
},
},
{ {
"depends_on": "insert_code",
"fieldname": "javascript",
"fieldtype": "Code",
"label": "Javascript",
"options": "Javascript",
"depends_on": "insert_code",
"fieldname": "javascript",
"fieldtype": "Code",
"label": "Javascript",
"options": "Javascript",
"permlevel": 0 "permlevel": 0
},
},
{ {
"fieldname": "custom_css",
"fieldtype": "Section Break",
"label": "Custom CSS",
"fieldname": "custom_css",
"fieldtype": "Section Break",
"label": "Custom CSS",
"permlevel": 0 "permlevel": 0
},
},
{ {
"fieldname": "insert_style",
"fieldtype": "Check",
"label": "Insert Style",
"fieldname": "insert_style",
"fieldtype": "Check",
"label": "Insert Style",
"permlevel": 0 "permlevel": 0
},
},
{ {
"depends_on": "insert_style",
"fieldname": "css",
"fieldtype": "Code",
"label": "CSS",
"options": "CSS",
"depends_on": "insert_style",
"fieldname": "css",
"fieldtype": "Code",
"label": "CSS",
"options": "CSS",
"permlevel": 0 "permlevel": 0
} }
],
"icon": "icon-file-alt",
"idx": 1,
"max_attachments": 20,
"modified": "2014-05-26 03:36:51.942919",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Page",
"owner": "Administrator",
],
"icon": "icon-file-alt",
"idx": 1,
"max_attachments": 20,
"modified": "2014-06-17 05:56:30.267409",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Page",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"submit": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"submit": 0,
"write": 1 "write": 1
} }
] ]
}
}

+ 32
- 3
frappe/website/doctype/web_page/web_page.py Ver arquivo

@@ -2,14 +2,43 @@
# MIT License. See license.txt # MIT License. See license.txt


from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, os, time, re
import frappe, re
import requests, requests.exceptions import requests, requests.exceptions
from frappe.website.website_generator import WebsiteGenerator from frappe.website.website_generator import WebsiteGenerator
from frappe.website.utils import cleanup_page_name
from frappe.utils import cint
from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
from frappe.website.utils import find_first_image, get_comment_list

template = "templates/generators/web_page.html"
condition_field = "published"


class WebPage(WebsiteGenerator): class WebPage(WebsiteGenerator):
save_versions = True save_versions = True
def get_context(self, context):
if context.slideshow:
context.update(get_slideshow(self))

if self.enable_comments:
context.comment_list = get_comment_list(self.doctype, self.name)

context.update({
"style": self.css or "",
"script": self.javascript or ""
})

context.metatags = {
"name": self.title,
"description": self.description or (self.main_section or "")[:150]
}

image = find_first_image(self.main_section or "")
if image:
context.metatags["image"] = image

if not context.header:
context.header = self.title

return context



def check_broken_links(): def check_broken_links():
cnt = 0 cnt = 0


+ 238
- 5
frappe/website/doctype/website_group/website_group.py Ver arquivo

@@ -4,17 +4,250 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.website.website_generator import WebsiteGenerator from frappe.website.website_generator import WebsiteGenerator
from frappe.templates.generators.website_group import clear_cache
from frappe.model.naming import make_autoname
from frappe.website.render import can_cache
from frappe.templates.website_group.forum import get_post_list_html

no_cache = True
template = "templates/generators/website_group.html"


class WebsiteGroup(WebsiteGenerator): class WebsiteGroup(WebsiteGenerator):
def get_page_title(self): def get_page_title(self):
return self.group_title return self.group_title
def on_update(self): def on_update(self):
WebsiteGenerator.on_update(self) WebsiteGenerator.on_update(self)
clear_cache(website_group=self.name) clear_cache(website_group=self.name)
def after_insert(self): def after_insert(self):
clear_cache(path=self.parent_website_route) clear_cache(path=self.parent_website_route)

def get_context(self, context):
group, view = guess_group_view(context)

try:
if not has_access(context.access, view):
raise frappe.PermissionError

return get_group_context(group, view, context)

except frappe.DoesNotExistError:
return {
"content": '<div class="alert alert-danger full-page">'
'The page you are looking for does not exist.</div>'
}
except frappe.PermissionError:
return {
"content": '<div class="alert alert-danger full-page">'
'You are not permitted to view this page.</div>'
}

return context


def get_group_context(group, view, context):
cache_key = "website_group_context:{}:{}".format(group, view)

views = get_views(context.doc.group_type)
view = frappe._dict(views.get(view))

if can_cache(view.no_cache):
group_context = frappe.cache().get_value(cache_key)
if group_context:
return group_context

group_context = build_group_context(group, view, views, context)

if can_cache(view.get("no_cache")):
frappe.cache().set_value(cache_key, group_context)

return group_context

def build_group_context(group, view, views, context):
title = "{} - {}".format(context.doc.group_title, view.get("label"))

group_context = frappe._dict({
"group": context.doc,
"view": view,
"views": [v[1] for v in sorted(views.iteritems(), key=lambda (k, v): v.get("idx"))],
"title": title,
"pathname": context.pathname
})
group_context.update(build_view_context(group_context))

return group_context

def build_view_context(context):
from frappe.templates.website_group.post import get_post_context

if context.view.name in ("popular", "feed", "open", "closed", "upcoming", "past"):
context.post_list_html = get_post_list_html(context.group.name, context.view.name)

elif context.view.name == "edit":
context.post = frappe.get_doc("Post", frappe.form_dict.name).as_dict()

if context.post.assigned_to:
context.user = frappe.get_doc("User", context.post.assigned_to)

elif context.view.name == "settings":
context.users = frappe.db.sql("""select p.*, wsp.`read`, wsp.`write`, wsp.`admin`
from `tabUser` p, `tabWebsite Route Permission` wsp
where wsp.website_route=%s and wsp.user=p.name""", context.pathname, as_dict=True)

elif context.view.name == "post":
context.update(get_post_context(context))

return context

def guess_group_view(context):
group = context.docname
view = frappe.form_dict.view or get_default_view(context.doc.group_type)
return group, view

def get_default_view(group_type):
for view, opts in get_views(group_type).iteritems():
if opts.get("default"):
return view

def get_views(group_type=None):
if group_type:
group_views = frappe._dict(views[group_type])
else:
group_views = {}
for group_type in views:
group_views.update(views[group_type].copy())

group_views.update(common_views)

if group_type == "Forum":
group_views["post"]["upvote"] = True

return group_views

def has_access(access, view):
if view=="settings":
return access.get("admin")
elif view in ("add", "edit"):
return access.get("write")
else:
return access.get("read")

def clear_cache(path=None, website_group=None):
from frappe.templates.website_group.post import clear_post_cache
if path:
website_groups = [frappe.db.get_value("Website Route", path, "docname")]
elif website_group:
website_groups = [website_group]
else:
clear_post_cache()
website_groups = frappe.db.sql_list("""select name from `tabWebsite Group`""")

cache = frappe.cache()
all_views = get_views()
for group in website_groups:
for view in all_views:
cache.delete_value("website_group_context:{}:{}".format(group, view))

def clear_event_cache():
for group in frappe.db.sql_list("""select name from `tabWebsite Group` where group_type='Event'"""):
clear_cache(website_group=group)

def clear_cache_on_doc_event(doc, method, *args, **kwargs):
clear_cache(path=doc.website_route, website_group=doc.website_group)

def get_pathname(group):
return frappe.db.get_value("Website Route", {"ref_doctype": "Website Group",
"docname": group})

views = {
"Forum": {
"popular": {
"name": "popular",
"template_path": "templates/website_group/forum.html",
"label": "Popular",
"icon": "icon-heart",
"default": True,
"upvote": True,
"idx": 1
},
"feed": {
"name": "feed",
"template_path": "templates/website_group/forum.html",
"label": "Feed",
"icon": "icon-rss",
"upvote": True,
"idx": 2
}
},
"Tasks": {
"open": {
"name": "open",
"template_path": "templates/website_group/forum.html",
"label": "Open",
"icon": "icon-inbox",
"default": True,
"upvote": True,
"idx": 1
},
"closed": {
"name": "closed",
"template_path": "templates/website_group/forum.html",
"label": "Closed",
"icon": "icon-smile",
"idx": 2
}
},
"Events": {
"upcoming": {
"name": "upcoming",
"template_path": "templates/website_group/forum.html",
"label": "Upcoming",
"icon": "icon-calendar",
"default": True,
"idx": 1
},
"past": {
"name": "past",
"template_path": "templates/website_group/forum.html",
"label": "Past",
"icon": "icon-time",
"idx": 2
}
}
}

common_views = {
"post": {
"name": "post",
"template_path": "templates/website_group/post.html",
"label": "Post",
"icon": "icon-comments",
"hidden": True,
"no_cache": True,
"idx": 3
},
"edit": {
"name": "edit",
"template_path": "templates/website_group/edit_post.html",
"label": "Edit Post",
"icon": "icon-pencil",
"hidden": True,
"no_cache": True,
"idx": 4
},
"add": {
"name": "add",
"template_path": "templates/website_group/edit_post.html",
"label": "Add Post",
"icon": "icon-plus",
"hidden": True,
"idx": 5
},
"settings": {
"name": "settings",
"template_path": "templates/website_group/settings.html",
"label": "Settings",
"icon": "icon-cog",
"hidden": True,
"idx": 6
}
}

+ 14
- 7
frappe/website/doctype/website_route/website_route.json Ver arquivo

@@ -1,7 +1,7 @@
{ {
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:page_name", "autoname": "field:page_name",
"creation": "2013-11-18 15:38:40.000000",
"creation": "2013-11-18 15:38:40",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"fields": [ "fields": [
@@ -38,8 +38,9 @@
}, },
{ {
"fieldname": "docname", "fieldname": "docname",
"fieldtype": "Data",
"fieldtype": "Dynamic Link",
"label": "Docname", "label": "Docname",
"options": "ref_doctype",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
}, },
@@ -50,10 +51,16 @@
"permlevel": 0 "permlevel": 0
}, },
{ {
"fieldname": "website_template",
"fieldtype": "Link",
"label": "Website Template",
"options": "Website Template",
"fieldname": "template",
"fieldtype": "Read Only",
"label": "Template",
"permlevel": 0
},
{
"fieldname": "controller",
"fieldtype": "Read Only",
"label": "Controller",
"options": "",
"permlevel": 0 "permlevel": 0
}, },
{ {
@@ -112,7 +119,7 @@
} }
], ],
"idx": 1, "idx": 1,
"modified": "2014-02-24 12:46:59.000000",
"modified": "2014-06-27 05:04:57.721756",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Website", "module": "Website",
"name": "Website Route", "name": "Website Route",


+ 34
- 82
frappe/website/doctype/website_route/website_route.py Ver arquivo

@@ -22,59 +22,45 @@ class WebsiteRoute(NestedSet):
return url return url


def validate(self): def validate(self):
if self.get_url() != self.name:
self.rename()
self.check_if_page_name_is_unique() self.check_if_page_name_is_unique()
self.make_private_if_parent_is_private()
if not self.is_new():
self.renumber_if_moved()
self.set_idx()

def renumber_if_moved(self):
current_parent = frappe.db.get_value("Website Route", self.name, "parent_website_route")
if current_parent and current_parent != self.parent_website_route:
# move-up

# sitemap
frappe.db.sql("""update `tabWebsite Route` set idx=idx-1
where parent_website_route=%s and idx>%s""", (current_parent, self.idx))

# source table
frappe.db.sql("""update `tab{0}` set idx=idx-1
where parent_website_route=%s and idx>%s""".format(self.ref_doctype),
(current_parent, self.idx))
self.idx = None
if not frappe.flags.in_sync_website:
self.make_private_if_parent_is_private()


def on_update(self): def on_update(self):
if not frappe.flags.in_rebuild_config:
if self.get_url() != self.name:
self.rename()
if not frappe.flags.in_sync_website:
NestedSet.on_update(self) NestedSet.on_update(self)
self.clear_cache() self.clear_cache()


def set_idx(self):
if self.parent_website_route:
if self.idx == None:
self.set_idx_as_last()

def set_idx_as_last(self):
# new, append
self.idx = int(frappe.db.sql("""select ifnull(max(ifnull(idx, -1)), -1)
from `tabWebsite Route`
where ifnull(parent_website_route, '')=%s and name!=%s""",
(self.parent_website_route or '',
self.name))[0][0]) + 1

def rename(self):
def rename(self, new_page_name=None, new_parent_website_route=None):
self.old_name = self.name self.old_name = self.name
self.old_parent_website_route = self.parent_website_route

# get new route
if new_page_name != None:
self.page_name = new_page_name
if new_parent_website_route != None:
self.parent_website_route = new_parent_website_route
self.name = self.get_url() self.name = self.get_url()
frappe.db.sql("""update `tabWebsite Route` set name=%s where name=%s""",
(self.name, self.old_name))

# update values (don't run triggers)
frappe.db.sql("""update `tabWebsite Route` set
name=%s, page_name=%s, parent_website_route=%s where name=%s""",
(self.name, self.page_name, self.parent_website_route, self.old_name))

self.rename_links() self.rename_links()
self.rename_descendants() self.rename_descendants()
self.clear_cache(self.old_name) self.clear_cache(self.old_name)
self.clear_cache(self.old_parent_website_route)
self.clear_cache(self.parent_website_route)


def rename_links(self): def rename_links(self):
for doctype in frappe.db.sql_list("""select parent from tabDocField where fieldtype='Link' and
fieldname='parent_website_route' and options='Website Route'"""):
for doctype in frappe.db.sql_list("""select parent from tabDocField
where fieldtype='Link'
and fieldname='parent_website_route'
and options='Website Route'
and parent!='Website Route'"""):
for name in frappe.db.sql_list("""select name from `tab{}` for name in frappe.db.sql_list("""select name from `tab{}`
where parent_website_route=%s""".format(doctype), self.old_name): where parent_website_route=%s""".format(doctype), self.old_name):
frappe.db.set_value(doctype, name, "parent_website_route", self.name) frappe.db.set_value(doctype, name, "parent_website_route", self.name)
@@ -82,7 +68,7 @@ class WebsiteRoute(NestedSet):
def rename_descendants(self): def rename_descendants(self):
# rename children # rename children
for name in frappe.db.sql_list("""select name from `tabWebsite Route` for name in frappe.db.sql_list("""select name from `tabWebsite Route`
where parent_website_route=%s""", self.name):
where parent_website_route=%s""", self.old_name):
child = frappe.get_doc("Website Route", name) child = frappe.get_doc("Website Route", name)
child.parent_website_route = self.name child.parent_website_route = self.name
child.save() child.save()
@@ -92,7 +78,7 @@ class WebsiteRoute(NestedSet):
if self.page_or_generator == "Page": if self.page_or_generator == "Page":
# for a page, name and website sitemap config form a unique key # for a page, name and website sitemap config form a unique key
exists = frappe.db.sql("""select name from `tabWebsite Route` exists = frappe.db.sql("""select name from `tabWebsite Route`
where name=%s and website_template!=%s""", (self.name, self.website_template))
where name=%s""", self.name)
else: else:
# for a generator, name, ref_doctype and docname make a unique key # for a generator, name, ref_doctype and docname make a unique key
exists = frappe.db.sql("""select name from `tabWebsite Route` exists = frappe.db.sql("""select name from `tabWebsite Route`
@@ -119,39 +105,13 @@ class WebsiteRoute(NestedSet):


def clear_cache(self, name=None): def clear_cache(self, name=None):
from frappe.website.render import clear_cache from frappe.website.render import clear_cache
clear_cache(name or self.name)

if self.parent_website_route:
clear_cache(self.parent_website_route)

def add_to_sitemap(options):
website_route = frappe.new_doc("Website Route")

for key in sitemap_fields:
website_route.set(key, options.get(key))
if not website_route.page_name:
website_route.page_name = options.get("link_name")
website_route.website_template = options.get("link_name")

website_route.insert(ignore_permissions=True)

return website_route.idx

def update_sitemap(website_route, options):
website_route = frappe.get_doc("Website Route", website_route)

for key in sitemap_fields:
website_route.set(key, options.get(key))

if not website_route.page_name:
# for pages
website_route.page_name = options.get("link_name")

website_route.website_template = options.get("link_name")
website_route.ignore_links = True
website_route.save(ignore_permissions=True)
if name:
clear_cache(name)
else:
if self.parent_website_route:
clear_cache(self.parent_website_route)


return website_route.idx
clear_cache(self.name)


def remove_sitemap(page_name=None, ref_doctype=None, docname=None): def remove_sitemap(page_name=None, ref_doctype=None, docname=None):
if page_name: if page_name:
@@ -159,11 +119,3 @@ def remove_sitemap(page_name=None, ref_doctype=None, docname=None):
elif ref_doctype and docname: elif ref_doctype and docname:
frappe.delete_doc("Website Route", frappe.db.sql_list("""select name from `tabWebsite Route` frappe.delete_doc("Website Route", frappe.db.sql_list("""select name from `tabWebsite Route`
where ref_doctype=%s and docname=%s""", (ref_doctype, docname)), ignore_permissions=True, force=True) where ref_doctype=%s and docname=%s""", (ref_doctype, docname)), ignore_permissions=True, force=True)

def cleanup_sitemap():
"""remove sitemap records where its config do not exist anymore"""
to_delete = frappe.db.sql_list("""select name from `tabWebsite Route` ws
where not exists(select name from `tabWebsite Template` wsc
where wsc.name=ws.website_template)""")

frappe.delete_doc("Website Route", to_delete, ignore_permissions=True)

+ 0
- 1
frappe/website/doctype/website_slideshow/website_slideshow.py Ver arquivo

@@ -9,7 +9,6 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document


class WebsiteSlideshow(Document): class WebsiteSlideshow(Document):

def on_update(self): def on_update(self):
# a slide show can be in use and any change in it should get reflected # a slide show can be in use and any change in it should get reflected
from frappe.website.render import clear_cache from frappe.website.render import clear_cache


+ 0
- 0
frappe/website/doctype/website_template/__init__.py Ver arquivo


+ 0
- 130
frappe/website/doctype/website_template/website_template.json Ver arquivo

@@ -1,130 +0,0 @@
{
"autoname": "field:link_name",
"creation": "2013-11-18 15:35:00.000000",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"fieldname": "page_or_generator",
"fieldtype": "Select",
"label": "Page or Generator",
"options": "Page\nGenerator",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "ref_doctype",
"fieldtype": "Link",
"label": "Ref DocType",
"options": "DocType",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "link_name",
"fieldtype": "Data",
"label": "Link Name",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "page_title",
"fieldtype": "Data",
"label": "Page Title",
"permlevel": 0
},
{
"fieldname": "base_template_path",
"fieldtype": "Data",
"label": "Base Template Path",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "template_path",
"fieldtype": "Data",
"label": "Template Path",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "controller",
"fieldtype": "Data",
"label": "Controller",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "lastmod",
"fieldtype": "Data",
"label": "Lastmod",
"permlevel": 0
},
{
"fieldname": "no_cache",
"fieldtype": "Check",
"label": "No Cache",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "no_sitemap",
"fieldtype": "Check",
"label": "No Sitemap",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "no_sidebar",
"fieldtype": "Check",
"label": "No Sidebar",
"permlevel": 0
},
{
"fieldname": "page_name_field",
"fieldtype": "Data",
"label": "Page Name Field",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "condition_field",
"fieldtype": "Data",
"label": "Condition Field",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "sort_by",
"fieldtype": "Data",
"label": "Sort By",
"permlevel": 0
},
{
"fieldname": "sort_order",
"fieldtype": "Data",
"label": "Sort Order",
"permlevel": 0
}
],
"idx": 1,
"modified": "2014-02-24 12:47:44.000000",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Template",
"owner": "Administrator",
"permissions": [
{
"cancel": 0,
"create": 0,
"delete": 1,
"export": 0,
"permlevel": 0,
"read": 1,
"report": 1,
"role": "System Manager",
"write": 0
}
]
}

+ 0
- 142
frappe/website/doctype/website_template/website_template.py Ver arquivo

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

# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
import frappe.utils
import os
from frappe import _
from frappe.website.doctype.website_route.website_route import add_to_sitemap, update_sitemap, cleanup_sitemap
from frappe.utils.nestedset import rebuild_tree

from frappe.model.document import Document

class WebsiteTemplate(Document):
def after_insert(self):
if self.page_or_generator == "Page":
website_route = frappe.db.get_value("Website Route",
{"website_template": self.name, "page_or_generator": "Page"})
opts = self.as_dict()
opts.update({"public_read": 1})

if website_route:
update_sitemap(website_route, opts)
else:
add_to_sitemap(opts)
else:
condition = ""
if self.condition_field:
condition = " where ifnull(%s, 0)=1" % self.condition_field
for name in frappe.db.sql_list("""select name from `tab{doctype}`
{condition} order by idx asc, {sort_field} {sort_order}""".format(
doctype = self.ref_doctype,
condition = condition,
sort_field = getattr(self, "sort_field", "name"),
sort_order = getattr(self, "sort_order", "asc")
)):
doc = frappe.get_doc(self.ref_doctype, name)
# regenerate route
doc.run_method("on_update")
def rebuild_website_template():
# TODO
frappe.flags.in_rebuild_config = True
frappe.db.sql("""delete from `tabWebsite Template`""")
for app in frappe.get_installed_apps():
if app=="webnotes": app="frappe"
build_website_template(app)
cleanup_sitemap()
frappe.flags.in_rebuild_config = False
# enable nested set and rebuild
rebuild_tree("Website Route", "parent_website_route")
frappe.db.commit()


def build_website_template(app):
config = {"pages": {}, "generators":{}}
pages, generators = get_pages_and_generators(app)
for args in pages:
add_website_template(**args)

for args in generators:
add_website_template(**args)
frappe.db.commit()

def get_pages_and_generators(app):
pages = []
generators = []
app_path = frappe.get_app_path(app)

for config_type in ("pages", "generators"):
path = os.path.join(app_path, "templates", config_type)
if os.path.exists(path):
for fname in os.listdir(path):
fname = frappe.utils.cstr(fname)
if fname.split(".")[-1] in ("html", "xml", "js", "css"):
if config_type=="pages":
pages.append({"page_or_generator": "Page", "app": app, "path": path,
"fname":fname, "app_path":app_path})
else:
generators.append({"page_or_generator": "Generator", "app": app, "path": path,
"fname":fname, "app_path":app_path})
return pages, generators
def add_website_template(page_or_generator, app, path, fname, app_path):
name = fname[:-5] if fname.endswith(".html") else fname
wsc = frappe._dict({
"doctype": "Website Template",
"page_or_generator": page_or_generator,
"link_name": name,
"template_path": os.path.relpath(os.path.join(path, fname), app_path),
})
wsc.controller = get_template_controller(app, path, fname)
if wsc.controller:
# verbose print wsc.controller
module = frappe.get_module(wsc.controller)
wsc.no_cache = getattr(module, "no_cache", 0)
wsc.no_sitemap = wsc.no_cache or getattr(module, "no_sitemap", 0)
wsc.no_sidebar = wsc.no_sidebar or getattr(module, "no_sidebar", 0)
wsc.ref_doctype = getattr(module, "doctype", None)
wsc.page_name_field = getattr(module, "page_name_field", "page_name")
wsc.condition_field = getattr(module, "condition_field", None)
wsc.sort_by = getattr(module, "sort_by", "name")
wsc.sort_order = getattr(module, "sort_order", "asc")
wsc.base_template_path = getattr(module, "base_template_path", None)
wsc.page_title = getattr(module, "page_title", _(name.title()))
if frappe.db.exists("Website Template", wsc.link_name):
# found by earlier app, override
frappe.db.sql("""delete from `tabWebsite Template` where name=%s""", (wsc.link_name,))
frappe.get_doc(wsc).insert()
return name
def get_template_controller(app, path, fname):
controller = None
controller_name = fname.split(".")[0].replace("-", "_") + ".py"
controller_path = os.path.join(path, controller_name)
if os.path.exists(controller_path):
controller = app + "." + os.path.relpath(controller_path[:-3], frappe.get_app_path(app)).replace(os.path.sep, ".")
return controller

+ 14
- 0
frappe/website/js/website.js Ver arquivo

@@ -107,6 +107,10 @@ $.extend(frappe, {
} catch(e) { } catch(e) {
console.log(data.exc); console.log(data.exc);
} }
if (opts.error_msg && data._server_messages) {
var server_messages = (JSON.parse(data._server_messages || '[]')).join("<br>");
$(opts.error_msg).html(server_messages).toggle(true);
}
} else{ } else{
if(opts.btn) { if(opts.btn) {
$(opts.btn).addClass("btn-success"); $(opts.btn).addClass("btn-success");
@@ -286,6 +290,10 @@ $.extend(frappe, {
$('[data-html-block]').each(function(i, section) { $('[data-html-block]').each(function(i, section) {
var $section = $(section); var $section = $(section);
var stype = $section.attr("data-html-block"); var stype = $section.attr("data-html-block");

// handle meta separately
if (stype==="meta_block") return;

var block_data = data[stype] || ""; var block_data = data[stype] || "";


// NOTE: use frappe.ready instead of $.ready for reliable execution // NOTE: use frappe.ready instead of $.ready for reliable execution
@@ -307,6 +315,12 @@ $.extend(frappe, {
}); });
if(data.title) $("title").html(data.title); if(data.title) $("title").html(data.title);


// change meta tags
$('[data-html-block="meta_block"]').remove();
if(data.meta_block) {
$("head").append(data.meta_block);
}

// change id of current page // change id of current page
$(".page-container").attr("id", "page-" + data.path); $(".page-container").attr("id", "page-" + data.path);




+ 10
- 92
frappe/website/page/sitemap_browser/sitemap_browser.js Ver arquivo

@@ -1,24 +1,23 @@
// 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"


frappe.pages['sitemap-browser'].onload = function(wrapper) {
frappe.pages['sitemap-browser'].onload = function(wrapper) {
frappe.ui.make_app_page({ frappe.ui.make_app_page({
parent: wrapper, parent: wrapper,
title: 'Sitemap Browser', title: 'Sitemap Browser',
});
});
wrapper.appframe.add_module_icon("Website") wrapper.appframe.add_module_icon("Website")


wrapper.appframe.set_title_right('Refresh', function() {
wrapper.appframe.set_title_right('Refresh', function() {
frappe.website.sitemap.tree.rootnode.reload(); frappe.website.sitemap.tree.rootnode.reload();
}); });


$(wrapper) $(wrapper)
.find(".layout-side-section") .find(".layout-side-section")
.html('<div class="text-muted">'+
__('Click on a link to get options to expand get options ') +
__('Add') + ' / ' + __('Edit') + ' / '+ __('Remove') + '.</div>')
.html('<div class="text-muted">'+
__('Click on a link to get options') + '</div>')


frappe.website.sitemap = new frappe.website.SitemapBrowser(
frappe.website.sitemap = new frappe.website.SitemapBrowser(
$(wrapper) $(wrapper)
.find(".layout-main-section") .find(".layout-main-section")
.css({ .css({
@@ -34,31 +33,13 @@ frappe.website.SitemapBrowser = Class.extend({
$(parent).empty(); $(parent).empty();
var me = this; var me = this;
this.tree = new frappe.ui.Tree({ this.tree = new frappe.ui.Tree({
parent: $(parent),
parent: $(parent),
label: "Sitemap", label: "Sitemap",
method: 'frappe.website.page.sitemap_browser.sitemap_browser.get_children', method: 'frappe.website.page.sitemap_browser.sitemap_browser.get_children',
toolbar: [ toolbar: [
{ {
toggle_btn: true, toggle_btn: true,
}, },
{
label: __("Update Parent"),
click: function(node, btn) {
me.update_parent();
}
},
{
label: __("Up"),
click: function(node, btn) {
me.up_or_down("up");
}
},
{
label: __("Down"),
click: function(node, btn) {
me.up_or_down("down");
}
},
{ {
label: __("Open"), label: __("Open"),
click: function(node, btn) { click: function(node, btn) {
@@ -66,7 +47,7 @@ frappe.website.SitemapBrowser = Class.extend({
} }
} }
] ]
// drop: function(dragged_node, dropped_node, dragged_element, dropped_element) { // drop: function(dragged_node, dropped_node, dragged_element, dropped_element) {
// frappe.website.sitemap.update_parent(dragged_node.label, dropped_node.label, function(r) { // frappe.website.sitemap.update_parent(dragged_node.label, dropped_node.label, function(r) {
// if(!r.exc) { // if(!r.exc) {
@@ -78,7 +59,7 @@ frappe.website.SitemapBrowser = Class.extend({
}); });
this.tree.rootnode.$a this.tree.rootnode.$a
.data('node-data', {value: "Sitemap", expandable:1}) .data('node-data', {value: "Sitemap", expandable:1})
.click();
.click();
}, },
selected_node: function() { selected_node: function() {
return this.tree.$w.find('.tree-link.selected'); return this.tree.$w.find('.tree-link.selected');
@@ -87,67 +68,4 @@ frappe.website.SitemapBrowser = Class.extend({
var node = this.selected_node(); var node = this.selected_node();
frappe.set_route("Form", "Website Route", node.data("label")); frappe.set_route("Form", "Website Route", node.data("label"));
}, },
up_or_down: function(up_or_down) {
var node = this.tree.get_selected_node();
frappe.call({
method: "frappe.website.page.sitemap_browser.sitemap_browser.move",
args: {
"name": node.label,
"up_or_down": up_or_down
},
callback: function(r) {
if(r.message==="ok") {
node.parent.insertBefore(up_or_down==="up" ?
node.parent.prev() : node.parent.next().next());
//(node.parent_node || node).reload();
}
}
});
},
update_parent: function() {
var me = this;
if(!this.move_dialog) {
this.move_dialog = new frappe.ui.Dialog({
title: __("Move"),
fields: [
{
fieldtype: "Link",
fieldname: "new_parent",
label: "New Parent",
reqd: 1,
options: "Website Route"
},
{
fieldtype: "Button",
fieldname: "update",
label: "Update",
}
]
});
this.move_dialog.get_input("update").on("click", function() {
var node = me.tree.get_selected_node();
var values = me.move_dialog.get_values();
if(!values) return;
me._update_parent(node.label, values.new_parent, function(r) {
me.move_dialog.hide();
(node.parent_node || node).reload();
})
});
}
this.move_dialog.show();
this.move_dialog.get_input("new_parent").val("");
},
_update_parent: function(name, parent, callback) {
frappe.call({
method: "frappe.website.page.sitemap_browser.sitemap_browser.update_parent",
args: {
"name": name,
"new_parent": parent
},
callback: function(r) {
callback(r);
}
});
}
});
});

+ 3
- 1
frappe/website/render.py Ver arquivo

@@ -164,7 +164,9 @@ def clear_cache(path=None):


if path: if path:
delete_page_cache(path) delete_page_cache(path)

for p in frappe.db.sql_list('''select name from
`tabWebsite Route` where name like "{0}/%"'''.format(path.replace('"', '\"'))):
delete_page_cache(p)
else: else:
for p in frappe.db.sql_list("""select name from `tabWebsite Route`"""): for p in frappe.db.sql_list("""select name from `tabWebsite Route`"""):
if p is not None: if p is not None:


+ 16
- 11
frappe/website/sitemap.py Ver arquivo

@@ -24,21 +24,24 @@ def build_sitemap_options(path):
sitemap_options = frappe._dict(frappe.get_doc("Website Route", path).as_dict()) sitemap_options = frappe._dict(frappe.get_doc("Website Route", path).as_dict())
home_page = get_home_page() home_page = get_home_page()


sitemap_config = frappe.get_doc("Website Template",
sitemap_options.get("website_template")).as_dict()
if sitemap_options.controller:
module = frappe.get_module(sitemap_options.controller)


# get sitemap config fields too
for fieldname in ("base_template_path", "template_path", "controller",
"no_cache", "no_sitemap", "page_name_field", "condition_field"):
sitemap_options[fieldname] = sitemap_config.get(fieldname)
# get sitemap config fields too
for prop in ("base_template_path", "template", "no_cache", "no_sitemap",
"condition_field"):
if hasattr(module, prop):
sitemap_options[prop] = getattr(module, prop)


sitemap_options.doctype = sitemap_options.ref_doctype sitemap_options.doctype = sitemap_options.ref_doctype
sitemap_options.title = sitemap_options.page_title sitemap_options.title = sitemap_options.page_title
sitemap_options.pathname = sitemap_options.name sitemap_options.pathname = sitemap_options.name


# establish hierarchy # establish hierarchy
sitemap_options.parents = frappe.db.sql("""select name, page_title from `tabWebsite Route`
where lft < %s and rgt > %s order by lft asc""", (sitemap_options.lft, sitemap_options.rgt), as_dict=True)
sitemap_options.parents = frappe.db.sql("""select name, page_title from
`tabWebsite Route`
where lft < %s and rgt > %s
order by lft asc""", (sitemap_options.lft, sitemap_options.rgt), as_dict=True)


if not sitemap_options.no_sidebar: if not sitemap_options.no_sidebar:
sitemap_options.children = get_route_children(sitemap_options.pathname, home_page) sitemap_options.children = get_route_children(sitemap_options.pathname, home_page)
@@ -70,14 +73,16 @@ def get_route_children(pathname, home_page=None):


if children: if children:
# if children are from generator and sort order is specified, then get that condition # if children are from generator and sort order is specified, then get that condition
website_template = frappe.get_doc("Website Template", children[0].website_template)
if website_template.sort_by!="name":
module = frappe.get_module(children[0].controller)
if hasattr(module, "sort_by"):
children = frappe.db.sql("""select t1.* from children = frappe.db.sql("""select t1.* from
`tabWebsite Route` t1, `tab{ref_doctype}` t2 `tabWebsite Route` t1, `tab{ref_doctype}` t2
where ifnull(t1.parent_website_route,'')=%s where ifnull(t1.parent_website_route,'')=%s
and t1.public_read=1 and t1.public_read=1
and t1.docname = t2.name and t1.docname = t2.name
order by t2.{sort_by} {sort_order}""".format(**website_template.as_dict()),
order by {sort_by}""".format(
ref_doctype = children[0].ref_doctype,
sort_by = module.sort_by),
pathname, as_dict=True) pathname, as_dict=True)


children = [frappe.get_doc("Website Route", pathname)] + children children = [frappe.get_doc("Website Route", pathname)] + children


+ 89
- 50
frappe/website/statics.py Ver arquivo

@@ -2,15 +2,15 @@
# MIT License. See license.txt # MIT License. See license.txt


from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, os, time
import frappe, os, time, sys


from frappe import _
from frappe.utils import cint from frappe.utils import cint
from markdown2 import markdown from markdown2 import markdown
from frappe.website.sitemap import get_route_children, get_next
# from frappe.website.sitemap import get_route_children, get_next


def sync_statics(rebuild=False): def sync_statics(rebuild=False):
s = sync() s = sync()
s.verbose = True
while True: while True:
s.start(rebuild) s.start(rebuild)
frappe.db.commit() frappe.db.commit()
@@ -19,17 +19,19 @@ def sync_statics(rebuild=False):


class sync(object): class sync(object):
def start(self, rebuild=False): def start(self, rebuild=False):
self.verbose = False
self.synced = [] self.synced = []
self.synced_paths = []
self.to_insert = []
self.to_update = []
self.updated = 0 self.updated = 0
self.rebuild = rebuild self.rebuild = rebuild
for app in frappe.get_installed_apps(): for app in frappe.get_installed_apps():
self.sync_for_app(app) self.sync_for_app(app)


self.insert_and_update()
self.cleanup() self.cleanup()


if self.updated:
print str(self.updated) + " files updated"

def sync_for_app(self, app): def sync_for_app(self, app):
self.statics_path = frappe.get_app_path(app, "templates", "statics") self.statics_path = frappe.get_app_path(app, "templates", "statics")
if os.path.exists(self.statics_path): if os.path.exists(self.statics_path):
@@ -38,17 +40,15 @@ class sync(object):




def sync_folder(self, basepath, folders, files): def sync_folder(self, basepath, folders, files):
folder_route = os.path.relpath(basepath, self.statics_path)
self.get_index_txt(basepath, files) self.get_index_txt(basepath, files)
self.sync_index_page(basepath, files)
index_found = self.sync_index_page(basepath, files)


if not frappe.db.exists("Website Route", folder_route) and basepath!=self.statics_path:
if not index_found and basepath!=self.statics_path:
# not synced either by generator or by index.html # not synced either by generator or by index.html
return return


if self.index: if self.index:
self.sync_using_given_index(basepath, folders, files) self.sync_using_given_index(basepath, folders, files)

else: else:
self.sync_alphabetically(basepath, folders, [filename for filename in files if filename.endswith('html') or filename.endswith('md')]) self.sync_alphabetically(basepath, folders, [filename for filename in files if filename.endswith('html') or filename.endswith('md')])


@@ -64,7 +64,7 @@ class sync(object):
fname = "index." + extn fname = "index." + extn
if fname in files: if fname in files:
self.sync_file(fname, os.path.join(basepath, fname), None) self.sync_file(fname, os.path.join(basepath, fname), None)
return
return True


def sync_using_given_index(self, basepath, folders, files): def sync_using_given_index(self, basepath, folders, files):
for i, page_name in enumerate(self.index): for i, page_name in enumerate(self.index):
@@ -108,12 +108,42 @@ class sync(object):
["name", "idx", "static_file_timestamp", "docname"], as_dict=True) ["name", "idx", "static_file_timestamp", "docname"], as_dict=True)


if route_details: if route_details:
self.update_web_page(route_details, fpath, priority, parent_website_route)
page = self.get_route_details_for_update(route_details, fpath,
priority, parent_website_route)
if page:
self.to_update.append(page)
else: else:
# Route does not exist, new page # Route does not exist, new page
self.insert_web_page(route, fpath, page_name, priority, parent_website_route)
page = self.get_web_page_for_insert(route, fpath, page_name,
priority, parent_website_route)
self.to_insert.append(page)


def insert_web_page(self, route, fpath, page_name, priority, parent_website_route):
self.synced.append(route)

def insert_and_update(self):
if self.to_insert:
for i, page in enumerate(self.to_insert):
if self.verbose:
print "Inserting " + page.route
else:
sys.stdout.write("\rInserting statics {0}/{1}".format(i+1, len(self.to_insert)))
sys.stdout.flush()

self.insert_web_page(page)
if not self.verbose: print ""

if self.to_update:
for i, route_details in enumerate(self.to_update):
if self.verbose:
print "Updating " + route_details.name
else:
sys.stdout.write("\rUpdating statics {0}/{1}".format(i+1, len(self.to_update)))
sys.stdout.flush()

self.update_web_page(route_details)
if not self.verbose: print ""

def get_web_page_for_insert(self, route, fpath, page_name, priority, parent_website_route):
page = frappe.get_doc({ page = frappe.get_doc({
"doctype":"Web Page", "doctype":"Web Page",
"idx": priority, "idx": priority,
@@ -122,66 +152,75 @@ class sync(object):
"parent_website_route": parent_website_route "parent_website_route": parent_website_route
}) })


page.fpath = fpath
page.route = route
page.update(get_static_content(fpath, page_name, route)) page.update(get_static_content(fpath, page_name, route))
return page


def insert_web_page(self, page):
try: try:
page.insert() page.insert()
except frappe.NameError:
except frappe.NameError, e:
print e
# page exists, if deleted static, delete it and try again # page exists, if deleted static, delete it and try again
old_route = frappe.get_doc("Website Route", {"ref_doctype":"Web Page", old_route = frappe.get_doc("Website Route", {"ref_doctype":"Web Page",
"docname": page.name}) "docname": page.name})
if old_route.static_file_timestamp and not os.path.exists(os.path.join(self.statics_path,
old_route.name)):

if old_route.static_file_timestamp and \
not os.path.exists(os.path.join(self.statics_path, old_route.name)):
frappe.delete_doc("Web Page", page.name) frappe.delete_doc("Web Page", page.name)
page.insert() # retry page.insert() # retry



# update timestamp # update timestamp
route_doc = frappe.get_doc("Website Route", {"ref_doctype": "Web Page", route_doc = frappe.get_doc("Website Route", {"ref_doctype": "Web Page",
"docname": page.name}) "docname": page.name})
route_doc.static_file_timestamp = cint(os.path.getmtime(fpath))
route_doc.static_file_timestamp = cint(os.path.getmtime(page.fpath))
route_doc.save() route_doc.save()


self.updated += 1
print route_doc.name + " inserted"
self.synced.append(route)

def update_web_page(self, route_details, fpath, priority, parent_website_route):
def get_route_details_for_update(self, route_details, fpath, priority, parent_website_route):
out = None
if not route_details.docname: if not route_details.docname:
print "Ignoring {0} because page found".format(route_details.name) print "Ignoring {0} because page found".format(route_details.name)
return return

if str(cint(os.path.getmtime(fpath)))!= route_details.static_file_timestamp \ if str(cint(os.path.getmtime(fpath)))!= route_details.static_file_timestamp \
or (cint(route_details.idx) != cint(priority) and (priority is not None) \ or (cint(route_details.idx) != cint(priority) and (priority is not None) \
or self.rebuild): or self.rebuild):


page = frappe.get_doc("Web Page", route_details.docname)
page.update(get_static_content(fpath, route_details.docname, route_details.name))
page.idx = priority
page.save()
out = route_details
out.idx = priority
out.fpath = fpath


route_doc = frappe.get_doc("Website Route", route_details.name)
route_doc.static_file_timestamp = cint(os.path.getmtime(fpath))
route_doc.save()
return out


print route_doc.name + " updated"
self.updated += 1
def update_web_page(self, route_details):
page = frappe.get_doc("Web Page", route_details.docname)
page.update(get_static_content(route_details.fpath,
route_details.docname, route_details.name))
page.save()


self.synced.append(route_details.name)
route_doc = frappe.get_doc("Website Route", route_details.name)
route_doc.static_file_timestamp = cint(os.path.getmtime(route_details.fpath))
route_doc.save()


def cleanup(self): def cleanup(self):
if self.synced: if self.synced:
# delete static web pages that are not in immediate list
frappe.delete_doc("Web Page", frappe.db.sql_list("""select docname frappe.delete_doc("Web Page", frappe.db.sql_list("""select docname
from `tabWebsite Route` from `tabWebsite Route`
where ifnull(static_file_timestamp,'')!='' and name not in ({}) where ifnull(static_file_timestamp,'')!='' and name not in ({})
order by (rgt-lft) asc""".format(', '.join(["%s"]*len(self.synced))), order by (rgt-lft) asc""".format(', '.join(["%s"]*len(self.synced))),
tuple(self.synced))) tuple(self.synced)))
else: else:
# delete all static web pages
frappe.delete_doc("Web Page", frappe.db.sql_list("""select docname frappe.delete_doc("Web Page", frappe.db.sql_list("""select docname
from `tabWebsite Route` from `tabWebsite Route`
where ifnull(static_file_timestamp,'')!='' where ifnull(static_file_timestamp,'')!=''
order by (rgt-lft) asc""")) order by (rgt-lft) asc"""))


def delete_static_web_pages():
for name in frappe.db.sql_list("""select docname from `tabWebsite Route`
where ifnull(static_file_timestamp,'')!=''"""):
frappe.db.sql("delete from `tabWeb Page` where name=%s", name)


def get_static_content(fpath, docname, route): def get_static_content(fpath, docname, route):
d = frappe._dict({}) d = frappe._dict({})
@@ -197,21 +236,21 @@ def get_static_content(fpath, docname, route):
d.title = first_line[2:] d.title = first_line[2:]
content = "\n".join(lines[1:]) content = "\n".join(lines[1:])


if "{index}" in content:
children = get_route_children(route)
html = frappe.get_template("templates/includes/static_index.html").render({
"items":children})
content = content.replace("{index}", html)
if "{next}" in content:
next_item = get_next(route)
html = ""
if next_item:
html = '''<p>
<br><a href="{name}" class="btn btn-primary">
{page_title} <i class="icon-chevron-right"></i></a>
</p>'''.format(**next_item)
content = content.replace("{next}", html)
# if "{index}" in content:
# children = get_route_children(route)
# html = frappe.get_template("templates/includes/static_index.html").render({
# "items":children})
# content = content.replace("{index}", html)
#
# if "{next}" in content:
# next_item = get_next(route)
# html = ""
# if next_item:
# html = '''<p>
# <br><a href="{name}" class="btn btn-primary">
# {page_title} <i class="icon-chevron-right"></i></a>
# </p>'''.format(**next_item)
# content = content.replace("{next}", html)


content = markdown(content) content = markdown(content)




+ 116
- 0
frappe/website/sync.py Ver arquivo

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

from __future__ import unicode_literals
import frappe, os, sys
from frappe.modules import load_doctype_module
from frappe.utils.nestedset import rebuild_tree
import statics, render

def sync(app=None):
if app:
apps = [app]
else:
apps = frappe.get_installed_apps()

print "Resetting..."
render.clear_cache()

# delete all static web pages
statics.delete_static_web_pages()

# delete all routes (resetting)
frappe.db.sql("delete from `tabWebsite Route`")

print "Finding routes..."
routes, generators = [], []
for app in apps:
routes += get_sync_pages(app)
generators += get_sync_generators(app)

sync_pages(routes)
sync_generators(generators)

# sync statics
statics_sync = statics.sync()
statics_sync.start()

def sync_pages(routes):
l = len(routes)
if l:
for i, r in enumerate(routes):
r.insert(ignore_permissions=True)
sys.stdout.write("\rUpdating pages {0}/{1}".format(i+1, l))
sys.stdout.flush()
print ""

def sync_generators(generators):
l = len(generators)
if l:
frappe.flags.in_sync_website = True
for i, g in enumerate(generators):
doc = frappe.get_doc(g[0], g[1])
doc.ignore_links = True
doc.save(ignore_permissions=True)
sys.stdout.write("\rUpdating generators {0}/{1}".format(i+1, l))
sys.stdout.flush()

frappe.flags.in_sync_website = False
rebuild_tree("Website Route", "parent_website_route")

# HACK! update public_read, public_write
for name in frappe.db.sql_list("""select name from `tabWebsite Route` where ifnull(parent_website_route, '')!=''
order by lft"""):
route = frappe.get_doc("Website Route", name)
route.make_private_if_parent_is_private()
route.db_update()

print ""

def get_sync_pages(app):
app_path = frappe.get_app_path(app)
pages = []

path = os.path.join(app_path, "templates", "pages")
if os.path.exists(path):
for fname in os.listdir(path):
fname = frappe.utils.cstr(fname)
page_name, extn = fname.rsplit(".", 1)
if extn in ("html", "xml", "js", "css"):
route_page_name = page_name if extn=="html" else fname

# add website route
route = frappe.new_doc("Website Route")
route.page_or_generator = "Page"
route.template = os.path.relpath(os.path.join(path, fname), app_path)
route.page_name = route_page_name
route.public_read = 1
controller_path = os.path.join(path, page_name + ".py")

if os.path.exists(controller_path):
controller = app + "." + os.path.relpath(controller_path,
app_path).replace(os.path.sep, ".")[:-3]
route.controller = controller
try:
route.page_title = frappe.get_attr(controller + "." + "page_title")
except AttributeError:
pass

pages.append(route)

return pages

def get_sync_generators(app):
generators = []
for doctype in frappe.get_hooks("website_generators", app_name = app):
condition, order_by = "", "name asc"
module = load_doctype_module(doctype)
if hasattr(module, "condition_field"):
condition = " where ifnull({0}, 0)=1 ".format(module.condition_field)
if hasattr(module, "sort_by"):
order_by = "{0} {1}".format(module.sort_by, module.sort_order)
for name in frappe.db.sql_list("select name from `tab{0}` {1} order by {2}".format(doctype,
condition, order_by)):
generators.append((doctype, name))

return generators

+ 22
- 2
frappe/website/template.py Ver arquivo

@@ -10,6 +10,8 @@ from jinja2.utils import concat
from jinja2 import meta from jinja2 import meta
import re import re


from sitemap import get_next

def render_blocks(context): def render_blocks(context):
"""returns a dict of block name and its rendered content""" """returns a dict of block name and its rendered content"""


@@ -27,7 +29,7 @@ def render_blocks(context):
for block, render in template.blocks.items(): for block, render in template.blocks.items():
out[block] = scrub_relative_urls(concat(render(template.new_context(context)))) out[block] = scrub_relative_urls(concat(render(template.new_context(context))))


_render_blocks(context["template_path"])
_render_blocks(context["template"])


# default blocks if not found # default blocks if not found
if "title" not in out and out.get("header"): if "title" not in out and out.get("header"):
@@ -39,19 +41,37 @@ def render_blocks(context):
if "header" not in out and out.get("title"): if "header" not in out and out.get("title"):
out["header"] = out["title"] out["header"] = out["title"]


if not out["header"].startswith("<h"):
if out.get("header") and not out["header"].startswith("<h"):
out["header"] = "<h2>" + out["header"] + "</h2>" out["header"] = "<h2>" + out["header"] + "</h2>"


if "breadcrumbs" not in out: if "breadcrumbs" not in out:
out["breadcrumbs"] = scrub_relative_urls( out["breadcrumbs"] = scrub_relative_urls(
frappe.get_template("templates/includes/breadcrumbs.html").render(context)) frappe.get_template("templates/includes/breadcrumbs.html").render(context))


if "meta_block" not in out:
out["meta_block"] = frappe.get_template("templates/includes/meta_block.html").render(context)


if "<!-- no-sidebar -->" in out.get("content", ""): if "<!-- no-sidebar -->" in out.get("content", ""):
out["no_sidebar"] = 1 out["no_sidebar"] = 1


if "<!-- title:" in out.get("content", ""): if "<!-- title:" in out.get("content", ""):
out["title"] = re.findall('<!-- title:([^>]*) -->', out.get("content"))[0].strip() out["title"] = re.findall('<!-- title:([^>]*) -->', out.get("content"))[0].strip()


if "{index}" in out.get("content", "") and context.get("children"):
html = frappe.get_template("templates/includes/static_index.html").render({
"items": context["children"]})
out["content"] = out["content"].replace("{index}", html)

if "{next}" in out.get("content", ""):
next_item = get_next(context["pathname"])
if next_item:
if next_item.name[0]!="/": next_item.name = "/" + next_item.name
html = '''<p><br><a href="{name}" class="btn btn-primary">
{page_title} <i class="icon-chevron-right"></i></a>
</p>'''.format(**next_item)
out["content"] = out["content"].replace("{next}", html)

if "sidebar" not in out and not out.get("no_sidebar"): if "sidebar" not in out and not out.get("no_sidebar"):
out["sidebar"] = scrub_relative_urls( out["sidebar"] = scrub_relative_urls(
frappe.get_template("templates/includes/sidebar.html").render(context)) frappe.get_template("templates/includes/sidebar.html").render(context))


+ 6
- 0
frappe/website/utils.py Ver arquivo

@@ -20,6 +20,12 @@ def find_first_image(html):
def can_cache(no_cache=False): def can_cache(no_cache=False):
return not (frappe.conf.disable_website_cache or no_cache) return not (frappe.conf.disable_website_cache or no_cache)


def get_comment_list(doctype, name):
return frappe.db.sql("""select
comment, comment_by_fullname, creation
from `tabComment` where comment_doctype=%s
and comment_docname=%s order by creation""", (doctype, name), as_dict=1) or []

def get_home_page(): def get_home_page():
def _get_home_page(): def _get_home_page():
role_home_page = frappe.get_hooks("role_home_page") role_home_page = frappe.get_hooks("role_home_page")


+ 62
- 74
frappe/website/website_generator.py Ver arquivo

@@ -7,129 +7,117 @@ from frappe.model.document import Document
from frappe.model.naming import append_number_if_name_exists from frappe.model.naming import append_number_if_name_exists
from frappe.website.utils import cleanup_page_name from frappe.website.utils import cleanup_page_name
from frappe.utils import now from frappe.utils import now
from frappe.modules import get_module_name, load_doctype_module


from frappe.website.doctype.website_route.website_route import add_to_sitemap, update_sitemap, remove_sitemap
from frappe.website.doctype.website_route.website_route import remove_sitemap


class WebsiteGenerator(Document): class WebsiteGenerator(Document):
def autoname(self): def autoname(self):
self.setup_generator()
if not self.website_template: return

self.name = self.get_page_name() self.name = self.get_page_name()
append_number_if_name_exists(self) append_number_if_name_exists(self)


def onload(self): def onload(self):
self.get("__onload").website_route = frappe.db.get_value("Website Route", {"ref_doctype": self.doctype, "docname": self.name})

def set_page_name(self):
"""set page name based on parent page_name and title"""
page_name = cleanup_page_name(self.get_page_title())

if self.is_new():
self.set(self.website_template.page_name_field, page_name)
else:
frappe.db.set(self, self.website_template.page_name_field, page_name)

return page_name
self.get("__onload").website_route = self.get_route()


def get_parent_website_route(self): def get_parent_website_route(self):
return self.parent_website_route

def setup_generator(self):
if not hasattr(self, "website_template"):
website_template = frappe.db.get_values("Website Template",
{"ref_doctype": self.doctype}, "*")
if website_template:
self.website_template = website_template[0]
else:
self.website_template = None
return self.get("parent_website_route", "")


def on_update(self): def on_update(self):
self.update_sitemap() self.update_sitemap()
if getattr(self, "save_versions", False): if getattr(self, "save_versions", False):
frappe.add_version(self) frappe.add_version(self)


def after_rename(self, olddn, newdn, merge):
self.setup_generator()
if not self.website_template: return
def get_route(self):
parent = self.get_parent_website_route()
return ((parent + "/") if parent else "") + self.get_page_name()


frappe.db.sql("""update `tabWebsite Route`
set docname=%s where ref_doctype=%s and docname=%s""", (newdn, self.doctype, olddn))
def get_route_docname(self, name=None):
return frappe.db.get_value("Website Route",
{"ref_doctype":self.doctype, "docname": name or self.name})


if merge:
remove_sitemap(ref_doctype=self.doctype, docname=olddn)
def after_rename(self, olddn, newdn, merge):
if self.is_condition_field_enabled():
self.update_route(self.get_route_docname())


def on_trash(self): def on_trash(self):
self.setup_generator()
if not self.website_template: return

remove_sitemap(ref_doctype=self.doctype, docname=self.name) remove_sitemap(ref_doctype=self.doctype, docname=self.name)


def is_condition_field_enabled(self):
self.controller_module = load_doctype_module(self.doctype)
if hasattr(self.controller_module, "condition_field"):
return self.get(self.controller_module.condition_field) and True or False
else:
return True

def update_sitemap(self): def update_sitemap(self):
self.setup_generator()
if not self.website_template: return
# update route of all descendants
route_docname = self.get_route_docname()


if self.website_template.condition_field and \
not self.get(self.website_template.condition_field):
# condition field failed, remove and return!
remove_sitemap(ref_doctype=self.doctype, docname=self.name)
if not self.is_condition_field_enabled():
frappe.delete_doc("Website Route", route_docname, ignore_permissions=True)
return return


self.add_or_update_sitemap()
if route_docname:
self.update_route(route_docname)
else:
self.insert_route()


def add_or_update_sitemap(self):
page_name = self.get_page_name()
def update_route(self, route_docname):
route = frappe.get_doc("Website Route", route_docname)
if self.get_route() != route_docname:
route.rename(self.get_page_name(), self.get_parent_website_route())


existing_site_map = frappe.db.get_value("Website Route", {"ref_doctype": self.doctype,
"docname": self.name})
route.idx = self.idx
route.page_title = self.get_page_title()
self.update_permissions(route)
route.save()


def insert_route(self):
if self.modified: if self.modified:
# for sitemap.xml
lastmod = frappe.utils.get_datetime(self.modified).strftime("%Y-%m-%d") lastmod = frappe.utils.get_datetime(self.modified).strftime("%Y-%m-%d")
else: else:
lastmod = now() lastmod = now()


opts = frappe._dict({
route = frappe.new_doc("Website Route")
route.update({
"page_or_generator": "Generator", "page_or_generator": "Generator",
"ref_doctype":self.doctype, "ref_doctype":self.doctype,
"idx": self.idx, "idx": self.idx,
"docname": self.name, "docname": self.name,
"page_name": page_name,
"link_name": self.website_template.name,
"page_name": self.get_page_name(),
"controller": get_module_name(self.doctype, self.meta.module),
"template": self.controller_module.template,
"lastmod": lastmod, "lastmod": lastmod,
"parent_website_route": self.get_parent_website_route(), "parent_website_route": self.get_parent_website_route(),
"page_title": self.get_page_title(),
"public_read": 1 if not self.website_template.no_sidebar else 0
"page_title": self.get_page_title()
}) })


self.update_permissions(opts)

if existing_site_map:
idx = update_sitemap(existing_site_map, opts)
else:
idx = add_to_sitemap(opts)

if idx!=None and self.idx != idx:
frappe.db.set(self, "idx", idx)
self.update_permissions(route)
route.ignore_links = True
route.insert(ignore_permissions=True)


def update_permissions(self, opts):
def update_permissions(self, route):
if self.meta.get_field("public_read"): if self.meta.get_field("public_read"):
opts.public_read = self.public_read
opts.public_write = self.public_write
route.public_read = self.public_read
route.public_write = self.public_write
else: else:
opts.public_read = 1
route.public_read = 1


def get_page_name(self): def get_page_name(self):
page_name = self._get_page_name()
if not page_name:
page_name = self.set_page_name()
return self.get_or_make_page_name()


return self._get_page_name()
def get_page_name_field(self):
return self.page_name_field if hasattr(self, "page_name_field") else "page_name"


def _get_page_name(self):
if self.meta.get_field(self.website_template.page_name_field):
return self.get(self.website_template.page_name_field)
else:
return cleanup_page_name(self.get_page_title())
def get_or_make_page_name(self):
page_name = self.get(self.get_page_name_field())
if not page_name:
page_name = cleanup_page_name(self.get_page_title())
if self.is_new():
self.set(self.get_page_name_field(), page_name)

return page_name


def get_page_title(self): def get_page_title(self):
return self.get("title") or (self.name.replace("-", " ").replace("_", " ").title()) return self.get("title") or (self.name.replace("-", " ").replace("_", " ").title())

+ 14
- 2
frappe/widgets/form/meta.py Ver arquivo

@@ -6,7 +6,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, os import frappe, os
from frappe.model.meta import Meta from frappe.model.meta import Meta
from frappe.modules import scrub, get_module_path
from frappe.modules import scrub, get_module_path, load_doctype_module
from frappe.model.workflow import get_workflow_name from frappe.model.workflow import get_workflow_name


###### ######
@@ -35,10 +35,11 @@ class FormMeta(Meta):
self.add_code() self.add_code()
self.load_print_formats() self.load_print_formats()
self.load_workflows() self.load_workflows()
self.load_form_grid_templates()


def as_dict(self, no_nulls=False): def as_dict(self, no_nulls=False):
d = super(FormMeta, self).as_dict(no_nulls=no_nulls) d = super(FormMeta, self).as_dict(no_nulls=no_nulls)
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js",
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", "__form_grid_templates",
"__linked_with", "__messages", "__print_formats", "__workflow_docs"): "__linked_with", "__messages", "__print_formats", "__workflow_docs"):
d[k] = self.get(k) d[k] = self.get(k)


@@ -153,6 +154,17 @@ class FormMeta(Meta):
self.set("__workflow_docs", workflow_docs) self.set("__workflow_docs", workflow_docs)




def load_form_grid_templates(self):
module = load_doctype_module(self.name)
app = module.__name__.split(".")[0]
templates = {}
if hasattr(module, "form_grid_templates"):
for key, path in module.form_grid_templates.iteritems():
with open(frappe.get_app_path(app, path), "r") as f:
templates[key] = f.read()

self.set("__form_grid_templates", templates)

def render_jinja(content): def render_jinja(content):
if "{% include" in content: if "{% include" in content:
content = frappe.get_jenv().from_string(content).render() content = frappe.get_jenv().from_string(content).render()


+ 12
- 7
frappe/widgets/query_report.py Ver arquivo

@@ -5,7 +5,6 @@ from __future__ import unicode_literals


import frappe import frappe
import os, json import os, json
import types


from frappe import _ from frappe import _
from frappe.modules import scrub, get_module_path from frappe.modules import scrub, get_module_path
@@ -31,11 +30,16 @@ def get_script(report_name):
module_path = get_module_path(module) module_path = get_module_path(module)
report_folder = os.path.join(module_path, "report", scrub(report.name)) report_folder = os.path.join(module_path, "report", scrub(report.name))
script_path = os.path.join(report_folder, scrub(report.name) + ".js") script_path = os.path.join(report_folder, scrub(report.name) + ".js")
print_path = os.path.join(report_folder, scrub(report.name) + ".html")


script = None
script, html_format = None, None
if os.path.exists(script_path): if os.path.exists(script_path):
with open(script_path, "r") as script:
script = script.read()
with open(script_path, "r") as f:
script = f.read()

if os.path.exists(print_path):
with open(print_path, "r") as f:
html_format = f.read()


if not script and report.javascript: if not script and report.javascript:
script = report.javascript script = report.javascript
@@ -47,7 +51,10 @@ def get_script(report_name):
if frappe.lang != "en": if frappe.lang != "en":
frappe.response["__messages"] = frappe.get_lang_dict("report", report_name) frappe.response["__messages"] = frappe.get_lang_dict("report", report_name)


return script
return {
"script": script,
"html_format": html_format
}


@frappe.whitelist() @frappe.whitelist()
def run(report_name, filters=()): def run(report_name, filters=()):
@@ -148,8 +155,6 @@ def get_linked_doctypes(columns):


def get_user_match_filters(doctypes, ref_doctype): def get_user_match_filters(doctypes, ref_doctype):
match_filters = {} match_filters = {}
doctypes_meta = {}
tables = []


for dt in doctypes: for dt in doctypes:
match_filters.update(frappe.widgets.reportview.build_match_conditions(dt, False)) match_filters.update(frappe.widgets.reportview.build_match_conditions(dt, False))


+ 2
- 0
frappe/widgets/reportview.py Ver arquivo

@@ -150,6 +150,8 @@ def scrub_user_tags(tagcount):
rdict = {} rdict = {}
tagdict = dict(tagcount) tagdict = dict(tagcount)
for t in tagdict: for t in tagdict:
if not t:
continue
alltags = t.split(',') alltags = t.split(',')
for tag in alltags: for tag in alltags:
if tag: if tag:


+ 0
- 2
requirements.txt Ver arquivo

@@ -16,8 +16,6 @@ slugify
termcolor termcolor
werkzeug werkzeug
semantic_version semantic_version
lxml
inlinestyler
rauth>=0.6.2 rauth>=0.6.2
requests==1.2.3 requests==1.2.3
celery celery


Carregando…
Cancelar
Salvar