Merge branch 'develop' into permlevel-apis

This commit is contained in:
gavin 2023-01-10 17:45:46 +05:30 committed by GitHub
commit 2b27652bd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 310 additions and 97 deletions

View file

@ -23,10 +23,15 @@ def fetch_pr_data(pr_number, repo, endpoint=""):
def req(url):
"Simple resilient request call to handle rate limits."
headers = None
token = os.environ.get("GITHUB_TOKEN")
if token:
headers = {"authorization": f"Bearer {token}"}
retries = 0
while True:
try:
req = urllib.request.Request(url)
req = urllib.request.Request(url, headers=headers)
return urllib.request.urlopen(req)
except HTTPError as exc:
if exc.code == 403 and retries < 5:

View file

@ -9,6 +9,7 @@ concurrency:
cancel-in-progress: true
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
contents: read
jobs:
@ -31,6 +32,7 @@ jobs:
TYPE: "server"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test:
name: Patch

View file

@ -12,6 +12,7 @@ concurrency:
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
contents: read
jobs:
@ -34,6 +35,7 @@ jobs:
TYPE: "server"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test:
name: Unit Tests

View file

@ -11,6 +11,7 @@ concurrency:
cancel-in-progress: true
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
contents: read
jobs:
@ -33,6 +34,7 @@ jobs:
TYPE: "ui"
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test:
runs-on: ubuntu-latest

View file

