Merge branch 'develop' into data-options-child-table
This commit is contained in:
commit
65c29161b0
108 changed files with 1678 additions and 1012 deletions
|
|
@ -78,6 +78,7 @@
|
|||
"has_common": true,
|
||||
"has_words": true,
|
||||
"validate_email": true,
|
||||
"validate_name": true,
|
||||
"validate_phone": true,
|
||||
"get_number_format": true,
|
||||
"format_number": true,
|
||||
|
|
|
|||
11
.travis.yml
11
.travis.yml
|
|
@ -7,14 +7,19 @@ addons:
|
|||
- test_site_producer
|
||||
mariadb: 10.3
|
||||
postgresql: 9.5
|
||||
chrome: stable
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
cache:
|
||||
- pip
|
||||
- npm
|
||||
- yarn
|
||||
pip: true
|
||||
npm: true
|
||||
yarn: true
|
||||
directories:
|
||||
# we also need to cache folder with Cypress binary
|
||||
# https://docs.cypress.io/guides/guides/continuous-integration.html#Caching
|
||||
- ~/.cache
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
context('Depends On', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
return cy.new_form('Test Depends On');
|
||||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ context('FileUploader', () => {
|
|||
open_upload_dialog();
|
||||
|
||||
cy.get_open_dialog().find('a:contains("web link")').click();
|
||||
cy.get_open_dialog().find('.file-web-link input').type('https://github.com');
|
||||
cy.get_open_dialog().find('.file-web-link input').type('https://github.com', { delay: 100, force: true });
|
||||
cy.server();
|
||||
cy.route('POST', '/api/method/upload_file').as('upload_file');
|
||||
cy.get_open_dialog().find('.btn-primary').click();
|
||||
|
|
|
|||
|
|
@ -6,14 +6,17 @@ context('Form', () => {
|
|||
return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.visit('/desk#workspace/Website');
|
||||
});
|
||||
it('create a new form', () => {
|
||||
cy.visit('/desk#Form/ToDo/New ToDo 1');
|
||||
cy.fill_field('description', 'this is a test todo', 'Text Editor').blur();
|
||||
cy.get('.page-title').should('contain', 'Not Saved');
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: 'api/method/frappe.desk.form.save.savedocs'
|
||||
}).as('form_save');
|
||||
cy.get('.primary-action').click();
|
||||
cy.wait('@form_save').its('status').should('eq', 200);
|
||||
cy.visit('/desk#List/ToDo');
|
||||
cy.location('hash').should('eq', '#List/ToDo/List');
|
||||
cy.get('h1').should('be.visible').and('contain', 'To Do');
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
|
|||
if (fieldtype === 'Select') {
|
||||
cy.get('@input').select(value);
|
||||
} else {
|
||||
cy.get('@input').type(value, { waitForAnimations: false });
|
||||
cy.get('@input').type(value, { waitForAnimations: false, force: true });
|
||||
}
|
||||
return cy.get('@input');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -219,7 +219,10 @@ class LoginManager:
|
|||
user = frappe.db.get_value("User", filters={"username": user}, fieldname="name") or user
|
||||
|
||||
self.check_if_enabled(user)
|
||||
self.user = self.check_password(user, pwd)
|
||||
if not frappe.form_dict.get('tmp_id'):
|
||||
self.user = self.check_password(user, pwd)
|
||||
else:
|
||||
self.user = user
|
||||
|
||||
def force_user_to_reset_password(self):
|
||||
if not self.user:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"hidden": 0,
|
||||
"label": "Tools",
|
||||
"links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
|
||||
"links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Video\",\n \"name\": \"Video\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Tools",
|
||||
"modified": "2020-04-01 11:24:40.804346",
|
||||
"modified": "2020-04-20 18:21:14.152537",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Tools",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from frappe.utils.change_log import get_versions
|
|||
from frappe.translate import get_lang_dict
|
||||
from frappe.email.inbox import get_email_accounts
|
||||
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
|
||||
from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled
|
||||
from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points
|
||||
from frappe.social.doctype.post.post import frequently_visited_links
|
||||
|
||||
|
|
@ -79,6 +80,7 @@ def get_bootinfo():
|
|||
bootinfo.success_action = get_success_action()
|
||||
bootinfo.update(get_email_accounts(user=frappe.session.user))
|
||||
bootinfo.energy_points_enabled = is_energy_point_enabled()
|
||||
bootinfo.website_tracking_enabled = is_tracking_enabled()
|
||||
bootinfo.points = get_energy_points(frappe.session.user)
|
||||
bootinfo.frequently_visited_links = frequently_visited_links()
|
||||
bootinfo.link_preview_doctypes = get_link_preview_doctypes()
|
||||
|
|
@ -268,4 +270,18 @@ def get_success_action():
|
|||
return frappe.get_all("Success Action", fields=["*"])
|
||||
|
||||
def get_link_preview_doctypes():
|
||||
return [d.name for d in frappe.db.get_all('DocType', {'show_preview_popup': 1})]
|
||||
from frappe.utils import cint
|
||||
|
||||
link_preview_doctypes = [d.name for d in frappe.db.get_all('DocType', {'show_preview_popup': 1})]
|
||||
customizations = frappe.get_all("Property Setter",
|
||||
fields=['doc_type', 'value'],
|
||||
filters={'property': 'show_preview_popup'}
|
||||
)
|
||||
|
||||
for custom in customizations:
|
||||
if not cint(custom.value) and custom.doc_type in link_preview_doctypes:
|
||||
link_preview_doctypes.remove(custom.doc_type)
|
||||
else:
|
||||
link_preview_doctypes.append(custom.doc_type)
|
||||
|
||||
return link_preview_doctypes
|
||||
|
|
|
|||
|
|
@ -522,7 +522,7 @@ def run_ui_tests(context, app, headless=False):
|
|||
password_env = 'CYPRESS_adminPassword={}'.format(admin_password) if admin_password else ''
|
||||
|
||||
# run for headless mode
|
||||
run_or_open = 'run --record --key 4a48f41c-11b3-425b-aa88-c58048fa69eb' if headless else 'open'
|
||||
run_or_open = 'run --browser chrome --record --key 4a48f41c-11b3-425b-aa88-c58048fa69eb' if headless else 'open'
|
||||
command = '{site_env} {password_env} yarn run cypress {run_or_open}'
|
||||
formatted_command = command.format(site_env=site_env, password_env=password_env, run_or_open=run_or_open)
|
||||
frappe.commands.popen(formatted_command, cwd=app_base_path, raise_err=True)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class TestExporter(unittest.TestCase):
|
|||
e = Exporter('Web Page', export_fields='All')
|
||||
csv_array = e.get_csv_array()
|
||||
header = csv_array[0]
|
||||
self.assertEqual(len(header), 24)
|
||||
self.assertEqual(len(header), 28)
|
||||
|
||||
|
||||
def test_exports_selected_fields(self):
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
"label",
|
||||
"fieldtype",
|
||||
"fieldname",
|
||||
"reqd",
|
||||
"precision",
|
||||
"length",
|
||||
"reqd",
|
||||
"search_index",
|
||||
"in_list_view",
|
||||
"in_standard_filter",
|
||||
|
|
@ -453,7 +453,7 @@
|
|||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-15 02:26:03.310781",
|
||||
"modified": "2020-04-19 21:54:13.783908",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -477,7 +477,8 @@ class DocType(Document):
|
|||
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields']))
|
||||
if field_dict:
|
||||
new_field_dicts.append(field_dict[0])
|
||||
remaining_field_names.remove(fieldname)
|
||||
if fieldname in remaining_field_names:
|
||||
remaining_field_names.remove(fieldname)
|
||||
|
||||
for fieldname in remaining_field_names:
|
||||
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields']))
|
||||
|
|
@ -498,7 +499,8 @@ class DocType(Document):
|
|||
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])))
|
||||
if field_dict:
|
||||
new_field_dicts.append(field_dict[0])
|
||||
remaining_field_names.remove(fieldname)
|
||||
if fieldname in remaining_field_names:
|
||||
remaining_field_names.remove(fieldname)
|
||||
|
||||
for fieldname in remaining_field_names:
|
||||
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])))
|
||||
|
|
@ -893,7 +895,7 @@ def validate_fields(meta):
|
|||
field.fetch_from = field.fetch_from.strip('\n').strip()
|
||||
|
||||
def validate_data_field_type(docfield):
|
||||
if docfield.fieldtype == "Data":
|
||||
if docfield.fieldtype == "Data" and not (docfield.oldfieldtype and docfield.oldfieldtype != "Data"):
|
||||
if docfield.options and (docfield.options not in data_field_options):
|
||||
df_str = frappe.bold(_(docfield.label))
|
||||
text_str = _("{0} is an invalid Data field.").format(df_str) + "<br>" * 2 + _("Only Options allowed for Data field are:") + "<br>"
|
||||
|
|
|
|||
|
|
@ -1,46 +1,45 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
"""
|
||||
record of files
|
||||
|
||||
naming for same name files: file.gif, file-1.gif, file-2.gif etc
|
||||
"""
|
||||
|
||||
import frappe
|
||||
import json
|
||||
import os
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import re
|
||||
import hashlib
|
||||
import mimetypes
|
||||
import imghdr
|
||||
import io
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
import requests
|
||||
import requests.exceptions
|
||||
import imghdr
|
||||
from PIL import Image, ImageFile, ImageOps
|
||||
from six import PY2, StringIO, string_types, text_type
|
||||
from six.moves.urllib.parse import quote, unquote
|
||||
|
||||
from frappe.utils import get_hook_method, get_files_path, random_string, encode, cstr, call_hook_method, cint
|
||||
from frappe import _
|
||||
from frappe import conf
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
import frappe
|
||||
from frappe import _, conf
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import strip
|
||||
from PIL import Image, ImageOps
|
||||
from six import StringIO, string_types
|
||||
from six.moves.urllib.parse import unquote, quote
|
||||
from six import text_type, PY2
|
||||
import zipfile
|
||||
from frappe.utils import call_hook_method, cint, cstr, encode, get_files_path, get_hook_method, random_string, strip
|
||||
|
||||
|
||||
class MaxFileSizeReachedError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class FolderNotEmpty(frappe.ValidationError): pass
|
||||
class FolderNotEmpty(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
exclude_from_linked_with = True
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
|
||||
|
||||
class File(Document):
|
||||
|
|
@ -697,7 +696,7 @@ def remove_file(fid=None, attached_to_doctype=None, attached_to_name=None, from_
|
|||
|
||||
|
||||
def get_max_file_size():
|
||||
return conf.get('max_file_size') or 10485760
|
||||
return cint(conf.get('max_file_size')) or 10485760
|
||||
|
||||
|
||||
def remove_all(dt, dn, from_delete=False):
|
||||
|
|
@ -714,7 +713,10 @@ def has_permission(doc, ptype=None, user=None):
|
|||
has_access = False
|
||||
user = user or frappe.session.user
|
||||
|
||||
if not doc.is_private or doc.owner == user or user == 'Administrator':
|
||||
if ptype == 'create':
|
||||
has_access = frappe.has_permission('File', 'create', user=user)
|
||||
|
||||
if not doc.is_private or doc.owner in [user, 'Guest'] or user == 'Administrator':
|
||||
has_access = True
|
||||
|
||||
if doc.attached_to_doctype and doc.attached_to_name:
|
||||
|
|
|
|||
|
|
@ -97,47 +97,49 @@ frappe.ui.form.on('User', {
|
|||
});
|
||||
}, __("Password"));
|
||||
|
||||
frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => {
|
||||
if (value === 1 && frm.doc.name != "Administrator") {
|
||||
frm.add_custom_button(__("Reset LDAP Password"), function() {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __("Reset LDAP Password"),
|
||||
fields: [
|
||||
{
|
||||
label: __("New Password"),
|
||||
fieldtype: "Password",
|
||||
fieldname: "new_password",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Confirm New Password"),
|
||||
fieldtype: "Password",
|
||||
fieldname: "confirm_password",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Logout All Sessions"),
|
||||
fieldtype: "Check",
|
||||
fieldname: "logout_sessions"
|
||||
if (frappe.user.has_role("System Manager")) {
|
||||
frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => {
|
||||
if (value === 1 && frm.doc.name != "Administrator") {
|
||||
frm.add_custom_button(__("Reset LDAP Password"), function() {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __("Reset LDAP Password"),
|
||||
fields: [
|
||||
{
|
||||
label: __("New Password"),
|
||||
fieldtype: "Password",
|
||||
fieldname: "new_password",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Confirm New Password"),
|
||||
fieldtype: "Password",
|
||||
fieldname: "confirm_password",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Logout All Sessions"),
|
||||
fieldtype: "Check",
|
||||
fieldname: "logout_sessions"
|
||||
}
|
||||
],
|
||||
primary_action: (values) => {
|
||||
d.hide();
|
||||
if (values.new_password !== values.confirm_password) {
|
||||
frappe.throw(__("Passwords do not match!"));
|
||||
}
|
||||
frappe.call(
|
||||
"frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", {
|
||||
user: frm.doc.email,
|
||||
password: values.new_password,
|
||||
logout: values.logout_sessions
|
||||
});
|
||||
}
|
||||
],
|
||||
primary_action: (values) => {
|
||||
d.hide();
|
||||
if (values.new_password !== values.confirm_password) {
|
||||
frappe.throw(__("Passwords do not match!"));
|
||||
}
|
||||
frappe.call(
|
||||
"frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", {
|
||||
user: frm.doc.email,
|
||||
password: values.new_password,
|
||||
logout: values.logout_sessions
|
||||
});
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
}, __("Password"));
|
||||
}
|
||||
});
|
||||
});
|
||||
d.show();
|
||||
}, __("Password"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frm.add_custom_button(__("Reset OTP Secret"), function() {
|
||||
frappe.call({
|
||||
|
|
|
|||
|
|
@ -551,6 +551,7 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password=
|
|||
|
||||
res = _get_user_for_update_password(key, old_password)
|
||||
if res.get('message'):
|
||||
frappe.local.response.http_status_code = 410
|
||||
return res['message']
|
||||
else:
|
||||
user = res['user']
|
||||
|
|
@ -718,7 +719,7 @@ def _get_user_for_update_password(key, old_password):
|
|||
user = frappe.db.get_value("User", {"reset_password_key": key})
|
||||
if not user:
|
||||
return {
|
||||
'message': _("Cannot Update: Incorrect / Expired Link.")
|
||||
'message': _("The Link specified has either been used before or Invalid")
|
||||
}
|
||||
|
||||
elif old_password:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import unittest
|
|||
|
||||
class TestUserPermission(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql("DELETE FROM `tabUser Permission` WHERE `user`='test_bulk_creation_update@example.com'")
|
||||
frappe.db.sql("""DELETE FROM `tabUser Permission`
|
||||
WHERE `user` in ('test_bulk_creation_update@example.com', 'test_user_perm1@example.com')""")
|
||||
|
||||
def test_default_user_permission_validation(self):
|
||||
user = create_user('test_default_permission@example.com')
|
||||
|
|
@ -20,6 +21,26 @@ class TestUserPermission(unittest.TestCase):
|
|||
param = get_params(user, 'User', perm_user.name, is_default=1)
|
||||
self.assertRaises(frappe.ValidationError, add_user_permissions, param)
|
||||
|
||||
def test_default_user_permission(self):
|
||||
frappe.set_user('Administrator')
|
||||
user = create_user('test_user_perm1@example.com', 'Website Manager')
|
||||
for category in ['general', 'public']:
|
||||
if not frappe.db.exists('Blog Category', category):
|
||||
frappe.get_doc({'doctype': 'Blog Category',
|
||||
'category_name': category, 'title': category}).insert()
|
||||
|
||||
param = get_params(user, 'Blog Category', 'general', is_default=1)
|
||||
add_user_permissions(param)
|
||||
|
||||
param = get_params(user, 'Blog Category', 'public')
|
||||
add_user_permissions(param)
|
||||
|
||||
frappe.set_user('test_user_perm1@example.com')
|
||||
doc = frappe.new_doc("Blog Post")
|
||||
|
||||
self.assertEquals(doc.blog_category, 'general')
|
||||
frappe.set_user('Administrator')
|
||||
|
||||
def test_apply_to_all(self):
|
||||
''' Create User permission for User having access to all applicable Doctypes'''
|
||||
user = create_user('test_bulk_creation_update@example.com')
|
||||
|
|
@ -88,7 +109,7 @@ class TestUserPermission(unittest.TestCase):
|
|||
self.assertIsNone(removed_applicable_second)
|
||||
self.assertEquals(is_created, 1)
|
||||
|
||||
def create_user(email):
|
||||
def create_user(email, role="System Manager"):
|
||||
''' create user with role system manager '''
|
||||
if frappe.db.exists('User', email):
|
||||
return frappe.get_doc('User', email)
|
||||
|
|
@ -96,7 +117,7 @@ def create_user(email):
|
|||
user = frappe.new_doc('User')
|
||||
user.email = email
|
||||
user.first_name = email.split("@")[0]
|
||||
user.add_roles("System Manager")
|
||||
user.add_roles(role)
|
||||
return user
|
||||
|
||||
def get_params(user, doctype, docname, is_default=0, applicable=None):
|
||||
|
|
|
|||
10
frappe/core/doctype/video/test_video.py
Normal file
10
frappe/core/doctype/video/test_video.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 TestVideo(unittest.TestCase):
|
||||
pass
|
||||
8
frappe/core/doctype/video/video.js
Normal file
8
frappe/core/doctype/video/video.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('Video', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
106
frappe/core/doctype/video/video.json
Normal file
106
frappe/core/doctype/video/video.json
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"creation": "2018-10-17 05:47:13.087395",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"provider",
|
||||
"url",
|
||||
"column_break_4",
|
||||
"publish_date",
|
||||
"duration",
|
||||
"section_break_7",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "provider",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Provider",
|
||||
"options": "YouTube\nVimeo",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "url",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "URL",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "publish_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Publish Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "duration",
|
||||
"fieldtype": "Data",
|
||||
"label": "Duration"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-22 12:09:49.057403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Video",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class WebViewItem(Document):
|
||||
class Video(Document):
|
||||
pass
|
||||
|
|
@ -28,6 +28,7 @@ def get_info(show_failed=False):
|
|||
if j.kwargs.get('site')==frappe.local.site:
|
||||
jobs.append({
|
||||
'job_name': j.kwargs.get('kwargs', {}).get('playbook_method') \
|
||||
or j.kwargs.get('kwargs', {}).get('job_type') \
|
||||
or str(j.kwargs.get('job_name')),
|
||||
'status': j.get_status(), 'queue': name,
|
||||
'creation': format_datetime(convert_utc_to_user_timezone(j.created_at)),
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
"in_list_view",
|
||||
"in_standard_filter",
|
||||
"in_global_search",
|
||||
"in_preview",
|
||||
"bold",
|
||||
"report_hide",
|
||||
"search_index",
|
||||
|
|
@ -371,12 +372,18 @@
|
|||
"fieldname": "allow_in_quick_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow in Quick Entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-16 14:52:43.954709",
|
||||
"modified": "2020-04-10 11:57:10.392218",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Field",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
"track_views",
|
||||
"allow_auto_repeat",
|
||||
"allow_import",
|
||||
"show_preview_popup",
|
||||
"image_view",
|
||||
"column_break_5",
|
||||
"title_field",
|
||||
|
|
@ -203,6 +204,12 @@
|
|||
"depends_on": "doc_type",
|
||||
"fieldname": "section_break_23",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_preview_popup",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Preview Popup"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
|
|
@ -210,7 +217,7 @@
|
|||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-27 15:06:35.443861",
|
||||
"modified": "2020-04-10 12:16:01.320411",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ doctype_properties = {
|
|||
'track_views': 'Check',
|
||||
'allow_auto_repeat': 'Check',
|
||||
'allow_import': 'Check',
|
||||
'show_preview_popup': 'Check',
|
||||
'email_append_to': 'Check',
|
||||
'subject_field': 'Data',
|
||||
'sender_field': 'Data'
|
||||
|
|
@ -53,6 +54,7 @@ docfield_properties = {
|
|||
'in_list_view': 'Check',
|
||||
'in_standard_filter': 'Check',
|
||||
'in_global_search': 'Check',
|
||||
'in_preview': 'Check',
|
||||
'bold': 'Check',
|
||||
'hidden': 'Check',
|
||||
'collapsible': 'Check',
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"in_list_view",
|
||||
"in_standard_filter",
|
||||
"in_global_search",
|
||||
"in_preview",
|
||||
"bold",
|
||||
"allow_in_quick_entry",
|
||||
"translatable",
|
||||
|
|
@ -381,12 +382,18 @@
|
|||
"fieldtype": "Code",
|
||||
"label": "Read Only Depends On",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-15 02:26:59.673750",
|
||||
"modified": "2020-04-10 11:58:44.573537",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
if (frm.doc.report_name) {
|
||||
frm.trigger('set_chart_report_filters');
|
||||
}
|
||||
|
||||
if (!frappe.boot.developer_mode) {
|
||||
frm.set_df_property("custom_options", "hidden", 1);
|
||||
}
|
||||
},
|
||||
|
||||
source: function(frm) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"type",
|
||||
"column_break_2",
|
||||
"color",
|
||||
"custom_options",
|
||||
"section_break_10",
|
||||
"last_synced_on"
|
||||
],
|
||||
|
|
@ -124,7 +125,7 @@
|
|||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Type",
|
||||
"options": "Line\nBar\nPercentage\nPie",
|
||||
"options": "Line\nBar\nPercentage\nPie\nDonut",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
|
|
@ -213,10 +214,16 @@
|
|||
"label": "Y Axis",
|
||||
"mandatory_depends_on": "eval:doc.report_name && !doc.is_custom",
|
||||
"options": "Dashboard Chart Field"
|
||||
},
|
||||
{
|
||||
"description": "Ex: \"colors\": [\"#d1d8dd\", \"#ff5858\"]",
|
||||
"fieldname": "custom_options",
|
||||
"fieldtype": "Code",
|
||||
"label": "Custom Options"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-08 18:54:36.739183",
|
||||
"modified": "2020-04-20 23:49:11.389909",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard Chart",
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d
|
|||
to_date = chart.to_date
|
||||
|
||||
timegrain = time_interval or chart.time_interval
|
||||
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json)
|
||||
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json) or []
|
||||
|
||||
# don't include cancelled documents
|
||||
filters.append([chart.document_type, 'docstatus', '<', 2, False])
|
||||
|
|
@ -97,6 +97,10 @@ def create_report_chart(args):
|
|||
_doc = frappe.new_doc('Dashboard Chart')
|
||||
|
||||
_doc.update(args)
|
||||
|
||||
if (args.get("custom_options")):
|
||||
_doc.custom_options = json.dumps(args.get("custom_options"))
|
||||
|
||||
if frappe.db.exists('Dashboard Chart', args.chart_name):
|
||||
args.chart_name = append_number_if_name_exists('Dashboard Chart', args.chart_name)
|
||||
_doc.chart_name = args.chart_name
|
||||
|
|
@ -108,6 +112,7 @@ def create_report_chart(args):
|
|||
@frappe.whitelist()
|
||||
def add_chart_to_dashboard(args):
|
||||
args = frappe.parse_json(args)
|
||||
|
||||
dashboard = frappe.get_doc('Dashboard', args.dashboard)
|
||||
dashboard_link = frappe.new_doc('Dashboard Chart Link')
|
||||
dashboard_link.chart = args.chart_name
|
||||
|
|
@ -362,6 +367,8 @@ class DashboardChart(Document):
|
|||
self.check_required_field()
|
||||
self.check_document_type()
|
||||
|
||||
self.validate_custom_options()
|
||||
|
||||
def check_required_field(self):
|
||||
if not self.document_type:
|
||||
frappe.throw(_("Document type is required to create a dashboard chart"))
|
||||
|
|
@ -378,3 +385,10 @@ class DashboardChart(Document):
|
|||
def check_document_type(self):
|
||||
if frappe.get_meta(self.document_type).issingle:
|
||||
frappe.throw("You cannot create a dashboard chart from single DocTypes")
|
||||
|
||||
def validate_custom_options(self):
|
||||
if self.custom_options:
|
||||
try:
|
||||
json.loads(self.custom_options)
|
||||
except ValueError as error:
|
||||
frappe.throw("Invalid json added in the custom options: %s" % error)
|
||||
|
|
@ -196,8 +196,6 @@ class FormMeta(Meta):
|
|||
self.get("__messages").update(messages, as_value=True)
|
||||
|
||||
def load_dashboard(self):
|
||||
if self.custom:
|
||||
return
|
||||
self.set('__dashboard', self.get_dashboard_data())
|
||||
|
||||
def load_kanban_meta(self):
|
||||
|
|
|
|||
|
|
@ -268,8 +268,9 @@ def get_open_count(doctype, name, items=[]):
|
|||
"count": out,
|
||||
}
|
||||
|
||||
module = frappe.get_meta_module(doctype)
|
||||
if hasattr(module, "get_timeline_data"):
|
||||
out["timeline_data"] = module.get_timeline_data(doctype, name)
|
||||
if not meta.custom:
|
||||
module = frappe.get_meta_module(doctype)
|
||||
if hasattr(module, "get_timeline_data"):
|
||||
out["timeline_data"] = module.get_timeline_data(doctype, name)
|
||||
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ def get_prepared_report_result(report, filters, dn="", user=None):
|
|||
columns = json.loads(doc.columns) if doc.columns else data[0]
|
||||
|
||||
for column in columns:
|
||||
if isinstance(column, dict):
|
||||
if isinstance(column, dict) and column.get("label"):
|
||||
column["label"] = _(column["label"])
|
||||
|
||||
latest_report_data = {
|
||||
|
|
@ -299,6 +299,7 @@ def export_query():
|
|||
_("You can try changing the filters of your report."))
|
||||
return
|
||||
|
||||
data.columns = [col for col in data.columns if isinstance(col, dict) and not col.get('hidden')]
|
||||
columns = get_columns_dict(data.columns)
|
||||
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
|
|
@ -310,7 +311,7 @@ def export_query():
|
|||
frappe.response['type'] = 'binary'
|
||||
|
||||
|
||||
def build_xlsx_data(columns, data, visible_idx,include_indentation):
|
||||
def build_xlsx_data(columns, data, visible_idx, include_indentation):
|
||||
result = [[]]
|
||||
|
||||
# add column headings
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class EmailDomain(Document):
|
|||
|
||||
except Exception:
|
||||
frappe.throw(_("Incoming email account not correct"))
|
||||
return None
|
||||
|
||||
finally:
|
||||
try:
|
||||
if self.use_imap:
|
||||
|
|
@ -48,9 +48,10 @@ class EmailDomain(Document):
|
|||
test.quit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if self.use_ssl_for_outgoing:
|
||||
if not self.smtp_port:
|
||||
if self.get('use_ssl_for_outgoing'):
|
||||
if not self.get('smtp_port'):
|
||||
self.smtp_port = 465
|
||||
|
||||
sess = smtplib.SMTP_SSL((self.smtp_server or "").encode('utf-8'),
|
||||
|
|
@ -62,28 +63,15 @@ class EmailDomain(Document):
|
|||
sess.quit()
|
||||
except Exception:
|
||||
frappe.throw(_("Outgoing email account not correct"))
|
||||
return None
|
||||
return
|
||||
|
||||
def on_update(self):
|
||||
"""update all email accounts using this domain"""
|
||||
for email_account in frappe.get_all("Email Account",
|
||||
filters={"domain": self.name}):
|
||||
|
||||
for email_account in frappe.get_all("Email Account", filters={"domain": self.name}):
|
||||
try:
|
||||
email_account = frappe.get_doc("Email Account",
|
||||
email_account.name)
|
||||
email_account.set("email_server",self.email_server)
|
||||
email_account.set("use_imap",self.use_imap)
|
||||
email_account.set("use_ssl",self.use_ssl)
|
||||
email_account.set("use_tls",self.use_tls)
|
||||
email_account.set("attachment_limit",self.attachment_limit)
|
||||
email_account.set("smtp_server",self.smtp_server)
|
||||
email_account.set("smtp_port",self.smtp_port)
|
||||
email_account.set("use_ssl_for_outgoing", self.use_ssl_for_outgoing)
|
||||
email_account.set("append_emails_to_sent_folder", self.append_emails_to_sent_folder)
|
||||
email_account = frappe.get_doc("Email Account", email_account.name)
|
||||
for attr in ["email_server", "use_imap", "use_ssl", "use_tls", "attachment_limit", "smtp_server", "smtp_port", "use_ssl_for_outgoing", "append_emails_to_sent_folder"]:
|
||||
email_account.set(attr, self.get(attr, default=0))
|
||||
email_account.save()
|
||||
|
||||
except Exception as e:
|
||||
frappe.msgprint(email_account.name)
|
||||
frappe.throw(e)
|
||||
return None
|
||||
frappe.msgprint(_("Error has occurred in {0}").format(email_account.name), raise_exception=e.__class__)
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ class TimestampMismatchError(ValidationError): pass
|
|||
class EmptyTableError(ValidationError): pass
|
||||
class LinkExistsError(ValidationError): pass
|
||||
class InvalidEmailAddressError(ValidationError): pass
|
||||
class InvalidNameError(ValidationError): pass
|
||||
class InvalidPhoneNumberError(ValidationError): pass
|
||||
class TemplateNotFoundError(ValidationError): pass
|
||||
class UniqueValidationError(ValidationError): pass
|
||||
|
|
@ -95,4 +96,4 @@ class DataTooLongException(ValidationError): pass
|
|||
# OAuth exceptions
|
||||
class InvalidAuthorizationHeader(CSRFTokenError): pass
|
||||
class InvalidAuthorizationPrefix(CSRFTokenError): pass
|
||||
class InvalidAuthorizationToken(CSRFTokenError): pass
|
||||
class InvalidAuthorizationToken(CSRFTokenError): pass
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe import _, safe_encode
|
||||
from frappe.model.document import Document
|
||||
|
||||
from frappe.twofactor import (should_run_2fa, authenticate_for_2factor,confirm_otp_token)
|
||||
|
||||
class LDAPSettings(Document):
|
||||
def validate(self):
|
||||
|
|
@ -237,6 +237,10 @@ def login():
|
|||
user = ldap.authenticate(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd))
|
||||
|
||||
frappe.local.login_manager.user = user.name
|
||||
if should_run_2fa(user.name):
|
||||
authenticate_for_2factor(user.name)
|
||||
if not confirm_otp_token(frappe.local.login_manager):
|
||||
return False
|
||||
frappe.local.login_manager.post_login()
|
||||
|
||||
# because of a GET request!
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ table_fields = ('Table', 'Table MultiSelect')
|
|||
core_doctypes_list = ('DocType', 'DocField', 'DocPerm', 'DocType Action', 'DocType Link', 'User', 'Role', 'Has Role',
|
||||
'Page', 'Module Def', 'Print Format', 'Report', 'Customize Form',
|
||||
'Customize Form Field', 'Property Setter', 'Custom Field', 'Custom Script')
|
||||
data_field_options = ('Email', 'Phone')
|
||||
data_field_options = ('Email', 'Name', 'Phone')
|
||||
|
||||
def copytables(srctype, src, srcfield, tartype, tar, tarfield, srcfields, tarfields=[]):
|
||||
if not tarfields:
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ from frappe.model import default_fields, table_fields
|
|||
from frappe.model.naming import set_new_name
|
||||
from frappe.model.utils.link_count import notify_link_count
|
||||
from frappe.modules import load_doctype_module
|
||||
from frappe.model import display_fieldtypes, data_fieldtypes
|
||||
from frappe.model import display_fieldtypes
|
||||
from frappe.utils.password import get_decrypted_password, set_encrypted_password
|
||||
from frappe.utils import (cint, flt, now, cstr, strip_html, getdate, get_datetime, to_timedelta,
|
||||
from frappe.utils import (cint, flt, now, cstr, strip_html,
|
||||
sanitize_html, sanitize_email, cast_fieldtype)
|
||||
from frappe.utils.html_utils import unescape_html
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
max_positive_value = {
|
||||
'smallint': 2 ** 15,
|
||||
|
|
@ -288,7 +289,7 @@ class BaseDocument(object):
|
|||
if k in default_fields:
|
||||
del doc[k]
|
||||
|
||||
for key in ("_user_tags", "__islocal", "__onload", "_liked_by", "__run_link_triggers"):
|
||||
for key in ("_user_tags", "__islocal", "__onload", "_liked_by", "__run_link_triggers", "__unsaved"):
|
||||
if self.get(key):
|
||||
doc[key] = self.get(key)
|
||||
|
||||
|
|
@ -564,13 +565,20 @@ class BaseDocument(object):
|
|||
for data_field in self.meta.get_data_fields():
|
||||
data = self.get(data_field.fieldname)
|
||||
data_field_options = data_field.get("options")
|
||||
old_fieldtype = data_field.get("oldfieldtype")
|
||||
|
||||
if old_fieldtype and old_fieldtype != "Data":
|
||||
continue
|
||||
|
||||
if data_field_options == "Email":
|
||||
if (self.owner in STANDARD_USERS) and (data in STANDARD_USERS):
|
||||
return
|
||||
continue
|
||||
for email_address in frappe.utils.split_emails(data):
|
||||
frappe.utils.validate_email_address(email_address, throw=True)
|
||||
|
||||
if data_field_options == "Name":
|
||||
frappe.utils.validate_name(data, throw=True)
|
||||
|
||||
if data_field_options == "Phone":
|
||||
frappe.utils.validate_phone_number(data, throw=True)
|
||||
|
||||
|
|
@ -678,7 +686,7 @@ class BaseDocument(object):
|
|||
# doesn't look like html so no need
|
||||
continue
|
||||
|
||||
elif "<!-- markdown -->" in value and not ("<script" in value or "javascript:" in value):
|
||||
elif "<!-- markdown -->" in value and not bool(BeautifulSoup(value, "html.parser").find()):
|
||||
# should be handled separately via the markdown converter function
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -74,11 +74,9 @@ def set_user_and_static_default_values(doc):
|
|||
def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records, default_doc):
|
||||
# don't set defaults for "User" link field using User Permissions!
|
||||
if df.fieldtype == "Link" and df.options != "User":
|
||||
# 1 - look in user permissions only for document_type==Setup
|
||||
# We don't want to include permissions of transactions to be used for defaults.
|
||||
if (frappe.get_meta(df.options).document_type=="Setup"
|
||||
and not df.ignore_user_permissions and default_doc):
|
||||
return default_doc
|
||||
# If user permission has Is Default enabled or single-user permission has found against respective doctype.
|
||||
if (not df.ignore_user_permissions and default_doc):
|
||||
return default_doc
|
||||
|
||||
# 2 - Look in user defaults
|
||||
user_default = defaults.get(df.fieldname)
|
||||
|
|
|
|||
|
|
@ -268,6 +268,10 @@ class Document(BaseDocument):
|
|||
if hasattr(self, "__islocal"):
|
||||
delattr(self, "__islocal")
|
||||
|
||||
# clear unsaved flag
|
||||
if hasattr(self, "__unsaved"):
|
||||
delattr(self, "__unsaved")
|
||||
|
||||
if not (frappe.flags.in_migrate or frappe.local.flags.in_install or frappe.flags.in_setup_wizard):
|
||||
follow_document(self.doctype, self.name, frappe.session.user)
|
||||
return self
|
||||
|
|
@ -329,6 +333,10 @@ class Document(BaseDocument):
|
|||
self.update_children()
|
||||
self.run_post_save_methods()
|
||||
|
||||
# clear unsaved flag
|
||||
if hasattr(self, "__unsaved"):
|
||||
delattr(self, "__unsaved")
|
||||
|
||||
return self
|
||||
|
||||
def copy_attachments_from_amended_from(self):
|
||||
|
|
@ -583,6 +591,9 @@ class Document(BaseDocument):
|
|||
if high_permlevel_fields:
|
||||
self.reset_values_if_no_permlevel_access(has_access_to, high_permlevel_fields)
|
||||
|
||||
# If new record then don't reset the values for child table
|
||||
if self.is_new(): return
|
||||
|
||||
# check for child tables
|
||||
for df in self.meta.get_table_fields():
|
||||
high_permlevel_fields = frappe.get_meta(df.options).get_high_permlevel_fields()
|
||||
|
|
@ -1318,6 +1329,9 @@ def make_event_update_log(doc, update_type):
|
|||
|
||||
def check_doctype_has_consumers(doctype):
|
||||
"""Check if doctype has event consumers for event streaming"""
|
||||
if not frappe.db.exists("DocType", "Event Consumer"):
|
||||
return False
|
||||
|
||||
event_consumers = frappe.get_all('Event Consumer')
|
||||
for event_consumer in event_consumers:
|
||||
consumer = frappe.get_doc('Event Consumer', event_consumer.name)
|
||||
|
|
|
|||
|
|
@ -425,17 +425,19 @@ class Meta(Document):
|
|||
implemented in other Frappe applications via hooks.
|
||||
'''
|
||||
data = frappe._dict()
|
||||
try:
|
||||
module = load_doctype_module(self.name, suffix='_dashboard')
|
||||
if hasattr(module, 'get_data'):
|
||||
data = frappe._dict(module.get_data())
|
||||
except ImportError:
|
||||
pass
|
||||
if not self.custom:
|
||||
try:
|
||||
module = load_doctype_module(self.name, suffix='_dashboard')
|
||||
if hasattr(module, 'get_data'):
|
||||
data = frappe._dict(module.get_data())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
self.add_doctype_links(data)
|
||||
|
||||
for hook in frappe.get_hooks("override_doctype_dashboards", {}).get(self.name, []):
|
||||
data = frappe.get_attr(hook)(data=data)
|
||||
if not self.custom:
|
||||
for hook in frappe.get_hooks("override_doctype_dashboards", {}).get(self.name, []):
|
||||
data = frappe.get_attr(hook)(data=data)
|
||||
|
||||
return data
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("contacts", "doctype", "contact_email")
|
||||
frappe.reload_doc("contacts", "doctype", "contact_phone")
|
||||
frappe.reload_doc("contacts", "doctype", "contact")
|
||||
|
||||
contact_details = frappe.db.sql("""
|
||||
SELECT
|
||||
`name`, `email_id`, `phone`, `mobile_no`, `modified_by`, `creation`, `modified`
|
||||
|
|
@ -10,10 +14,6 @@ def execute():
|
|||
and `tabContact Email`.email_id=`tabContact`.email_id)
|
||||
""", as_dict=True)
|
||||
|
||||
frappe.reload_doc("contacts", "doctype", "contact_email")
|
||||
frappe.reload_doc("contacts", "doctype", "contact_phone")
|
||||
frappe.reload_doc("contacts", "doctype", "contact")
|
||||
|
||||
email_values = []
|
||||
phone_values = []
|
||||
for count, contact_detail in enumerate(contact_details):
|
||||
|
|
|
|||
|
|
@ -441,18 +441,16 @@ frappe.PrintFormatBuilder = Class.extend({
|
|||
});
|
||||
},
|
||||
setup_field_settings: function() {
|
||||
|
||||
this.page.main.find(".field-settings").on("click", () => {
|
||||
var field = $(this).parent();
|
||||
|
||||
this.page.main.find(".field-settings").on("click", e => {
|
||||
const field = $(e.currentTarget).parent();
|
||||
// new dialog
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: "Set Properties",
|
||||
fields: [
|
||||
{
|
||||
label:__("Label"),
|
||||
fieldname:"label",
|
||||
fieldtype:"Data"
|
||||
label: __("Label"),
|
||||
fieldname: "label",
|
||||
fieldtype: "Data"
|
||||
},
|
||||
{
|
||||
label: __("Align Value"),
|
||||
|
|
@ -485,7 +483,7 @@ frappe.PrintFormatBuilder = Class.extend({
|
|||
});
|
||||
|
||||
// set current value
|
||||
if(field.attr('data-align')) {
|
||||
if (field.attr('data-align')) {
|
||||
d.set_value('align', field.attr('data-align'));
|
||||
} else {
|
||||
d.set_value('align', 'left');
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@
|
|||
"public/css/font-awesome.css",
|
||||
"public/css/octicons/octicons.css",
|
||||
"public/less/desk.less",
|
||||
"public/less/module.less",
|
||||
"public/less/flex.less",
|
||||
"public/less/indicator.less",
|
||||
"public/less/avatar.less",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ frappe.ui.form.ControlBarcode = frappe.ui.form.ControlData.extend({
|
|||
const svg = this.barcode_area.find('svg')[0];
|
||||
JsBarcode(svg, value, this.get_options(value));
|
||||
$(svg).attr('data-barcode-value', value);
|
||||
$(svg).attr('width', '100%');
|
||||
return this.barcode_area.html();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({
|
|||
this.ace_editor_target = $('<div class="ace-editor-target"></div>')
|
||||
.appendTo(this.input_area);
|
||||
|
||||
this.expanded = false;
|
||||
this.$expand_button = $(`<button class="btn btn-xs btn-default">${__('Expand')}</button>`).click(() => {
|
||||
this.expanded = !this.expanded;
|
||||
this.refresh_height();
|
||||
this.toggle_label();
|
||||
}).appendTo(this.$input_wrapper);
|
||||
// styling
|
||||
this.ace_editor_target.addClass('border rounded');
|
||||
this.ace_editor_target.css('height', 300);
|
||||
|
|
@ -26,6 +32,16 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({
|
|||
}, 300));
|
||||
},
|
||||
|
||||
refresh_height() {
|
||||
this.ace_editor_target.css('height', this.expanded ? 600 : 300);
|
||||
this.editor.resize();
|
||||
},
|
||||
|
||||
toggle_label() {
|
||||
const button_label = this.expanded ? __('Collapse') : __('Expand');
|
||||
this.$expand_button.text(button_label);
|
||||
},
|
||||
|
||||
set_language() {
|
||||
const language_map = {
|
||||
'Javascript': 'ace/mode/javascript',
|
||||
|
|
@ -34,7 +50,9 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({
|
|||
'CSS': 'ace/mode/css',
|
||||
'Markdown': 'ace/mode/markdown',
|
||||
'SCSS': 'ace/mode/scss',
|
||||
'JSON': 'ace/mode/json'
|
||||
'JSON': 'ace/mode/json',
|
||||
'Golang': 'ace/mode/golang',
|
||||
'Go': 'ace/mode/golang'
|
||||
};
|
||||
const language = this.df.options;
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({
|
|||
if(this.df.options == 'Phone') {
|
||||
this.df.invalid = !validate_phone(v);
|
||||
return v;
|
||||
} else if (this.df.options == 'Name') {
|
||||
this.df.invalid = !validate_name(v);
|
||||
return v;
|
||||
} else if(this.df.options == 'Email') {
|
||||
var email_list = frappe.utils.split_emails(v);
|
||||
if (!email_list) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ frappe.ui.form.ControlMultiSelectList = frappe.ui.form.ControlData.extend({
|
|||
this.$list_wrapper = $(template);
|
||||
this.$input = $('<input>');
|
||||
this.input = this.$input.get(0);
|
||||
this.has_input = true;
|
||||
this.$list_wrapper.prependTo(this.input_area);
|
||||
this.$filter_input = this.$list_wrapper.find('input');
|
||||
this.$list_wrapper.on('click', '.dropdown-menu', e => {
|
||||
|
|
|
|||
|
|
@ -184,13 +184,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
frappe.model.on(me.doctype, "*", function(fieldname, value, doc) {
|
||||
// set input
|
||||
if(doc.name===me.docname) {
|
||||
if ((value==='' || value===null) && !doc[fieldname]) {
|
||||
// both the incoming and outgoing values are falsy
|
||||
// the texteditor, summernote, changes nulls to empty strings on render,
|
||||
// so ignore those changes
|
||||
} else {
|
||||
me.dirty();
|
||||
}
|
||||
me.dirty();
|
||||
|
||||
let field = me.fields_dict[fieldname];
|
||||
field && field.refresh(fieldname);
|
||||
|
|
|
|||
|
|
@ -22,9 +22,6 @@ export default class GridRow {
|
|||
if(me.grid.allow_on_grid_editing() && me.grid.is_editable()) {
|
||||
// pass
|
||||
} else {
|
||||
if (!me.grid.is_editable()) {
|
||||
me.docfields.map(df => df.read_only = 1);
|
||||
}
|
||||
me.toggle_view();
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
},
|
||||
|
||||
refresh: function() {
|
||||
if(this.frm.doc.__islocal) {
|
||||
if (this.frm.doc.__islocal) {
|
||||
this.sidebar.toggle(false);
|
||||
} else {
|
||||
this.sidebar.toggle(true);
|
||||
|
|
@ -81,12 +81,34 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
}
|
||||
this.frm.viewers.refresh();
|
||||
this.frm.tags && this.frm.tags.refresh(this.frm.get_docinfo().tags);
|
||||
this.sidebar.find(".modified-by").html(__("{0} edited this {1}",
|
||||
["<strong>" + frappe.user.full_name(this.frm.doc.modified_by) + "</strong>",
|
||||
"<br>" + comment_when(this.frm.doc.modified)]));
|
||||
this.sidebar.find(".created-by").html(__("{0} created this {1}",
|
||||
["<strong>" + frappe.user.full_name(this.frm.doc.owner) + "</strong>",
|
||||
"<br>" + comment_when(this.frm.doc.creation)]));
|
||||
|
||||
if (this.frm.doc.route && cint(frappe.boot.website_tracking_enabled)) {
|
||||
let route = this.frm.doc.route;
|
||||
frappe.utils.get_page_view_count(route).then((res) => {
|
||||
this.sidebar
|
||||
.find(".pageview-count")
|
||||
.html(
|
||||
__("{0} Page Views", [String(res.message).bold()])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.sidebar
|
||||
.find(".modified-by")
|
||||
.html(
|
||||
__("{0} edited this {1}", [
|
||||
frappe.user.full_name(this.frm.doc.modified_by).bold(),
|
||||
"<br>" + comment_when(this.frm.doc.modified),
|
||||
])
|
||||
);
|
||||
this.sidebar
|
||||
.find(".created-by")
|
||||
.html(
|
||||
__("{0} created this {1}", [
|
||||
frappe.user.full_name(this.frm.doc.owner).bold(),
|
||||
"<br>" + comment_when(this.frm.doc.creation),
|
||||
])
|
||||
);
|
||||
|
||||
this.refresh_like();
|
||||
frappe.ui.form.set_user_image(this.frm);
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
<ul class="list-unstyled sidebar-menu text-muted">
|
||||
<li class="pageview-count"></li>
|
||||
<li class="modified-by"></li>
|
||||
<li class="created-by"></li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -137,10 +137,8 @@ $.extend(frappe.model, {
|
|||
|
||||
// don't set defaults for "User" link field using User Permissions!
|
||||
if (df.fieldtype==="Link" && df.options!=="User") {
|
||||
// 1 - look in user permissions for document_type=="Setup".
|
||||
// We don't want to include permissions of transactions to be used for defaults.
|
||||
if (df.linked_document_type==="Setup"
|
||||
&& has_user_permissions && default_doc) {
|
||||
// If user permission has Is Default enabled or single-user permission has found against respective doctype.
|
||||
if (has_user_permissions && default_doc) {
|
||||
return default_doc;
|
||||
}
|
||||
|
||||
|
|
@ -161,10 +159,6 @@ $.extend(frappe.model, {
|
|||
user_default = frappe.boot.user.last_selected_values[df.options];
|
||||
}
|
||||
|
||||
if (!user_default && default_doc) {
|
||||
user_default = default_doc;
|
||||
}
|
||||
|
||||
var is_allowed_user_default = user_default &&
|
||||
(!has_user_permissions || allowed_records.includes(user_default));
|
||||
|
||||
|
|
|
|||
|
|
@ -352,3 +352,9 @@ frappe.utils.new_auto_repeat_prompt = function(frm) {
|
|||
__('Save')
|
||||
);
|
||||
}
|
||||
|
||||
frappe.utils.get_page_view_count = function(route) {
|
||||
return frappe.call("frappe.website.doctype.web_page_view.web_page_view.get_page_view_count", {
|
||||
path: route
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ window.validate_phone = function(txt) {
|
|||
return frappe.utils.validate_type(txt, "phone");
|
||||
};
|
||||
|
||||
window.validate_name = function(txt) {
|
||||
return frappe.utils.validate_type(txt, "name");
|
||||
};
|
||||
|
||||
window.nth = function(number) {
|
||||
number = cint(number);
|
||||
var s = 'th';
|
||||
|
|
@ -73,4 +77,4 @@ window.has_common = function(list1, list2) {
|
|||
if(in_list(list2, list1[i]))return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ function prettyDate(date, mini) {
|
|||
// Return short format of time difference
|
||||
if (day_diff == 0) {
|
||||
if (diff < 60) {
|
||||
return __("Now");
|
||||
return __("now");
|
||||
} else if (diff < 3600) {
|
||||
return __("{0} m", [Math.floor(diff / 60)]);
|
||||
} else if (diff < 86400) {
|
||||
|
|
@ -21,20 +21,20 @@ function prettyDate(date, mini) {
|
|||
}
|
||||
} else {
|
||||
if (day_diff < 7) {
|
||||
return __("{0} D", [day_diff]);
|
||||
return __("{0} d", [day_diff]);
|
||||
} else if (day_diff < 31) {
|
||||
return __("{0} W", [Math.ceil(day_diff / 7)]);
|
||||
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)]);
|
||||
return __("{0} y", [Math.ceil(day_diff / 365)]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Return long format of time difference
|
||||
if (day_diff == 0) {
|
||||
if (diff < 60) {
|
||||
return __("Just now");
|
||||
return __("just now");
|
||||
} else if (diff < 120) {
|
||||
return __("1 minute ago");
|
||||
} else if (diff < 3600) {
|
||||
|
|
@ -46,7 +46,7 @@ function prettyDate(date, mini) {
|
|||
}
|
||||
} else {
|
||||
if (day_diff == 1) {
|
||||
return __("Yesterday");
|
||||
return __("yesterday");
|
||||
} else if (day_diff < 7) {
|
||||
return __("{0} days ago", [day_diff]);
|
||||
} else if (day_diff < 14) {
|
||||
|
|
|
|||
|
|
@ -237,6 +237,9 @@ Object.assign(frappe.utils, {
|
|||
case "phone":
|
||||
regExp = /^([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$/;
|
||||
break;
|
||||
case "name":
|
||||
regExp = /^[\w][\w'-]*([ \w][\w'-]+)*$/;
|
||||
break;
|
||||
case "number":
|
||||
regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/;
|
||||
break;
|
||||
|
|
@ -745,7 +748,36 @@ Object.assign(frappe.utils, {
|
|||
});
|
||||
|
||||
return $el;
|
||||
}
|
||||
},
|
||||
|
||||
get_browser() {
|
||||
var ua = navigator.userAgent,
|
||||
tem,
|
||||
M =
|
||||
ua.match(
|
||||
/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i
|
||||
) || [];
|
||||
if (/trident/i.test(M[1])) {
|
||||
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
|
||||
return { name: "IE", version: tem[1] || "" };
|
||||
}
|
||||
if (M[1] === "Chrome") {
|
||||
tem = ua.match(/\bOPR|Edge\/(\d+)/);
|
||||
if (tem != null) {
|
||||
return { name: "Opera", version: tem[1] };
|
||||
}
|
||||
}
|
||||
M = M[2]
|
||||
? [M[1], M[2]]
|
||||
: [navigator.appName, navigator.appVersion, "-?"];
|
||||
if ((tem = ua.match(/version\/(\d+)/i)) != null) {
|
||||
M.splice(1, 1, tem[1]);
|
||||
}
|
||||
return {
|
||||
name: M[0],
|
||||
version: M[1],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Array de duplicate
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ frappe.breadcrumbs = {
|
|||
|
||||
preferred: {
|
||||
"File": "",
|
||||
"Video": "",
|
||||
"Dashboard": "Customization",
|
||||
"Dashboard Chart": "Customization",
|
||||
"Dashboard Chart Source": "Customization",
|
||||
"Dashboard Chart Source": "Customization"
|
||||
},
|
||||
|
||||
module_map: {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
primary_action: function() {
|
||||
me.delete_saved_draft();
|
||||
me.send_action();
|
||||
}
|
||||
},
|
||||
minimizable: true
|
||||
});
|
||||
|
||||
['recipients', 'cc', 'bcc'].forEach(field => {
|
||||
|
|
|
|||
|
|
@ -344,10 +344,6 @@ class DesktopPage {
|
|||
{
|
||||
color: "orange",
|
||||
description: __("No Records Created")
|
||||
},
|
||||
{
|
||||
color: "red",
|
||||
description: __("Has Open Entries")
|
||||
}
|
||||
].map(item => {
|
||||
return `<div class="legend-item small text-muted justify-flex-start">
|
||||
|
|
|
|||
|
|
@ -183,7 +183,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
create_dashboard_chart(chart_args, dashboard_name, chart_name) {
|
||||
|
||||
let args = {
|
||||
'dashboard': dashboard_name || null,
|
||||
'chart_type': 'Report',
|
||||
|
|
@ -191,8 +190,15 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
'type': chart_args.chart_type || frappe.model.unscrub(chart_args.type),
|
||||
'color': chart_args.color,
|
||||
'filters_json': JSON.stringify(this.get_filter_values()),
|
||||
'custom_options': {}
|
||||
};
|
||||
|
||||
for (let key in chart_args) {
|
||||
if (key != "data") {
|
||||
args['custom_options'][key] = chart_args[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.chart_fields) {
|
||||
let x_field_title = toTitle(chart_args.x_field);
|
||||
let y_field_title = toTitle(chart_args.y_fields[0]);
|
||||
|
|
@ -1084,7 +1090,12 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
], ({ file_format, include_indentation }) => {
|
||||
this.make_access_log('Export', file_format);
|
||||
if (file_format === 'CSV') {
|
||||
const column_row = this.columns.map(col => col.label);
|
||||
const column_row = this.columns.reduce((acc, col) => {
|
||||
if (!col.hidden) {
|
||||
acc.push(col.label);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
const data = this.get_data_for_csv(include_indentation);
|
||||
const out = [column_row].concat(data);
|
||||
|
||||
|
|
|
|||
|
|
@ -457,7 +457,8 @@ export default class ChartWidget extends Widget {
|
|||
Line: "line",
|
||||
Bar: "bar",
|
||||
Percentage: "percentage",
|
||||
Pie: "pie"
|
||||
Pie: "pie",
|
||||
Donut: "donut"
|
||||
};
|
||||
|
||||
let colors = [];
|
||||
|
|
@ -490,6 +491,14 @@ export default class ChartWidget extends Widget {
|
|||
shortenYAxisNumbers: 1
|
||||
}
|
||||
};
|
||||
|
||||
if (this.chart_doc.custom_options) {
|
||||
let custom_options = JSON.parse(this.chart_doc.custom_options);
|
||||
for (let key in custom_options) {
|
||||
chart_args[key] = custom_options[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.dashboard_chart) {
|
||||
this.dashboard_chart = new frappe.Chart(
|
||||
this.chart_wrapper[0],
|
||||
|
|
|
|||
|
|
@ -770,6 +770,7 @@ h6.uppercase, .h6.uppercase {
|
|||
|
||||
.help-box {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
pre {
|
||||
|
|
|
|||
147
frappe/public/less/module.less
Normal file
147
frappe/public/less/module.less
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
@import "variables.less";
|
||||
|
||||
.module-head {
|
||||
padding: 15px 30px;
|
||||
border-bottom: 1px solid @light-border-color;
|
||||
}
|
||||
|
||||
.module-head h1 {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.module-body {
|
||||
padding: 0px 15px;
|
||||
|
||||
.section-head {
|
||||
margin-bottom: 15px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.module-section {
|
||||
border-bottom: 1px solid @light-border-color;
|
||||
|
||||
.module-section-link {
|
||||
line-height: 1.5em;
|
||||
// font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.module-section-column {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
@media(min-width: @screen-xs) {
|
||||
.module-section:nth-child(even) {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
|
||||
.module-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: @screen-sm) {
|
||||
.module-body {
|
||||
margin-top: 15px;
|
||||
border-top: 1px solid @border-color;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: @screen-xs) {
|
||||
.module-body {
|
||||
margin-top: 0;
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: @screen-xs) {
|
||||
.module-section {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.module-section-column {
|
||||
border-bottom: 1px solid @light-border-color;
|
||||
}
|
||||
|
||||
.module-section-column:nth-child(even) {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
|
||||
.module-section:last-child .module-section-column:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.module-item {
|
||||
margin: 0px;
|
||||
padding: 7px;
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid @border-color;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
-webkit-transition: 0.2s;
|
||||
}
|
||||
|
||||
.module-item h4 {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.module-item .module-item-description {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.module-item .badge {
|
||||
margin-top: -2px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.module-item:hover, .module-item:focus {
|
||||
background-color: @panel-bg;
|
||||
}
|
||||
|
||||
.module-item:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.module-link.active .icon-chevron-right {
|
||||
margin-top: 4px;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.module-item-progress {
|
||||
margin-bottom: 10px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
.module-item-progress-total {
|
||||
height: 7px;
|
||||
background-color: #999999;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.module-item-progress-open {
|
||||
height: 7px;
|
||||
background-color: red;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
@media(max-width: @screen-xs) {
|
||||
|
||||
body[data-route^="Module"] {
|
||||
.page-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.page-actions {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.layout-main-section {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -273,7 +273,8 @@ body[data-route^="Module"] .main-menu {
|
|||
}
|
||||
|
||||
.layout-side-section .form-sidebar {
|
||||
.modified-by {
|
||||
.modified-by,
|
||||
.pageview-count {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,17 @@ footer {
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// make navbar padding consistent with the page
|
||||
.navbar {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
.container {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar.bg-dark {
|
||||
.dropdown-menu {
|
||||
font-size: .75rem;
|
||||
|
|
|
|||
|
|
@ -64,6 +64,24 @@
|
|||
{% if not only_static %}
|
||||
{% block navbar_right_extension %}{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
{% if show_sidebar and sidebar_items %}
|
||||
<div class="d-block d-sm-none">
|
||||
<hr>
|
||||
{% for item in sidebar_items -%}
|
||||
<li class="nav-item">
|
||||
{% if item.type != 'input' %}
|
||||
<a href="{{ item.route }}" class="nav-link {{ 'text-dark' if pathname==item.route else 'text-muted'}}"
|
||||
{% if item.target %}target="{{ item.target }}"{% endif %}>
|
||||
{{ _(item.title or item.label) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{%- endfor %}
|
||||
<hr>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include "templates/includes/navbar/navbar_search.html" %}
|
||||
{% include "templates/includes/navbar/navbar_login.html" %}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@
|
|||
}
|
||||
|
||||
.ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
</div>
|
||||
|
||||
{% block page_container %}
|
||||
<main class="{% if not theme.use_full_width %}container{% endif %} my-5">
|
||||
<main class="{% if not full_width %}container my-5{% endif %}">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="page-header">
|
||||
{% block header %}{% endblock %}
|
||||
|
|
@ -38,9 +38,11 @@
|
|||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro container_attributes() %}
|
||||
id="page-{{ name or route | e }}" data-path="{{ pathname | e }}" {%- if page_or_generator=="Generator" %}source-type="Generator" data-doctype="{{ doctype }}"{% endif %}
|
||||
{% endmacro %}
|
||||
{% macro container_attributes() -%}
|
||||
id="page-{{ name or route | e }}" data-path="{{ pathname | e }}"
|
||||
{%- if page_or_generator=="Generator" %}source-type="Generator" data-doctype="{{ doctype }}"{%- endif %}
|
||||
{%- if source_content_type %}source-content-type="{{ source_content_type }}"{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% if show_sidebar %}
|
||||
<div class="container">
|
||||
|
|
|
|||
|
|
@ -374,11 +374,11 @@ def delete_qrimage(user, check_expiry=False):
|
|||
|
||||
def delete_all_barcodes_for_users():
|
||||
'''Task to delete all barcodes for user.'''
|
||||
if not two_factor_is_enabled():
|
||||
return
|
||||
|
||||
users = frappe.get_all('User', {'enabled':1})
|
||||
for user in users:
|
||||
if not two_factor_is_enabled(user=user.name):
|
||||
continue
|
||||
delete_qrimage(user.name, check_expiry=True)
|
||||
|
||||
def should_remove_barcode_image(barcode):
|
||||
|
|
|
|||
|
|
@ -81,13 +81,29 @@ def validate_phone_number(phone_number, throw=False):
|
|||
return False
|
||||
|
||||
phone_number = phone_number.strip()
|
||||
match = re.match("([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$", phone_number)
|
||||
match = re.match(r"([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$", phone_number)
|
||||
|
||||
if not match and throw:
|
||||
frappe.throw(frappe._("{0} is not a valid Phone Number").format(phone_number), frappe.InvalidPhoneNumberError)
|
||||
|
||||
return bool(match)
|
||||
|
||||
def validate_name(name, throw=False):
|
||||
"""Returns True if the name is valid
|
||||
valid names may have unicode and ascii characters, dash, quotes, numbers
|
||||
anything else is considered invalid
|
||||
"""
|
||||
if not name:
|
||||
return False
|
||||
|
||||
name = name.strip()
|
||||
match = re.match(r"^[\w][\w\'\-]*([ \w][\w\'\-]+)*$", name)
|
||||
|
||||
if not match and throw:
|
||||
frappe.throw(frappe._("{0} is not a valid Name").format(name), frappe.InvalidNameError)
|
||||
|
||||
return bool(match)
|
||||
|
||||
def validate_email_address(email_str, throw=False):
|
||||
"""Validates the email string"""
|
||||
email = email_str = (email_str or "").strip()
|
||||
|
|
|
|||
|
|
@ -174,9 +174,12 @@ def parse_latest_non_beta_release(response):
|
|||
Returns
|
||||
json : json object pertaining to the latest non-beta release
|
||||
"""
|
||||
for release in response:
|
||||
if release['prerelease'] == True: continue
|
||||
return release
|
||||
version_list = [release.get('tag_name').strip('v') for release in response if not release.get('prerelease')]
|
||||
|
||||
if version_list:
|
||||
return sorted(version_list, key=Version, reverse=True)[0]
|
||||
|
||||
return None
|
||||
|
||||
def check_release_on_github(app):
|
||||
# Check if repo remote is on github
|
||||
|
|
@ -199,12 +202,11 @@ def check_release_on_github(app):
|
|||
|
||||
org_name = remote_url.split('/')[3]
|
||||
r = requests.get('https://api.github.com/repos/{}/{}/releases'.format(org_name, app))
|
||||
if r.status_code == 200 and r.json():
|
||||
if r.ok:
|
||||
lastest_non_beta_release = parse_latest_non_beta_release(r.json())
|
||||
return Version(lastest_non_beta_release['tag_name'].strip('v')), org_name
|
||||
else:
|
||||
# In case of an improper response or if there are no releases
|
||||
return None
|
||||
return Version(lastest_non_beta_release), org_name
|
||||
# In case of an improper response or if there are no releases
|
||||
return None
|
||||
|
||||
def add_message_to_redis(update_json):
|
||||
# "update-message" will store the update message string
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from logging.handlers import RotatingFileHandler
|
|||
from six import text_type
|
||||
|
||||
default_log_level = logging.DEBUG
|
||||
LOG_FILENAME = '../logs/frappe.log'
|
||||
LOG_FILENAME = '../logs/{}-frappe.log'.format(frappe.local.site)
|
||||
|
||||
def get_logger(module, with_more_info=True):
|
||||
if module in frappe.loggers:
|
||||
|
|
@ -57,4 +57,3 @@ def set_log_level(level):
|
|||
'''Use this method to set log level to something other than the default DEBUG'''
|
||||
frappe.log_level = getattr(logging, (level or '').upper(), None) or default_log_level
|
||||
frappe.loggers = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -218,6 +218,6 @@ def send_private_file(path):
|
|||
|
||||
def handle_session_stopped():
|
||||
frappe.respond_as_web_page(_("Updating"),
|
||||
_("Your system is being updated. Please refresh again after a few moments"),
|
||||
_("Your system is being updated. Please refresh again after a few moments."),
|
||||
http_status_code=503, indicator_color='orange', fullpage = True, primary_action=None)
|
||||
return frappe.website.render.render("message", http_status_code=503)
|
||||
|
|
|
|||
|
|
@ -221,24 +221,24 @@ def add_metatags(context):
|
|||
tags = frappe._dict(context.get("metatags") or {})
|
||||
|
||||
if tags:
|
||||
if not "twitter:card" in tags:
|
||||
tags["twitter:card"] = "summary_large_image"
|
||||
|
||||
if not "og:type" in tags:
|
||||
tags["og:type"] = "article"
|
||||
|
||||
if tags.get("name"):
|
||||
tags["og:title"] = tags["twitter:title"] = tags["name"]
|
||||
name = tags.get('name') or tags.get('title')
|
||||
if name:
|
||||
tags["og:title"] = tags["twitter:title"] = name
|
||||
|
||||
if tags.get("title"):
|
||||
tags["og:title"] = tags["twitter:title"] = tags["title"]
|
||||
|
||||
if tags.get("description"):
|
||||
tags["og:description"] = tags["twitter:description"] = tags["description"]
|
||||
description = tags.get("description") or context.description
|
||||
if description:
|
||||
tags['description'] = tags["og:description"] = tags["twitter:description"] = description
|
||||
|
||||
image = tags.get('image', context.image or None)
|
||||
if image:
|
||||
tags["og:image"] = tags["twitter:image:src"] = tags["image"] = frappe.utils.get_url(image)
|
||||
tags['twitter:card'] = "summary_large_image"
|
||||
|
||||
if context.author or tags.get('author'):
|
||||
tags['author'] = context.author or tags.get('author')
|
||||
|
||||
if context.path:
|
||||
tags['og:url'] = tags['url'] = frappe.utils.get_url(context.path)
|
||||
|
|
@ -246,11 +246,6 @@ def add_metatags(context):
|
|||
if context.published_on:
|
||||
tags['datePublished'] = context.published_on
|
||||
|
||||
if context.author:
|
||||
tags['author'] = context.author
|
||||
|
||||
if context.description:
|
||||
tags['description'] = context.description
|
||||
|
||||
tags['language'] = frappe.local.lang or 'en'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,274 +1,108 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:short_name",
|
||||
"beta": 0,
|
||||
"creation": "2013-03-25 16:00:51",
|
||||
"custom": 0,
|
||||
"description": "User ID of a Blogger",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"autoname": "field:short_name",
|
||||
"creation": "2013-03-25 16:00:51",
|
||||
"description": "User ID of a Blogger",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"disabled",
|
||||
"short_name",
|
||||
"full_name",
|
||||
"user",
|
||||
"bio",
|
||||
"avatar",
|
||||
"posts"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Will be used in url (usually first name).",
|
||||
"fieldname": "short_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Short Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"description": "Will be used in url (usually first name).",
|
||||
"fieldname": "short_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Short Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "full_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Full Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "full_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Full Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"label": "User",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "bio",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Bio",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "bio",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Bio"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "avatar",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Avatar",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "avatar",
|
||||
"fieldtype": "Attach",
|
||||
"label": "Avatar"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "posts",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Posts",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "posts",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Posts",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-user",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 1,
|
||||
"modified": "2018-10-10 14:40:40.407657",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Blogger",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 1,
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Blog Post",
|
||||
"link_fieldname": "blogger"
|
||||
}
|
||||
],
|
||||
"max_attachments": 1,
|
||||
"modified": "2020-04-19 08:21:09.684300",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Blogger",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Website Manager",
|
||||
"set_user_permissions": 1,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"import": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Website Manager",
|
||||
"set_user_permissions": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Blogger",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Blogger",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"title_field": "full_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "full_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
0
frappe/website/doctype/color/__init__.py
Normal file
0
frappe/website/doctype/color/__init__.py
Normal file
8
frappe/website/doctype/color/color.js
Normal file
8
frappe/website/doctype/color/color.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('Color', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
44
frappe/website/doctype/color/color.json
Normal file
44
frappe/website/doctype/color/color.json
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "Prompt",
|
||||
"creation": "2020-04-19 02:25:37.010180",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"color"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Color",
|
||||
"in_list_view": 1,
|
||||
"label": "Color",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-19 02:25:47.417772",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Color",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Website Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/website/doctype/color/color.py
Normal file
10
frappe/website/doctype/color/color.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 Color(Document):
|
||||
pass
|
||||
10
frappe/website/doctype/color/test_color.py
Normal file
10
frappe/website/doctype/color/test_color.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 TestColor(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_guest_to_view": 1,
|
||||
"allow_import": 1,
|
||||
"creation": "2013-03-28 10:35:30",
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
"slideshow",
|
||||
"cb1",
|
||||
"published",
|
||||
"full_width",
|
||||
"show_title",
|
||||
"start_date",
|
||||
"end_date",
|
||||
|
|
@ -39,6 +41,10 @@
|
|||
"sb2",
|
||||
"header",
|
||||
"breadcrumbs",
|
||||
"metatags_section",
|
||||
"meta_title",
|
||||
"meta_description",
|
||||
"meta_image",
|
||||
"set_meta_tags"
|
||||
],
|
||||
"fields": [
|
||||
|
|
@ -217,7 +223,7 @@
|
|||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "sb2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Header, Breadcrumbs and Meta Tags"
|
||||
"label": "Header and Breadcrumbs"
|
||||
},
|
||||
{
|
||||
"description": "HTML for header section. Optional",
|
||||
|
|
@ -235,21 +241,49 @@
|
|||
{
|
||||
"fieldname": "set_meta_tags",
|
||||
"fieldtype": "Button",
|
||||
"label": "Set Meta Tags"
|
||||
"label": "Add Custom Tags"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "dynamic_template",
|
||||
"fieldtype": "Check",
|
||||
"label": "Dynamic Template"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "full_width",
|
||||
"fieldtype": "Check",
|
||||
"label": "Full Width"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "metatags_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Meta Tags"
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"icon": "fa fa-file-alt",
|
||||
"idx": 1,
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"max_attachments": 20,
|
||||
"modified": "2019-10-02 13:58:50.825481",
|
||||
"modified": "2020-04-19 12:26:21.546908",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Page",
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class WebPage(WebsiteGenerator):
|
|||
|
||||
def get_context(self, context):
|
||||
context.main_section = get_html_content_based_on_type(self, 'main_section', self.content_type)
|
||||
context.source_content_type = self.content_type
|
||||
self.render_dynamic(context)
|
||||
|
||||
# if static page, get static content
|
||||
|
|
@ -127,13 +128,11 @@ class WebPage(WebsiteGenerator):
|
|||
|
||||
def set_metatags(self, context):
|
||||
context.metatags = {
|
||||
"name": context.title
|
||||
"name": self.meta_title or self.title,
|
||||
"description": self.meta_description,
|
||||
"image": self.meta_image or find_first_image(context.main_section or "")
|
||||
}
|
||||
|
||||
image = find_first_image(context.main_section or "")
|
||||
if image:
|
||||
context.metatags["image"] = image
|
||||
|
||||
def validate_dates(self):
|
||||
if self.end_date:
|
||||
if self.start_date and get_datetime(self.end_date) < get_datetime(self.start_date):
|
||||
|
|
|
|||
0
frappe/website/doctype/web_page_view/__init__.py
Normal file
0
frappe/website/doctype/web_page_view/__init__.py
Normal file
10
frappe/website/doctype/web_page_view/test_web_page_view.py
Normal file
10
frappe/website/doctype/web_page_view/test_web_page_view.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 TestWebPageView(unittest.TestCase):
|
||||
pass
|
||||
8
frappe/website/doctype/web_page_view/web_page_view.js
Normal file
8
frappe/website/doctype/web_page_view/web_page_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 Page View', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
75
frappe/website/doctype/web_page_view/web_page_view.json
Normal file
75
frappe/website/doctype/web_page_view/web_page_view.json
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-04-15 22:54:46.009703",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"path",
|
||||
"referrer",
|
||||
"browser",
|
||||
"browser_version",
|
||||
"date"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "path",
|
||||
"fieldtype": "Data",
|
||||
"label": "Path",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "referrer",
|
||||
"fieldtype": "Data",
|
||||
"label": "Referrer",
|
||||
"search_index": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "browser",
|
||||
"fieldtype": "Data",
|
||||
"label": "Browser",
|
||||
"search_index": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "browser_version",
|
||||
"fieldtype": "Data",
|
||||
"label": "Browser Version",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Date",
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-15 23:31:27.517793",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Page 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
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "path",
|
||||
"track_changes": 1
|
||||
}
|
||||
43
frappe/website/doctype/web_page_view/web_page_view.py
Normal file
43
frappe/website/doctype/web_page_view/web_page_view.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# -*- 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 WebPageView(Document):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def make_view_log(path, referrer=None, browser=None, version=None, url=None, user_tz=None):
|
||||
request_dict = frappe.request.__dict__
|
||||
user_agent = request_dict.get('environ', {}).get('HTTP_USER_AGENT')
|
||||
|
||||
is_unique = True
|
||||
if referrer.startswith(url):
|
||||
is_unique = False
|
||||
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
|
||||
if is_tracking_enabled():
|
||||
view = frappe.new_doc("Web Page View")
|
||||
view.path = path
|
||||
view.referrer = referrer
|
||||
view.browser = browser
|
||||
view.browser_version = version
|
||||
view.time_zone = user_tz
|
||||
view.user_agent = user_agent
|
||||
view.is_unique = is_unique
|
||||
view.insert(ignore_permissions=True)
|
||||
|
||||
return
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_page_view_count(path):
|
||||
return frappe.db.count("Web Page View", filters={'path': path})
|
||||
|
||||
def is_tracking_enabled():
|
||||
return frappe.db.get_value("Website Settings", "Website Settings", "enable_view_tracking")
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
{%- endif -%}
|
||||
|
||||
{%- macro render_element(element) -%}
|
||||
{%- if element.element_type=='Content' -%}
|
||||
{%- if element.element_type in ('Content', 'Web View') -%}
|
||||
<div class="web-content {{ element_class(element) }}" {{ element_style(element) }}>
|
||||
{{ element.web_content_html }}
|
||||
</div>
|
||||
|
|
@ -25,17 +25,16 @@
|
|||
{%- endmacro -%}
|
||||
|
||||
{%- macro element_style(element) -%}
|
||||
{%- if element.element_style -%}
|
||||
style = "{{ element.element_style }}"
|
||||
{%- if element.element_style or element.background_color -%}
|
||||
style = "{{ element.element_style or '' }} {%if element.background_color %}background-color: {{ element.background_color }};{% endif %}"
|
||||
{%- endif -%}
|
||||
{%- endmacro -%}
|
||||
|
||||
|
||||
{%- macro render_sections(sections) -%}
|
||||
{%- for section in sections -%}
|
||||
<section class='section {{ section.element_class or "" }} {{ section.hide and "hidden" or "" }}'>
|
||||
<div class='section-body container'>
|
||||
<section class='section {{ section.element_class or "" }} {{ section.hide and "hidden" or "" }}' {{ element_style(section) }}>
|
||||
<div class='section-body {% if section.contain_section_width %}container{% endif %}'>
|
||||
{%- if section.section_intro -%}
|
||||
|
||||
<div class='section-intro'>{{ section.section_intro }}</div>
|
||||
{%- endif -%}
|
||||
|
||||
|
|
@ -74,4 +73,11 @@
|
|||
{%- endif -%}
|
||||
</div>
|
||||
</section>
|
||||
{%- endfor -%}
|
||||
{%- endfor -%}
|
||||
{%- endmacro -%}
|
||||
|
||||
{% if content_type == 'HTML' -%}
|
||||
{{ content_html }}
|
||||
{%- else -%}
|
||||
{{ render_sections(sections) }}
|
||||
{%- endif -%}
|
||||
|
|
@ -14,6 +14,7 @@ class TestWebView(unittest.TestCase):
|
|||
@classmethod
|
||||
def setUpClass(cls):
|
||||
frappe.delete_doc_if_exists('Web View', 'test-web-view')
|
||||
frappe.delete_doc_if_exists('Web View', 'html-web-view')
|
||||
frappe.delete_doc_if_exists('CSS Class', 'test-css-class')
|
||||
|
||||
frappe.get_doc(dict(
|
||||
|
|
@ -22,12 +23,25 @@ class TestWebView(unittest.TestCase):
|
|||
css = '.test-class { color: red; }'
|
||||
)).insert()
|
||||
|
||||
# simple html webview
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Web View',
|
||||
title = 'HTML Web View',
|
||||
route = 'html-web-view',
|
||||
published = 1,
|
||||
content_type = 'HTML',
|
||||
content_html = '<h1>Hello HTML</h1>'
|
||||
)).insert()
|
||||
|
||||
# simple web view with components
|
||||
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Web View',
|
||||
title = 'Test Web View',
|
||||
route = 'test-web-view',
|
||||
published = 1,
|
||||
items = [
|
||||
content_type = 'Components',
|
||||
components = [
|
||||
dict(
|
||||
element_type = 'Section',
|
||||
section_type = 'List'
|
||||
|
|
@ -57,19 +71,27 @@ class TestWebView(unittest.TestCase):
|
|||
web_content_type = 'Markdown',
|
||||
web_content_markdown = 'Column 2'
|
||||
),
|
||||
dict(
|
||||
element_type = 'Web View',
|
||||
web_view = 'html-web-view',
|
||||
),
|
||||
]
|
||||
)).insert()
|
||||
|
||||
def test_web_view(self):
|
||||
html = get_page_content('test-web-view')
|
||||
#print(html)
|
||||
self.assert_web_view_in_html(html)
|
||||
|
||||
def test_html_web_view(self):
|
||||
html = get_page_content('html-web-view')
|
||||
self.assertTrue('Hello HTML' in 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('Hello HTML' in html)
|
||||
self.assertTrue('.test-class { color: red; }' in html)
|
||||
|
||||
def test_web_view_in_footer(self):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
"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",
|
||||
|
|
@ -12,18 +11,21 @@
|
|||
"field_order": [
|
||||
"title",
|
||||
"route",
|
||||
"column_break_4",
|
||||
"full_width",
|
||||
"published",
|
||||
"items",
|
||||
"css"
|
||||
"section_break_6",
|
||||
"content_type",
|
||||
"content_html",
|
||||
"components",
|
||||
"style_section",
|
||||
"css",
|
||||
"metatags_section",
|
||||
"meta_title",
|
||||
"meta_description",
|
||||
"meta_image"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Items",
|
||||
"options": "Web View Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
|
|
@ -36,8 +38,7 @@
|
|||
"fieldname": "route",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Route",
|
||||
"reqd": 1
|
||||
"label": "Route"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -49,12 +50,73 @@
|
|||
"fieldname": "css",
|
||||
"fieldtype": "Code",
|
||||
"label": "CSS"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "full_width",
|
||||
"fieldtype": "Check",
|
||||
"label": "Full Width"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Content"
|
||||
},
|
||||
{
|
||||
"default": "Components",
|
||||
"fieldname": "content_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Content Type",
|
||||
"options": "Components\nHTML",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.content_type==='Components'",
|
||||
"fieldname": "components",
|
||||
"fieldtype": "Table",
|
||||
"label": "Components",
|
||||
"options": "Web View Component"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.content_type===\"HTML\"",
|
||||
"fieldname": "content_html",
|
||||
"fieldtype": "HTML Editor",
|
||||
"label": "Content HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "style_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Style"
|
||||
},
|
||||
{
|
||||
"fieldname": "metatags_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Meta Tags"
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "meta_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Image"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"modified": "2020-04-15 23:58:12.208049",
|
||||
"modified": "2020-04-22 00:54:23.413077",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web View",
|
||||
|
|
|
|||
|
|
@ -10,49 +10,71 @@ import frappe
|
|||
|
||||
class WebView(WebsiteGenerator):
|
||||
def get_context(self, context):
|
||||
# group items into sections
|
||||
# group components into sections
|
||||
if self.content_type=='Components':
|
||||
self.build_components(context)
|
||||
|
||||
self.set_metatags(context)
|
||||
return context
|
||||
|
||||
def build_components(self, context):
|
||||
context.sections = []
|
||||
context.css_rules = []
|
||||
for item in self.items:
|
||||
if not context.sections and item.element_type!='Section':
|
||||
for component in self.components:
|
||||
if not context.sections and component.element_type!='Section':
|
||||
self.add_default_section(context)
|
||||
|
||||
if item.element_type=='Section':
|
||||
self.add_section(context, item)
|
||||
if component.element_type=='Section':
|
||||
self.add_section(context, component)
|
||||
else:
|
||||
self.add_item(context, item)
|
||||
self.add_component(context, component)
|
||||
|
||||
self.add_css_class(context, item)
|
||||
self.add_css_class(context, component)
|
||||
self.add_color(component)
|
||||
self.add_missing_semi(component)
|
||||
|
||||
return context
|
||||
|
||||
def add_section(self, context, item):
|
||||
item.elements = []
|
||||
context.sections.append(item)
|
||||
def add_section(self, context, component):
|
||||
component.elements = []
|
||||
context.sections.append(component)
|
||||
|
||||
if item.section_intro:
|
||||
item.section_intro = markdown(item.section_intro)
|
||||
if component.section_intro:
|
||||
component.section_intro = markdown(component.section_intro)
|
||||
|
||||
def add_item(self, context, item):
|
||||
if item.hide:
|
||||
def add_component(self, context, component):
|
||||
if component.hide:
|
||||
return
|
||||
|
||||
if item.web_content_type == 'Markdown':
|
||||
item.web_content_html = markdown(item.web_content_markdown)
|
||||
if component.element_type == 'Web View' and component.web_view:
|
||||
component.web_content_html = frappe.get_doc('Web View', component.web_view).render_content()
|
||||
|
||||
if item.title:
|
||||
item.element_id = frappe.scrub(item.title)
|
||||
elif component.web_content_type == 'Markdown':
|
||||
component.web_content_html = markdown(component.web_content_markdown)
|
||||
|
||||
context.sections[-1].elements.append(item)
|
||||
if component.title:
|
||||
component.element_id = frappe.scrub(component.title)
|
||||
|
||||
def add_css_class(self, context, item):
|
||||
context.sections[-1].elements.append(component)
|
||||
|
||||
def add_css_class(self, context, component):
|
||||
# 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 component.element_class and not component.hide:
|
||||
css, is_dynamic = frappe.db.get_value('CSS Class', component.element_class, ['css', 'is_dynamic'])
|
||||
if is_dynamic:
|
||||
css = frappe.render_template(css, self.get_theme())
|
||||
context.css_rules.append(css)
|
||||
|
||||
def add_color(self, component):
|
||||
# convert to css color
|
||||
if component.background_color and not component.hide:
|
||||
component.background_color = frappe.db.get_value('Color',
|
||||
component.background_color, 'color', cache=True)
|
||||
|
||||
def add_missing_semi(self, component):
|
||||
if component.element_style and not component.element_style.strip().endswith(';'):
|
||||
component.element_style = component.element_style.strip() + ';'
|
||||
|
||||
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()))
|
||||
|
|
@ -72,3 +94,11 @@ class WebView(WebsiteGenerator):
|
|||
title='Default Section',
|
||||
elements=[]
|
||||
))
|
||||
|
||||
def set_metatags(self, context):
|
||||
context.metatags = {
|
||||
"name": self.meta_title or context.title,
|
||||
"description": self.meta_description,
|
||||
"image": self.meta_image
|
||||
}
|
||||
|
||||
|
|
|
|||
0
frappe/website/doctype/web_view_component/__init__.py
Normal file
0
frappe/website/doctype/web_view_component/__init__.py
Normal file
|
|
@ -8,11 +8,14 @@
|
|||
"element_type",
|
||||
"title",
|
||||
"hide",
|
||||
"contain_section_width",
|
||||
"column_break_3",
|
||||
"columns",
|
||||
"background_color",
|
||||
"element_class",
|
||||
"element_style",
|
||||
"section_break_5",
|
||||
"web_view",
|
||||
"section_type",
|
||||
"web_content_type",
|
||||
"web_content_html",
|
||||
|
|
@ -26,33 +29,35 @@
|
|||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Element Type",
|
||||
"options": "Section\nContent\nParagraph\nWeb List\nWeb Form",
|
||||
"options": "Section\nContent\nImage\nWeb View",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "List",
|
||||
"depends_on": "eval:doc.element_type==='Section'",
|
||||
"fieldname": "section_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Section Type",
|
||||
"options": "\nList\nTabbed\nGrid"
|
||||
"options": "List\nTabbed\nGrid"
|
||||
},
|
||||
{
|
||||
"default": "Markdown",
|
||||
"depends_on": "eval:doc.element_type==='Content'",
|
||||
"fieldname": "web_content_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Web Content Type",
|
||||
"options": "\nHTML\nMarkdown"
|
||||
"options": "Markdown\nHTML"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.web_content_type==='HTML'",
|
||||
"depends_on": "eval:doc.element_type === 'Content' && doc.web_content_type === 'HTML'",
|
||||
"fieldname": "web_content_html",
|
||||
"fieldtype": "HTML Editor",
|
||||
"label": "Web Content HTML"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.web_content_type==='Markdown'",
|
||||
"depends_on": "eval:doc.element_type === 'Content' && doc.web_content_type === 'Markdown'",
|
||||
"fieldname": "web_content_markdown",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Web Content Markdown"
|
||||
|
|
@ -104,14 +109,34 @@
|
|||
"fieldname": "element_style",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Element Style"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.element_type==='Section'",
|
||||
"fieldname": "contain_section_width",
|
||||
"fieldtype": "Check",
|
||||
"label": "Contain Section Width"
|
||||
},
|
||||
{
|
||||
"fieldname": "background_color",
|
||||
"fieldtype": "Link",
|
||||
"label": "Background Color",
|
||||
"options": "Color"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.element_type==='Web View'",
|
||||
"fieldname": "web_view",
|
||||
"fieldtype": "Link",
|
||||
"label": "Web View",
|
||||
"options": "Web View"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-28 14:21:50.014823",
|
||||
"modified": "2020-04-19 03:02:53.233036",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web View Item",
|
||||
"name": "Web View Component",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
|
|
@ -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 WebViewComponent(Document):
|
||||
pass
|
||||
|
|
@ -56,6 +56,10 @@ frappe.ui.form.on('Website Settings', {
|
|||
});
|
||||
},
|
||||
|
||||
enable_view_tracking: function(frm) {
|
||||
frappe.boot.website_tracking_enabled = frm.doc.enable_view_tracking;
|
||||
},
|
||||
|
||||
set_parent_options: function(frm, doctype, name) {
|
||||
var item = frappe.get_doc(doctype, name);
|
||||
if(item.parentfield === "top_bar_items") {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"footer_items",
|
||||
"hide_footer_signup",
|
||||
"integrations",
|
||||
"enable_view_tracking",
|
||||
"enable_google_indexing",
|
||||
"authorize_api_indexing_access",
|
||||
"indexing_refresh_token",
|
||||
|
|
@ -196,7 +197,7 @@
|
|||
"collapsible": 1,
|
||||
"fieldname": "integrations",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Google Integrations"
|
||||
"label": "Integrations"
|
||||
},
|
||||
{
|
||||
"description": "Add Google Analytics ID: eg. UA-89XXX57-1. Please search help on Google Analytics for more information.",
|
||||
|
|
@ -330,6 +331,12 @@
|
|||
"fieldtype": "Button",
|
||||
"label": "Authorize API Indexing Access"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_view_tracking",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable In App Website Tracking"
|
||||
},
|
||||
{
|
||||
"default": "Standard",
|
||||
"fieldname": "footer_type",
|
||||
|
|
@ -364,7 +371,7 @@
|
|||
"issingle": 1,
|
||||
"links": [],
|
||||
"max_attachments": 10,
|
||||
"modified": "2020-04-21 16:46:59.947403",
|
||||
"modified": "2020-04-21 12:37:44.070662",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Website Settings",
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ def get_website_settings():
|
|||
for k in ["banner_html", "brand_html", "copyright", "twitter_share_via",
|
||||
"facebook_share", "google_plus_one", "twitter_share", "linked_in_share",
|
||||
"disable_signup", "hide_footer_signup", "head_html", "title_prefix",
|
||||
"navbar_search"]:
|
||||
"navbar_search", "enable_view_tracking"]:
|
||||
if hasattr(settings, k):
|
||||
context[k] = settings.get(k)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@
|
|||
"google_font",
|
||||
"font_size",
|
||||
"font_properties",
|
||||
"use_full_width",
|
||||
"column_break_7",
|
||||
"button_rounded_corners",
|
||||
"button_shadows",
|
||||
"button_gradients",
|
||||
"column_break_11",
|
||||
"primary_color",
|
||||
"text_color",
|
||||
"light_color",
|
||||
|
|
@ -99,29 +101,29 @@
|
|||
"fieldtype": "Data",
|
||||
"label": "Font Size"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "primary_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Primary Color"
|
||||
"fieldtype": "Link",
|
||||
"label": "Primary Color",
|
||||
"options": "Color"
|
||||
},
|
||||
{
|
||||
"fieldname": "text_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Text Color"
|
||||
"fieldtype": "Link",
|
||||
"label": "Text Color",
|
||||
"options": "Color"
|
||||
},
|
||||
{
|
||||
"fieldname": "dark_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Dark Color"
|
||||
"fieldtype": "Link",
|
||||
"label": "Dark Color",
|
||||
"options": "Color"
|
||||
},
|
||||
{
|
||||
"fieldname": "background_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Background Color"
|
||||
"fieldtype": "Link",
|
||||
"label": "Background Color",
|
||||
"options": "Color"
|
||||
},
|
||||
{
|
||||
"fieldname": "stylesheet_section",
|
||||
|
|
@ -135,8 +137,9 @@
|
|||
},
|
||||
{
|
||||
"fieldname": "light_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Light Color"
|
||||
"fieldtype": "Link",
|
||||
"label": "Light Color",
|
||||
"options": "Color"
|
||||
},
|
||||
{
|
||||
"default": "300,600",
|
||||
|
|
@ -145,14 +148,30 @@
|
|||
"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"
|
||||
"default": "1",
|
||||
"fieldname": "button_rounded_corners",
|
||||
"fieldtype": "Check",
|
||||
"label": "Button Rounded Corners"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "button_shadows",
|
||||
"fieldtype": "Check",
|
||||
"label": "Button Shadows"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "button_gradients",
|
||||
"fieldtype": "Check",
|
||||
"label": "Button Gradients"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-03-19 09:46:48.750150",
|
||||
"modified": "2020-04-19 05:18:49.820803",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Website Theme",
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class WebsiteTheme(Document):
|
|||
def is_standard_and_not_valid_user(self):
|
||||
return (not self.custom
|
||||
and not frappe.local.conf.get('developer_mode')
|
||||
and not (frappe.flags.in_import or frappe.flags.in_test))
|
||||
and not (frappe.flags.in_import or frappe.flags.in_test or frappe.flags.in_migrate))
|
||||
|
||||
def on_trash(self):
|
||||
if self.is_standard_and_not_valid_user():
|
||||
|
|
@ -61,10 +61,13 @@ class WebsiteTheme(Document):
|
|||
from subprocess import Popen, PIPE
|
||||
|
||||
folder_path = join_path(frappe.utils.get_bench_path(), 'sites', 'assets', 'css')
|
||||
self.delete_old_theme_files(folder_path)
|
||||
|
||||
if not self.custom:
|
||||
self.delete_old_theme_files(folder_path)
|
||||
|
||||
# add a random suffix
|
||||
file_name = frappe.scrub(self.name) + '_' + frappe.generate_hash('Website Theme', 8) + '.css'
|
||||
suffix = frappe.generate_hash('Website Theme', 8) if self.custom else 'style'
|
||||
file_name = frappe.scrub(self.name) + '_' + suffix + '.css'
|
||||
output_path = join_path(folder_path, file_name)
|
||||
|
||||
content = get_scss(self)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue