feat: add basic autocomplete support, for Person
URL endpoint only for now, form widget to come later
This commit is contained in:
		
							parent
							
								
									4bf2bb42fb
								
							
						
					
					
						commit
						9d261de45a
					
				
					 6 changed files with 391 additions and 203 deletions
				
			
		| 
						 | 
				
			
			@ -263,6 +263,12 @@ class MasterView(View):
 | 
			
		|||
 | 
			
		||||
       This is optional; see also :meth:`get_form_fields()`.
 | 
			
		||||
 | 
			
		||||
    .. attribute:: has_autocomplete
 | 
			
		||||
 | 
			
		||||
       Boolean indicating whether the view model supports
 | 
			
		||||
       "autocomplete" - i.e. it should have an :meth:`autocomplete()`
 | 
			
		||||
       view.  Default is ``False``.
 | 
			
		||||
 | 
			
		||||
    .. attribute:: configurable
 | 
			
		||||
 | 
			
		||||
       Boolean indicating whether the master view supports
 | 
			
		||||
| 
						 | 
				
			
			@ -286,6 +292,7 @@ class MasterView(View):
 | 
			
		|||
    viewable = True
 | 
			
		||||
    editable = True
 | 
			
		||||
    deletable = True
 | 
			
		||||
    has_autocomplete = False
 | 
			
		||||
    configurable = False
 | 
			
		||||
 | 
			
		||||
    # current action
 | 
			
		||||
