@@ -377,7 +377,7 @@ def get_module(modulename): | |||
return importlib.import_module(modulename) | |||
def scrub(txt): | |||
return txt.replace(' ','_').replace('-', '_').replace('/', '_').lower() | |||
return txt.replace(' ','_').replace('-', '_').lower() | |||
def unscrub(txt): | |||
return txt.replace('_',' ').replace('-', ' ').title() | |||
@@ -1,5 +1,5 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
from frappe.utils.minify import JavascriptMinify | |||
@@ -16,7 +16,7 @@ def bundle(no_compress, make_copy=False): | |||
# build js files | |||
make_asset_dirs(make_copy=make_copy) | |||
build(no_compress) | |||
def watch(no_compress): | |||
"""watch and rebuild if necessary""" | |||
import time | |||
@@ -25,18 +25,18 @@ def watch(no_compress): | |||
while True: | |||
if files_dirty(): | |||
build(no_compress=True) | |||
time.sleep(3) | |||
def make_asset_dirs(make_copy=False): | |||
assets_path = os.path.join(frappe.local.sites_path, "assets") | |||
for dir_path in [ | |||
os.path.join(assets_path, 'js'), | |||
os.path.join(assets_path, 'js'), | |||
os.path.join(assets_path, 'css')]: | |||
if not os.path.exists(dir_path): | |||
os.makedirs(dir_path) | |||
# symlink app/public > assets/app | |||
for app_name in frappe.get_all_apps(True): | |||
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") | |||
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) | |||
# reset_app_html() | |||
@@ -79,39 +79,46 @@ def get_build_maps(): | |||
else: | |||
s = os.path.join(app_path, source) | |||
source_paths.append(s) | |||
build_maps[target] = source_paths | |||
except Exception, e: | |||
print path | |||
raise | |||
return build_maps | |||
timestamps = {} | |||
def pack(target, sources, no_compress): | |||
from cStringIO import StringIO | |||
outtype, outtxt = target.split(".")[-1], '' | |||
jsm = JavascriptMinify() | |||
for f in sources: | |||
suffix = None | |||
if ':' in f: f, suffix = f.split(':') | |||
if not os.path.exists(f) or os.path.isdir(f): continue | |||
timestamps[f] = os.path.getmtime(f) | |||
try: | |||
with open(f, 'r') as sourcefile: | |||
with open(f, 'r') as sourcefile: | |||
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() | |||
jsm.minify(tmpin, tmpout) | |||
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: | |||
outtxt += ('\n/*\n *\t%s\n */' % f) | |||
outtxt += '\n' + data + '\n' | |||
except Exception, e: | |||
print "--Error in:" + f + "--" | |||
print frappe.get_traceback() | |||
@@ -119,10 +126,10 @@ def pack(target, sources, no_compress): | |||
if not no_compress and outtype == 'css': | |||
pass | |||
#outtxt = cssmin(outtxt) | |||
with open(target, 'w') as f: | |||
f.write(outtxt.encode("utf-8")) | |||
print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024))) | |||
def files_dirty(): | |||
@@ -135,4 +142,4 @@ def files_dirty(): | |||
return True | |||
else: | |||
return False | |||
@@ -230,7 +230,7 @@ def setup_utilities(parser): | |||
# clear | |||
parser.add_argument("--clear_web", default=False, action="store_true", | |||
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") | |||
parser.add_argument("--sync_statics", default=False, action="store_true", | |||
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) | |||
for each in apps: | |||
if each in all_apps: | |||
add_to_installed_apps(each, rebuild_sitemap=False) | |||
add_to_installed_apps(each, rebuild_website=False) | |||
frappe.destroy() | |||
@cmd | |||
@@ -402,13 +402,11 @@ def update(remote=None, branch=None, reload_gunicorn=False): | |||
subprocess.check_output("killall -HUP gunicorn".split()) | |||
@cmd | |||
def latest(rebuild_website_config=True, quiet=False): | |||
def latest(rebuild_website=True, quiet=False): | |||
import frappe.modules.patch_handler | |||
import frappe.model.sync | |||
from frappe.website import rebuild_config | |||
from frappe.utils.fixtures import sync_fixtures | |||
import frappe.translate | |||
from frappe.website import statics | |||
verbose = not quiet | |||
@@ -419,17 +417,11 @@ def latest(rebuild_website_config=True, quiet=False): | |||
frappe.modules.patch_handler.run_all() | |||
# sync | |||
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() | |||
sync_fixtures() | |||
if rebuild_website: | |||
build_website() | |||
finally: | |||
frappe.destroy() | |||
@@ -561,10 +553,11 @@ def clear_all_sessions(): | |||
frappe.destroy() | |||
@cmd | |||
def build_sitemap(): | |||
from frappe.website import rebuild_config | |||
def build_website(): | |||
import frappe.website.sync | |||
frappe.connect() | |||
rebuild_config() | |||
frappe.website.sync.sync() | |||
frappe.db.commit() | |||
frappe.destroy() | |||
@cmd | |||
@@ -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): | |||
footer = None | |||
if sent_via: | |||
if hasattr(sent_via, "get_sender"): | |||
d.sender = sent_via.get_sender(d) or d.sender | |||
@@ -13,6 +13,10 @@ from frappe.model.document import Document | |||
from frappe.model.db_schema import type_map | |||
from frappe.core.doctype.property_setter.property_setter import make_property_setter | |||
form_grid_templates = { | |||
"fields": "templates/form_grid/fields.html" | |||
} | |||
class DocType(Document): | |||
def validate(self): | |||
if not frappe.conf.get("developer_mode"): | |||
@@ -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": [ | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "cb0", | |||
"fieldtype": "Column Break", | |||
"fieldname": "cb0", | |||
"fieldtype": "Column Break", | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "mail_password", | |||
"fieldtype": "Password", | |||
"label": "Mail Password", | |||
"fieldname": "mail_password", | |||
"fieldtype": "Password", | |||
"label": "Mail Password", | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"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 | |||
}, | |||
{ | |||
"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": [ | |||
{ | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "System Manager", | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "System Manager", | |||
"write": 1 | |||
} | |||
] | |||
} | |||
} |
@@ -23,3 +23,6 @@ class OutgoingEmailSettings(Document): | |||
# exceptions are handled in session connect | |||
sess = smtpserver.sess | |||
def get_mail_footer(): | |||
return frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "footer") or "" |
@@ -20,7 +20,7 @@ | |||
@media (max-width: 768px) { | |||
.case-wrapper { | |||
margin: 12px; | |||
margin: 9px; | |||
width: 70px; | |||
height: 80px; | |||
} | |||
@@ -35,6 +35,7 @@ frappe.PermissionEngine = Class.extend({ | |||
me.setup_appframe(); | |||
} | |||
}); | |||
}, | |||
setup_appframe: function() { | |||
var me = this; | |||
@@ -192,8 +193,8 @@ frappe.PermissionEngine = Class.extend({ | |||
} | |||
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) | |||
.css("text-transform", "capitalize"); | |||
@@ -214,9 +215,12 @@ frappe.PermissionEngine = Class.extend({ | |||
me.set_show_users(role_cell, d.role); | |||
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") | |||
.removeClass("col-md-4") | |||
.css({"margin-top": "15px"}); | |||
d.help = ""; | |||
} | |||
var cell = add_cell(row, d, "permlevel"); | |||
@@ -290,6 +294,12 @@ frappe.PermissionEngine = Class.extend({ | |||
}, | |||
add_check_events: function() { | |||
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() { | |||
var chk = $(this); | |||
var args = { | |||
@@ -2,7 +2,7 @@ frappe.pages['user-permissions'].onload = function(wrapper) { | |||
frappe.ui.make_app_page({ | |||
parent: wrapper, | |||
title: "User Permissions Manager", | |||
icon: "icon-user", | |||
icon: "icon-shield", | |||
single_column: true | |||
}); | |||
$(wrapper).find(".layout-main").html("<div class='user-settings' style='min-height: 200px;'></div>\ | |||
@@ -45,6 +45,10 @@ frappe.UserPermissions = Class.extend({ | |||
}, | |||
make: function() { | |||
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({ | |||
module:"frappe.core", | |||
page:"user_permissions", | |||
@@ -44,3 +44,4 @@ class DocstatusTransitionError(ValidationError): pass | |||
class TimestampMismatchError(ValidationError): pass | |||
class EmptyTableError(ValidationError): pass | |||
class LinkExistsError(ValidationError): pass | |||
class InvalidEmailAddressError(ValidationError): pass |
@@ -26,7 +26,7 @@ web_include_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"] | |||
@@ -34,34 +34,33 @@ notification_config = "frappe.core.notifications.get_notification_config" | |||
before_tests = "frappe.utils.install.before_tests" | |||
website_generators = ["Web Page", "Blog Post", "Website Group", "Blog Category"] | |||
# permissions | |||
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 = { | |||
"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 = { | |||
"*": { | |||
"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 = { | |||
"all": ["frappe.utils.email_lib.bulk.flush"], | |||
@@ -72,6 +71,8 @@ scheduler_events = { | |||
"frappe.sessions.clear_expired_sessions", | |||
], | |||
"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" |
@@ -10,8 +10,8 @@ import os, json | |||
import frappe | |||
import frappe.database | |||
import getpass | |||
from frappe import _ | |||
from frappe.model.db_schema import DbManager | |||
import frappe.website.sync | |||
from frappe.model.sync import sync_for | |||
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 []: | |||
frappe.get_attr(after_install)() | |||
sync_fixtures() | |||
print "Installing Fixtures..." | |||
sync_fixtures(name) | |||
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() | |||
if not app_name in installed_apps: | |||
installed_apps.append(app_name) | |||
frappe.db.set_global("installed_apps", json.dumps(installed_apps)) | |||
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() | |||
@@ -292,7 +292,7 @@ class BaseDocument(object): | |||
return | |||
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 | |||
options = (df.options or "").split("\n") | |||
@@ -22,7 +22,7 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, | |||
elif isinstance(target_doc, basestring): | |||
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") | |||
map_doc(source_doc, target_doc, table_maps[source_doc.doctype]) | |||
@@ -9,7 +9,6 @@ from __future__ import unicode_literals | |||
import frappe | |||
import os, sys | |||
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 | |||
def sync_all(force=0, verbose=False): | |||
@@ -23,40 +22,41 @@ def sync_all(force=0, verbose=False): | |||
frappe.clear_cache() | |||
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 []: | |||
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 |
@@ -44,13 +44,26 @@ def export_doc(doctype, name, module=None): | |||
def get_doctype_module(doctype): | |||
return frappe.db.get_value('DocType', doctype, 'module') or "core" | |||
doctype_python_modules = {} | |||
def load_doctype_module(doctype, module=None, prefix=""): | |||
if not module: | |||
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(\ | |||
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)] |
@@ -41,4 +41,7 @@ frappe.patches.v4_0.fix_attach_field_file_url | |||
execute:frappe.reset_perms("User") #2014-06-13 | |||
execute:frappe.db.sql("""delete from `tabUserRole` where ifnull(parentfield, '')=''""") #2014-06-17 | |||
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 | |||
@@ -17,11 +17,11 @@ def execute(): | |||
frappe.reload_doc("website", "doctype", frappe.scrub(d)) | |||
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_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") | |||
for d in ("blog_category", "blog_post", "web_page", "website_route", "website_group", "post", "user_vote"): | |||
@@ -1,23 +1,24 @@ | |||
import frappe | |||
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() |
@@ -6,9 +6,9 @@ from __future__ import unicode_literals | |||
import frappe | |||
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") | |||
@@ -32,7 +32,6 @@ | |||
"public/css/tag-it.css", | |||
"public/css/bootstrap.css", | |||
"public/css/bootstrap-responsive.css", | |||
"public/css/font-awesome.css", | |||
"public/css/desk.css", | |||
"public/css/appframe.css", | |||
@@ -67,6 +66,9 @@ | |||
"public/js/frappe/router.js", | |||
"public/js/frappe/desk.js", | |||
"public/js/frappe/defaults.js", | |||
"public/js/lib/microtemplate.js", | |||
"public/html/print_template.html", | |||
"public/js/legacy/globals.js", | |||
"public/js/legacy/datatype.js", | |||
@@ -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> |
@@ -6,6 +6,11 @@ frappe.ui.form.Grid = Class.extend({ | |||
$.extend(this, opts); | |||
this.fieldinfo = {}; | |||
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; | |||
}, | |||
make: function() { | |||
@@ -61,7 +66,7 @@ frappe.ui.form.Grid = Class.extend({ | |||
if(!force && this.data_rows_are_same(data)) { | |||
// soft refresh | |||
this.header_row.refresh(); | |||
this.header_row && this.header_row.refresh(); | |||
for(var i in this.grid_rows) { | |||
this.grid_rows[i].refresh(); | |||
} | |||
@@ -261,38 +266,27 @@ frappe.ui.form.GridRow = Class.extend({ | |||
}, | |||
make_static_display: function() { | |||
var me = this; | |||
this.make_static_display_template(); | |||
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); | |||
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.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;">\ | |||
<span class="text-success grid-insert-row" style="padding: 4px;">\ | |||
<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() { | |||
if(this.static_display_template) return; | |||
@@ -443,55 +463,6 @@ frappe.ui.form.GridRow = Class.extend({ | |||
$.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.grid.open_grid_row = this; | |||
@@ -41,7 +41,6 @@ frappe.ui.set_user_background = function(src, selector, style) { | |||
frappe.dom.set_style(repl('%(selector)s { \ | |||
background: url("%(src)s") center center;\ | |||
background-attachment: fixed; \ | |||
background-size: 100%; \ | |||
%(style)s \ | |||
}', {src:src, selector:selector, style: style==="Fill Screen" ? "background-size: cover;" : ""})); | |||
} | |||
@@ -247,5 +247,5 @@ frappe.utils = { | |||
var dataURL = canvas.toDataURL("image/jpeg"); | |||
setTimeout(function() { callback(dataURL); }, 10 ); | |||
} | |||
} | |||
}, | |||
}; |
@@ -1,8 +1,8 @@ | |||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
// MIT License. See license.txt | |||
// MIT License. See license.txt | |||
// provide a namespace | |||
if(!window.frappe) | |||
if(!window.frappe) | |||
window.frappe = {}; | |||
frappe.provide = function(namespace) { | |||
// docs: create a namespace // | |||
@@ -22,4 +22,5 @@ frappe.provide("locals"); | |||
frappe.provide("frappe.settings"); | |||
frappe.provide("frappe.utils"); | |||
frappe.provide("frappe.ui"); | |||
frappe.provide("frappe.modules"); | |||
frappe.provide("frappe.modules"); | |||
frappe.provide("frappe.templates"); |
@@ -71,6 +71,7 @@ frappe.request.call = function(opts) { | |||
500: function() { | |||
msgprint(__("Server Error: Please check your server logs or contact tech support.")) | |||
opts.error && opts.error(); | |||
} | |||
}, | |||
async: opts.async | |||
@@ -144,7 +145,6 @@ frappe.request.prepare = function(opts) { | |||
} | |||
frappe.request.cleanup = function(opts, r) { | |||
// stop button indicator | |||
if(opts.btn) $(opts.btn).done_working(); | |||
@@ -259,12 +259,19 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
this.appframe.add_icon_btn("2", "icon-shield", | |||
__("User Permissions Manager"), function() { | |||
frappe.route_options = { | |||
property: me.doctype | |||
doctype: me.doctype | |||
}; | |||
frappe.set_route("user-permissions"); | |||
}); | |||
} | |||
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() { | |||
frappe.set_route("Form", "Customize Form", { | |||
doctype: me.doctype | |||
@@ -107,8 +107,9 @@ frappe.views.QueryReport = Class.extend({ | |||
}, | |||
callback: function(r) { | |||
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_html_format(r.message.html_format); | |||
me.refresh(); | |||
} | |||
}); | |||
@@ -124,6 +125,37 @@ frappe.views.QueryReport = Class.extend({ | |||
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() { | |||
this.clear_filters(); | |||
var me = this; | |||
@@ -6,6 +6,8 @@ frappe.utils.full_name = function(fn, ln) { | |||
} | |||
function fmt_money(v, format){ | |||
// deprecated! | |||
// for backward compatibility | |||
return format_number(v, format); | |||
} | |||
@@ -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); | |||
}; |
@@ -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"> | |||
{%- block head_include %}{% endblock -%} | |||
{%- 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 %} | |||
<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="row"> | |||
<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"> | |||
{%- if header is defined -%}{{ header }}{%- endif -%} | |||
</div> | |||
<div class="page-breadcrumbs" data-html-block="breadcrumbs"> | |||
{%- if breadcrumbs is defined -%}{{ breadcrumbs }}{%- endif -%} | |||
</div> | |||
<!-- <a class="visible-xs toggle-sidebar no-decoration pull-right"> | |||
<i class="icon-chevron-down"></i> | |||
</a> --> | |||
</div> | |||
<div class="col-sm-3 text-right"> | |||
<div class="page-header-right"></div> | |||
@@ -2,4 +2,4 @@ | |||
<p>You have a new message from: <b>{{ from }}</b></p> | |||
<p>{{ message }}</p> | |||
<hr> | |||
<p><a href="{{ link }}">Login and view in Browser</a></p> | |||
<p><a href="{{ link }}">Login and view in Browser</a></p> |
@@ -2,9 +2,9 @@ | |||
<p>Dear {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},</p> | |||
<p>A new account has been created for you.</p> | |||
<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> | |||
<p>You can also copy-paste this link in your browser <a href="{{ link }}">{{ link }}</a></p> | |||
<p>Thank you,<br> | |||
{{ user_fullname }}</p> | |||
{{ user_fullname }}</p> |
@@ -4,273 +4,16 @@ | |||
<meta name="viewport" content="width=device-width" /> | |||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | |||
<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> | |||
<body> | |||
<div class="wrapper"> | |||
<!-- 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 --> | |||
<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 --> | |||
</div> | |||
<div class="print-html">{{ print_html or "" }}</div> | |||
</body> | |||
</html> |
@@ -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> | |||
{% } %} |
@@ -1,2 +0,0 @@ | |||
doctype = "Blog Category" | |||
no_cache = 1 |
@@ -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 |
@@ -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 |
@@ -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 | |||
} | |||
} |
@@ -8,9 +8,8 @@ var blog = { | |||
get_list: function() { | |||
$.ajax({ | |||
method: "GET", | |||
url: "/", | |||
url: "/api/method/frappe.website.doctype.blog_post.blog_post.get_blog_list", | |||
data: { | |||
cmd: "frappe.templates.generators.blog_post.get_blog_list", | |||
start: blog.start, | |||
by: get_url_arg("by"), | |||
category: window.category || get_url_arg("category") | |||
@@ -18,7 +18,7 @@ | |||
<fieldset> | |||
<input class="form-control" name="comment_by_fullname" placeholder="Your Name" type="text"/><br> | |||
<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 | |||
placeholder="Comment"/> | |||
</textarea><br> | |||
@@ -62,6 +62,11 @@ $(document).ready(function() { | |||
return false; | |||
} | |||
if (!valid_email(args.comment_by)) { | |||
frappe.msgprint("Please enter a valid email address."); | |||
return false; | |||
} | |||
frappe.call({ | |||
btn: this, | |||
type: "POST", | |||
@@ -43,7 +43,7 @@ def add_comment(args=None): | |||
ifnull(unsubscribed, 0)=0""", (comment.comment_doctype, comment.comment_docname))] | |||
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 | |||
@@ -124,5 +124,6 @@ frappe.ready(function() { | |||
window.location.hash = "#login"; | |||
login.bind_events(); | |||
login.login(); | |||
$(".form-signup, .form-forgot").removeClass("hide"); | |||
$(document).trigger('login_rendered'); | |||
}); |
@@ -0,0 +1,5 @@ | |||
{%- if metatags -%} | |||
{%- for name in metatags %} | |||
<meta name="{{ name }}" content="{{ metatags[name]|striptags }}" data-html-block="meta_block"> | |||
{%- endfor -%} | |||
{%- endif -%} |
@@ -39,7 +39,7 @@ | |||
</a> | |||
<ul class="dropdown-menu"> | |||
{%- 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.url -%} | |||
@@ -47,7 +47,6 @@ | |||
{%- if child.icon -%} | |||
<i class="icon-fixed-width {{ child.icon }}"></i> | |||
{%- endif -%} | |||
{{ child.label }} | |||
</a> | |||
{%- endif -%} | |||
@@ -2,10 +2,10 @@ | |||
{% if children -%} | |||
{%- for child in children -%} | |||
<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 | |||
{% 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 %}"> | |||
{{ child.page_title }} | |||
{% if not child.public_read %} | |||
@@ -7,7 +7,7 @@ | |||
<div class="row"> | |||
<div class="col-xs-1 text-right" style="padding-right: 0px;"><b>{{ loop.index }}.</b></div> | |||
<div class="col-xs-11"> | |||
<a href="{{ item.name }}">{{ item.page_title }}</a> | |||
<a href="/{{ item.name }}">{{ item.page_title }}</a> | |||
</div> | |||
</div> | |||
</li> | |||
@@ -1,4 +1,4 @@ | |||
# 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 |
@@ -4,5 +4,6 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
page_title = "Blog" | |||
def get_context(context): | |||
return frappe.get_doc("Blog Settings", "Blog Settings").as_dict() |
@@ -43,7 +43,7 @@ | |||
</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> | |||
<input type="text" id="signup_fullname" | |||
class="form-control" placeholder="{{ _('Full Name') }}" required autofocus> | |||
@@ -56,7 +56,7 @@ | |||
</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> | |||
<input type="email" id="forgot_email" | |||
class="form-control" placeholder="{{ _('Email Id') }}" required autofocus> | |||
@@ -1,5 +1,5 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
@@ -15,16 +15,14 @@ def get_context(context): | |||
"""generate the sitemap XML""" | |||
host = get_request_site_address() | |||
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): | |||
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} | |||
@@ -8,58 +8,58 @@ from frappe.website.permissions import get_access | |||
@frappe.whitelist(allow_guest=True) | |||
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 | |||
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}) | |||
access = get_access(pathname) | |||
if not access.get("read"): | |||
return frappe.PermissionError | |||
conditions = "" | |||
values = [group] | |||
group_type = frappe.db.get_value("Website Group", group, "group_type") | |||
if group_type == "Events": | |||
# should show based on time upto precision of hour | |||
# because the current hour should also be in upcoming | |||
values.append(now_datetime().replace(minute=0, second=0, microsecond=0)) | |||
if view in ("feed", "closed"): | |||
order_by = "p.creation desc" | |||
if view == "closed": | |||
conditions += " and p.is_task=1 and p.status='Closed'" | |||
elif view in ("popular", "open"): | |||
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) | |||
if view == "open": | |||
conditions += " and p.is_task=1 and p.status='Open'" | |||
elif view == "upcoming": | |||
conditions += " and p.is_event=1 and p.event_datetime >= %s" | |||
order_by = "p.event_datetime asc" | |||
elif view == "past": | |||
conditions += " and p.is_event=1 and p.event_datetime < %s" | |||
order_by = "p.event_datetime desc" | |||
values += [int(limit_start), int(limit_length)] | |||
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 | |||
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), | |||
tuple(values), as_dict=True, debug=True) | |||
context = { "posts": posts, "limit_start": limit_start, "view": get_views(group_type)[view] } | |||
return frappe.get_template("templates/includes/post_list.html").render(context) | |||
@@ -7,7 +7,7 @@ from frappe import _ | |||
from frappe.utils import get_fullname | |||
from frappe.website.permissions import get_access | |||
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): | |||
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 | |||
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 | |||
frappe.db.set_value("Post", post.name, "picture_url", post.picture_url) | |||
@@ -4,7 +4,7 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
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 | |||
@frappe.whitelist() | |||
@@ -12,17 +12,17 @@ def suggest_user(term, group): | |||
pathname = get_pathname(group) | |||
if not get_access(pathname).get("admin"): | |||
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 | |||
from `tabUser` pr | |||
from `tabUser` pr | |||
where (pr.first_name like %(term)s or pr.last_name like %(term)s) | |||
and pr.user_type = "Website User" | |||
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) | |||
template = frappe.get_template("templates/includes/user_display.html") | |||
return [{ | |||
"value": "{} {}".format(pr.first_name or "", pr.last_name or ""), | |||
@@ -35,7 +35,7 @@ def add_sitemap_permission(group, user): | |||
pathname = get_pathname(group) | |||
if not get_access(pathname).get("admin"): | |||
raise frappe.PermissionError | |||
permission = frappe.get_doc({ | |||
"doctype": "Website Route Permission", | |||
"website_route": pathname, | |||
@@ -43,11 +43,11 @@ def add_sitemap_permission(group, user): | |||
"read": 1 | |||
}) | |||
permission.insert(ignore_permissions=True) | |||
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)) | |||
return frappe.get_template("templates/includes/sitemap_permission.html").render({ | |||
"user": user | |||
}) | |||
@@ -57,18 +57,18 @@ def update_permission(group, user, perm, value): | |||
pathname = get_pathname(group) | |||
if not get_access(pathname).get("admin"): | |||
raise frappe.PermissionError | |||
permission = frappe.get_doc("Website Route Permission", {"website_route": pathname, "user": user}) | |||
permission.set(perm, int(value)) | |||
permission.save(ignore_permissions=True) | |||
# send email | |||
if perm=="admin" and int(value): | |||
group_title = frappe.db.get_value("Website Route", pathname, "page_title") | |||
subject = "You have been made Administrator of Group " + group_title | |||
send(recipients=[user], | |||
send(recipients=[user], | |||
subject= subject, add_unsubscribe_link=False, | |||
message="""<h3>Group Notification<h3>\ | |||
<p>%s</p>\ | |||
@@ -82,15 +82,15 @@ def update_description(group, description): | |||
group = frappe.get_doc("Website Group", group) | |||
group.group_description = description | |||
group.save(ignore_permissions=True) | |||
@frappe.whitelist() | |||
def add_website_group(group, new_group, public_read, public_write, group_type="Forum"): | |||
if not get_access(get_pathname(group)).get("admin"): | |||
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}) | |||
frappe.get_doc({ | |||
"doctype": "Website 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, | |||
"public_read": int(public_read), | |||
"public_write": int(public_write) | |||
}).insert(ignore_permissions=True) | |||
}).insert(ignore_permissions=True) |
@@ -119,6 +119,12 @@ app_version = "0.0.1" | |||
# "Role": "home_page" | |||
# }} | |||
# Generators | |||
# ---------- | |||
# automatically create page for each record of this doctype | |||
# website_generators = ["Web Page"] | |||
# Installation | |||
# ------------ | |||
@@ -16,6 +16,7 @@ class BulkLimitCrossedError(frappe.ValidationError): pass | |||
def send(recipients=None, sender=None, doctype='User', email_field='email', | |||
subject='[No Subject]', message='[No Content]', ref_doctype=None, ref_docname=None, | |||
add_unsubscribe_link=True): | |||
def is_unsubscribed(rdata): | |||
if not rdata: | |||
return 1 | |||
@@ -81,7 +82,7 @@ def add(email, sender, subject, formatted, text_content=None, | |||
try: | |||
e.message = get_email(email, sender=e.sender, formatted=formatted, subject=subject, | |||
text_content=text_content).as_string() | |||
except frappe.ValidationError: | |||
except frappe.InvalidEmailAddressError: | |||
# bad email id - don't add to queue | |||
return | |||
@@ -4,15 +4,16 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe import msgprint, throw, _ | |||
from frappe.utils import scrub_urls, cstr | |||
from frappe.utils import scrub_urls | |||
import email.utils | |||
from markdown2 import markdown | |||
def get_email(recipients, sender='', msg='', subject='[No Subject]', | |||
text_content = None, footer=None, print_html=None, formatted=None): | |||
"""send an html email as multipart with attachments and all""" | |||
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) | |||
return emailobj | |||
@@ -151,17 +152,18 @@ class EMail: | |||
def _validate(email): | |||
"""validate an email field""" | |||
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 | |||
if not self.sender: | |||
self.sender = frappe.db.get_value('Outgoing Email Settings', None, | |||
'auto_email_id') or frappe.conf.get('auto_email_id') or None | |||
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: | |||
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.reply_to = _validate(self.reply_to) | |||
@@ -189,7 +191,6 @@ class EMail: | |||
def get_formatted_html(subject, message, footer=None, print_html=None): | |||
# imported here to avoid cyclic import | |||
import inlinestyler.utils | |||
message = scrub_urls(message) | |||
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 | |||
}) | |||
# 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): | |||
"""append a footer (signature)""" | |||
footer = footer or "" | |||
# control panel | |||
footer += frappe.db.get_default('mail_footer') or '' | |||
# hooks | |||
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-->" | |||
@@ -6,8 +6,12 @@ from __future__ import unicode_literals | |||
import frappe, os | |||
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")): | |||
for fname in os.listdir(frappe.get_app_path(app, "fixtures")): | |||
if fname.endswith(".json") or fname.endswith(".csv"): | |||
@@ -1,7 +1,2 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# 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 |
@@ -53,9 +53,13 @@ def build_context(sitemap_options): | |||
# provide doc | |||
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) | |||
if module and hasattr(module, "get_context"): | |||
@@ -63,7 +67,7 @@ def build_context(sitemap_options): | |||
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) | |||
return context | |||
@@ -6,15 +6,21 @@ import frappe | |||
from frappe.website.website_generator import WebsiteGenerator | |||
from frappe.website.render import clear_cache | |||
template = "templates/generators/blog_category.html" | |||
no_cache = True | |||
class BlogCategory(WebsiteGenerator): | |||
def autoname(self): | |||
# to override autoname of WebsiteGenerator | |||
self.name = self.category_name | |||
def get_page_title(self): | |||
return self.title or self.name | |||
def on_update(self): | |||
WebsiteGenerator.on_update(self) | |||
clear_cache() | |||
def get_parent_website_route(self): | |||
parent_website_sitemap = super(BlogCategory, self).get_parent_website_route() | |||
return parent_website_sitemap or "blog" |
@@ -44,7 +44,8 @@ | |||
"in_list_view": 1, | |||
"label": "Blog Category", | |||
"options": "Blog Category", | |||
"permlevel": 0 | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"fieldname": "parent_website_route", | |||
@@ -95,7 +96,7 @@ | |||
"icon": "icon-quote-left", | |||
"idx": 1, | |||
"max_attachments": 5, | |||
"modified": "2014-05-27 03:49:07.888408", | |||
"modified": "2014-06-27 05:08:37.936947", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Blog Post", | |||
@@ -7,19 +7,22 @@ import frappe, re | |||
from frappe.website.website_generator import WebsiteGenerator | |||
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): | |||
save_versions = True | |||
def get_page_title(self): | |||
return self.title | |||
def validate(self): | |||
if not self.blog_intro: | |||
self.blog_intro = self.content[:140] | |||
re.sub("\<[^>]*\>", "", self.blog_intro) | |||
self.blog_intro = re.sub("\<[^>]*\>", "", self.blog_intro) | |||
if self.blog_intro: | |||
self.blog_intro = self.blog_intro[:140] | |||
@@ -27,22 +30,93 @@ class BlogPost(WebsiteGenerator): | |||
if self.published and not self.published_on: | |||
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 | |||
frappe.db.sql("""update tabBlogger set posts=(select count(*) from `tabBlog Post` | |||
where ifnull(blogger,'')=tabBlogger.name) | |||
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): | |||
WebsiteGenerator.on_update(self) | |||
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(): | |||
for blog in frappe.db.sql_list("""select page_name from | |||
`tabBlog Post` where ifnull(published,0)=1"""): | |||
clear_cache(blog) | |||
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 | |||
@@ -39,7 +39,7 @@ class Post(Document): | |||
def on_update(self): | |||
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_post_cache(self.parent_post or self.name) | |||
@@ -5,56 +5,34 @@ import frappe | |||
test_records = frappe.get_test_records('Web Page') | |||
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): | |||
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") | |||
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") | |||
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") | |||
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): | |||
web_page = frappe.get_doc("Web Page", "test-web-page-1") | |||
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"}), | |||
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") | |||
web_page.parent_website_route = "" | |||
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") | |||
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() |
@@ -18,6 +18,7 @@ $.extend(cur_frm.cscript, { | |||
}, | |||
refresh: function(doc) { | |||
cur_frm.cscript.layout(doc); | |||
cur_frm.set_intro(""); | |||
if (!doc.__islocal && doc.published) { | |||
cur_frm.set_intro(__("Published on website at: {0}", | |||
[repl('<a href="/%(website_route)s" target="_blank">/%(website_route)s</a>', doc.__onload)])); | |||
@@ -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": [ | |||
{ | |||
"fieldname": "section_title", | |||
"fieldtype": "Section Break", | |||
"label": "Title", | |||
"fieldname": "section_title", | |||
"fieldtype": "Section Break", | |||
"label": "Title", | |||
"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 | |||
}, | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "published", | |||
"fieldtype": "Check", | |||
"label": "Published", | |||
"fieldname": "published", | |||
"fieldtype": "Check", | |||
"label": "Published", | |||
"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%" | |||
}, | |||
}, | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"description": "Page content", | |||
"fieldname": "sb1", | |||
"fieldtype": "Section Break", | |||
"label": "Content", | |||
"description": "Page content", | |||
"fieldname": "sb1", | |||
"fieldtype": "Section Break", | |||
"label": "Content", | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "enable_comments", | |||
"fieldtype": "Check", | |||
"label": "Enable Comments", | |||
"fieldname": "enable_comments", | |||
"fieldtype": "Check", | |||
"label": "Enable Comments", | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "custom_javascript", | |||
"fieldtype": "Section Break", | |||
"label": "Custom Javascript", | |||
"fieldname": "custom_javascript", | |||
"fieldtype": "Section Break", | |||
"label": "Custom Javascript", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"description": "Add code as <script>", | |||
"fieldname": "insert_code", | |||
"fieldtype": "Check", | |||
"label": "Insert Code", | |||
"description": "Add code as <script>", | |||
"fieldname": "insert_code", | |||
"fieldtype": "Check", | |||
"label": "Insert Code", | |||
"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 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "custom_css", | |||
"fieldtype": "Section Break", | |||
"label": "Custom CSS", | |||
"fieldname": "custom_css", | |||
"fieldtype": "Section Break", | |||
"label": "Custom CSS", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "insert_style", | |||
"fieldtype": "Check", | |||
"label": "Insert Style", | |||
"fieldname": "insert_style", | |||
"fieldtype": "Check", | |||
"label": "Insert Style", | |||
"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 | |||
} | |||
], | |||
"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": [ | |||
{ | |||
"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 | |||
} | |||
] | |||
} | |||
} |
@@ -2,14 +2,43 @@ | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe, os, time, re | |||
import frappe, re | |||
import requests, requests.exceptions | |||
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): | |||
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(): | |||
cnt = 0 | |||
@@ -4,17 +4,250 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
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): | |||
def get_page_title(self): | |||
return self.group_title | |||
def on_update(self): | |||
WebsiteGenerator.on_update(self) | |||
clear_cache(website_group=self.name) | |||
def after_insert(self): | |||
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 | |||
} | |||
} |
@@ -1,7 +1,7 @@ | |||
{ | |||
"allow_rename": 1, | |||
"autoname": "field:page_name", | |||
"creation": "2013-11-18 15:38:40.000000", | |||
"creation": "2013-11-18 15:38:40", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"fields": [ | |||
@@ -38,8 +38,9 @@ | |||
}, | |||
{ | |||
"fieldname": "docname", | |||
"fieldtype": "Data", | |||
"fieldtype": "Dynamic Link", | |||
"label": "Docname", | |||
"options": "ref_doctype", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
@@ -50,10 +51,16 @@ | |||
"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 | |||
}, | |||
{ | |||
@@ -112,7 +119,7 @@ | |||
} | |||
], | |||
"idx": 1, | |||
"modified": "2014-02-24 12:46:59.000000", | |||
"modified": "2014-06-27 05:04:57.721756", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Website Route", | |||
@@ -22,59 +22,45 @@ class WebsiteRoute(NestedSet): | |||
return url | |||
def validate(self): | |||
if self.get_url() != self.name: | |||
self.rename() | |||
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): | |||
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) | |||
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_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() | |||
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_descendants() | |||
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): | |||
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{}` | |||
where parent_website_route=%s""".format(doctype), self.old_name): | |||
frappe.db.set_value(doctype, name, "parent_website_route", self.name) | |||
@@ -82,7 +68,7 @@ class WebsiteRoute(NestedSet): | |||
def rename_descendants(self): | |||
# rename children | |||
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.parent_website_route = self.name | |||
child.save() | |||
@@ -92,7 +78,7 @@ class WebsiteRoute(NestedSet): | |||
if self.page_or_generator == "Page": | |||
# for a page, name and website sitemap config form a unique key | |||
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: | |||
# for a generator, name, ref_doctype and docname make a unique key | |||
exists = frappe.db.sql("""select name from `tabWebsite Route` | |||
@@ -119,39 +105,13 @@ class WebsiteRoute(NestedSet): | |||
def clear_cache(self, name=None): | |||
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): | |||
if page_name: | |||
@@ -159,11 +119,3 @@ def remove_sitemap(page_name=None, ref_doctype=None, docname=None): | |||
elif ref_doctype and docname: | |||
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) | |||
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) |
@@ -9,7 +9,6 @@ import frappe | |||
from frappe.model.document import Document | |||
class WebsiteSlideshow(Document): | |||
def on_update(self): | |||
# a slide show can be in use and any change in it should get reflected | |||
from frappe.website.render import clear_cache | |||
@@ -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 | |||
} | |||
] | |||
} |
@@ -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 | |||
@@ -107,6 +107,10 @@ $.extend(frappe, { | |||
} catch(e) { | |||
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{ | |||
if(opts.btn) { | |||
$(opts.btn).addClass("btn-success"); | |||
@@ -286,6 +290,10 @@ $.extend(frappe, { | |||
$('[data-html-block]').each(function(i, section) { | |||
var $section = $(section); | |||
var stype = $section.attr("data-html-block"); | |||
// handle meta separately | |||
if (stype==="meta_block") return; | |||
var block_data = data[stype] || ""; | |||
// NOTE: use frappe.ready instead of $.ready for reliable execution | |||
@@ -307,6 +315,12 @@ $.extend(frappe, { | |||
}); | |||
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 | |||
$(".page-container").attr("id", "page-" + data.path); | |||
@@ -1,24 +1,23 @@ | |||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
// MIT License. See license.txt" | |||
frappe.pages['sitemap-browser'].onload = function(wrapper) { | |||
frappe.pages['sitemap-browser'].onload = function(wrapper) { | |||
frappe.ui.make_app_page({ | |||
parent: wrapper, | |||
title: 'Sitemap Browser', | |||
}); | |||
}); | |||
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(); | |||
}); | |||
$(wrapper) | |||
.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) | |||
.find(".layout-main-section") | |||
.css({ | |||
@@ -34,31 +33,13 @@ frappe.website.SitemapBrowser = Class.extend({ | |||
$(parent).empty(); | |||
var me = this; | |||
this.tree = new frappe.ui.Tree({ | |||
parent: $(parent), | |||
parent: $(parent), | |||
label: "Sitemap", | |||
method: 'frappe.website.page.sitemap_browser.sitemap_browser.get_children', | |||
toolbar: [ | |||
{ | |||
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"), | |||
click: function(node, btn) { | |||
@@ -66,7 +47,7 @@ frappe.website.SitemapBrowser = Class.extend({ | |||
} | |||
} | |||
] | |||
// drop: function(dragged_node, dropped_node, dragged_element, dropped_element) { | |||
// frappe.website.sitemap.update_parent(dragged_node.label, dropped_node.label, function(r) { | |||
// if(!r.exc) { | |||
@@ -78,7 +59,7 @@ frappe.website.SitemapBrowser = Class.extend({ | |||
}); | |||
this.tree.rootnode.$a | |||
.data('node-data', {value: "Sitemap", expandable:1}) | |||
.click(); | |||
.click(); | |||
}, | |||
selected_node: function() { | |||
return this.tree.$w.find('.tree-link.selected'); | |||
@@ -87,67 +68,4 @@ frappe.website.SitemapBrowser = Class.extend({ | |||
var node = this.selected_node(); | |||
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); | |||
} | |||
}); | |||
} | |||
}); | |||
}); |
@@ -164,7 +164,9 @@ def clear_cache(path=None): | |||
if 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: | |||
for p in frappe.db.sql_list("""select name from `tabWebsite Route`"""): | |||
if p is not None: | |||
@@ -24,21 +24,24 @@ def build_sitemap_options(path): | |||
sitemap_options = frappe._dict(frappe.get_doc("Website Route", path).as_dict()) | |||
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.title = sitemap_options.page_title | |||
sitemap_options.pathname = sitemap_options.name | |||
# 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: | |||
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 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 | |||
`tabWebsite Route` t1, `tab{ref_doctype}` t2 | |||
where ifnull(t1.parent_website_route,'')=%s | |||
and t1.public_read=1 | |||
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) | |||
children = [frappe.get_doc("Website Route", pathname)] + children | |||
@@ -2,15 +2,15 @@ | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe, os, time | |||
import frappe, os, time, sys | |||
from frappe import _ | |||
from frappe.utils import cint | |||
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): | |||
s = sync() | |||
s.verbose = True | |||
while True: | |||
s.start(rebuild) | |||
frappe.db.commit() | |||
@@ -19,17 +19,19 @@ def sync_statics(rebuild=False): | |||
class sync(object): | |||
def start(self, rebuild=False): | |||
self.verbose = False | |||
self.synced = [] | |||
self.synced_paths = [] | |||
self.to_insert = [] | |||
self.to_update = [] | |||
self.updated = 0 | |||
self.rebuild = rebuild | |||
for app in frappe.get_installed_apps(): | |||
self.sync_for_app(app) | |||
self.insert_and_update() | |||
self.cleanup() | |||
if self.updated: | |||
print str(self.updated) + " files updated" | |||
def sync_for_app(self, app): | |||
self.statics_path = frappe.get_app_path(app, "templates", "statics") | |||
if os.path.exists(self.statics_path): | |||
@@ -38,17 +40,15 @@ class sync(object): | |||
def sync_folder(self, basepath, folders, files): | |||
folder_route = os.path.relpath(basepath, self.statics_path) | |||
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 | |||
return | |||
if self.index: | |||
self.sync_using_given_index(basepath, folders, files) | |||
else: | |||
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 | |||
if fname in files: | |||
self.sync_file(fname, os.path.join(basepath, fname), None) | |||
return | |||
return True | |||
def sync_using_given_index(self, basepath, folders, files): | |||
for i, page_name in enumerate(self.index): | |||
@@ -108,12 +108,42 @@ class sync(object): | |||
["name", "idx", "static_file_timestamp", "docname"], as_dict=True) | |||
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: | |||
# 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({ | |||
"doctype":"Web Page", | |||
"idx": priority, | |||
@@ -122,66 +152,75 @@ class sync(object): | |||
"parent_website_route": parent_website_route | |||
}) | |||
page.fpath = fpath | |||
page.route = route | |||
page.update(get_static_content(fpath, page_name, route)) | |||
return page | |||
def insert_web_page(self, page): | |||
try: | |||
page.insert() | |||
except frappe.NameError: | |||
except frappe.NameError, e: | |||
print e | |||
# page exists, if deleted static, delete it and try again | |||
old_route = frappe.get_doc("Website Route", {"ref_doctype":"Web Page", | |||
"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) | |||
page.insert() # retry | |||
# update timestamp | |||
route_doc = frappe.get_doc("Website Route", {"ref_doctype": "Web Page", | |||
"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() | |||
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: | |||
print "Ignoring {0} because page found".format(route_details.name) | |||
return | |||
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 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): | |||
if self.synced: | |||
# delete static web pages that are not in immediate list | |||
frappe.delete_doc("Web Page", frappe.db.sql_list("""select docname | |||
from `tabWebsite Route` | |||
where ifnull(static_file_timestamp,'')!='' and name not in ({}) | |||
order by (rgt-lft) asc""".format(', '.join(["%s"]*len(self.synced))), | |||
tuple(self.synced))) | |||
else: | |||
# delete all static web pages | |||
frappe.delete_doc("Web Page", frappe.db.sql_list("""select docname | |||
from `tabWebsite Route` | |||
where ifnull(static_file_timestamp,'')!='' | |||
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): | |||
d = frappe._dict({}) | |||
@@ -197,21 +236,21 @@ def get_static_content(fpath, docname, route): | |||
d.title = first_line[2:] | |||
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) | |||
@@ -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 |
@@ -10,6 +10,8 @@ from jinja2.utils import concat | |||
from jinja2 import meta | |||
import re | |||
from sitemap import get_next | |||
def render_blocks(context): | |||
"""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(): | |||
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 | |||
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"): | |||
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>" | |||
if "breadcrumbs" not in out: | |||
out["breadcrumbs"] = scrub_relative_urls( | |||
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", ""): | |||
out["no_sidebar"] = 1 | |||
if "<!-- title:" in out.get("content", ""): | |||
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"): | |||
out["sidebar"] = scrub_relative_urls( | |||
frappe.get_template("templates/includes/sidebar.html").render(context)) | |||
@@ -20,6 +20,12 @@ def find_first_image(html): | |||
def can_cache(no_cache=False): | |||
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(): | |||
role_home_page = frappe.get_hooks("role_home_page") | |||
@@ -7,129 +7,117 @@ from frappe.model.document import Document | |||
from frappe.model.naming import append_number_if_name_exists | |||
from frappe.website.utils import cleanup_page_name | |||
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): | |||
def autoname(self): | |||
self.setup_generator() | |||
if not self.website_template: return | |||
self.name = self.get_page_name() | |||
append_number_if_name_exists(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): | |||
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): | |||
self.update_sitemap() | |||
if getattr(self, "save_versions", False): | |||
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): | |||
self.setup_generator() | |||
if not self.website_template: return | |||
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): | |||
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 | |||
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: | |||
# for sitemap.xml | |||
lastmod = frappe.utils.get_datetime(self.modified).strftime("%Y-%m-%d") | |||
else: | |||
lastmod = now() | |||
opts = frappe._dict({ | |||
route = frappe.new_doc("Website Route") | |||
route.update({ | |||
"page_or_generator": "Generator", | |||
"ref_doctype":self.doctype, | |||
"idx": self.idx, | |||
"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, | |||
"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"): | |||
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: | |||
opts.public_read = 1 | |||
route.public_read = 1 | |||
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): | |||
return self.get("title") or (self.name.replace("-", " ").replace("_", " ").title()) |
@@ -6,7 +6,7 @@ | |||
from __future__ import unicode_literals | |||
import frappe, os | |||
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 | |||
###### | |||
@@ -35,10 +35,11 @@ class FormMeta(Meta): | |||
self.add_code() | |||
self.load_print_formats() | |||
self.load_workflows() | |||
self.load_form_grid_templates() | |||
def as_dict(self, no_nulls=False): | |||
d = super(FormMeta, self).as_dict(no_nulls=no_nulls) | |||
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", | |||
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", "__form_grid_templates", | |||
"__linked_with", "__messages", "__print_formats", "__workflow_docs"): | |||
d[k] = self.get(k) | |||
@@ -153,6 +154,17 @@ class FormMeta(Meta): | |||
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): | |||
if "{% include" in content: | |||
content = frappe.get_jenv().from_string(content).render() | |||
@@ -5,7 +5,6 @@ from __future__ import unicode_literals | |||
import frappe | |||
import os, json | |||
import types | |||
from frappe import _ | |||
from frappe.modules import scrub, get_module_path | |||
@@ -31,11 +30,16 @@ def get_script(report_name): | |||
module_path = get_module_path(module) | |||
report_folder = os.path.join(module_path, "report", scrub(report.name)) | |||
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): | |||
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: | |||
script = report.javascript | |||
@@ -47,7 +51,10 @@ def get_script(report_name): | |||
if frappe.lang != "en": | |||
frappe.response["__messages"] = frappe.get_lang_dict("report", report_name) | |||
return script | |||
return { | |||
"script": script, | |||
"html_format": html_format | |||
} | |||
@frappe.whitelist() | |||
def run(report_name, filters=()): | |||
@@ -148,8 +155,6 @@ def get_linked_doctypes(columns): | |||
def get_user_match_filters(doctypes, ref_doctype): | |||
match_filters = {} | |||
doctypes_meta = {} | |||
tables = [] | |||
for dt in doctypes: | |||
match_filters.update(frappe.widgets.reportview.build_match_conditions(dt, False)) | |||
@@ -150,6 +150,8 @@ def scrub_user_tags(tagcount): | |||
rdict = {} | |||
tagdict = dict(tagcount) | |||
for t in tagdict: | |||
if not t: | |||
continue | |||
alltags = t.split(',') | |||
for tag in alltags: | |||
if tag: | |||
@@ -16,8 +16,6 @@ slugify | |||
termcolor | |||
werkzeug | |||
semantic_version | |||
lxml | |||
inlinestyler | |||
rauth>=0.6.2 | |||
requests==1.2.3 | |||
celery | |||