# -*- coding: utf-8; -*- import decimal import functools from unittest import TestCase from unittest.mock import MagicMock, patch from sqlalchemy import orm from pyramid import testing from pyramid.response import Response from pyramid.httpexceptions import HTTPNotFound from wuttjamaican.conf import WuttaConfig from wuttaweb.views import master as mod from wuttaweb.views import View from wuttaweb.subscribers import new_request_set_user from tests.util import WebTestCase class TestMasterView(WebTestCase): def make_view(self): return mod.MasterView(self.request) def test_defaults(self): with patch.multiple(mod.MasterView, create=True, model_name='Widget', model_key='uuid', has_autocomplete=True, configurable=True): mod.MasterView.defaults(self.pyramid_config) ############################## # class methods ############################## def test_get_model_class(self): # no model class by default self.assertIsNone(mod.MasterView.get_model_class()) # subclass may specify MyModel = MagicMock() with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertIs(mod.MasterView.get_model_class(), MyModel) def test_get_model_name(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_model_name) # subclass may specify model name with patch.multiple(mod.MasterView, create=True, model_name='Widget'): self.assertEqual(mod.MasterView.get_model_name(), 'Widget') # or it may specify model class MyModel = MagicMock(__name__='Blaster') with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertEqual(mod.MasterView.get_model_name(), 'Blaster') def test_get_model_name_normalized(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_model_name_normalized) # subclass may specify *normalized* model name with patch.multiple(mod.MasterView, create=True, model_name_normalized='widget'): self.assertEqual(mod.MasterView.get_model_name_normalized(), 'widget') # or it may specify *standard* model name with patch.multiple(mod.MasterView, create=True, model_name='Blaster'): self.assertEqual(mod.MasterView.get_model_name_normalized(), 'blaster') # or it may specify model class MyModel = MagicMock(__name__='Dinosaur') with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertEqual(mod.MasterView.get_model_name_normalized(), 'dinosaur') def test_get_model_title(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_model_title) # subclass may specify model title with patch.multiple(mod.MasterView, create=True, model_title='Wutta Widget'): self.assertEqual(mod.MasterView.get_model_title(), "Wutta Widget") # or it may specify model name with patch.multiple(mod.MasterView, create=True, model_name='Blaster'): self.assertEqual(mod.MasterView.get_model_title(), "Blaster") # or it may specify model class MyModel = MagicMock(__name__='Dinosaur') with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertEqual(mod.MasterView.get_model_title(), "Dinosaur") def test_get_model_title_plural(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_model_title_plural) # subclass may specify *plural* model title with patch.multiple(mod.MasterView, create=True, model_title_plural='People'): self.assertEqual(mod.MasterView.get_model_title_plural(), "People") # or it may specify *singular* model title with patch.multiple(mod.MasterView, create=True, model_title='Wutta Widget'): self.assertEqual(mod.MasterView.get_model_title_plural(), "Wutta Widgets") # or it may specify model name with patch.multiple(mod.MasterView, create=True, model_name='Blaster'): self.assertEqual(mod.MasterView.get_model_title_plural(), "Blasters") # or it may specify model class MyModel = MagicMock(__name__='Dinosaur') with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertEqual(mod.MasterView.get_model_title_plural(), "Dinosaurs") def test_get_model_key(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_model_key) # subclass may specify model key with patch.multiple(mod.MasterView, create=True, model_key='uuid'): self.assertEqual(mod.MasterView.get_model_key(), ('uuid',)) def test_get_route_prefix(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_route_prefix) # subclass may specify route prefix with patch.multiple(mod.MasterView, create=True, route_prefix='widgets'): self.assertEqual(mod.MasterView.get_route_prefix(), 'widgets') # subclass may specify *normalized* model name with patch.multiple(mod.MasterView, create=True, model_name_normalized='blaster'): self.assertEqual(mod.MasterView.get_route_prefix(), 'blasters') # or it may specify *standard* model name with patch.multiple(mod.MasterView, create=True, model_name = 'Dinosaur'): self.assertEqual(mod.MasterView.get_route_prefix(), 'dinosaurs') # or it may specify model class MyModel = MagicMock(__name__='Truck') with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertEqual(mod.MasterView.get_route_prefix(), 'trucks') def test_get_permission_prefix(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_permission_prefix) # subclass may specify permission prefix with patch.object(mod.MasterView, 'permission_prefix', new='widgets', create=True): self.assertEqual(mod.MasterView.get_permission_prefix(), 'widgets') # subclass may specify route prefix with patch.object(mod.MasterView, 'route_prefix', new='widgets', create=True): self.assertEqual(mod.MasterView.get_permission_prefix(), 'widgets') # or it may specify model class Truck = MagicMock(__name__='Truck') with patch.object(mod.MasterView, 'model_class', new=Truck, create=True): self.assertEqual(mod.MasterView.get_permission_prefix(), 'trucks') def test_get_url_prefix(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_url_prefix) # subclass may specify url prefix with patch.multiple(mod.MasterView, create=True, url_prefix='/widgets'): self.assertEqual(mod.MasterView.get_url_prefix(), '/widgets') # or it may specify route prefix with patch.multiple(mod.MasterView, create=True, route_prefix='trucks'): self.assertEqual(mod.MasterView.get_url_prefix(), '/trucks') # or it may specify *normalized* model name with patch.multiple(mod.MasterView, create=True, model_name_normalized='blaster'): self.assertEqual(mod.MasterView.get_url_prefix(), '/blasters') # or it may specify *standard* model name with patch.multiple(mod.MasterView, create=True, model_name='Dinosaur'): self.assertEqual(mod.MasterView.get_url_prefix(), '/dinosaurs') # or it may specify model class MyModel = MagicMock(__name__='Machine') with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertEqual(mod.MasterView.get_url_prefix(), '/machines') def test_get_instance_url_prefix(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_instance_url_prefix) # typical example with url_prefix and simple key with patch.multiple(mod.MasterView, create=True, url_prefix='/widgets', model_key='uuid'): self.assertEqual(mod.MasterView.get_instance_url_prefix(), '/widgets/{uuid}') # typical example with composite key with patch.multiple(mod.MasterView, create=True, url_prefix='/widgets', model_key=('foo', 'bar')): self.assertEqual(mod.MasterView.get_instance_url_prefix(), '/widgets/{foo}|{bar}') def test_get_template_prefix(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_template_prefix) # subclass may specify template prefix with patch.multiple(mod.MasterView, create=True, template_prefix='/widgets'): self.assertEqual(mod.MasterView.get_template_prefix(), '/widgets') # or it may specify url prefix with patch.multiple(mod.MasterView, create=True, url_prefix='/trees'): self.assertEqual(mod.MasterView.get_template_prefix(), '/trees') # or it may specify route prefix with patch.multiple(mod.MasterView, create=True, route_prefix='trucks'): self.assertEqual(mod.MasterView.get_template_prefix(), '/trucks') # or it may specify *normalized* model name with patch.multiple(mod.MasterView, create=True, model_name_normalized='blaster'): self.assertEqual(mod.MasterView.get_template_prefix(), '/blasters') # or it may specify *standard* model name with patch.multiple(mod.MasterView, create=True, model_name='Dinosaur'): self.assertEqual(mod.MasterView.get_template_prefix(), '/dinosaurs') # or it may specify model class MyModel = MagicMock(__name__='Machine') with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertEqual(mod.MasterView.get_template_prefix(), '/machines') def test_get_grid_key(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_grid_key) # subclass may specify grid key with patch.multiple(mod.MasterView, create=True, grid_key='widgets'): self.assertEqual(mod.MasterView.get_grid_key(), 'widgets') # or it may specify route prefix with patch.multiple(mod.MasterView, create=True, route_prefix='trucks'): self.assertEqual(mod.MasterView.get_grid_key(), 'trucks') # or it may specify *normalized* model name with patch.multiple(mod.MasterView, create=True, model_name_normalized='blaster'): self.assertEqual(mod.MasterView.get_grid_key(), 'blasters') # or it may specify *standard* model name with patch.multiple(mod.MasterView, create=True, model_name='Dinosaur'): self.assertEqual(mod.MasterView.get_grid_key(), 'dinosaurs') # or it may specify model class MyModel = MagicMock(__name__='Machine') with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertEqual(mod.MasterView.get_grid_key(), 'machines') def test_get_config_title(self): # error by default (since no model class) self.assertRaises(AttributeError, mod.MasterView.get_config_title) # subclass may specify config title with patch.multiple(mod.MasterView, create=True, config_title='Widgets'): self.assertEqual(mod.MasterView.get_config_title(), "Widgets") # subclass may specify *plural* model title with patch.multiple(mod.MasterView, create=True, model_title_plural='People'): self.assertEqual(mod.MasterView.get_config_title(), "People") # or it may specify *singular* model title with patch.multiple(mod.MasterView, create=True, model_title='Wutta Widget'): self.assertEqual(mod.MasterView.get_config_title(), "Wutta Widgets") # or it may specify model name with patch.multiple(mod.MasterView, create=True, model_name='Blaster'): self.assertEqual(mod.MasterView.get_config_title(), "Blasters") # or it may specify model class MyModel = MagicMock(__name__='Dinosaur') with patch.multiple(mod.MasterView, create=True, model_class=MyModel): self.assertEqual(mod.MasterView.get_config_title(), "Dinosaurs") ############################## # support methods ############################## def test_get_class_hierarchy(self): class MyView(mod.MasterView): pass view = MyView(self.request) classes = view.get_class_hierarchy() self.assertEqual(classes, [View, mod.MasterView, MyView]) def test_has_perm(self): model = self.app.model auth = self.app.get_auth_handler() with patch.multiple(mod.MasterView, create=True, model_name='Setting'): view = self.make_view() # anonymous user self.assertFalse(view.has_perm('list')) self.assertFalse(self.request.has_perm('list')) # reset del self.request.user_permissions # make user with perms barney = model.User(username='barney') self.session.add(barney) blokes = model.Role(name="Blokes") self.session.add(blokes) barney.roles.append(blokes) auth.grant_permission(blokes, 'settings.list') self.session.commit() # this user has perms self.request.user = barney self.assertTrue(view.has_perm('list')) self.assertTrue(self.request.has_perm('settings.list')) def test_has_any_perm(self): model = self.app.model auth = self.app.get_auth_handler() with patch.multiple(mod.MasterView, create=True, model_name='Setting'): view = self.make_view() # anonymous user self.assertFalse(view.has_any_perm('list', 'view')) self.assertFalse(self.request.has_any_perm('settings.list', 'settings.view')) # reset del self.request.user_permissions # make user with perms barney = model.User(username='barney') self.session.add(barney) blokes = model.Role(name="Blokes") self.session.add(blokes) barney.roles.append(blokes) auth.grant_permission(blokes, 'settings.view') self.session.commit() # this user has perms self.request.user = barney self.assertTrue(view.has_any_perm('list', 'view')) self.assertTrue(self.request.has_any_perm('settings.list', 'settings.view')) def test_render_to_response(self): self.pyramid_config.include('wuttaweb.views.common') self.pyramid_config.include('wuttaweb.views.auth') self.pyramid_config.add_route('appinfo', '/appinfo/') def widgets(request): return {} self.pyramid_config.add_route('widgets', '/widgets/') self.pyramid_config.add_view(widgets, route_name='widgets') # basic sanity check using /master/index.mako # (nb. it skips /widgets/index.mako since that doesn't exist) with patch.multiple(mod.MasterView, create=True, model_name='Widget', creatable=False): view = mod.MasterView(self.request) response = view.render_to_response('index', {}) self.assertIsInstance(response, Response) # basic sanity check using /appinfo/index.mako with patch.multiple(mod.MasterView, create=True, model_name='AppInfo', route_prefix='appinfo', url_prefix='/appinfo', creatable=False): view = mod.MasterView(self.request) response = view.render_to_response('index', { # nb. grid is required for this template 'grid': MagicMock(), }) self.assertIsInstance(response, Response) # bad template name causes error with patch.multiple(mod.MasterView, create=True, model_name='Widget'): self.assertRaises(IOError, view.render_to_response, 'nonexistent', {}) def test_get_index_title(self): with patch.multiple(mod.MasterView, create=True, model_title_plural = "Wutta Widgets"): view = mod.MasterView(self.request) self.assertEqual(view.get_index_title(), "Wutta Widgets") def test_collect_labels(self): # no labels by default view = self.make_view() labels = view.collect_labels() self.assertEqual(labels, {}) # labels come from all classes; subclass wins with patch.object(View, 'labels', new={'foo': "Foo", 'bar': "Bar"}, create=True): with patch.object(mod.MasterView, 'labels', new={'foo': "FOO FIGHTERS"}, create=True): view = self.make_view() labels = view.collect_labels() self.assertEqual(labels, {'foo': "FOO FIGHTERS", 'bar': "Bar"}) def test_set_labels(self): model = self.app.model with patch.object(mod.MasterView, 'model_class', new=model.Setting, create=True): # no labels by default view = self.make_view() grid = view.make_model_grid(session=self.session) view.set_labels(grid) self.assertEqual(grid.labels, {}) # labels come from all classes; subclass wins with patch.object(mod.MasterView, 'labels', new={'name': "SETTING NAME"}, create=True): view = self.make_view() view.set_labels(grid) self.assertEqual(grid.labels, {'name': "SETTING NAME"}) def test_make_model_grid(self): model = self.app.model # no model class with patch.multiple(mod.MasterView, create=True, model_name='Widget', model_key='uuid'): view = mod.MasterView(self.request) grid = view.make_model_grid() self.assertIsNone(grid.model_class) # explicit model class with patch.multiple(mod.MasterView, create=True, model_class=model.Setting): grid = view.make_model_grid(session=self.session) self.assertIs(grid.model_class, model.Setting) # no row class by default with patch.multiple(mod.MasterView, create=True, model_class=model.Setting): grid = view.make_model_grid(session=self.session) self.assertIsNone(grid.row_class) # can specify row class get_row_class = MagicMock() with patch.multiple(mod.MasterView, create=True, model_class=model.Setting, grid_row_class=get_row_class): grid = view.make_model_grid(session=self.session) self.assertIs(grid.row_class, get_row_class) # no actions by default with patch.multiple(mod.MasterView, create=True, model_class=model.Setting): grid = view.make_model_grid(session=self.session) self.assertEqual(grid.actions, []) # now let's test some more actions logic with patch.multiple(mod.MasterView, create=True, model_class=model.Setting, viewable=True, editable=True, deletable=True): # should have 3 actions now, but for lack of perms grid = view.make_model_grid(session=self.session) self.assertEqual(len(grid.actions), 0) # but root user has perms, so gets 3 actions with patch.object(self.request, 'is_root', new=True): grid = view.make_model_grid(session=self.session) self.assertEqual(len(grid.actions), 3) def test_get_grid_data(self): model = self.app.model self.app.save_setting(self.session, 'foo', 'bar') self.session.commit() setting = self.session.query(model.Setting).one() view = self.make_view() # empty by default self.assertFalse(hasattr(mod.MasterView, 'model_class')) data = view.get_grid_data(session=self.session) self.assertEqual(data, []) # grid with model class will produce data query with patch.multiple(mod.MasterView, create=True, model_class=model.Setting): view = mod.MasterView(self.request) query = view.get_grid_data(session=self.session) self.assertIsInstance(query, orm.Query) data = query.all() self.assertEqual(len(data), 1) self.assertIs(data[0], setting) def test_configure_grid(self): model = self.app.model # uuid field is pruned with patch.multiple(mod.MasterView, create=True, model_class=model.Setting): view = mod.MasterView(self.request) grid = view.make_grid(model_class=model.Setting, columns=['uuid', 'name', 'value']) self.assertIn('uuid', grid.columns) view.configure_grid(grid) self.assertNotIn('uuid', grid.columns) def test_grid_render_bool(self): model = self.app.model view = self.make_view() user = model.User(username='barney', active=None) # null value = view.grid_render_bool(user, 'active', None) self.assertIsNone(value) # true user.active = True value = view.grid_render_bool(user, 'active', True) self.assertEqual(value, "Yes") # false user.active = False value = view.grid_render_bool(user, 'active', False) self.assertEqual(value, "No") def test_grid_render_currency(self): model = self.app.model view = self.make_view() obj = {'amount': None} # null value = view.grid_render_currency(obj, 'amount', None) self.assertIsNone(value) # normal amount obj['amount'] = decimal.Decimal('100.42') value = view.grid_render_currency(obj, 'amount', '100.42') self.assertEqual(value, "$100.42") # negative amount obj['amount'] = decimal.Decimal('-100.42') value = view.grid_render_currency(obj, 'amount', '-100.42') self.assertEqual(value, "($100.42)") def test_grid_render_notes(self): model = self.app.model view = self.make_view() # null text = None role = model.Role(name="Foo", notes=text) value = view.grid_render_notes(role, 'notes', text) self.assertIsNone(value) # short string text = "hello world" role = model.Role(name="Foo", notes=text) value = view.grid_render_notes(role, 'notes', text) self.assertEqual(value, text) # long string text = "hello world " * 20 role = model.Role(name="Foo", notes=text) value = view.grid_render_notes(role, 'notes', text) self.assertIn('