Merge branch 'develop' into email-domain-fixes
This commit is contained in:
commit
e48a26ced7
66 changed files with 1176 additions and 327 deletions
28
.github/frappe_linter/translation.py
vendored
Normal file
28
.github/frappe_linter/translation.py
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import re
|
||||
import sys
|
||||
|
||||
errors_encounter = 0
|
||||
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
|
||||
start_pattern = re.compile(r"_{1,2}\([\"']{1,3}")
|
||||
|
||||
# skip first argument
|
||||
files = sys.argv[1:]
|
||||
for _file in files:
|
||||
if not _file.endswith(('.py', '.js')):
|
||||
continue
|
||||
with open(_file, 'r') as f:
|
||||
print(f'Checking: {_file}')
|
||||
for num, line in enumerate(f, 1):
|
||||
all_matches = start_pattern.finditer(line)
|
||||
if all_matches:
|
||||
for match in all_matches:
|
||||
verify = pattern.search(line)
|
||||
if not verify:
|
||||
errors_encounter += 1
|
||||
print(f'A syntax error has been discovered at line number: {num}')
|
||||
print(f'Syntax error occurred with: {line}')
|
||||
if errors_encounter > 0:
|
||||
print('You can visit "https://frappe.io/docs/user/en/translations" to resolve this error.')
|
||||
assert 1+1 == 3
|
||||
else:
|
||||
print('Good To Go!')
|
||||
22
.github/workflows/translation_linter.yml
vendored
Normal file
22
.github/workflows/translation_linter.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
name: Frappe Linter
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- version-12-hotfix
|
||||
- version-11-hotfix
|
||||
jobs:
|
||||
check_translation:
|
||||
name: Translation Syntax Check
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup python3
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.6
|
||||
- name: Validating Translation Syntax
|
||||
run: |
|
||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||
files=$(git diff --name-only $GITHUB_BASE_REF)
|
||||
python $GITHUB_WORKSPACE/.github/frappe_linter/translation.py $files
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
pull_request_rules:
|
||||
- name: Automatic merge on CI success and review
|
||||
conditions:
|
||||
- status-success=Codacy/PR Quality Review
|
||||
- status-success=Sider
|
||||
- status-success=Semantic Pull Request
|
||||
- status-success=Travis CI - Pull Request
|
||||
- status-success=security/snyk - package.json (frappe)
|
||||
|
|
@ -14,7 +14,7 @@ pull_request_rules:
|
|||
method: merge
|
||||
- name: Automatic squash on CI success and review
|
||||
conditions:
|
||||
- status-success=Codacy/PR Quality Review
|
||||
- status-success=Sider
|
||||
- status-success=Semantic Pull Request
|
||||
- status-success=Travis CI - Pull Request
|
||||
- status-success=security/snyk - package.json (frappe)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"baseUrl": "http://test_site_ui:8000",
|
||||
"projectId": "92odwv",
|
||||
"adminPassword": "admin"
|
||||
"adminPassword": "admin",
|
||||
"defaultCommandTimeout": 10000,
|
||||
"pageLoadTimeout": 15000
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,23 @@
|
|||
from __future__ import unicode_literals, absolute_import, print_function
|
||||
# imports - standard imports
|
||||
import atexit
|
||||
import compileall
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# imports - third party imports
|
||||
import click
|
||||
import hashlib, os, sys, compileall, re
|
||||
|
||||
# imports - module imports
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.commands import pass_context, get_site
|
||||
from frappe.commands import get_site, pass_context
|
||||
from frappe.commands.scheduler import _is_scheduler_enabled
|
||||
from frappe.installer import update_site_config
|
||||
from frappe.utils import touch_file, get_site_path
|
||||
from six import text_type
|
||||
from frappe.utils import get_site_path, touch_file
|
||||
|
||||
|
||||
@click.command('new-site')
|
||||
@click.argument('site')
|
||||
|
|
@ -68,32 +78,33 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N
|
|||
|
||||
make_site_dirs()
|
||||
|
||||
installing = None
|
||||
try:
|
||||
installing = touch_file(get_site_path('locks', 'installing.lock'))
|
||||
installing = touch_file(get_site_path('locks', 'installing.lock'))
|
||||
atexit.register(_new_site_cleanup, site, mariadb_root_username, mariadb_root_password)
|
||||
|
||||
install_db(root_login=mariadb_root_username, root_password=mariadb_root_password,
|
||||
db_name=db_name, admin_password=admin_password, verbose=verbose,
|
||||
source_sql=source_sql, force=force, reinstall=reinstall, db_password=db_password, db_type=db_type, db_host=db_host, db_port=db_port, no_mariadb_socket=no_mariadb_socket)
|
||||
install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name,
|
||||
admin_password=admin_password, verbose=verbose, source_sql=source_sql, force=force, reinstall=reinstall,
|
||||
db_password=db_password, db_type=db_type, db_host=db_host, db_port=db_port, no_mariadb_socket=no_mariadb_socket)
|
||||
apps_to_install = ['frappe'] + (frappe.conf.get("install_apps") or []) + (list(install_apps) or [])
|
||||
for app in apps_to_install:
|
||||
_install_app(app, verbose=verbose, set_as_patched=not source_sql)
|
||||
|
||||
apps_to_install = ['frappe'] + (frappe.conf.get("install_apps") or []) + (list(install_apps) or [])
|
||||
for app in apps_to_install:
|
||||
_install_app(app, verbose=verbose, set_as_patched=not source_sql)
|
||||
os.remove(installing)
|
||||
|
||||
frappe.utils.scheduler.toggle_scheduler(enable_scheduler)
|
||||
frappe.db.commit()
|
||||
frappe.utils.scheduler.toggle_scheduler(enable_scheduler)
|
||||
frappe.db.commit()
|
||||
|
||||
scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
|
||||
print("*** Scheduler is", scheduler_status, "***")
|
||||
scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
|
||||
print("*** Scheduler is", scheduler_status, "***")
|
||||
|
||||
except frappe.exceptions.ImproperDBConfigurationError:
|
||||
_drop_site(site, mariadb_root_username, mariadb_root_password, force=True)
|
||||
def _new_site_cleanup(site, mariadb_root_username, mariadb_root_password):
|
||||
installing = get_site_path('locks', 'installing.lock')
|
||||
|
||||
finally:
|
||||
if installing and os.path.exists(installing):
|
||||
os.remove(installing)
|
||||
if installing and os.path.exists(installing):
|
||||
if mariadb_root_password:
|
||||
_drop_site(site, mariadb_root_username, mariadb_root_password, force=True)
|
||||
shutil.rmtree(site)
|
||||
|
||||
frappe.destroy()
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('restore')
|
||||
@click.argument('sql-file-path')
|
||||
|
|
@ -317,10 +328,18 @@ def backup(context, with_files=False, backup_path_db=None, backup_path_files=Non
|
|||
"Backup"
|
||||
from frappe.utils.backups import scheduled_backup
|
||||
verbose = context.verbose
|
||||
exit_code = 0
|
||||
for site in context.sites:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, backup_path_private_files=backup_path_private_files, force=True)
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, backup_path_private_files=backup_path_private_files, force=True)
|
||||
except Exception as e:
|
||||
if verbose:
|
||||
print("Backup failed for {0}. Database or site_config.json may be corrupted".format(site))
|
||||
exit_code = 1
|
||||
continue
|
||||
|
||||
if verbose:
|
||||
from frappe.utils import now
|
||||
print("database backup taken -", odb.backup_path_db, "- on", now())
|
||||
|
|
@ -329,6 +348,7 @@ def backup(context, with_files=False, backup_path_db=None, backup_path_files=Non
|
|||
print("private files backup taken -", odb.backup_path_private_files, "- on", now())
|
||||
|
||||
frappe.destroy()
|
||||
sys.exit(exit_code)
|
||||
|
||||
@click.command('remove-from-installed-apps')
|
||||
@click.argument('app')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -608,8 +608,7 @@ def get_local_image(file_url):
|
|||
try:
|
||||
image = Image.open(file_path)
|
||||
except IOError:
|
||||
frappe.msgprint(_("Unable to read file format for {0}").format(file_url))
|
||||
raise
|
||||
frappe.msgprint(_("Unable to read file format for {0}").format(file_url), raise_exception=True)
|
||||
|
||||
content = None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:language_code",
|
||||
"creation": "2014-08-22 16:12:17.249590",
|
||||
|
|
@ -41,7 +42,9 @@
|
|||
}
|
||||
],
|
||||
"icon": "fa fa-globe",
|
||||
"modified": "2019-07-19 16:32:12.652550",
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-16 22:11:33.066852",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Language",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"field_order": [
|
||||
"stopped",
|
||||
"method",
|
||||
"server_script",
|
||||
"frequency",
|
||||
"cron_format",
|
||||
"last_execution",
|
||||
|
|
@ -63,6 +64,14 @@
|
|||
"options": "All\nHourly\nHourly Long\nDaily\nDaily Long\nWeekly\nWeekly Long\nMonthly\nMonthly Long\nCron\nYearly\nAnnual",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "server_script",
|
||||
"fieldtype": "Link",
|
||||
"label": "Server Script",
|
||||
"options": "Server Script",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
|
|
@ -72,7 +81,7 @@
|
|||
"link_fieldname": "scheduled_job_type"
|
||||
}
|
||||
],
|
||||
"modified": "2019-12-09 11:10:21.259929",
|
||||
"modified": "2020-04-05 17:27:33.480562",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Scheduled Job Type",
|
||||
|
|
|
|||
|
|
@ -70,7 +70,12 @@ class ScheduledJobType(Document):
|
|||
self.scheduler_log = None
|
||||
try:
|
||||
self.log_status('Start')
|
||||
frappe.get_attr(self.method)()
|
||||
if self.server_script:
|
||||
script_name = frappe.db.get_value("Server Script", self.server_script)
|
||||
if script_name:
|
||||
frappe.get_doc('Server Script', script_name).execute_scheduled_method()
|
||||
else:
|
||||
frappe.get_attr(self.method)()
|
||||
frappe.db.commit()
|
||||
self.log_status('Complete')
|
||||
except Exception:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,45 @@
|
|||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Server Script', {
|
||||
// refresh: function(frm) {
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.script_type === 'Scheduler Event' && !frm.doc.disabled){
|
||||
frm.add_custom_button('Schedule Script', function() {
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: "Schedule Script Execution",
|
||||
fields: [
|
||||
{
|
||||
fieldname: "event_type",
|
||||
label: __('Select Event Type'),
|
||||
fieldtype: "Select",
|
||||
options: "All\nHourly\nDaily\nWeekly\nMonthly\nYearly\nHourly Long\nDaily Long\nWeekly Long\nMonthly Long"
|
||||
},
|
||||
],
|
||||
primary_action_label: __('Schedule Script'),
|
||||
primary_action: () => {
|
||||
d.get_primary_btn().attr('disabled', true);
|
||||
var data = d.get_values();
|
||||
d.hide();
|
||||
if(data) {
|
||||
frm.events.schedule_script(frm, data);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
d.show();
|
||||
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
schedule_script(frm, data){
|
||||
frm.call({
|
||||
method: "frappe.core.doctype.server_script.server_script.setup_scheduler_events",
|
||||
args: {
|
||||
'script_name': frm.doc.name,
|
||||
'frequency': data.event_type
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// }
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Script Type",
|
||||
"options": "DocType Event\nAPI",
|
||||
"options": "DocType Event\nScheduler Event\nAPI",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2019-12-17 12:55:07.389775",
|
||||
"modified": "2020-04-06 11:24:38.161555",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Server Script",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.safe_exec import safe_exec
|
||||
from frappe import _
|
||||
|
||||
|
||||
class ServerScript(Document):
|
||||
@staticmethod
|
||||
|
|
@ -31,3 +33,39 @@ class ServerScript(Document):
|
|||
# execute event
|
||||
safe_exec(self.script, None, dict(doc = doc))
|
||||
|
||||
def execute_scheduled_method(self):
|
||||
if self.script_type == 'Scheduler Event':
|
||||
safe_exec(self.script)
|
||||
else:
|
||||
# wrong report type!
|
||||
raise frappe.DoesNotExistError
|
||||
|
||||
@frappe.whitelist()
|
||||
def setup_scheduler_events(script_name, frequency):
|
||||
method = frappe.scrub(script_name) + '_' + frequency.lower()
|
||||
scheduled_script = frappe.db.get_value('Scheduled Job Type',
|
||||
dict(method=method))
|
||||
|
||||
if not scheduled_script:
|
||||
doc = frappe.get_doc(dict(
|
||||
doctype = 'Scheduled Job Type',
|
||||
method = method,
|
||||
frequency = frequency,
|
||||
server_script = script_name
|
||||
))
|
||||
|
||||
doc.insert()
|
||||
|
||||
frappe.msgprint(_('Enabled scheduled execution for script {0}').format(script_name))
|
||||
|
||||
else:
|
||||
doc = frappe.get_doc('Scheduled Job Type', scheduled_script)
|
||||
doc.update(dict(
|
||||
doctype = 'Scheduled Job Type',
|
||||
method = method,
|
||||
frequency = frequency,
|
||||
server_script = script_name
|
||||
))
|
||||
doc.save()
|
||||
|
||||
frappe.msgprint(_('Scheduled execution for script {0} has updated').format(script_name))
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ def get_server_script_map():
|
|||
script_map.setdefault(script.reference_doctype, {}).setdefault(script.doctype_event, []).append(script.name)
|
||||
else:
|
||||
script_map.setdefault('_api', {})[script.api_method] = script.name
|
||||
|
||||
frappe.cache().set_value('server_script_map', script_map)
|
||||
|
||||
return script_map
|
||||
return script_map
|
||||
|
|
@ -208,9 +208,11 @@ class CustomizeForm(Document):
|
|||
self.validate_fieldtype_change(df, meta_df[0].get(property), df.get(property))
|
||||
|
||||
elif property == "allow_on_submit" and df.get(property):
|
||||
frappe.msgprint(_("Row {0}: Not allowed to enable Allow on Submit for standard fields")\
|
||||
.format(df.idx))
|
||||
continue
|
||||
if not frappe.db.get_value("DocField",
|
||||
{"parent": self.doc_type, "fieldname": df.fieldname}, "allow_on_submit"):
|
||||
frappe.msgprint(_("Row {0}: Not allowed to enable Allow on Submit for standard fields")\
|
||||
.format(df.idx))
|
||||
continue
|
||||
|
||||
elif property == "reqd" and \
|
||||
((frappe.db.get_value("DocField",
|
||||
|
|
@ -369,7 +371,12 @@ class CustomizeForm(Document):
|
|||
for allowed_changes in allowed_fieldtype_change:
|
||||
if (old_value in allowed_changes and new_value in allowed_changes):
|
||||
allowed = True
|
||||
if frappe.db.type_map.get(old_value)[1] > frappe.db.type_map.get(new_value)[1]:
|
||||
old_value_length = cint(frappe.db.type_map.get(old_value)[1])
|
||||
new_value_length = cint(frappe.db.type_map.get(new_value)[1])
|
||||
|
||||
# Ignore fieldtype check validation if new field type has unspecified maxlength
|
||||
# Changes like DATA to TEXT, where new_value_lenth equals 0 will not be validated
|
||||
if new_value_length and (old_value_length > new_value_length):
|
||||
self.check_length_for_fieldtypes.append({'df': df, 'old_value': old_value})
|
||||
self.validate_fieldtype_length()
|
||||
else:
|
||||
|
|
@ -381,7 +388,7 @@ class CustomizeForm(Document):
|
|||
def validate_fieldtype_length(self):
|
||||
for field in self.check_length_for_fieldtypes:
|
||||
df = field.get('df')
|
||||
max_length = frappe.db.type_map.get(df.fieldtype)[1]
|
||||
max_length = cint(frappe.db.type_map.get(df.fieldtype)[1])
|
||||
fieldname = df.fieldname
|
||||
docs = frappe.db.sql('''
|
||||
SELECT name, {fieldname}, LENGTH({fieldname}) AS len
|
||||
|
|
|
|||
|
|
@ -506,9 +506,13 @@ class BaseDocument(object):
|
|||
fetch_from_fieldname = _df.fetch_from.split('.')[-1]
|
||||
value = values[fetch_from_fieldname]
|
||||
if _df.fieldtype == 'Small Text' or _df.fieldtype == 'Text' or _df.fieldtype == 'Data':
|
||||
fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname)
|
||||
fetch_from_ft = fetch_from_df.get('fieldtype')
|
||||
if fetch_from_fieldname in default_fields:
|
||||
from frappe.model.meta import get_default_df
|
||||
fetch_from_df = get_default_df(fetch_from_fieldname)
|
||||
else:
|
||||
fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname)
|
||||
|
||||
fetch_from_ft = fetch_from_df.get('fieldtype')
|
||||
if fetch_from_ft == 'Text Editor' and value:
|
||||
value = unescape_html(strip_html(value))
|
||||
setattr(self, _df.fieldname, value)
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ def check_permission_and_not_submitted(doc):
|
|||
|
||||
# check if submitted
|
||||
if doc.docstatus == 1:
|
||||
frappe.msgprint(_("{0} {1}: Submitted Record cannot be deleted.").format(_(doc.doctype), doc.name),
|
||||
frappe.msgprint(_("{0} {1}: Submitted Record cannot be deleted. You must {2} Cancel {3} it first.").format(_(doc.doctype), doc.name, "<a href='https://docs.erpnext.com//docs/user/manual/en/setting-up/articles/delete-submitted-document' target='_blank'>", "</a>"),
|
||||
raise_exception=True)
|
||||
|
||||
def check_if_doc_is_linked(doc, method="Delete"):
|
||||
|
|
|
|||
|
|
@ -272,3 +272,4 @@ execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Account')
|
|||
execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings')
|
||||
frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats
|
||||
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders()
|
||||
frappe.patches.v13_0.website_theme_custom_scss
|
||||
|
|
|
|||
0
frappe/patches/v13_0/__init__.py
Normal file
0
frappe/patches/v13_0/__init__.py
Normal file
10
frappe/patches/v13_0/website_theme_custom_scss.py
Normal file
10
frappe/patches/v13_0/website_theme_custom_scss.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('Website Theme')
|
||||
for theme in frappe.get_all('Website Theme'):
|
||||
doc = frappe.get_doc('Website Theme', theme.name)
|
||||
if not doc.custom_scss and doc.theme_scss:
|
||||
# move old theme to new theme
|
||||
doc.custom_scss = doc.theme_scss
|
||||
doc.save()
|
||||
|
|
@ -147,7 +147,7 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
|||
[{ 'color': [] }, { 'background': [] }],
|
||||
['blockquote', 'code-block'],
|
||||
['link', 'image'],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }, { 'list': 'check' }],
|
||||
[{ 'align': [] }],
|
||||
[{ 'indent': '-1'}, { 'indent': '+1' }],
|
||||
[{'table': [
|
||||
|
|
|
|||
|
|
@ -125,8 +125,9 @@ frappe.ui.form.Dashboard = Class.extend({
|
|||
},
|
||||
|
||||
format_percent: function(title, percent) {
|
||||
var width = cint(percent) < 1 ? 1 : cint(percent);
|
||||
var progress_class = "progress-bar-success";
|
||||
const percentage = cint(percent);
|
||||
const width = percentage < 0 ? 100 : percentage;
|
||||
const progress_class = percentage < 0 ? "progress-bar-danger" : "progress-bar-success";
|
||||
|
||||
return [{
|
||||
title: title,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
remove_empty_rows();
|
||||
|
||||
$(frm.wrapper).addClass('validated-form');
|
||||
if (frm.is_dirty() && check_mandatory()) {
|
||||
if ((action !== 'Save' || frm.is_dirty()) && check_mandatory()) {
|
||||
_call({
|
||||
method: "frappe.desk.form.save.savedocs",
|
||||
args: { doc: frm.doc, action: action },
|
||||
|
|
|
|||
|
|
@ -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 + '`';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +1,71 @@
|
|||
// moment strings for translation
|
||||
function prettyDate(date, mini) {
|
||||
if (!date) return '';
|
||||
|
||||
function prettyDate(time, mini) {
|
||||
if (!time) {
|
||||
time = new Date();
|
||||
}
|
||||
if ('moment' in window) { // use frappe.ImportError ;)
|
||||
let ret;
|
||||
if (frappe.sys_defaults && frappe.sys_defaults.time_zone) {
|
||||
ret = moment.tz(time, frappe.sys_defaults.time_zone).locale(frappe.boot.lang).fromNow(mini);
|
||||
} else {
|
||||
ret = moment(time).locale(frappe.boot.lang).fromNow(mini);
|
||||
}
|
||||
if (mini) {
|
||||
if (ret === moment().locale(frappe.boot.lang).fromNow(mini)) {
|
||||
ret = __("now");
|
||||
} else {
|
||||
var parts = ret.split(" ");
|
||||
if (parts.length > 1) {
|
||||
if (parts[0] === "a" || parts[0] === "an") {
|
||||
parts[0] = 1;
|
||||
}
|
||||
if (parts[1].substr(0, 2) === "mo") {
|
||||
ret = parts[0] + " M";
|
||||
} else {
|
||||
ret = parts[0] + " " + parts[1].substr(0, 1);
|
||||
}
|
||||
}
|
||||
if (typeof (date) == "string")
|
||||
date = new Date((date || "").replace(/-/g, "/").replace(/[TZ]/g, " ").replace(/\.[0-9]*/, ""));
|
||||
|
||||
let diff = (((new Date()).getTime() - date.getTime()) / 1000);
|
||||
let day_diff = Math.floor(diff / 86400);
|
||||
|
||||
if (isNaN(day_diff) || day_diff < 0) return '';
|
||||
|
||||
if (mini) {
|
||||
// Return short format of time difference
|
||||
if (day_diff == 0) {
|
||||
if (diff < 60) {
|
||||
return __("Now");
|
||||
} else if (diff < 3600) {
|
||||
return __("{0} m", [Math.floor(diff / 60)]);
|
||||
} else if (diff < 86400) {
|
||||
return __("{0} h", [Math.floor(diff / 3600)]);
|
||||
}
|
||||
} else {
|
||||
if (day_diff < 7) {
|
||||
return __("{0} D", [day_diff]);
|
||||
} else if (day_diff < 31) {
|
||||
return __("{0} W", [Math.ceil(day_diff / 7)]);
|
||||
} else if (day_diff < 365) {
|
||||
return __("{0} M", [Math.ceil(day_diff / 30)]);
|
||||
} else {
|
||||
return __("{0} Y", [Math.ceil(day_diff / 365)]);
|
||||
}
|
||||
ret = ret.substr(0, 5);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
if (!time) return '';
|
||||
var date = time;
|
||||
if (typeof (time) == "string")
|
||||
date = new Date((time || "").replace(/-/g, "/").replace(/[TZ]/g, " ").replace(/\.[0-9]*/, ""));
|
||||
|
||||
var diff = (((new Date()).getTime() - date.getTime()) / 1000),
|
||||
day_diff = Math.floor(diff / 86400);
|
||||
|
||||
if (isNaN(day_diff) || day_diff < 0)
|
||||
return '';
|
||||
|
||||
var when = day_diff == 0 && (
|
||||
diff < 60 && __("just now") ||
|
||||
diff < 120 && __("1 minute ago") ||
|
||||
diff < 3600 && __("{0} minutes ago", [Math.floor(diff / 60)]) ||
|
||||
diff < 7200 && __("1 hour ago") ||
|
||||
diff < 86400 && ("{0} hours ago", [Math.floor(diff / 3600)])) ||
|
||||
day_diff == 1 && __("Yesterday") ||
|
||||
day_diff < 7 && __("{0} days ago", day_diff) ||
|
||||
day_diff < 31 && __("{0} weeks ago", [Math.ceil(day_diff / 7)]) ||
|
||||
day_diff < 365 && __("{0} months ago", [Math.ceil(day_diff / 30)]) ||
|
||||
__("> {0} year(s) ago", [Math.floor(day_diff / 365)]);
|
||||
|
||||
return when;
|
||||
// Return long format of time difference
|
||||
if (day_diff == 0) {
|
||||
if (diff < 60) {
|
||||
return __("Just now");
|
||||
} else if (diff < 120) {
|
||||
return __("1 minute ago");
|
||||
} else if (diff < 3600) {
|
||||
return __("{0} minutes ago", [Math.floor(diff / 60)]);
|
||||
} else if (diff < 7200) {
|
||||
return __("1 hour ago");
|
||||
} else if (diff < 86400) {
|
||||
return __("{0} hours ago", [Math.floor(diff / 3600)]);
|
||||
}
|
||||
} else {
|
||||
if (day_diff == 1) {
|
||||
return __("Yesterday");
|
||||
} else if (day_diff < 7) {
|
||||
return __("{0} days ago", [day_diff]);
|
||||
} else if (day_diff < 14) {
|
||||
return __("1 week ago");
|
||||
} else if (day_diff < 31) {
|
||||
return __("{0} weeks ago", [Math.ceil(day_diff / 7)]);
|
||||
} else if (day_diff < 62) {
|
||||
return __("1 month ago");
|
||||
} else if (day_diff < 365) {
|
||||
return __("{0} months ago", [Math.ceil(day_diff / 30)]);
|
||||
} else if (day_diff < 730) {
|
||||
return __("1 year ago");
|
||||
} else {
|
||||
return __("{0} years ago", [Math.ceil(day_diff / 365)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
frappe.provide("frappe.datetime");
|
||||
window.comment_when = function(datetime, mini) {
|
||||
var timestamp = frappe.datetime.str_to_user ?
|
||||
|
|
|
|||
|
|
@ -675,7 +675,9 @@ Object.assign(frappe.utils, {
|
|||
return __(frappe.utils.to_title_case(route[0], true));
|
||||
},
|
||||
report_column_total: function(values, column, type) {
|
||||
if (values.length > 0) {
|
||||
if (column.column.disable_total) {
|
||||
return '';
|
||||
} else if (values.length > 0) {
|
||||
if (column.column.fieldtype == "Percent" || type === "mean") {
|
||||
return values.reduce((a, b) => a + flt(b)) / values.length;
|
||||
} else if (column.column.fieldtype == "Int") {
|
||||
|
|
|
|||
|
|
@ -203,8 +203,8 @@ class DesktopPage {
|
|||
this.allow_customization && this.make_customization_link();
|
||||
|
||||
let create_shortcuts_and_cards = () => {
|
||||
this.data.shortcuts.items.length && this.make_shortcuts();
|
||||
this.data.cards.items.length && this.make_cards();
|
||||
this.make_shortcuts();
|
||||
this.make_cards();
|
||||
|
||||
if (this.allow_customization) {
|
||||
// Move the widget group up to align with labels if customization is allowed
|
||||
|
|
@ -212,7 +212,7 @@ class DesktopPage {
|
|||
}
|
||||
};
|
||||
|
||||
if (!this.sections["onboarding"] && this.data.charts.items.length) {
|
||||
if (!this.sections["onboarding"]) {
|
||||
this.make_charts().then(() => {
|
||||
create_shortcuts_and_cards();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
@ -113,8 +113,9 @@ frappe.ready(function() {
|
|||
df.only_select = true;
|
||||
}
|
||||
if (["Attach", "Attach Image"].includes(df.fieldtype)) {
|
||||
if (!df.options)
|
||||
if (typeof df.options !== "object") {
|
||||
df.options = {};
|
||||
}
|
||||
df.options.disable_file_browser = true;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ details > summary {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
details > summary:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.text-color {
|
||||
color: @text-color !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,23 @@
|
|||
@import "multilevel-dropdown";
|
||||
@import "website-image";
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
|
||||
> div {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.navbar.bg-dark {
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ class TestDBUpdate(unittest.TestCase):
|
|||
frappe.reload_doctype('User', force=True)
|
||||
frappe.model.meta.trim_tables('User')
|
||||
make_property_setter(doctype, 'bio', 'fieldtype', 'Text', 'Data')
|
||||
make_property_setter(doctype, 'middle_name', 'fieldtype', 'Data', 'Text')
|
||||
make_property_setter(doctype, 'enabled', 'default', '1', 'Int')
|
||||
|
||||
frappe.db.updatedb(doctype)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ apps/frappe/frappe/templates/emails/download_data.html,We have received a reques
|
|||
DocType: System Settings,"If enabled, the password strength will be enforced based on the Minimum Password Score value. A value of 2 being medium strong and 4 being very strong.","Falls diese Option aktiviert ist, wird die Passwortstärke auf der Grundlage des Minimum Password Score Wertes erzwungen. Ein Wert von 2 ist mittelstark und 4 sehr stark."
|
||||
DocType: About Us Settings,"""Team Members"" or ""Management""",„Teammitglieder“ oder „Management“
|
||||
apps/frappe/frappe/core/doctype/doctype/doctype.py,Default for 'Check' type of field must be either '0' or '1',Standard für 'Prüfen'-Feldtyp muss entweder '0' oder '1' sein
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,Yesterday,Gestern
|
||||
DocType: Contact,Designation,Bezeichnung
|
||||
apps/frappe/frappe/email/doctype/email_account/email_account.py,Automatic Linking can be activated only for one Email Account.,Die automatische Verknüpfung kann nur für ein E-Mail-Konto aktiviert werden.
|
||||
DocType: Test Runner,Test Runner,Tester
|
||||
|
|
@ -120,7 +119,6 @@ DocType: Dashboard Chart,Timespan,Zeitspanne
|
|||
apps/frappe/frappe/public/js/frappe/file_uploader/FileUploader.vue,Web Link,Weblink
|
||||
DocType: Deleted Document,Restored,Restauriert
|
||||
apps/frappe/frappe/public/js/frappe/form/print.js,"Error connecting to QZ Tray Application...<br><br> You need to have QZ Tray application installed and running, to use the Raw Print feature.<br><br><a href=""https://qz.io/download/"" target=""_blank"">Click here to Download and install QZ Tray</a>.<br> <a href=""https://erpnext.com/docs/user/manual/en/setting-up/print/raw-printing"" target=""_blank"">Click here to learn more about Raw Printing</a>.","Fehler beim Verbinden mit der QZ-Tray-Anwendung ... <br><br> Sie müssen die QZ Tray-Anwendung installiert haben und ausführen, um die Raw Print-Funktion verwenden zu können. <br><br> <a href=""https://qz.io/download/"" target=""_blank"">Klicken Sie hier, um QZ Tray herunterzuladen und zu installieren</a> . <br> <a href=""https://erpnext.com/docs/user/manual/en/setting-up/print/raw-printing"" target=""_blank"">Klicken Sie hier, um mehr über den Rohdruck zu erfahren</a> ."
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,1 minute ago,Vor 1 Minute
|
||||
apps/frappe/frappe/core/page/permission_manager/permission_manager_help.html,"Apart from System Manager, roles with Set User Permissions right can set permissions for other users for that Document Type.",Zusätzlich zum System-Manager können Rollen mit der Erlaubnis Benutzer anzulegen Berechtigungen für andere Nutzer für diesen Dokumententyp setzen.
|
||||
apps/frappe/frappe/website/doctype/website_theme/website_theme.js,Configure Theme,Thema konfigurieren
|
||||
DocType: Company History,Company History,Unternehmensgeschichte
|
||||
|
|
@ -971,7 +969,6 @@ apps/frappe/frappe/public/js/frappe/list/list_view.js,Share URL,URL teilen
|
|||
DocType: System Settings,Allow Consecutive Login Attempts ,Erlaube aufeinanderfolgende Login-Versuche
|
||||
apps/frappe/frappe/templates/pages/integrations/stripe_checkout.html,An error occured during the payment process. Please contact us.,Während des Bezahlvorgangs ist ein Fehler aufgetreten. Bitte kontaktieren Sie uns.
|
||||
DocType: Onboarding Slide,If Slide Type is Create or Settings there should be a 'create_onboarding_docs' method in the {ref_doctype}.py file bound to be executed after the slide is completed.,"Wenn der Folientyp "Erstellen" oder "Einstellungen" ist, sollte die Methode "create_onboarding_docs" in der Datei "{ref_doctype} .py" enthalten sein, die nach Abschluss der Folie ausgeführt werden soll."
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} days ago,vor {0} Tag(en)
|
||||
DocType: Email Account,Awaiting Password,Warte auf Passwort
|
||||
DocType: Address,Address Line 1,Adresse Zeile 1
|
||||
apps/frappe/frappe/public/js/frappe/ui/filters/filter.js,Not Descendants Of,Nicht Nachkommen von
|
||||
|
|
@ -1360,7 +1357,6 @@ apps/frappe/frappe/social/doctype/energy_point_rule/energy_point_rule.py,Referen
|
|||
DocType: PayPal Settings,PayPal Settings,PayPal-Einstellungen
|
||||
apps/frappe/frappe/core/page/permission_manager/permission_manager.js,Select Document Type,Dokumenttyp auswählen
|
||||
apps/frappe/frappe/utils/nestedset.py,Cannot delete {0} as it has child nodes,"{0} kann nicht gelöscht werden, da es Unterknoten gibt"
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} minutes ago,vor {0} Minute(n)
|
||||
apps/frappe/frappe/automation/doctype/assignment_rule/assignment_rule.py,Assignment Day {0} has been repeated.,Der Zuordnungstag {0} wurde wiederholt.
|
||||
DocType: Kanban Board Column,lightblue,hellblau
|
||||
apps/frappe/frappe/integrations/doctype/webhook/webhook.py,Same Field is entered more than once,Gleiches Feld wird mehrmals eingegeben
|
||||
|
|
@ -1787,7 +1783,6 @@ DocType: Notification Log,Assignment,Zuordnung
|
|||
DocType: Notification,Slack Channel,Slack-Kanal
|
||||
DocType: About Us Team Member,Image Link,Bildverknüpfung
|
||||
DocType: Auto Email Report,Report Filters,Berichtsfilter
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,now,jetzt
|
||||
DocType: Workflow State,step-backward,Schritt zurück
|
||||
apps/frappe/frappe/utils/boilerplate.py,{app_title},{app_title}
|
||||
apps/frappe/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py,Please set Dropbox access keys in your site config,Bitte Dropbox-Zugriffsdaten in den Einstellungen der Seite setzen
|
||||
|
|
@ -2520,7 +2515,6 @@ apps/frappe/frappe/public/js/frappe/form/print.js,QZ Tray Connection Active!,QZ-
|
|||
DocType: Contact Us Settings,Settings for Contact Us Page,Einstellungen Kontakt
|
||||
DocType: Server Script,Script Type,Skripttyp
|
||||
DocType: Print Settings,Enable Print Server,Aktivieren Sie den Druckserver
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} weeks ago,vor {0} Woche(n)
|
||||
DocType: Email Account,Footer,Fußzeile
|
||||
apps/frappe/frappe/config/integrations.py,Authentication,Authentifizierung
|
||||
apps/frappe/frappe/utils/verified_command.py,Invalid Link,Ungültige Verknüpfung
|
||||
|
|
@ -3444,7 +3438,6 @@ apps/frappe/frappe/integrations/doctype/ldap_settings/ldap_settings.py,Please In
|
|||
apps/frappe/frappe/core/doctype/data_import/log_details.html,Row Status,Zeilenstatus
|
||||
DocType: S3 Backup Settings,sa-east-1,sa-east-1
|
||||
DocType: Workflow Transition,Workflow Transition,Workflow-Übergang
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} months ago,vor {0} Monate(n)
|
||||
apps/frappe/frappe/custom/doctype/custom_field/custom_field.py,Custom Fields can only be added to a standard DocType.,Benutzerdefinierte Felder können nur zu einem Standard-DocType hinzugefügt werden.
|
||||
apps/frappe/frappe/public/js/frappe/list/list_sidebar_group_by.js,Created By,Erstellt von
|
||||
apps/frappe/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py,Dropbox Setup,Dropbox-Setup
|
||||
|
|
@ -3500,7 +3493,6 @@ apps/frappe/frappe/website/doctype/website_theme/website_theme.js,Theme Colors,T
|
|||
apps/frappe/frappe/printing/page/print_format_builder/print_format_builder.js,Select a DocType to make a new format,"DocType auswählen, um ein neues Format zu erstellen"
|
||||
apps/frappe/frappe/desk/page/user_profile/user_profile.js,User does not exist,Benutzer existiert nicht
|
||||
apps/frappe/frappe/automation/doctype/auto_repeat/auto_repeat.py,'Recipients' not specified,"Keine ""Empfänger"" angegeben"
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,just now,gerade eben
|
||||
apps/frappe/frappe/public/js/frappe/ui/filters/edit_filter.html,Apply,Anwenden
|
||||
DocType: Footer Item,Policy,Politik
|
||||
apps/frappe/frappe/integrations/utils.py,{0} Settings not found,{0} Einstellungen nicht gefunden
|
||||
|
|
@ -3592,7 +3584,6 @@ DocType: Auto Email Report,Period,Periode
|
|||
apps/frappe/frappe/core/doctype/data_import_beta/data_import_beta.js,About {0} minute remaining,Noch ungefähr {0} Minuten
|
||||
apps/frappe/frappe/www/login.py,Invalid Login Token,Invalid Login Token
|
||||
apps/frappe/frappe/public/js/frappe/chat.js,Discard,Verwerfen
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,1 hour ago,vor 1 Stunde
|
||||
DocType: Website Settings,Home Page,Startseite
|
||||
DocType: Error Snapshot,Parent Error Snapshot,Momentaufnahme des übergeordneten Fehlers
|
||||
apps/frappe/frappe/public/js/frappe/data_import/import_preview.js,Map columns from {0} to fields in {1},Ordnen Sie Spalten von {0} Feldern in {1} zu.
|
||||
|
|
@ -3804,7 +3795,6 @@ DocType: Webhook,on_update_after_submit,on_update_after_submit
|
|||
DocType: System Settings,Allow Login using User Name,Login mit Benutzernamen zulassen
|
||||
apps/frappe/frappe/core/doctype/report/report.js,Enable Report,Bericht aktivieren
|
||||
DocType: DocField,Display Depends On,Anzeige ist abhängig von
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,> {0} year(s) ago,> {0} Jahr (e) her
|
||||
DocType: Social Login Key,API Endpoint,API-Endpunkt
|
||||
DocType: Web Page,Insert Code,Code einfügen
|
||||
DocType: Data Migration Run,Current Mapping Type,Aktueller Kartentyp
|
||||
|
|
@ -4188,3 +4178,23 @@ DocType: DocField,Ignore User Permissions,Ignorieren von Benutzerberechtigungen
|
|||
apps/frappe/frappe/public/js/frappe/web_form/web_form.js,Saved Successfully,Erfolgreich gespeichert
|
||||
apps/frappe/frappe/core/doctype/user/user.py,Please ask your administrator to verify your sign-up,Bitte fragen Sie Ihren Administrator Ihre Anmeldung bis zum überprüfen
|
||||
DocType: Domain Settings,Active Domains,Aktive Domains
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,Now,Jetzt
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} m,{0} m
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} h,{0} h
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} D,{0} T
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} W,{0} W
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} M,{0} M
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} Y,{0} J
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,Just now,Gerade eben
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,1 minute ago,Vor einer Minute
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} minutes ago,Vor {0} Minuten
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,1 hour ago,Vor einer Stunde
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} hours ago,Vor {0} Stunden
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,Yesterday,Gestern
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} days ago,Vor {0} Tagen
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,1 week ago,Vor einer Woche
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} weeks ago,Vor {0} Wochen
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,1 month ago,Vor einem Monat
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} months ago,Vor {0} Monaten
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,1 year ago,Vor einem Jahr
|
||||
apps/frappe/frappe/public/js/frappe/utils/pretty_date.js,{0} years ago,Vor {0} Jahren
|
||||
|
|
|
@ -73,7 +73,7 @@ def enqueue(method, queue='default', timeout=None, event=None,
|
|||
def enqueue_doc(doctype, name=None, method=None, queue='default', timeout=300,
|
||||
now=False, **kwargs):
|
||||
'''Enqueue a method to be run on a document'''
|
||||
enqueue('frappe.utils.background_jobs.run_doc_method', doctype=doctype, name=name,
|
||||
return enqueue('frappe.utils.background_jobs.run_doc_method', doctype=doctype, name=name,
|
||||
doc_method=method, queue=queue, timeout=timeout, now=now, **kwargs)
|
||||
|
||||
def run_doc_method(doctype, name, doc_method, **kwargs):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -131,9 +131,9 @@ def create_auth_table():
|
|||
frappe.db.create_auth_table()
|
||||
|
||||
def encrypt(pwd):
|
||||
if len(pwd) > 100:
|
||||
# encrypting > 100 chars will lead to truncation
|
||||
frappe.throw(_('Password cannot be more than 100 characters long'))
|
||||
if len(pwd) > 127:
|
||||
# encrypting > 127 chars will lead to truncation
|
||||
frappe.throw(_('Password cannot be more than 127 characters long'))
|
||||
|
||||
cipher_suite = Fernet(encode(get_encryption_key()))
|
||||
cipher_text = cstr(cipher_suite.encrypt(encode(pwd)))
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from frappe.website.utils import (get_shade, get_toc, get_next_link)
|
|||
from frappe.modules import scrub
|
||||
from frappe.www.printview import get_visible_columns
|
||||
import frappe.exceptions
|
||||
import frappe.integrations.utils
|
||||
|
||||
class ServerScriptNotEnabled(frappe.PermissionError): pass
|
||||
|
||||
|
|
@ -79,6 +80,8 @@ def get_safe_globals():
|
|||
user=user,
|
||||
csrf_token=frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else ''
|
||||
),
|
||||
make_get_request = frappe.integrations.utils.make_get_request,
|
||||
make_post_request = frappe.integrations.utils.make_post_request,
|
||||
socketio_port=frappe.conf.socketio_port,
|
||||
get_hooks=frappe.get_hooks,
|
||||
),
|
||||
|
|
|
|||
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