Merge pull request #9958 from rmehta/web-view
feat(Web View): Create Web Views
This commit is contained in:
commit
141a7241b0
34 changed files with 820 additions and 202 deletions
|
|
@ -15,7 +15,8 @@ global_cache_keys = ("app_hooks", "installed_apps",
|
|||
"app_modules", "module_app", "system_settings",
|
||||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
|
||||
'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version',
|
||||
'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts')
|
||||
'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts',
|
||||
'sitemap_routes')
|
||||
|
||||
user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
|
||||
"defaults", "user_permissions", "home_page", "linked_with",
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ class DocType(Document):
|
|||
if d.fieldtype:
|
||||
if (not getattr(d, "fieldname", None)):
|
||||
if d.label:
|
||||
d.fieldname = d.label.strip().lower().replace(' ','_')
|
||||
d.fieldname = d.label.strip().lower().replace(' ','_').strip('?')
|
||||
if d.fieldname in restricted:
|
||||
d.fieldname = d.fieldname + '1'
|
||||
if d.fieldtype=='Section Break':
|
||||
|
|
@ -914,7 +914,7 @@ def validate_fields(meta):
|
|||
if not d.permlevel: d.permlevel = 0
|
||||
if d.fieldtype not in table_fields: d.allow_bulk_edit = 0
|
||||
if not d.fieldname:
|
||||
d.fieldname = d.fieldname.lower()
|
||||
d.fieldname = d.fieldname.lower().strip('?')
|
||||
|
||||
check_illegal_characters(d.fieldname)
|
||||
check_invalid_fieldnames(meta.get("name"), d.fieldname)
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ frappe.ui.GroupBy = class {
|
|||
}
|
||||
|
||||
apply_settings(settings) {
|
||||
|
||||
if (!settings.group_by.startsWith('`tab')) {
|
||||
settings.group_by = '`tab' + this.doctype + '`.`' + settings.group_by + '`';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import WebFormList from './web_form_list'
|
|||
import WebForm from './web_form'
|
||||
|
||||
frappe.ready(function() {
|
||||
let query_params = frappe.utils.get_query_params();
|
||||
let wrapper = $(".web-form-wrapper");
|
||||
let is_list = parseInt(wrapper.data('is-list'));
|
||||
let is_list = parseInt(wrapper.data('is-list')) || query_params.is_list;
|
||||
let webform_doctype = wrapper.data('web-form-doctype');
|
||||
let webform_name = wrapper.data('web-form');
|
||||
let login_required = parseInt(wrapper.data('login-required'));
|
||||
let allow_delete = parseInt(wrapper.data('allow-delete'));
|
||||
let query_params = frappe.utils.get_query_params();
|
||||
let doc_name = query_params.name || '';
|
||||
let is_new = query_params.new;
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ frappe.ready(function() {
|
|||
settings: {
|
||||
allow_delete
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function show_form() {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,11 @@
|
|||
{%- endblock -%}
|
||||
|
||||
{%- block navbar -%}
|
||||
{% include "templates/includes/navbar/navbar.html" %}
|
||||
{%- if navbar_content -%}
|
||||
{{ navbar_content }}
|
||||
{%- else -%}
|
||||
{% include "templates/includes/navbar/navbar.html" %}
|
||||
{%- endif -%}
|
||||
{%- endblock -%}
|
||||
|
||||
{% block content %}
|
||||
|
|
@ -70,7 +74,11 @@
|
|||
{% endblock %}
|
||||
|
||||
{%- block footer -%}
|
||||
{% include "templates/includes/footer/footer.html" %}
|
||||
{%- if footer_content -%}
|
||||
{{ footer_content }}
|
||||
{%- else -%}
|
||||
{% include "templates/includes/footer/footer.html" %}
|
||||
{%- endif -%}
|
||||
{%- endblock -%}
|
||||
|
||||
{% block base_scripts %}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
</div>
|
||||
|
||||
{% block page_container %}
|
||||
<main class="container my-5">
|
||||
<main class="{% if not theme.use_full_width %}container{% endif %} my-5">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="page-header">
|
||||
{% block header %}{% endblock %}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ from frappe.utils import get_html_for_route
|
|||
|
||||
class TestSitemap(unittest.TestCase):
|
||||
def test_sitemap(self):
|
||||
from frappe.test_runner import make_test_records
|
||||
make_test_records('Blog Post')
|
||||
blogs = frappe.db.get_all('Blog Post', {'published': 1}, ['route'], limit=1)
|
||||
xml = get_html_for_route('sitemap.xml')
|
||||
self.assertTrue('/about</loc>' in xml)
|
||||
|
|
|
|||
|
|
@ -68,10 +68,7 @@ def render_template(template, context, is_path=None, safe_render=True):
|
|||
if not template:
|
||||
return ""
|
||||
|
||||
# if it ends with .html then its a freaking path, not html
|
||||
if (is_path
|
||||
or template.startswith("templates/")
|
||||
or (template.endswith('.html') and '\n' not in template)):
|
||||
if (is_path or guess_is_path(template)):
|
||||
return get_jenv().get_template(template).render(context)
|
||||
else:
|
||||
if safe_render and ".__" in template:
|
||||
|
|
@ -81,6 +78,16 @@ def render_template(template, context, is_path=None, safe_render=True):
|
|||
except TemplateError:
|
||||
throw(title="Jinja Template Error", msg="<pre>{template}</pre><pre>{tb}</pre>".format(template=template, tb=get_traceback()))
|
||||
|
||||
def guess_is_path(template):
|
||||
# template can be passed as a path or content
|
||||
# if its single line and ends with a html, then its probably a path
|
||||
if not '\n' in template and '.' in template:
|
||||
extn = template.rsplit('.')[-1]
|
||||
if extn in ('html', 'css', 'scss', 'py'):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_jloader():
|
||||
import frappe
|
||||
|
|
|
|||
0
frappe/website/doctype/css_class/__init__.py
Normal file
0
frappe/website/doctype/css_class/__init__.py
Normal file
8
frappe/website/doctype/css_class/css_class.js
Normal file
8
frappe/website/doctype/css_class/css_class.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('CSS Class', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
60
frappe/website/doctype/css_class/css_class.json
Normal file
60
frappe/website/doctype/css_class/css_class.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"creation": "2020-03-17 15:03:31.431344",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"is_global",
|
||||
"is_dynamic",
|
||||
"css"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_global",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Global?"
|
||||
},
|
||||
{
|
||||
"fieldname": "css",
|
||||
"fieldtype": "Code",
|
||||
"in_list_view": 1,
|
||||
"label": "CSS",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Website Theme elements are accessible as Jinja variables. Example: \"{{ primary_color }}\"",
|
||||
"fieldname": "is_dynamic",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Dynamic?"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-03-17 17:01:14.874631",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "CSS Class",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Website Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/website/doctype/css_class/css_class.py
Normal file
10
frappe/website/doctype/css_class/css_class.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class CSSClass(Document):
|
||||
pass
|
||||
10
frappe/website/doctype/css_class/test_css_class.py
Normal file
10
frappe/website/doctype/css_class/test_css_class.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestCSSClass(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -156,6 +156,9 @@ def get_context(context):
|
|||
# only a single doc allowed and no existing doc, hence new
|
||||
frappe.form_dict.new = 1
|
||||
|
||||
if frappe.form_dict.is_list:
|
||||
context.is_list = True
|
||||
|
||||
# always render new form if login is not required or doesn't allow editing existing ones
|
||||
if not self.login_required or not self.allow_edit:
|
||||
frappe.form_dict.new = 1
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ class WebPage(WebsiteGenerator):
|
|||
|
||||
|
||||
def check_publish_status():
|
||||
# called via daily scheduler
|
||||
web_pages = frappe.get_all("Web Page", fields=["name", "published", "start_date", "end_date"])
|
||||
now_date = get_datetime(now())
|
||||
|
||||
|
|
|
|||
0
frappe/website/doctype/web_view/__init__.py
Normal file
0
frappe/website/doctype/web_view/__init__.py
Normal file
7
frappe/website/doctype/web_view/templates/web_view.html
Normal file
7
frappe/website/doctype/web_view/templates/web_view.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block page_content %}
|
||||
{% include "frappe/website/doctype/web_view/templates/web_view_content.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<!-- this is a sample default web page template -->
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{%- if css_rules or css -%}
|
||||
|
||||
<style>
|
||||
{%- for css_rule in css_rules -%}
|
||||
{{ css_rule }}
|
||||
{%- endfor -%}
|
||||
{{ css or "" }}
|
||||
</style>
|
||||
{%- endif -%}
|
||||
|
||||
{%- macro render_element(element) -%}
|
||||
{%- if element.element_type=='Content' -%}
|
||||
<div class="web-content {{ element_class(element) }}" {{ element_style(element) }}>
|
||||
{{ element.web_content_html }}
|
||||
</div>
|
||||
{%- elif element.element_type=='Image' -%}
|
||||
<img src='{{ element.image_url }}'
|
||||
{%- if element.element_class -%}class='{{ element.element_class }}'{%- endif -%}
|
||||
{{ element_style(element) }}>
|
||||
{%- endif -%}
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro element_class(element) -%}
|
||||
{{ element.element_class or "" }}
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro element_style(element) -%}
|
||||
{%- if element.element_style -%}
|
||||
style = "{{ element.element_style }}"
|
||||
{%- endif -%}
|
||||
{%- endmacro -%}
|
||||
|
||||
|
||||
{%- for section in sections -%}
|
||||
<section class='section {{ section.element_class or "" }} {{ section.hide and "hidden" or "" }}'>
|
||||
<div class='section-body container'>
|
||||
{%- if section.section_intro -%}
|
||||
|
||||
<div class='section-intro'>{{ section.section_intro }}</div>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if section.section_type == 'List' -%}
|
||||
{%- for element in section.elements -%}
|
||||
{{ render_element(element) }}
|
||||
{%- endfor -%}
|
||||
|
||||
{%- elif section.section_type == 'Grid' -%}
|
||||
<div class='row'>
|
||||
{%- for element in section.elements -%}
|
||||
<div class='col-md-{{ element.columns or 6 }}'>
|
||||
{{ render_element(element) }}
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
|
||||
{%- elif section.section_type == 'Tabbed' -%}
|
||||
<ul class="nav" role="tablist">
|
||||
{%- for element in section.elements -%}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ loop.index == 1 and 'active' or ''}}" href="#{{ element.element_id }}" role="tab" data-toggle="tab">
|
||||
{{ element.title }}
|
||||
</a>
|
||||
</li>
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
{%- for element in section.elements -%}
|
||||
<div class="tab-pane {{ loop.index == 1 and 'show active' or ''}}" role="tabpanel" id="{{ element.element_id }}">
|
||||
{{ render_element(element) }}
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
|
||||
{%- endif -%}
|
||||
</div>
|
||||
</section>
|
||||
{%- endfor -%}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<a href={{ route }}>{{ title }}</a>
|
||||
</div>
|
||||
<!-- this is a sample default list template -->
|
||||
93
frappe/website/doctype/web_view/test_web_view.py
Normal file
93
frappe/website/doctype/web_view/test_web_view.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
from frappe.website.doctype.web_page.test_web_page import get_page_content
|
||||
|
||||
test_dependencies = ['Web Page'] # for test
|
||||
|
||||
class TestWebView(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
frappe.delete_doc_if_exists('Web View', 'test-web-view')
|
||||
frappe.delete_doc_if_exists('CSS Class', 'test-css-class')
|
||||
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'CSS Class',
|
||||
name = 'test-css-class',
|
||||
css = '.test-class { color: red; }'
|
||||
)).insert()
|
||||
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Web View',
|
||||
title = 'Test Web View',
|
||||
route = 'test-web-view',
|
||||
published = 1,
|
||||
items = [
|
||||
dict(
|
||||
element_type = 'Section',
|
||||
section_type = 'List'
|
||||
),
|
||||
dict(
|
||||
element_type = 'Content',
|
||||
web_content_type = 'Markdown',
|
||||
web_content_markdown = '## Heading\n\nBody'
|
||||
),
|
||||
dict(
|
||||
element_type = 'Content',
|
||||
web_content_type = 'HTML',
|
||||
web_content_html = '<div>Here is some HTML</div>'
|
||||
),
|
||||
dict(
|
||||
element_type = 'Section',
|
||||
section_type = 'Grid'
|
||||
),
|
||||
dict(
|
||||
element_type = 'Content',
|
||||
element_class = 'test-css-class',
|
||||
web_content_type = 'Markdown',
|
||||
web_content_markdown = 'Column 1'
|
||||
),
|
||||
dict(
|
||||
element_type = 'Content',
|
||||
web_content_type = 'Markdown',
|
||||
web_content_markdown = 'Column 2'
|
||||
),
|
||||
]
|
||||
)).insert()
|
||||
|
||||
def test_web_view(self):
|
||||
html = get_page_content('test-web-view')
|
||||
#print(html)
|
||||
self.assert_web_view_in_html(html)
|
||||
|
||||
def assert_web_view_in_html(self, html):
|
||||
self.assertTrue('<h2 id="heading">Heading</h2>' in html)
|
||||
self.assertTrue('<div>Here is some HTML</div>' in html)
|
||||
self.assertTrue('Column 1' in html)
|
||||
self.assertTrue('Column 2' in html)
|
||||
self.assertTrue('.test-class { color: red; }' in html)
|
||||
|
||||
def test_web_view_in_footer(self):
|
||||
website_settings = frappe.get_single("Website Settings")
|
||||
website_settings.footer_type = 'Web View'
|
||||
website_settings.footer_web_view = 'test-web-view'
|
||||
website_settings.save()
|
||||
|
||||
html = get_page_content('test-web-page-1')
|
||||
|
||||
website_settings.footer_type = 'Standard'
|
||||
website_settings.footer_web_view = ''
|
||||
website_settings.save()
|
||||
|
||||
# web view should still come as footer
|
||||
self.assert_web_view_in_html(html)
|
||||
|
||||
html_without_footer = get_page_content('test-web-page-1')
|
||||
|
||||
# no more footer
|
||||
self.assertFalse('Column 1' in html_without_footer)
|
||||
8
frappe/website/doctype/web_view/web_view.js
Normal file
8
frappe/website/doctype/web_view/web_view.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Web View', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
92
frappe/website/doctype/web_view/web_view.json
Normal file
92
frappe/website/doctype/web_view/web_view.json
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_guest_to_view": 1,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"beta": 1,
|
||||
"creation": "2020-03-16 15:28:03.828741",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"route",
|
||||
"published",
|
||||
"items",
|
||||
"css"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Items",
|
||||
"options": "Web View Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Route",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "published",
|
||||
"fieldtype": "Check",
|
||||
"label": "Published"
|
||||
},
|
||||
{
|
||||
"fieldname": "css",
|
||||
"fieldtype": "Code",
|
||||
"label": "CSS"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"modified": "2020-04-15 23:58:12.208049",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web View",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Website Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"route": "route",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
74
frappe/website/doctype/web_view/web_view.py
Normal file
74
frappe/website/doctype/web_view/web_view.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
from frappe.utils import markdown
|
||||
import frappe
|
||||
|
||||
class WebView(WebsiteGenerator):
|
||||
def get_context(self, context):
|
||||
# group items into sections
|
||||
context.sections = []
|
||||
context.css_rules = []
|
||||
for item in self.items:
|
||||
if not context.sections and item.element_type!='Section':
|
||||
self.add_default_section(context)
|
||||
|
||||
if item.element_type=='Section':
|
||||
self.add_section(context, item)
|
||||
else:
|
||||
self.add_item(context, item)
|
||||
|
||||
self.add_css_class(context, item)
|
||||
|
||||
return context
|
||||
|
||||
def add_section(self, context, item):
|
||||
item.elements = []
|
||||
context.sections.append(item)
|
||||
|
||||
if item.section_intro:
|
||||
item.section_intro = markdown(item.section_intro)
|
||||
|
||||
def add_item(self, context, item):
|
||||
if item.hide:
|
||||
return
|
||||
|
||||
if item.web_content_type == 'Markdown':
|
||||
item.web_content_html = markdown(item.web_content_markdown)
|
||||
|
||||
if item.title:
|
||||
item.element_id = frappe.scrub(item.title)
|
||||
|
||||
context.sections[-1].elements.append(item)
|
||||
|
||||
def add_css_class(self, context, item):
|
||||
# add css class definitions selected by the user
|
||||
if item.element_class and not item.hide:
|
||||
css, is_dynamic = frappe.db.get_value('CSS Class', item.element_class, ['css', 'is_dynamic'])
|
||||
if is_dynamic:
|
||||
css = frappe.render_template(css, self.get_theme())
|
||||
context.css_rules.append(css)
|
||||
|
||||
def render_content(self):
|
||||
# webview can be rendered as an object (see footer)
|
||||
return frappe.render_template("frappe/website/doctype/web_view/templates/web_view_content.html", self.get_context(self.as_dict()))
|
||||
|
||||
def get_theme(self):
|
||||
# get theme properties
|
||||
if not hasattr(self, '_theme'):
|
||||
default_theme = frappe.db.get_value("Website Settings", "Website Settings", "website_theme")
|
||||
self._theme = frappe.get_value('Website Theme', default_theme, '*')
|
||||
return self._theme
|
||||
|
||||
def add_default_section(self, context):
|
||||
# add a default section if not added
|
||||
context.sections.append(frappe._dict(
|
||||
element_type='Section',
|
||||
section_type='List',
|
||||
title='Default Section',
|
||||
elements=[]
|
||||
))
|
||||
0
frappe/website/doctype/web_view_item/__init__.py
Normal file
0
frappe/website/doctype/web_view_item/__init__.py
Normal file
121
frappe/website/doctype/web_view_item/web_view_item.json
Normal file
121
frappe/website/doctype/web_view_item/web_view_item.json
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-03-16 15:25:17.530296",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"element_type",
|
||||
"title",
|
||||
"hide",
|
||||
"column_break_3",
|
||||
"columns",
|
||||
"element_class",
|
||||
"element_style",
|
||||
"section_break_5",
|
||||
"section_type",
|
||||
"web_content_type",
|
||||
"web_content_html",
|
||||
"web_content_markdown",
|
||||
"image_url",
|
||||
"section_intro"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "element_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Element Type",
|
||||
"options": "Section\nContent\nParagraph\nWeb List\nWeb Form",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.element_type==='Section'",
|
||||
"fieldname": "section_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Section Type",
|
||||
"options": "\nList\nTabbed\nGrid"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.element_type==='Content'",
|
||||
"fieldname": "web_content_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Web Content Type",
|
||||
"options": "\nHTML\nMarkdown"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.web_content_type==='HTML'",
|
||||
"fieldname": "web_content_html",
|
||||
"fieldtype": "HTML Editor",
|
||||
"label": "Web Content HTML"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.web_content_type==='Markdown'",
|
||||
"fieldname": "web_content_markdown",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Web Content Markdown"
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"label": "Columns"
|
||||
},
|
||||
{
|
||||
"fieldname": "element_class",
|
||||
"fieldtype": "Link",
|
||||
"label": "Element Class",
|
||||
"options": "CSS Class"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.element_type==='Image'",
|
||||
"fieldname": "image_url",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Image URL"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.element_type==='Section'",
|
||||
"fieldname": "section_intro",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Section Intro"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide"
|
||||
},
|
||||
{
|
||||
"fieldname": "element_style",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Element Style"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-28 14:21:50.014823",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web View Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/website/doctype/web_view_item/web_view_item.py
Normal file
10
frappe/website/doctype/web_view_item/web_view_item.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class WebViewItem(Document):
|
||||
pass
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestWebsiteSettings(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -19,11 +19,15 @@
|
|||
"set_banner_from_image",
|
||||
"favicon",
|
||||
"top_bar",
|
||||
"top_bar_type",
|
||||
"top_bar_web_view",
|
||||
"navbar_search",
|
||||
"top_bar_items",
|
||||
"banner",
|
||||
"banner_html",
|
||||
"footer",
|
||||
"footer_type",
|
||||
"footer_web_view",
|
||||
"copyright",
|
||||
"address",
|
||||
"footer_items",
|
||||
|
|
@ -130,11 +134,13 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.top_bar_type==='Standard'",
|
||||
"fieldname": "navbar_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Search in Top Bar"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.top_bar_type==='Standard'",
|
||||
"fieldname": "top_bar_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Top Bar Items",
|
||||
|
|
@ -160,17 +166,20 @@
|
|||
"label": "Footer"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.footer_type==='Standard'",
|
||||
"fieldname": "copyright",
|
||||
"fieldtype": "Data",
|
||||
"label": "Copyright"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.footer_type==='Standard'",
|
||||
"description": "Address and other legal information you may want to put in the footer.",
|
||||
"fieldname": "address",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Address"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.footer_type==='Standard'",
|
||||
"fieldname": "footer_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Footer Items",
|
||||
|
|
@ -178,6 +187,7 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.footer_type==='Standard'",
|
||||
"fieldname": "hide_footer_signup",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Footer Signup"
|
||||
|
|
@ -319,6 +329,34 @@
|
|||
"fieldname": "authorize_api_indexing_access",
|
||||
"fieldtype": "Button",
|
||||
"label": "Authorize API Indexing Access"
|
||||
},
|
||||
{
|
||||
"default": "Standard",
|
||||
"fieldname": "footer_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Footer Type",
|
||||
"options": "Standard\nWeb View"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.footer_type==='Web View'",
|
||||
"fieldname": "footer_web_view",
|
||||
"fieldtype": "Link",
|
||||
"label": "Footer Web View",
|
||||
"options": "Web View"
|
||||
},
|
||||
{
|
||||
"default": "Standard",
|
||||
"fieldname": "top_bar_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Top Bar Type",
|
||||
"options": "Standard\nWeb View"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.top_bar_type==='Web View'",
|
||||
"fieldname": "top_bar_web_view",
|
||||
"fieldtype": "Link",
|
||||
"label": "Top Bar Web View",
|
||||
"options": "Web View"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
|
|
@ -326,7 +364,7 @@
|
|||
"issingle": 1,
|
||||
"links": [],
|
||||
"max_attachments": 10,
|
||||
"modified": "2020-02-21 16:46:59.947403",
|
||||
"modified": "2020-04-21 16:46:59.947403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Website Settings",
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ def get_website_settings():
|
|||
context[key] = context[key][-1]
|
||||
|
||||
add_website_theme(context)
|
||||
add_webviews(context, settings)
|
||||
|
||||
if not context.get("favicon"):
|
||||
context["favicon"] = "/assets/frappe/images/favicon.png"
|
||||
|
|
@ -158,6 +159,17 @@ def get_website_settings():
|
|||
|
||||
return context
|
||||
|
||||
def add_webviews(context, settings):
|
||||
# render footer as webview, not standard view
|
||||
# see base.html for how this is handled
|
||||
if settings.footer_type=='Web View' and settings.footer_web_view:
|
||||
context.footer_content = frappe.get_doc('Web View',
|
||||
settings.footer_web_view).render_content()
|
||||
|
||||
if settings.top_bar_type=='Web View' and settings.top_bar_web_view:
|
||||
context.navbar_content = frappe.get_doc('Web View',
|
||||
settings.top_bar_web_view).render_content()
|
||||
|
||||
def get_items(parentfield):
|
||||
all_top_items = frappe.db.sql("""\
|
||||
select * from `tabTop Bar Item`
|
||||
|
|
|
|||
|
|
@ -2,10 +2,30 @@
|
|||
# See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
test_records = frappe.get_test_records('Website Theme')
|
||||
|
||||
class TestWebsiteTheme(unittest.TestCase):
|
||||
pass
|
||||
def test_website_theme(self):
|
||||
if os.environ.get('CI'):
|
||||
# no node-sass on travis (?)
|
||||
return
|
||||
|
||||
frappe.delete_doc_if_exists('Website Theme', 'test-theme')
|
||||
theme = frappe.get_doc(dict(
|
||||
doctype = 'Website Theme',
|
||||
theme = 'test-theme',
|
||||
google_font = 'Inter',
|
||||
custom_scss = 'body { font-size: 16.5px; }'
|
||||
)).insert()
|
||||
|
||||
with open(theme.theme_url[1:]) as f:
|
||||
css = f.read()
|
||||
|
||||
self.assertTrue(theme.custom_scss in css)
|
||||
self.assertTrue('fonts.googleapis.com' in css)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ frappe.ui.form.on('Website Theme', {
|
|||
frm.clear_custom_buttons();
|
||||
frm.toggle_display(["module", "custom"], !frappe.boot.developer_mode);
|
||||
|
||||
frm.trigger('setup_configure_theme');
|
||||
frm.trigger('set_default_theme_button_and_indicator');
|
||||
|
||||
if (!frm.doc.custom && !frappe.boot.developer_mode) {
|
||||
|
|
@ -17,96 +16,6 @@ frappe.ui.form.on('Website Theme', {
|
|||
}
|
||||
},
|
||||
|
||||
setup_configure_theme(frm) {
|
||||
frm.add_custom_button(__('Configure Theme'), () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Configure Theme'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Font Styles'),
|
||||
fieldtype: 'Section Break'
|
||||
},
|
||||
{
|
||||
label: __('Google Font'),
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'google_font',
|
||||
description: __('Add the name of a "Google Web Font" e.g. "Open Sans"')
|
||||
},
|
||||
{
|
||||
label: __('Font Size (px)'),
|
||||
fieldtype: 'Int',
|
||||
fieldname: 'font_size',
|
||||
default: 16
|
||||
},
|
||||
{
|
||||
label: __('Theme Colors'),
|
||||
fieldtype: 'Section Break',
|
||||
},
|
||||
{
|
||||
label: __('Primary Color'),
|
||||
fieldtype: 'Color',
|
||||
fieldname: 'primary_color'
|
||||
},
|
||||
{
|
||||
label: __('Dark Color'),
|
||||
fieldtype: 'Color',
|
||||
fieldname: 'dark_color'
|
||||
},
|
||||
{
|
||||
label: __('Text Color'),
|
||||
fieldtype: 'Color',
|
||||
fieldname: 'text_color'
|
||||
},
|
||||
{
|
||||
label: __('Background Color'),
|
||||
fieldtype: 'Color',
|
||||
fieldname: 'background_color'
|
||||
},
|
||||
{
|
||||
label: __('Misc'),
|
||||
fieldtype: 'Section Break',
|
||||
},
|
||||
{
|
||||
label: __('Navbar Style'),
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'navbar_style',
|
||||
options: [
|
||||
'Light',
|
||||
'Dark'
|
||||
],
|
||||
default: 'Light'
|
||||
},
|
||||
{
|
||||
label: __('Enable Shadows'),
|
||||
fieldtype: 'Check',
|
||||
fieldname: 'enable_shadows'
|
||||
},
|
||||
{
|
||||
label: __('Enable Gradients'),
|
||||
fieldtype: 'Check',
|
||||
fieldname: 'enable_gradients'
|
||||
},
|
||||
{
|
||||
label: __('Rounded Corners'),
|
||||
fieldtype: 'Check',
|
||||
fieldname: 'enable_rounded',
|
||||
default: 1
|
||||
},
|
||||
],
|
||||
primary_action: (values) => {
|
||||
frm.set_value('theme_json', JSON.stringify(values));
|
||||
frm.events.set_theme_from_config(frm, values);
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
|
||||
if (frm.doc.theme_json) {
|
||||
d.set_values(JSON.parse(frm.doc.theme_json));
|
||||
}
|
||||
d.show();
|
||||
});
|
||||
},
|
||||
|
||||
set_default_theme_button_and_indicator(frm) {
|
||||
frappe.db.get_single_value('Website Settings', 'website_theme')
|
||||
.then(value => {
|
||||
|
|
@ -122,92 +31,5 @@ frappe.ui.form.on('Website Theme', {
|
|||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_theme_from_config(frm, config) {
|
||||
const {
|
||||
google_font,
|
||||
font_size,
|
||||
primary_color,
|
||||
dark_color,
|
||||
text_color,
|
||||
background_color,
|
||||
navbar_style,
|
||||
enable_shadows,
|
||||
enable_gradients,
|
||||
enable_rounded
|
||||
} = config;
|
||||
|
||||
let scss_lines = [];
|
||||
let js_lines = [];
|
||||
if (google_font) {
|
||||
const google_font_slug = google_font.split(' ').join('+');
|
||||
const font_family_default = `'-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif'`;
|
||||
scss_lines.push(
|
||||
`@import url('https://fonts.googleapis.com/css?family=${google_font_slug}:400,300,400italic,700&subset=latin,latin-ext');`,
|
||||
`$font-family-sans-serif: "${google_font}", ${font_family_default};`
|
||||
);
|
||||
}
|
||||
if (primary_color) {
|
||||
scss_lines.push(
|
||||
`$primary: ${primary_color};`
|
||||
);
|
||||
}
|
||||
if (dark_color) {
|
||||
scss_lines.push(
|
||||
`$dark: ${dark_color};`
|
||||
);
|
||||
}
|
||||
if (text_color) {
|
||||
scss_lines.push(
|
||||
`$body-color: ${text_color};`
|
||||
);
|
||||
}
|
||||
if (background_color) {
|
||||
scss_lines.push(
|
||||
`$body-bg: ${background_color};`
|
||||
);
|
||||
}
|
||||
|
||||
scss_lines.push(
|
||||
`$enable-shadows: ${Boolean(enable_shadows)};`
|
||||
);
|
||||
|
||||
scss_lines.push(
|
||||
`$enable-gradients: ${Boolean(enable_gradients)};`
|
||||
);
|
||||
|
||||
scss_lines.push(
|
||||
`$enable-rounded: ${Boolean(enable_rounded)};`
|
||||
);
|
||||
|
||||
if (font_size) {
|
||||
scss_lines.push(
|
||||
'\n',
|
||||
`body {\n\tfont-size: ${font_size}px;\n}`
|
||||
);
|
||||
}
|
||||
|
||||
if (navbar_style === 'Dark') {
|
||||
if (!(frm.doc.js || '').includes(`.addClass('navbar-dark bg-dark')`)) {
|
||||
js_lines.push(
|
||||
`frappe.ready(() => {`,
|
||||
`\t$('.navbar').removeClass('navbar-light bg-white').addClass('navbar-dark bg-dark')`,
|
||||
`})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
scss_lines.push(
|
||||
`@import "frappe/public/scss/website";`,
|
||||
'\n'
|
||||
);
|
||||
|
||||
// set scss
|
||||
frm.set_value('theme_scss', scss_lines.join('\n'));
|
||||
|
||||
// set js
|
||||
const js = frm.doc.js || '';
|
||||
frm.set_value('js', js_lines.join('\n') + js);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"autoname": "field:theme",
|
||||
"creation": "2015-02-18 12:46:38.168929",
|
||||
|
|
@ -9,7 +10,20 @@
|
|||
"theme",
|
||||
"module",
|
||||
"custom",
|
||||
"configuration_section",
|
||||
"google_font",
|
||||
"font_size",
|
||||
"font_properties",
|
||||
"use_full_width",
|
||||
"column_break_7",
|
||||
"primary_color",
|
||||
"text_color",
|
||||
"light_color",
|
||||
"dark_color",
|
||||
"background_color",
|
||||
"stylesheet_section",
|
||||
"theme_scss",
|
||||
"custom_scss",
|
||||
"theme_json",
|
||||
"theme_url",
|
||||
"custom_js_section",
|
||||
|
|
@ -43,7 +57,8 @@
|
|||
"fieldname": "theme_scss",
|
||||
"fieldtype": "Code",
|
||||
"label": "Theme",
|
||||
"options": "SCSS"
|
||||
"options": "SCSS",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "theme_url",
|
||||
|
|
@ -68,9 +83,76 @@
|
|||
"hidden": 1,
|
||||
"label": "Theme JSON",
|
||||
"options": "JSON"
|
||||
},
|
||||
{
|
||||
"fieldname": "configuration_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Configuration"
|
||||
},
|
||||
{
|
||||
"fieldname": "google_font",
|
||||
"fieldtype": "Data",
|
||||
"label": "Google Font"
|
||||
},
|
||||
{
|
||||
"fieldname": "font_size",
|
||||
"fieldtype": "Data",
|
||||
"label": "Font Size"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "primary_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Primary Color"
|
||||
},
|
||||
{
|
||||
"fieldname": "text_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Text Color"
|
||||
},
|
||||
{
|
||||
"fieldname": "dark_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Dark Color"
|
||||
},
|
||||
{
|
||||
"fieldname": "background_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Background Color"
|
||||
},
|
||||
{
|
||||
"fieldname": "stylesheet_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Stylesheet"
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_scss",
|
||||
"fieldtype": "Code",
|
||||
"label": "Custom SCSS"
|
||||
},
|
||||
{
|
||||
"fieldname": "light_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Light Color"
|
||||
},
|
||||
{
|
||||
"default": "300,600",
|
||||
"fieldname": "font_properties",
|
||||
"fieldtype": "Data",
|
||||
"label": "Font Properties"
|
||||
},
|
||||
{
|
||||
"description": "Content will not be inside a \"container\" class, you will have to add your own containers for different sections.",
|
||||
"fieldname": "use_full_width",
|
||||
"fieldtype": "Data",
|
||||
"label": "Use Full Width"
|
||||
}
|
||||
],
|
||||
"modified": "2019-06-14 18:36:21.283390",
|
||||
"links": [],
|
||||
"modified": "2020-03-19 09:46:48.750150",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Website Theme",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from os.path import join as join_path, exists as path_exists
|
|||
class WebsiteTheme(Document):
|
||||
def validate(self):
|
||||
self.validate_if_customizable()
|
||||
self.render_theme()
|
||||
self.validate_theme()
|
||||
|
||||
def on_update(self):
|
||||
|
|
@ -35,12 +36,14 @@ class WebsiteTheme(Document):
|
|||
if self.is_standard_and_not_valid_user():
|
||||
frappe.throw(_("Please Duplicate this Website Theme to customize."))
|
||||
|
||||
def render_theme(self):
|
||||
self.theme_scss = frappe.render_template('frappe/website/doctype/website_theme/website_theme_template.scss', self.as_dict())
|
||||
|
||||
def validate_theme(self):
|
||||
'''Generate theme css if theme_scss has changed'''
|
||||
if self.theme_scss:
|
||||
doc_before_save = self.get_doc_before_save()
|
||||
if doc_before_save is None or self.theme_scss != doc_before_save.theme_scss:
|
||||
self.generate_bootstrap_theme()
|
||||
doc_before_save = self.get_doc_before_save()
|
||||
if doc_before_save is None or get_scss(self) != get_scss(doc_before_save):
|
||||
self.generate_bootstrap_theme()
|
||||
|
||||
def export_doc(self):
|
||||
"""Export to standard folder `[module]/website_theme/[name]/[name].json`."""
|
||||
|
|
@ -57,9 +60,14 @@ class WebsiteTheme(Document):
|
|||
def generate_bootstrap_theme(self):
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
folder_path = join_path(frappe.utils.get_bench_path(), 'sites', 'assets', 'css')
|
||||
self.delete_old_theme_files(folder_path)
|
||||
|
||||
# add a random suffix
|
||||
file_name = frappe.scrub(self.name) + '_' + frappe.generate_hash('Website Theme', 8) + '.css'
|
||||
output_path = join_path(frappe.utils.get_bench_path(), 'sites', 'assets', 'css', file_name)
|
||||
content = self.theme_scss
|
||||
output_path = join_path(folder_path, file_name)
|
||||
|
||||
content = get_scss(self)
|
||||
content = content.replace('\n', '\\n')
|
||||
command = ['node', 'generate_bootstrap_theme.js', output_path, content]
|
||||
|
||||
|
|
@ -76,6 +84,12 @@ class WebsiteTheme(Document):
|
|||
|
||||
frappe.msgprint(_('Compiled Successfully'), alert=True)
|
||||
|
||||
def delete_old_theme_files(self, folder_path):
|
||||
import os
|
||||
for fname in os.listdir(folder_path):
|
||||
if fname.startswith(frappe.scrub(self.name) + '_') and fname.endswith('.css'):
|
||||
os.remove(os.path.join(folder_path, fname))
|
||||
|
||||
def generate_theme_if_not_exist(self):
|
||||
bench_path = frappe.utils.get_bench_path()
|
||||
if self.theme_url:
|
||||
|
|
@ -116,3 +130,7 @@ def generate_theme_files_if_not_exist():
|
|||
doc.save()
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), "Theme File Generation Failed")
|
||||
|
||||
def get_scss(doc):
|
||||
return (doc.theme_scss or '') + '\n' + (doc.custom_scss or '')
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
{% if google_font %}
|
||||
@import url('https://fonts.googleapis.com/css?family={{ google_font.replace(' ', '+') }}:{{ font_properties }}&display=swap');
|
||||
$font-family-sans-serif: "{{ google_font }}", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
{% endif %}
|
||||
|
||||
{% if primary_color %}$primary: {{ primary_color }};{% endif %}
|
||||
{% if dark_color %}$dark: {{ dark_color }};{% endif %}
|
||||
{% if text_color %}$body-color: {{ text_color }};{% endif %}
|
||||
{% if background_color %}$body-bg: {{ background_color }};{% endif %}
|
||||
|
||||
$enable-shadows: {{ enable_shadows and "true" or "false" }};
|
||||
$enable-gradients: {{ enable_gradients and "true" or "false" }};
|
||||
$enable-rounded: {{ enable_rounded and "true" or "false" }};
|
||||
|
||||
@import "frappe/public/scss/website";
|
||||
|
||||
body {
|
||||
{% if font_size %}
|
||||
font-size: {{ font_size }};
|
||||
{% endif %}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue