diff --git a/doc/migration.rst b/doc/migration.rst
index c0f8f36d91..878436f27b 100644
--- a/doc/migration.rst
+++ b/doc/migration.rst
@@ -24,6 +24,14 @@ a database from another series.
Before
~~~~~~
+* Remove hard-coded admin user and add root user:
+
+ .. code-block:: SQL
+
+ DELETE FROM "ir_model_data" WHERE module = 'res' AND fs_id = 'user_admin';
+ INSERT INTO ir_model_data (fs_id, model, module, db_id, noupdate, field_names) VALUES
+ ('user_root', 'res.user', 'res', 0, False, '["login", "name", "active", "administrator"]');
+
* If the ``trytond-worker`` is configured, stop it and finish all dequeued
tasks:
@@ -36,6 +44,15 @@ Before
After
~~~~~
+* Set the administrator flag on the ``admin`` user:
+
+ .. code-block:: SQL
+
+ UPDATE "res_user" SET administrator = true WHERE login IN ('root', 'admin');
+
+ .. note::
+ Include in the clause all the users' login that are administrators.
+
* If ``account`` module is activated, update the reconciliation date using
maturity date:
diff --git a/modules/account/account.xml b/modules/account/account.xml
index db5b9f485c..ea5d390695 100644
--- a/modules/account/account.xml
+++ b/modules/account/account.xml
@@ -10,14 +10,6 @@ this repository contains the full copyright notices and license terms. -->
Accounting Administration
-
-
-
-
-
-
-
-
tryton-account
diff --git a/modules/account/move.xml b/modules/account/move.xml
index ef5cb87334..5b8ad0e879 100644
--- a/modules/account/move.xml
+++ b/modules/account/move.xml
@@ -75,11 +75,6 @@ this repository contains the full copyright notices and license terms. -->
Account Move
-
-
-
-
@@ -154,11 +149,6 @@ this repository contains the full copyright notices and license terms. -->
Account Move Reconciliation
-
-
-
-
diff --git a/modules/account_asset/asset.xml b/modules/account_asset/asset.xml
index 3031eb465c..62b41767ce 100644
--- a/modules/account_asset/asset.xml
+++ b/modules/account_asset/asset.xml
@@ -80,11 +80,6 @@
Asset
-
-
-
-
diff --git a/modules/account_budget/account.xml b/modules/account_budget/account.xml
index 62818ddf90..96b23a1a5b 100644
--- a/modules/account_budget/account.xml
+++ b/modules/account_budget/account.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Account Budget
-
-
-
-
Account Credit Limit
-
-
-
-
account.configuration
diff --git a/modules/account_credit_limit/party.py b/modules/account_credit_limit/party.py
index 18118b0e77..e96238c58f 100644
--- a/modules/account_credit_limit/party.py
+++ b/modules/account_credit_limit/party.py
@@ -74,7 +74,8 @@ class Party(metaclass=PoolMeta):
if user_id == 0:
return True
user = User(user_id)
- return origin and group in user.groups
+ return origin and (group in user.groups
+ or User.is_administrator(user_id))
for model in self._credit_limit_to_lock():
Model = pool.get(model)
diff --git a/modules/account_dunning/dunning.xml b/modules/account_dunning/dunning.xml
index 640583fe42..218104116e 100644
--- a/modules/account_dunning/dunning.xml
+++ b/modules/account_dunning/dunning.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Dunning
-
-
-
-
Invoice
-
-
-
-
diff --git a/modules/account_payment/payment.xml b/modules/account_payment/payment.xml
index 666118f196..ea6b5b861a 100644
--- a/modules/account_payment/payment.xml
+++ b/modules/account_payment/payment.xml
@@ -11,19 +11,9 @@ this repository contains the full copyright notices and license terms. -->
Payment
-
-
-
-
-
Payment Approval
-
-
-
-
id="sequence_type_account_payment_group">
Account Payment Group
-
-
-
-
Account Payment Group
-
-
-
-
diff --git a/modules/account_payment_check/account.xml b/modules/account_payment_check/account.xml
index 3ea9cfdd67..84ceda4e35 100644
--- a/modules/account_payment_check/account.xml
+++ b/modules/account_payment_check/account.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Account Payment Check
-
-
-
-
diff --git a/modules/account_payment_sepa/payment.xml b/modules/account_payment_sepa/payment.xml
index 241bee8736..2d3606a2e5 100644
--- a/modules/account_payment_sepa/payment.xml
+++ b/modules/account_payment_sepa/payment.xml
@@ -160,11 +160,6 @@ this repository contains the full copyright notices and license terms. -->
SEPA Mandate
-
-
-
-
diff --git a/modules/account_product/account.xml b/modules/account_product/account.xml
index 2a6713748b..3b8c05fd77 100644
--- a/modules/account_product/account.xml
+++ b/modules/account_product/account.xml
@@ -7,10 +7,6 @@ this repository contains the full copyright notices and license terms. -->
Account Product Administration
-
-
-
-
account.create_chart.properties
diff --git a/modules/account_statement/statement.xml b/modules/account_statement/statement.xml
index 97d98a58d0..e3017b423b 100644
--- a/modules/account_statement/statement.xml
+++ b/modules/account_statement/statement.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Statement
-
-
-
-
Landed Cost
-
-
-
-
diff --git a/modules/account_stock_shipment_cost/account.xml b/modules/account_stock_shipment_cost/account.xml
index 7675a8bffc..ad55df0a20 100644
--- a/modules/account_stock_shipment_cost/account.xml
+++ b/modules/account_stock_shipment_cost/account.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Shipment Cost
-
-
-
-
diff --git a/modules/analytic_account/analytic_account.xml b/modules/analytic_account/analytic_account.xml
index 10dae77515..097c3e646a 100644
--- a/modules/analytic_account/analytic_account.xml
+++ b/modules/analytic_account/analytic_account.xml
@@ -6,10 +6,5 @@ this repository contains the full copyright notices and license terms. -->
Analytic Administration
-
-
-
-
diff --git a/modules/attendance/attendance.xml b/modules/attendance/attendance.xml
index 378daa6c68..64f3dc1883 100644
--- a/modules/attendance/attendance.xml
+++ b/modules/attendance/attendance.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Attendance Administration
-
-
-
-
tryton-attendance
diff --git a/modules/bank/bank.xml b/modules/bank/bank.xml
index ddd40e9f62..d9f090216e 100644
--- a/modules/bank/bank.xml
+++ b/modules/bank/bank.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Bank Administration
-
-
-
-
tryton-bank
diff --git a/modules/carrier/carrier.xml b/modules/carrier/carrier.xml
index 9491ed8311..aa6b595fce 100644
--- a/modules/carrier/carrier.xml
+++ b/modules/carrier/carrier.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Carrier Administration
-
-
-
-
tryton-carrier
diff --git a/modules/commission/commission.xml b/modules/commission/commission.xml
index a930a95c39..8cd5f86547 100644
--- a/modules/commission/commission.xml
+++ b/modules/commission/commission.xml
@@ -10,14 +10,6 @@ this repository contains the full copyright notices and license terms. -->
Commission Administration
-
-
-
-
-
-
-
-
tryton-commission
diff --git a/modules/company/company.xml b/modules/company/company.xml
index aeb9d3f513..e19d76657f 100644
--- a/modules/company/company.xml
+++ b/modules/company/company.xml
@@ -6,18 +6,9 @@ this repository contains the full copyright notices and license terms. -->
Company Administration
-
-
-
-
-
Employee Administration
-
-
-
-
tryton-company
diff --git a/modules/country/country.xml b/modules/country/country.xml
index 95246ff839..704a6c9b6a 100644
--- a/modules/country/country.xml
+++ b/modules/country/country.xml
@@ -14,11 +14,6 @@ this repository contains the full copyright notices and license terms. -->
sequence="30"
id="menu_country"
icon="tryton-country"/>
-
country.organization
@@ -59,14 +54,6 @@ this repository contains the full copyright notices and license terms. -->
-
- country.organization
-
-
-
-
-
-
country.organization.member
@@ -149,14 +136,6 @@ this repository contains the full copyright notices and license terms. -->
-
- country.region
-
-
-
-
-
-
country.country
@@ -209,14 +188,6 @@ this repository contains the full copyright notices and license terms. -->
-
- country.country
-
-
-
-
-
-
country.subdivision
@@ -236,14 +207,6 @@ this repository contains the full copyright notices and license terms. -->
-
- country.subdivision
-
-
-
-
-
-
country.postal_code
@@ -285,13 +248,5 @@ this repository contains the full copyright notices and license terms. -->
-
- country.postal_code
-
-
-
-
-
-
diff --git a/modules/currency/currency.xml b/modules/currency/currency.xml
index 4ca83eef83..dbee28ea8c 100644
--- a/modules/currency/currency.xml
+++ b/modules/currency/currency.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Currency Administration
-
-
-
-
tryton-currency
diff --git a/modules/customs/customs.xml b/modules/customs/customs.xml
index 249860813d..dbdd9edd16 100644
--- a/modules/customs/customs.xml
+++ b/modules/customs/customs.xml
@@ -6,11 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Customs Administration
-
-
-
-
tryton-customs
diff --git a/modules/document_incoming/document.xml b/modules/document_incoming/document.xml
index c804095ded..2e187a0c6d 100644
--- a/modules/document_incoming/document.xml
+++ b/modules/document_incoming/document.xml
@@ -6,19 +6,10 @@ this repository contains the full copyright notices and license terms. -->
Document Incoming
-
-
-
-
-
Document Incoming Administrator
-
-
-
-
tryton-document-incoming
diff --git a/modules/edocument_peppol/edocument.xml b/modules/edocument_peppol/edocument.xml
index 117a7d8e23..5050d4b1dd 100644
--- a/modules/edocument_peppol/edocument.xml
+++ b/modules/edocument_peppol/edocument.xml
@@ -8,11 +8,6 @@ this repository contains the full copyright notices and license terms. -->
parent="ir.menu_administration"
sequence="20"
id="menu_peppol"/>
-
edocument.peppol
@@ -80,14 +75,6 @@ this repository contains the full copyright notices and license terms. -->
-
- edocument.peppol
-
-
-
-
-
-
edocument.peppol
@@ -95,13 +82,6 @@ this repository contains the full copyright notices and license terms. -->
-
- edocument.peppol
- data
-
-
-
-
User in companies
@@ -142,10 +122,6 @@ this repository contains the full copyright notices and license terms. -->
retry
Retry
-
-
-
-
@@ -156,10 +132,6 @@ this repository contains the full copyright notices and license terms. -->
cancel
Cancel
-
-
-
-
@@ -205,14 +177,6 @@ this repository contains the full copyright notices and license terms. -->
-
- edocument.peppol.service
-
-
-
-
-
-
User in companies
diff --git a/modules/inbound_email/inbound_email.xml b/modules/inbound_email/inbound_email.xml
index cbb3e2fddf..bdea843aaf 100644
--- a/modules/inbound_email/inbound_email.xml
+++ b/modules/inbound_email/inbound_email.xml
@@ -55,14 +55,6 @@ this repository contains the full copyright notices and license terms. -->
-
- inbound.email.inbox
-
-
-
-
-
-
inbound.email
@@ -104,14 +96,6 @@ this repository contains the full copyright notices and license terms. -->
-
- inbound.email
-
-
-
-
-
-
inbound.email
diff --git a/modules/marketing/marketing.xml b/modules/marketing/marketing.xml
index ffcba07d48..2cadf41f50 100644
--- a/modules/marketing/marketing.xml
+++ b/modules/marketing/marketing.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Marketing
-
-
-
-
tryton-marketing
diff --git a/modules/notification_email/notification.xml b/modules/notification_email/notification.xml
index 9f27c5d6b3..580f66e42c 100644
--- a/modules/notification_email/notification.xml
+++ b/modules/notification_email/notification.xml
@@ -45,13 +45,5 @@ this repository contains the full copyright notices and license terms. -->
-
- notification.email
-
-
-
-
-
-
diff --git a/modules/party/party.xml b/modules/party/party.xml
index 2f7e978a34..a310051ecf 100644
--- a/modules/party/party.xml
+++ b/modules/party/party.xml
@@ -6,11 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Party Administration
-
-
-
-
tryton-party
@@ -93,11 +88,6 @@ this repository contains the full copyright notices and license terms. -->
Party
-
-
-
-
diff --git a/modules/product/category.xml b/modules/product/category.xml
index f7b0dcfe73..ea9470ecbc 100644
--- a/modules/product/category.xml
+++ b/modules/product/category.xml
@@ -83,10 +83,6 @@ this repository contains the full copyright notices and license terms. -->
Category
-
-
-
-
diff --git a/modules/product/product.xml b/modules/product/product.xml
index f0c79c7b9e..39a1ba80db 100644
--- a/modules/product/product.xml
+++ b/modules/product/product.xml
@@ -7,10 +7,6 @@ this repository contains the full copyright notices and license terms. -->
Product Administration
-
-
-
-
tryton-product
@@ -240,11 +236,6 @@ this repository contains the full copyright notices and license terms. -->
Variant
-
-
-
-
@@ -253,11 +244,6 @@ this repository contains the full copyright notices and license terms. -->
Product
-
-
-
-
diff --git a/modules/production/bom.xml b/modules/production/bom.xml
index 7313ab1c48..eead91980b 100644
--- a/modules/production/bom.xml
+++ b/modules/production/bom.xml
@@ -139,10 +139,6 @@ this repository contains the full copyright notices and license terms. -->
BOM
-
-
-
-
diff --git a/modules/production/production.xml b/modules/production/production.xml
index 01de6e3d61..0ae6d31663 100644
--- a/modules/production/production.xml
+++ b/modules/production/production.xml
@@ -6,21 +6,10 @@ this repository contains the full copyright notices and license terms. -->
Production
-
-
-
-
-
Production Administration
-
-
-
-
tryton-production
@@ -172,11 +161,6 @@ this repository contains the full copyright notices and license terms. -->
Production
-
-
-
-
diff --git a/modules/project/project.xml b/modules/project/project.xml
index f0d2cf2643..1722dd5771 100644
--- a/modules/project/project.xml
+++ b/modules/project/project.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Project Administration
-
-
-
-
tryton-project
diff --git a/modules/project/work.xml b/modules/project/work.xml
index 4667ad12fd..f3e686bfd4 100644
--- a/modules/project/work.xml
+++ b/modules/project/work.xml
@@ -44,10 +44,6 @@ this repository contains the full copyright notices and license terms. -->
Project Work Effort
-
-
-
-
diff --git a/modules/project_invoice/project.xml b/modules/project_invoice/project.xml
index 88a1880cad..4ee8a20fe5 100644
--- a/modules/project_invoice/project.xml
+++ b/modules/project_invoice/project.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Project Invoice
-
-
-
-
project.work
diff --git a/modules/purchase/purchase.xml b/modules/purchase/purchase.xml
index 3d620e8c85..5738ac442c 100644
--- a/modules/purchase/purchase.xml
+++ b/modules/purchase/purchase.xml
@@ -10,14 +10,6 @@ this repository contains the full copyright notices and license terms. -->
Purchase Administrator
-
-
-
-
-
-
-
-
tryton-purchase
@@ -369,11 +361,6 @@ this repository contains the full copyright notices and license terms. -->
Purchase
-
-
-
-
diff --git a/modules/purchase_amendment/purchase.xml b/modules/purchase_amendment/purchase.xml
index eb53be59e4..fee65d9e96 100644
--- a/modules/purchase_amendment/purchase.xml
+++ b/modules/purchase_amendment/purchase.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Purchase Amendment
-
-
-
-
purchase.purchase
diff --git a/modules/purchase_blanket_agreement/purchase.xml b/modules/purchase_blanket_agreement/purchase.xml
index 60bb30ff7b..191e5007c0 100644
--- a/modules/purchase_blanket_agreement/purchase.xml
+++ b/modules/purchase_blanket_agreement/purchase.xml
@@ -7,10 +7,6 @@ this repository contains the full copyright notices and license terms. -->
Purchase Blanket Agreement
-
-
-
-
purchase.configuration
@@ -21,10 +17,6 @@ this repository contains the full copyright notices and license terms. -->
Purchase Blanket Agreement
-
-
-
-
diff --git a/modules/purchase_request/purchase_request.xml b/modules/purchase_request/purchase_request.xml
index 2c711890cd..0528f12905 100644
--- a/modules/purchase_request/purchase_request.xml
+++ b/modules/purchase_request/purchase_request.xml
@@ -8,12 +8,6 @@ this repository contains the full copyright notices and license terms. -->
Purchase Request
-
-
-
-
-
purchase.request
form
diff --git a/modules/purchase_request_quotation/purchase.xml b/modules/purchase_request_quotation/purchase.xml
index aeb47efa35..af9d802dca 100644
--- a/modules/purchase_request_quotation/purchase.xml
+++ b/modules/purchase_request_quotation/purchase.xml
@@ -206,12 +206,6 @@ this repository contains the full copyright notices and license terms. -->
id="sequence_type_purchase_request_quotation">
Purchase Request Quotation
-
-
-
-
Purchase Requisition Approval
-
-
-
-
Purchase Requisition
-
-
-
-
-
-
-
-
Quality
-
-
-
-
-
Quality Administration
-
-
-
-
tryton-quality
@@ -87,10 +78,6 @@ this repository contains the full copyright notices and license terms. -->
Quality Inspection
-
-
-
-
@@ -99,10 +86,6 @@ this repository contains the full copyright notices and license terms. -->
Quality Alert
-
-
-
-
diff --git a/modules/sale/sale.xml b/modules/sale/sale.xml
index 69667212cf..ffb405c02e 100644
--- a/modules/sale/sale.xml
+++ b/modules/sale/sale.xml
@@ -10,14 +10,6 @@ this repository contains the full copyright notices and license terms. -->
Sales Administrator
-
-
-
-
-
-
-
-
tryton-sale
@@ -382,11 +374,6 @@ this repository contains the full copyright notices and license terms. -->
Sale
-
-
-
-
diff --git a/modules/sale_amendment/sale.xml b/modules/sale_amendment/sale.xml
index 0c544c7cd9..7737d7a280 100644
--- a/modules/sale_amendment/sale.xml
+++ b/modules/sale_amendment/sale.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Sale Amendment
-
-
-
-
sale.sale
diff --git a/modules/sale_blanket_agreement/sale.xml b/modules/sale_blanket_agreement/sale.xml
index c1b31cb7f0..f90e424b5d 100644
--- a/modules/sale_blanket_agreement/sale.xml
+++ b/modules/sale_blanket_agreement/sale.xml
@@ -7,10 +7,6 @@ this repository contains the full copyright notices and license terms. -->
Sale Blanket Agreement
-
-
-
-
sale.configuration
@@ -21,10 +17,6 @@ this repository contains the full copyright notices and license terms. -->
Sale Blanket Agreement
-
-
-
-
diff --git a/modules/sale_complaint/sale.xml b/modules/sale_complaint/sale.xml
index 3ad0f35f0d..3d0d6adb99 100644
--- a/modules/sale_complaint/sale.xml
+++ b/modules/sale_complaint/sale.xml
@@ -17,11 +17,6 @@ this repository contains the full copyright notices and license terms. -->
Customer Complaint
-
-
-
-
diff --git a/modules/sale_gift_card/sale.xml b/modules/sale_gift_card/sale.xml
index 62788a2ec3..13c1985f70 100644
--- a/modules/sale_gift_card/sale.xml
+++ b/modules/sale_gift_card/sale.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Gift Card Administration
-
-
-
-
Gift Card
diff --git a/modules/sale_opportunity/opportunity.xml b/modules/sale_opportunity/opportunity.xml
index 44119d4065..8efb43171b 100644
--- a/modules/sale_opportunity/opportunity.xml
+++ b/modules/sale_opportunity/opportunity.xml
@@ -11,10 +11,6 @@ this repository contains the full copyright notices and license terms. -->
Sale Opportunity
-
-
-
-
sale.opportunity
@@ -31,11 +27,6 @@ this repository contains the full copyright notices and license terms. -->
Sale Opportunity
-
-
-
-
diff --git a/modules/sale_point/sale.xml b/modules/sale_point/sale.xml
index c3b4b79a02..08febeaf8b 100644
--- a/modules/sale_point/sale.xml
+++ b/modules/sale_point/sale.xml
@@ -6,32 +6,17 @@ this repository contains the full copyright notices and license terms. -->
POS
-
-
-
-
-
-
POS Posting
-
-
-
-
POS
-
-
-
-
diff --git a/modules/sale_rental/sale.xml b/modules/sale_rental/sale.xml
index 4ecbf54418..e50a2a9fc7 100644
--- a/modules/sale_rental/sale.xml
+++ b/modules/sale_rental/sale.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Rental
-
-
-
-
diff --git a/modules/sale_stock_quantity/sale.py b/modules/sale_stock_quantity/sale.py
index 125a0771cd..bd5b57cab8 100644
--- a/modules/sale_stock_quantity/sale.py
+++ b/modules/sale_stock_quantity/sale.py
@@ -84,7 +84,7 @@ class Sale(metaclass=PoolMeta):
if user_id == 0:
return True
user = User(user_id)
- return group in user.groups
+ return group in user.groups or User.is_administrator(user_id)
def filter_line(line):
return (line.warehouse
diff --git a/modules/sale_stock_quantity/sale.xml b/modules/sale_stock_quantity/sale.xml
index 1b2f556063..486810d2fc 100644
--- a/modules/sale_stock_quantity/sale.xml
+++ b/modules/sale_stock_quantity/sale.xml
@@ -6,10 +6,5 @@ this repository contains the full copyright notices and license terms. -->
Sale Stock Quantity
-
-
-
-
diff --git a/modules/sale_subscription/configuration.xml b/modules/sale_subscription/configuration.xml
index e02f1721e3..3582624bb5 100644
--- a/modules/sale_subscription/configuration.xml
+++ b/modules/sale_subscription/configuration.xml
@@ -12,11 +12,6 @@ this repository contains the full copyright notices and license terms. -->
Subscription
-
-
-
-
diff --git a/modules/sale_supply_drop_shipment/stock.xml b/modules/sale_supply_drop_shipment/stock.xml
index e44bd1cd8f..32b4e1ea67 100644
--- a/modules/sale_supply_drop_shipment/stock.xml
+++ b/modules/sale_supply_drop_shipment/stock.xml
@@ -17,11 +17,6 @@ this repository contains the full copyright notices and license terms. -->
Drop Shipment
-
-
-
-
diff --git a/modules/stock/inventory.xml b/modules/stock/inventory.xml
index b88865e26d..40dc194483 100644
--- a/modules/stock/inventory.xml
+++ b/modules/stock/inventory.xml
@@ -155,11 +155,6 @@ this repository contains the full copyright notices and license terms. -->
Inventory
-
-
-
-
diff --git a/modules/stock/shipment.xml b/modules/stock/shipment.xml
index db5944410e..32811a7fad 100644
--- a/modules/stock/shipment.xml
+++ b/modules/stock/shipment.xml
@@ -444,11 +444,6 @@ this repository contains the full copyright notices and license terms. -->
Customer Shipment
-
-
-
-
@@ -463,11 +458,6 @@ this repository contains the full copyright notices and license terms. -->
Supplier Shipment
-
-
-
-
@@ -482,11 +472,6 @@ this repository contains the full copyright notices and license terms. -->
Internal Shipment
-
-
-
-
@@ -501,11 +486,6 @@ this repository contains the full copyright notices and license terms. -->
Customer Return Shipment
-
-
-
-
@@ -521,11 +501,6 @@ this repository contains the full copyright notices and license terms. -->
Supplier Return Shipment
-
-
-
-
diff --git a/modules/stock/stock.xml b/modules/stock/stock.xml
index 66e15f736e..6c4f9b11d4 100644
--- a/modules/stock/stock.xml
+++ b/modules/stock/stock.xml
@@ -18,25 +18,6 @@ this repository contains the full copyright notices and license terms. -->
Stock Cancellation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
tryton-stock
diff --git a/modules/stock_forecast/forecast.xml b/modules/stock_forecast/forecast.xml
index fd0b44dcdc..165dce5546 100644
--- a/modules/stock_forecast/forecast.xml
+++ b/modules/stock_forecast/forecast.xml
@@ -6,11 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Stock Forecast
-
-
-
-
Complete Forecast
diff --git a/modules/stock_location_move/stock.xml b/modules/stock_location_move/stock.xml
index 1eb45a7d37..c8b10a22d0 100644
--- a/modules/stock_location_move/stock.xml
+++ b/modules/stock_location_move/stock.xml
@@ -6,11 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Stock Location Movable
-
-
-
-
stock.location
diff --git a/modules/stock_lot/ir.xml b/modules/stock_lot/ir.xml
index 91a87901a7..d71192223f 100644
--- a/modules/stock_lot/ir.xml
+++ b/modules/stock_lot/ir.xml
@@ -6,10 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Stock Lot
-
-
-
-
diff --git a/modules/stock_lot_sled/stock.xml b/modules/stock_lot_sled/stock.xml
index 7f563dea7e..bcd0389b0b 100644
--- a/modules/stock_lot_sled/stock.xml
+++ b/modules/stock_lot_sled/stock.xml
@@ -6,11 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Stock Force Expiration
-
-
-
-
stock.configuration
diff --git a/modules/stock_package/stock.xml b/modules/stock_package/stock.xml
index 4ee7633f5c..a123b37c37 100644
--- a/modules/stock_package/stock.xml
+++ b/modules/stock_package/stock.xml
@@ -6,11 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Stock Package
-
-
-
-
diff --git a/modules/timesheet/timesheet.xml b/modules/timesheet/timesheet.xml
index 9fc2b9f481..119e342039 100644
--- a/modules/timesheet/timesheet.xml
+++ b/modules/timesheet/timesheet.xml
@@ -6,11 +6,6 @@ this repository contains the full copyright notices and license terms. -->
Timesheet Administration
-
-
-
-
tryton-timesheet
diff --git a/modules/user_role/res.py b/modules/user_role/res.py
index 5de1576ff7..198064eef2 100644
--- a/modules/user_role/res.py
+++ b/modules/user_role/res.py
@@ -4,7 +4,7 @@ import datetime as dt
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool, PoolMeta
-from trytond.pyson import Bool, Eval, Id, If
+from trytond.pyson import Bool, Eval, If
class Role(ModelSQL, ModelView):
@@ -52,7 +52,7 @@ class User(metaclass=PoolMeta):
roles = fields.One2Many(
'res.user.role', 'user', "Roles",
states={
- 'invisible': Eval('id', -1) == Id('res', 'user_admin'),
+ 'invisible': Eval('administrator', False),
})
@classmethod
@@ -65,6 +65,15 @@ class User(metaclass=PoolMeta):
cls._context_fields.append('roles')
+ @fields.depends('administrator', 'roles')
+ def on_change_administrator(self):
+ try:
+ super().on_change_administrator()
+ except AttributeError:
+ pass
+ if self.administrator:
+ self.roles = []
+
@classmethod
def on_modification(cls, mode, users, field_names=None):
super().on_modification(mode, users, field_names=field_names)
@@ -73,18 +82,13 @@ class User(metaclass=PoolMeta):
@classmethod
def sync_roles(cls, users=None, date=None, clear=False):
- pool = Pool()
- ModelData = pool.get('ir.model.data')
-
- admin_id = ModelData.get_id('res', 'user_admin')
-
if date is None:
date = dt.datetime.now()
if users is None:
users = cls.search([])
to_write = []
for user in users:
- if user.id == admin_id:
+ if user.administrator:
continue
if not user.roles and not clear:
continue
@@ -106,9 +110,7 @@ class UserRole(ModelSQL, ModelView):
user = fields.Many2One(
'res.user', "User", ondelete='CASCADE', required=True,
domain=[
- If(Eval('user', -1) == Id('res', 'user_admin'),
- ('id', '=', -1),
- ()),
+ ('administrator', '=', False),
])
role = fields.Many2One(
'res.role', "Role", ondelete='CASCADE', required=True)
diff --git a/modules/user_role/res.xml b/modules/user_role/res.xml
index ac550fde30..31fa3404d9 100644
--- a/modules/user_role/res.xml
+++ b/modules/user_role/res.xml
@@ -42,14 +42,6 @@ this repository contains the full copyright notices and license terms. -->
-
- res.role
-
-
-
-
-
-
res.user
@@ -76,14 +68,6 @@ this repository contains the full copyright notices and license terms. -->
-
- res.user.role
-
-
-
-
-
-
diff --git a/modules/web_shop_product_data_feed/product.xml b/modules/web_shop_product_data_feed/product.xml
index 5d2ccd8300..f87c5db5f4 100644
--- a/modules/web_shop_product_data_feed/product.xml
+++ b/modules/web_shop_product_data_feed/product.xml
@@ -57,14 +57,6 @@ this repository contains the full copyright notices and license terms. -->
-
- product.category.google
-
-
-
-
-
-
product.category.facebook
@@ -114,13 +106,5 @@ this repository contains the full copyright notices and license terms. -->
-
- product.category.facebook
-
-
-
-
-
-
diff --git a/modules/web_shop_shopify/stock.xml b/modules/web_shop_shopify/stock.xml
index decc105a95..05cdb58e71 100644
--- a/modules/web_shop_shopify/stock.xml
+++ b/modules/web_shop_shopify/stock.xml
@@ -43,13 +43,5 @@ this repository contains the full copyright notices and license terms. -->
-
- stock.shipment.shopify_identifier
-
-
-
-
-
-
diff --git a/modules/web_shop_shopify/web.xml b/modules/web_shop_shopify/web.xml
index 6129bf892d..f12e0f04f0 100644
--- a/modules/web_shop_shopify/web.xml
+++ b/modules/web_shop_shopify/web.xml
@@ -63,15 +63,6 @@ this repository contains the full copyright notices and license terms. -->
-
- web.shop.shopify_identifier
-
-
-
-
-
-
-
web.shop.shopify_identifier
set_to_update
diff --git a/modules/web_shortener/web.xml b/modules/web_shortener/web.xml
index 1021b82c7c..f57aad61e1 100644
--- a/modules/web_shortener/web.xml
+++ b/modules/web_shortener/web.xml
@@ -41,14 +41,6 @@
-
- web.shortened_url
-
-
-
-
-
-
diff --git a/modules/web_user/user.xml b/modules/web_user/user.xml
index da31b2ebfc..5240e548f2 100644
--- a/modules/web_user/user.xml
+++ b/modules/web_user/user.xml
@@ -67,14 +67,6 @@ this repository contains the full copyright notices and license terms. -->
-
- web.user
-
-
-
-
-
-
web.user
diff --git a/proteus/proteus/tests/test_model.py b/proteus/proteus/tests/test_model.py
index 51ed0bc4cd..73c454df93 100644
--- a/proteus/proteus/tests/test_model.py
+++ b/proteus/proteus/tests/test_model.py
@@ -1,12 +1,31 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-from proteus import Model
+from proteus import Model, config
from .common import ProteusTestCase
class TestModel(ProteusTestCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ config.set_trytond()
+
+ Group = Model.get('res.group')
+ User = Model.get('res.user')
+ ModelAccess = Model.get('ir.model.access')
+
+ group = Group(name="Administration")
+ group.save()
+
+ admin, = User.find([('login', '=', 'admin')])
+ admin.groups.append(group)
+ admin.save()
+
+ access = ModelAccess(model='res.user', perm_read=True, group=group)
+ access.save()
+
def test_class_cache(self):
User1 = Model.get('res.user')
User2 = Model.get('res.user')
diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG
index 68d373cb69..c53738e0ac 100644
--- a/trytond/CHANGELOG
+++ b/trytond/CHANGELOG
@@ -1,3 +1,4 @@
+* Replace the administration group by a flag on the user
* Make DBTestCase public
* Add function to import XML files
* Add support for router in Pool
diff --git a/trytond/doc/topics/modules/index.rst b/trytond/doc/topics/modules/index.rst
index 17822135e5..7c41cdad3b 100644
--- a/trytond/doc/topics/modules/index.rst
+++ b/trytond/doc/topics/modules/index.rst
@@ -128,11 +128,6 @@ The following snippet gives a first idea of what an XML file looks:
Party Administration
-
-
-
-
= 0),
diff --git a/trytond/trytond/backend/sqlite/init.sql b/trytond/trytond/backend/sqlite/init.sql
index 4c35491250..fc5e900cad 100644
--- a/trytond/trytond/backend/sqlite/init.sql
+++ b/trytond/trytond/backend/sqlite/init.sql
@@ -35,12 +35,12 @@ CREATE TABLE ir_model_data (
model VARCHAR NOT NULL,
module VARCHAR NOT NULL,
db_id INTEGER,
- noupdate BOOLEAN
+ noupdate BOOLEAN,
+ field_names TEXT
);
-INSERT INTO ir_model_data (id, fs_id, model, module, db_id, noupdate) VALUES
- (1, 'user_admin', 'res.user', 'res', 1, False),
- (2, 'group_admin', 'res.group', 'res', 1, False);
+INSERT INTO ir_model_data (id, fs_id, model, module, db_id, noupdate, field_names) VALUES
+ (1, 'user_root', 'res.user', 'res', 0, False, '["login", "name", "active", "administrator"]');
CREATE TABLE ir_ui_view (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -89,21 +89,19 @@ CREATE TABLE res_user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR,
active BOOLEAN NOT NULL,
- login VARCHAR NOT NULL
+ login VARCHAR NOT NULL,
+ administrator BOOLEAN NOT NULL
);
CREATE UNIQUE INDEX res_user_login_key ON res_user (login);
-INSERT INTO res_user (id, login, name, active) VALUES (0, 'root', 'Root', 0);
-INSERT INTO res_user (id, login, name, active) VALUES (1, 'admin', 'Administrator', 1);
+INSERT INTO res_user (id, login, name, active, administrator) VALUES (0, 'root', 'Root', 0, 1);
CREATE TABLE res_group (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR
);
-INSERT INTO res_group (id, name) VALUES (1, 'Administration');
-
CREATE TABLE "res_user-res_group" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
"user" INTEGER NOT NULL,
diff --git a/trytond/trytond/commandline.py b/trytond/trytond/commandline.py
index 666ea6c3dd..3ef76b0401 100644
--- a/trytond/trytond/commandline.py
+++ b/trytond/trytond/commandline.py
@@ -150,12 +150,15 @@ def get_parser_admin():
parser.add_argument("--activate-dependencies", dest="activatedeps",
action="store_true",
help="activate missing dependencies of updated modules")
+ parser.add_argument(
+ "--admin-login", dest="admin_login", default="admin",
+ help="login of an administrator user (default: admin)")
parser.add_argument("--email", dest="email", type=validate_email,
help="set the admin email")
parser.add_argument("-p", "--password", dest="password",
- action='store_true', help="set the admin password")
+ action='store_true', help="set the administrator password")
parser.add_argument("--reset-password", dest='reset_password',
- action='store_true', help="reset the admin password")
+ action='store_true', help="reset the administrator password")
parser.add_argument("--test-email", dest='test_email', type=validate_email,
help="send a test email to the specified address")
parser.add_argument("-m", "--update-modules-list", action="store_true",
@@ -176,7 +179,7 @@ def get_parser_admin():
help="export module translations to locale folder")
parser.epilog = ('The first time a database is initialized '
- 'or when the password is set, the admin password is read '
+ 'or when the password is set, the administrator password is read '
'from file defined by TRYTONPASSFILE environment variable '
'or interactively asked from the user.\n'
'The config file can be specified in the TRYTOND_CONFIG '
diff --git a/trytond/trytond/convert.py b/trytond/trytond/convert.py
index 45b20d6605..2c1559ddd5 100644
--- a/trytond/trytond/convert.py
+++ b/trytond/trytond/convert.py
@@ -66,8 +66,9 @@ class MenuitemTagHandler:
values[attr] = attributes.get(attr)
values['icon'] = attributes.get('icon', 'tryton-folder')
- if attributes.get('active'):
- values['active'] = bool(eval(attributes['active']))
+ for attr in ['active', 'administration']:
+ if attr in attributes:
+ values[attr] = bool(eval(attributes[attr]))
if values.get('parent'):
model, id_ = self.mh.get_id(values['parent'])
diff --git a/trytond/trytond/ir/action.xml b/trytond/trytond/ir/action.xml
index 1207b5777f..8da77f69ad 100644
--- a/trytond/trytond/ir/action.xml
+++ b/trytond/trytond/ir/action.xml
@@ -48,14 +48,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.action
-
-
-
-
-
-
User in groups
@@ -63,7 +55,7 @@ this repository contains the full copyright notices and license terms. -->
-
+
diff --git a/trytond/trytond/ir/chat.xml b/trytond/trytond/ir/chat.xml
index d94f6823f2..70434db7ba 100644
--- a/trytond/trytond/ir/chat.xml
+++ b/trytond/trytond/ir/chat.xml
@@ -37,10 +37,6 @@ this repository contains the full copyright notices and license terms. -->
ir.chat.channel
This action will make previous entry point unusable. Do you want to continue?
-
-
-
-
ir.chat.channel
@@ -49,14 +45,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.chat.channel
-
-
-
-
-
-
ir.chat.follower
diff --git a/trytond/trytond/ir/cron.xml b/trytond/trytond/ir/cron.xml
index 764a84a215..795ca1a1c2 100644
--- a/trytond/trytond/ir/cron.xml
+++ b/trytond/trytond/ir/cron.xml
@@ -47,10 +47,6 @@ this repository contains the full copyright notices and license terms. -->
run_once
Run Once
-
-
-
-
ir.cron
@@ -59,14 +55,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.cron
-
-
-
-
-
-
ir.cron.log
diff --git a/trytond/trytond/ir/email.xml b/trytond/trytond/ir/email.xml
index 46c3963c38..e8d5555db7 100644
--- a/trytond/trytond/ir/email.xml
+++ b/trytond/trytond/ir/email.xml
@@ -103,13 +103,5 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.email.template
-
-
-
-
-
-
diff --git a/trytond/trytond/ir/error.xml b/trytond/trytond/ir/error.xml
index ba291a3347..e64c3f3ee4 100644
--- a/trytond/trytond/ir/error.xml
+++ b/trytond/trytond/ir/error.xml
@@ -81,14 +81,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.error
-
-
-
-
-
-
diff --git a/trytond/trytond/ir/export.xml b/trytond/trytond/ir/export.xml
index 26c7880c1a..ee533f427c 100644
--- a/trytond/trytond/ir/export.xml
+++ b/trytond/trytond/ir/export.xml
@@ -43,14 +43,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.export
-
-
-
-
-
-
ir.export.line
diff --git a/trytond/trytond/ir/lang.xml b/trytond/trytond/ir/lang.xml
index 4da39cd3c7..0355f93919 100644
--- a/trytond/trytond/ir/lang.xml
+++ b/trytond/trytond/ir/lang.xml
@@ -633,14 +633,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.lang
-
-
-
-
-
-
ir.lang.config.start
@@ -653,10 +645,6 @@ this repository contains the full copyright notices and license terms. -->
ir.lang.config
-
-
-
-
diff --git a/trytond/trytond/ir/message.xml b/trytond/trytond/ir/message.xml
index b4e84cd775..4c43d3f037 100644
--- a/trytond/trytond/ir/message.xml
+++ b/trytond/trytond/ir/message.xml
@@ -40,14 +40,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.message
-
-
-
-
-
-
diff --git a/trytond/trytond/ir/model.py b/trytond/trytond/ir/model.py
index fd03f369bf..281384ee78 100644
--- a/trytond/trytond/ir/model.py
+++ b/trytond/trytond/ir/model.py
@@ -596,11 +596,13 @@ class ModelAccess(
cursor = Transaction().connection.cursor()
model_access = cls.__table__()
+ is_administrator = User.is_administrator()
groups = User.get_groups()
access = {}
for model in models:
- maccess = cls._get_access_cache.get((groups, model), default=-1)
+ maccess = cls._get_access_cache.get(
+ (is_administrator, groups, model), default=-1)
if maccess == -1:
break
access[model] = maccess
@@ -639,38 +641,42 @@ class ModelAccess(
access[model] = default_singleton
else:
access[model] = default
- cursor.execute(*model_access.select(
- model_access.model,
- Max(Case(
- (model_access.perm_read == Literal(True), 1),
- else_=0)),
- Max(Case(
- (model_access.perm_write == Literal(True), 1),
- else_=0)),
- Max(Case(
- (model_access.perm_create == Literal(True), 1),
- else_=0)),
- Max(Case(
- (model_access.perm_delete == Literal(True), 1),
- else_=0)),
- where=model_access.model.in_(all_models)
- & (model_access.active == Literal(True))
- & (model_access.group.in_(groups or [-1])
- | (model_access.group == Null)),
- group_by=model_access.model))
- raw_access = {
- m: {'read': r, 'write': w, 'create': c, 'delete': d}
- for m, r, w, c, d in cursor}
- for model in models:
- access[model] = {
- perm: max(
- (raw_access[m][perm] for m in model2models[model]
- if m in raw_access),
- default=access[model][perm])
- for perm in ['read', 'write', 'create', 'delete']}
+ if not is_administrator:
+ cursor.execute(*model_access.select(
+ model_access.model,
+ Max(Case(
+ (model_access.perm_read == Literal(True), 1),
+ else_=0)),
+ Max(Case(
+ (model_access.perm_write == Literal(True), 1),
+ else_=0)),
+ Max(Case(
+ (model_access.perm_create == Literal(True), 1),
+ else_=0)),
+ Max(Case(
+ (model_access.perm_delete == Literal(True), 1),
+ else_=0)),
+ where=model_access.model.in_(all_models)
+ & (model_access.active == Literal(True))
+ & (model_access.group.in_(groups or [-1])
+ | (model_access.group == Null)),
+ group_by=model_access.model))
+ raw_access = {
+ m: {'read': r, 'write': w, 'create': c, 'delete': d}
+ for m, r, w, c, d in cursor}
+
+ for model in models:
+ access[model] = {
+ perm: max(
+ (raw_access[m][perm] for m in model2models[model]
+ if m in raw_access),
+ default=access[model][perm])
+ for perm in ['read', 'write', 'create', 'delete']}
+
for model, maccess in access.items():
- cls._get_access_cache.set((groups, model), maccess)
+ cls._get_access_cache.set(
+ (is_administrator, groups, model), maccess)
return access
@classmethod
@@ -829,12 +835,13 @@ class ModelFieldAccess(
pool = Pool()
User = pool.get('res.user')
field_access = cls.__table__()
-
+ is_administrator = User.is_administrator()
groups = User.get_groups()
accesses = {}
for model in models:
- maccesses = cls._get_access_cache.get((groups, model))
+ maccesses = cls._get_access_cache.get(
+ (is_administrator, groups, model))
if maccesses is None:
break
accesses[model] = maccesses
@@ -843,31 +850,34 @@ class ModelFieldAccess(
default = {}
accesses = dict((m, default) for m in models)
- cursor = Transaction().connection.cursor()
- cursor.execute(*field_access.select(
- field_access.model,
- field_access.field,
- Max(Case(
- (field_access.perm_read == Literal(True), 1),
- else_=0)),
- Max(Case(
- (field_access.perm_write == Literal(True), 1),
- else_=0)),
- Max(Case(
- (field_access.perm_create == Literal(True), 1),
- else_=0)),
- Max(Case(
- (field_access.perm_delete == Literal(True), 1),
- else_=0)),
- where=field_access.model.in_(models)
- & (field_access.active == Literal(True))
- & (field_access.group.in_(groups or [-1])
- | (field_access.group == Null)),
- group_by=[field_access.model, field_access.field]))
- for m, f, r, w, c, d in cursor:
- accesses[m][f] = {'read': r, 'write': w, 'create': c, 'delete': d}
+ if not is_administrator:
+ cursor = Transaction().connection.cursor()
+ cursor.execute(*field_access.select(
+ field_access.model,
+ field_access.field,
+ Max(Case(
+ (field_access.perm_read == Literal(True), 1),
+ else_=0)),
+ Max(Case(
+ (field_access.perm_write == Literal(True), 1),
+ else_=0)),
+ Max(Case(
+ (field_access.perm_create == Literal(True), 1),
+ else_=0)),
+ Max(Case(
+ (field_access.perm_delete == Literal(True), 1),
+ else_=0)),
+ where=field_access.model.in_(models)
+ & (field_access.active == Literal(True))
+ & (field_access.group.in_(groups or [-1])
+ | (field_access.group == Null)),
+ group_by=[field_access.model, field_access.field]))
+ for m, f, r, w, c, d in cursor:
+ accesses[m][f] = {
+ 'read': r, 'write': w, 'create': c, 'delete': d}
for model, maccesses in accesses.items():
- cls._get_access_cache.set((groups, model), maccesses)
+ cls._get_access_cache.set(
+ (is_administrator, groups, model), maccesses)
return accesses
@classmethod
diff --git a/trytond/trytond/ir/model.xml b/trytond/trytond/ir/model.xml
index a33ad86686..6921546faa 100644
--- a/trytond/trytond/ir/model.xml
+++ b/trytond/trytond/ir/model.xml
@@ -47,14 +47,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.model
-
-
-
-
-
-
ir.model.field
diff --git a/trytond/trytond/ir/module.xml b/trytond/trytond/ir/module.xml
index 133defe7c1..6318d46e7e 100644
--- a/trytond/trytond/ir/module.xml
+++ b/trytond/trytond/ir/module.xml
@@ -47,50 +47,30 @@ this repository contains the full copyright notices and license terms. -->
activate
Activate
-
-
-
-
ir.module
deactivate
Deactivate (beta)
-
-
-
-
ir.module
upgrade
Upgrade
-
-
-
-
ir.module
cancel
Cancel
-
-
-
-
ir.module
apply
Apply
-
-
-
-
ir.module
@@ -99,14 +79,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.module
-
-
-
-
-
-
ir.module.dependency
@@ -175,10 +147,6 @@ this repository contains the full copyright notices and license terms. -->
Perform Pending Activation/Upgrade
ir.module.activate_upgrade
-
-
-
-
ir.module.activate_upgrade.start
diff --git a/trytond/trytond/ir/resource.py b/trytond/trytond/ir/resource.py
index 4a593407d8..d0d092783f 100644
--- a/trytond/trytond/ir/resource.py
+++ b/trytond/trytond/ir/resource.py
@@ -57,10 +57,11 @@ class ResourceAccessMixin(ModelStorage):
@classmethod
def check_access(cls, ids, mode='read'):
pool = Pool()
+ User = pool.get('res.user')
ModelAccess = pool.get('ir.model.access')
Rule = pool.get('ir.rule')
transaction = Transaction()
- if transaction.user == 0 or not transaction.check_access:
+ if not transaction.check_access or User.is_administrator():
return
records = defaultdict(set)
with without_check_access():
diff --git a/trytond/trytond/ir/rule.py b/trytond/trytond/ir/rule.py
index f9e12c645d..fe0f75aa5b 100644
--- a/trytond/trytond/ir/rule.py
+++ b/trytond/trytond/ir/rule.py
@@ -210,7 +210,8 @@ class Rule(ModelSQL, ModelView):
pool = Pool()
User = pool.get('res.user')
return {
- 'groups': User.get_groups()
+ 'is_administrator': User.is_administrator(),
+ 'groups': User.get_groups(),
}
@classmethod
@@ -218,7 +219,11 @@ class Rule(ModelSQL, ModelView):
pool = Pool()
User = pool.get('res.user')
# _datetime value will be added to the domain
- return (Transaction().context.get('_datetime'), User.get_groups())
+ return (
+ Transaction().context.get('_datetime'),
+ User.is_administrator(),
+ User.get_groups(),
+ )
@classmethod
def get(cls, model_name, mode='read'):
@@ -234,7 +239,11 @@ class Rule(ModelSQL, ModelView):
assert mode in cls.modes
- groups = User.get_groups()
+ if User.is_administrator():
+ group_clause = Literal(True)
+ else:
+ groups = User.get_groups()
+ group_clause = rule_group_group.group.in_(groups or [-1])
model_names, model2field = _get_access_models(pool.get(model_name))
model_names = list(model_names)
@@ -247,7 +256,7 @@ class Rule(ModelSQL, ModelView):
).select(rule_table.id,
where=(rule_group.model.in_(model_names))
& (getattr(rule_group, 'perm_%s' % mode) == Literal(True))
- & (rule_group_group.group.in_(groups or [-1])
+ & (group_clause
| (rule_group.default_p == Literal(True))
| (rule_group.global_p == Literal(True))
)))
@@ -259,7 +268,7 @@ class Rule(ModelSQL, ModelView):
).select(rule_group.id,
where=(rule_group.model.in_(model_names))
& ~rule_group.id.in_(rule_table.select(rule_table.rule_group))
- & rule_group_group.group.in_(groups or [-1])))
+ & group_clause))
no_rules = cursor.fetchone()
clause = defaultdict(lambda: ['OR'])
diff --git a/trytond/trytond/ir/rule.xml b/trytond/trytond/ir/rule.xml
index f2828981b2..33e0c7d360 100644
--- a/trytond/trytond/ir/rule.xml
+++ b/trytond/trytond/ir/rule.xml
@@ -42,14 +42,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.rule.group
-
-
-
-
-
-
ir.rule
diff --git a/trytond/trytond/ir/sequence.xml b/trytond/trytond/ir/sequence.xml
index 3d02e48faf..72471f071b 100644
--- a/trytond/trytond/ir/sequence.xml
+++ b/trytond/trytond/ir/sequence.xml
@@ -50,7 +50,7 @@ this repository contains the full copyright notices and license terms. -->
-
+
@@ -96,7 +96,7 @@ this repository contains the full copyright notices and license terms. -->
-
+
@@ -141,13 +141,5 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.sequence.type
-
-
-
-
-
-
diff --git a/trytond/trytond/ir/translation.xml b/trytond/trytond/ir/translation.xml
index 5a405b94ba..dd4986f1e5 100644
--- a/trytond/trytond/ir/translation.xml
+++ b/trytond/trytond/ir/translation.xml
@@ -57,14 +57,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.translation
-
-
-
-
-
-
Translations
@@ -147,10 +139,6 @@ this repository contains the full copyright notices and license terms. -->
Synchronize Translations
ir.translation.update
-
-
-
-
form_action
@@ -185,10 +173,6 @@ this repository contains the full copyright notices and license terms. -->
Export Translations
ir.translation.export
-
-
-
-
-
- ir.trigger
-
-
-
-
-
-
diff --git a/trytond/trytond/ir/ui/icon.xml b/trytond/trytond/ir/ui/icon.xml
index 4f1c21acfd..3bc52da5a4 100644
--- a/trytond/trytond/ir/ui/icon.xml
+++ b/trytond/trytond/ir/ui/icon.xml
@@ -42,13 +42,5 @@
-
- ir.ui.icon
-
-
-
-
-
-
diff --git a/trytond/trytond/ir/ui/menu.py b/trytond/trytond/ir/ui/menu.py
index a56324832a..d6da9f4bba 100644
--- a/trytond/trytond/ir/ui/menu.py
+++ b/trytond/trytond/ir/ui/menu.py
@@ -6,6 +6,7 @@ from itertools import groupby
from trytond.model import (
DeactivableMixin, ModelSQL, ModelView, fields, sequence_ordered, tree)
from trytond.pool import Pool
+from trytond.pyson import Eval
from trytond.rpc import RPC
from trytond.transaction import Transaction, inactive_records
@@ -112,7 +113,13 @@ class UIMenu(
'ir.ui.menu-res.group', 'menu', 'group', "Groups",
filter=[
('active', '=', True),
- ])
+ ],
+ states={
+ 'invisible': Eval('administration', False),
+ })
+ administration = fields.Boolean(
+ "Administrator",
+ help="Check to restrict access to only administrators.")
favorite = fields.Function(fields.Boolean('Favorite'), 'get_favorite')
favorites = fields.Many2Many(
'ir.ui.menu.favorite', 'menu', 'user', "Favorites")
@@ -129,6 +136,10 @@ class UIMenu(
def default_sequence(cls):
return 50
+ @classmethod
+ def default_administration(cls):
+ return False
+
@staticmethod
def list_icons():
pool = Pool()
diff --git a/trytond/trytond/ir/ui/menu.xml b/trytond/trytond/ir/ui/menu.xml
index ecc0dccf19..caace2f1f3 100644
--- a/trytond/trytond/ir/ui/menu.xml
+++ b/trytond/trytond/ir/ui/menu.xml
@@ -67,14 +67,6 @@ this repository contains the full copyright notices and license terms. -->
-
-
diff --git a/trytond/trytond/ir/ui/ui.xml b/trytond/trytond/ir/ui/ui.xml
index 0dec71fca8..faff3967c7 100644
--- a/trytond/trytond/ir/ui/ui.xml
+++ b/trytond/trytond/ir/ui/ui.xml
@@ -42,13 +42,10 @@ this repository contains the full copyright notices and license terms. -->
-
show
Show
-
-
-
-
ir.ui.view
@@ -67,14 +63,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.ui.view
-
-
-
-
-
-
ir.ui.view_tree_width
@@ -185,14 +173,6 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.ui.view_tree_state
-
-
-
-
-
-
ir.ui.view_search
@@ -235,13 +215,5 @@ this repository contains the full copyright notices and license terms. -->
-
- ir.ui.view_search
-
-
-
-
-
-
diff --git a/trytond/trytond/ir/view/ui_menu_form.xml b/trytond/trytond/ir/view/ui_menu_form.xml
index 4ed92f5357..9f586ba85c 100644
--- a/trytond/trytond/ir/view/ui_menu_form.xml
+++ b/trytond/trytond/ir/view/ui_menu_form.xml
@@ -17,7 +17,10 @@ this repository contains the full copyright notices and license terms. -->
-
+
+
+
+
diff --git a/trytond/trytond/model/modelview.py b/trytond/trytond/model/modelview.py
index 18a06e9ab3..3cbf1fbfbb 100644
--- a/trytond/trytond/model/modelview.py
+++ b/trytond/trytond/model/modelview.py
@@ -751,7 +751,7 @@ class ModelView(Model):
assert len(records) == len(set(records)), "Duplicate records"
- if (transaction.user != 0) and check_access:
+ if not User.is_administrator() and check_access:
ModelAccess.check(cls.__name__, 'read')
if issubclass(cls, ModelStorage):
# Check record rule access
@@ -770,6 +770,7 @@ class ModelView(Model):
else:
ModelAccess.check(cls.__name__, 'write')
+ if (transaction.user != 0) and check_access:
states = cls._buttons.get(func.__name__, {})
for state_name in {'invisible', 'readonly'} & states.keys():
state = _pyson_encoder.encode(states[state_name])
diff --git a/trytond/trytond/report/report.py b/trytond/trytond/report/report.py
index 0be547314c..49a301120e 100644
--- a/trytond/trytond/report/report.py
+++ b/trytond/trytond/report/report.py
@@ -154,20 +154,23 @@ class Report(URLMixin, PoolBase):
return
with check_access():
- groups = set(User.get_groups())
- report_groups = ActionReport.get_groups(cls.__name__, action.id)
- if report_groups and not groups & report_groups:
- groups = Group.browse(User.get_groups())
- raise AccessError(
- gettext(
- 'ir.msg_access_report_error',
- report=cls.__name__),
- gettext(
- 'ir.msg_context_groups',
- groups=', '.join(g.rec_name for g in groups)))
+ if not User.is_administrator():
+ groups = set(User.get_groups())
+ report_groups = ActionReport.get_groups(
+ cls.__name__, action.id)
+ if report_groups and not groups & report_groups:
+ groups = Group.browse(User.get_groups())
+ raise AccessError(
+ gettext(
+ 'ir.msg_access_report_error',
+ report=cls.__name__),
+ gettext(
+ 'ir.msg_context_groups',
+ groups=', '.join(g.rec_name for g in groups)))
+ if model:
+ ModelAccess.check(model, 'read')
if model:
- ModelAccess.check(model, 'read')
Rule.check(model, ids)
@classmethod
diff --git a/trytond/trytond/res/group.xml b/trytond/trytond/res/group.xml
index 480016285b..d8a65cf9cf 100644
--- a/trytond/trytond/res/group.xml
+++ b/trytond/trytond/res/group.xml
@@ -3,9 +3,6 @@
this repository contains the full copyright notices and license terms. -->
-
- Administration
-
res.group
form
@@ -46,13 +43,5 @@ this repository contains the full copyright notices and license terms. -->
-
- res.group
-
-
-
-
-
-
diff --git a/trytond/trytond/res/notification.xml b/trytond/trytond/res/notification.xml
index 50de6dad7a..54bffb1567 100644
--- a/trytond/trytond/res/notification.xml
+++ b/trytond/trytond/res/notification.xml
@@ -47,15 +47,6 @@ this repository contains the full copyright notices and license terms. -->
-
- res.notification
-
-
-
-
-
-
-
Own notification
res.notification
@@ -77,10 +68,6 @@ this repository contains the full copyright notices and license terms. -->
-
-
-
-
res.notification
diff --git a/trytond/trytond/res/user.py b/trytond/trytond/res/user.py
index fdd0334db2..f1f5a2293c 100644
--- a/trytond/trytond/res/user.py
+++ b/trytond/trytond/res/user.py
@@ -31,6 +31,7 @@ from sql.conditionals import Case
from sql.functions import CurrentTimestamp
import trytond.config as config
+from trytond.backend import scalar_row
from trytond.cache import Cache
from trytond.exceptions import LoginException, RateLimitException, UserError
from trytond.i18n import gettext, ngettext
@@ -122,6 +123,14 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
'invisible': not _has_password,
},
depends=['password_reset'])
+ administrator = fields.Boolean(
+ "Administrator",
+ states={
+ 'readonly': (Eval('administrator', False)
+ & ((Eval('id') == Eval('context', {}).get('user'))
+ | (Eval('id', -1) == 0))),
+ },
+ help="When checked the user has the access rights of all groups.")
signature = fields.Text('Signature')
menu = fields.Many2One(
'ir.action', "Menu",
@@ -130,8 +139,11 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
actions = fields.Many2Many('res.user-ir.action', 'user', 'action',
'Actions', help='Actions that will be run at login.',
size=5)
- groups = fields.Many2Many('res.user-res.group',
- 'user', 'group', 'Groups')
+ groups = fields.Many2Many(
+ 'res.user-res.group', 'user', 'group', "Groups",
+ states={
+ 'invisible': Eval('administrator', False),
+ })
applications = fields.One2Many(
'res.user.application', 'user', "Applications")
language = fields.Many2One('ir.lang', 'Language',
@@ -152,6 +164,7 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
sessions = fields.Function(fields.Integer('Sessions'),
'get_sessions')
_get_groups_cache = Cache('res_user.get_groups', context=False)
+ _is_administrator_cache = Cache('res_user.is_administrator', context=False)
@classmethod
def __setup__(cls):
@@ -191,6 +204,7 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
cls._context_fields = [
'language',
'language_direction',
+ 'administrator',
'groups',
]
cls._order.insert(0, ('name', 'ASC'))
@@ -366,6 +380,8 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
or {'active', 'password'} & set(field_names)):
Session.clear(users)
UserDevice.clear([u.login for u in users])
+ if field_names is None or 'administrator' in field_names:
+ cls._is_administrator_cache.clear()
# Clean cursor cache as it could be filled by domain_get
for cache in Transaction().cache.values():
@@ -487,8 +503,7 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
getattr(user, field).rec_name
elif cls._fields[field]._type in ('one2many', 'many2many'):
res[field] = [x.id for x in getattr(user, field)]
- admin_id = ModelData.get_id('res.user_admin')
- if field == 'actions' and user.id == admin_id:
+ if field == 'actions' and user.is_administrator(user.id):
config_wizard_id = ModelData.get_id('ir',
'act_module_config_wizard')
action_id = Action.get_action_id(config_wizard_id)
@@ -550,7 +565,8 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
user_id = Transaction().user
user = cls(user_id)
for field in values:
- if field not in fields or field == 'groups':
+ if (field not in fields
+ or field in {'administrator', 'groups'}):
del values_clean[field]
if field == 'language':
langs = Lang.search([
@@ -581,7 +597,8 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
for name, definition in result['fields'].items():
definition = dict(definition)
result['fields'][name] = definition
- definition['readonly'] = name in {'groups', 'language_direction'}
+ definition['readonly'] = name in {
+ 'administrator', 'groups', 'language_direction'}
def convert2selection(definition, name):
del definition[name]['relation']
@@ -613,6 +630,23 @@ class User(avatar_mixin(100, 'login'), DeactivableMixin, ModelSQL, ModelView):
selection.append((action.id, action.rec_name))
return result
+ @classmethod
+ def is_administrator(cls, user=None):
+ transaction = Transaction()
+ if user is None:
+ user = transaction.user
+ else:
+ user = int(user)
+ if (is_admin := cls._is_administrator_cache.get(user)) is not None:
+ return is_admin
+
+ table = cls.__table__()
+ cursor = transaction.connection.cursor(row_factory=scalar_row)
+ cursor.execute(*table.select(
+ table.administrator, where=table.id == user))
+ administrator = cursor.fetchone()
+ return cls._is_administrator_cache.set(user, bool(administrator))
+
@classmethod
def get_groups(cls):
'''
diff --git a/trytond/trytond/res/user.xml b/trytond/trytond/res/user.xml
index f9cc5d6191..0b5955688a 100644
--- a/trytond/trytond/res/user.xml
+++ b/trytond/trytond/res/user.xml
@@ -3,16 +3,13 @@
this repository contains the full copyright notices and license terms. -->
-
- admin
- Administrator
-
+
+ root
+ Root
+
+
-
-
-
-
res.user
form
@@ -59,14 +56,6 @@ this repository contains the full copyright notices and license terms. -->
-
- res.user
-
-
-
-
-
-
res.user
reset_password
@@ -146,11 +135,6 @@ this repository contains the full copyright notices and license terms. -->
-
-
-
-
Reset Password
diff --git a/trytond/trytond/res/view/user_form.xml b/trytond/trytond/res/view/user_form.xml
index 13a3f8653c..bb3b077ef9 100644
--- a/trytond/trytond/res/view/user_form.xml
+++ b/trytond/trytond/res/view/user_form.xml
@@ -5,8 +5,12 @@ this repository contains the full copyright notices and license terms. -->
+
diff --git a/trytond/trytond/tests/sequence.xml b/trytond/trytond/tests/sequence.xml
index 4222916e75..52a6842fd3 100644
--- a/trytond/trytond/tests/sequence.xml
+++ b/trytond/trytond/tests/sequence.xml
@@ -7,11 +7,6 @@ this repository contains the full copyright notices and license terms. -->
Test
-
-
-
-
diff --git a/trytond/trytond/tests/test_access.py b/trytond/trytond/tests/test_access.py
index 2a221e5dc5..ebab6d9640 100644
--- a/trytond/trytond/tests/test_access.py
+++ b/trytond/trytond/tests/test_access.py
@@ -5,7 +5,8 @@
from trytond.model import ModelAccessProxy
from trytond.model.exceptions import AccessError
from trytond.pool import Pool
-from trytond.tests.test_tryton import DBTestCase, with_transaction
+from trytond.tests.test_tryton import (
+ DB_NAME, USER, DBTestCase, with_transaction)
from trytond.transaction import Transaction
_context = {'_check_access': True}
@@ -14,14 +15,32 @@ _context = {'_check_access': True}
class _ModelAccessTestCase(DBTestCase):
module = 'tests'
_perm = None
-
model_name = model_access_name = 'test.access'
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ with Transaction().start(DB_NAME, USER):
+ pool = Pool()
+ User = pool.get('res.user')
+ Group = pool.get('res.group')
+
+ user, = User.create([{
+ 'login': 'user',
+ 'administrator': False,
+ }])
+ cls.user_id = user.id
+
+ Group.create([{
+ 'name': 'Group',
+ 'users': [('add', [user.id])],
+ }])
+
@property
def group(self):
pool = Pool()
Group = pool.get('res.group')
- group, = Group.search([('users', '=', Transaction().user)])
+ group, = Group.search([('users', '=', self.user_id)])
return group
def _assert(self, record):
@@ -37,7 +56,8 @@ class _ModelAccessTestCase(DBTestCase):
TestAccess = pool.get(self.model_name)
record, = TestAccess.create([{}])
- self._assert(record)
+ with Transaction().set_user(self.user_id):
+ self._assert(record)
@with_transaction(context=_context)
def test_access_without_group(self):
@@ -52,7 +72,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert(record)
+ with Transaction().set_user(self.user_id):
+ self._assert(record)
@with_transaction(context=_context)
def test_no_access_without_group(self):
@@ -67,7 +88,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert_raises(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises(record)
@with_transaction(context=_context)
def test_one_access_with_groups(self):
@@ -87,7 +109,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert(record)
+ with Transaction().set_user(self.user_id):
+ self._assert(record)
@with_transaction(context=_context)
def test_one_access_without_group(self):
@@ -107,7 +130,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert(record)
+ with Transaction().set_user(self.user_id):
+ self._assert(record)
@with_transaction(context=_context)
def test_all_access_with_groups(self):
@@ -127,7 +151,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert(record)
+ with Transaction().set_user(self.user_id):
+ self._assert(record)
@with_transaction(context=_context)
def test_no_access_with_groups(self):
@@ -147,7 +172,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert_raises(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises(record)
@with_transaction(context=_context)
def test_one_access_with_group(self):
@@ -162,7 +188,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert(record)
+ with Transaction().set_user(self.user_id):
+ self._assert(record)
@with_transaction(context=_context)
def test_no_access_with_group(self):
@@ -177,7 +204,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert_raises(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises(record)
@with_transaction(context=_context)
def test_one_access_with_other_group(self):
@@ -199,7 +227,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert(record)
+ with Transaction().set_user(self.user_id):
+ self._assert(record)
@with_transaction(context=_context)
def test_no_access_with_other_group(self):
@@ -221,7 +250,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert_raises(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises(record)
@with_transaction(context=_context)
def test_no_access_with_inactive_group(self):
@@ -244,7 +274,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert_raises(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises(record)
@with_transaction(context=_context)
def test_no_access_with_member_inactive_group(self):
@@ -271,7 +302,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert_raises(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises(record)
@with_transaction(context=_context)
def test_one_access_with_other_group_no_perm(self):
@@ -293,7 +325,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert(record)
+ with Transaction().set_user(self.user_id):
+ self._assert(record)
@with_transaction(context=_context)
def test_access_inherited_from_parent(self):
@@ -318,7 +351,8 @@ class _ModelAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert(record)
+ with Transaction().set_user(self.user_id):
+ self._assert(record)
class ModelAccessReadTestCase(_ModelAccessTestCase):
@@ -334,9 +368,11 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
pool = Pool()
TestAccess = pool.get(self.model_name)
with self.assertRaises(AccessError):
- TestAccess.read([record.id], ['field1'])
+ with Transaction().set_user(self.user_id):
+ TestAccess.read([record.id], ['field1'])
with self.assertRaises(AccessError):
- TestAccess.search([])
+ with Transaction().set_user(self.user_id):
+ TestAccess.search([])
@with_transaction(context=_context)
def test_access_relate_empty(self):
@@ -345,9 +381,12 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
TestAccess = pool.get(self.model_name)
record, = TestAccess.create([{}])
- TestAccess.read([record.id], ['relate.value'])
- TestAccess.search([('relate.value', '=', 42)])
- TestAccess.search([('reference.value', '=', 42, 'test.access.relate')])
+ with Transaction().set_user(self.user_id):
+ TestAccess.read([record.id], ['relate.value'])
+ TestAccess.search([('relate.value', '=', 42)])
+ TestAccess.search([
+ ('reference.value', '=', 42, 'test.access.relate'),
+ ])
@with_transaction(context=_context)
def test_access_relate(self):
@@ -361,12 +400,15 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
'perm_read': True,
}])
- TestAccess.read([record.id], ['relate.value'])
- TestAccess.search([('relate.value', '=', 42)])
- TestAccess.search([('reference.value', '=', 42, 'test.access.relate')])
- TestAccess.search([('dict_.key', '=', 42)])
- TestAccess.search([], order=[('relate.value', 'ASC')])
- TestAccess.search([], order=[('dict_.key', 'ASC')])
+ with Transaction().set_user(self.user_id):
+ TestAccess.read([record.id], ['relate.value'])
+ TestAccess.search([('relate.value', '=', 42)])
+ TestAccess.search([
+ ('reference.value', '=', 42, 'test.access.relate'),
+ ])
+ TestAccess.search([('dict_.key', '=', 42)])
+ TestAccess.search([], order=[('relate.value', 'ASC')])
+ TestAccess.search([], order=[('dict_.key', 'ASC')])
@with_transaction(context=_context)
def test_no_access_relate(self):
@@ -380,15 +422,16 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
'perm_read': False,
}])
- with self.assertRaises(AccessError):
- TestAccess.read([record.id], ['relate.value'])
- with self.assertRaises(AccessError):
- TestAccess.search([('relate.value', '=', 42)])
- with self.assertRaises(AccessError):
- TestAccess.search(
- [('reference.value', '=', 42, 'test.access.relate')])
- with self.assertRaises(AccessError):
- TestAccess.search([], order=[('relate.value', 'ASC')])
+ with Transaction().set_user(self.user_id):
+ with self.assertRaises(AccessError):
+ TestAccess.read([record.id], ['relate.value'])
+ with self.assertRaises(AccessError):
+ TestAccess.search([('relate.value', '=', 42)])
+ with self.assertRaises(AccessError):
+ TestAccess.search(
+ [('reference.value', '=', 42, 'test.access.relate')])
+ with self.assertRaises(AccessError):
+ TestAccess.search([], order=[('relate.value', 'ASC')])
@with_transaction()
def test_model_access_proxy(self):
@@ -402,7 +445,8 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
'perm_read': True,
}])
- ModelAccessProxy(record, {})
+ with Transaction().set_user(self.user_id):
+ ModelAccessProxy(record, {})
@with_transaction()
def test_model_access_proxy_no_access(self):
@@ -416,8 +460,9 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
'perm_read': False,
}])
- with self.assertRaises(AccessError):
- ModelAccessProxy(record, {})
+ with Transaction().set_user(self.user_id):
+ with self.assertRaises(AccessError):
+ ModelAccessProxy(record, {})
class ModelAccessWriteTestCase(_ModelAccessTestCase):
@@ -488,11 +533,30 @@ class _ModelFieldAccessTestCase(DBTestCase):
module = 'tests'
_perm = None
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ with Transaction().start(DB_NAME, USER):
+ pool = Pool()
+ User = pool.get('res.user')
+ Group = pool.get('res.group')
+
+ user, = User.create([{
+ 'login': 'user',
+ 'administrator': False,
+ }])
+ cls.user_id = user.id
+
+ Group.create([{
+ 'name': 'Group',
+ 'users': [('add', [user.id])],
+ }])
+
@property
def group(self):
pool = Pool()
Group = pool.get('res.group')
- group, = Group.search([('users', '=', Transaction().user)])
+ group, = Group.search([('users', '=', self.user_id)])
return group
def _assert1(self, record):
@@ -514,8 +578,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
TestAccess = pool.get('test.access')
record, = TestAccess.create([{}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_access_without_group(self):
@@ -531,8 +596,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_no_access_without_group(self):
@@ -548,8 +614,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert_raises1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_one_access_with_groups(self):
@@ -571,8 +638,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_one_access_without_group(self):
@@ -594,8 +662,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_all_access_with_groups(self):
@@ -617,8 +686,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_no_access_with_groups(self):
@@ -640,8 +710,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert_raises1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_one_access_with_group(self):
@@ -657,8 +728,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_no_access_with_group(self):
@@ -674,8 +746,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert_raises1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_one_access_with_other_group(self):
@@ -699,8 +772,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_no_access_with_other_group(self):
@@ -724,8 +798,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert_raises1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_no_access_with_inactive_group(self):
@@ -750,8 +825,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert_raises1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_no_access_with_member_inactive_group(self):
@@ -780,8 +856,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert_raises1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_one_access_with_other_group_no_perm(self):
@@ -805,8 +882,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_access_inherited_from_parent(self):
@@ -833,8 +911,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_two_access(self):
@@ -858,8 +937,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_two_no_access(self):
@@ -883,8 +963,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert_raises1(record)
- self._assert_raises2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises1(record)
+ self._assert_raises2(record)
@with_transaction(context=_context)
def test_two_both_access(self):
@@ -908,8 +989,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert1(record)
- self._assert_raises2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert_raises2(record)
@with_transaction(context=_context)
def test_two_access_with_group(self):
@@ -933,8 +1015,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_two_access_with_groups(self):
@@ -958,8 +1041,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: True,
}])
- self._assert1(record)
- self._assert2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert2(record)
@with_transaction(context=_context)
def test_two_no_access_with_group(self):
@@ -983,8 +1067,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert_raises1(record)
- self._assert_raises2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert_raises1(record)
+ self._assert_raises2(record)
@with_transaction(context=_context)
def test_two_both_access_with_group(self):
@@ -1008,8 +1093,9 @@ class _ModelFieldAccessTestCase(DBTestCase):
self._perm: False,
}])
- self._assert1(record)
- self._assert_raises2(record)
+ with Transaction().set_user(self.user_id):
+ self._assert1(record)
+ self._assert_raises2(record)
class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
@@ -1049,8 +1135,11 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
pool = Pool()
TestAccess = pool.get('test.access')
- TestAccess.search([('relate.value', '=', 42)])
- TestAccess.search([('reference.value', '=', 42, 'test.access.relate')])
+ with Transaction().set_user(self.user_id):
+ TestAccess.search([('relate.value', '=', 42)])
+ TestAccess.search([
+ ('reference.value', '=', 42, 'test.access.relate'),
+ ])
@with_transaction(context=_context)
def test_access_search_relate(self):
@@ -1064,11 +1153,14 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
'perm_read': True,
}])
- TestAccess.search([('relate.value', '=', 42)])
- TestAccess.search([('reference.value', '=', 42, 'test.access.relate')])
- TestAccess.search([
- ('reference.parent.value', '=', 42, 'test.access.relate')])
- TestAccess.search([], order=[('relate.value', 'ASC')])
+ with Transaction().set_user(self.user_id):
+ TestAccess.search([('relate.value', '=', 42)])
+ TestAccess.search([
+ ('reference.value', '=', 42, 'test.access.relate'),
+ ])
+ TestAccess.search([
+ ('reference.parent.value', '=', 42, 'test.access.relate')])
+ TestAccess.search([], order=[('relate.value', 'ASC')])
@with_transaction(context=_context)
def test_no_access_search_relate(self):
@@ -1082,16 +1174,19 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
'perm_read': False,
}])
- with self.assertRaises(AccessError):
- TestAccess.search([('relate.value', '=', 42)])
- with self.assertRaises(AccessError):
- TestAccess.search(
- [('reference.value', '=', 42, 'test.access.relate')])
- with self.assertRaises(AccessError):
- TestAccess.search(
- [('reference.parent.value', '=', 42, 'test.access.relate')])
- with self.assertRaises(AccessError):
- TestAccess.search([], order=[('relate.value', 'ASC')])
+ with Transaction().set_user(self.user_id):
+ with self.assertRaises(AccessError):
+ TestAccess.search([('relate.value', '=', 42)])
+ with self.assertRaises(AccessError):
+ TestAccess.search(
+ [('reference.value', '=', 42, 'test.access.relate')])
+ with self.assertRaises(AccessError):
+ TestAccess.search([
+ ('reference.parent.value', '=', 42,
+ 'test.access.relate'),
+ ])
+ with self.assertRaises(AccessError):
+ TestAccess.search([], order=[('relate.value', 'ASC')])
@with_transaction(context=_context)
def test_access_search_relate_parent_field(self):
@@ -1105,7 +1200,8 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
'perm_read': True,
}])
- TestAccess.search([('relate', 'child_of', 42, 'parent')])
+ with Transaction().set_user(self.user_id):
+ TestAccess.search([('relate', 'child_of', 42, 'parent')])
@with_transaction(context=_context)
def test_no_access_search_relate_parent_field(self):
@@ -1119,8 +1215,9 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
'perm_read': False,
}])
- with self.assertRaises(AccessError):
- TestAccess.search([('relate', 'child_of', 42, 'parent')])
+ with Transaction().set_user(self.user_id):
+ with self.assertRaises(AccessError):
+ TestAccess.search([('relate', 'child_of', 42, 'parent')])
@with_transaction()
def test_model_access_proxy(self):
@@ -1154,8 +1251,9 @@ class ModelFieldAccessReadTestCase(_ModelFieldAccessTestCase):
proxy = ModelAccessProxy(record, {})
- with self.assertRaises(AccessError):
- ModelAccessProxy(proxy.field1)
+ with Transaction().set_user(self.user_id):
+ with self.assertRaises(AccessError):
+ ModelAccessProxy(proxy.field1)
class ModelFieldAccessWriteTestCase(_ModelFieldAccessTestCase):
@@ -1187,6 +1285,25 @@ class ModelFieldAccessWriteTestCase(_ModelFieldAccessTestCase):
class MenuActionAccessReadTestCase(DBTestCase):
module = 'tests'
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ with Transaction().start(DB_NAME, USER):
+ pool = Pool()
+ User = pool.get('res.user')
+ Group = pool.get('res.group')
+
+ user, = User.create([{
+ 'login': 'user',
+ 'administrator': False,
+ }])
+ cls.user_id = user.id
+
+ Group.create([{
+ 'name': 'Group',
+ 'users': [('add', [user.id])],
+ }])
+
def create_menu(self, access=None):
pool = Pool()
Action = pool.get('ir.action.act_window')
@@ -1213,7 +1330,8 @@ class MenuActionAccessReadTestCase(DBTestCase):
menu = self.create_menu()
- self.assertEqual(Menu.search([('id', '=', menu.id)]), [menu])
+ with Transaction().set_user(self.user_id):
+ self.assertEqual(Menu.search([('id', '=', menu.id)]), [menu])
@with_transaction(context=_context)
def test_access(self):
@@ -1223,7 +1341,8 @@ class MenuActionAccessReadTestCase(DBTestCase):
menu = self.create_menu(True)
- self.assertEqual(Menu.search([('id', '=', menu.id)]), [menu])
+ with Transaction().set_user(self.user_id):
+ self.assertEqual(Menu.search([('id', '=', menu.id)]), [menu])
@with_transaction(context=_context)
def test_no_access(self):
@@ -1233,7 +1352,8 @@ class MenuActionAccessReadTestCase(DBTestCase):
menu = self.create_menu(False)
- self.assertEqual(Menu.search([('id', '=', menu.id)]), [])
+ with Transaction().set_user(self.user_id):
+ self.assertEqual(Menu.search([('id', '=', menu.id)]), [])
del _ModelAccessTestCase, _ModelFieldAccessTestCase
diff --git a/trytond/trytond/tests/test_copy.py b/trytond/trytond/tests/test_copy.py
index 3cfb18e0f6..cd73e3b52f 100644
--- a/trytond/trytond/tests/test_copy.py
+++ b/trytond/trytond/tests/test_copy.py
@@ -271,11 +271,14 @@ class CopyTestCase(DBTestCase):
def test_no_acccess_copy_with_custom_value(self):
"Test copying field with no access and custom value"
pool = Pool()
+ User = pool.get('res.user')
FieldAccess = pool.get('ir.model.field.access')
TestAccess = pool.get('test.copy.access')
record, = TestAccess.create([{'name': 'foo'}])
+ user = User(login='foo')
+ user.save()
FieldAccess.create([{
'model': 'test.copy.access',
'field': 'name',
@@ -284,16 +287,20 @@ class CopyTestCase(DBTestCase):
'perm_write': False,
}])
- with self.assertRaises(AccessError):
- new_record, = TestAccess.copy([record])
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessError):
+ new_record, = TestAccess.copy([record])
@with_transaction(context={'_check_access': True})
def test_no_acccess_copy_with_default(self):
"Test copying field with no access but default value"
pool = Pool()
+ User = pool.get('res.user')
FieldAccess = pool.get('ir.model.field.access')
TestAccess = pool.get('test.copy.access')
+ user = User(login='foo')
+ user.save()
FieldAccess.create([{
'model': 'test.copy.access',
'field': 'name',
@@ -302,20 +309,24 @@ class CopyTestCase(DBTestCase):
'perm_write': False,
}])
- record, = TestAccess.create([{}])
- self.assertEqual(record.name, "Default")
- new_record, = TestAccess.copy([record])
- self.assertEqual(new_record.name, "Default")
+ with Transaction().set_user(user.id):
+ record, = TestAccess.create([{}])
+ self.assertEqual(record.name, "Default")
+ new_record, = TestAccess.copy([record])
+ self.assertEqual(new_record.name, "Default")
@with_transaction(context={'_check_access': True})
def test_no_acccess_copy_with_defaults(self):
"Test copying field with no access and defaults"
pool = Pool()
+ User = pool.get('res.user')
FieldAccess = pool.get('ir.model.field.access')
TestAccess = pool.get('test.copy.access')
record, = TestAccess.create([{}])
+ user = User(login='foo')
+ user.save()
FieldAccess.create([{
'model': 'test.copy.access',
'field': 'name',
@@ -324,19 +335,23 @@ class CopyTestCase(DBTestCase):
'perm_write': False,
}])
- with self.assertRaises(AccessError):
- new_record, = TestAccess.copy(
- [record], default={'name': 'nondefault'})
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessError):
+ new_record, = TestAccess.copy(
+ [record], default={'name': 'nondefault'})
@with_transaction(context={'_check_access': True})
def test_copy_with_no_read_access(self):
"Test copying field with no read access"
pool = Pool()
+ User = pool.get('res.user')
FieldAccess = pool.get('ir.model.field.access')
TestAccess = pool.get('test.copy.access')
record, = TestAccess.create([{}])
+ user = User(login='foo')
+ user.save()
FieldAccess.create([{
'model': 'test.copy.access',
'field': 'name',
@@ -345,8 +360,9 @@ class CopyTestCase(DBTestCase):
'perm_write': False,
}])
- new_record, = TestAccess.copy([record])
- self.assertNotEqual(new_record.id, record.id)
+ with Transaction().set_user(user.id):
+ new_record, = TestAccess.copy([record])
+ self.assertNotEqual(new_record.id, record.id)
@with_transaction()
def test_copy_empty(self):
diff --git a/trytond/trytond/tests/test_exportdata.py b/trytond/trytond/tests/test_exportdata.py
index 413b78b31d..7d6010a1ee 100644
--- a/trytond/trytond/tests/test_exportdata.py
+++ b/trytond/trytond/tests/test_exportdata.py
@@ -544,6 +544,7 @@ class ExportDataTestCase(DBTestCase):
pool = Pool()
ExportData = pool.get('test.export_data')
ModelAccess = pool.get('ir.model.access')
+ User = pool.get('res.user')
export, = ExportData.create([{
'char': "Test",
@@ -553,16 +554,24 @@ class ExportDataTestCase(DBTestCase):
'perm_read': False,
}])
- with self.assertRaises(AccessError):
- ExportData.export_data([export], ['char'])
+ u, = User.create([{
+ 'login': 'user',
+ 'administrator': False,
+ }])
+ with Transaction().set_user(u.id):
+ with self.assertRaises(AccessError):
+ ExportData.export_data([export], ['char'])
@with_transaction(context={'_check_access': True})
def test_field_access(self):
"Test export without field access"
pool = Pool()
+ User = pool.get('res.user')
ExportData = pool.get('test.export_data')
FieldAccess = pool.get('ir.model.field.access')
+ user = User(login='user')
+ user.save()
export, = ExportData.create([{
'char': "Test",
}])
@@ -572,16 +581,20 @@ class ExportDataTestCase(DBTestCase):
'perm_read': False,
}])
- with self.assertRaises(AccessError):
- ExportData.export_data([export], ['char'])
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessError):
+ ExportData.export_data([export], ['char'])
@with_transaction(context={'_check_access': True})
def test_rule_access(self):
"Test export with rule access"
pool = Pool()
+ User = pool.get('res.user')
ExportData = pool.get('test.export_data')
RuleGroup = pool.get('ir.rule.group')
+ user = User(login='user')
+ user.save()
export1, export2 = ExportData.create([{
'char': "foo",
}, {
@@ -598,6 +611,7 @@ class ExportDataTestCase(DBTestCase):
}])],
}])
- ExportData.export_data([export1], ['char'])
- with self.assertRaises(AccessError):
- ExportData.export_data([export2], ['char'])
+ with Transaction().set_user(user.id):
+ ExportData.export_data([export1], ['char'])
+ with self.assertRaises(AccessError):
+ ExportData.export_data([export2], ['char'])
diff --git a/trytond/trytond/tests/test_ir.py b/trytond/trytond/tests/test_ir.py
index a84aefedae..89e53705db 100644
--- a/trytond/trytond/tests/test_ir.py
+++ b/trytond/trytond/tests/test_ir.py
@@ -573,22 +573,26 @@ class IrTestCase(ModuleTestCase):
ModelData = pool.get('ir.model.data')
User = pool.get('res.user')
- admin_id = ModelData.get_id('res', 'user_admin')
- admin, = User.search([('login', '=', 'admin')])
+ root_id = ModelData.get_id('res', 'user_root')
+ root, = User.search([('login', '=', 'root'), ('active', '=', False)])
- self.assertEqual(admin_id, admin.id)
+ self.assertEqual(root_id, root.id)
@with_transaction()
def test_model_data_get_id_dot(self):
"Test ModelData.get_id with dot"
pool = Pool()
ModelData = pool.get('ir.model.data')
- User = pool.get('res.user')
+ Button = pool.get('ir.model.button')
- admin_id = ModelData.get_id('res.user_admin')
- admin, = User.search([('login', '=', 'admin')])
+ validate_id = ModelData.get_id(
+ 'res.user_application_validate_button')
+ button, = Button.search([
+ ('model', '=', 'res.user.application'),
+ ('name', '=', 'validate_'),
+ ])
- self.assertEqual(admin_id, admin.id)
+ self.assertEqual(validate_id, button.id)
@with_transaction()
def test_email_send(self):
diff --git a/trytond/trytond/tests/test_model.py b/trytond/trytond/tests/test_model.py
index 4a420a1613..3495c55619 100644
--- a/trytond/trytond/tests/test_model.py
+++ b/trytond/trytond/tests/test_model.py
@@ -330,16 +330,19 @@ class ModelTestCase(DBTestCase):
pool = Pool()
Model = pool.get('test.model')
FieldAccess = pool.get('ir.model.field.access')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
FieldAccess.create([{
'model': Model.__name__,
'field': 'name',
'perm_write': False,
}])
- definition = Model.fields_get(['name'])
-
- self.assertTrue(definition['name']['readonly'])
+ with Transaction().set_user(user.id):
+ definition = Model.fields_get(['name'])
+ self.assertTrue(definition['name']['readonly'])
@with_transaction()
def test_copy(self):
diff --git a/trytond/trytond/tests/test_modelview.py b/trytond/trytond/tests/test_modelview.py
index c30ccf20cd..2d4cc78441 100644
--- a/trytond/trytond/tests/test_modelview.py
+++ b/trytond/trytond/tests/test_modelview.py
@@ -12,6 +12,7 @@ from trytond.model.modelview import set_visible
from trytond.pool import Pool
from trytond.pyson import Eval, PYSONDecoder, PYSONEncoder
from trytond.tests.test_tryton import DBTestCase, with_transaction
+from trytond.transaction import Transaction
class ModelView(DBTestCase):
@@ -404,70 +405,144 @@ class ModelView(DBTestCase):
})
@with_transaction(context={'_check_access': True})
- def test_button_access(self):
- 'Test Button Access'
+ def test_button_access_wo_any_access(self):
+ 'Test Button Access without any access'
+ pool = Pool()
+ TestModel = pool.get('test.modelview.button')
+ Button = pool.get('ir.model.button')
+ User = pool.get('res.user')
+
+ user = User(login='foo')
+ user.save()
+ button = Button(model=TestModel.__name__, name='test')
+ button.save()
+
+ test = TestModel()
+ with Transaction().set_user(user.id):
+ TestModel.test([test])
+
+ @with_transaction(context={'_check_access': True})
+ def test_button_access_wo_read(self):
+ 'Test Button Access without read access'
+ pool = Pool()
+ TestModel = pool.get('test.modelview.button')
+ Button = pool.get('ir.model.button')
+ ModelAccess = pool.get('ir.model.access')
+ User = pool.get('res.user')
+
+ user = User(login='foo')
+ user.save()
+ button = Button(model=TestModel.__name__, name='test')
+ button.save()
+ access = ModelAccess(
+ model=TestModel.__name__, group=None, perm_read=False)
+ access.save()
+
+ test = TestModel()
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessError):
+ TestModel.test([test])
+
+ @with_transaction(context={'_check_access': True})
+ def test_button_access_wo_write(self):
+ 'Test Button Access without write access'
+ pool = Pool()
+ TestModel = pool.get('test.modelview.button')
+ Button = pool.get('ir.model.button')
+ ModelAccess = pool.get('ir.model.access')
+ User = pool.get('res.user')
+
+ user = User(login='foo')
+ user.save()
+ button = Button(model=TestModel.__name__, name='test')
+ button.save()
+ access = ModelAccess(
+ model=TestModel.__name__, group=None, perm_read=True,
+ perm_write=False)
+ access.save()
+
+ test = TestModel()
+ with Transaction().set_user(2):
+ with self.assertRaises(AccessError):
+ TestModel.test([test])
+
+ @with_transaction(context={'_check_access': True})
+ def test_button_access_wo_write_w_button(self):
+ 'Test Button Access without write access with button access'
pool = Pool()
TestModel = pool.get('test.modelview.button')
Button = pool.get('ir.model.button')
ModelAccess = pool.get('ir.model.access')
Group = pool.get('res.group')
+ User = pool.get('res.user')
- admin, = Group.search([('name', '=', 'Administration')])
- test = TestModel()
-
+ user = User(login='foo')
+ user.save()
+ group = Group(name='Group')
+ group.users = [user]
+ group.save()
button = Button(model=TestModel.__name__, name='test')
+ button.groups = [group]
button.save()
-
- # Without model/button access
- TestModel.test([test])
-
- # Without read access
access = ModelAccess(
- model=TestModel.__name__, group=None, perm_read=False)
+ model=TestModel.__name__, group=None, perm_read=True,
+ perm_write=False)
access.save()
- with self.assertRaises(AccessError):
+
+ test = TestModel()
+ with Transaction().set_user(user.id):
TestModel.test([test])
- # Without write access
- access.perm_read = True
- access.perm_write = False
- access.save()
- with self.assertRaises(AccessError):
- TestModel.test([test])
+ @with_transaction(context={'_check_access': True})
+ def test_button_access_wo_button(self):
+ 'Test Button Access without button access'
+ pool = Pool()
+ TestModel = pool.get('test.modelview.button')
+ Button = pool.get('ir.model.button')
+ ModelAccess = pool.get('ir.model.access')
+ User = pool.get('res.user')
+ Group = pool.get('res.group')
- # Without write access but with button access
- button.groups = [admin]
- button.save()
- TestModel.test([test])
-
- # Without button access
- ModelAccess.delete([access])
+ user = User(login='foo')
+ user.save()
no_group = Group(name='no group')
no_group.save()
+ button = Button(model=TestModel.__name__, name='test')
button.groups = [no_group]
button.save()
- with self.assertRaises(AccessButtonError):
- TestModel.test([test])
+ access = ModelAccess(
+ model=TestModel.__name__, group=None, perm_read=True,
+ perm_write=False)
+ access.save()
+
+ test = TestModel()
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessButtonError):
+ TestModel.test([test])
@with_transaction(context={'_check_access': True})
def test_button_access_state(self):
"Test Button Access with states"
pool = Pool()
Model = pool.get('test.modelview.button')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
record = Model()
- Model.test_invisible([record])
- Model.test_readonly([record])
-
- record = Model(value=42)
-
- with self.assertRaises(AccessButtonError):
+ with Transaction().set_user(user.id):
Model.test_invisible([record])
-
- with self.assertRaises(AccessButtonError):
Model.test_readonly([record])
+ record = Model(value=42)
+
+ with self.assertRaises(AccessButtonError):
+ Model.test_invisible([record])
+
+ with self.assertRaises(AccessButtonError):
+ Model.test_readonly([record])
+
@with_transaction(context={'_check_access': True})
def test_button_no_rule(self):
"Test no Button Rule"
@@ -493,23 +568,27 @@ class ModelView(DBTestCase):
Button = pool.get('ir.model.button')
ButtonRule = pool.get('ir.model.button.rule')
ButtonClick = pool.get('ir.model.button.click')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
rule = ButtonRule(number_user=2)
button = Button(model=TestModel.__name__, name='test', rules=[rule])
button.save()
- record = TestModel(id=-1)
- with patch.object(TestModel, 'test_non_decorated') as button_func:
- TestModel.test([record])
- button_func.assert_called_with([])
+ with Transaction().set_user(user.id):
+ record = TestModel(id=-1)
+ with patch.object(TestModel, 'test_non_decorated') as button_func:
+ TestModel.test([record])
+ button_func.assert_called_with([])
- clicks = ButtonClick.search([
- ('button', '=', button.id),
- ('record_id', '=', record.id),
- ])
- self.assertEqual(len(clicks), 1)
- click, = clicks
- self.assertEqual(click.user.id, 1)
+ clicks = ButtonClick.search([
+ ('button', '=', button.id),
+ ('record_id', '=', record.id),
+ ])
+ self.assertEqual(len(clicks), 1)
+ click, = clicks
+ self.assertEqual(click.user.id, user.id)
@with_transaction(context={'_check_access': True})
def test_button_rule_passed(self):
@@ -519,23 +598,27 @@ class ModelView(DBTestCase):
Button = pool.get('ir.model.button')
ButtonRule = pool.get('ir.model.button.rule')
ButtonClick = pool.get('ir.model.button.click')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
rule = ButtonRule(number_user=1)
button = Button(model=TestModel.__name__, name='test', rules=[rule])
button.save()
- record = TestModel(id=-1)
- with patch.object(TestModel, 'test_non_decorated') as button_func:
- TestModel.test([record])
- button_func.assert_called_with([record])
+ with Transaction().set_user(user.id):
+ record = TestModel(id=-1)
+ with patch.object(TestModel, 'test_non_decorated') as button_func:
+ TestModel.test([record])
+ button_func.assert_called_with([record])
- clicks = ButtonClick.search([
- ('button', '=', button.id),
- ('record_id', '=', record.id),
- ])
- self.assertEqual(len(clicks), 1)
- click, = clicks
- self.assertEqual(click.user.id, 1)
+ clicks = ButtonClick.search([
+ ('button', '=', button.id),
+ ('record_id', '=', record.id),
+ ])
+ self.assertEqual(len(clicks), 1)
+ click, = clicks
+ self.assertEqual(click.user.id, user.id)
@with_transaction()
def test_button_rule_test_condition(self):
@@ -672,19 +755,23 @@ class ModelView(DBTestCase):
pool = Pool()
TestModel = pool.get('test.modelview.link')
ModelAccess = pool.get('ir.model.access')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
access = ModelAccess(
model='test.modelview.link.target', group=None, perm_read=False)
access.save()
- arch = TestModel.fields_view_get()['arch']
- parser = etree.XMLParser()
- tree = etree.fromstring(arch, parser=parser)
- links = tree.xpath('//link')
- labels = tree.xpath('//label')
+ with Transaction().set_user(user.id):
+ arch = TestModel.fields_view_get()['arch']
+ parser = etree.XMLParser()
+ tree = etree.fromstring(arch, parser=parser)
+ links = tree.xpath('//link')
+ labels = tree.xpath('//label')
- self.assertFalse(links)
- self.assertTrue(labels)
+ self.assertFalse(links)
+ self.assertTrue(labels)
@unittest.skipUnless(hasattr(etree, 'RelaxNG'), "etree is missing RelaxNG")
@with_transaction()
@@ -714,7 +801,10 @@ class ModelView(DBTestCase):
ActionWindow = pool.get('ir.action.act_window')
Group = pool.get('res.group')
ActionGroup = pool.get('ir.action-res.group')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
group = Group(name="Group")
group.save()
action_window, = ActionWindow.search(
@@ -723,14 +813,15 @@ class ModelView(DBTestCase):
action=action_window.action,
group=group).save()
- arch = TestModel.fields_view_get()['arch']
- parser = etree.XMLParser()
- tree = etree.fromstring(arch, parser=parser)
- links = tree.xpath('//link')
- labels = tree.xpath('//label')
+ with Transaction().set_user(user.id):
+ arch = TestModel.fields_view_get()['arch']
+ parser = etree.XMLParser()
+ tree = etree.fromstring(arch, parser=parser)
+ links = tree.xpath('//link')
+ labels = tree.xpath('//label')
- self.assertFalse(links)
- self.assertTrue(labels)
+ self.assertFalse(links)
+ self.assertTrue(labels)
@with_transaction()
def test_rpc_setup(self):
@@ -779,7 +870,10 @@ class ModelView(DBTestCase):
pool = Pool()
EmptyPage = pool.get('test.modelview.empty_page.access')
FieldAccess = pool.get('ir.model.field.access')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
access = FieldAccess()
access.model = EmptyPage.__name__
access.field = 'foo'
@@ -787,12 +881,13 @@ class ModelView(DBTestCase):
access.perm_write = False
access.save()
- arch = EmptyPage.fields_view_get(view_type='form')['arch']
+ with Transaction().set_user(user.id):
+ arch = EmptyPage.fields_view_get(view_type='form')['arch']
- parser = etree.XMLParser()
- tree = etree.fromstring(arch, parser=parser)
- pages = tree.xpath('//page')
- self.assertEqual(len(pages), 0)
+ parser = etree.XMLParser()
+ tree = etree.fromstring(arch, parser=parser)
+ pages = tree.xpath('//page')
+ self.assertEqual(len(pages), 0)
@with_transaction()
def test_active_field(self):
@@ -813,7 +908,10 @@ class ModelView(DBTestCase):
pool = Pool()
CircularDepends = pool.get('test.modelview.circular_depends')
FieldAccess = pool.get('ir.model.field.access')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
FieldAccess.create([{
'model': CircularDepends.__name__,
'field': 'foo',
@@ -821,9 +919,9 @@ class ModelView(DBTestCase):
'perm_read': False,
}])
- fields = CircularDepends.fields_view_get(view_type='form')['fields']
-
- self.assertEqual(fields, {})
+ with Transaction().set_user(user.id):
+ fields_view = CircularDepends.fields_view_get(view_type='form')
+ self.assertEqual(fields_view['fields'], {})
@with_transaction()
def test_depends_depends(self):
@@ -854,7 +952,10 @@ class ModelView(DBTestCase):
pool = Pool()
Button = pool.get('test.modelview.button_depends')
FieldAccess = pool.get('ir.model.field.access')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
FieldAccess.create([{
'model': Button.__name__,
'field': 'value',
@@ -862,12 +963,13 @@ class ModelView(DBTestCase):
'perm_read': False,
}])
- arch = Button.fields_view_get(view_type='form')['arch']
- parser = etree.XMLParser()
- tree = etree.fromstring(arch, parser=parser)
- buttons = tree.xpath('//button')
+ with Transaction().set_user(user.id):
+ arch = Button.fields_view_get(view_type='form')['arch']
+ parser = etree.XMLParser()
+ tree = etree.fromstring(arch, parser=parser)
+ buttons = tree.xpath('//button')
- self.assertEqual(len(buttons), 0)
+ self.assertEqual(len(buttons), 0)
@with_transaction()
def test_view_attributes(self):
diff --git a/trytond/trytond/tests/test_report.py b/trytond/trytond/tests/test_report.py
index 1fa693112f..60807fcd4a 100644
--- a/trytond/trytond/tests/test_report.py
+++ b/trytond/trytond/tests/test_report.py
@@ -52,7 +52,7 @@ class ReportTestCase(DBTestCase):
Report.execute([], {}),
('txt', 'Administrator\n', False, 'Test Report'))
- @with_transaction()
+ @with_transaction(context={'_check_access': True})
def test_execute_without_access(self):
"Execute report without model access"
with file_open('report.xml', subdir='tests') as xml:
@@ -60,9 +60,12 @@ class ReportTestCase(DBTestCase):
pool = Pool()
ActionReport = pool.get('ir.action.report')
+ User = pool.get('res.user')
Group = pool.get('res.group')
Report = pool.get('test.test_report', type='report')
+ user = User(login='foo')
+ user.save()
group = Group(name="Test")
group.save()
action_report, = ActionReport.search([
@@ -72,9 +75,12 @@ class ReportTestCase(DBTestCase):
action_report.save()
with self.assertRaises(AccessError):
- Report.execute([], {'model': 'test.access'})
+ with Transaction().set_user(user.id):
+ Report.execute(
+ [],
+ {'model': 'test.access', 'action_id': action_report.id})
- @with_transaction()
+ @with_transaction(context={'_check_access': True})
def test_execute_without_model_access(self):
"Execute report without model access"
with file_open('report.xml', subdir='tests') as xml:
@@ -83,21 +89,27 @@ class ReportTestCase(DBTestCase):
pool = Pool()
Report = pool.get('test.test_report', type='report')
ModelAccess = pool.get('ir.model.access')
+ User = pool.get('res.user')
+
+ user = User(login='foo')
+ user.save()
ModelAccess.create([{
'model': 'test.access',
'perm_write': False,
}])
with self.assertRaises(AccessError):
- Report.execute([], {'model': 'test.access'})
+ with Transaction().set_user(user.id):
+ Report.execute([], {'model': 'test.access'})
- @with_transaction()
+ @with_transaction(context={'_check_access': True})
def test_execute_without_read_access(self):
"Execute report without read access"
with file_open('report.xml', subdir='tests') as xml:
import_xml(xml)
pool = Pool()
+ User = pool.get('res.user')
Report = pool.get('test.test_report', type='report')
Model = pool.get('test.access')
RuleGroup = pool.get('ir.rule.group')
@@ -114,8 +126,12 @@ class ReportTestCase(DBTestCase):
}])],
}])
+ user = User(login='foo')
+ user.save()
+
with self.assertRaises(AccessError):
- Report.execute([record.id], {'model': 'test.access'})
+ with Transaction().set_user(user.id):
+ Report.execute([record.id], {'model': 'test.access'})
@unittest.skipUnless(mrml, "required mrml")
@with_transaction()
diff --git a/trytond/trytond/tests/test_resource.py b/trytond/trytond/tests/test_resource.py
index 6b4c0d458a..c423edfc2c 100644
--- a/trytond/trytond/tests/test_resource.py
+++ b/trytond/trytond/tests/test_resource.py
@@ -110,7 +110,11 @@ class ResourceTestCase(DBTestCase):
Note = pool.get('ir.note')
Resource = pool.get('test.resource')
ModelAccess = pool.get('ir.model.access')
+ User = pool.get('res.user')
+ transaction = Transaction()
+ user = User(login='foo')
+ user.save()
ModelAccess.create([{
'model': Resource.__name__,
'group': None,
@@ -119,7 +123,8 @@ class ResourceTestCase(DBTestCase):
}])
record, = Resource.create([{}])
- with Transaction().set_context(_check_access=True):
+ with (transaction.set_user(user.id),
+ transaction.set_context(_check_access=True)):
note, = Note.create([{
'resource': record,
'message': "Foo",
@@ -135,7 +140,11 @@ class ResourceTestCase(DBTestCase):
Note = pool.get('ir.note')
Resource = pool.get('test.resource')
ModelAccess = pool.get('ir.model.access')
+ User = pool.get('res.user')
+ transaction = Transaction()
+ user = User(login='foo')
+ user.save()
ModelAccess.create([{
'model': Resource.__name__,
'group': None,
@@ -149,7 +158,8 @@ class ResourceTestCase(DBTestCase):
'message': "Message",
}])
- with Transaction().set_context(_check_access=True):
+ with (transaction.set_user(user.id),
+ transaction.set_context(_check_access=True)):
with self.assertRaises(AccessError):
Note.create([{
'resource': record,
@@ -170,7 +180,11 @@ class ResourceTestCase(DBTestCase):
Note = pool.get('ir.note')
Resource = pool.get('test.resource')
RuleGroup = pool.get('ir.rule.group')
+ User = pool.get('res.user')
+ transaction = Transaction()
+ user = User(login='foo')
+ user.save()
RuleGroup.create([{
'name': "Test",
'model': Resource.__name__,
@@ -183,7 +197,8 @@ class ResourceTestCase(DBTestCase):
}])
record, = Resource.create([{}])
- with Transaction().set_context(_check_access=True):
+ with (transaction.set_user(user.id),
+ transaction.set_context(_check_access=True)):
note, = Note.create([{
'resource': record,
'message': "Foo",
@@ -199,7 +214,11 @@ class ResourceTestCase(DBTestCase):
Note = pool.get('ir.note')
Resource = pool.get('test.resource')
RuleGroup = pool.get('ir.rule.group')
+ User = pool.get('res.user')
+ transaction = Transaction()
+ user = User(login='foo')
+ user.save()
RuleGroup.create([{
'name': "Test",
'model': Resource.__name__,
@@ -217,7 +236,8 @@ class ResourceTestCase(DBTestCase):
'message': "Message",
}])
- with Transaction().set_context(_check_access=True):
+ with (transaction.set_user(user.id),
+ transaction.set_context(_check_access=True)):
with self.assertRaises(AccessError):
Note.create([{
'resource': record,
diff --git a/trytond/trytond/tests/test_rest.py b/trytond/trytond/tests/test_rest.py
index 48188dedfc..33a044c68a 100644
--- a/trytond/trytond/tests/test_rest.py
+++ b/trytond/trytond/tests/test_rest.py
@@ -29,10 +29,18 @@ class RESTTestCase(RouteTestCase):
def setUpDatabase(cls):
pool = Pool()
User = pool.get('res.user')
+ Group = pool.get('res.group')
UserApplication = pool.get('res.user.application')
+
+ group = Group(name="Group")
+ group.save()
+ cls.user_group_id = group.id
+ user = User(login='user', email='user@tryton.org')
+ user.groups = [group]
+ user.save()
+ cls.user_id = user.id
+
admin, = User.search([('login', '=', 'admin')])
- admin.email = 'admin@tryton.org'
- admin.save()
application = UserApplication(user=admin, application='rest')
application.save()
cls.key = application.key
@@ -49,13 +57,13 @@ class RESTTestCase(RouteTestCase):
f'{self.db_name}/rest/model/res.user', headers=self.headers,
query_string=[
('d', urlsafe_b64encode(json.dumps(
- [('login', '=', 'admin')]).encode())),
+ [('login', '=', 'user')]).encode())),
])
self.assertEqual(response.status_code, HTTPStatus.OK)
self.assertEqual(
response.json,
- [{'id': 1, '__name__': 'res.user', 'rec_name': 'Administrator'}])
+ [{'id': self.user_id, '__name__': 'res.user', 'rec_name': 'user'}])
self.assertEqual(response.headers.get('Content-Language'), 'fr')
def test_search_range(self):
@@ -129,43 +137,45 @@ class RESTTestCase(RouteTestCase):
def test_get(self):
"Test get"
response = self.client().get(
- f'{self.db_name}/rest/model/res.user/1', headers=self.headers)
+ f'{self.db_name}/rest/model/res.user/{self.user_id}',
+ headers=self.headers)
self.assertEqual(
response.json,
- {'id': 1, '__name__': 'res.user', 'rec_name': 'Administrator'})
+ {'id': self.user_id, '__name__': 'res.user', 'rec_name': 'user'})
def test_get_fields(self):
"Test get fields"
response = self.client().get(
- f'{self.db_name}/rest/model/res.user/1', headers=self.headers,
+ f'{self.db_name}/rest/model/res.user/{self.user_id}',
+ headers=self.headers,
query_string=[
- ('f', 'name'),
('f', 'login'),
('f', 'groups.name'),
])
self.assertEqual(
response.json,
- {'id': 1, '__name__': 'res.user',
- 'name': "Administrator", 'login': 'admin',
+ {'id': self.user_id, '__name__': 'res.user',
+ 'login': 'user',
'groups': [
- {'id': 1, '__name__': 'res.group',
- 'name': 'Administration'}],
+ {'id': self.user_group_id, '__name__': 'res.group',
+ 'name': 'Group'}],
})
def test_get_fields_2many(self):
"Test get only xxx2many fields"
response = self.client().get(
- f'{self.db_name}/rest/model/res.user/1', headers=self.headers,
+ f'{self.db_name}/rest/model/res.user/{self.user_id}',
+ headers=self.headers,
query_string=[
('f', 'groups'),
])
self.assertEqual(
response.json,
- {'id': 1, '__name__': 'res.user',
- 'groups': [1],
+ {'id': self.user_id, '__name__': 'res.user',
+ 'groups': [self.user_group_id],
})
def test_get_not_found(self):
@@ -241,13 +251,17 @@ class RESTTestCase(RouteTestCase):
def test_button(self):
"Test button"
response = self.client().post(
- f'{self.db_name}/rest/model/res.user/1/reset_password',
+ f'{self.db_name}/rest/model/'
+ f'res.user/{self.user_id}/reset_password',
headers=self.headers)
self.assertEqual(response.status_code, HTTPStatus.OK)
self.assertEqual(
- response.json,
- {'id': 1, '__name__': 'res.user', 'rec_name': 'Administrator'})
+ response.json, {
+ 'id': self.user_id,
+ '__name__': 'res.user',
+ 'rec_name': 'user',
+ })
def test_button_no_record(self):
"Test button without record"
@@ -261,7 +275,8 @@ class RESTTestCase(RouteTestCase):
def test_button_data(self):
"Test button with data"
response = self.client().post(
- f'{self.db_name}/rest/model/res.user/1/reset_password',
+ f'{self.db_name}/rest/model/'
+ f'res.user/{self.user_id}/reset_password',
json={
'length': 12,
},
@@ -280,7 +295,8 @@ class RESTTestCase(RouteTestCase):
def test_button_no_button(self):
"Test no button"
response = self.client().post(
- f'{self.db_name}/rest/model/res.user/1/validate',
+ f'{self.db_name}/rest/model/'
+ f'res.user/{self.user_id}/validate',
headers=self.headers)
self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN)
@@ -290,10 +306,12 @@ class RESTTestCase(RouteTestCase):
c = self.client()
response = c.post(
- f'{self.db_name}/rest/model/res.user/1/reset_password',
+ f'{self.db_name}/rest/model/'
+ f'res.user/{self.user_id}/reset_password',
headers=self.headers)
response = c.get(
- f'{self.db_name}/rest/report/res.user.email_reset_password/1',
+ f'{self.db_name}/rest/report/'
+ f'res.user.email_reset_password/{self.user_id}',
headers=self.headers)
self.assertEqual(response.status_code, HTTPStatus.OK)
diff --git a/trytond/trytond/tests/test_rule.py b/trytond/trytond/tests/test_rule.py
index df5de82beb..a956203e08 100644
--- a/trytond/trytond/tests/test_rule.py
+++ b/trytond/trytond/tests/test_rule.py
@@ -5,7 +5,9 @@ import json
from trytond.model import ModelAccessProxy
from trytond.model.exceptions import AccessError
from trytond.pool import Pool
-from trytond.tests.test_tryton import DBTestCase, with_transaction
+from trytond.tests.test_tryton import (
+ DB_NAME, USER, DBTestCase, with_transaction)
+from trytond.transaction import Transaction
_context = {'_check_access': True}
@@ -14,13 +16,27 @@ class ModelRuleTestCase(DBTestCase):
"Test Model Rule"
module = 'tests'
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ with Transaction().start(DB_NAME, USER):
+ pool = Pool()
+ User = pool.get('res.user')
+
+ user, = User.create([{
+ 'login': 'user',
+ 'administrator': False,
+ }])
+ cls.user_id = user.id
+
@with_transaction(context=_context)
def test_perm_create_without_rule(self):
"Test create without rule"
pool = Pool()
TestRule = pool.get('test.rule')
- test, = TestRule.create([{}])
+ with Transaction().set_user(self.user_id):
+ test, = TestRule.create([{}])
@with_transaction(context=_context)
def test_perm_create_with_rule(self):
@@ -43,7 +59,8 @@ class ModelRuleTestCase(DBTestCase):
}])],
}])
- test, = TestRule.create([{'field': 'bar'}])
+ with Transaction().set_user(self.user_id):
+ test, = TestRule.create([{'field': 'bar'}])
@with_transaction(context=_context)
def test_perm_create_with_rule_fail(self):
@@ -66,8 +83,10 @@ class ModelRuleTestCase(DBTestCase):
}])],
}])
- with self.assertRaisesRegex(AccessError, "Field different from foo"):
- test, = TestRule.create([{'field': 'foo'}])
+ with Transaction().set_user(self.user_id):
+ with self.assertRaisesRegex(
+ AccessError, "Field different from foo"):
+ test, = TestRule.create([{'field': 'foo'}])
@with_transaction(context=_context)
def test_perm_create_with_default_rule_fail(self):
@@ -91,8 +110,10 @@ class ModelRuleTestCase(DBTestCase):
}])],
}])
- with self.assertRaisesRegex(AccessError, "Field different from foo"):
- test, = TestRule.create([{'field': 'foo'}])
+ with Transaction().set_user(self.user_id):
+ with self.assertRaisesRegex(
+ AccessError, "Field different from foo"):
+ test, = TestRule.create([{'field': 'foo'}])
@with_transaction(context=_context)
def test_perm_write_without_rule(self):
@@ -102,7 +123,8 @@ class ModelRuleTestCase(DBTestCase):
test, = TestRule.create([{}])
- TestRule.write([test], {'field': 'foo'})
+ with Transaction().set_user(self.user_id):
+ TestRule.write([test], {'field': 'foo'})
@with_transaction(context=_context)
def test_perm_write_with_rule(self):
@@ -126,7 +148,8 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'test'}])
- TestRule.write([test], {'field': 'bar'})
+ with Transaction().set_user(self.user_id):
+ TestRule.write([test], {'field': 'bar'})
@with_transaction(context=_context)
def test_perm_write_with_rule_fail_before(self):
@@ -150,8 +173,10 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'foo'}])
- with self.assertRaisesRegex(AccessError, "Field different from foo"):
- TestRule.write([test], {'field': 'bar'})
+ with Transaction().set_user(self.user_id):
+ with self.assertRaisesRegex(
+ AccessError, "Field different from foo"):
+ TestRule.write([test], {'field': 'bar'})
@with_transaction(context=_context)
def test_perm_write_with_rule_fail_after(self):
@@ -175,8 +200,10 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'bar'}])
- with self.assertRaisesRegex(AccessError, "Field different from foo"):
- TestRule.write([test], {'field': 'foo'})
+ with Transaction().set_user(self.user_id):
+ with self.assertRaisesRegex(
+ AccessError, "Field different from foo"):
+ TestRule.write([test], {'field': 'foo'})
@with_transaction(context=_context)
def test_perm_delete_without_rule(self):
@@ -186,7 +213,8 @@ class ModelRuleTestCase(DBTestCase):
test, = TestRule.create([{}])
- TestRule.delete([test])
+ with Transaction().set_user(self.user_id):
+ TestRule.delete([test])
@with_transaction(context=_context)
def test_perm_delete_with_rule(self):
@@ -210,7 +238,8 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'bar'}])
- TestRule.delete([test])
+ with Transaction().set_user(self.user_id):
+ TestRule.delete([test])
@with_transaction(context=_context)
def test_perm_delete_with_rule_fail(self):
@@ -234,8 +263,10 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'foo'}])
- with self.assertRaisesRegex(AccessError, "Field different from foo"):
- TestRule.delete([test])
+ with Transaction().set_user(self.user_id):
+ with self.assertRaisesRegex(
+ AccessError, "Field different from foo"):
+ TestRule.delete([test])
@with_transaction(context=_context)
def test_perm_read_without_rule(self):
@@ -245,7 +276,8 @@ class ModelRuleTestCase(DBTestCase):
test, = TestRule.create([{'field': 'foo'}])
- TestRule.read([test.id], ['field'])
+ with Transaction().set_user(self.user_id):
+ TestRule.read([test.id], ['field'])
@with_transaction(context=_context)
def test_perm_read_with_rule(self):
@@ -269,7 +301,8 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'bar'}])
- TestRule.read([test.id], ['field'])
+ with Transaction().set_user(self.user_id):
+ TestRule.read([test.id], ['field'])
@with_transaction(context=_context)
def test_perm_read_with_rule_fail(self):
@@ -293,8 +326,10 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'foo'}])
- with self.assertRaisesRegex(AccessError, "Field different from foo"):
- TestRule.read([test.id], ['field'])
+ with Transaction().set_user(self.user_id):
+ with self.assertRaisesRegex(
+ AccessError, "Field different from foo"):
+ TestRule.read([test.id], ['field'])
@with_transaction(context=_context)
def test_perm_read_with_rule_no_sql_type_fail(self):
@@ -318,8 +353,10 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'foo'}])
- with self.assertRaisesRegex(AccessError, "Field different from foo"):
- TestRule.read([test.id], ['rec_name'])
+ with Transaction().set_user(self.user_id):
+ with self.assertRaisesRegex(
+ AccessError, "Field different from foo"):
+ TestRule.read([test.id], ['rec_name'])
@with_transaction(context=_context)
def test_search_without_rule(self):
@@ -329,7 +366,8 @@ class ModelRuleTestCase(DBTestCase):
test, = TestRule.create([{'field': 'foo'}])
- self.assertListEqual(TestRule.search([]), [test])
+ with Transaction().set_user(self.user_id):
+ self.assertEqual(TestRule.search([]), [test])
@with_transaction(context=_context)
def test_search_with_rule(self):
@@ -353,7 +391,8 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'bar'}])
- self.assertListEqual(TestRule.search([]), [test])
+ with Transaction().set_user(self.user_id):
+ self.assertEqual(TestRule.search([]), [test])
@with_transaction(context=_context)
def test_search_with_rule_match(self):
@@ -377,7 +416,8 @@ class ModelRuleTestCase(DBTestCase):
}])
test, = TestRule.create([{'field': 'foo'}])
- self.assertListEqual(TestRule.search([]), [])
+ with Transaction().set_user(self.user_id):
+ self.assertEqual(TestRule.search([]), [])
@with_transaction(context=_context)
def test_write_field_no_rule(self):
@@ -386,8 +426,9 @@ class ModelRuleTestCase(DBTestCase):
TestRule = pool.get('test.rule')
writable, = TestRule.create([{'field': 'foo'}])
- value, = TestRule.read([writable.id], ['_write'])
- self.assertEqual(value['_write'], True)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([writable.id], ['_write'])
+ self.assertEqual(value['_write'], True)
@with_transaction(context=_context)
def test_write_field_rule_True(self):
@@ -411,8 +452,9 @@ class ModelRuleTestCase(DBTestCase):
}])
writable, = TestRule.create([{'field': 'bar'}])
- value, = TestRule.read([writable.id], ['_write'])
- self.assertEqual(value['_write'], True)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([writable.id], ['_write'])
+ self.assertEqual(value['_write'], True)
@with_transaction(context=_context)
def test_write_field_rule_False(self):
@@ -436,8 +478,9 @@ class ModelRuleTestCase(DBTestCase):
}])
non_writable, = TestRule.create([{'field': 'foo'}])
- value, = TestRule.read([non_writable.id], ['_write'])
- self.assertEqual(value['_write'], False)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([non_writable.id], ['_write'])
+ self.assertEqual(value['_write'], False)
@with_transaction(context=_context)
def test_write_field_relation_rule_True(self):
@@ -463,8 +506,9 @@ class ModelRuleTestCase(DBTestCase):
relation, = TestRuleRelation.create([{'field': 'bar'}])
writable, = TestRule.create([{'relation': relation}])
- value, = TestRule.read([writable.id], ['_write'])
- self.assertEqual(value['_write'], True)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([writable.id], ['_write'])
+ self.assertEqual(value['_write'], True)
@with_transaction(context=_context)
def test_write_field_relation_rule_False(self):
@@ -490,8 +534,9 @@ class ModelRuleTestCase(DBTestCase):
relation, = TestRuleRelation.create([{'field': 'foo'}])
non_writable, = TestRule.create([{'relation': relation}])
- value, = TestRule.read([non_writable.id], ['_write'])
- self.assertEqual(value['_write'], False)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([non_writable.id], ['_write'])
+ self.assertEqual(value['_write'], False)
@with_transaction(context=_context)
def test_delete_field_no_rule(self):
@@ -500,8 +545,9 @@ class ModelRuleTestCase(DBTestCase):
TestRule = pool.get('test.rule')
deletable, = TestRule.create([{'field': 'foo'}])
- value, = TestRule.read([deletable.id], ['_delete'])
- self.assertEqual(value['_delete'], True)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([deletable.id], ['_delete'])
+ self.assertEqual(value['_delete'], True)
@with_transaction(context=_context)
def test_delete_field_rule_True(self):
@@ -525,8 +571,9 @@ class ModelRuleTestCase(DBTestCase):
}])
deletable, = TestRule.create([{'field': 'bar'}])
- value, = TestRule.read([deletable.id], ['_delete'])
- self.assertEqual(value['_delete'], True)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([deletable.id], ['_delete'])
+ self.assertEqual(value['_delete'], True)
@with_transaction(context=_context)
def test_delete_field_rule_False(self):
@@ -550,8 +597,9 @@ class ModelRuleTestCase(DBTestCase):
}])
non_deletable, = TestRule.create([{'field': 'foo'}])
- value, = TestRule.read([non_deletable.id], ['_delete'])
- self.assertEqual(value['_delete'], False)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([non_deletable.id], ['_delete'])
+ self.assertEqual(value['_delete'], False)
@with_transaction(context=_context)
def test_delete_field_relation_rule_True(self):
@@ -577,8 +625,9 @@ class ModelRuleTestCase(DBTestCase):
relation, = TestRuleRelation.create([{'field': 'bar'}])
deletable, = TestRule.create([{'relation': relation}])
- value, = TestRule.read([deletable.id], ['_delete'])
- self.assertEqual(value['_delete'], True)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([deletable.id], ['_delete'])
+ self.assertEqual(value['_delete'], True)
@with_transaction(context=_context)
def test_delete_field_relation_rule_False(self):
@@ -604,8 +653,9 @@ class ModelRuleTestCase(DBTestCase):
relation, = TestRuleRelation.create([{'field': 'foo'}])
non_deletable, = TestRule.create([{'relation': relation}])
- value, = TestRule.read([non_deletable.id], ['_delete'])
- self.assertEqual(value['_delete'], False)
+ with Transaction().set_user(self.user_id):
+ value, = TestRule.read([non_deletable.id], ['_delete'])
+ self.assertEqual(value['_delete'], False)
@with_transaction(context=_context)
def test_model_with_rule(self):
@@ -631,7 +681,8 @@ class ModelRuleTestCase(DBTestCase):
rule, = TestRule.create([{'field': 'bar'}])
test, = TestRuleModel.create([{'rule': rule.id, 'name': 'foo'}])
- TestRuleModel.read([test.id], ['name'])
+ with Transaction().set_user(self.user_id):
+ TestRuleModel.read([test.id], ['name'])
@with_transaction(context=_context)
def test_model_with_rule_fail(self):
@@ -657,8 +708,10 @@ class ModelRuleTestCase(DBTestCase):
rule, = TestRule.create([{'field': 'foo'}])
test, = TestRuleModel.create([{'rule': rule.id, 'name': 'foo'}])
- with self.assertRaisesRegex(AccessError, "Field different from foo"):
- TestRuleModel.read([test.id], ['name'])
+ with Transaction().set_user(self.user_id):
+ with self.assertRaisesRegex(
+ AccessError, "Field different from foo"):
+ TestRuleModel.read([test.id], ['name'])
@with_transaction()
def test_model_access_proxy(self):
@@ -700,5 +753,6 @@ class ModelRuleTestCase(DBTestCase):
}])
record, = TestRule.create([{'field': 'foo'}])
- with self.assertRaises(AccessError):
- ModelAccessProxy(record, {})
+ with Transaction().set_user(self.user_id):
+ with self.assertRaises(AccessError):
+ ModelAccessProxy(record, {})
diff --git a/trytond/trytond/tests/test_tryton.py b/trytond/trytond/tests/test_tryton.py
index 2f9a5c271f..5004009c33 100644
--- a/trytond/trytond/tests/test_tryton.py
+++ b/trytond/trytond/tests/test_tryton.py
@@ -1309,10 +1309,17 @@ def create_db(name=DB_NAME, lang='en'):
language, = Lang.search([('code', '=', lang)])
language.translatable = True
language.save()
- users = User.search([('login', '!=', 'root')])
- User.write(users, {
- 'language': language.id,
- })
+ user = User.__table__()
+ u, = User.create([{
+ 'login': 'admin',
+ 'name': 'Administrator',
+ 'administrator': True,
+ 'language': language.id,
+ }])
+ cursor = Transaction().connection.cursor()
+ # Tests relies on the admin user being the one with a fixed
+ cursor.execute(
+ *user.update([user.id], [USER], where=user.id == u.id))
Module = pool.get('ir.module')
Module.update_list()
else:
diff --git a/trytond/trytond/tests/test_wizard.py b/trytond/trytond/tests/test_wizard.py
index 444b45509b..649fae9fa3 100644
--- a/trytond/trytond/tests/test_wizard.py
+++ b/trytond/trytond/tests/test_wizard.py
@@ -73,7 +73,10 @@ class WizardTestCase(DBTestCase):
ActionWizard = pool.get('ir.action.wizard')
Group = pool.get('res.group')
Wizard = pool.get('test.test_wizard', type='wizard')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
group = Group(name="Test")
group.save()
action_wizard, = ActionWizard.search([
@@ -82,8 +85,9 @@ class WizardTestCase(DBTestCase):
action_wizard.groups = [group]
action_wizard.save()
- with self.assertRaises(AccessError):
- session_id, start_state, end_state = Wizard.create()
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessError):
+ session_id, start_state, end_state = Wizard.create()
@with_transaction()
def test_execute(self):
@@ -121,16 +125,19 @@ class WizardTestCase(DBTestCase):
}}, 'next_')
self.assertEqual(len(result['actions']), 1)
- @with_transaction()
+ @with_transaction(context={'_check_access': True})
def test_execute_without_access(self):
"Execute wizard without access"
pool = Pool()
ActionWizard = pool.get('ir.action.wizard')
Group = pool.get('res.group')
Wizard = pool.get('test.test_wizard', type='wizard')
+ User = pool.get('res.user')
session_id, start_state, end_state = Wizard.create()
+ user = User(login='foo')
+ user.save()
group = Group(name="Test")
group.save()
action_wizard, = ActionWizard.search([
@@ -139,49 +146,61 @@ class WizardTestCase(DBTestCase):
action_wizard.groups = [group]
action_wizard.save()
- with self.assertRaises(AccessError):
- with Transaction().set_context(active_model='test.access'):
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessError):
Wizard.execute(session_id, {}, start_state)
- @with_transaction()
+ @with_transaction(context={'_check_access': True})
def test_execute_without_model_access(self):
"Execute wizard without model access"
pool = Pool()
Wizard = pool.get('test.test_wizard', type='wizard')
ModelAccess = pool.get('ir.model.access')
+ User = pool.get('res.user')
+
+ user = User(login='foo')
+ user.save()
ModelAccess.create([{
'model': 'test.access',
'perm_write': False,
}])
session_id, start_state, end_state = Wizard.create()
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessError):
+ with Transaction().set_context(active_model='test.access'):
+ Wizard.execute(session_id, {}, start_state)
- with self.assertRaises(AccessError):
- with Transaction().set_context(active_model='test.access'):
- Wizard.execute(session_id, {}, start_state)
-
- @with_transaction()
+ @with_transaction(context={'_check_access': True})
def test_execute_without_read_access(self):
"Execute wizard without read access"
pool = Pool()
Wizard = pool.get('test.test_wizard', type='wizard')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
session_id, start_state, end_state = Wizard.create()
- with self.assertRaises(AccessError):
- with Transaction().set_context(
- active_model='test.access', active_id=1):
- Wizard.execute(session_id, {}, start_state)
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessError):
+ with Transaction().set_context(
+ active_model='test.access', active_id=1):
+ Wizard.execute(session_id, {}, start_state)
- @with_transaction()
+ @with_transaction(context={'_check_access': True})
def test_execute_wrong_model(self):
"Execute wizard on wrong model"
pool = Pool()
Wizard = pool.get('test.test_wizard', type='wizard')
+ User = pool.get('res.user')
+ user = User(login='foo')
+ user.save()
session_id, start_state, end_state = Wizard.create()
- with self.assertRaises(AccessError):
- with Transaction().set_context(
- active_model='test.test_wizard.start'):
- Wizard.execute(session_id, {}, start_state)
+ with Transaction().set_user(user.id):
+ with self.assertRaises(AccessError):
+ with Transaction().set_context(
+ active_model='test.test_wizard.start'):
+ Wizard.execute(session_id, {}, start_state)
diff --git a/trytond/trytond/tryton.rnc b/trytond/trytond/tryton.rnc
index 64bf1e8a12..36aaf092e3 100644
--- a/trytond/trytond/tryton.rnc
+++ b/trytond/trytond/tryton.rnc
@@ -33,4 +33,5 @@ attlist.menuitem &= attribute parent { text }?
attlist.menuitem &= attribute action { text }?
attlist.menuitem &= attribute groups { text }?
attlist.menuitem &= attribute active { text }?
+attlist.menuitem &= attribute administration { text }?
start = tryton
diff --git a/trytond/trytond/wizard/wizard.py b/trytond/trytond/wizard/wizard.py
index 43ff98e5fb..eab006e7c9 100644
--- a/trytond/trytond/wizard/wizard.py
+++ b/trytond/trytond/wizard/wizard.py
@@ -259,37 +259,40 @@ class Wizard(URLMixin, PoolBase):
model = context.get('active_model')
if model:
Model = pool.get(model)
- if model and model != 'ir.ui.menu':
- ModelAccess.check(model, 'read')
- models = ActionWizard.get_models(
- cls.__name__, action_id=context.get('action_id'))
- if model and models and model not in models:
- groups = Group.browse(User.get_groups())
- raise AccessError(
- gettext(
- 'ir.msg_access_wizard_model_error',
- wizard=cls.__name__,
- model=model),
- gettext(
- 'ir.msg_context_groups',
- groups=', '.join(g.rec_name for g in groups)))
- groups = set(User.get_groups())
- wizard_groups = ActionWizard.get_groups(cls.__name__,
- action_id=context.get('action_id'))
- if wizard_groups:
- if not groups & wizard_groups:
+ if not User.is_administrator():
+ if model and model != 'ir.ui.menu':
+ ModelAccess.check(model, 'read')
+ models = ActionWizard.get_models(
+ cls.__name__, action_id=context.get('action_id'))
+ if model and models and model not in models:
groups = Group.browse(User.get_groups())
raise AccessError(
gettext(
- 'ir.msg_access_wizard_error',
- wizard=cls.__name__),
+ 'ir.msg_access_wizard_model_error',
+ wizard=cls.__name__,
+ model=model),
gettext(
'ir.msg_context_groups',
groups=', '.join(g.rec_name for g in groups)))
- elif model and model != 'ir.ui.menu' and not transaction.readonly:
- if (not callable(getattr(Model, 'table_query', None))
- or Model.write.__func__ != ModelSQL.write.__func__):
- ModelAccess.check(model, 'write')
+ groups = set(User.get_groups())
+ wizard_groups = ActionWizard.get_groups(cls.__name__,
+ action_id=context.get('action_id'))
+ if wizard_groups:
+ if not groups & wizard_groups:
+ groups = Group.browse(User.get_groups())
+ raise AccessError(
+ gettext(
+ 'ir.msg_access_wizard_error',
+ wizard=cls.__name__),
+ gettext(
+ 'ir.msg_context_groups',
+ groups=', '.join(g.rec_name for g in groups)))
+ elif (model and model != 'ir.ui.menu'
+ and not transaction.readonly):
+ if (not callable(getattr(Model, 'table_query', None))
+ or Model.write.__func__ != ModelSQL.write.__func__
+ ):
+ ModelAccess.check(model, 'write')
if model:
ids = context.get('active_ids') or []