feat: add basic Roles view
can't edit user/role/perm mappings yet, just minimal CRUD
This commit is contained in:
		
							parent
							
								
									eac3b81918
								
							
						
					
					
						commit
						7ad6a9d5a0
					
				
					 8 changed files with 178 additions and 5 deletions
				
			
		| 
						 | 
					@ -29,5 +29,6 @@
 | 
				
			||||||
   views.essential
 | 
					   views.essential
 | 
				
			||||||
   views.master
 | 
					   views.master
 | 
				
			||||||
   views.people
 | 
					   views.people
 | 
				
			||||||
 | 
					   views.roles
 | 
				
			||||||
   views.settings
 | 
					   views.settings
 | 
				
			||||||
   views.users
 | 
					   views.users
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								docs/api/wuttaweb/views.roles.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								docs/api/wuttaweb/views.roles.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``wuttaweb.views.roles``
 | 
				
			||||||
 | 
					========================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: wuttaweb.views.roles
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
| 
						 | 
					@ -152,6 +152,11 @@ class MenuHandler(GenericHandler):
 | 
				
			||||||
                    'route': 'users',
 | 
					                    'route': 'users',
 | 
				
			||||||
                    'perm': 'users.list',
 | 
					                    'perm': 'users.list',
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    'title': "Roles",
 | 
				
			||||||
 | 
					                    'route': 'roles',
 | 
				
			||||||
 | 
					                    'perm': 'roles.list',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                {'type': 'sep'},
 | 
					                {'type': 'sep'},
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    'title': "App Info",
 | 
					                    'title': "App Info",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,9 @@
 | 
				
			||||||
        <b-field horizontal label="App Title">
 | 
					        <b-field horizontal label="App Title">
 | 
				
			||||||
          <span>${app.get_title()}</span>
 | 
					          <span>${app.get_title()}</span>
 | 
				
			||||||
        </b-field>
 | 
					        </b-field>
 | 
				
			||||||
 | 
					        <b-field horizontal label="Production Mode">
 | 
				
			||||||
 | 
					          <span>${config.production()}</span>
 | 
				
			||||||
 | 
					        </b-field>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </nav>
 | 
					  </nav>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,6 +33,7 @@ That will in turn include the following modules:
 | 
				
			||||||
* :mod:`wuttaweb.views.common`
 | 
					* :mod:`wuttaweb.views.common`
 | 
				
			||||||
* :mod:`wuttaweb.views.settings`
 | 
					* :mod:`wuttaweb.views.settings`
 | 
				
			||||||
* :mod:`wuttaweb.views.people`
 | 
					* :mod:`wuttaweb.views.people`
 | 
				
			||||||
 | 
					* :mod:`wuttaweb.views.roles`
 | 
				
			||||||
* :mod:`wuttaweb.views.users`
 | 
					* :mod:`wuttaweb.views.users`
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,6 +45,7 @@ def defaults(config, **kwargs):
 | 
				
			||||||
    config.include(mod('wuttaweb.views.common'))
 | 
					    config.include(mod('wuttaweb.views.common'))
 | 
				
			||||||
    config.include(mod('wuttaweb.views.settings'))
 | 
					    config.include(mod('wuttaweb.views.settings'))
 | 
				
			||||||
    config.include(mod('wuttaweb.views.people'))
 | 
					    config.include(mod('wuttaweb.views.people'))
 | 
				
			||||||
 | 
					    config.include(mod('wuttaweb.views.roles'))
 | 
				
			||||||
    config.include(mod('wuttaweb.views.users'))
 | 
					    config.include(mod('wuttaweb.views.users'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										100
									
								
								src/wuttaweb/views/roles.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/wuttaweb/views/roles.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,100 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8; -*-
 | 
				
			||||||
 | 
					################################################################################
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  wuttaweb -- Web App for Wutta Framework
 | 
				
			||||||
 | 
					#  Copyright © 2024 Lance Edgar
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  This file is part of Wutta Framework.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Wutta Framework is free software: you can redistribute it and/or modify it
 | 
				
			||||||
 | 
					#  under the terms of the GNU General Public License as published by the Free
 | 
				
			||||||
 | 
					#  Software Foundation, either version 3 of the License, or (at your option) any
 | 
				
			||||||
 | 
					#  later version.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Wutta Framework is distributed in the hope that it will be useful, but
 | 
				
			||||||
 | 
					#  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | 
				
			||||||
 | 
					#  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | 
				
			||||||
 | 
					#  more details.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  You should have received a copy of the GNU General Public License along with
 | 
				
			||||||
 | 
					#  Wutta Framework.  If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					################################################################################
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Views for roles
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from wuttjamaican.db.model import Role
 | 
				
			||||||
 | 
					from wuttaweb.views import MasterView
 | 
				
			||||||
 | 
					from wuttaweb.db import Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RoleView(MasterView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Master view for roles.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Notable URLs provided by this class:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    * ``/roles/``
 | 
				
			||||||
 | 
					    * ``/roles/new``
 | 
				
			||||||
 | 
					    * ``/roles/XXX``
 | 
				
			||||||
 | 
					    * ``/roles/XXX/edit``
 | 
				
			||||||
 | 
					    * ``/roles/XXX/delete``
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    model_class = Role
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    grid_columns = [
 | 
				
			||||||
 | 
					        'name',
 | 
				
			||||||
 | 
					        'notes',
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO: master should handle this, possibly via configure_form()
 | 
				
			||||||
 | 
					    def get_query(self, session=None):
 | 
				
			||||||
 | 
					        """ """
 | 
				
			||||||
 | 
					        model = self.app.model
 | 
				
			||||||
 | 
					        query = super().get_query(session=session)
 | 
				
			||||||
 | 
					        return query.order_by(model.Role.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def configure_grid(self, g):
 | 
				
			||||||
 | 
					        """ """
 | 
				
			||||||
 | 
					        super().configure_grid(g)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # name
 | 
				
			||||||
 | 
					        g.set_link('name')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def configure_form(self, f):
 | 
				
			||||||
 | 
					        """ """
 | 
				
			||||||
 | 
					        super().configure_form(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # never show these
 | 
				
			||||||
 | 
					        f.remove('permission_refs',
 | 
				
			||||||
 | 
					                 'user_refs')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # name
 | 
				
			||||||
 | 
					        f.set_validator('name', self.unique_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def unique_name(self, node, value):
 | 
				
			||||||
 | 
					        """ """
 | 
				
			||||||
 | 
					        model = self.app.model
 | 
				
			||||||
 | 
					        session = Session()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        query = session.query(model.Role)\
 | 
				
			||||||
 | 
					                       .filter(model.Role.name == value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.editing:
 | 
				
			||||||
 | 
					            uuid = self.request.matchdict['uuid']
 | 
				
			||||||
 | 
					            query = query.filter(model.Role.uuid != uuid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if query.count():
 | 
				
			||||||
 | 
					            node.raise_invalid("Name must be unique")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def defaults(config, **kwargs):
 | 
				
			||||||
 | 
					    base = globals()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    RoleView = kwargs.get('RoleView', base['RoleView'])
 | 
				
			||||||
 | 
					    RoleView.defaults(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def includeme(config):
 | 
				
			||||||
 | 
					    defaults(config)
 | 
				
			||||||
							
								
								
									
										57
									
								
								tests/views/test_roles.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tests/views/test_roles.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8; -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from unittest.mock import patch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sqlalchemy import orm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import colander
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from wuttaweb.views import roles as mod
 | 
				
			||||||
 | 
					from tests.util import WebTestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestRoleView(WebTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def make_view(self):
 | 
				
			||||||
 | 
					        return mod.RoleView(self.request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_query(self):
 | 
				
			||||||
 | 
					        view = self.make_view()
 | 
				
			||||||
 | 
					        query = view.get_query(session=self.session)
 | 
				
			||||||
 | 
					        self.assertIsInstance(query, orm.Query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_configure_grid(self):
 | 
				
			||||||
 | 
					        model = self.app.model
 | 
				
			||||||
 | 
					        view = self.make_view()
 | 
				
			||||||
 | 
					        grid = view.make_grid(model_class=model.Role)
 | 
				
			||||||
 | 
					        self.assertFalse(grid.is_linked('name'))
 | 
				
			||||||
 | 
					        view.configure_grid(grid)
 | 
				
			||||||
 | 
					        self.assertTrue(grid.is_linked('name'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_configure_form(self):
 | 
				
			||||||
 | 
					        model = self.app.model
 | 
				
			||||||
 | 
					        view = self.make_view()
 | 
				
			||||||
 | 
					        form = view.make_form(model_class=model.Person)
 | 
				
			||||||
 | 
					        self.assertNotIn('name', form.validators)
 | 
				
			||||||
 | 
					        view.configure_form(form)
 | 
				
			||||||
 | 
					        self.assertIsNotNone(form.validators['name'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_unique_name(self):
 | 
				
			||||||
 | 
					        model = self.app.model
 | 
				
			||||||
 | 
					        view = self.make_view()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        role = model.Role(name='Foo')
 | 
				
			||||||
 | 
					        self.session.add(role)
 | 
				
			||||||
 | 
					        self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with patch.object(mod, 'Session', return_value=self.session):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # invalid if same name in data
 | 
				
			||||||
 | 
					            node = colander.SchemaNode(colander.String(), name='name')
 | 
				
			||||||
 | 
					            self.assertRaises(colander.Invalid, view.unique_name, node, 'Foo')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # but not if name belongs to current role
 | 
				
			||||||
 | 
					            view.editing = True
 | 
				
			||||||
 | 
					            self.request.matchdict = {'uuid': role.uuid}
 | 
				
			||||||
 | 
					            node = colander.SchemaNode(colander.String(), name='name')
 | 
				
			||||||
 | 
					            self.assertIsNone(view.unique_name(node, 'Foo'))
 | 
				
			||||||
| 
						 | 
					@ -5,16 +5,15 @@ from unittest.mock import patch
 | 
				
			||||||
from sqlalchemy import orm
 | 
					from sqlalchemy import orm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import colander
 | 
					import colander
 | 
				
			||||||
from pyramid.httpexceptions import HTTPNotFound
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from wuttaweb.views import users
 | 
					from wuttaweb.views import users as mod
 | 
				
			||||||
from tests.util import WebTestCase
 | 
					from tests.util import WebTestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestPersonView(WebTestCase):
 | 
					class TestUserView(WebTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def make_view(self):
 | 
					    def make_view(self):
 | 
				
			||||||
        return users.UserView(self.request)
 | 
					        return mod.UserView(self.request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_get_query(self):
 | 
					    def test_get_query(self):
 | 
				
			||||||
        view = self.make_view()
 | 
					        view = self.make_view()
 | 
				
			||||||
| 
						 | 
					@ -45,7 +44,7 @@ class TestPersonView(WebTestCase):
 | 
				
			||||||
        self.session.add(user)
 | 
					        self.session.add(user)
 | 
				
			||||||
        self.session.commit()
 | 
					        self.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with patch.object(users, 'Session', return_value=self.session):
 | 
					        with patch.object(mod, 'Session', return_value=self.session):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # invalid if same username in data
 | 
					            # invalid if same username in data
 | 
				
			||||||
            node = colander.SchemaNode(colander.String(), name='username')
 | 
					            node = colander.SchemaNode(colander.String(), name='username')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue