Merge branch 'develop' of https://github.com/frappe/frappe into add-route-index-build-as-a-background-job
This commit is contained in:
commit
48bc68c130
166 changed files with 2919 additions and 2043 deletions
|
|
@ -18,6 +18,7 @@ context('Form', () => {
|
|||
cy.get('.primary-action').click();
|
||||
cy.wait('@form_save').its('response.statusCode').should('eq', 200);
|
||||
cy.visit('/app/todo');
|
||||
cy.wait(300);
|
||||
cy.get('.title-text').should('be.visible').and('contain', 'To Do');
|
||||
cy.get('.list-row').should('contain', 'this is a test todo');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -258,12 +258,17 @@ function get_watch_config() {
|
|||
async function clean_dist_folders(apps) {
|
||||
for (let app of apps) {
|
||||
let public_path = get_public_path(app);
|
||||
await fs.promises.rmdir(path.resolve(public_path, "dist", "js"), {
|
||||
recursive: true
|
||||
});
|
||||
await fs.promises.rmdir(path.resolve(public_path, "dist", "css"), {
|
||||
recursive: true
|
||||
});
|
||||
let paths = [
|
||||
path.resolve(public_path, "dist", "js"),
|
||||
path.resolve(public_path, "dist", "css")
|
||||
];
|
||||
for (let target of paths) {
|
||||
if (fs.existsSync(target)) {
|
||||
// rmdir is deprecated in node 16, this will work in both node 14 and 16
|
||||
let rmdir = fs.promises.rm || fs.promises.rmdir;
|
||||
await rmdir(target, { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1491,7 +1491,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None,
|
|||
:param style: Print Format style.
|
||||
:param as_pdf: Return as PDF. Default False.
|
||||
:param password: Password to encrypt the pdf with. Default None"""
|
||||
from frappe.website.render import build_page
|
||||
from frappe.website.serve import get_response_content
|
||||
from frappe.utils.pdf import get_pdf
|
||||
|
||||
local.form_dict.doctype = doctype
|
||||
|
|
@ -1506,7 +1506,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None,
|
|||
options = {'password': password}
|
||||
|
||||
if not html:
|
||||
html = build_page("printview")
|
||||
html = get_response_content("printview")
|
||||
|
||||
if as_pdf:
|
||||
return get_pdf(html, output = output, options = options)
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ import frappe.handler
|
|||
import frappe.auth
|
||||
import frappe.api
|
||||
import frappe.utils.response
|
||||
import frappe.website.render
|
||||
from frappe.utils import get_site_name, sanitize_html
|
||||
from frappe.middlewares import StaticDataMiddleware
|
||||
from frappe.website.serve import get_response
|
||||
from frappe.utils.error import make_error_snapshot
|
||||
from frappe.core.doctype.comment.comment import update_comments_in_parent_after_request
|
||||
from frappe import _
|
||||
|
|
@ -72,7 +72,7 @@ def application(request):
|
|||
response = frappe.utils.response.download_private_file(request.path)
|
||||
|
||||
elif request.method in ('GET', 'HEAD', 'POST'):
|
||||
response = frappe.website.render.render()
|
||||
response = get_response()
|
||||
|
||||
else:
|
||||
raise NotFound
|
||||
|
|
@ -266,8 +266,7 @@ def handle_exception(e):
|
|||
make_error_snapshot(e)
|
||||
|
||||
if return_as_message:
|
||||
response = frappe.website.render.render("message",
|
||||
http_status_code=http_status_code)
|
||||
response = get_response("message", http_status_code=http_status_code)
|
||||
|
||||
return response
|
||||
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ class AutoRepeat(Document):
|
|||
if self.reference_doctype and self.reference_document:
|
||||
res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id'])
|
||||
res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id'])
|
||||
email_ids = list(set([d.email_id for d in res]))
|
||||
email_ids = {d.email_id for d in res}
|
||||
if not email_ids:
|
||||
frappe.msgprint(_('No contacts linked to document'), alert=True)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ def clear_domain_cache(user=None):
|
|||
cache.delete_value(domain_cache_keys)
|
||||
|
||||
def clear_global_cache():
|
||||
from frappe.website.render import clear_cache as clear_website_cache
|
||||
from frappe.website.utils import clear_website_cache
|
||||
|
||||
clear_doctype_cache()
|
||||
clear_website_cache()
|
||||
|
|
|
|||
|
|
@ -69,14 +69,14 @@ def watch(apps=None):
|
|||
def clear_cache(context):
|
||||
"Clear cache, doctype cache and defaults"
|
||||
import frappe.sessions
|
||||
import frappe.website.render
|
||||
from frappe.website.utils import clear_website_cache
|
||||
from frappe.desk.notifications import clear_notifications
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.connect(site)
|
||||
frappe.clear_cache()
|
||||
clear_notifications()
|
||||
frappe.website.render.clear_cache()
|
||||
clear_website_cache()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
if not context.sites:
|
||||
|
|
@ -86,12 +86,12 @@ def clear_cache(context):
|
|||
@pass_context
|
||||
def clear_website_cache(context):
|
||||
"Clear website cache"
|
||||
import frappe.website.render
|
||||
from frappe.website.utils import clear_website_cache
|
||||
for site in context.sites:
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.website.render.clear_cache()
|
||||
clear_website_cache()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
if not context.sites:
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil
|
|||
doctypes = frappe.db.get_all("DocField", filters=filters, fields=["parent"],
|
||||
distinct=True, as_list=True)
|
||||
|
||||
doctypes = tuple([d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)])
|
||||
doctypes = tuple(d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE))
|
||||
|
||||
filters.update({
|
||||
"dt": ("not in", [d[0] for d in doctypes])
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ def address_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
|
||||
def get_condensed_address(doc):
|
||||
fields = ["address_title", "address_line1", "address_line2", "city", "county", "state", "country"]
|
||||
return ", ".join([doc.get(d) for d in fields if doc.get(d)])
|
||||
return ", ".join(doc.get(d) for d in fields if doc.get(d))
|
||||
|
||||
def update_preferred_address(address, field):
|
||||
frappe.db.set_value('Address', address, field, 0)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from frappe.core.doctype.user.user import extract_mentions
|
|||
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\
|
||||
get_title, get_title_html
|
||||
from frappe.utils import get_fullname
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.website.utils import clear_cache
|
||||
from frappe.database.schema import add_column
|
||||
from frappe.exceptions import ImplicitCommitError
|
||||
|
||||
|
|
|
|||
|
|
@ -449,7 +449,7 @@ class ImportFile:
|
|||
for row in data_without_first_row:
|
||||
row_values = row.get_values(parent_column_indexes)
|
||||
# if the row is blank, it's a child row doc
|
||||
if all([v in INVALID_VALUES for v in row_values]):
|
||||
if all(v in INVALID_VALUES for v in row_values):
|
||||
rows.append(row)
|
||||
continue
|
||||
# if we encounter a row which has values in parent columns,
|
||||
|
|
@ -606,7 +606,7 @@ class Row:
|
|||
if df.fieldtype == "Select":
|
||||
select_options = get_select_options(df)
|
||||
if select_options and value not in select_options:
|
||||
options_string = ", ".join([frappe.bold(d) for d in select_options])
|
||||
options_string = ", ".join(frappe.bold(d) for d in select_options)
|
||||
msg = _("Value must be one of {0}").format(options_string)
|
||||
self.warnings.append(
|
||||
{"row": self.row_number, "field": df_as_json(df), "message": msg,}
|
||||
|
|
@ -902,7 +902,7 @@ class Column:
|
|||
|
||||
if self.df.fieldtype == "Link":
|
||||
# find all values that dont exist
|
||||
values = list(set([cstr(v) for v in self.column_values[1:] if v]))
|
||||
values = list({cstr(v) for v in self.column_values[1:] if v})
|
||||
exists = [
|
||||
d.name for d in frappe.db.get_all(self.df.options, filters={"name": ("in", values)})
|
||||
]
|
||||
|
|
@ -935,11 +935,11 @@ class Column:
|
|||
elif self.df.fieldtype == "Select":
|
||||
options = get_select_options(self.df)
|
||||
if options:
|
||||
values = list(set([cstr(v) for v in self.column_values[1:] if v]))
|
||||
invalid = list(set(values) - set(options))
|
||||
values = {cstr(v) for v in self.column_values[1:] if v}
|
||||
invalid = values - set(options)
|
||||
if invalid:
|
||||
valid_values = ", ".join([frappe.bold(o) for o in options])
|
||||
invalid_values = ", ".join([frappe.bold(i) for i in invalid])
|
||||
valid_values = ", ".join(frappe.bold(o) for o in options)
|
||||
invalid_values = ", ".join(frappe.bold(i) for i in invalid)
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
|
|||
if d.get("name") and d["name"].startswith('"'):
|
||||
d["name"] = d["name"][1:-1]
|
||||
|
||||
if sum([0 if not val else 1 for val in d.values()]):
|
||||
if sum(0 if not val else 1 for val in d.values()):
|
||||
d['doctype'] = dt
|
||||
if dt == doctype:
|
||||
doc.update(d)
|
||||
|
|
@ -533,6 +533,6 @@ def get_parent_field(doctype, parenttype):
|
|||
|
||||
def delete_child_rows(rows, doctype):
|
||||
"""delete child rows for all parents"""
|
||||
for p in list(set([r[1] for r in rows])):
|
||||
for p in list(set(r[1] for r in rows)):
|
||||
if p:
|
||||
frappe.db.sql("""delete from `tab{0}` where parent=%s""".format(doctype), p)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from frappe.cache_manager import clear_user_cache, clear_controller_cache
|
|||
|
||||
# imports - module imports
|
||||
import frappe
|
||||
import frappe.website.render
|
||||
from frappe import _
|
||||
from frappe.utils import now, cint
|
||||
from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields, data_field_options
|
||||
|
|
@ -23,6 +22,7 @@ from frappe.model.docfield import supports_translation
|
|||
from frappe.modules.import_file import get_file_path
|
||||
from frappe.model.meta import Meta
|
||||
from frappe.desk.utils import validate_route_conflict
|
||||
from frappe.website.utils import clear_cache
|
||||
|
||||
class InvalidFieldNameError(frappe.ValidationError): pass
|
||||
class UniqueFieldnameError(frappe.ValidationError): pass
|
||||
|
|
@ -193,7 +193,7 @@ class DocType(Document):
|
|||
|
||||
self.flags.update_fields_to_fetch_queries = []
|
||||
|
||||
if set(old_fields_to_fetch) != set([df.fieldname for df in new_meta.get_fields_to_fetch()]):
|
||||
if set(old_fields_to_fetch) != set(df.fieldname for df in new_meta.get_fields_to_fetch()):
|
||||
for df in new_meta.get_fields_to_fetch():
|
||||
if df.fieldname not in old_fields_to_fetch:
|
||||
link_fieldname, source_fieldname = df.fetch_from.split('.', 1)
|
||||
|
|
@ -248,7 +248,7 @@ class DocType(Document):
|
|||
frappe.throw(_('Field "route" is mandatory for Web Views'), title='Missing Field')
|
||||
|
||||
# clear website cache
|
||||
frappe.website.render.clear_cache()
|
||||
clear_cache()
|
||||
|
||||
def change_modified_of_parent(self):
|
||||
"""Change the timestamp of parent DocType if the current one is a child to clear caches."""
|
||||
|
|
@ -550,11 +550,6 @@ class DocType(Document):
|
|||
from frappe.modules.export_file import export_to_files
|
||||
export_to_files(record_list=[['DocType', self.name]], create_init=True)
|
||||
|
||||
def import_doc(self):
|
||||
"""Import from standard folder `[module]/doctype/[name]/[name].json`."""
|
||||
from frappe.modules.import_module import import_from_files
|
||||
import_from_files(record_list=[[self.module, 'doctype', self.name]])
|
||||
|
||||
def make_controller_template(self):
|
||||
"""Make boilerplate controller template."""
|
||||
make_boilerplate("controller._py", self)
|
||||
|
|
@ -762,7 +757,7 @@ def validate_fields(meta):
|
|||
invalid_fields = ('doctype',)
|
||||
if fieldname in invalid_fields:
|
||||
frappe.throw(_("{0}: Fieldname cannot be one of {1}")
|
||||
.format(docname, ", ".join([frappe.bold(d) for d in invalid_fields])))
|
||||
.format(docname, ", ".join(frappe.bold(d) for d in invalid_fields)))
|
||||
|
||||
def check_unique_fieldname(docname, fieldname):
|
||||
duplicates = list(filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields)))
|
||||
|
|
@ -996,7 +991,7 @@ def validate_fields(meta):
|
|||
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>"
|
||||
df_options_str = "<ul><li>" + "</li><li>".join([_(x) for x in data_field_options]) + "</ul>"
|
||||
df_options_str = "<ul><li>" + "</li><li>".join(_(x) for x in data_field_options) + "</ul>"
|
||||
|
||||
frappe.msgprint(text_str + df_options_str, title="Invalid Data Field", raise_exception=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ class Domain(Document):
|
|||
|
||||
# enable
|
||||
frappe.db.sql('''update `tabPortal Menu Item` set enabled=1
|
||||
where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.allow_sidebar_items])))
|
||||
where route in ({0})'''.format(', '.join('"{0}"'.format(d) for d in self.data.allow_sidebar_items)))
|
||||
|
||||
if self.data.remove_sidebar_items:
|
||||
# disable all
|
||||
|
|
@ -118,4 +118,4 @@ class Domain(Document):
|
|||
|
||||
# enable
|
||||
frappe.db.sql('''update `tabPortal Menu Item` set enabled=0
|
||||
where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.remove_sidebar_items])))
|
||||
where route in ({0})'''.format(', '.join('"{0}"'.format(d) for d in self.data.remove_sidebar_items)))
|
||||
|
|
|
|||
0
frappe/core/doctype/feedback/__init__.py
Normal file
0
frappe/core/doctype/feedback/__init__.py
Normal file
8
frappe/core/doctype/feedback/feedback.js
Normal file
8
frappe/core/doctype/feedback/feedback.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2021, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Feedback', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
86
frappe/core/doctype/feedback/feedback.json
Normal file
86
frappe/core/doctype/feedback/feedback.json
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2021-06-03 19:02:55.328423",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_doctype",
|
||||
"reference_name",
|
||||
"column_break_3",
|
||||
"email",
|
||||
"rating",
|
||||
"section_break_6",
|
||||
"feedback"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "email",
|
||||
"fieldtype": "Data",
|
||||
"label": "Email",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rating",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Rating",
|
||||
"precision": "1",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "feedback",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Feedback",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Document Type",
|
||||
"options": "\nBlog Post"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"options": "reference_doctype",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-14 15:11:26.005805",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Feedback",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"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",
|
||||
"title_field": "reference_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
8
frappe/core/doctype/feedback/feedback.py
Normal file
8
frappe/core/doctype/feedback/feedback.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Feedback(Document):
|
||||
pass
|
||||
27
frappe/core/doctype/feedback/test_feedback.py
Normal file
27
frappe/core/doctype/feedback/test_feedback.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2021, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestFeedback(unittest.TestCase):
|
||||
def test_feedback_creation_updation(self):
|
||||
from frappe.website.doctype.blog_post.test_blog_post import make_test_blog
|
||||
test_blog = make_test_blog()
|
||||
|
||||
frappe.db.sql("delete from `tabFeedback` where reference_doctype = 'Blog Post'")
|
||||
|
||||
from frappe.templates.includes.feedback.feedback import add_feedback, update_feedback
|
||||
feedback = add_feedback('Blog Post', test_blog.name, 5, 'New feedback','test@test.com')
|
||||
|
||||
self.assertEqual(feedback.feedback, 'New feedback')
|
||||
self.assertEqual(feedback.rating, 5)
|
||||
|
||||
updated_feedback = update_feedback('Blog Post', test_blog.name, 6, 'Updated feedback', 'test@test.com')
|
||||
|
||||
self.assertEqual(updated_feedback.feedback, 'Updated feedback')
|
||||
self.assertEqual(updated_feedback.rating, 6)
|
||||
|
||||
frappe.db.sql("delete from `tabFeedback` where reference_doctype = 'Blog Post'")
|
||||
|
||||
test_blog.delete()
|
||||
|
|
@ -13,7 +13,7 @@ from frappe.utils.password import update_password as _update_password, check_pas
|
|||
from frappe.desk.notifications import clear_notifications
|
||||
from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings, toggle_notifications
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.website.utils import is_signup_enabled
|
||||
from frappe.website.utils import is_signup_disabled
|
||||
from frappe.rate_limiter import rate_limit
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.core.doctype.user_type.user_type import user_linked_with_permission_on_doctype
|
||||
|
|
@ -839,7 +839,7 @@ def verify_password(password):
|
|||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def sign_up(email, full_name, redirect_to):
|
||||
if not is_signup_enabled():
|
||||
if is_signup_disabled():
|
||||
frappe.throw(_('Sign Up is disabled'), title='Not Allowed')
|
||||
|
||||
user = frappe.db.get("User", {"email": email})
|
||||
|
|
@ -931,7 +931,7 @@ def user_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
LIMIT %(page_len)s OFFSET %(start)s
|
||||
""".format(
|
||||
user_type_condition = user_type_condition,
|
||||
standard_users=", ".join([frappe.db.escape(u) for u in STANDARD_USERS]),
|
||||
standard_users=", ".join(frappe.db.escape(u) for u in STANDARD_USERS),
|
||||
key=searchfield,
|
||||
fcond=get_filters_cond(doctype, filters, conditions),
|
||||
mcond=get_match_cond(doctype)
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@ class UserPermission(Document):
|
|||
self.validate_default_permission()
|
||||
|
||||
def on_update(self):
|
||||
frappe.cache().delete_value('user_permissions')
|
||||
frappe.cache().hdel('user_permissions', self.user)
|
||||
frappe.publish_realtime('update_user_permissions')
|
||||
|
||||
def on_trash(self): # pylint: disable=no-self-use
|
||||
frappe.cache().delete_value('user_permissions')
|
||||
frappe.cache().hdel('user_permissions', self.user)
|
||||
frappe.publish_realtime('update_user_permissions')
|
||||
|
||||
def validate_user_permission(self):
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ class UserType(Document):
|
|||
self.select_doctypes = []
|
||||
|
||||
select_doctypes = []
|
||||
user_doctypes = tuple([row.document_type for row in self.user_doctypes])
|
||||
user_doctypes = [row.document_type for row in self.user_doctypes]
|
||||
|
||||
for doctype in user_doctypes:
|
||||
doc = frappe.get_meta(doctype)
|
||||
|
|
@ -265,4 +265,4 @@ def apply_permissions_for_non_standard_user_type(doc, method=None):
|
|||
user_doc.update_children()
|
||||
add_user_permission(doc.doctype, doc.name, doc.get(data[1]))
|
||||
else:
|
||||
frappe.db.set_value('User Permission', perm_data[0], 'user', doc.get(data[1]))
|
||||
frappe.db.set_value('User Permission', perm_data[0], 'user', doc.get(data[1]))
|
||||
|
|
|
|||
|
|
@ -355,9 +355,9 @@ class CustomizeForm(Document):
|
|||
|
||||
def delete_custom_fields(self):
|
||||
meta = frappe.get_meta(self.doc_type)
|
||||
fields_to_remove = (set([df.fieldname for df in meta.get("fields")])
|
||||
- set(df.fieldname for df in self.get("fields")))
|
||||
|
||||
fields_to_remove = (
|
||||
{df.fieldname for df in meta.get("fields")} - {df.fieldname for df in self.get("fields")}
|
||||
)
|
||||
for fieldname in fields_to_remove:
|
||||
df = meta.get("fields", {"fieldname": fieldname})[0]
|
||||
if df.get("is_custom_field"):
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ class Database(object):
|
|||
values[key] = value[1]
|
||||
if isinstance(value[1], (tuple, list)):
|
||||
# value is a list in tuple ("in", ("A", "B"))
|
||||
_rhs = " ({0})".format(", ".join([self.escape(v) for v in value[1]]))
|
||||
_rhs = " ({0})".format(", ".join(self.escape(v) for v in value[1]))
|
||||
del values[key]
|
||||
|
||||
if _operator not in ["=", "!=", ">", ">=", "<", "<=", "like", "in", "not in", "not like"]:
|
||||
|
|
@ -1010,7 +1010,7 @@ class Database(object):
|
|||
:params values: list of list of values
|
||||
"""
|
||||
insert_list = []
|
||||
fields = ", ".join(["`"+field+"`" for field in fields])
|
||||
fields = ", ".join("`"+field+"`" for field in fields)
|
||||
|
||||
for idx, value in enumerate(values):
|
||||
insert_list.append(tuple(value))
|
||||
|
|
|
|||
0
frappe/desk/doctype/form_tour/__init__.py
Normal file
0
frappe/desk/doctype/form_tour/__init__.py
Normal file
24
frappe/desk/doctype/form_tour/form_tour.js
Normal file
24
frappe/desk/doctype/form_tour/form_tour.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) 2021, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Form Tour', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("reference_doctype", function() {
|
||||
return {
|
||||
filters: {
|
||||
istable: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("field", "steps", function() {
|
||||
return {
|
||||
query: "frappe.desk.doctype.form_tour.form_tour.get_docfield_list",
|
||||
filters: {
|
||||
doctype: frm.doc.reference_doctype,
|
||||
hidden: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
75
frappe/desk/doctype/form_tour/form_tour.json
Normal file
75
frappe/desk/doctype/form_tour/form_tour.json
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "field:title",
|
||||
"creation": "2021-05-21 23:02:52.242721",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"reference_doctype",
|
||||
"completed",
|
||||
"section_break_3",
|
||||
"steps"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Document",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "steps",
|
||||
"fieldtype": "Table",
|
||||
"label": "Steps",
|
||||
"options": "Form Tour Step",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.__islocal != 1",
|
||||
"fieldname": "completed",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mark as Completed"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-26 19:36:59.093753",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Form Tour",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"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
|
||||
}
|
||||
32
frappe/desk/doctype/form_tour/form_tour.py
Normal file
32
frappe/desk/doctype/form_tour/form_tour.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class FormTour(Document):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_docfield_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
or_filters = [
|
||||
['fieldname', 'like', '%' + txt + '%'],
|
||||
['label', 'like', '%' + txt + '%'],
|
||||
['fieldtype', 'like', '%' + txt + '%']
|
||||
]
|
||||
|
||||
parent_doctype = filters.pop('doctype')
|
||||
excluded_fieldtypes = ['Column Break']
|
||||
excluded_fieldtypes += filters.get('excluded_fieldtypes', [])
|
||||
|
||||
docfields = frappe.get_all(
|
||||
doctype,
|
||||
fields=["name as value", "label", "fieldtype"],
|
||||
filters={'parent': parent_doctype, 'fieldtype': ['not in', excluded_fieldtypes]},
|
||||
or_filters=or_filters,
|
||||
limit_start=start,
|
||||
limit_page_length=page_len,
|
||||
as_list=1,
|
||||
)
|
||||
return docfields
|
||||
8
frappe/desk/doctype/form_tour/test_form_tour.py
Normal file
8
frappe/desk/doctype/form_tour/test_form_tour.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Copyright (c) 2021, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestFormTour(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/desk/doctype/form_tour_step/__init__.py
Normal file
0
frappe/desk/doctype/form_tour_step/__init__.py
Normal file
85
frappe/desk/doctype/form_tour_step/form_tour_step.json
Normal file
85
frappe/desk/doctype/form_tour_step/form_tour_step.json
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2021-05-21 23:05:45.342114",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"field",
|
||||
"title",
|
||||
"description",
|
||||
"column_break_2",
|
||||
"position",
|
||||
"fieldname",
|
||||
"label",
|
||||
"condition"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 4,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "HTML Editor",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "field",
|
||||
"fieldtype": "Link",
|
||||
"label": "Field",
|
||||
"options": "DocField",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "field.fieldname",
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Fieldname",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "field.label",
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Bottom",
|
||||
"fieldname": "position",
|
||||
"fieldtype": "Select",
|
||||
"label": "Position",
|
||||
"options": "Left\nLeft Center\nLeft Bottom\nTop\nTop Center\nTop Right\nRight\nRight Center\nRight Bottom\nBottom\nBottom Center\nBottom Right\nMid Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "next_step_condition",
|
||||
"fieldtype": "Code",
|
||||
"label": "Next Step Condition",
|
||||
"options": "JS"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-26 19:44:48.737453",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Form Tour Step",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
8
frappe/desk/doctype/form_tour_step/form_tour_step.py
Normal file
8
frappe/desk/doctype/form_tour_step/form_tour_step.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Copyright (c) 2021, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class FormTourStep(Document):
|
||||
pass
|
||||
|
|
@ -21,7 +21,7 @@ class GlobalSearchSettings(Document):
|
|||
dts.append(dt.document_type)
|
||||
|
||||
if core_dts:
|
||||
core_dts = (", ".join([frappe.bold(dt) for dt in core_dts]))
|
||||
core_dts = ", ".join(frappe.bold(dt) for dt in core_dts)
|
||||
frappe.throw(_("Core Modules {0} cannot be searched in Global Search.").format(core_dts))
|
||||
|
||||
if repeated_dts:
|
||||
|
|
@ -60,7 +60,7 @@ def update_global_search_doctypes():
|
|||
if search_doctypes.get(domain):
|
||||
global_search_doctypes.extend(search_doctypes.get(domain))
|
||||
|
||||
doctype_list = set([dt.name for dt in frappe.get_all("DocType")])
|
||||
doctype_list = {dt.name for dt in frappe.get_all("DocType")}
|
||||
allowed_in_global_search = []
|
||||
|
||||
for dt in global_search_doctypes:
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ def update_tags(doc, tags):
|
|||
:param doc: Document to be added to global tags
|
||||
"""
|
||||
|
||||
new_tags = list(set([tag.strip() for tag in tags.split(",") if tag]))
|
||||
new_tags = {tag.strip() for tag in tags.split(",") if tag}
|
||||
|
||||
for tag in new_tags:
|
||||
if not frappe.db.exists("Tag Link", {"parenttype": doc.doctype, "parent": doc.name, "tag": tag}):
|
||||
|
|
@ -186,4 +186,4 @@ def get_documents_for_tag(tag):
|
|||
|
||||
@frappe.whitelist()
|
||||
def get_tags_list_for_awesomebar():
|
||||
return [t.name for t in frappe.get_list("Tag")]
|
||||
return [t.name for t in frappe.get_list("Tag")]
|
||||
|
|
|
|||
|
|
@ -55,8 +55,7 @@ class Workspace(Document):
|
|||
for link in self.links:
|
||||
link = link.as_dict()
|
||||
if link.type == "Card Break":
|
||||
|
||||
if card_links:
|
||||
if card_links and (not current_card.only_for or current_card.only_for == frappe.get_system_settings('country')):
|
||||
current_card['links'] = card_links
|
||||
cards.append(current_card)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +1,25 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
import frappe
|
||||
import email.utils
|
||||
import functools
|
||||
import imaplib
|
||||
import re
|
||||
import json
|
||||
import socket
|
||||
import time
|
||||
import functools
|
||||
|
||||
import email.utils
|
||||
|
||||
from frappe import _, are_emails_muted
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (validate_email_address, cint, cstr, get_datetime,
|
||||
DATE_FORMAT, strip, comma_or, sanitize_html, add_days, parse_addr)
|
||||
from frappe.utils.user import is_system_user
|
||||
from frappe.utils.jinja import render_template
|
||||
from frappe.email.smtp import SMTPServer
|
||||
from frappe.email.receive import EmailServer, InboundMail, SentEmailInInboxError
|
||||
from poplib import error_proto
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from datetime import datetime, timedelta
|
||||
from poplib import error_proto
|
||||
|
||||
import frappe
|
||||
from frappe import _, are_emails_muted, safe_encode
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.utils.background_jobs import enqueue, get_jobs
|
||||
from frappe.utils.html_utils import clean_email_html
|
||||
from frappe.utils.error import raise_error_on_no_output
|
||||
from frappe.email.receive import EmailServer, InboundMail, SentEmailInInboxError
|
||||
from frappe.email.smtp import SMTPServer
|
||||
from frappe.email.utils import get_port
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, comma_or, cstr, parse_addr, validate_email_address
|
||||
from frappe.utils.background_jobs import enqueue, get_jobs
|
||||
from frappe.utils.error import raise_error_on_no_output
|
||||
from frappe.utils.jinja import render_template
|
||||
from frappe.utils.user import get_system_managers
|
||||
|
||||
OUTGOING_EMAIL_ACCOUNT_MISSING = _("Please setup default Email Account from Setup > Email > Email Account")
|
||||
|
||||
|
|
@ -577,8 +570,8 @@ class EmailAccount(Document):
|
|||
email_server.update_flag(uid_list=uid_list)
|
||||
|
||||
# mark communication as read
|
||||
docnames = ",".join([ "'%s'"%flag.get("communication") for flag in flags \
|
||||
if flag.get("action") == "Read" ])
|
||||
docnames = ",".join("'%s'"%flag.get("communication") for flag in flags \
|
||||
if flag.get("action") == "Read")
|
||||
self.set_communication_seen_status(docnames, seen=1)
|
||||
|
||||
# mark communication as unread
|
||||
|
|
@ -608,7 +601,6 @@ class EmailAccount(Document):
|
|||
|
||||
|
||||
def append_email_to_sent_folder(self, message):
|
||||
|
||||
email_server = None
|
||||
try:
|
||||
email_server = self.get_incoming_server(in_receive=True)
|
||||
|
|
@ -622,7 +614,8 @@ class EmailAccount(Document):
|
|||
|
||||
if email_server.imap:
|
||||
try:
|
||||
email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message.encode())
|
||||
message = safe_encode(message)
|
||||
email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message)
|
||||
except Exception:
|
||||
frappe.log_error()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"creation": "2015-03-18 06:08:32.729800",
|
||||
"doctype": "DocType",
|
||||
|
|
@ -50,7 +51,7 @@
|
|||
"link_fieldname": "email_group"
|
||||
}
|
||||
],
|
||||
"modified": "2020-09-24 16:41:55.286377",
|
||||
"modified": "2021-06-15 11:25:13.556201",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Group",
|
||||
|
|
|
|||
|
|
@ -179,7 +179,14 @@ class SendMailContext:
|
|||
else:
|
||||
email_status = self.is_mail_sent_to_all() and 'Sent'
|
||||
email_status = email_status or (self.sent_to and 'Partially Sent') or 'Not Sent'
|
||||
self.queue_doc.update_status(status = email_status, commit = True)
|
||||
|
||||
update_fields = {'status': email_status}
|
||||
if self.email_account_doc.is_exists_in_db():
|
||||
update_fields['email_account'] = self.email_account_doc.name
|
||||
else:
|
||||
update_fields['email_account'] = None
|
||||
|
||||
self.queue_doc.update_status(**update_fields, commit = True)
|
||||
|
||||
def log_exception(self, exc_type, exc_val, exc_tb):
|
||||
if exc_type:
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class TestNewsletter(unittest.TestCase):
|
|||
email_queue_list = [frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue")]
|
||||
self.assertEqual(len(email_queue_list), 4)
|
||||
|
||||
recipients = set([e.recipients[0].recipient for e in email_queue_list])
|
||||
recipients = {e.recipients[0].recipient for e in email_queue_list}
|
||||
self.assertTrue(set(emails).issubset(recipients))
|
||||
|
||||
def test_unsubscribe(self):
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ def get_email_accounts(user=None):
|
|||
"all_accounts": ""
|
||||
}
|
||||
|
||||
all_accounts = ",".join([ account.get("email_account") for account in accounts ])
|
||||
all_accounts = ",".join(account.get("email_account") for account in accounts)
|
||||
if len(accounts) > 1:
|
||||
email_accounts.append({
|
||||
"email_account": all_accounts,
|
||||
|
|
|
|||
|
|
@ -6,15 +6,61 @@ from frappe import msgprint, _
|
|||
from frappe.utils.verified_command import get_signed_params, verify_request
|
||||
from frappe.utils import get_url, now_datetime, cint
|
||||
|
||||
def get_emails_sent_this_month():
|
||||
return frappe.db.sql("""
|
||||
SELECT COUNT(*) FROM `tabEmail Queue`
|
||||
WHERE `status`='Sent' AND EXTRACT(YEAR_MONTH FROM `creation`) = EXTRACT(YEAR_MONTH FROM NOW())
|
||||
""")[0][0]
|
||||
def get_emails_sent_this_month(email_account=None):
|
||||
"""Get count of emails sent from a specific email account.
|
||||
|
||||
def get_emails_sent_today():
|
||||
return frappe.db.sql("""SELECT COUNT(`name`) FROM `tabEmail Queue` WHERE
|
||||
`status` in ('Sent', 'Not Sent', 'Sending') AND `creation` > (NOW() - INTERVAL '24' HOUR)""")[0][0]
|
||||
:param email_account: name of the email account used to send mail
|
||||
|
||||
if email_account=None, email account filter is not applied while counting
|
||||
"""
|
||||
q = """
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
`tabEmail Queue`
|
||||
WHERE
|
||||
`status`='Sent'
|
||||
AND
|
||||
EXTRACT(YEAR_MONTH FROM `creation`) = EXTRACT(YEAR_MONTH FROM NOW())
|
||||
"""
|
||||
|
||||
q_args = {}
|
||||
if email_account is not None:
|
||||
if email_account:
|
||||
q += " AND email_account = %(email_account)s"
|
||||
q_args['email_account'] = email_account
|
||||
else:
|
||||
q += " AND (email_account is null OR email_account='')"
|
||||
|
||||
return frappe.db.sql(q, q_args)[0][0]
|
||||
|
||||
def get_emails_sent_today(email_account=None):
|
||||
"""Get count of emails sent from a specific email account.
|
||||
|
||||
:param email_account: name of the email account used to send mail
|
||||
|
||||
if email_account=None, email account filter is not applied while counting
|
||||
"""
|
||||
q = """
|
||||
SELECT
|
||||
COUNT(`name`)
|
||||
FROM
|
||||
`tabEmail Queue`
|
||||
WHERE
|
||||
`status` in ('Sent', 'Not Sent', 'Sending')
|
||||
AND
|
||||
`creation` > (NOW() - INTERVAL '24' HOUR)
|
||||
"""
|
||||
|
||||
q_args = {}
|
||||
if email_account is not None:
|
||||
if email_account:
|
||||
q += " AND email_account = %(email_account)s"
|
||||
q_args['email_account'] = email_account
|
||||
else:
|
||||
q += " AND (email_account is null OR email_account='')"
|
||||
|
||||
return frappe.db.sql(q, q_args)[0][0]
|
||||
|
||||
def get_unsubscribe_message(unsubscribe_message, expose_recipients):
|
||||
if unsubscribe_message:
|
||||
|
|
|
|||
|
|
@ -802,7 +802,7 @@ class InboundMail(Email):
|
|||
except frappe.DuplicateEntryError:
|
||||
# try and find matching parent
|
||||
parent_name = frappe.db.get_value(self.email_account.append_to,
|
||||
{email_fileds.sender_field: email.from_email}
|
||||
{email_fileds.sender_field: self.from_email}
|
||||
)
|
||||
if parent_name:
|
||||
parent.name = parent_name
|
||||
|
|
|
|||
|
|
@ -282,10 +282,10 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False)
|
|||
|
||||
|
||||
def post_install(rebuild_website=False):
|
||||
from frappe.website import render
|
||||
from frappe.website.utils import clear_website_cache
|
||||
|
||||
if rebuild_website:
|
||||
render.clear_cache()
|
||||
clear_website_cache()
|
||||
|
||||
init_singles()
|
||||
frappe.db.commit()
|
||||
|
|
@ -537,7 +537,7 @@ def is_downgrade(sql_file_path, verbose=False):
|
|||
|
||||
def is_partial(sql_file_path):
|
||||
with open(sql_file_path) as f:
|
||||
header = " ".join([f.readline() for _ in range(5)])
|
||||
header = " ".join(f.readline() for _ in range(5))
|
||||
if "Partial Backup" in header:
|
||||
return True
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class LDAPSettings(Document):
|
|||
|
||||
def sync_roles(self, user, additional_groups=None):
|
||||
|
||||
current_roles = set([d.role for d in user.get("roles")])
|
||||
current_roles = set(d.role for d in user.get("roles"))
|
||||
|
||||
needed_roles = set()
|
||||
needed_roles.add(self.default_role)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from frappe.utils.dashboard import sync_dashboards
|
|||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.cache_manager import clear_global_cache
|
||||
from frappe.desk.notifications import clear_notifications
|
||||
from frappe.website import render
|
||||
from frappe.website.utils import clear_website_cache
|
||||
from frappe.core.doctype.language.language import sync_languages
|
||||
from frappe.modules.utils import sync_customizations
|
||||
from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs
|
||||
|
|
@ -79,7 +79,7 @@ Otherwise, check the server logs and ensure that all the required services are r
|
|||
frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu()
|
||||
|
||||
# syncs statics
|
||||
render.clear_cache()
|
||||
clear_website_cache()
|
||||
|
||||
# updating installed applications data
|
||||
frappe.get_single('Installed Applications').update_versions()
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ def delete_fields(args_dict, delete=0):
|
|||
frappe.db.sql("""
|
||||
DELETE FROM `tabSingles`
|
||||
WHERE doctype='%s' AND field IN (%s)
|
||||
""" % (dt, ", ".join(["'{}'".format(f) for f in fields])))
|
||||
""" % (dt, ", ".join("'{}'".format(f) for f in fields)))
|
||||
else:
|
||||
existing_fields = frappe.db.multisql({
|
||||
"mariadb": "DESC `tab%s`" % dt,
|
||||
|
|
@ -188,7 +188,7 @@ def delete_fields(args_dict, delete=0):
|
|||
frappe.db.commit()
|
||||
|
||||
query = "ALTER TABLE `tab%s` " % dt + \
|
||||
", ".join(["DROP COLUMN `%s`" % f for f in fields_need_to_delete])
|
||||
", ".join("DROP COLUMN `%s`" % f for f in fields_need_to_delete)
|
||||
frappe.db.sql(query)
|
||||
|
||||
if frappe.db.db_type == 'postgres':
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ class BaseDocument(object):
|
|||
frappe.db.sql("""INSERT INTO `tab{doctype}` ({columns})
|
||||
VALUES ({values})""".format(
|
||||
doctype = self.doctype,
|
||||
columns = ", ".join(["`"+c+"`" for c in columns]),
|
||||
columns = ", ".join("`"+c+"`" for c in columns),
|
||||
values = ", ".join(["%s"] * len(columns))
|
||||
), list(d.values()))
|
||||
except Exception as e:
|
||||
|
|
@ -397,7 +397,7 @@ class BaseDocument(object):
|
|||
frappe.db.sql("""UPDATE `tab{doctype}`
|
||||
SET {values} WHERE `name`=%s""".format(
|
||||
doctype = self.doctype,
|
||||
values = ", ".join(["`"+c+"`=%s" for c in columns])
|
||||
values = ", ".join("`"+c+"`=%s" for c in columns)
|
||||
), list(d.values()) + [name])
|
||||
except Exception as e:
|
||||
if frappe.db.is_unique_key_violation(e):
|
||||
|
|
|
|||
|
|
@ -43,8 +43,14 @@ class DatabaseQuery(object):
|
|||
|
||||
# filters and fields swappable
|
||||
# its hard to remember what comes first
|
||||
if (isinstance(fields, dict)
|
||||
or (isinstance(fields, list) and fields and isinstance(fields[0], list))):
|
||||
if (
|
||||
isinstance(fields, dict)
|
||||
or (
|
||||
fields
|
||||
and isinstance(fields, list)
|
||||
and isinstance(fields[0], list)
|
||||
)
|
||||
):
|
||||
# if fields is given as dict/list of list, its probably filters
|
||||
filters, fields = fields, filters
|
||||
|
||||
|
|
@ -56,10 +62,7 @@ class DatabaseQuery(object):
|
|||
if fields:
|
||||
self.fields = fields
|
||||
else:
|
||||
if pluck:
|
||||
self.fields = ["`tab{0}`.`{1}`".format(self.doctype, pluck)]
|
||||
else:
|
||||
self.fields = ["`tab{0}`.`name`".format(self.doctype)]
|
||||
self.fields = [f"`tab{self.doctype}`.`{pluck or 'name'}`"]
|
||||
|
||||
if start: limit_start = start
|
||||
if page_length: limit_page_length = page_length
|
||||
|
|
@ -70,7 +73,7 @@ class DatabaseQuery(object):
|
|||
self.docstatus = docstatus or []
|
||||
self.group_by = group_by
|
||||
self.order_by = order_by
|
||||
self.limit_start = 0 if (limit_start is False) else cint(limit_start)
|
||||
self.limit_start = cint(limit_start)
|
||||
self.limit_page_length = cint(limit_page_length) if limit_page_length else None
|
||||
self.with_childnames = with_childnames
|
||||
self.debug = debug
|
||||
|
|
@ -157,11 +160,10 @@ class DatabaseQuery(object):
|
|||
|
||||
# left join parent, child tables
|
||||
for child in self.tables[1:]:
|
||||
args.tables += " {join} {child} on ({child}.parent = {main}.name)".format(join=self.join,
|
||||
child=child, main=self.tables[0])
|
||||
args.tables += f" {self.join} {child} on ({child}.parent = {self.tables[0]}.name)"
|
||||
|
||||
if self.grouped_or_conditions:
|
||||
self.conditions.append("({0})".format(" or ".join(self.grouped_or_conditions)))
|
||||
self.conditions.append(f"({' or '.join(self.grouped_or_conditions)})")
|
||||
|
||||
args.conditions = ' and '.join(self.conditions)
|
||||
|
||||
|
|
@ -186,9 +188,9 @@ class DatabaseQuery(object):
|
|||
fields.append(field)
|
||||
elif "as" in field.lower().split(" "):
|
||||
col, _, new = field.split()
|
||||
fields.append("`{0}` as {1}".format(col, new))
|
||||
fields.append(f"`{col}` as {new}")
|
||||
else:
|
||||
fields.append("`{0}`".format(field))
|
||||
fields.append(f"`{field}`")
|
||||
|
||||
args.fields = ", ".join(fields)
|
||||
|
||||
|
|
@ -260,10 +262,10 @@ class DatabaseQuery(object):
|
|||
if any(keyword in field.lower().split() for keyword in blacklisted_keywords):
|
||||
_raise_exception()
|
||||
|
||||
if any("({0}".format(keyword) in field.lower() for keyword in blacklisted_keywords):
|
||||
if any(f"({keyword}" in field.lower() for keyword in blacklisted_keywords):
|
||||
_raise_exception()
|
||||
|
||||
if any("{0}(".format(keyword) in field.lower() for keyword in blacklisted_functions):
|
||||
if any(f"{keyword}(" in field.lower() for keyword in blacklisted_functions):
|
||||
_raise_exception()
|
||||
|
||||
if '@' in field.lower():
|
||||
|
|
@ -287,22 +289,30 @@ class DatabaseQuery(object):
|
|||
|
||||
def extract_tables(self):
|
||||
"""extract tables from fields"""
|
||||
self.tables = ['`tab' + self.doctype + '`']
|
||||
|
||||
self.tables = [f"`tab{self.doctype}`"]
|
||||
sql_functions = [
|
||||
"dayofyear(",
|
||||
"extract(",
|
||||
"locate(",
|
||||
"strpos(",
|
||||
"count(",
|
||||
"sum(",
|
||||
"avg(",
|
||||
]
|
||||
# add tables from fields
|
||||
if self.fields:
|
||||
for f in self.fields:
|
||||
if ( not ("tab" in f and "." in f) ) or ("locate(" in f) or ("strpos(" in f) or \
|
||||
("count(" in f) or ("avg(" in f) or ("sum(" in f) or ("extract(" in f) or ("dayofyear(" in f):
|
||||
for field in self.fields:
|
||||
if not ("tab" in field and "." in field) or any(x for x in sql_functions if x in field):
|
||||
continue
|
||||
|
||||
table_name = f.split('.')[0]
|
||||
table_name = field.split('.')[0]
|
||||
|
||||
if table_name.lower().startswith('group_concat('):
|
||||
table_name = table_name[13:]
|
||||
if table_name.lower().startswith('ifnull('):
|
||||
table_name = table_name[7:]
|
||||
if not table_name[0]=='`':
|
||||
table_name = '`' + table_name + '`'
|
||||
table_name = f"`{table_name}`"
|
||||
if not table_name in self.tables:
|
||||
self.append_table(table_name)
|
||||
|
||||
|
|
@ -311,8 +321,7 @@ class DatabaseQuery(object):
|
|||
doctype = table_name[4:-1]
|
||||
ptype = 'select' if frappe.only_has_select_perm(doctype) else 'read'
|
||||
|
||||
if (not self.flags.ignore_permissions) and\
|
||||
(not frappe.has_permission(doctype, ptype=ptype)):
|
||||
if not self.flags.ignore_permissions and not frappe.has_permission(doctype, ptype=ptype):
|
||||
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(doctype))
|
||||
raise frappe.PermissionError(doctype)
|
||||
|
||||
|
|
@ -326,7 +335,7 @@ class DatabaseQuery(object):
|
|||
if len(self.tables) > 1:
|
||||
for idx, field in enumerate(self.fields):
|
||||
if '.' not in field and not _in_standard_sql_methods(field):
|
||||
self.fields[idx] = '{0}.{1}'.format(self.tables[0], field)
|
||||
self.fields[idx] = f"{self.tables[0]}.{field}"
|
||||
|
||||
def get_table_columns(self):
|
||||
try:
|
||||
|
|
@ -375,7 +384,7 @@ class DatabaseQuery(object):
|
|||
if not self.flags.ignore_permissions:
|
||||
match_conditions = self.build_match_conditions()
|
||||
if match_conditions:
|
||||
self.conditions.append("(" + match_conditions + ")")
|
||||
self.conditions.append(f"({match_conditions})")
|
||||
|
||||
def build_filter_conditions(self, filters, conditions, ignore_permissions=None):
|
||||
"""build conditions from user filters"""
|
||||
|
|
@ -407,8 +416,7 @@ class DatabaseQuery(object):
|
|||
if 'ifnull(' in f.fieldname:
|
||||
column_name = f.fieldname
|
||||
else:
|
||||
column_name = '{tname}.{fname}'.format(tname=tname,
|
||||
fname=f.fieldname)
|
||||
column_name = f"{tname}.{f.fieldname}"
|
||||
|
||||
can_be_null = True
|
||||
|
||||
|
|
@ -450,7 +458,7 @@ class DatabaseQuery(object):
|
|||
fallback = "''"
|
||||
value = [frappe.db.escape((v.name or '').strip(), percent=False) for v in result]
|
||||
if len(value):
|
||||
value = "({0})".format(", ".join(value))
|
||||
value = f"({', '.join(value)})"
|
||||
else:
|
||||
value = "('')"
|
||||
# changing operator to IN as the above code fetches all the parent / child values and convert into tuple
|
||||
|
|
@ -466,7 +474,7 @@ class DatabaseQuery(object):
|
|||
fallback = "''"
|
||||
value = [frappe.db.escape((v or '').strip(), percent=False) for v in values]
|
||||
if len(value):
|
||||
value = "({0})".format(", ".join(value))
|
||||
value = f"({', '.join(value)})"
|
||||
else:
|
||||
value = "('')"
|
||||
else:
|
||||
|
|
@ -503,7 +511,7 @@ class DatabaseQuery(object):
|
|||
can_be_null = True
|
||||
|
||||
if 'ifnull' not in column_name:
|
||||
column_name = 'ifnull({}, {})'.format(column_name, fallback)
|
||||
column_name = f'ifnull({column_name}, {fallback})'
|
||||
|
||||
elif df and df.fieldtype=="Date":
|
||||
value = frappe.db.format_date(f.value)
|
||||
|
|
@ -540,21 +548,19 @@ class DatabaseQuery(object):
|
|||
|
||||
# escape value
|
||||
if isinstance(value, str) and not f.operator.lower() == 'between':
|
||||
value = "{0}".format(frappe.db.escape(value, percent=False))
|
||||
value = f"{frappe.db.escape(value, percent=False)}"
|
||||
|
||||
if (self.ignore_ifnull
|
||||
if (
|
||||
self.ignore_ifnull
|
||||
or not can_be_null
|
||||
or (f.value and f.operator.lower() in ('=', 'like'))
|
||||
or 'ifnull(' in column_name.lower()):
|
||||
or 'ifnull(' in column_name.lower()
|
||||
):
|
||||
if f.operator.lower() == 'like' and frappe.conf.get('db_type') == 'postgres':
|
||||
f.operator = 'ilike'
|
||||
condition = '{column_name} {operator} {value}'.format(
|
||||
column_name=column_name, operator=f.operator,
|
||||
value=value)
|
||||
condition = f'{column_name} {f.operator} {value}'
|
||||
else:
|
||||
condition = 'ifnull({column_name}, {fallback}) {operator} {value}'.format(
|
||||
column_name=column_name, fallback=fallback, operator=f.operator,
|
||||
value=value)
|
||||
condition = f'ifnull({column_name}, {fallback}) {f.operator} {value}'
|
||||
|
||||
return condition
|
||||
|
||||
|
|
@ -572,10 +578,12 @@ class DatabaseQuery(object):
|
|||
role_permissions = frappe.permissions.get_role_permissions(meta, user=self.user)
|
||||
self.shared = frappe.share.get_shared(self.doctype, self.user)
|
||||
|
||||
if (not meta.istable and
|
||||
if (
|
||||
not meta.istable and
|
||||
not (role_permissions.get("select") or role_permissions.get("read")) and
|
||||
not self.flags.ignore_permissions and
|
||||
not has_any_user_permission_for_doctype(self.doctype, self.user, self.reference_doctype)):
|
||||
not has_any_user_permission_for_doctype(self.doctype, self.user, self.reference_doctype)
|
||||
):
|
||||
only_if_shared = True
|
||||
if not self.shared:
|
||||
frappe.throw(_("No permission to read {0}").format(self.doctype), frappe.PermissionError)
|
||||
|
|
@ -585,8 +593,10 @@ class DatabaseQuery(object):
|
|||
else:
|
||||
#if has if_owner permission skip user perm check
|
||||
if role_permissions.get("has_if_owner_enabled") and role_permissions.get("if_owner", {}):
|
||||
self.match_conditions.append("`tab{0}`.`owner` = {1}".format(self.doctype,
|
||||
frappe.db.escape(self.user, percent=False)))
|
||||
self.match_conditions.append(
|
||||
f"`tab{self.doctype}`.`owner` = {frappe.db.escape(self.user, percent=False)}"
|
||||
)
|
||||
|
||||
# add user permission only if role has read perm
|
||||
elif role_permissions.get("read") or role_permissions.get("select"):
|
||||
# get user permissions
|
||||
|
|
@ -605,8 +615,7 @@ class DatabaseQuery(object):
|
|||
|
||||
# share is an OR condition, if there is a role permission
|
||||
if not only_if_shared and self.shared and conditions:
|
||||
conditions = "({conditions}) or ({shared_condition})".format(
|
||||
conditions=conditions, shared_condition=self.get_share_condition())
|
||||
conditions = f"({conditions}) or ({self.get_share_condition()})"
|
||||
|
||||
return conditions
|
||||
|
||||
|
|
@ -614,8 +623,7 @@ class DatabaseQuery(object):
|
|||
return self.match_filters
|
||||
|
||||
def get_share_condition(self):
|
||||
return """`tab{0}`.name in ({1})""".format(self.doctype, ", ".join(["%s"] * len(self.shared))) % \
|
||||
tuple([frappe.db.escape(s, percent=False) for s in self.shared])
|
||||
return f"`tab{self.doctype}`.name in ({', '.join(frappe.db.escape(s, percent=False) for s in self.shared)})"
|
||||
|
||||
def add_user_permissions(self, user_permissions):
|
||||
meta = frappe.get_meta(self.doctype)
|
||||
|
|
@ -640,9 +648,7 @@ class DatabaseQuery(object):
|
|||
if frappe.get_system_settings("apply_strict_user_permissions"):
|
||||
condition = ""
|
||||
else:
|
||||
empty_value_condition = "ifnull(`tab{doctype}`.`{fieldname}`, '')=''".format(
|
||||
doctype=self.doctype, fieldname=df.get('fieldname')
|
||||
)
|
||||
empty_value_condition = f"ifnull(`tab{self.doctype}`.`{df.get('fieldname')}`, '')=''"
|
||||
condition = empty_value_condition + " or "
|
||||
|
||||
for permission in user_permission_values:
|
||||
|
|
@ -650,9 +656,7 @@ class DatabaseQuery(object):
|
|||
docs.append(permission.get('doc'))
|
||||
|
||||
# append docs based on user permission applicable on reference doctype
|
||||
|
||||
# this is useful when getting list of docs from a link field
|
||||
|
||||
# in this case parent doctype of the link
|
||||
# will be the reference doctype
|
||||
|
||||
|
|
@ -664,14 +668,9 @@ class DatabaseQuery(object):
|
|||
docs.append(permission.get('doc'))
|
||||
|
||||
if docs:
|
||||
condition += "`tab{doctype}`.`{fieldname}` in ({values})".format(
|
||||
doctype=self.doctype,
|
||||
fieldname=df.get('fieldname'),
|
||||
values=", ".join(
|
||||
[(frappe.db.escape(doc, percent=False)) for doc in docs])
|
||||
)
|
||||
|
||||
match_conditions.append("({condition})".format(condition=condition))
|
||||
values = ", ".join(frappe.db.escape(doc, percent=False) for doc in docs)
|
||||
condition += f"`tab{self.doctype}`.`{df.get('fieldname')}` in ({values})"
|
||||
match_conditions.append(f"({condition})")
|
||||
match_filters[df.get('options')] = docs
|
||||
|
||||
if match_conditions:
|
||||
|
|
@ -721,17 +720,17 @@ class DatabaseQuery(object):
|
|||
# `idx desc, modified desc`
|
||||
# will covert to
|
||||
# `tabItem`.`idx` desc, `tabItem`.`modified` desc
|
||||
args.order_by = ', '.join(['`tab{0}`.`{1}` {2}'.format(self.doctype,
|
||||
f.split()[0].strip(), f.split()[1].strip()) for f in meta.sort_field.split(',')])
|
||||
args.order_by = ', '.join(
|
||||
f"`tab{self.doctype}`.`{f.split()[0].strip()}` {f.split()[1].strip()}" for f in meta.sort_field.split(',')
|
||||
)
|
||||
else:
|
||||
sort_field = meta.sort_field or 'modified'
|
||||
sort_order = (meta.sort_field and meta.sort_order) or 'desc'
|
||||
|
||||
args.order_by = "`tab{0}`.`{1}` {2}".format(self.doctype, sort_field or "modified", sort_order or "desc")
|
||||
args.order_by = f"`tab{self.doctype}`.`{sort_field or 'modified'}` {sort_order or 'desc'}"
|
||||
|
||||
# draft docs always on top
|
||||
if hasattr(meta, 'is_submittable') and meta.is_submittable:
|
||||
args.order_by = "`tab{0}`.docstatus asc, {1}".format(self.doctype, args.order_by)
|
||||
args.order_by = f"`tab{self.doctype}`.docstatus asc, {args.order_by}"
|
||||
|
||||
def validate_order_by_and_group_by(self, parameters):
|
||||
"""Check order by, group by so that atleast one column is selected and does not have subquery"""
|
||||
|
|
@ -802,17 +801,16 @@ def get_order_by(doctype, meta):
|
|||
# `idx desc, modified desc`
|
||||
# will covert to
|
||||
# `tabItem`.`idx` desc, `tabItem`.`modified` desc
|
||||
order_by = ', '.join(['`tab{0}`.`{1}` {2}'.format(doctype,
|
||||
f.split()[0].strip(), f.split()[1].strip()) for f in meta.sort_field.split(',')])
|
||||
order_by = ', '.join(f"`tab{doctype}`.`{f.split()[0].strip()}` {f.split()[1].strip()}" for f in meta.sort_field.split(','))
|
||||
|
||||
else:
|
||||
sort_field = meta.sort_field or 'modified'
|
||||
sort_order = (meta.sort_field and meta.sort_order) or 'desc'
|
||||
|
||||
order_by = "`tab{0}`.`{1}` {2}".format(doctype, sort_field or "modified", sort_order or "desc")
|
||||
order_by = f"`tab{doctype}`.`{sort_field or 'modified'}` {sort_order or 'desc'}"
|
||||
|
||||
# draft docs always on top
|
||||
if meta.is_submittable:
|
||||
order_by = "`tab{0}`.docstatus asc, {1}".format(doctype, order_by)
|
||||
order_by = f"`tab{doctype}`.docstatus asc, {order_by}"
|
||||
|
||||
return order_by
|
||||
|
||||
|
|
|
|||
|
|
@ -664,7 +664,7 @@ def trim_tables(doctype=None):
|
|||
and not f.startswith("_")]
|
||||
if columns_to_remove:
|
||||
print(doctype, "columns removed:", columns_to_remove)
|
||||
columns_to_remove = ", ".join(["drop `{0}`".format(c) for c in columns_to_remove])
|
||||
columns_to_remove = ", ".join("drop `{0}`".format(c) for c in columns_to_remove)
|
||||
query = """alter table `tab{doctype}` {columns}""".format(
|
||||
doctype=doctype, columns=columns_to_remove)
|
||||
frappe.db.sql_ddl(query)
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ def update_user_settings(old, new, link_fields):
|
|||
if not link_fields: return
|
||||
|
||||
# find the user settings for the linked doctypes
|
||||
linked_doctypes = set([d.parent for d in link_fields if not d.issingle])
|
||||
linked_doctypes = {d.parent for d in link_fields if not d.issingle}
|
||||
user_settings_details = frappe.db.sql('''SELECT `user`, `doctype`, `data`
|
||||
FROM `__UserSettings`
|
||||
WHERE `data` like %s
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ def has_controller_permissions(doc, ptype, user=None):
|
|||
return None
|
||||
|
||||
def get_doctypes_with_read():
|
||||
return list(set([p.parent if type(p.parent) == str else p.parent.encode('UTF8') for p in get_valid_perms()]))
|
||||
return list({p.parent if type(p.parent) == str else p.parent.encode('UTF8') for p in get_valid_perms()})
|
||||
|
||||
def get_valid_perms(doctype=None, user=None):
|
||||
'''Get valid permissions for the current user from DocPerm and Custom DocPerm'''
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@
|
|||
"label": "Print Style"
|
||||
},
|
||||
{
|
||||
"default": "Modern",
|
||||
"default": "Redesign",
|
||||
"fieldname": "print_style",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
|
|
@ -183,7 +183,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-22 23:42:09.471022",
|
||||
"modified": "2021-02-15 14:16:18.474254",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Printing",
|
||||
"name": "Print Settings",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import Quill from 'quill';
|
||||
import ImageResize from 'quill-image-resize';
|
||||
import MagicUrl from 'quill-magic-url';
|
||||
|
||||
|
||||
Quill.register('modules/imageResize', ImageResize);
|
||||
Quill.register('modules/magicUrl', MagicUrl);
|
||||
const CodeBlockContainer = Quill.import('formats/code-block-container');
|
||||
CodeBlockContainer.tagName = 'PRE';
|
||||
Quill.register(CodeBlockContainer, true);
|
||||
|
|
@ -148,7 +151,8 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
|
|||
modules: {
|
||||
toolbar: this.get_toolbar_options(),
|
||||
table: true,
|
||||
imageResize: {}
|
||||
imageResize: {},
|
||||
magicUrl: true
|
||||
},
|
||||
theme: 'snow'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ frappe.ui.form.Dashboard = class FormDashboard {
|
|||
constructor(opts) {
|
||||
$.extend(this, opts);
|
||||
this.setup_dashboard_sections();
|
||||
this.set_open_count = frappe.utils.throttle(this.set_open_count, 500);
|
||||
}
|
||||
|
||||
setup_dashboard_sections() {
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ class FormTimeline extends BaseTimeline {
|
|||
}
|
||||
doc.owner = doc.sender;
|
||||
doc.user_full_name = doc.sender_full_name;
|
||||
doc.content = frappe.dom.remove_script_and_style(doc.content);
|
||||
let communication_content = $(frappe.render_template('timeline_message_box', { doc }));
|
||||
if (allow_reply) {
|
||||
this.setup_reply(communication_content, doc);
|
||||
|
|
@ -248,6 +249,7 @@ class FormTimeline extends BaseTimeline {
|
|||
}
|
||||
|
||||
get_comment_timeline_content(doc) {
|
||||
doc.content = frappe.dom.remove_script_and_style(doc.content);
|
||||
const comment_content = $(frappe.render_template('timeline_message_box', { doc }));
|
||||
this.setup_comment_actions(comment_content, doc);
|
||||
return comment_content;
|
||||
|
|
|
|||
|
|
@ -1607,7 +1607,9 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
}
|
||||
|
||||
show_tour(on_finish) {
|
||||
if (!Array.isArray(frappe.tour[this.doctype])) {
|
||||
const tour_info = frappe.tour[this.doctype];
|
||||
|
||||
if (!Array.isArray(tour_info)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1619,23 +1621,29 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
keyboardControl: true,
|
||||
nextBtnText: 'Next',
|
||||
prevBtnText: 'Previous',
|
||||
opacity: 0.25,
|
||||
onNext: () => {
|
||||
if (!driver.hasNextStep()) {
|
||||
on_finish && on_finish();
|
||||
}
|
||||
}
|
||||
opacity: 0.25
|
||||
});
|
||||
|
||||
this.layout.sections.forEach(section => section.collapse(false));
|
||||
|
||||
let steps = frappe.tour[this.doctype].map(step => {
|
||||
let steps = tour_info.map(step => {
|
||||
let field = this.get_docfield(step.fieldname);
|
||||
return {
|
||||
element: `.frappe-control[data-fieldname='${step.fieldname}']`,
|
||||
popover: {
|
||||
title: step.title || field.label,
|
||||
description: step.description
|
||||
description: step.description,
|
||||
position: step.position || 'bottom'
|
||||
},
|
||||
onNext: () => {
|
||||
const next_condition_satisfied = this.layout.evaluate_depends_on_value(step.next_step_condition || true);
|
||||
if (!next_condition_satisfied) {
|
||||
driver.preventMove();
|
||||
}
|
||||
|
||||
if (!driver.hasNextStep()) {
|
||||
on_finish && on_finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -221,9 +221,13 @@ frappe.form.formatters = {
|
|||
Tag: function(value) {
|
||||
var html = "";
|
||||
$.each((value || "").split(","), function(i, v) {
|
||||
if(v) html+= '<span class="label label-info" \
|
||||
style="margin-right: 7px; cursor: pointer;"\
|
||||
data-field="_user_tags" data-label="'+v+'">'+v +'</span>';
|
||||
if (v) html += `
|
||||
<span
|
||||
class="data-pill btn-xs align-center ellipsis"
|
||||
style="background-color: var(--control-bg); box-shadow: none; margin-right: 4px;"
|
||||
data-field="_user_tags" data-label="${v}'">
|
||||
${v}
|
||||
</span>`;
|
||||
});
|
||||
return html;
|
||||
},
|
||||
|
|
@ -310,6 +314,7 @@ frappe.form.get_formatter = function(fieldtype) {
|
|||
|
||||
frappe.format = function(value, df, options, doc) {
|
||||
if(!df) df = {"fieldtype":"Data"};
|
||||
if (df.fieldname == '_user_tags') df.fieldtype = 'Tag';
|
||||
var fieldtype = df.fieldtype || "Data";
|
||||
|
||||
// format Dynamic Link as a Link
|
||||
|
|
|
|||
|
|
@ -210,9 +210,9 @@ export default class Grid {
|
|||
|
||||
delete_all_rows() {
|
||||
frappe.confirm(__("Are you sure you want to delete all rows?"), () => {
|
||||
this.frm.doc[this.df.fieldname] = [];
|
||||
$(this.parent).find('.rows').empty();
|
||||
this.grid_rows = [];
|
||||
this.grid_rows.forEach(row => {
|
||||
row.remove();
|
||||
});
|
||||
this.frm.script_manager.trigger(this.df.fieldname + "_delete", this.doctype);
|
||||
|
||||
this.wrapper.find('.grid-heading-row .grid-row-check:checked:first').prop('checked', 0);
|
||||
|
|
@ -236,6 +236,10 @@ export default class Grid {
|
|||
}
|
||||
|
||||
refresh_remove_rows_button() {
|
||||
if (this.df.cannot_delete_rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.remove_rows_button.toggleClass('hidden',
|
||||
this.wrapper.find('.grid-body .grid-row-check:checked:first').length ? false : true);
|
||||
this.remove_all_rows_button.toggleClass('hidden',
|
||||
|
|
|
|||
|
|
@ -569,6 +569,9 @@ export default class GridRow {
|
|||
.find('.grid-insert-row-below, .grid-insert-row, .grid-duplicate-row, .grid-append-row')
|
||||
.toggle(!cannot_add_rows);
|
||||
|
||||
this.wrapper.find('.grid-delete-row')
|
||||
.toggle(!(this.grid.df && this.grid.df.cannot_delete_rows));
|
||||
|
||||
frappe.dom.freeze("", "dark");
|
||||
if (cur_frm) cur_frm.cur_grid = this;
|
||||
this.wrapper.addClass("grid-row-open");
|
||||
|
|
|
|||
|
|
@ -323,9 +323,12 @@ frappe.ui.FilterGroup = class {
|
|||
}
|
||||
|
||||
add_filters_to_filter_group(filters) {
|
||||
filters.forEach((filter) => {
|
||||
this.add_filter(filter[0], filter[1], filter[2], filter[3]);
|
||||
});
|
||||
if (filters.length) {
|
||||
this.toggle_empty_filters(false);
|
||||
filters.forEach((filter) => {
|
||||
this.add_filter(filter[0], filter[1], filter[2], filter[3]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
add(filters, refresh = true) {
|
||||
|
|
|
|||
|
|
@ -381,10 +381,11 @@ frappe.ui.GroupBy = class {
|
|||
this.group_by_fields = {};
|
||||
this.all_fields = {};
|
||||
|
||||
let fields = this.report_view.meta.fields.filter((f) =>
|
||||
const fields = this.report_view.meta.fields.filter((f) =>
|
||||
['Select', 'Link', 'Data', 'Int', 'Check'].includes(f.fieldtype)
|
||||
);
|
||||
this.group_by_fields[this.doctype] = fields;
|
||||
const tag_field = {fieldname: '_user_tags', fieldtype: 'Data', label: __('Tags')};
|
||||
this.group_by_fields[this.doctype] = fields.concat(tag_field);
|
||||
this.all_fields[this.doctype] = this.report_view.meta.fields;
|
||||
|
||||
const standard_fields_filter = (df) =>
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ frappe.throw = function(msg) {
|
|||
|
||||
frappe.confirm = function(message, confirm_action, reject_action) {
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __("Confirm"),
|
||||
primary_action_label: __("Yes"),
|
||||
title: __("Confirm", null, "Title of confirmation dialog"),
|
||||
primary_action_label: __("Yes", null, "Approve confirmation dialog"),
|
||||
primary_action: () => {
|
||||
confirm_action && confirm_action();
|
||||
d.hide();
|
||||
},
|
||||
secondary_action_label: __("No"),
|
||||
secondary_action_label: __("No", null, "Dismiss confirmation dialog"),
|
||||
secondary_action: () => d.hide(),
|
||||
});
|
||||
|
||||
|
|
@ -88,9 +88,9 @@ frappe.prompt = function(fields, callback, title, primary_label) {
|
|||
if(!$.isArray(fields)) fields = [fields];
|
||||
var d = new frappe.ui.Dialog({
|
||||
fields: fields,
|
||||
title: title || __("Enter Value"),
|
||||
title: title || __("Enter Value", null, "Title of prompt dialog"),
|
||||
});
|
||||
d.set_primary_action(primary_label || __("Submit"), function() {
|
||||
d.set_primary_action(primary_label || __("Submit", null, "Primary action of prompt dialog"), function() {
|
||||
var values = d.get_values();
|
||||
if(!values) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -377,11 +377,12 @@ frappe.ui.Page = class Page {
|
|||
});
|
||||
}
|
||||
|
||||
add_actions_menu_item(label, click, standard) {
|
||||
add_actions_menu_item(label, click, standard, shortcut) {
|
||||
return this.add_dropdown_item({
|
||||
label,
|
||||
click,
|
||||
standard,
|
||||
shortcut,
|
||||
parent: this.actions,
|
||||
show_parent: false
|
||||
});
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
x_fields.push({
|
||||
label: col.content,
|
||||
fieldname: col.id,
|
||||
value: col.id,
|
||||
value: col.id,
|
||||
});
|
||||
|
||||
// numeric values in y
|
||||
|
|
@ -1024,8 +1024,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
return docfield.fieldtype === 'Date' ? 'right' : 'left';
|
||||
})();
|
||||
|
||||
let id = fieldname;
|
||||
|
||||
// child table column
|
||||
const id = doctype !== this.doctype ? `${doctype}:${fieldname}` : fieldname;
|
||||
if (doctype !== this.doctype && fieldname !== '_aggregate_column') {
|
||||
id = `${doctype}:${fieldname}`;
|
||||
}
|
||||
|
||||
let width = (docfield ? cint(docfield.width) : null) || null;
|
||||
if (this.report_doc) {
|
||||
|
|
|
|||
|
|
@ -271,18 +271,19 @@ class ShortcutDialog extends WidgetDialog {
|
|||
}
|
||||
|
||||
process_data(data) {
|
||||
let stats_filter = {};
|
||||
|
||||
if (this.dialog.get_value("type") == "DocType" && this.filter_group) {
|
||||
let filters = this.filter_group.get_filters();
|
||||
let stats_filter = null;
|
||||
|
||||
if (filters.length) {
|
||||
stats_filter = {};
|
||||
filters.forEach((arr) => {
|
||||
stats_filter[arr[1]] = [arr[2], arr[3]];
|
||||
});
|
||||
|
||||
data.stats_filter = JSON.stringify(stats_filter);
|
||||
stats_filter = JSON.stringify(stats_filter);
|
||||
}
|
||||
data.stats_filter = stats_filter;
|
||||
}
|
||||
|
||||
data.label = data.label
|
||||
|
|
|
|||
|
|
@ -241,6 +241,7 @@ textarea.form-control {
|
|||
|
||||
// rating
|
||||
.rating {
|
||||
cursor: pointer;
|
||||
--star-fill: var(--gray-300);
|
||||
.star-hover {
|
||||
--star-fill: var(--yellow-100);
|
||||
|
|
@ -248,6 +249,24 @@ textarea.form-control {
|
|||
.star-click {
|
||||
--star-fill: var(--yellow-300);
|
||||
}
|
||||
|
||||
.rating-box {
|
||||
background-color: var(--gray-300);
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin: 4px 2px;
|
||||
}
|
||||
.rating-hover {
|
||||
background-color: var(--yellow-100);
|
||||
}
|
||||
.rating-click {
|
||||
background-color: var(--yellow-300);
|
||||
}
|
||||
}
|
||||
|
||||
.frappe-control .control-value {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,12 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.form-grid .grid-heading-row .template-row {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.form-grid .template-row {
|
||||
width: calc(100% - 30px);
|
||||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@
|
|||
}
|
||||
|
||||
.ql-editor.read-mode {
|
||||
height: unset;
|
||||
padding: 0;
|
||||
.mention {
|
||||
--user-mention-bg-color: var(--control-bg);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@
|
|||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.card {
|
||||
border: 1px solid var(--border-color)
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ $font-sizes-mobile: (
|
|||
}
|
||||
|
||||
li {
|
||||
text-indent: 0.25rem;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from whoosh.fields import ID, TEXT, Schema
|
|||
import frappe
|
||||
from frappe.search.full_text_search import FullTextSearch
|
||||
from frappe.utils import set_request, update_progress_bar
|
||||
from frappe.website.render import render_page
|
||||
from frappe.website.serve import get_response_content
|
||||
|
||||
INDEX_NAME = "web_routes"
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ class WebsiteSearch(FullTextSearch):
|
|||
|
||||
try:
|
||||
set_request(method="GET", path=route)
|
||||
content = render_page(route)
|
||||
content = get_response_content(route)
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
page_content = soup.find(class_="page_content")
|
||||
text_content = page_content.text if page_content else ""
|
||||
|
|
|
|||
|
|
@ -167,7 +167,8 @@ def get_csrf_token():
|
|||
|
||||
def generate_csrf_token():
|
||||
frappe.local.session.data.csrf_token = frappe.generate_hash()
|
||||
frappe.local.session_obj.update(force=True)
|
||||
if not frappe.flags.in_test:
|
||||
frappe.local.session_obj.update(force=True)
|
||||
|
||||
class Session:
|
||||
def __init__(self, user, resume=False, full_name=None, user_type=None):
|
||||
|
|
|
|||
|
|
@ -49,8 +49,10 @@
|
|||
{% endif %}
|
||||
<script>
|
||||
frappe.ready(function() {
|
||||
let guest_allowed = "{{ guest_allowed or ''}}";
|
||||
|
||||
if (!frappe.is_user_logged_in()) {
|
||||
$(".login-required, .comment-form-wrapper").toggleClass("hidden");
|
||||
!guest_allowed && $(".login-required, .comment-form-wrapper").toggleClass("hidden");
|
||||
} else {
|
||||
$('input.comment_by').prop("disabled", true);
|
||||
$('input.comment_email').prop("disabled", true);
|
||||
|
|
@ -91,6 +93,16 @@
|
|||
route: "{{ pathname }}",
|
||||
}
|
||||
|
||||
if(!args.comment_by || !args.comment_email || !args.comment) {
|
||||
frappe.msgprint("{{ _("All fields are necessary to submit the comment.") }}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (args.comment_email!=='Administrator' && !validate_email(args.comment_email)) {
|
||||
frappe.msgprint("{{ _("Please enter a valid email address.") }}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!args.comment || !args.comment.trim()) {
|
||||
frappe.msgprint("{{ _("Please add a valid comment.") }}");
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -2,15 +2,18 @@
|
|||
# MIT License. See license.txt
|
||||
import frappe
|
||||
import re
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.website.utils import clear_cache
|
||||
from frappe.utils import add_to_date, now
|
||||
|
||||
from frappe import _
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route):
|
||||
doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
|
||||
if frappe.session.user == 'Guest' and doc.doctype not in ['Blog Post', 'Web Page']:
|
||||
return
|
||||
|
||||
if not comment.strip():
|
||||
frappe.msgprint(_('The comment cannot be empty'))
|
||||
return False
|
||||
|
|
@ -22,17 +25,14 @@ def add_comment(comment, comment_email, comment_by, reference_doctype, reference
|
|||
frappe.msgprint(_('Comments cannot have links or email addresses'))
|
||||
return False
|
||||
|
||||
if not comment_email == frappe.session.user:
|
||||
comment_email = frappe.session.user
|
||||
|
||||
comments_count = frappe.db.count("Comment", {
|
||||
"comment_type": "Comment",
|
||||
"comment_email": frappe.session.user,
|
||||
"comment_email": comment_email,
|
||||
"creation": (">", add_to_date(now(), hours=-1))
|
||||
})
|
||||
|
||||
if comments_count > 20:
|
||||
frappe.msgprint(_('Hourly comment limit reached for: {0}').format(frappe.bold(frappe.session.user)))
|
||||
frappe.msgprint(_('Hourly comment limit reached for: {0}').format(frappe.bold(comment_email)))
|
||||
return False
|
||||
|
||||
comment = doc.add_comment(
|
||||
|
|
|
|||
0
frappe/templates/includes/feedback/__init__.py
Normal file
0
frappe/templates/includes/feedback/__init__.py
Normal file
166
frappe/templates/includes/feedback/feedback.html
Normal file
166
frappe/templates/includes/feedback/feedback.html
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
<div class="add-feedback-section">
|
||||
<div class="feedback-form-wrapper">
|
||||
<a class="give-feedback btn btn-light btn-sm">{{ _("How would you rate the blog?") }}</a>
|
||||
<div style="display: none;" id="feedback-form">
|
||||
<p>{{ _("How would you rate the blog?") }}</p>
|
||||
<div class="alert" style="display:none;"></div>
|
||||
<form>
|
||||
<fieldset>
|
||||
<div class="row" style="margin-bottom: 15px;">
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control feedback_email" name="feedback_email" placeholder="{{ _("Your Email Address") }}" type="email">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="rating">
|
||||
{% for rating in [1, 2, 3, 4, 5 ,6, 7, 8, 9, 10] %}
|
||||
<div class="icon rating-box" data-rating="{{ rating }}">{{ rating }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<textarea class="form-control" name="feedback" rows=10 placeholder="{{ _("Feedback") }}"></textarea>
|
||||
</p>
|
||||
<button class="btn btn-sm" id="toggle-feedback" style="margin-top:10px; margin-right:2px;">
|
||||
{{ _("Back") }}
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm" id="submit-feedback" style="margin-top:10px">
|
||||
{{ _("Submit") }}
|
||||
</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
frappe.ready(() => {
|
||||
let feedback = "{{ user_feedback.feedback or ''}}"
|
||||
let user_rating = parseInt("{{ user_feedback.rating or 0 }}")
|
||||
let rating = user_rating;
|
||||
feedback && $("#submit-feedback").html(__("Update"));
|
||||
|
||||
if (frappe.is_user_logged_in()) {
|
||||
$(".feedback_email").parent().toggleClass("hidden");
|
||||
if (feedback) {
|
||||
$("[name='feedback']").val(feedback);
|
||||
toggle_feedback();
|
||||
set_rating(rating);
|
||||
}
|
||||
}
|
||||
|
||||
$('.give-feedback').click(() => toggle_feedback());
|
||||
|
||||
|
||||
$('.rating').find('.rating-box').hover((ev) => {
|
||||
const el = $(ev.currentTarget);
|
||||
rating = el.data('rating');
|
||||
el.parent().children('.rating-box').each( function(e) {
|
||||
if (e < rating) {
|
||||
$(this).addClass('rating-hover');
|
||||
} else {
|
||||
$(this).removeClass('rating-hover');
|
||||
}
|
||||
});
|
||||
}, (ev) => {
|
||||
const el = $(ev.currentTarget);
|
||||
el.parent().children('.rating-box').each( function() {
|
||||
$(this).removeClass('rating-hover');
|
||||
});
|
||||
});
|
||||
|
||||
$('.rating').find('.rating-box').click((ev) => {
|
||||
const el = $(ev.currentTarget);
|
||||
rating = el.data('rating');
|
||||
el.parent().children('.rating-box').each( function(e) {
|
||||
if (e < rating) {
|
||||
$(this).addClass('rating-click');
|
||||
} else {
|
||||
$(this).removeClass('rating-click');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#submit-feedback').click((ev) => {
|
||||
let update = ev.target.innerText !== __("Submit");
|
||||
let args = {
|
||||
reference_doctype: "{{ reference_doctype or doctype }}",
|
||||
reference_name: "{{ reference_name or name }}",
|
||||
rating: rating,
|
||||
feedback: $("[name='feedback']").val(),
|
||||
feedback_email: $("[name='feedback_email']").val() || frappe.user_id
|
||||
}
|
||||
|
||||
if (args.rating == 0) {
|
||||
frappe.msgprint("{{ _("Rating is required!") }}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!args.feedback || !args.feedback.trim()) {
|
||||
frappe.msgprint("{{ _("Please add a valid feedback.") }}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (args.feedback_email!=='Administrator' && !validate_email(args.feedback_email)) {
|
||||
frappe.msgprint("{{ _("Please enter a valid email address.") }}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!update) {
|
||||
frappe.call({
|
||||
method: "frappe.templates.includes.feedback.feedback.add_feedback",
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
toggle_feedback();
|
||||
if (!frappe.is_user_logged_in()) {
|
||||
$("[name='feedback']").val('');
|
||||
set_rating(0);
|
||||
} else {
|
||||
feedback = $("[name='feedback']").val();
|
||||
user_rating = rating;
|
||||
$("#submit-feedback").html(__("Update"));
|
||||
}
|
||||
frappe.msgprint({message:__("Thank you for your valuable feedback!"), indicator:'green'});
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (feedback == $("[name='feedback']").val() && rating == user_rating) {
|
||||
frappe.msgprint({message:__("Please update rating or feedback before saving."), indicator:'red'});
|
||||
return false;
|
||||
}
|
||||
frappe.call({
|
||||
method: "frappe.templates.includes.feedback.feedback.update_feedback",
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
toggle_feedback();
|
||||
feedback = $("[name='feedback']").val();
|
||||
user_rating = rating;
|
||||
frappe.msgprint({message:__("Feedback updated successfully!"), indicator:'green'});
|
||||
}
|
||||
})
|
||||
}
|
||||
return false;
|
||||
})
|
||||
|
||||
$('#toggle-feedback').click(() => {
|
||||
toggle_feedback();
|
||||
return false;
|
||||
})
|
||||
|
||||
function set_rating(rating) {
|
||||
let el = $('.rating').find('.rating-box');
|
||||
el.children('.rating-box').prevObject.each( function(e) {
|
||||
if (e < rating) {
|
||||
$(this).addClass('rating-click');
|
||||
} else {
|
||||
$(this).removeClass('rating-click');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggle_feedback() {
|
||||
$(".give-feedback").toggle();
|
||||
$("#feedback-form").toggle();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
63
frappe/templates/includes/feedback/feedback.py
Normal file
63
frappe/templates/includes/feedback/feedback.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
from frappe import _
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def add_feedback(reference_doctype, reference_name, rating, feedback, feedback_email):
|
||||
doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
if doc.disable_feedback == 1:
|
||||
return
|
||||
|
||||
doc = frappe.new_doc('Feedback')
|
||||
doc.reference_doctype = reference_doctype
|
||||
doc.reference_name = reference_name
|
||||
doc.rating = rating
|
||||
doc.feedback = feedback
|
||||
doc.email = feedback_email
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
subject = _('New Feedback on {0}: {1}').format(reference_doctype, reference_name)
|
||||
send_mail(doc, subject)
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_feedback(reference_doctype, reference_name, rating, feedback, feedback_email):
|
||||
doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
if doc.disable_feedback == 1:
|
||||
return
|
||||
|
||||
filters = {
|
||||
"email": feedback_email,
|
||||
"reference_doctype": reference_doctype,
|
||||
"reference_name": reference_name
|
||||
}
|
||||
d = frappe.get_all('Feedback', filters=filters, limit=1)
|
||||
doc = frappe.get_doc('Feedback', d[0].name)
|
||||
doc.rating = rating
|
||||
doc.feedback = feedback
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
subject = _('Feedback updated on {0}: {1}').format(reference_doctype, reference_name)
|
||||
send_mail(doc, subject)
|
||||
return doc
|
||||
|
||||
def send_mail(feedback, subject):
|
||||
doc = frappe.get_doc(feedback.reference_doctype, feedback.reference_name)
|
||||
|
||||
message = ("<p>{0} ({1})</p>".format(feedback.feedback, feedback.rating)
|
||||
+ "<p><a href='{0}/app/marketing-asset-feedback/{1}' style='font-size: 80%'>{2}</a></p>".format(frappe.utils.get_request_site_address(),
|
||||
feedback.name,
|
||||
_("View Feedback")))
|
||||
|
||||
# notify creator
|
||||
frappe.sendmail(
|
||||
recipients=frappe.db.get_value('User', doc.owner, 'email') or doc.owner,
|
||||
subject=subject,
|
||||
message=message,
|
||||
reference_doctype=doc.doctype,
|
||||
reference_name=doc.name
|
||||
)
|
||||
|
|
@ -3,11 +3,6 @@
|
|||
{% for item in children_map[route] %}
|
||||
<li>
|
||||
<a href="{{ url_prefix }}{{ item.route }}">{{ item.title }}</a>
|
||||
{#
|
||||
{% if children_map[item.route] %}
|
||||
{{ make_item_list(item.route, children_map) }}
|
||||
{% endif %}
|
||||
#}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
<!-- base template for testing -->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
{%- block style %}
|
||||
{% if colocated_css -%}
|
||||
<style>{{ colocated_css }}</style>
|
||||
{%- endif %}
|
||||
{%- endblock -%}
|
||||
</head>
|
||||
<body>
|
||||
{% include "templates/includes/breadcrumbs.html" %}
|
||||
<h1>This is for testing</h1>
|
||||
{% block content %}{% endblock %}
|
||||
{%- block script %}
|
||||
{% if colocated_js -%}
|
||||
<script>{{ colocated_js }}</script>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ def run_tests_for_module(module, verbose=False, tests=(), profile=False, junit_x
|
|||
for doctype in module.test_dependencies:
|
||||
make_test_records(doctype, verbose=verbose)
|
||||
|
||||
frappe.db.commit()
|
||||
return _run_unittest(module, verbose=verbose, tests=tests, profile=profile, junit_xml_output=junit_xml_output)
|
||||
|
||||
def _run_unittest(modules, verbose=False, tests=(), profile=False, junit_xml_output=False):
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ class TestResourceAPI(unittest.TestCase):
|
|||
for name in self.GENERATED_DOCUMENTS:
|
||||
frappe.delete_doc_if_exists(self.DOCTYPE, name)
|
||||
|
||||
def setUp(self):
|
||||
# commit to ensure consistency in session (postgres CI randomly fails)
|
||||
if frappe.conf.db_type == "postgres":
|
||||
frappe.db.commit()
|
||||
|
||||
@property
|
||||
def sid(self):
|
||||
if not getattr(self, "_sid", None):
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ def exists_in_backup(doctypes, file):
|
|||
)
|
||||
with gzip.open(file, "rb") as f:
|
||||
content = f.read().decode("utf8")
|
||||
return all([predicate.format(doctype).lower() in content.lower() for doctype in doctypes])
|
||||
return all(predicate.format(doctype).lower() in content.lower() for doctype in doctypes)
|
||||
|
||||
|
||||
class BaseTestCommands(unittest.TestCase):
|
||||
|
|
@ -355,12 +355,12 @@ class TestCommands(BaseTestCommands):
|
|||
# test 2: bare functionality for single site
|
||||
self.execute("bench --site {site} list-apps")
|
||||
self.assertEqual(self.returncode, 0)
|
||||
list_apps = set([
|
||||
list_apps = set(
|
||||
_x.split()[0] for _x in self.stdout.split("\n")
|
||||
])
|
||||
)
|
||||
doctype = frappe.get_single("Installed Applications").installed_applications
|
||||
if doctype:
|
||||
installed_apps = set([x.app_name for x in doctype])
|
||||
installed_apps = set(x.app_name for x in doctype)
|
||||
else:
|
||||
installed_apps = set(frappe.get_installed_apps())
|
||||
self.assertSetEqual(list_apps, installed_apps)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,18 @@ class TestReportview(unittest.TestCase):
|
|||
def test_basic(self):
|
||||
self.assertTrue({"name":"DocType"} in DatabaseQuery("DocType").execute(limit_page_length=None))
|
||||
|
||||
def test_extract_tables(self):
|
||||
db_query = DatabaseQuery("DocType")
|
||||
add_custom_field("DocType", 'test_tab_field', 'Data')
|
||||
|
||||
db_query.fields = ["tabNote.creation", "test_tab_field", "tabDocType.test_tab_field"]
|
||||
db_query.extract_tables()
|
||||
self.assertIn("`tabNote`", db_query.tables)
|
||||
self.assertIn("`tabDocType`", db_query.tables)
|
||||
self.assertNotIn("test_tab_field", db_query.tables)
|
||||
|
||||
clear_custom_fields("DocType")
|
||||
|
||||
def test_build_match_conditions(self):
|
||||
clear_user_permissions_for_doctype('Blog Post', 'test2@example.com')
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import unittest
|
|||
import frappe
|
||||
import frappe.recorder
|
||||
from frappe.utils import set_request
|
||||
from frappe.website.render import render_page
|
||||
from frappe.website.serve import get_response_content
|
||||
|
||||
import sqlparse
|
||||
|
||||
|
|
@ -121,5 +121,5 @@ class TestRecorder(unittest.TestCase):
|
|||
self.assertEqual(call['exact_copies'], query[1])
|
||||
|
||||
def test_error_page_rendering(self):
|
||||
content = render_page("error")
|
||||
content = get_response_content("error")
|
||||
self.assertIn("Error", content)
|
||||
|
|
|
|||
|
|
@ -1,38 +1,41 @@
|
|||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.website import render
|
||||
from frappe.website.utils import get_home_page
|
||||
from frappe.utils import set_request
|
||||
from frappe.website.serve import get_response, get_response_content
|
||||
from frappe.website.utils import (build_response, clear_website_cache, get_home_page)
|
||||
|
||||
|
||||
class TestWebsite(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.set_user('Guest')
|
||||
|
||||
def test_home_page_for_role(self):
|
||||
frappe.delete_doc_if_exists('User', 'test-user-for-home-page@example.com')
|
||||
frappe.delete_doc_if_exists('Role', 'home-page-test')
|
||||
frappe.delete_doc_if_exists('Web Page', 'home-page-test')
|
||||
def tearDown(self):
|
||||
frappe.set_user('Administrator')
|
||||
|
||||
def test_home_page(self):
|
||||
frappe.set_user('Administrator')
|
||||
# test home page via role
|
||||
user = frappe.get_doc(dict(
|
||||
doctype='User',
|
||||
email='test-user-for-home-page@example.com',
|
||||
first_name='test')).insert()
|
||||
first_name='test')).insert(ignore_if_duplicate=True)
|
||||
|
||||
role = frappe.get_doc(dict(
|
||||
doctype = 'Role',
|
||||
role_name = 'home-page-test',
|
||||
desk_access = 0,
|
||||
home_page = '/home-page-test'
|
||||
)).insert()
|
||||
)).insert(ignore_if_duplicate=True)
|
||||
|
||||
user.add_roles(role.name)
|
||||
user.save()
|
||||
|
||||
frappe.db.set_value('Role', 'home-page-test', 'home_page', 'home-page-test')
|
||||
frappe.set_user('test-user-for-home-page@example.com')
|
||||
self.assertEqual(get_home_page(), 'home-page-test')
|
||||
|
||||
frappe.set_user('Administrator')
|
||||
role.home_page = ''
|
||||
role.save()
|
||||
frappe.db.set_value('Role', 'home-page-test', 'home_page', '')
|
||||
|
||||
# home page via portal settings
|
||||
frappe.db.set_value('Portal Settings', None, 'default_portal_home', 'test-portal-home')
|
||||
|
|
@ -41,10 +44,45 @@ class TestWebsite(unittest.TestCase):
|
|||
frappe.cache().hdel('home_page', frappe.session.user)
|
||||
self.assertEqual(get_home_page(), 'test-portal-home')
|
||||
|
||||
def test_page_load(self):
|
||||
frappe.db.set_value("Portal Settings", None, "default_portal_home", '')
|
||||
clear_website_cache()
|
||||
|
||||
# home page via website settings
|
||||
frappe.db.set_value("Website Settings", None, "home_page", 'contact')
|
||||
self.assertEqual(get_home_page(), 'contact')
|
||||
|
||||
frappe.db.set_value("Website Settings", None, "home_page", None)
|
||||
clear_website_cache()
|
||||
|
||||
# fallback homepage
|
||||
self.assertEqual(get_home_page(), 'me')
|
||||
|
||||
# fallback homepage for guest
|
||||
frappe.set_user('Guest')
|
||||
self.assertEqual(get_home_page(), 'login')
|
||||
frappe.set_user('Administrator')
|
||||
|
||||
# test homepage via hooks
|
||||
clear_website_cache()
|
||||
set_home_page_hook('get_website_user_home_page', 'frappe.www._test._test_home_page.get_website_user_home_page')
|
||||
self.assertEqual(get_home_page(), '_test/_test_folder')
|
||||
|
||||
clear_website_cache()
|
||||
set_home_page_hook('website_user_home_page', 'login')
|
||||
self.assertEqual(get_home_page(), 'login')
|
||||
|
||||
clear_website_cache()
|
||||
set_home_page_hook('home_page', 'about')
|
||||
self.assertEqual(get_home_page(), 'about')
|
||||
|
||||
clear_website_cache()
|
||||
set_home_page_hook('role_home_page', {'home-page-test': 'home-page-test'})
|
||||
self.assertEqual(get_home_page(), 'home-page-test')
|
||||
|
||||
|
||||
def test_page_load(self):
|
||||
set_request(method='POST', path='login')
|
||||
response = render.render()
|
||||
response = get_response()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
|
@ -52,14 +90,52 @@ class TestWebsite(unittest.TestCase):
|
|||
|
||||
self.assertTrue('// login.js' in html)
|
||||
self.assertTrue('<!-- login.html -->' in html)
|
||||
|
||||
def test_static_page(self):
|
||||
set_request(method='GET', path='/_test/static-file-test.png')
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_error_page(self):
|
||||
set_request(method='GET', path='/_test/problematic_page')
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 500)
|
||||
|
||||
def test_login(self):
|
||||
set_request(method='GET', path='/login')
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
html = frappe.safe_decode(response.get_data())
|
||||
|
||||
self.assertTrue('// login.js' in html)
|
||||
self.assertTrue('<!-- login.html -->' in html)
|
||||
|
||||
def test_app(self):
|
||||
frappe.set_user('Administrator')
|
||||
set_request(method='GET', path='/app')
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
html = frappe.safe_decode(response.get_data())
|
||||
self.assertTrue('window.app = true;' in html)
|
||||
frappe.local.session_obj = None
|
||||
|
||||
def test_not_found(self):
|
||||
set_request(method='GET', path='/_test/missing')
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
def test_redirect(self):
|
||||
import frappe.hooks
|
||||
frappe.set_user('Administrator')
|
||||
|
||||
frappe.hooks.website_redirects = [
|
||||
dict(source=r'/testfrom', target=r'://testto1'),
|
||||
dict(source=r'/testfromregex.*', target=r'://testto2'),
|
||||
dict(source=r'/testsub/(.*)', target=r'://testto3/\1')
|
||||
dict(source=r'/testsub/(.*)', target=r'://testto3/\1'),
|
||||
dict(source=r'/courses/course\?course=(.*)', target=r'/courses/\1', match_with_query_string=True),
|
||||
]
|
||||
|
||||
website_settings = frappe.get_doc('Website Settings')
|
||||
|
|
@ -69,32 +145,82 @@ class TestWebsite(unittest.TestCase):
|
|||
})
|
||||
website_settings.save()
|
||||
|
||||
frappe.cache().delete_key('app_hooks')
|
||||
frappe.cache().delete_key('website_redirects')
|
||||
|
||||
set_request(method='GET', path='/testfrom')
|
||||
response = render.render()
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertEqual(response.headers.get('Location'), r'://testto1')
|
||||
|
||||
set_request(method='GET', path='/testfromregex/test')
|
||||
response = render.render()
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertEqual(response.headers.get('Location'), r'://testto2')
|
||||
|
||||
set_request(method='GET', path='/testsub/me')
|
||||
response = render.render()
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertEqual(response.headers.get('Location'), r'://testto3/me')
|
||||
|
||||
set_request(method='GET', path='/test404')
|
||||
response = render.render()
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
set_request(method='GET', path='/testsource')
|
||||
response = render.render()
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertEqual(response.headers.get('Location'), '/testtarget')
|
||||
|
||||
set_request(method='GET', path='/courses/course?course=data')
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 301)
|
||||
self.assertEqual(response.headers.get('Location'), '/courses/data')
|
||||
|
||||
delattr(frappe.hooks, 'website_redirects')
|
||||
frappe.cache().delete_key('app_hooks')
|
||||
|
||||
def test_custom_page_renderer(self):
|
||||
import frappe.hooks
|
||||
frappe.hooks.page_renderer = ['frappe.tests.test_website.CustomPageRenderer']
|
||||
frappe.cache().delete_key('app_hooks')
|
||||
set_request(method='GET', path='/custom')
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 3984)
|
||||
|
||||
set_request(method='GET', path='/new')
|
||||
content = get_response_content()
|
||||
self.assertIn("<div>Custom Page Response</div>", content)
|
||||
|
||||
set_request(method='GET', path='/random')
|
||||
response = get_response()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
delattr(frappe.hooks, 'page_renderer')
|
||||
frappe.cache().delete_key('app_hooks')
|
||||
|
||||
def test_printview_page(self):
|
||||
content = get_response_content('/Language/en')
|
||||
self.assertIn('<div class="print-format">', content)
|
||||
self.assertIn('<div>Language</div>', content)
|
||||
|
||||
|
||||
def set_home_page_hook(key, value):
|
||||
from frappe import hooks
|
||||
# reset home_page hooks
|
||||
for hook in ('get_website_user_home_page','website_user_home_page','role_home_page','home_page'):
|
||||
if hasattr(hooks, hook):
|
||||
delattr(hooks, hook)
|
||||
|
||||
setattr(hooks, key, value)
|
||||
frappe.cache().delete_key('app_hooks')
|
||||
|
||||
class CustomPageRenderer():
|
||||
def __init__(self, path, status_code=None):
|
||||
self.path = path
|
||||
# custom status code
|
||||
self.status_code = 3984
|
||||
|
||||
def can_render(self):
|
||||
if self.path in ('new', 'custom'):
|
||||
return True
|
||||
|
||||
def render(self):
|
||||
return build_response(self.path, """<div>Custom Page Response</div>""", self.status_code)
|
||||
|
|
|
|||
|
|
@ -284,8 +284,8 @@ def clear_cache():
|
|||
def get_messages_for_app(app, deduplicate=True):
|
||||
"""Returns all messages (list) for a specified `app`"""
|
||||
messages = []
|
||||
modules = ", ".join(['"{}"'.format(m.title().replace("_", " ")) \
|
||||
for m in frappe.local.app_modules[app]])
|
||||
modules = ", ".join('"{}"'.format(m.title().replace("_", " ")) \
|
||||
for m in frappe.local.app_modules[app])
|
||||
|
||||
# doctypes
|
||||
if modules:
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ def random_string(length):
|
|||
"""generate a random string"""
|
||||
import string
|
||||
from random import choice
|
||||
return ''.join([choice(string.ascii_letters + string.digits) for i in range(length)])
|
||||
return ''.join(choice(string.ascii_letters + string.digits) for i in range(length))
|
||||
|
||||
|
||||
def has_gravatar(email):
|
||||
|
|
@ -311,7 +311,7 @@ def make_esc(esc_chars):
|
|||
"""
|
||||
Function generator for Escaping special characters
|
||||
"""
|
||||
return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
|
||||
return lambda s: ''.join('\\' + c if c in esc_chars else c for c in s)
|
||||
|
||||
# esc / unescape characters -- used for command line
|
||||
def esc(s, esc_chars):
|
||||
|
|
@ -748,9 +748,9 @@ def set_request(**kwargs):
|
|||
frappe.local.request = Request(builder.get_environ())
|
||||
|
||||
def get_html_for_route(route):
|
||||
from frappe.website import render
|
||||
from frappe.website.serve import get_response
|
||||
set_request(method='GET', path=route)
|
||||
response = render.render()
|
||||
response = get_response()
|
||||
html = frappe.safe_decode(response.get_data())
|
||||
return html
|
||||
|
||||
|
|
|
|||
|
|
@ -307,8 +307,8 @@ class BackupGenerator:
|
|||
backup_summary = self.get_summary()
|
||||
print("Backup Summary for {0} at {1}".format(frappe.local.site, now()))
|
||||
|
||||
title = max([len(x) for x in backup_summary])
|
||||
path = max([len(x["path"]) for x in backup_summary.values()])
|
||||
title = max(len(x) for x in backup_summary)
|
||||
path = max(len(x["path"]) for x in backup_summary.values())
|
||||
|
||||
for _type, info in backup_summary.items():
|
||||
template = "{{0:{0}}}: {{1:{1}}} {{2}}".format(title, path)
|
||||
|
|
@ -381,7 +381,7 @@ class BackupGenerator:
|
|||
"",
|
||||
])
|
||||
|
||||
generated_header = "\n".join([f"-- {x}" for x in database_header_content]) + "\n"
|
||||
generated_header = "\n".join(f"-- {x}" for x in database_header_content) + "\n"
|
||||
|
||||
with gzip.open(args.backup_path_db, "wt") as f:
|
||||
f.write(generated_header)
|
||||
|
|
|
|||
|
|
@ -359,7 +359,6 @@ Configuration for docs
|
|||
"""
|
||||
|
||||
# source_link = "https://github.com/[org_name]/{app_name}"
|
||||
# docs_base_url = "https://[org_name].github.io/{app_name}"
|
||||
# headline = "App that does everything"
|
||||
# sub_heading = "Yes, you got that right the first time, everything"
|
||||
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@ class BotParser(object):
|
|||
|
||||
def format_list(self, data):
|
||||
'''Format list as markdown'''
|
||||
return _('I found these: ') + ', '.join([' [{title}](/app/Form/{doctype}/{name})'.format(
|
||||
return _('I found these:') + ' ' + ', '.join(' [{title}](/app/Form/{doctype}/{name})'.format(
|
||||
title = d.title or d.name,
|
||||
doctype=self.get_doctype(),
|
||||
name=d.name) for d in data])
|
||||
name=d.name) for d in data)
|
||||
|
||||
def get_doctype(self):
|
||||
'''returns the doctype name from self.tables'''
|
||||
|
|
@ -56,8 +56,8 @@ class ShowNotificationBot(BotParser):
|
|||
|
||||
if open_items:
|
||||
return ("Following items need your attention:\n\n"
|
||||
+ "\n\n".join(["{0} [{1}](/app/List/{1})".format(d[1], d[0])
|
||||
for d in open_items if d[1] > 0]))
|
||||
+ "\n\n".join("{0} [{1}](/app/List/{1})".format(d[1], d[0])
|
||||
for d in open_items if d[1] > 0))
|
||||
else:
|
||||
return 'Take it easy, nothing urgent needs your attention'
|
||||
|
||||
|
|
|
|||
|
|
@ -307,14 +307,14 @@ def get_routes_to_index():
|
|||
|
||||
def add_route_to_global_search(route):
|
||||
from bs4 import BeautifulSoup
|
||||
from frappe.website.render import render_page
|
||||
from frappe.website.serve import get_response_content
|
||||
from frappe.utils import set_request
|
||||
frappe.set_user('Guest')
|
||||
frappe.local.no_cache = True
|
||||
|
||||
try:
|
||||
set_request(method='GET', path=route)
|
||||
content = render_page(route)
|
||||
content = get_response_content(route)
|
||||
soup = BeautifulSoup(content, 'html.parser')
|
||||
page_content = soup.find(class_='page_content')
|
||||
text_content = page_content.text if page_content else ''
|
||||
|
|
@ -329,7 +329,7 @@ def add_route_to_global_search(route):
|
|||
route=route
|
||||
)
|
||||
sync_value_in_queue(value)
|
||||
except (frappe.PermissionError, frappe.DoesNotExistError, frappe.ValidationError, Exception):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
frappe.set_user('Administrator')
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ def resolve_class(classes):
|
|||
return classes
|
||||
|
||||
if isinstance(classes, (list, tuple)):
|
||||
return " ".join([resolve_class(c) for c in classes]).strip()
|
||||
return " ".join(resolve_class(c) for c in classes).strip()
|
||||
|
||||
if isinstance(classes, dict):
|
||||
return " ".join([classname for classname in classes if classes[classname]]).strip()
|
||||
return " ".join(classname for classname in classes if classes[classname]).strip()
|
||||
|
||||
return classes
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ def get_pdf(html, options=None, output=None):
|
|||
except OSError as e:
|
||||
if any([error in str(e) for error in PDF_CONTENT_ERRORS]):
|
||||
if not filedata:
|
||||
print(html, options)
|
||||
frappe.throw(_("PDF generation failed because of broken image links"))
|
||||
|
||||
# allow pdfs with missing images if file got created
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from PyPDF2 import PdfFileWriter
|
|||
|
||||
no_cache = 1
|
||||
|
||||
base_template_path = "templates/www/printview.html"
|
||||
base_template_path = "www/printview.html"
|
||||
standard_format = "templates/print_formats/standard.html"
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -147,8 +147,8 @@ def json_handler(obj):
|
|||
|
||||
def as_page():
|
||||
"""print web page"""
|
||||
from frappe.website.render import render
|
||||
return render(frappe.response['route'], http_status_code=frappe.response.get("http_status_code"))
|
||||
from frappe.website.serve import get_response
|
||||
return get_response(frappe.response['route'], http_status_code=frappe.response.get("http_status_code"))
|
||||
|
||||
def redirect():
|
||||
return werkzeug.utils.redirect(frappe.response.location)
|
||||
|
|
@ -215,7 +215,8 @@ def send_private_file(path):
|
|||
return response
|
||||
|
||||
def handle_session_stopped():
|
||||
from frappe.website.serve import get_response
|
||||
frappe.respond_as_web_page(_("Updating"),
|
||||
_("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)
|
||||
return get_response("message", http_status_code=503)
|
||||
|
|
|
|||
|
|
@ -1,296 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
import frappe, os, json
|
||||
|
||||
from frappe.website.doctype.website_settings.website_settings import get_website_settings
|
||||
from frappe.website.router import get_page_context
|
||||
from frappe.model.document import Document
|
||||
|
||||
def get_context(path, args=None):
|
||||
if args and args.source:
|
||||
context = args
|
||||
else:
|
||||
context = get_page_context(path)
|
||||
if args:
|
||||
context.update(args)
|
||||
|
||||
if hasattr(frappe.local, 'request'):
|
||||
# for <body data-path=""> (remove leading slash)
|
||||
# path could be overriden in render.resolve_from_map
|
||||
context["path"] = frappe.local.request.path.strip('/ ')
|
||||
else:
|
||||
context["path"] = path
|
||||
|
||||
context.canonical = frappe.utils.get_url(frappe.utils.escape_html(context.path))
|
||||
context.route = context.path
|
||||
context = build_context(context)
|
||||
|
||||
# set using frappe.respond_as_web_page
|
||||
if hasattr(frappe.local, 'response') and frappe.local.response.get('context'):
|
||||
context.update(frappe.local.response.context)
|
||||
|
||||
# to be able to inspect the context dict
|
||||
# Use the macro "inspect" from macros.html
|
||||
context._context_dict = context
|
||||
|
||||
context.developer_mode = frappe.conf.developer_mode
|
||||
|
||||
return context
|
||||
|
||||
def update_controller_context(context, controller):
|
||||
module = frappe.get_module(controller)
|
||||
|
||||
if module:
|
||||
# get config fields
|
||||
for prop in ("base_template_path", "template", "no_cache", "sitemap",
|
||||
"condition_field"):
|
||||
if hasattr(module, prop):
|
||||
context[prop] = getattr(module, prop)
|
||||
|
||||
if hasattr(module, "get_context"):
|
||||
import inspect
|
||||
try:
|
||||
if inspect.getfullargspec(module.get_context).args:
|
||||
ret = module.get_context(context)
|
||||
else:
|
||||
ret = module.get_context()
|
||||
if ret:
|
||||
context.update(ret)
|
||||
except (frappe.PermissionError, frappe.PageDoesNotExistError, frappe.Redirect):
|
||||
raise
|
||||
except:
|
||||
if not any([frappe.flags.in_migrate, frappe.flags.in_website_search_build]):
|
||||
frappe.errprint(frappe.utils.get_traceback())
|
||||
|
||||
if hasattr(module, "get_children"):
|
||||
context.children = module.get_children(context)
|
||||
|
||||
|
||||
def build_context(context):
|
||||
"""get_context method of doc or module is supposed to render
|
||||
content templates and push it into context"""
|
||||
context = frappe._dict(context)
|
||||
|
||||
if not "url_prefix" in context:
|
||||
context.url_prefix = ""
|
||||
|
||||
if context.url_prefix and context.url_prefix[-1]!='/':
|
||||
context.url_prefix += '/'
|
||||
|
||||
# for backward compatibility
|
||||
context.docs_base_url = '/docs'
|
||||
|
||||
context.update(get_website_settings(context))
|
||||
context.update(frappe.local.conf.get("website_context") or {})
|
||||
|
||||
# provide doc
|
||||
if context.doc:
|
||||
context.update(context.doc.as_dict())
|
||||
context.update(context.doc.get_website_properties())
|
||||
|
||||
if not context.template:
|
||||
context.template = context.doc.meta.get_web_template()
|
||||
|
||||
if hasattr(context.doc, "get_context"):
|
||||
ret = context.doc.get_context(context)
|
||||
|
||||
if ret:
|
||||
context.update(ret)
|
||||
|
||||
for prop in ("no_cache", "sitemap"):
|
||||
if not prop in context:
|
||||
context[prop] = getattr(context.doc, prop, False)
|
||||
|
||||
elif context.controller:
|
||||
# controller based context
|
||||
update_controller_context(context, context.controller)
|
||||
|
||||
# controller context extensions
|
||||
context_controller_hooks = frappe.get_hooks("extend_website_page_controller_context") or {}
|
||||
for controller, extension in context_controller_hooks.items():
|
||||
if isinstance(extension, list):
|
||||
for ext in extension:
|
||||
if controller == context.controller:
|
||||
update_controller_context(context, ext)
|
||||
else:
|
||||
update_controller_context(context, extension)
|
||||
|
||||
add_metatags(context)
|
||||
add_sidebar_and_breadcrumbs(context)
|
||||
|
||||
# determine templates to be used
|
||||
if not context.base_template_path:
|
||||
app_base = frappe.get_hooks("base_template")
|
||||
context.base_template_path = app_base[-1] if app_base else "templates/base.html"
|
||||
|
||||
if context.title_prefix and context.title and not context.title.startswith(context.title_prefix):
|
||||
context.title = '{0} - {1}'.format(context.title_prefix, context.title)
|
||||
|
||||
# apply context from hooks
|
||||
update_website_context = frappe.get_hooks('update_website_context')
|
||||
for method in update_website_context:
|
||||
values = frappe.get_attr(method)(context)
|
||||
if values:
|
||||
context.update(values)
|
||||
|
||||
return context
|
||||
|
||||
def load_sidebar(context, sidebar_json_path):
|
||||
with open(sidebar_json_path, 'r') as sidebarfile:
|
||||
try:
|
||||
sidebar_json = sidebarfile.read()
|
||||
context.sidebar_items = json.loads(sidebar_json)
|
||||
context.show_sidebar = 1
|
||||
except json.decoder.JSONDecodeError:
|
||||
frappe.throw('Invalid Sidebar JSON at ' + sidebar_json_path)
|
||||
|
||||
def get_sidebar_json_path(path, look_for=False):
|
||||
'''
|
||||
Get _sidebar.json path from directory path
|
||||
|
||||
:param path: path of the current diretory
|
||||
:param look_for: if True, look for _sidebar.json going upwards from given path
|
||||
|
||||
:return: _sidebar.json path
|
||||
'''
|
||||
if os.path.split(path)[1] == 'www' or path == '/' or not path:
|
||||
return ''
|
||||
|
||||
sidebar_json_path = os.path.join(path, '_sidebar.json')
|
||||
if os.path.exists(sidebar_json_path):
|
||||
return sidebar_json_path
|
||||
else:
|
||||
if look_for:
|
||||
return get_sidebar_json_path(os.path.split(path)[0], look_for)
|
||||
else:
|
||||
return ''
|
||||
|
||||
def add_sidebar_and_breadcrumbs(context):
|
||||
'''Add sidebar and breadcrumbs to context'''
|
||||
from frappe.website.router import get_page_info_from_template
|
||||
if context.show_sidebar:
|
||||
context.no_cache = 1
|
||||
add_sidebar_data(context)
|
||||
else:
|
||||
if context.basepath:
|
||||
hooks = frappe.get_hooks('look_for_sidebar_json')
|
||||
look_for_sidebar_json = hooks[0] if hooks else 0
|
||||
sidebar_json_path = get_sidebar_json_path(
|
||||
context.basepath,
|
||||
look_for_sidebar_json
|
||||
)
|
||||
if sidebar_json_path:
|
||||
load_sidebar(context, sidebar_json_path)
|
||||
|
||||
if context.add_breadcrumbs and not context.parents:
|
||||
if context.basepath:
|
||||
parent_path = os.path.dirname(context.path).rstrip('/')
|
||||
page_info = get_page_info_from_template(parent_path)
|
||||
if page_info:
|
||||
context.parents = [dict(route=parent_path, title=page_info.title)]
|
||||
|
||||
def add_sidebar_data(context):
|
||||
from frappe.utils.user import get_fullname_and_avatar
|
||||
import frappe.www.list
|
||||
|
||||
if context.show_sidebar and context.website_sidebar:
|
||||
context.sidebar_items = frappe.get_all('Website Sidebar Item',
|
||||
filters=dict(parent=context.website_sidebar), fields=['title', 'route', '`group`'],
|
||||
order_by='idx asc')
|
||||
|
||||
if not context.sidebar_items:
|
||||
sidebar_items = frappe.cache().hget('portal_menu_items', frappe.session.user)
|
||||
if sidebar_items == None:
|
||||
sidebar_items = []
|
||||
roles = frappe.get_roles()
|
||||
portal_settings = frappe.get_doc('Portal Settings', 'Portal Settings')
|
||||
|
||||
def add_items(sidebar_items, items):
|
||||
for d in items:
|
||||
if d.get('enabled') and ((not d.get('role')) or d.get('role') in roles):
|
||||
sidebar_items.append(d.as_dict() if isinstance(d, Document) else d)
|
||||
|
||||
if not portal_settings.hide_standard_menu:
|
||||
add_items(sidebar_items, portal_settings.get('menu'))
|
||||
|
||||
if portal_settings.custom_menu:
|
||||
add_items(sidebar_items, portal_settings.get('custom_menu'))
|
||||
|
||||
items_via_hooks = frappe.get_hooks('portal_menu_items')
|
||||
if items_via_hooks:
|
||||
for i in items_via_hooks: i['enabled'] = 1
|
||||
add_items(sidebar_items, items_via_hooks)
|
||||
|
||||
frappe.cache().hset('portal_menu_items', frappe.session.user, sidebar_items)
|
||||
|
||||
context.sidebar_items = sidebar_items
|
||||
|
||||
info = get_fullname_and_avatar(frappe.session.user)
|
||||
context["fullname"] = info.fullname
|
||||
context["user_image"] = info.avatar
|
||||
context["user"] = info.name
|
||||
|
||||
|
||||
def add_metatags(context):
|
||||
tags = frappe._dict(context.get("metatags") or {})
|
||||
|
||||
if "og:type" not in tags:
|
||||
tags["og:type"] = "article"
|
||||
|
||||
if "title" not in tags and context.title:
|
||||
tags["title"] = context.title
|
||||
|
||||
title = tags.get("name") or tags.get("title")
|
||||
if title:
|
||||
tags["og:title"] = tags["twitter:title"] = title
|
||||
tags["twitter:card"] = "summary"
|
||||
|
||||
if "description" not in tags and context.description:
|
||||
tags["description"] = context.description
|
||||
|
||||
description = tags.get("description")
|
||||
if description:
|
||||
tags["og:description"] = tags["twitter:description"] = description
|
||||
|
||||
if "image" not in tags and context.image:
|
||||
tags["image"] = context.image
|
||||
|
||||
image = tags.get("image")
|
||||
if image:
|
||||
tags["og:image"] = tags["twitter:image"] = tags["image"] = frappe.utils.get_url(image)
|
||||
tags['twitter:card'] = "summary_large_image"
|
||||
|
||||
if "author" not in tags and context.author:
|
||||
tags["author"] = context.author
|
||||
|
||||
tags["og:url"] = tags["url"] = frappe.utils.get_url(context.path)
|
||||
|
||||
if "published_on" not in tags and context.published_on:
|
||||
tags["published_on"] = context.published_on
|
||||
|
||||
if "published_on" in tags:
|
||||
tags["datePublished"] = tags["published_on"]
|
||||
del tags["published_on"]
|
||||
|
||||
tags["language"] = frappe.local.lang or "en"
|
||||
|
||||
# Get meta tags from Website Route meta
|
||||
# they can override the defaults set above
|
||||
route = context.path
|
||||
if route == '':
|
||||
# homepage
|
||||
route = frappe.db.get_single_value('Website Settings', 'home_page')
|
||||
|
||||
route_exists = (route
|
||||
and not route.endswith(('.js', '.css'))
|
||||
and frappe.db.exists('Website Route Meta', route))
|
||||
|
||||
if route_exists:
|
||||
website_route_meta = frappe.get_doc('Website Route Meta', route)
|
||||
for meta_tag in website_route_meta.meta_tags:
|
||||
d = meta_tag.get_meta_dict()
|
||||
tags.update(d)
|
||||
|
||||
# update tags in context
|
||||
context.metatags = tags
|
||||
|
|
@ -10,7 +10,7 @@ from frappe.model.document import Document
|
|||
class AboutUsSettings(Document):
|
||||
|
||||
def on_update(self):
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.website.utils import clear_cache
|
||||
clear_cache("about")
|
||||
|
||||
def get_args():
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# MIT License. See license.txt
|
||||
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.website.utils import clear_cache
|
||||
|
||||
class BlogCategory(WebsiteGenerator):
|
||||
def autoname(self):
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"featured",
|
||||
"hide_cta",
|
||||
"disable_comments",
|
||||
"disable_feedback",
|
||||
"section_break_5",
|
||||
"blog_intro",
|
||||
"content_type",
|
||||
|
|
@ -191,6 +192,13 @@
|
|||
"fieldtype": "Data",
|
||||
"label": "Meta Title",
|
||||
"length": 60
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Feedback on this blog post will be disabled if checked.",
|
||||
"fieldname": "disable_feedback",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Feedback"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
|
|
@ -200,7 +208,7 @@
|
|||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"max_attachments": 5,
|
||||
"modified": "2020-12-23 14:28:36.311389",
|
||||
"modified": "2021-06-14 13:50:02.109719",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Blog Post",
|
||||
|
|
|
|||
|
|
@ -4,18 +4,13 @@
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.website.utils import clear_cache
|
||||
from frappe.utils import today, cint, global_date_format, get_fullname, strip_html_tags, markdown, sanitize_html
|
||||
from math import ceil
|
||||
from frappe.website.utils import (find_first_image, get_html_content_based_on_type,
|
||||
get_comment_list)
|
||||
|
||||
class BlogPost(WebsiteGenerator):
|
||||
website = frappe._dict(
|
||||
route = 'blog',
|
||||
order_by = "published_on desc"
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_route(self):
|
||||
if not self.route:
|
||||
|
|
@ -102,12 +97,14 @@ class BlogPost(WebsiteGenerator):
|
|||
context.metatags["image"] = self.meta_image or image or None
|
||||
|
||||
self.load_comments(context)
|
||||
self.load_feedback(context)
|
||||
|
||||
context.category = frappe.db.get_value("Blog Category",
|
||||
context.doc.blog_category, ["title", "route"], as_dict=1)
|
||||
context.parents = [{"name": _("Home"), "route":"/"},
|
||||
{"name": "Blog", "route": "/blog"},
|
||||
{"label": context.category.title, "route":context.category.route}]
|
||||
context.guest_allowed = True
|
||||
|
||||
def fetch_cta(self):
|
||||
if frappe.db.get_single_value("Blog Settings", "show_cta_in_blog", cache=True):
|
||||
|
|
@ -149,6 +146,17 @@ class BlogPost(WebsiteGenerator):
|
|||
else:
|
||||
context.comment_text = _('{0} comments').format(len(context.comment_list))
|
||||
|
||||
def load_feedback(self, context):
|
||||
feedback = frappe.get_all('Feedback',
|
||||
fields=['email', 'feedback', 'rating'],
|
||||
filters=dict(
|
||||
reference_doctype=self.doctype,
|
||||
reference_name=self.name,
|
||||
email=frappe.session.user
|
||||
)
|
||||
)
|
||||
context.user_feedback = feedback[0] if feedback else ''
|
||||
|
||||
def set_read_time(self):
|
||||
content = self.content or self.content_html or ''
|
||||
if self.content_type == "Markdown":
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue