From 305301a53efe9b54bf9b8ad2ecd215983cc346c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Sun, 13 Sep 2026 17:28:32 +0200 Subject: [PATCH] Replace the administration group by a flag on the user --- doc/migration.rst | 17 + modules/account/account.xml | 8 - modules/account/move.xml | 10 - modules/account_asset/asset.xml | 5 - modules/account_budget/account.xml | 4 - modules/account_credit_limit/account.xml | 5 - modules/account_credit_limit/party.py | 3 +- modules/account_dunning/dunning.xml | 4 - modules/account_invoice/invoice.xml | 5 - modules/account_payment/payment.xml | 20 - modules/account_payment_check/account.xml | 4 - modules/account_payment_sepa/payment.xml | 5 - modules/account_product/account.xml | 4 - modules/account_statement/statement.xml | 4 - modules/account_stock_landed_cost/account.xml | 5 - .../account_stock_shipment_cost/account.xml | 4 - modules/analytic_account/analytic_account.xml | 5 - modules/attendance/attendance.xml | 4 - modules/bank/bank.xml | 4 - modules/carrier/carrier.xml | 4 - modules/commission/commission.xml | 8 - modules/company/company.xml | 9 - modules/country/country.xml | 45 --- modules/currency/currency.xml | 4 - modules/customs/customs.xml | 5 - modules/document_incoming/document.xml | 9 - modules/edocument_peppol/edocument.xml | 36 -- modules/inbound_email/inbound_email.xml | 16 - modules/marketing/marketing.xml | 4 - modules/notification_email/notification.xml | 8 - modules/party/party.xml | 10 - modules/product/category.xml | 4 - modules/product/product.xml | 14 - modules/production/bom.xml | 4 - modules/production/production.xml | 16 - modules/project/project.xml | 4 - modules/project/work.xml | 4 - modules/project_invoice/project.xml | 4 - modules/purchase/purchase.xml | 13 - modules/purchase_amendment/purchase.xml | 4 - .../purchase_blanket_agreement/purchase.xml | 8 - modules/purchase_request/purchase_request.xml | 6 - .../purchase_request_quotation/purchase.xml | 6 - modules/purchase_requisition/purchase.xml | 16 - modules/quality/quality.xml | 17 - modules/sale/sale.xml | 13 - modules/sale_amendment/sale.xml | 4 - modules/sale_blanket_agreement/sale.xml | 8 - modules/sale_complaint/sale.xml | 5 - modules/sale_gift_card/sale.xml | 4 - modules/sale_opportunity/opportunity.xml | 9 - modules/sale_point/sale.xml | 15 - modules/sale_rental/sale.xml | 4 - modules/sale_stock_quantity/sale.py | 2 +- modules/sale_stock_quantity/sale.xml | 5 - modules/sale_subscription/configuration.xml | 5 - modules/sale_supply_drop_shipment/stock.xml | 5 - modules/stock/inventory.xml | 5 - modules/stock/shipment.xml | 25 -- modules/stock/stock.xml | 19 - modules/stock_forecast/forecast.xml | 5 - modules/stock_location_move/stock.xml | 5 - modules/stock_lot/ir.xml | 4 - modules/stock_lot_sled/stock.xml | 5 - modules/stock_package/stock.xml | 5 - modules/timesheet/timesheet.xml | 5 - modules/user_role/res.py | 24 +- modules/user_role/res.xml | 16 - .../web_shop_product_data_feed/product.xml | 16 - modules/web_shop_shopify/stock.xml | 8 - modules/web_shop_shopify/web.xml | 9 - modules/web_shortener/web.xml | 8 - modules/web_user/user.xml | 8 - proteus/proteus/tests/test_model.py | 21 +- trytond/CHANGELOG | 1 + trytond/doc/topics/modules/index.rst | 5 - trytond/trytond/admin.py | 52 ++- trytond/trytond/backend/postgresql/init.sql | 12 +- trytond/trytond/backend/sqlite/init.sql | 16 +- trytond/trytond/commandline.py | 9 +- trytond/trytond/convert.py | 5 +- trytond/trytond/ir/action.xml | 10 +- trytond/trytond/ir/chat.xml | 12 - trytond/trytond/ir/cron.xml | 12 - trytond/trytond/ir/email.xml | 8 - trytond/trytond/ir/error.xml | 8 - trytond/trytond/ir/export.xml | 8 - trytond/trytond/ir/lang.xml | 12 - trytond/trytond/ir/message.xml | 8 - trytond/trytond/ir/model.py | 124 ++++--- trytond/trytond/ir/model.xml | 8 - trytond/trytond/ir/module.xml | 32 -- trytond/trytond/ir/resource.py | 3 +- trytond/trytond/ir/rule.py | 19 +- trytond/trytond/ir/rule.xml | 8 - trytond/trytond/ir/sequence.xml | 12 +- trytond/trytond/ir/translation.xml | 16 - trytond/trytond/ir/trigger.xml | 8 - trytond/trytond/ir/ui/icon.xml | 8 - trytond/trytond/ir/ui/menu.py | 13 +- trytond/trytond/ir/ui/menu.xml | 14 +- trytond/trytond/ir/ui/ui.xml | 5 +- trytond/trytond/ir/ui/view.xml | 28 -- trytond/trytond/ir/view/ui_menu_form.xml | 5 +- trytond/trytond/model/modelview.py | 3 +- trytond/trytond/report/report.py | 27 +- trytond/trytond/res/group.xml | 11 - trytond/trytond/res/notification.xml | 13 - trytond/trytond/res/user.py | 46 ++- trytond/trytond/res/user.xml | 26 +- trytond/trytond/res/view/user_form.xml | 15 +- .../res/view/user_form_preferences.xml | 7 +- trytond/trytond/res/view/user_list.xml | 1 + trytond/trytond/tests/sequence.xml | 5 - trytond/trytond/tests/test_access.py | 342 ++++++++++++------ trytond/trytond/tests/test_copy.py | 38 +- trytond/trytond/tests/test_exportdata.py | 28 +- trytond/trytond/tests/test_ir.py | 18 +- trytond/trytond/tests/test_model.py | 9 +- trytond/trytond/tests/test_modelview.py | 270 +++++++++----- trytond/trytond/tests/test_report.py | 28 +- trytond/trytond/tests/test_resource.py | 28 +- trytond/trytond/tests/test_rest.py | 62 ++-- trytond/trytond/tests/test_rule.py | 156 +++++--- trytond/trytond/tests/test_tryton.py | 15 +- trytond/trytond/tests/test_wizard.py | 59 ++- trytond/trytond/tryton.rnc | 1 + trytond/trytond/wizard/wizard.py | 53 +-- 128 files changed, 1045 insertions(+), 1362 deletions(-) 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 - - - - @@ -155,12 +145,6 @@ this repository contains the full copyright notices and license terms. id="sequence_type_purchase_requisition"> 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. --> - - ir.ui.menu - - - - - - User in groups @@ -82,11 +74,7 @@ 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/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 []