| 
						 | 
				
			
			@ -573,6 +580,84 @@ class MasterView(View):
 | 
			
		|||
        session = self.app.get_session(obj)
 | 
			
		||||
        session.delete(obj)
 | 
			
		||||
 | 
			
		||||
    ##############################
 | 
			
		||||
    # autocomplete methods
 | 
			
		||||
    ##############################
 | 
			
		||||
 | 
			
		||||
    def autocomplete(self):
 | 
			
		||||
        """
 | 
			
		||||
        View which accepts a single ``term`` param, and returns a JSON
 | 
			
		||||
        list of autocomplete results to match.
 | 
			
		||||
 | 
			
		||||
        By default, this view is included only if
 | 
			
		||||
        :attr:`has_autocomplete` is true.  It usually maps to a URL
 | 
			
		||||
        like ``/widgets/autocomplete``.
 | 
			
		||||
 | 
			
		||||
        Subclass generally does not need to override this method, but
 | 
			
		||||
        rather should override the others which this calls:
 | 
			
		||||
 | 
			
		||||
        * :meth:`autocomplete_data()`
 | 
			
		||||
        * :meth:`autocomplete_normalize()`
 | 
			
		||||
        """
 | 
			
		||||
        term = self.request.GET.get('term', '')
 | 
			
		||||
        if not term:
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        data = self.autocomplete_data(term)
 | 
			
		||||
        if not data:
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        max_results = 100       # TODO
 | 
			
		||||
 | 
			
		||||
        results = []
 | 
			
		||||
        for obj in data[:max_results]:
 | 
			
		||||
            normal = self.autocomplete_normalize(obj)
 | 
			
		||||
            if normal:
 | 
			
		||||
                results.append(normal)
 | 
			
		||||
 | 
			
		||||
        return results
 | 
			
		||||
 | 
			
		||||
    def autocomplete_data(self, term):
 | 
			
		||||
        """
 | 
			
		||||
        Should return the data/query for the "matching" model records,
 | 
			
		||||
        based on autocomplete search term.  This is called by
 | 
			
		||||
        :meth:`autocomplete()`.
 | 
			
		||||
 | 
			
		||||
        Subclass must override this; default logic returns no data.
 | 
			
		||||
 | 
			
		||||
        :param term: String search term as-is from user, e.g. "foo bar".
 | 
			
		||||
 | 
			
		||||
        :returns: List of data records, or SQLAlchemy query.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
    def autocomplete_normalize(self, obj):
 | 
			
		||||
        """
 | 
			
		||||
        Should return a "normalized" version of the given model
 | 
			
		||||
        record, suitable for autocomplete JSON results.  This is
 | 
			
		||||
        called by :meth:`autocomplete()`.
 | 
			
		||||
 | 
			
		||||
        Subclass may need to override this; default logic is
 | 
			
		||||
        simplistic but will work for basic models.  It returns the
 | 
			
		||||
        "autocomplete results" dict for the object::
 | 
			
		||||
 | 
			
		||||
           {
 | 
			
		||||
               'value': obj.uuid,
 | 
			
		||||
               'label': str(obj),
 | 
			
		||||
           }
 | 
			
		||||
 | 
			
		||||
        The 2 keys shown are required; any other keys will be ignored
 | 
			
		||||
        by the view logic but may be useful on the frontend widget.
 | 
			
		||||
 | 
			
		||||
        :param obj: Model record/instance.
 | 
			
		||||
 | 
			
		||||
        :returns: Dict of "autocomplete results" format, as shown
 | 
			
		||||
           above.
 | 
			
		||||
        """
 | 
			
		||||
        return {
 | 
			
		||||
            'value': obj.uuid,
 | 
			
		||||
            'label': str(obj),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    ##############################
 | 
			
		||||
    # configure methods
 | 
			
		||||
    ##############################
 | 
			
		||||
| 
						 | 
				
			
			@ -1888,6 +1973,15 @@ class MasterView(View):
 | 
			
		|||
                                        f'{permission_prefix}.delete',
 | 
			
		||||
                                        f"Delete {model_title}")
 | 
			
		||||
 | 
			
		||||
        # autocomplete
 | 
			
		||||
        if cls.has_autocomplete:
 | 
			
		||||
            config.add_route(f'{route_prefix}.autocomplete',
 | 
			
		||||
                             f'{url_prefix}/autocomplete')
 | 
			
		||||
            config.add_view(cls, attr='autocomplete',
 | 
			
		||||
                            route_name=f'{route_prefix}.autocomplete',
 | 
			
		||||
                            renderer='json',
 | 
			
		||||
                            permission=f'{route_prefix}.list')
 | 
			
		||||
 | 
			
		||||
        # configure
 | 
			
		||||
        if cls.configurable:
 | 
			
		||||
            config.add_route(f'{route_prefix}.configure',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,8 @@
 | 
			
		|||
Views for people
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
 | 
			
		||||
from wuttjamaican.db.model import Person
 | 
			
		||||
from wuttaweb.views import MasterView
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +48,7 @@ class PersonView(MasterView):
 | 
			
		|||
    model_title_plural = "People"
 | 
			
		||||
    route_prefix = 'people'
 | 
			
		||||
    sort_defaults = 'full_name'
 | 
			
		||||
    has_autocomplete = True
 | 
			
		||||
 | 
			
		||||
    grid_columns = [
 | 
			
		||||
        'full_name',
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +88,17 @@ class PersonView(MasterView):
 | 
			
		|||
        if 'users' in f:
 | 
			
		||||
            f.fields.remove('users')
 | 
			
		||||
 | 
			
		||||
    def autocomplete_query(self, term):
 | 
			
		||||
        """ """
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        session = self.Session()
 | 
			
		||||
        query = session.query(model.Person)
 | 
			
		||||
        criteria = [model.Person.full_name.ilike(f'%{word}%')
 | 
			
		||||
                    for word in term.split()]
 | 
			
		||||
        query = query.filter(sa.and_(*criteria))\
 | 
			
		||||
                     .order_by(model.Person.full_name)
 | 
			
		||||
        return query
 | 
			
		||||
 | 
			
		||||
    def view_profile(self, session=None):
 | 
			
		||||
        """ """
 | 
			
		||||
        person = self.get_instance(session=session)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -197,6 +197,14 @@ class SettingView(MasterView):
 | 
			
		|||
    model_title = "Raw Setting"
 | 
			
		||||
    sort_defaults = 'name'
 | 
			
		||||
 | 
			
		||||
    # TODO: master should handle this (per model key)
 | 
			
		||||
    def configure_grid(self, g):
 | 
			
		||||
        """ """
 | 
			
		||||
        super().configure_grid(g)
 | 
			
		||||
 | 
			
		||||
        # name
 | 
			
		||||
        g.set_link('name')
 | 
			
		||||
 | 
			
		||||
    def configure_form(self, f):
 | 
			
		||||
        """ """
 | 
			
		||||
        super().configure_form(f)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@ from pyramid.response import Response
 | 
			
		|||
from pyramid.httpexceptions import HTTPNotFound
 | 
			
		||||
 | 
			
		||||
from wuttjamaican.conf import WuttaConfig
 | 
			
		||||
from wuttaweb.views import master
 | 
			
		||||
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
 | 
			
		||||
| 
						 | 
				
			
			@ -19,14 +19,15 @@ from tests.util import WebTestCase
 | 
			
		|||
class TestMasterView(WebTestCase):
 | 
			
		||||
 | 
			
		||||
    def make_view(self):
 | 
			
		||||
        return master.MasterView(self.request)
 | 
			
		||||
        return mod.MasterView(self.request)
 | 
			
		||||
 | 
			
		||||
    def test_defaults(self):
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Widget',
 | 
			
		||||
                            model_key='uuid',
 | 
			
		||||
                            has_autocomplete=True,
 | 
			
		||||
                            configurable=True):
 | 
			
		||||
            master.MasterView.defaults(self.pyramid_config)
 | 
			
		||||
            mod.MasterView.defaults(self.pyramid_config)
 | 
			
		||||
 | 
			
		||||
    ##############################
 | 
			
		||||
    # class methods
 | 
			
		||||
| 
						 | 
				
			
			@ -35,317 +36,315 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
    def test_get_model_class(self):
 | 
			
		||||
        
 | 
			
		||||
        # no model class by default
 | 
			
		||||
        self.assertIsNone(master.MasterView.get_model_class())
 | 
			
		||||
        self.assertIsNone(mod.MasterView.get_model_class())
 | 
			
		||||
 | 
			
		||||
        # subclass may specify
 | 
			
		||||
        MyModel = MagicMock()
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertIs(master.MasterView.get_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, master.MasterView.get_model_name)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_model_name)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify model name
 | 
			
		||||
        master.MasterView.model_name = 'Widget'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_model_name(), 'Widget')
 | 
			
		||||
        del master.MasterView.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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_model_name(), 'Blaster')
 | 
			
		||||
            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, master.MasterView.get_model_name_normalized)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_model_name_normalized)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify *normalized* model name
 | 
			
		||||
        master.MasterView.model_name_normalized = 'widget'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_model_name_normalized(), 'widget')
 | 
			
		||||
        del master.MasterView.model_name_normalized
 | 
			
		||||
        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
 | 
			
		||||
        master.MasterView.model_name = 'Blaster'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_model_name_normalized(), 'blaster')
 | 
			
		||||
        del master.MasterView.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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_model_name_normalized(), 'dinosaur')
 | 
			
		||||
            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, master.MasterView.get_model_title)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_model_title)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify  model title
 | 
			
		||||
        master.MasterView.model_title = 'Wutta Widget'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_model_title(), "Wutta Widget")
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.model_name = 'Blaster'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_model_title(), "Blaster")
 | 
			
		||||
        del master.MasterView.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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_model_title(), "Dinosaur")
 | 
			
		||||
            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, master.MasterView.get_model_title_plural)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_model_title_plural)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify *plural* model title
 | 
			
		||||
        master.MasterView.model_title_plural = 'People'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_model_title_plural(), "People")
 | 
			
		||||
        del master.MasterView.model_title_plural
 | 
			
		||||
        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
 | 
			
		||||
        master.MasterView.model_title = 'Wutta Widget'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_model_title_plural(), "Wutta Widgets")
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.model_name = 'Blaster'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_model_title_plural(), "Blasters")
 | 
			
		||||
        del master.MasterView.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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_model_title_plural(), "Dinosaurs")
 | 
			
		||||
            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, master.MasterView.get_model_key)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_model_key)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify model key
 | 
			
		||||
        master.MasterView.model_key = 'uuid'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_model_key(), ('uuid',))
 | 
			
		||||
        del master.MasterView.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, master.MasterView.get_route_prefix)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_route_prefix)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify route prefix
 | 
			
		||||
        master.MasterView.route_prefix = 'widgets'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_route_prefix(), 'widgets')
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.model_name_normalized = 'blaster'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_route_prefix(), 'blasters')
 | 
			
		||||
        del master.MasterView.model_name_normalized
 | 
			
		||||
        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
 | 
			
		||||
        master.MasterView.model_name = 'Dinosaur'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_route_prefix(), 'dinosaurs')
 | 
			
		||||
        del master.MasterView.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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_route_prefix(), 'trucks')
 | 
			
		||||
            self.assertEqual(mod.MasterView.get_route_prefix(), 'trucks')
 | 
			
		||||
 | 
			
		||||
    def test_get_permission_prefix(self):
 | 
			
		||||
 | 
			
		||||
        # error by default (since no model class)
 | 
			
		||||
        self.assertRaises(AttributeError, master.MasterView.get_permission_prefix)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_permission_prefix)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify permission prefix
 | 
			
		||||
        with patch.object(master.MasterView, 'permission_prefix', new='widgets', create=True):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_permission_prefix(), 'widgets')
 | 
			
		||||
        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(master.MasterView, 'route_prefix', new='widgets', create=True):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_permission_prefix(), 'widgets')
 | 
			
		||||
        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(master.MasterView, 'model_class', new=Truck, create=True):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_permission_prefix(), 'trucks')
 | 
			
		||||
        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, master.MasterView.get_url_prefix)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_url_prefix)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify url prefix
 | 
			
		||||
        master.MasterView.url_prefix = '/widgets'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_url_prefix(), '/widgets')
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.route_prefix = 'trucks'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_url_prefix(), '/trucks')
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.model_name_normalized = 'blaster'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_url_prefix(), '/blasters')
 | 
			
		||||
        del master.MasterView.model_name_normalized
 | 
			
		||||
        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
 | 
			
		||||
        master.MasterView.model_name = 'Dinosaur'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_url_prefix(), '/dinosaurs')
 | 
			
		||||
        del master.MasterView.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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_url_prefix(), '/machines')
 | 
			
		||||
            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, master.MasterView.get_instance_url_prefix)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_instance_url_prefix)
 | 
			
		||||
 | 
			
		||||
        # typical example with url_prefix and simple key
 | 
			
		||||
        master.MasterView.url_prefix = '/widgets'
 | 
			
		||||
        master.MasterView.model_key = 'uuid'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_instance_url_prefix(), '/widgets/{uuid}')
 | 
			
		||||
        del master.MasterView.url_prefix
 | 
			
		||||
        del master.MasterView.model_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
 | 
			
		||||
        master.MasterView.url_prefix = '/widgets'
 | 
			
		||||
        master.MasterView.model_key = ('foo', 'bar')
 | 
			
		||||
        self.assertEqual(master.MasterView.get_instance_url_prefix(), '/widgets/{foo}|{bar}')
 | 
			
		||||
        del master.MasterView.url_prefix
 | 
			
		||||
        del master.MasterView.model_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, master.MasterView.get_template_prefix)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_template_prefix)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify template prefix
 | 
			
		||||
        master.MasterView.template_prefix = '/widgets'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_template_prefix(), '/widgets')
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.url_prefix = '/trees'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_template_prefix(), '/trees')
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.route_prefix = 'trucks'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_template_prefix(), '/trucks')
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.model_name_normalized = 'blaster'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_template_prefix(), '/blasters')
 | 
			
		||||
        del master.MasterView.model_name_normalized
 | 
			
		||||
        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
 | 
			
		||||
        master.MasterView.model_name = 'Dinosaur'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_template_prefix(), '/dinosaurs')
 | 
			
		||||
        del master.MasterView.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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_template_prefix(), '/machines')
 | 
			
		||||
            self.assertEqual(mod.MasterView.get_template_prefix(), '/machines')
 | 
			
		||||
 | 
			
		||||
    def test_get_grid_key(self):
 | 
			
		||||
 | 
			
		||||
        # error by default (since no model class)
 | 
			
		||||
        self.assertRaises(AttributeError, master.MasterView.get_grid_key)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_grid_key)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify grid key
 | 
			
		||||
        master.MasterView.grid_key = 'widgets'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_grid_key(), 'widgets')
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.route_prefix = 'trucks'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_grid_key(), 'trucks')
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.model_name_normalized = 'blaster'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_grid_key(), 'blasters')
 | 
			
		||||
        del master.MasterView.model_name_normalized
 | 
			
		||||
        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
 | 
			
		||||
        master.MasterView.model_name = 'Dinosaur'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_grid_key(), 'dinosaurs')
 | 
			
		||||
        del master.MasterView.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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_grid_key(), 'machines')
 | 
			
		||||
            self.assertEqual(mod.MasterView.get_grid_key(), 'machines')
 | 
			
		||||
 | 
			
		||||
    def test_get_config_title(self):
 | 
			
		||||
 | 
			
		||||
        # error by default (since no model class)
 | 
			
		||||
        self.assertRaises(AttributeError, master.MasterView.get_config_title)
 | 
			
		||||
        self.assertRaises(AttributeError, mod.MasterView.get_config_title)
 | 
			
		||||
 | 
			
		||||
        # subclass may specify config title
 | 
			
		||||
        master.MasterView.config_title = 'Widgets'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_config_title(), "Widgets")
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.model_title_plural = 'People'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_config_title(), "People")
 | 
			
		||||
        del master.MasterView.model_title_plural
 | 
			
		||||
        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
 | 
			
		||||
        master.MasterView.model_title = 'Wutta Widget'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_config_title(), "Wutta Widgets")
 | 
			
		||||
        del master.MasterView.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
 | 
			
		||||
        master.MasterView.model_name = 'Blaster'
 | 
			
		||||
        self.assertEqual(master.MasterView.get_config_title(), "Blasters")
 | 
			
		||||
        del master.MasterView.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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=MyModel):
 | 
			
		||||
            self.assertEqual(master.MasterView.get_config_title(), "Dinosaurs")
 | 
			
		||||
            self.assertEqual(mod.MasterView.get_config_title(), "Dinosaurs")
 | 
			
		||||
 | 
			
		||||
    ##############################
 | 
			
		||||
    # support methods
 | 
			
		||||
    ##############################
 | 
			
		||||
 | 
			
		||||
    def test_get_class_hierarchy(self):
 | 
			
		||||
        class MyView(master.MasterView):
 | 
			
		||||
        class MyView(mod.MasterView):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        view = MyView(self.request)
 | 
			
		||||
        classes = view.get_class_hierarchy()
 | 
			
		||||
        self.assertEqual(classes, [View, master.MasterView, MyView])
 | 
			
		||||
        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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Setting'):
 | 
			
		||||
            view = self.make_view()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -374,7 +373,7 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        model = self.app.model
 | 
			
		||||
        auth = self.app.get_auth_handler()
 | 
			
		||||
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Setting'):
 | 
			
		||||
            view = self.make_view()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -410,20 +409,20 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
 | 
			
		||||
        # basic sanity check using /master/index.mako
 | 
			
		||||
        # (nb. it skips /widgets/index.mako since that doesn't exist)
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Widget',
 | 
			
		||||
                            creatable=False):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='AppInfo',
 | 
			
		||||
                            route_prefix='appinfo',
 | 
			
		||||
                            url_prefix='/appinfo',
 | 
			
		||||
                            creatable=False):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            response = view.render_to_response('index', {
 | 
			
		||||
                # nb. grid is required for this template
 | 
			
		||||
                'grid': MagicMock(),
 | 
			
		||||
| 
						 | 
				
			
			@ -431,15 +430,15 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
            self.assertIsInstance(response, Response)
 | 
			
		||||
 | 
			
		||||
        # bad template name causes error
 | 
			
		||||
        master.MasterView.model_name = 'Widget'
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Widget'):
 | 
			
		||||
            self.assertRaises(IOError, view.render_to_response, 'nonexistent', {})
 | 
			
		||||
        del master.MasterView.model_name
 | 
			
		||||
 | 
			
		||||
    def test_get_index_title(self):
 | 
			
		||||
        master.MasterView.model_title_plural = "Wutta Widgets"
 | 
			
		||||
        view = master.MasterView(self.request)
 | 
			
		||||
        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")
 | 
			
		||||
        del master.MasterView.model_title_plural
 | 
			
		||||
 | 
			
		||||
    def test_collect_labels(self):
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -450,14 +449,14 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
 | 
			
		||||
        # labels come from all classes; subclass wins
 | 
			
		||||
        with patch.object(View, 'labels', new={'foo': "Foo", 'bar': "Bar"}, create=True):
 | 
			
		||||
            with patch.object(master.MasterView, 'labels', new={'foo': "FOO FIGHTERS"}, 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(master.MasterView, 'model_class', new=model.Setting, create=True):
 | 
			
		||||
        with patch.object(mod.MasterView, 'model_class', new=model.Setting, create=True):
 | 
			
		||||
 | 
			
		||||
            # no labels by default
 | 
			
		||||
            view = self.make_view()
 | 
			
		||||
| 
						 | 
				
			
			@ -466,7 +465,7 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
            self.assertEqual(grid.labels, {})
 | 
			
		||||
 | 
			
		||||
            # labels come from all classes; subclass wins
 | 
			
		||||
            with patch.object(master.MasterView, 'labels', new={'name': "SETTING NAME"}, create=True):
 | 
			
		||||
            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"})
 | 
			
		||||
| 
						 | 
				
			
			@ -475,27 +474,27 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        model = self.app.model
 | 
			
		||||
 | 
			
		||||
        # no model class
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Widget',
 | 
			
		||||
                            model_key='uuid'):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            grid = view.make_model_grid()
 | 
			
		||||
            self.assertIsNone(grid.model_class)
 | 
			
		||||
 | 
			
		||||
        # explicit model class
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        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 actions by default
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting,
 | 
			
		||||
                            viewable=True,
 | 
			
		||||
                            editable=True,
 | 
			
		||||
| 
						 | 
				
			
			@ -518,14 +517,14 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        view = self.make_view()
 | 
			
		||||
 | 
			
		||||
        # empty by default
 | 
			
		||||
        self.assertFalse(hasattr(master.MasterView, 'model_class'))
 | 
			
		||||
        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(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            query = view.get_grid_data(session=self.session)
 | 
			
		||||
            self.assertIsInstance(query, orm.Query)
 | 
			
		||||
            data = query.all()
 | 
			
		||||
| 
						 | 
				
			
			@ -536,9 +535,9 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        model = self.app.model
 | 
			
		||||
 | 
			
		||||
        # uuid field is pruned
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            grid = view.make_grid(model_class=model.Setting,
 | 
			
		||||
                                  columns=['uuid', 'name', 'value'])
 | 
			
		||||
            self.assertIn('uuid', grid.columns)
 | 
			
		||||
| 
						 | 
				
			
			@ -574,13 +573,13 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        self.assertEqual(self.session.query(model.Setting).count(), 1)
 | 
			
		||||
 | 
			
		||||
        # default not implemented
 | 
			
		||||
        view = master.MasterView(self.request)
 | 
			
		||||
        view = mod.MasterView(self.request)
 | 
			
		||||
        self.assertRaises(NotImplementedError, view.get_instance)
 | 
			
		||||
 | 
			
		||||
        # fetch from DB if model class is known
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
 | 
			
		||||
            # existing setting is returned
 | 
			
		||||
            self.request.matchdict = {'name': 'foo'}
 | 
			
		||||
| 
						 | 
				
			
			@ -599,9 +598,9 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        self.session.add(setting)
 | 
			
		||||
        self.session.commit()
 | 
			
		||||
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting):
 | 
			
		||||
            master.MasterView.defaults(self.pyramid_config)
 | 
			
		||||
            mod.MasterView.defaults(self.pyramid_config)
 | 
			
		||||
            view = self.make_view()
 | 
			
		||||
            url = view.get_action_url_view(setting, 0)
 | 
			
		||||
            self.assertEqual(url, self.request.route_url('settings.view', name='foo'))
 | 
			
		||||
| 
						 | 
				
			
			@ -611,9 +610,9 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        setting = model.Setting(name='foo', value='bar')
 | 
			
		||||
        self.session.add(setting)
 | 
			
		||||
        self.session.commit()
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting):
 | 
			
		||||
            master.MasterView.defaults(self.pyramid_config)
 | 
			
		||||
            mod.MasterView.defaults(self.pyramid_config)
 | 
			
		||||
            view = self.make_view()
 | 
			
		||||
 | 
			
		||||
            # typical
 | 
			
		||||
| 
						 | 
				
			
			@ -630,9 +629,9 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        setting = model.Setting(name='foo', value='bar')
 | 
			
		||||
        self.session.add(setting)
 | 
			
		||||
        self.session.commit()
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting):
 | 
			
		||||
            master.MasterView.defaults(self.pyramid_config)
 | 
			
		||||
            mod.MasterView.defaults(self.pyramid_config)
 | 
			
		||||
            view = self.make_view()
 | 
			
		||||
 | 
			
		||||
            # typical
 | 
			
		||||
| 
						 | 
				
			
			@ -648,15 +647,15 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        model = self.app.model
 | 
			
		||||
 | 
			
		||||
        # no model class
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Widget',
 | 
			
		||||
                            model_key='uuid'):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            form = view.make_model_form()
 | 
			
		||||
            self.assertIsNone(form.model_class)
 | 
			
		||||
 | 
			
		||||
        # explicit model class
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting):
 | 
			
		||||
            form = view.make_model_form()
 | 
			
		||||
            self.assertIs(form.model_class, model.Setting)
 | 
			
		||||
| 
						 | 
				
			
			@ -665,9 +664,9 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        model = self.app.model
 | 
			
		||||
 | 
			
		||||
        # uuid field is pruned
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            form = view.make_form(model_class=model.Setting,
 | 
			
		||||
                                  fields=['uuid', 'name', 'value'])
 | 
			
		||||
            self.assertIn('uuid', form.fields)
 | 
			
		||||
| 
						 | 
				
			
			@ -681,17 +680,17 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        self.assertEqual(self.session.query(model.Setting).count(), 1)
 | 
			
		||||
 | 
			
		||||
        # no model class
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Widget',
 | 
			
		||||
                            model_key='uuid'):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            form = view.make_model_form(fields=['name', 'description'])
 | 
			
		||||
            form.validated = {'name': 'first'}
 | 
			
		||||
            obj = view.objectify(form)
 | 
			
		||||
            self.assertIs(obj, form.validated)
 | 
			
		||||
 | 
			
		||||
        # explicit model class (editing)
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting,
 | 
			
		||||
                            editing=True):
 | 
			
		||||
            form = view.make_model_form()
 | 
			
		||||
| 
						 | 
				
			
			@ -703,7 +702,7 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
            self.assertEqual(obj.value, 'blarg')
 | 
			
		||||
 | 
			
		||||
        # explicit model class (creating)
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting,
 | 
			
		||||
                            creating=True):
 | 
			
		||||
            form = view.make_model_form()
 | 
			
		||||
| 
						 | 
				
			
			@ -715,9 +714,9 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
 | 
			
		||||
    def test_persist(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
 | 
			
		||||
            # new instance is persisted
 | 
			
		||||
            setting = model.Setting(name='foo', value='bar')
 | 
			
		||||
| 
						 | 
				
			
			@ -741,12 +740,12 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        self.pyramid_config.add_route('settings.delete', '/settings/{name}/delete')
 | 
			
		||||
        
 | 
			
		||||
        # sanity/coverage check using /settings/
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Setting',
 | 
			
		||||
                            model_key='name',
 | 
			
		||||
                            get_index_url=MagicMock(return_value='/settings/'),
 | 
			
		||||
                            grid_columns=['name', 'value']):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            response = view.index()
 | 
			
		||||
 | 
			
		||||
            # then again with data, to include view action url
 | 
			
		||||
| 
						 | 
				
			
			@ -769,12 +768,12 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        model = self.app.model
 | 
			
		||||
 | 
			
		||||
        # sanity/coverage check using /settings/new
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Setting',
 | 
			
		||||
                            model_key='name',
 | 
			
		||||
                            get_index_url=MagicMock(return_value='/settings/'),
 | 
			
		||||
                            form_fields=['name', 'value']):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
 | 
			
		||||
            # no setting yet
 | 
			
		||||
            self.assertIsNone(self.app.get_setting(self.session, 'foo.bar'))
 | 
			
		||||
| 
						 | 
				
			
			@ -825,13 +824,13 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        # sanity/coverage check using /settings/XXX
 | 
			
		||||
        setting = {'name': 'foo.bar', 'value': 'baz'}
 | 
			
		||||
        self.request.matchdict = {'name': 'foo.bar'}
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Setting',
 | 
			
		||||
                            model_key='name',
 | 
			
		||||
                            get_index_url=MagicMock(return_value='/settings/'),
 | 
			
		||||
                            grid_columns=['name', 'value'],
 | 
			
		||||
                            form_fields=['name', 'value']):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            with patch.object(view, 'get_instance', return_value=setting):
 | 
			
		||||
                response = view.view()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -854,12 +853,12 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
 | 
			
		||||
        # sanity/coverage check using /settings/XXX/edit
 | 
			
		||||
        self.request.matchdict = {'name': 'foo.bar'}
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Setting',
 | 
			
		||||
                            model_key='name',
 | 
			
		||||
                            get_index_url=MagicMock(return_value='/settings/'),
 | 
			
		||||
                            form_fields=['name', 'value']):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            with patch.object(view, 'get_instance', new=get_instance):
 | 
			
		||||
 | 
			
		||||
                # get the form page
 | 
			
		||||
| 
						 | 
				
			
			@ -918,12 +917,12 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
 | 
			
		||||
        # sanity/coverage check using /settings/XXX/delete
 | 
			
		||||
        self.request.matchdict = {'name': 'foo.bar'}
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_name='Setting',
 | 
			
		||||
                            model_key='name',
 | 
			
		||||
                            get_index_url=MagicMock(return_value='/settings/'),
 | 
			
		||||
                            form_fields=['name', 'value']):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            with patch.object(view, 'get_instance', new=get_instance):
 | 
			
		||||
 | 
			
		||||
                # get the form page
 | 
			
		||||
| 
						 | 
				
			
			@ -960,14 +959,55 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
        self.session.commit()
 | 
			
		||||
        setting = self.session.query(model.Setting).one()
 | 
			
		||||
 | 
			
		||||
        with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
        with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                            model_class=model.Setting,
 | 
			
		||||
                            form_fields=['name', 'value']):
 | 
			
		||||
            view = master.MasterView(self.request)
 | 
			
		||||
            view = mod.MasterView(self.request)
 | 
			
		||||
            view.delete_instance(setting)
 | 
			
		||||
            self.session.commit()
 | 
			
		||||
            self.assertEqual(self.session.query(model.Setting).count(), 0)
 | 
			
		||||
 | 
			
		||||
    def test_autocomplete(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
 | 
			
		||||
        person1 = model.Person(full_name="George Jones")
 | 
			
		||||
        self.session.add(person1)
 | 
			
		||||
        person2 = model.Person(full_name="George Strait")
 | 
			
		||||
        self.session.add(person2)
 | 
			
		||||
        self.session.commit()
 | 
			
		||||
 | 
			
		||||
        # no results for empty term
 | 
			
		||||
        self.request.GET = {}
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        results = view.autocomplete()
 | 
			
		||||
        self.assertEqual(len(results), 0)
 | 
			
		||||
 | 
			
		||||
        # search yields no results
 | 
			
		||||
        self.request.GET = {'term': 'sally'}
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        with patch.object(view, 'autocomplete_data', return_value=[]):
 | 
			
		||||
            view = self.make_view()
 | 
			
		||||
            results = view.autocomplete()
 | 
			
		||||
            self.assertEqual(len(results), 0)
 | 
			
		||||
 | 
			
		||||
        # search yields 2 results
 | 
			
		||||
        self.request.GET = {'term': 'george'}
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        with patch.object(view, 'autocomplete_data', return_value=[person1, person2]):
 | 
			
		||||
            results = view.autocomplete()
 | 
			
		||||
            self.assertEqual(len(results), 2)
 | 
			
		||||
            self.assertEqual([res['value'] for res in results],
 | 
			
		||||
                             [p.uuid for p in [person1, person2]])
 | 
			
		||||
 | 
			
		||||
    def test_autocomplete_normalize(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
 | 
			
		||||
        person = model.Person(full_name="Betty Boop", uuid='bogus')
 | 
			
		||||
        normal = view.autocomplete_normalize(person)
 | 
			
		||||
        self.assertEqual(normal, {'value': 'bogus',
 | 
			
		||||
                                  'label': "Betty Boop"})
 | 
			
		||||
 | 
			
		||||
    def test_configure(self):
 | 
			
		||||
        self.pyramid_config.include('wuttaweb.views.common')
 | 
			
		||||
        self.pyramid_config.include('wuttaweb.views.auth')
 | 
			
		||||
| 
						 | 
				
			
			@ -983,10 +1023,10 @@ class TestMasterView(WebTestCase):
 | 
			
		|||
            {'name': 'wutta.value2', 'save_if_empty': False},
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        view = master.MasterView(self.request)
 | 
			
		||||
        view = mod.MasterView(self.request)
 | 
			
		||||
        with patch.object(self.request, 'current_route_url', return_value='/appinfo/configure'):
 | 
			
		||||
            with patch.object(master, 'Session', return_value=self.session):
 | 
			
		||||
                with patch.multiple(master.MasterView, create=True,
 | 
			
		||||
            with patch.object(mod, 'Session', return_value=self.session):
 | 
			
		||||
                with patch.multiple(mod.MasterView, create=True,
 | 
			
		||||
                                    model_name='AppInfo',
 | 
			
		||||
                                    route_prefix='appinfo',
 | 
			
		||||
                                    template_prefix='/appinfo',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,30 @@ class TestPersonView(WebTestCase):
 | 
			
		|||
        self.assertTrue(form.required_fields)
 | 
			
		||||
        self.assertFalse(form.required_fields['middle_name'])
 | 
			
		||||
 | 
			
		||||
    def test_autocomplete_query(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
 | 
			
		||||
        person1 = model.Person(full_name="George Jones")
 | 
			
		||||
        self.session.add(person1)
 | 
			
		||||
        person2 = model.Person(full_name="George Strait")
 | 
			
		||||
        self.session.add(person2)
 | 
			
		||||
        self.session.commit()
 | 
			
		||||
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        with patch.object(view, 'Session', return_value=self.session):
 | 
			
		||||
 | 
			
		||||
            # both people match
 | 
			
		||||
            query = view.autocomplete_query('george')
 | 
			
		||||
            self.assertEqual(query.count(), 2)
 | 
			
		||||
 | 
			
		||||
            # just 1 match
 | 
			
		||||
            query = view.autocomplete_query('jones')
 | 
			
		||||
            self.assertEqual(query.count(), 1)
 | 
			
		||||
 | 
			
		||||
            # no matches
 | 
			
		||||
            query = view.autocomplete_query('sally')
 | 
			
		||||
            self.assertEqual(query.count(), 0)
 | 
			
		||||
 | 
			
		||||
    def test_view_profile(self):
 | 
			
		||||
        self.pyramid_config.include('wuttaweb.views.common')
 | 
			
		||||
        self.pyramid_config.include('wuttaweb.views.auth')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,6 +67,14 @@ class TestSettingView(WebTestCase):
 | 
			
		|||
        data = query.all()
 | 
			
		||||
        self.assertEqual(len(data), 1)
 | 
			
		||||
 | 
			
		||||
    def test_configure_grid(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.Setting)
 | 
			
		||||
        self.assertFalse(grid.is_linked('name'))
 | 
			
		||||
        view.configure_grid(grid)
 | 
			
		||||
        self.assertTrue(grid.is_linked('name'))
 | 
			
		||||
 | 
			
		||||
    def test_configure_form(self):
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        form = view.make_form(fields=view.get_form_fields())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue