Merge branch 'develop' into gc-perms

This commit is contained in:
Raffael Meyer 2023-04-03 12:23:15 +02:00 committed by GitHub
commit d13ac4b17c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 150 additions and 92 deletions

View file

@ -98,15 +98,17 @@ context("Kanban Board", () => {
});
it("Checks if Kanban Board edits are blocked for non-System Manager and non-owner of the Board", () => {
// create admin kanban board
cy.call("frappe.tests.ui_test_helpers.create_todo", { description: "Frappe User ToDo" });
cy.switch_to_user("Administrator");
cy.call("frappe.tests.ui_test_helpers.create_admin_kanban");
// remove sys manager
cy.remove_role("frappe@example.com", "System Manager");
cy.switch_to_user("frappe@example.com");
const noSystemManager = "nosysmanager@example.com";
cy.call("frappe.tests.ui_test_helpers.create_test_user", {
username: noSystemManager,
});
cy.remove_role(noSystemManager, "System Manager");
cy.call("frappe.tests.ui_test_helpers.create_todo", { description: "Frappe User ToDo" });
cy.call("frappe.tests.ui_test_helpers.create_admin_kanban");
cy.switch_to_user(noSystemManager);
cy.visit("/app/todo/view/kanban/Admin Kanban");
@ -122,7 +124,8 @@ context("Kanban Board", () => {
// Column actions should be hidden (dropdown for 'Archive' and indicators)
cy.get(".kanban .column-options").should("have.length", 0);
cy.add_role("frappe@example.com", "System Manager");
cy.switch_to_user("Administrator");
cy.call("frappe.client.delete", { doctype: "User", name: noSystemManager });
});
after(() => {

View file

@ -1274,7 +1274,7 @@ def reload_doc(
return frappe.modules.reload_doc(module, dt, dn, force=force, reset_permissions=reset_permissions)
@whitelist()
@whitelist(methods=["POST", "PUT"])
def rename_doc(
doctype: str,
old: str,

View file

@ -254,19 +254,23 @@ def address_query(doctype, txt, searchfield, start, page_len, filters):
"""select
`tabAddress`.name, `tabAddress`.city, `tabAddress`.country
from
`tabAddress`, `tabDynamic Link`
`tabAddress`
join `tabDynamic Link`
on (`tabDynamic Link`.parent = `tabAddress`.name and `tabDynamic Link`.parenttype = 'Address')
where
`tabDynamic Link`.parent = `tabAddress`.name and
`tabDynamic Link`.parenttype = 'Address' and
`tabDynamic Link`.link_doctype = %(link_doctype)s and
`tabDynamic Link`.link_name = %(link_name)s and
ifnull(`tabAddress`.disabled, 0) = 0 and
({search_condition})
{mcond} {condition}
order by
if(locate(%(_txt)s, `tabAddress`.name), locate(%(_txt)s, `tabAddress`.name), 99999),
case
when locate(%(_txt)s, `tabAddress`.name) != 0
then locate(%(_txt)s, `tabAddress`.name)
else 99999
end,
`tabAddress`.idx desc, `tabAddress`.name
limit %(start)s, %(page_len)s """.format(
limit %(page_len)s offset %(start)s""".format(
mcond=get_match_cond(doctype),
search_condition=search_condition,
condition=condition or "",

View file

@ -1,7 +1,9 @@
# Copyright (c) 2015, Frappe Technologies and Contributors
# License: MIT. See LICENSE
from functools import partial
import frappe
from frappe.contacts.doctype.address.address import get_address_display
from frappe.contacts.doctype.address.address import address_query, get_address_display
from frappe.tests.utils import FrappeTestCase
@ -28,3 +30,29 @@ class TestAddress(FrappeTestCase):
address = frappe.get_list("Address")[0].name
display = get_address_display(frappe.get_doc("Address", address).as_dict())
self.assertTrue(display)
def test_address_query(self):
def query(doctype="Address", txt="", searchfield="name", start=0, page_len=20, filters=None):
if filters is None:
filters = {"link_doctype": "User", "link_name": "Administrator"}
return address_query(doctype, txt, searchfield, start, page_len, filters)
frappe.get_doc(
{
"address_type": "Billing",
"address_line1": "1",
"city": "Mumbai",
"state": "Maharashtra",
"country": "India",
"doctype": "Address",
"links": [
{
"link_doctype": "User",
"link_name": "Administrator",
}
],
}
).insert()
self.assertGreaterEqual(len(query(txt="Admin")), 1)
self.assertEqual(len(query(txt="what_zyx")), 0)

View file

@ -165,7 +165,7 @@ def _make(
if not comm.get_outgoing_email_account():
frappe.throw(
_(
"Unable to send mail because of a missing email account. Please setup default Email Account from Setup > Email > Email Account"
"Unable to send mail because of a missing email account. Please setup default Email Account from Settings > Email Account"
),
exc=frappe.OutgoingEmailError,
)

View file

@ -70,7 +70,7 @@ class CommunicationEmailMixin:
if include_sender:
cc.append(self.sender_mailid)
if is_inbound_mail_communcation:
if (doc_owner := self.get_owner()) not in frappe.STANDARD_USERS:
if (doc_owner := self.get_owner()) and (doc_owner not in frappe.STANDARD_USERS):
cc.append(doc_owner)
cc = set(cc) - {self.sender_mailid}
cc.update(self.get_assignees())
@ -216,7 +216,11 @@ class CommunicationEmailMixin:
"reference_name": self.reference_name,
"reference_type": self.reference_doctype,
}
return ToDo.get_owners(filters)
if self.reference_doctype and self.reference_name:
return ToDo.get_owners(filters)
else:
return []
@staticmethod
def filter_thread_notification_disbled_users(emails):

View file

@ -169,7 +169,6 @@ class ServerScript(Document):
return items
@frappe.whitelist()
def setup_scheduler_events(script_name, frequency):
"""Creates or Updates Scheduled Job Type documents based on the specified script name and frequency

View file

@ -219,7 +219,10 @@ frappe.ui.form.on("User", {
});
}
if (frappe.session.user == doc.name || frappe.user.has_role("System Manager")) {
if (
cint(frappe.boot.sysdefaults.enable_two_factor_auth) &&
(frappe.session.user == doc.name || frappe.user.has_role("System Manager"))
) {
frm.add_custom_button(
__("Reset OTP Secret"),
function () {

View file

@ -16,38 +16,27 @@ frappe.ui.form.on("Bulk Update", {
if (!frm.doc.update_value) {
frappe.throw(__('Field "value" is mandatory. Please specify value to be updated'));
} else {
frappe
.call({
method: "frappe.desk.doctype.bulk_update.bulk_update.update",
args: {
doctype: frm.doc.document_type,
field: frm.doc.field,
value: frm.doc.update_value,
condition: frm.doc.condition,
limit: frm.doc.limit,
},
})
.then((r) => {
let failed = r.message;
if (!failed) failed = [];
frm.call("bulk_update").then((r) => {
let failed = r.message;
if (!failed) failed = [];
if (failed.length && !r._server_messages) {
frappe.throw(
__("Cannot update {0}", [
failed.map((f) => (f.bold ? f.bold() : f)).join(", "),
])
);
} else {
frappe.msgprint({
title: __("Success"),
message: __("Updated Successfully"),
indicator: "green",
});
}
if (failed.length && !r._server_messages) {
frappe.throw(
__("Cannot update {0}", [
failed.map((f) => (f.bold ? f.bold() : f)).join(", "),
])
);
} else {
frappe.msgprint({
title: __("Success"),
message: __("Updated Successfully"),
indicator: "green",
});
}
frappe.hide_progress();
frm.save();
});
frappe.hide_progress();
frm.save();
});
}
});
},

View file

@ -10,26 +10,24 @@ from frappe.utils.scheduler import is_scheduler_inactive
class BulkUpdate(Document):
pass
@frappe.whitelist()
def bulk_update(self):
self.check_permission("write")
limit = self.limit if self.limit and cint(self.limit) < 500 else 500
condition = ""
if self.condition:
if ";" in self.condition:
frappe.throw(_("; not allowed in condition"))
@frappe.whitelist()
def update(doctype, field, value, condition="", limit=500):
if not limit or cint(limit) > 500:
limit = 500
condition = f" where {self.condition}"
if condition:
condition = " where " + condition
if ";" in condition:
frappe.throw(_("; not allowed in condition"))
docnames = frappe.db.sql_list(
f"""select name from `tab{doctype}`{condition} limit {limit} offset 0"""
)
data = {}
data[field] = value
return submit_cancel_or_update_docs(doctype, docnames, "update", data)
docnames = frappe.db.sql_list(
f"""select name from `tab{self.document_type}`{condition} limit {limit} offset 0"""
)
return submit_cancel_or_update_docs(
self.document_type, docnames, "update", {self.field: self.update_value}
)
@frappe.whitelist()

View file

@ -325,7 +325,7 @@ class EmailAccount(Document):
if _raise_error:
frappe.throw(
_("Please setup default Email Account from Setup > Email > Email Account"),
_("Please setup default Email Account from Settings > Email Account"),
frappe.OutgoingEmailError,
)

View file

@ -69,9 +69,7 @@ class SMTPServer:
if not self.server:
frappe.msgprint(
_(
"Email Account not setup. Please create a new Email Account from Setup > Email > Email Account"
),
_("Email Account not setup. Please create a new Email Account from Settings > Email Account"),
raise_exception=frappe.OutgoingEmailError,
)

View file

@ -27,6 +27,7 @@ frappe.ui.form.ControlBarcode = class ControlBarcode extends frappe.ui.form.Cont
let svg = value;
let barcode_value = "";
this.set_empty_description();
if (value && value.startsWith("<svg")) {
barcode_value = $(svg).attr("data-barcode-value");
}
@ -44,10 +45,14 @@ frappe.ui.form.ControlBarcode = class ControlBarcode extends frappe.ui.form.Cont
if (value) {
// Get svg
const svg = this.barcode_area.find("svg")[0];
JsBarcode(svg, value, this.get_options(value));
$(svg).attr("data-barcode-value", value);
$(svg).attr("width", "100%");
return this.barcode_area.html();
try {
JsBarcode(svg, value, this.get_options(value));
$(svg).attr("data-barcode-value", value);
$(svg).attr("width", "100%");
return this.barcode_area.html();
} catch (e) {
this.set_description(`Invalid Barcode: ${String(e)}`);
}
}
}

View file

@ -208,7 +208,6 @@ body {
// Overrides for each widgets
&.dashboard-widget-box {
min-height: 240px;
padding: var(--padding-md) var(--padding-lg);
.filter-chart {
background-color: var(--control-bg);
@ -238,13 +237,16 @@ body {
}
.widget-head {
padding: var(--padding-sm);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 6px;
}
.widget-body {
padding-top: 7px;
}
.widget-control {
display: flex;
align-items: center;
@ -560,7 +562,7 @@ body {
}
&.links-widget-box {
padding: 18px 12px;
padding: 12px 7px;
.link-item {
display: flex;

View file

@ -8,6 +8,7 @@ import unittest
from io import StringIO
from unittest.mock import patch
import git
import yaml
import frappe
@ -134,6 +135,9 @@ class TestBoilerPlate(unittest.TestCase):
self.check_parsable_python_files(new_app_dir)
app_repo = git.Repo(new_app_dir)
self.assertEqual(app_repo.active_branch.name, "develop")
def test_create_app_without_git_init(self):
app_name = "test_app_no_git"

View file

@ -426,12 +426,15 @@ def create_blog_post():
return doc
def create_test_user():
if frappe.db.exists("User", UI_TEST_USER):
@whitelist_for_tests
def create_test_user(username=None):
name = username or UI_TEST_USER
if frappe.db.exists("User", name):
return
user = frappe.new_doc("User")
user.email = UI_TEST_USER
user.email = name
user.first_name = "Frappe"
user.new_password = frappe.local.conf.admin_password
user.send_welcome_email = 0

View file

@ -52,6 +52,7 @@ Content,Inhalt,
Content Type,Inhaltstyp,
Create,Erstellen,
Created By,Erstellt von,
Crop,Zuschneiden,
Current,Laufend,
Custom HTML,Benutzerdefiniertes HTML,
Custom?,Benutzerdefiniert?,
@ -64,6 +65,7 @@ Delivery Status,Lieferstatus,
Department,Abteilung,
Details,Details,
Document Name,Dokumentenname,
Document Naming Settings,Dokumentenbenennungseinstellungen,
Document Status,Dokumentenstatus,
Document Type,Dokumententyp,
Domain,Domäne,
@ -1383,6 +1385,7 @@ Inverse,Invertieren,
Is,Ist,
Is Attachments Folder,Ist Ordner für Anhänge,
Is Child Table,Ist Untertabelle,
Is Custom,Ist benutzerdefiniert,
Is Custom Field,Ist benutzerdefiniertes Feld,
Is First Startup,Ist Erstes Startup,
Is Folder,Ist Ordner,
@ -1613,6 +1616,7 @@ Naming,Bezeichnung,
Naming Rule, Benennungsregel,
"Naming Options:\n<ol><li><b>field:[fieldname]</b> - By Field</li><li><b>naming_series:</b> - By Naming Series (field called naming_series must be present</li><li><b>Prompt</b> - Prompt user for a name</li><li><b>[series]</b> - Series by prefix (separated by a dot); for example PRE.#####</li>\n<li><b>format:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####}</b> - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used.</li></ol>","Namensoptionen: <ol><li> <b>Feld: [Feldname]</b> - Nach Feld </li><li> <b>naming_series:</b> - Nach der <b>Namensreihe</b> (das Feld naming_series muss vorhanden sein) </li><li> <b>Eingabeaufforderung</b> - Benutzer nach einem Namen fragen </li><li> <b>[Serie]</b> - Reihe nach Präfix (getrennt durch einen Punkt); zum Beispiel PRE. ##### </li><li> <b>Format: BEISPIEL- {MM} morewords {Feldname1} - {Feldname2} - {#####}</b> - Ersetzt alle verspannten Wörter (Feldnamen, Datumsworte (DD, MM, YY), Serien) durch ihren Wert. Außerhalb von Klammern können beliebige Zeichen verwendet werden. </li></ol>",
Naming Series mandatory,Nummernkreis zwingend erforderlich,
Navigation Settings,Navigationseinstellungen,
Nested set error. Please contact the Administrator.,Schachtelfehler. Bitte den Administrator kontaktieren.,
New Activity,Neue Aktivität,
New Chat,Neuer Chat,
@ -1720,11 +1724,11 @@ Note: Changing the Page Name will break previous URL to this page.,"Hinweis: Wen
Note: Multiple sessions will be allowed in case of mobile device,Hinweis: Mehrere Sitzungen wird im Falle einer mobilen Gerät erlaubt sein,
Nothing to show,Nichts anzuzeigen,
Nothing to update,Nichts zu aktualisieren,
Notification,Mitteilung,
Notification,Benachrichtigung,
Notification Recipient,Benachrichtigungsempfänger,
Notification Tones,Benachrichtigungstöne,
Notifications,Benachrichtigungen,
Notifications and bulk mails will be sent from this outgoing server.,Hinweise und Massen-E-Mails werden von diesem Postausgangsserver versendet.,
Notifications and bulk mails will be sent from this outgoing server.,Benachrichtigungen und Massen-E-Mails werden von diesem Postausgangsserver versendet.,
Notify Users On Every Login,Benutzer bei jeder Anmeldung benachrichtigen,
Notify if unreplied,"Benachrichtigen, wenn unbeantwortet",
Notify if unreplied for (in mins),"Benachrichtigen, wenn unbeantwortet für (in Minuten)",
@ -2323,6 +2327,7 @@ Show more details,Weiteres,
Show only errors,Zeige nur Fehler,
"Show title in browser window as ""Prefix - title""","Diesen Eintrag im Browser-Fenster als ""Präfix - Titel"" anzeigen",
Showing only Numeric fields from Report,Nur numerische Felder aus Bericht anzeigen,
Sidebar,Seitenleiste,
Sidebar Items,Elemente der Seitenleiste,
Sidebar Settings,Sidebar-Einstellungen,
Sidebar and Comments,Sidebar und Kommentare,

1 A4 A4
52 Content Type Inhaltstyp
53 Create Erstellen
54 Created By Erstellt von
55 Crop Zuschneiden
56 Current Laufend
57 Custom HTML Benutzerdefiniertes HTML
58 Custom? Benutzerdefiniert?
65 Department Abteilung
66 Details Details
67 Document Name Dokumentenname
68 Document Naming Settings Dokumentenbenennungseinstellungen
69 Document Status Dokumentenstatus
70 Document Type Dokumententyp
71 Domain Domäne
1385 Is Ist
1386 Is Attachments Folder Ist Ordner für Anhänge
1387 Is Child Table Ist Untertabelle
1388 Is Custom Ist benutzerdefiniert
1389 Is Custom Field Ist benutzerdefiniertes Feld
1390 Is First Startup Ist Erstes Startup
1391 Is Folder Ist Ordner
1616 Naming Rule Benennungsregel
1617 Naming Options:\n<ol><li><b>field:[fieldname]</b> - By Field</li><li><b>naming_series:</b> - By Naming Series (field called naming_series must be present</li><li><b>Prompt</b> - Prompt user for a name</li><li><b>[series]</b> - Series by prefix (separated by a dot); for example PRE.#####</li>\n<li><b>format:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####}</b> - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used.</li></ol> Namensoptionen: <ol><li> <b>Feld: [Feldname]</b> - Nach Feld </li><li> <b>naming_series:</b> - Nach der <b>Namensreihe</b> (das Feld naming_series muss vorhanden sein) </li><li> <b>Eingabeaufforderung</b> - Benutzer nach einem Namen fragen </li><li> <b>[Serie]</b> - Reihe nach Präfix (getrennt durch einen Punkt); zum Beispiel PRE. ##### </li><li> <b>Format: BEISPIEL- {MM} morewords {Feldname1} - {Feldname2} - {#####}</b> - Ersetzt alle verspannten Wörter (Feldnamen, Datumsworte (DD, MM, YY), Serien) durch ihren Wert. Außerhalb von Klammern können beliebige Zeichen verwendet werden. </li></ol>
1618 Naming Series mandatory Nummernkreis zwingend erforderlich
1619 Navigation Settings Navigationseinstellungen
1620 Nested set error. Please contact the Administrator. Schachtelfehler. Bitte den Administrator kontaktieren.
1621 New Activity Neue Aktivität
1622 New Chat Neuer Chat
1724 Note: Multiple sessions will be allowed in case of mobile device Hinweis: Mehrere Sitzungen wird im Falle einer mobilen Gerät erlaubt sein
1725 Nothing to show Nichts anzuzeigen
1726 Nothing to update Nichts zu aktualisieren
1727 Notification Mitteilung Benachrichtigung
1728 Notification Recipient Benachrichtigungsempfänger
1729 Notification Tones Benachrichtigungstöne
1730 Notifications Benachrichtigungen
1731 Notifications and bulk mails will be sent from this outgoing server. Hinweise und Massen-E-Mails werden von diesem Postausgangsserver versendet. Benachrichtigungen und Massen-E-Mails werden von diesem Postausgangsserver versendet.
1732 Notify Users On Every Login Benutzer bei jeder Anmeldung benachrichtigen
1733 Notify if unreplied Benachrichtigen, wenn unbeantwortet
1734 Notify if unreplied for (in mins) Benachrichtigen, wenn unbeantwortet für (in Minuten)
2327 Show only errors Zeige nur Fehler
2328 Show title in browser window as "Prefix - title" Diesen Eintrag im Browser-Fenster als "Präfix - Titel" anzeigen
2329 Showing only Numeric fields from Report Nur numerische Felder aus Bericht anzeigen
2330 Sidebar Seitenleiste
2331 Sidebar Items Elemente der Seitenleiste
2332 Sidebar Settings Sidebar-Einstellungen
2333 Sidebar and Comments Sidebar und Kommentare

View file

@ -450,12 +450,20 @@ def disable():
@frappe.whitelist()
def reset_otp_secret(user):
def reset_otp_secret(user: str):
if frappe.session.user != user:
frappe.only_for("System Manager", message=True)
otp_issuer = frappe.db.get_single_value("System Settings", "otp_issuer_name")
user_email = frappe.db.get_value("User", user, "email")
settings = frappe.get_cached_doc("System Settings")
if not settings.enable_two_factor_auth:
frappe.throw(
_("You have to enable Two Factor Auth from System Settings."),
title=_("Enable Two Factor Auth"),
)
otp_issuer = settings.otp_issuer_name or "Frappe Framework"
user_email = frappe.get_cached_value("User", user, "email")
clear_default(user + "_otplogin")
clear_default(user + "_otpsecret")
@ -463,10 +471,10 @@ def reset_otp_secret(user):
email_args = {
"recipients": user_email,
"sender": None,
"subject": _("OTP Secret Reset - {0}").format(otp_issuer or "Frappe Framework"),
"subject": _("OTP Secret Reset - {0}").format(otp_issuer),
"message": _(
"<p>Your OTP secret on {0} has been reset. If you did not perform this reset and did not request it, please contact your System Administrator immediately.</p>"
).format(otp_issuer or "Frappe Framework"),
).format(otp_issuer),
"delayed": False,
"retry": 3,
}

View file

@ -1031,8 +1031,13 @@ def groupby_metric(iterable: dict[str, list], key: str):
return records
def get_table_name(table_name: str) -> str:
return f"tab{table_name}" if not table_name.startswith("__") else table_name
def get_table_name(table_name: str, wrap_in_backticks: bool = False) -> str:
name = f"tab{table_name}" if not table_name.startswith("__") else table_name
if wrap_in_backticks:
return f"`{name}`"
return name
def squashify(what):

View file

@ -150,7 +150,7 @@ def _create_app_boilerplate(dest, hooks, no_git=False):
f.write(frappe.as_unicode(gitignore_template.format(app_name=hooks.app_name)))
# initialize git repository
app_repo = git.Repo.init(app_directory)
app_repo = git.Repo.init(app_directory, initial_branch="develop")
app_repo.git.add(A=True)
app_repo.index.commit("feat: Initialize App")

View file

@ -57,7 +57,7 @@ dependencies = [
"python-dateutil~=2.8.1",
"pytz==2022.1",
"rauth~=0.7.3",
"redis~=4.3.4",
"redis~=4.5.4",
"hiredis~=2.0.0",
"requests-oauthlib~=1.3.0",
"requests~=2.27.1",