@ -1,7 +1,7 @@
# Security Policy
The Frappe team and community take security issues in the Frappe Framework seriously. To report a security issue, fill out the form at [https://erpnext.com/security/report](https://erpnext.com/security/report).
The Frappe team and community take security issues in the Frappe Framework seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).
You can help us make Frappe and consequently all Frappe dependent apps like [ERPNext](https://erpnext.com) more secure by following the [Reporting guidelines](https://erpnext.com/security).
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.

View file

@ -9,7 +9,7 @@ coverage:
target: auto
threshold: 0.5%
flags:
- server-mariadb
- server
patch:
default:
target: 85%
@ -17,7 +17,7 @@ coverage:
only_pulls: true
if_ci_failed: ignore
flags:
- server-mariadb
- server
comment:
layout: "diff, flags"
@ -25,11 +25,7 @@ comment:
show_critical_paths: true
flags:
server-mariadb:
paths:
- "**/*.py"
carryforward: true
server-postgres:
server:
paths:
- "**/*.py"
carryforward: true

View file

@ -190,6 +190,48 @@ context("Workspace 2.0", () => {
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
});
it("Hide/Unhide Workspaces", () => {
// hide
cy.intercept({
method: "POST",
url: "api/method/frappe.desk.doctype.workspace.workspace.hide_page",
}).as("hide_page");
cy.get(".codex-editor__redactor .ce-block");
cy.get(".standard-actions .btn-secondary[data-label=Edit]").click();
cy.get('.sidebar-item-container[item-name="Duplicate Page"]')
.find(".sidebar-item-control .setting-btn")
.click();
cy.get('.sidebar-item-container[item-name="Duplicate Page"]')
.find('.dropdown-item[title="Hide Workspace"]')
.click({ force: true });
cy.wait(300);
cy.get('.standard-actions .btn-secondary[data-label="Discard"]').click();
cy.get('.sidebar-item-container[item-name="Duplicate Page"]').should("not.be.visible");
cy.wait("@hide_page");
// unhide
cy.intercept({
method: "POST",
url: "api/method/frappe.desk.doctype.workspace.workspace.unhide_page",
}).as("unhide_page");
cy.get(".codex-editor__redactor .ce-block");
cy.get(".standard-actions .btn-secondary[data-label=Edit]").click();
cy.get('.sidebar-item-container[item-name="Duplicate Page"]')
.find('[title="Unhide Workspace"]')
.click({ force: true });
cy.wait(300);
cy.get('.standard-actions .btn-secondary[data-label="Discard"]').click();
cy.get('.sidebar-item-container[item-name="Duplicate Page"]').should("be.visible");
cy.wait("@unhide_page");
});
it("Delete Duplicate Page", () => {
cy.intercept({
method: "POST",

View file

@ -42,27 +42,26 @@ def _supports_log_clearing(doctype: str) -> bool:
class LogSettings(Document):
def validate(self):
self.validate_supported_doctypes()
self.validate_duplicates()
self._remove_unsupported_doctypes()
self._deduplicate_entries()
self.add_default_logtypes()
def validate_supported_doctypes(self):
for entry in self.logs_to_clear:
def _remove_unsupported_doctypes(self):
for entry in list(self.logs_to_clear):
if _supports_log_clearing(entry.ref_doctype):
continue
msg = _("{} does not support automated log clearing.").format(frappe.bold(entry.ref_doctype))
if frappe.conf.developer_mode:
msg += "<br>" + _("Implement `clear_old_logs` method to enable auto error clearing.")
frappe.throw(msg, title=_("DocType not supported by Log Settings."))
frappe.msgprint(msg, title=_("DocType not supported by Log Settings."))
self.remove(entry)
def validate_duplicates(self):
def _deduplicate_entries(self):
seen = set()
for entry in self.logs_to_clear:
for entry in list(self.logs_to_clear):
if entry.ref_doctype in seen:
frappe.throw(
_("{} appears more than once in configured log doctypes.").format(entry.ref_doctype)
)
self.remove(entry)
seen.add(entry.ref_doctype)
def add_default_logtypes(self):

View file

@ -1,3 +1,5 @@
from pymysql.constants.ER import DUP_ENTRY
import frappe
from frappe import _
from frappe.database.schema import DBTable
@ -115,17 +117,15 @@ class MariaDBTable(DBTable):
frappe.db.sql(query)
except Exception as e:
# sanitize
if e.args[0] == 1060:
frappe.throw(str(e))
elif e.args[0] == 1062:
if query := locals().get("query"): # this weirdness is to avoid potentially unbounded vars
print(f"Failed to alter schema using query: {query}")
if e.args[0] == DUP_ENTRY:
fieldname = str(e).split("'")[-2]
frappe.throw(
_("{0} field cannot be set as unique in {1}, as there are non-unique existing values").format(
fieldname, self.table_name
)
)
elif e.args[0] == 1067:
frappe.throw(str(e.args[1]))
else:
raise e
raise

View file

@ -379,7 +379,17 @@ def get_workspace_sidebar_items():
# pages sorted based on sequence id
order_by = "sequence_id asc"
fields = ["name", "title", "for_user", "parent_page", "content", "public", "module", "icon"]
fields = [
"name",
"title",
"for_user",
"parent_page",
"content",
"public",
"module",
"icon",
"is_hidden",
]
all_pages = frappe.get_all(
"Workspace", fields=fields, filters=filters, order_by=order_by, ignore_permissions=True
)
@ -391,7 +401,7 @@ def get_workspace_sidebar_items():
try:
workspace = Workspace(page, True)
if has_access or workspace.is_permitted():
if page.public:
if page.public and (has_access or not page.is_hidden):
pages.append(page)
elif page.for_user == frappe.session.user:
private_pages.append(page)

View file

@ -19,6 +19,7 @@
"restrict_to_domain",
"hide_custom",
"public",
"is_hidden",
"content",
"tab_break_2",
"charts",
@ -174,11 +175,17 @@
"fieldtype": "Table",
"label": "Quick Lists",
"options": "Workspace Quick List"
},
{
"default": "0",
"fieldname": "is_hidden",
"fieldtype": "Check",
"label": "Is Hidden"
}
],
"in_create": 1,
"links": [],
"modified": "2023-01-07 17:02:48.278025",
"modified": "2023-01-07 19:37:39.512482",
"modified_by": "Administrator",
"module": "Desk",
"name": "Workspace",

View file

@ -247,6 +247,32 @@ def update_page(name, title, icon, parent, public):
return {"name": title, "public": public, "label": new_name}
def hide_unhide_page(page_name: str, is_hidden: bool):
page = frappe.get_doc("Workspace", page_name)
if page.get("public") and not is_workspace_manager():
frappe.throw(
_("Need Workspace Manager role to hide/unhide public workspaces"), frappe.PermissionError
)
if not page.get("public") and page.get("for_user") != frappe.session.user:
frappe.throw(_("Cannot update private workspace of other users"), frappe.PermissionError)
page.is_hidden = int(is_hidden)
page.save(ignore_permissions=True)
return True
@frappe.whitelist()
def hide_page(page_name: str):
return hide_unhide_page(page_name, 1)
@frappe.whitelist()
def unhide_page(page_name: str):
return hide_unhide_page(page_name, 0)
@frappe.whitelist()
def duplicate_page(page_name, new_page):
if not loads(new_page):

View file

@ -6,6 +6,7 @@ import json
import frappe
from frappe.core.doctype.submission_queue.submission_queue import queue_submission
from frappe.desk.form.load import run_onload
from frappe.model.docstatus import DocStatus
from frappe.monitor import add_data_to_monitor
from frappe.utils.scheduler import is_scheduler_inactive
@ -17,8 +18,14 @@ def savedocs(doc, action):
set_local_name(doc)
# action
doc.docstatus = {"Save": 0, "Submit": 1, "Update": 1, "Cancel": 2}[action]
if doc.docstatus == 1:
doc.docstatus = {
"Save": DocStatus.draft(),
"Submit": DocStatus.submitted(),
"Update": DocStatus.submitted(),
"Cancel": DocStatus.cancelled(),
}[action]
if doc.docstatus.is_submitted():
if action == "Submit" and doc.meta.queue_in_background and not is_scheduler_inactive():
queue_submission(doc, action)
return

View file

@ -294,7 +294,7 @@ def save_report(name, doctype, report_settings):
if report.report_type != "Report Builder":
frappe.throw(_("Only reports of type Report Builder can be edited"))
if report.owner != frappe.session.user and not frappe.has_permission("Report", "write"):
if report.owner != frappe.session.user and not report.has_permission("write"):
frappe.throw(_("Insufficient Permissions for editing Report"), frappe.PermissionError)
else:
report = frappe.new_doc("Report")
@ -323,7 +323,7 @@ def delete_report(name):
if report.report_type != "Report Builder":
frappe.throw(_("Only reports of type Report Builder can be deleted"))
if report.owner != frappe.session.user and not frappe.has_permission("Report", "delete"):
if report.owner != frappe.session.user and not report.has_permission("delete"):
frappe.throw(_("Insufficient Permissions for deleting Report"), frappe.PermissionError)
report.delete(ignore_permissions=True)

View file

@ -392,4 +392,5 @@ ignore_links_on_delete = [
"Email Queue",
"Document Share Key",
"Integration Request",
"Unhandled Email",
]

View file

@ -9,6 +9,7 @@ import frappe.defaults
import frappe.model.meta
from frappe import _, get_module_path
from frappe.desk.doctype.tag.tag import delete_tags_for_document
from frappe.model.docstatus import DocStatus
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.model.naming import revert_series_if_last
from frappe.model.utils import is_virtual_doctype
@ -265,7 +266,7 @@ def check_if_doc_is_linked(doc, method="Delete"):
# don't check for communication and todo!
continue
if method != "Delete" and (method != "Cancel" or item.docstatus != 1):
if method != "Delete" and (method != "Cancel" or not DocStatus(item.docstatus).is_submitted()):
# don't raise exception if not
# linked to a non-cancelled doc when deleting or to a submitted doc when cancelling
continue
@ -302,13 +303,12 @@ def check_if_doc_is_dynamically_linked(doc, method="Delete"):
refdoc.get(df.options) == doc.doctype
and refdoc.get(df.fieldname) == doc.name
and (
(method == "Delete" and refdoc.docstatus < 2)
or (method == "Cancel" and refdoc.docstatus == 1)
# linked to an non-cancelled doc when deleting
(method == "Delete" and not DocStatus(refdoc.docstatus).is_cancelled())
# linked to a submitted doc when cancelling
or (method == "Cancel" and DocStatus(refdoc.docstatus).is_submitted())
)
):
# raise exception only if
# linked to an non-cancelled doc when deleting
# or linked to a submitted doc when cancelling
raise_link_exists_exception(doc, df.parent, df.parent)
else:
# dynamic link in table
@ -321,14 +321,11 @@ def check_if_doc_is_dynamically_linked(doc, method="Delete"):
(doc.doctype, doc.name),
as_dict=True,
):
if (method == "Delete" and refdoc.docstatus < 2) or (
method == "Cancel" and refdoc.docstatus == 1
# linked to an non-cancelled doc when deleting
# or linked to a submitted doc when cancelling
if (method == "Delete" and not DocStatus(refdoc.docstatus).is_cancelled()) or (
method == "Cancel" and DocStatus(refdoc.docstatus).is_submitted()
):
# raise exception only if
# linked to an non-cancelled doc when deleting
# or linked to a submitted doc when cancelling
reference_doctype = refdoc.parenttype if meta.istable else df.parent
reference_docname = refdoc.parent if meta.istable else refdoc.name
at_position = f"at Row: {refdoc.idx}" if meta.istable else ""

View file

@ -27,22 +27,6 @@ rights = (
)
def check_admin_or_system_manager(user=None):
from frappe.utils.commands import warn
warn(
"The function check_admin_or_system_manager will be deprecated in version 15."
'Please use frappe.only_for("System Manager") instead.',
category=PendingDeprecationWarning,
)
if not user:
user = frappe.session.user
if ("System Manager" not in frappe.get_roles(user)) and (user != "Administrator"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
def print_has_permission_check_logs(func):
def inner(*args, **kwargs):
frappe.flags["has_permission_check_logs"] = []

View file

@ -48,6 +48,16 @@
<path d="M9 3L13 5.99999L9 9" stroke="var(--icon-stroke)" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" id="icon-unhide">
<path stroke="none" fill-rule="evenodd" clip-rule="evenodd" d="M2.10756 9.53547C1.93501 9.82126 1.93501 10.1787 2.10756 10.4645C3.75635 13.1955 6.60531 15 9.84351 15C13.0817 15 15.9307 13.1955 17.5795 10.4645C17.752 10.1787 17.752 9.82127 17.5795 9.53548C15.9307 6.80451 13.0817 5 9.84351 5C6.60531 5 3.75635 6.8045 2.10756 9.53547ZM10 13C11.6569 13 13 11.6569 13 10C13 8.34315 11.6569 7 10 7C8.34315 7 7 8.34315 7 10C7 11.6569 8.34315 13 10 13Z" fill="var(--icon-stroke)"/>
<circle cx="10" cy="10" r="1" stroke="none" fill="var(--icon-stroke)"/>
</symbol>
<symbol viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" id="icon-hide">
<rect stroke="none" x="3.02185" y="3.89151" width="1.26078" height="18.4481" rx="0.630391" transform="rotate(-45 3.02185 3.89151)" fill="var(--icon-stroke)"/>
<path stroke="none" fill-rule="evenodd" clip-rule="evenodd" d="M5.02016 6.99831C4.84611 6.82426 4.57032 6.80165 4.37821 6.95554C3.49472 7.66323 2.73193 8.53749 2.12941 9.53547C1.95686 9.82126 1.95686 10.1787 2.12941 10.4645C3.7782 13.1955 6.62716 15 9.86536 15C10.5301 15 11.1784 14.924 11.8032 14.7795C12.1655 14.6957 12.2727 14.2508 12.0098 13.9879L11.1052 13.0833C10.9747 12.9529 10.7837 12.9083 10.6027 12.9438C10.4148 12.9807 10.2206 13 10.0219 13C8.365 13 7.02185 11.6569 7.02185 10C7.02185 9.80128 7.04117 9.60707 7.07804 9.41915C7.11355 9.23815 7.06896 9.04711 6.93853 8.91668L5.02016 6.99831ZM12.1967 12.8433C11.9793 12.6259 12.011 12.2666 12.2202 12.0414C12.7176 11.506 13.0219 10.7885 13.0219 10C13.0219 8.34315 11.6787 7 10.0219 7C9.23334 7 8.51587 7.30421 7.98043 7.80167C7.75522 8.0109 7.3959 8.04255 7.17854 7.82518L5.98518 6.63183C5.75274 6.39939 5.80413 6.00935 6.10001 5.86613C7.24996 5.3095 8.52428 5 9.86536 5C13.1036 5 15.9525 6.80451 17.6013 9.53548C17.7739 9.82127 17.7739 10.1787 17.6013 10.4645C16.6787 11.9927 15.3803 13.2307 13.8482 14.0249C13.6613 14.1218 13.4343 14.0809 13.2854 13.932L12.1967 12.8433Z" fill="var(--icon-stroke)"/>
</symbol>
<symbol viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" id="icon-sidebar-collapse">
<path d="M12 6L6 12L12 18" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18 6L12 12L18 18" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 111 KiB

View file

@ -78,9 +78,13 @@ frappe.views.Workspace = class Workspace {
sidebar_item_container(item) {
return $(`
<div class="sidebar-item-container ${item.is_editable ? "is-draggable" : ""}" item-parent="${
item.parent_page
}" item-name="${item.title}" item-public="${item.public || 0}">
<div
class="sidebar-item-container ${item.is_editable ? "is-draggable" : ""}"
item-parent="${item.parent_page}"
item-name="${item.title}"
item-public="${item.public || 0}"
item-is-hidden="${item.is_hidden || 0}"
>
<div class="desk-sidebar-item standard-sidebar-item ${item.selected ? "selected" : ""}">
<a
href="/app/${
@ -393,6 +397,7 @@ frappe.views.Workspace = class Workspace {
this.page.set_secondary_action(__("Edit"), async () => {
if (!this.editor || !this.editor.readOnly) return;
this.is_read_only = false;
this.toggle_hidden_workspaces(true);
await this.editor.readOnly.toggle();
this.editor.isReady.then(() => {
this.initialize_editorjs_undo();
@ -441,6 +446,7 @@ frappe.views.Workspace = class Workspace {
this.page.set_secondary_action(__("Discard"), async () => {
this.discard = true;
this.clear_page_actions();
this.toggle_hidden_workspaces(false);
await this.editor.readOnly.toggle();
this.is_read_only = true;
this.sidebar_pages = this.cached_pages;
@ -455,6 +461,10 @@ frappe.views.Workspace = class Workspace {
}
}
toggle_hidden_workspaces(show) {
$(".desk-sidebar").toggleClass("show-hidden-workspaces", show);
}
show_sidebar_actions() {
this.sidebar.find(".standard-sidebar-section").addClass("show-control");
this.make_sidebar_sortable();
@ -481,6 +491,15 @@ frappe.views.Workspace = class Workspace {
null,
sidebar_control
);
} else if (item.is_hidden) {
frappe.utils.add_custom_button(
frappe.utils.icon("unhide", "sm"),
(e) => this.unhide_workspace(item, e),
"unhide-workspace-btn",
__("Unhide Workspace"),
null,
sidebar_control
);
} else {
frappe.utils.add_custom_button(
frappe.utils.icon("drag", "xs"),
@ -720,6 +739,12 @@ frappe.views.Workspace = class Workspace {
icon: frappe.utils.icon("duplicate", "sm"),
action: () => this.duplicate_page(item),
},
{
label: __("Hide"),
title: __("Hide Workspace"),
icon: frappe.utils.icon("hide", "sm"),
action: (e) => this.hide_workspace(item, e),
},
];
if (this.is_item_deletable(item)) {
@ -748,7 +773,7 @@ frappe.views.Workspace = class Workspace {
html.click((event) => {
event.stopPropagation();
action && action();
action && action(event);
});
return html;
@ -910,6 +935,47 @@ frappe.views.Workspace = class Workspace {
d.show();
}
hide_unhide_workspace(page, event, hide) {
page.is_hidden = hide;
let sidebar_control = event.target.closest(".sidebar-item-control");
let sidebar_item_container = sidebar_control.closest(".sidebar-item-container");
$(sidebar_item_container).attr("item-is-hidden", hide);
$(sidebar_control).empty();
this.add_sidebar_actions(page, $(sidebar_control));
this.add_drop_icon(page, $(sidebar_control), $(sidebar_item_container));
let cached_page = this.cached_pages.pages.findIndex((p) => p.name === page.name);
if (cached_page !== -1) {
this.cached_pages.pages[cached_page].is_hidden = hide;
}
let method = hide ? "hide_page" : "unhide_page";
frappe.call({
method: "frappe.desk.doctype.workspace.workspace." + method,
args: {
page_name: page.name,
},
callback: (r) => {
if (!r.message) return;
let message = hide ? "{0} is hidden successfully" : "{0} is unhidden successfully";
message = __(message, [page.title.bold()]);
frappe.show_alert({ message: message, indicator: "green" });
},
});
}
hide_workspace(page, event) {
this.hide_unhide_workspace(page, event, 1);
}
unhide_workspace(page, event) {
this.hide_unhide_workspace(page, event, 0);
}
make_sidebar_sortable() {
let me = this;
$(".nested-container").each(function () {

View file

@ -1017,7 +1017,29 @@ body {
.sidebar-item-container {
position: relative;
.sidebar-item-container{
&[item-is-hidden="1"] {
display: none;
opacity: 0.4;
&:hover {
opacity: 1;
}
}
&[item-is-hidden="0"] {
.drop-icon {
display: none;
}
&:has(.sidebar-item-container[item-is-hidden="0"]) {
.drop-icon {
display: inline-block;
}
}
}
.sidebar-item-container {
margin-left: 10px;
.standard-sidebar-item {
@ -1026,31 +1048,64 @@ body {
}
}
.standard-sidebar-section.show-control {
.desk-sidebar-item.standard-sidebar-item {
.desk-sidebar {
&.show-hidden-workspaces {
&:hover, &.selected {
.drag-handle {
display: inline-block;
}
.setting-btn, .duplicate-page {
display: inline-block;
margin-right: 8px;
}
.drop-icon {
padding: 10px 8px 10px 2px;
margin-left: -8px;
}
.unhide-workspace-btn {
display: none;
}
.block-click {
pointer-events:none;
.standard-sidebar-section {
display: block;
.sidebar-item-container {
&[item-is-hidden="1"] {
display: block;
}
&[item-is-hidden="0"] {
.drop-icon {
display: inline-block;
}
}
}
}
}
.standard-sidebar-section {
display: none;
&:has(> [item-is-hidden="0"]) {
display: block;
}
}
.standard-sidebar-section.show-control {
.desk-sidebar-item.standard-sidebar-item {
&:hover, &.selected {
.drag-handle {
display: inline-block;
}
.setting-btn, .duplicate-page, .unhide-workspace-btn {
display: inline-block;
margin-right: 8px;
}
.drop-icon {
padding: 10px 8px 10px 2px;
margin-left: -8px;
}
}
.block-click {
pointer-events:none;
}
}
}
}
.codex-editor__loader {
display: none !important;
}

View file

@ -495,7 +495,7 @@ def setup_image_doctype():
@whitelist_for_tests
def setup_inbox():
frappe.db.sql("DELETE FROM `tabUser Email`")
frappe.db.delete("User Email")
user = frappe.get_doc("User", frappe.session.user)
user.append("user_emails", {"email_account": "Email Linking"})

View file

@ -2,6 +2,7 @@
import logging
import os
from logging.handlers import RotatingFileHandler
from typing import Literal
# imports - module imports
import frappe
@ -95,7 +96,7 @@ class SiteContextFilter(logging.Filter):
return True
def set_log_level(level: int) -> None:
def set_log_level(level: Literal["ERROR", "WARNING", "WARN", "INFO", "DEBUG"]) -> None:
"""Use this method to set log level to something other than the default DEBUG"""
frappe.log_level = getattr(logging, (level or "").upper(), None) or default_log_level
frappe.loggers = {}

View file

@ -118,10 +118,10 @@ def get_rendered_template(
validate_print_permission(doc)
if doc.meta.is_submittable:
if doc.docstatus == 0 and not cint(print_settings.allow_print_for_draft):
if doc.docstatus.is_draft() and not cint(print_settings.allow_print_for_draft):
frappe.throw(_("Not allowed to print draft documents"), frappe.PermissionError)
if doc.docstatus == 2 and not cint(print_settings.allow_print_for_cancelled):
if doc.docstatus.is_cancelled() and not cint(print_settings.allow_print_for_cancelled):
frappe.throw(_("Not allowed to print cancelled documents"), frappe.PermissionError)
doc.run_method("before_print", print_settings)

View file

@ -15,18 +15,19 @@ def get_context(context):
host = get_request_site_address()
blog_list = frappe.db.sql(
"""\
select route as name, published_on, modified, title, content from `tabBlog Post`
where ifnull(published,0)=1
order by published_on desc limit 20""",
as_dict=1,
blog_list = frappe.get_all(
"Blog Post",
fields=["name", "published_on", "modified", "title", "content"],
filters={"published": 1},
order_by="published_on desc",
limit=20,
)
for blog in blog_list:
blog_page = cstr(quote(blog.name.encode("utf-8")))
blog.link = urljoin(host, blog_page)
blog.content = escape_html(blog.content or "")
blog.title = escape_html(blog.title or "")
if blog_list:
modified = max(blog["modified"] for blog in blog_list)