From b9bb433c392ac6ff82259fa0e743eedee5d6eea5 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Mon, 8 Aug 2022 17:31:16 +0200 Subject: [PATCH] =?UTF-8?q?book/line:=20berechtigungen=20f=C3=BCr=20owner,?= =?UTF-8?q?=20beobachter,=20bearbeiter=20+=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- book.py | 15 ++++ book.xml | 44 ++++++++++ line.xml | 68 +++++++++++++- locale/de.po | 49 ++++++++++- tests/test_book.py | 169 +++++++++++++++++++++++++++++++++++ tests/test_line.py | 214 ++++++++++++++++++++++++++++++++++++++++++++- view/book_form.xml | 10 +++ view/book_list.xml | 3 + wizard_openline.py | 15 ++++ 9 files changed, 584 insertions(+), 3 deletions(-) diff --git a/book.py b/book.py index c80e7e8..bae7cfd 100644 --- a/book.py +++ b/book.py @@ -7,6 +7,7 @@ from trytond.model import Workflow, ModelView, ModelSQL, fields, Check from trytond.pyson import Eval from trytond.exceptions import UserError from trytond.i18n import gettext +from trytond.transaction import Transaction STATES = { @@ -30,6 +31,14 @@ class Book(Workflow, ModelSQL, ModelView): btype = fields.Many2One(string='Type', required=True, model_name='cashbook.type', ondelete='RESTRICT', states=STATES, depends=DEPENDS) + owner = fields.Many2One(string='Owner', required=True, select=True, + model_name='res.user', ondelete='SET NULL') + reviewer = fields.Many2One(string='Reviewer', select=True, + help='Group of users who have write access to the cashbook.', + model_name='res.group', ondelete='SET NULL') + observer = fields.Many2One(string='Observer', select=True, + help='Group of users who have read-only access to the cashbook.', + model_name='res.group', ondelete='SET NULL') lines = fields.One2Many(string='Lines', field='cashbook', model_name='cashbook.line', states=STATES, depends=DEPENDS) @@ -71,6 +80,12 @@ class Book(Workflow, ModelSQL, ModelView): def default_state(cls): return 'open' + @classmethod + def default_owner(cls): + """ default: current user + """ + return Transaction().user + @classmethod @ModelView.button @Workflow.transition('open') diff --git a/book.xml b/book.xml index 72b73ca..ea9d71f 100644 --- a/book.xml +++ b/book.xml @@ -63,6 +63,50 @@ full copyright notices and license terms. --> + + + + Administrators: Cashbook read/write + + + + + + + + + + + + + + + + + + + + Owners, observers and reviewers: Cashbook read + + + + + + + + + + + + + + + + wfopen diff --git a/line.xml b/line.xml index b8eed11..212b091 100644 --- a/line.xml +++ b/line.xml @@ -72,7 +72,7 @@ full copyright notices and license terms. --> - + @@ -82,6 +82,72 @@ full copyright notices and license terms. --> + + + + Administrators: Cashbook line read/write + + + + + + + + + + + + + + + + + + + + Owners and reviewers: Cashbook line write + + + + + + + + + + + + + + + + + + + + Observer: Cashbook line read + + + + + + + + + + + + + + + + + wfedit diff --git a/locale/de.po b/locale/de.po index ccd7acf..5372a08 100644 --- a/locale/de.po +++ b/locale/de.po @@ -18,7 +18,6 @@ msgctxt "model:ir.message,text:msg_line_wrong_state_value" msgid "Invalid value of the field 'Status', allowed: edit, check, done." msgstr "Ungültiger Wert des Feldes 'Status', erlaubt: edit, check, done." - msgctxt "model:ir.message,text:msg_book_wrong_state_value" msgid "Invalid value of the field 'Status', allowed: open, closed, archive." msgstr "Ungültiger Wert des Feldes 'Status', erlaubt: open, closed, archive." @@ -57,6 +56,30 @@ msgid "Cashbook - WF - Done" msgstr "Kassenbuch - WF - Fertig" +################# +# ir.rule.group # +################# +msgctxt "model:ir.rule.group,name:rg_book_read" +msgid "Owners, observers and reviewers: Cashbook read" +msgstr "Eigentümer, Beobachter und Bearbeiter: Kassenbuch lesen" + +msgctxt "model:ir.rule.group,name:rg_book_write_adm" +msgid "Administrators: Cashbook read/write" +msgstr "Administratoren: Kassenbuch bearbeiten" + +msgctxt "model:ir.rule.group,name:rg_line_write_adm" +msgid "Administrators: Cashbook line read/write" +msgstr "Administratoren: Kassenbuchzeile bearbeiten" + +msgctxt "model:ir.rule.group,name:rg_line_write" +msgid "Owners and reviewers: Cashbook line write" +msgstr "Eigentümer und Bearbeiter: Kassenbuchzeile bearbeiten" + +msgctxt "model:ir.rule.group,name:rg_line_read" +msgid "Observer: Cashbook line read" +msgstr "Beobachter: Kassenbuchzeile lesen" + + ############## # ir.ui.menu # ############## @@ -136,6 +159,10 @@ msgctxt "model:cashbook.book,name:" msgid "Cashbook" msgstr "Kassenbuch" +msgctxt "view:cashbook.book:" +msgid "Owner & Authorizeds" +msgstr "Eigentümer & Autorisierte" + msgctxt "field:cashbook.book,name:" msgid "Name" msgstr "Name" @@ -160,6 +187,26 @@ msgctxt "selection:cashbook.book,state:" msgid "Archive" msgstr "Archiv" +msgctxt "field:cashbook.book,owner:" +msgid "Owner" +msgstr "Eigentümer" + +msgctxt "field:cashbook.book,reviewer:" +msgid "Reviewer" +msgstr "Bearbeiter" + +msgctxt "help:cashbook.book,reviewer:" +msgid "Group of users who have write access to the cashbook." +msgstr "Gruppe von Benutzern, die Schreibzugriff auf das Kassenbuch haben." + +msgctxt "field:cashbook.book,observer:" +msgid "Observer" +msgstr "Betrachter" + +msgctxt "help:cashbook.book,observer:" +msgid "Group of users who have read-only access to the cashbook." +msgstr "Gruppe von Benutzern, die nur Lesezugriff auf das Kassenbuch haben." + ################# # cashbook.line # diff --git a/tests/test_book.py b/tests/test_book.py index c5cd54a..a7eaf06 100644 --- a/tests/test_book.py +++ b/tests/test_book.py @@ -173,4 +173,173 @@ class BookTestCase(ModuleTestCase): }, ]) + @with_transaction() + def test_book_permission_owner(self): + """ create book + 2x users, add users to group, check access + """ + pool = Pool() + ResUser = pool.get('res.user') + ResGroup = pool.get('res.group') + Book = pool.get('cashbook.book') + Types = pool.get('cashbook.type') + + types, = Types.search([('short', '=', 'CAS')]) + grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) + usr_lst = ResUser.create([{ + 'login': 'frida', + 'name': 'Frida', + 'groups': [('add', [grp_cashbook.id])], + }, { + 'login': 'diego', + 'name': 'Diego', + 'groups': [('add', [grp_cashbook.id])], + }]) + self.assertEqual(len(usr_lst), 2) + self.assertEqual(usr_lst[0].name, 'Frida') + self.assertEqual(usr_lst[1].name, 'Diego') + + book, = Book.create([{ + 'name': 'Fridas book', + 'owner': usr_lst[0].id, + 'btype': types.id, + }]) + self.assertEqual(book.rec_name, 'Fridas book'), + self.assertEqual(book.owner.rec_name, 'Frida'), + + with Transaction().set_context({ + '_check_access': True, + }): + # change to user 'diego' , try access + with Transaction().set_user(usr_lst[1].id): + books = Book.search([]) + self.assertEqual(len(books), 0) + + # change to user 'frida' read/write book + with Transaction().set_user(usr_lst[0].id): + books = Book.search([]) + self.assertEqual(len(books), 1) + self.assertEqual(books[0].rec_name, 'Fridas book') + + self.assertRaisesRegex(UserError, + 'You are not allowed to access "Cashbook".', + Book.write, + *[ + books, + { + 'name': 'Book2', + }, + ]) + + @with_transaction() + def test_book_permission_reviewer(self): + """ create book + 2x users + 1x reviewer-group, add users to group, check access + """ + pool = Pool() + ResUser = pool.get('res.user') + ResGroup = pool.get('res.group') + Book = pool.get('cashbook.book') + Types = pool.get('cashbook.type') + + types, = Types.search([('short', '=', 'CAS')]) + grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) + grp_reviewer, = ResGroup.create([{ + 'name': 'Cashbook Reviewer', + }]) + + usr_lst = ResUser.create([{ + 'login': 'frida', + 'name': 'Frida', + 'groups': [('add', [grp_cashbook.id])], + }, { + 'login': 'diego', + 'name': 'Diego', + 'groups': [('add', [grp_cashbook.id, grp_reviewer.id])], + }]) + self.assertEqual(len(usr_lst), 2) + self.assertEqual(usr_lst[0].name, 'Frida') + self.assertEqual(usr_lst[1].name, 'Diego') + + # create cashbook + # add reviewer-group to allow read for users in group + book, = Book.create([{ + 'name': 'Fridas book', + 'owner': usr_lst[0].id, + 'reviewer': grp_reviewer.id, + 'btype': types.id, + }]) + self.assertEqual(book.rec_name, 'Fridas book'), + self.assertEqual(book.owner.rec_name, 'Frida'), + + with Transaction().set_context({ + '_check_access': True, + }): + # change to user 'diego' , try access + with Transaction().set_user(usr_lst[1].id): + books = Book.search([]) + self.assertEqual(len(books), 1) + self.assertEqual(len(books[0].reviewer.users), 1) + self.assertEqual(books[0].reviewer.users[0].rec_name, 'Diego') + + # change to user 'frida' read/write book + with Transaction().set_user(usr_lst[0].id): + books = Book.search([]) + self.assertEqual(len(books), 1) + self.assertEqual(books[0].rec_name, 'Fridas book') + + @with_transaction() + def test_book_permission_observer(self): + """ create book + 2x users + 1x observer-group, add users to group, check access + """ + pool = Pool() + ResUser = pool.get('res.user') + ResGroup = pool.get('res.group') + Book = pool.get('cashbook.book') + Types = pool.get('cashbook.type') + + types, = Types.search([('short', '=', 'CAS')]) + grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) + grp_observer, = ResGroup.create([{ + 'name': 'Cashbook Observer', + }]) + + usr_lst = ResUser.create([{ + 'login': 'frida', + 'name': 'Frida', + 'groups': [('add', [grp_cashbook.id])], + }, { + 'login': 'diego', + 'name': 'Diego', + 'groups': [('add', [grp_cashbook.id, grp_observer.id])], + }]) + self.assertEqual(len(usr_lst), 2) + self.assertEqual(usr_lst[0].name, 'Frida') + self.assertEqual(usr_lst[1].name, 'Diego') + + # create cashbook + # add observer-group to allow read for users in group + book, = Book.create([{ + 'name': 'Fridas book', + 'owner': usr_lst[0].id, + 'observer': grp_observer.id, + 'btype': types.id, + }]) + self.assertEqual(book.rec_name, 'Fridas book'), + self.assertEqual(book.owner.rec_name, 'Frida'), + + with Transaction().set_context({ + '_check_access': True, + }): + # change to user 'diego' , try access + with Transaction().set_user(usr_lst[1].id): + books = Book.search([]) + self.assertEqual(len(books), 1) + self.assertEqual(len(books[0].observer.users), 1) + self.assertEqual(books[0].observer.users[0].rec_name, 'Diego') + + # change to user 'frida' read/write book + with Transaction().set_user(usr_lst[0].id): + books = Book.search([]) + self.assertEqual(len(books), 1) + self.assertEqual(books[0].rec_name, 'Fridas book') + # end BookTestCase diff --git a/tests/test_line.py b/tests/test_line.py index 46b603b..85ba07c 100644 --- a/tests/test_line.py +++ b/tests/test_line.py @@ -150,4 +150,216 @@ class LineTestCase(ModuleTestCase): Lines.delete, [book.lines[0]]) -# end BookTestCase + @with_transaction() + def test_line_permission_owner(self): + """ create book+line + 2x users, add users to group, check access + """ + pool = Pool() + ResUser = pool.get('res.user') + ResGroup = pool.get('res.group') + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + Types = pool.get('cashbook.type') + + types, = Types.search([('short', '=', 'CAS')]) + grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) + usr_lst = ResUser.create([{ + 'login': 'frida', + 'name': 'Frida', + 'groups': [('add', [grp_cashbook.id])], + }, { + 'login': 'diego', + 'name': 'Diego', + 'groups': [('add', [grp_cashbook.id])], + }]) + self.assertEqual(len(usr_lst), 2) + self.assertEqual(usr_lst[0].name, 'Frida') + self.assertEqual(usr_lst[1].name, 'Diego') + + book, = Book.create([{ + 'name': 'Fridas book', + 'owner': usr_lst[0].id, + 'btype': types.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Test 1', + }])], + }]) + self.assertEqual(book.rec_name, 'Fridas book'), + self.assertEqual(book.owner.rec_name, 'Frida'), + + with Transaction().set_context({ + '_check_access': True, + }): + # change to user 'diego' , try access + with Transaction().set_user(usr_lst[1].id): + lines = Line.search([]) + self.assertEqual(len(lines), 0) + + # change to user 'frida' read/write book + with Transaction().set_user(usr_lst[0].id): + lines = Line.search([]) + self.assertEqual(len(lines), 1) + self.assertEqual(lines[0].cashbook.rec_name, 'Fridas book') + self.assertEqual(lines[0].rec_name, '05/01/2022 Test 1') + + Line.write(*[ + lines, + { + 'description': 'Test 2', + }]) + self.assertEqual(lines[0].rec_name, '05/01/2022 Test 2') + + @with_transaction() + def test_line_permission_reviewer(self): + """ create book+line + 2x users + 1x reviewer-group, add users to group, check access + """ + pool = Pool() + ResUser = pool.get('res.user') + ResGroup = pool.get('res.group') + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + Types = pool.get('cashbook.type') + + types, = Types.search([('short', '=', 'CAS')]) + grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) + grp_reviewer, = ResGroup.create([{ + 'name': 'Cashbook Reviewer', + }]) + + usr_lst = ResUser.create([{ + 'login': 'frida', + 'name': 'Frida', + 'groups': [('add', [grp_cashbook.id])], + }, { + 'login': 'diego', + 'name': 'Diego', + 'groups': [('add', [grp_cashbook.id, grp_reviewer.id])], + }]) + self.assertEqual(len(usr_lst), 2) + self.assertEqual(usr_lst[0].name, 'Frida') + self.assertEqual(usr_lst[1].name, 'Diego') + + # create cashbook + # add reviewer-group to allow write for users in group + book, = Book.create([{ + 'name': 'Fridas book', + 'owner': usr_lst[0].id, + 'reviewer': grp_reviewer.id, + 'btype': types.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Test 1', + }])], + }]) + self.assertEqual(book.rec_name, 'Fridas book'), + self.assertEqual(book.owner.rec_name, 'Frida'), + + with Transaction().set_context({ + '_check_access': True, + }): + # change to user 'diego' , try access + with Transaction().set_user(usr_lst[1].id): + lines = Line.search([]) + self.assertEqual(len(lines), 1) + self.assertEqual(len(lines[0].cashbook.reviewer.users), 1) + self.assertEqual(lines[0].cashbook.reviewer.users[0].rec_name, 'Diego') + self.assertEqual(lines[0].rec_name, '05/01/2022 Test 1') + Line.write(*[ + lines, + { + 'description': 'Test 2', + }]) + self.assertEqual(lines[0].rec_name, '05/01/2022 Test 2') + + # change to user 'frida' read/write line + with Transaction().set_user(usr_lst[0].id): + lines = Line.search([]) + self.assertEqual(len(lines), 1) + self.assertEqual(lines[0].rec_name, '05/01/2022 Test 2') + Line.write(*[ + lines, + { + 'description': 'Test 3', + }]) + self.assertEqual(lines[0].rec_name, '05/01/2022 Test 3') + + @with_transaction() + def test_line_permission_observer(self): + """ create book+line + 2x users + 1x observer-group, add users to group, check access + """ + pool = Pool() + ResUser = pool.get('res.user') + ResGroup = pool.get('res.group') + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + Types = pool.get('cashbook.type') + + types, = Types.search([('short', '=', 'CAS')]) + grp_cashbook, = ResGroup.search([('name', '=', 'Cashbook')]) + grp_observer, = ResGroup.create([{ + 'name': 'Cashbook Observer', + }]) + + usr_lst = ResUser.create([{ + 'login': 'frida', + 'name': 'Frida', + 'groups': [('add', [grp_cashbook.id])], + }, { + 'login': 'diego', + 'name': 'Diego', + 'groups': [('add', [grp_cashbook.id, grp_observer.id])], + }]) + self.assertEqual(len(usr_lst), 2) + self.assertEqual(usr_lst[0].name, 'Frida') + self.assertEqual(usr_lst[1].name, 'Diego') + + # create cashbook + # add reviewer-group to allow write for users in group + book, = Book.create([{ + 'name': 'Fridas book', + 'owner': usr_lst[0].id, + 'observer': grp_observer.id, + 'btype': types.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Test 1', + }])], + }]) + self.assertEqual(book.rec_name, 'Fridas book'), + self.assertEqual(book.owner.rec_name, 'Frida'), + + with Transaction().set_context({ + '_check_access': True, + }): + # change to user 'diego' , try access + with Transaction().set_user(usr_lst[1].id): + lines = Line.search([]) + self.assertEqual(len(lines), 1) + self.assertEqual(len(lines[0].cashbook.observer.users), 1) + self.assertEqual(lines[0].cashbook.observer.users[0].rec_name, 'Diego') + self.assertEqual(lines[0].rec_name, '05/01/2022 Test 1') + + self.assertRaisesRegex(UserError, + 'You are not allowed to write to records "[0-9]{1,}" of "Cashbook Line" because of at least one of these rules:\nOwners and reviewers: Cashbook line write - ', + Line.write, + *[ + lines, + { + 'description': 'Test 2', + }, + ]) + + # change to user 'frida' read/write line + with Transaction().set_user(usr_lst[0].id): + lines = Line.search([]) + self.assertEqual(len(lines), 1) + self.assertEqual(lines[0].rec_name, '05/01/2022 Test 1') + Line.write(*[ + lines, + { + 'description': 'Test 2', + }]) + self.assertEqual(lines[0].rec_name, '05/01/2022 Test 2') + +# end LineTestCase diff --git a/view/book_form.xml b/view/book_form.xml index f172268..5035ecb 100644 --- a/view/book_form.xml +++ b/view/book_form.xml @@ -16,4 +16,14 @@ full copyright notices and license terms. -->