feat: add initial MasterView support
				
					
				
			very minimal, index view only with empty page content
This commit is contained in:
		
							parent
							
								
									f5891d36fa
								
							
						
					
					
						commit
						9ac4f7525e
					
				
					 9 changed files with 760 additions and 2 deletions
				
			
		| 
						 | 
					@ -23,3 +23,4 @@
 | 
				
			||||||
   views.base
 | 
					   views.base
 | 
				
			||||||
   views.common
 | 
					   views.common
 | 
				
			||||||
   views.essential
 | 
					   views.essential
 | 
				
			||||||
 | 
					   views.master
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								docs/api/wuttaweb/views.master.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								docs/api/wuttaweb/views.master.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``wuttaweb.views.master``
 | 
				
			||||||
 | 
					=========================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: wuttaweb.views.master
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/wuttaweb/templates/master/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/wuttaweb/templates/master/index.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					## -*- coding: utf-8; -*-
 | 
				
			||||||
 | 
					<%inherit file="/page.mako" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%def name="page_content()">
 | 
				
			||||||
 | 
					  <p>TODO: index page content</p>
 | 
				
			||||||
 | 
					</%def>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					${parent.body()}
 | 
				
			||||||
| 
						 | 
					@ -27,9 +27,11 @@ For convenience, from this ``wuttaweb.views`` namespace you can access
 | 
				
			||||||
the following:
 | 
					the following:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* :class:`~wuttaweb.views.base.View`
 | 
					* :class:`~wuttaweb.views.base.View`
 | 
				
			||||||
 | 
					* :class:`~wuttaweb.views.master.MasterView`
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .base import View
 | 
					from .base import View
 | 
				
			||||||
 | 
					from .master import MasterView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def includeme(config):
 | 
					def includeme(config):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										443
									
								
								src/wuttaweb/views/master.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										443
									
								
								src/wuttaweb/views/master.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,443 @@
 | 
				
			||||||
 | 
					# -*- 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/>.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					################################################################################
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Base Logic for Master Views
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pyramid.renderers import render_to_response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from wuttaweb.views import View
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MasterView(View):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Base class for "master" views.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Master views typically map to a table in a DB, though not always.
 | 
				
			||||||
 | 
					    They essentially are a set of CRUD views for a certain type of
 | 
				
			||||||
 | 
					    data record.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Many attributes may be overridden in subclass.  For instance to
 | 
				
			||||||
 | 
					    define :attr:`model_class`::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       from wuttaweb.views import MasterView
 | 
				
			||||||
 | 
					       from wuttjamaican.db.model import Person
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       class MyPersonView(MasterView):
 | 
				
			||||||
 | 
					           model_class = Person
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       def includeme(config):
 | 
				
			||||||
 | 
					           MyPersonView.defaults(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Many of these attributes will only exist if they have been
 | 
				
			||||||
 | 
					       explicitly defined in a subclass.  There are corresponding
 | 
				
			||||||
 | 
					       ``get_xxx()`` methods which should be used instead of accessing
 | 
				
			||||||
 | 
					       these attributes directly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Optional reference to a data model class.  While not strictly
 | 
				
			||||||
 | 
					       required, most views will set this to a SQLAlchemy mapped
 | 
				
			||||||
 | 
					       class,
 | 
				
			||||||
 | 
					       e.g. :class:`wuttjamaican:wuttjamaican.db.model.auth.User`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Code should not access this directly but instead call
 | 
				
			||||||
 | 
					       :meth:`get_model_class()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: model_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Optional override for the view's data model name,
 | 
				
			||||||
 | 
					       e.g. ``'WuttaWidget'``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Code should not access this directly but instead call
 | 
				
			||||||
 | 
					       :meth:`get_model_name()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: model_name_normalized
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Optional override for the view's "normalized" data model name,
 | 
				
			||||||
 | 
					       e.g. ``'wutta_widget'``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Code should not access this directly but instead call
 | 
				
			||||||
 | 
					       :meth:`get_model_name_normalized()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: model_title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Optional override for the view's "humanized" (singular) model
 | 
				
			||||||
 | 
					       title, e.g. ``"Wutta Widget"``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Code should not access this directly but instead call
 | 
				
			||||||
 | 
					       :meth:`get_model_title()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: model_title_plural
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Optional override for the view's "humanized" (plural) model
 | 
				
			||||||
 | 
					       title, e.g. ``"Wutta Widgets"``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Code should not access this directly but instead call
 | 
				
			||||||
 | 
					       :meth:`get_model_title_plural()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: route_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Optional override for the view's route prefix,
 | 
				
			||||||
 | 
					       e.g. ``'wutta_widgets'``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Code should not access this directly but instead call
 | 
				
			||||||
 | 
					       :meth:`get_route_prefix()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: url_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Optional override for the view's URL prefix,
 | 
				
			||||||
 | 
					       e.g. ``'/widgets'``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Code should not access this directly but instead call
 | 
				
			||||||
 | 
					       :meth:`get_url_prefix()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: template_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Optional override for the view's template prefix,
 | 
				
			||||||
 | 
					       e.g. ``'/widgets'``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Code should not access this directly but instead call
 | 
				
			||||||
 | 
					       :meth:`get_template_prefix()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: listable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Boolean indicating whether the view model supports "listing" -
 | 
				
			||||||
 | 
					       i.e. it should have an :meth:`index()` view.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					    # attributes
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    listable = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					    # view methods
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def index(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        View to "list" (filter/browse) the model data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        This is the "default" view for the model and is what user sees
 | 
				
			||||||
 | 
					        when visiting the "root" path under the :attr:`url_prefix`,
 | 
				
			||||||
 | 
					        e.g. ``/widgets/``.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return self.render_to_response('index', {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					    # support methods
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_index_title(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the main index title for the master view.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        By default this returns the value from
 | 
				
			||||||
 | 
					        :meth:`get_model_title_plural()`.  Subclass may override as
 | 
				
			||||||
 | 
					        needed.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return self.get_model_title_plural()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def render_to_response(self, template, context):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Locate and render an appropriate template, with the given
 | 
				
			||||||
 | 
					        context, and return a :term:`response`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The specified ``template`` should be only the "base name" for
 | 
				
			||||||
 | 
					        the template - e.g.  ``'index'`` or ``'edit'``.  This method
 | 
				
			||||||
 | 
					        will then try to locate a suitable template file, based on
 | 
				
			||||||
 | 
					        values from :meth:`get_template_prefix()` and
 | 
				
			||||||
 | 
					        :meth:`get_fallback_templates()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        In practice this *usually* means two different template paths
 | 
				
			||||||
 | 
					        will be attempted, e.g. if ``template`` is ``'edit'`` and
 | 
				
			||||||
 | 
					        :attr:`template_prefix` is ``'/widgets'``:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        * ``/widgets/edit.mako``
 | 
				
			||||||
 | 
					        * ``/master/edit.mako``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The first template found to exist will be used for rendering.
 | 
				
			||||||
 | 
					        It then calls
 | 
				
			||||||
 | 
					        :func:`pyramid:pyramid.renderers.render_to_response()` and
 | 
				
			||||||
 | 
					        returns the result.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param template: Base name for the template.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param context: Data dict to be used as template context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: Response object containing the rendered template.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        defaults = {
 | 
				
			||||||
 | 
					            'index_title': self.get_index_title(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # merge defaults + caller-provided context
 | 
				
			||||||
 | 
					        defaults.update(context)
 | 
				
			||||||
 | 
					        context = defaults
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # first try the template path most specific to this view
 | 
				
			||||||
 | 
					        template_prefix = self.get_template_prefix()
 | 
				
			||||||
 | 
					        mako_path = f'{template_prefix}/{template}.mako'
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return render_to_response(mako_path, context, request=self.request)
 | 
				
			||||||
 | 
					        except IOError:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # failing that, try one or more fallback templates
 | 
				
			||||||
 | 
					            for fallback in self.get_fallback_templates(template):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    return render_to_response(fallback, context, request=self.request)
 | 
				
			||||||
 | 
					                except IOError:
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # if we made it all the way here, then we found no
 | 
				
			||||||
 | 
					            # templates at all, in which case re-attempt the first and
 | 
				
			||||||
 | 
					            # let that error raise on up
 | 
				
			||||||
 | 
					            return render_to_response(mako_path, context, request=self.request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_fallback_templates(self, template):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns a list of "fallback" template paths which may be
 | 
				
			||||||
 | 
					        attempted for rendering a view.  This is used within
 | 
				
			||||||
 | 
					        :meth:`render_to_response()` if the "first guess" template
 | 
				
			||||||
 | 
					        file was not found.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param template: Base name for a template (without prefix), e.g.
 | 
				
			||||||
 | 
					           ``'custom'``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: List of full template paths to be tried, based on
 | 
				
			||||||
 | 
					           the specified template.  For instance if ``template`` is
 | 
				
			||||||
 | 
					           ``'custom'`` this will (by default) return::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              ['/master/custom.mako']
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return [f'/master/{template}.mako']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					    # class methods
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_model_class(cls):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the model class for the view (if defined).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        A model class will *usually* be a SQLAlchemy mapped class,
 | 
				
			||||||
 | 
					        e.g. :class:`wuttjamaican:wuttjamaican.db.model.base.Person`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        There is no default value here, but a subclass may override by
 | 
				
			||||||
 | 
					        assigning :attr:`model_class`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Note that the model class is not *required* - however if you
 | 
				
			||||||
 | 
					        do not set the :attr:`model_class`, then you *must* set the
 | 
				
			||||||
 | 
					        :attr:`model_name`.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(cls, 'model_class'):
 | 
				
			||||||
 | 
					            return cls.model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_model_name(cls):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the model name for the view.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        A model name should generally be in the format of a Python
 | 
				
			||||||
 | 
					        class name, e.g. ``'WuttaWidget'``.  (Note this is
 | 
				
			||||||
 | 
					        *singular*, not plural.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The default logic will call :meth:`get_model_class()` and
 | 
				
			||||||
 | 
					        return that class name as-is.  A subclass may override by
 | 
				
			||||||
 | 
					        assigning :attr:`model_name`.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(cls, 'model_name'):
 | 
				
			||||||
 | 
					            return cls.model_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cls.get_model_class().__name__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_model_name_normalized(cls):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the "normalized" model name for the view.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        A normalized model name should generally be in the format of a
 | 
				
			||||||
 | 
					        Python variable name, e.g. ``'wutta_widget'``.  (Note this is
 | 
				
			||||||
 | 
					        *singular*, not plural.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The default logic will call :meth:`get_model_name()` and
 | 
				
			||||||
 | 
					        simply lower-case the result.  A subclass may override by
 | 
				
			||||||
 | 
					        assigning :attr:`model_name_normalized`.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(cls, 'model_name_normalized'):
 | 
				
			||||||
 | 
					            return cls.model_name_normalized
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cls.get_model_name().lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_model_title(cls):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the "humanized" (singular) model title for the view.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The model title will be displayed to the user, so should have
 | 
				
			||||||
 | 
					        proper grammar and capitalization, e.g. ``"Wutta Widget"``.
 | 
				
			||||||
 | 
					        (Note this is *singular*, not plural.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The default logic will call :meth:`get_model_name()` and use
 | 
				
			||||||
 | 
					        the result as-is.  A subclass may override by assigning
 | 
				
			||||||
 | 
					        :attr:`model_title`.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(cls, 'model_title'):
 | 
				
			||||||
 | 
					            return cls.model_title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cls.get_model_name()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_model_title_plural(cls):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the "humanized" (plural) model title for the view.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The model title will be displayed to the user, so should have
 | 
				
			||||||
 | 
					        proper grammar and capitalization, e.g. ``"Wutta Widgets"``.
 | 
				
			||||||
 | 
					        (Note this is *plural*, not singular.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The default logic will call :meth:`get_model_title()` and
 | 
				
			||||||
 | 
					        simply add a ``'s'`` to the end.  A subclass may override by
 | 
				
			||||||
 | 
					        assigning :attr:`model_title_plural`.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(cls, 'model_title_plural'):
 | 
				
			||||||
 | 
					            return cls.model_title_plural
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        model_title = cls.get_model_title()
 | 
				
			||||||
 | 
					        return f"{model_title}s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_route_prefix(cls):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the "route prefix" for the master view.  This prefix
 | 
				
			||||||
 | 
					        is used for all named routes defined by the view class.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        For instance if route prefix is ``'widgets'`` then a view
 | 
				
			||||||
 | 
					        might have these routes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        * ``'widgets'``
 | 
				
			||||||
 | 
					        * ``'widgets.create'``
 | 
				
			||||||
 | 
					        * ``'widgets.edit'``
 | 
				
			||||||
 | 
					        * ``'widgets.delete'``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The default logic will call
 | 
				
			||||||
 | 
					        :meth:`get_model_name_normalized()` and simply add an ``'s'``
 | 
				
			||||||
 | 
					        to the end, making it plural.  A subclass may override by
 | 
				
			||||||
 | 
					        assigning :attr:`route_prefix`.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(cls, 'route_prefix'):
 | 
				
			||||||
 | 
					            return cls.route_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        model_name = cls.get_model_name_normalized()
 | 
				
			||||||
 | 
					        return f'{model_name}s'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_url_prefix(cls):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the "URL prefix" for the master view.  This prefix is
 | 
				
			||||||
 | 
					        used for all URLs defined by the view class.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Using the same example as in :meth:`get_route_prefix()`, the
 | 
				
			||||||
 | 
					        URL prefix would be ``'/widgets'`` and the view would have
 | 
				
			||||||
 | 
					        defined routes for these URLs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        * ``/widgets/``
 | 
				
			||||||
 | 
					        * ``/widgets/new``
 | 
				
			||||||
 | 
					        * ``/widgets/XXX/edit``
 | 
				
			||||||
 | 
					        * ``/widgets/XXX/delete``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The default logic will call :meth:`get_route_prefix()` and
 | 
				
			||||||
 | 
					        simply add a ``'/'`` to the beginning.  A subclass may
 | 
				
			||||||
 | 
					        override by assigning :attr:`url_prefix`.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(cls, 'url_prefix'):
 | 
				
			||||||
 | 
					            return cls.url_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        route_prefix = cls.get_route_prefix()
 | 
				
			||||||
 | 
					        return f'/{route_prefix}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_template_prefix(cls):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Returns the "template prefix" for the master view.  This
 | 
				
			||||||
 | 
					        prefix is used to guess which template path to render for a
 | 
				
			||||||
 | 
					        given view.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Using the same example as in :meth:`get_url_prefix()`, the
 | 
				
			||||||
 | 
					        template prefix would also be ``'/widgets'`` and the templates
 | 
				
			||||||
 | 
					        assumed for those routes would be:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        * ``/widgets/index.mako``
 | 
				
			||||||
 | 
					        * ``/widgets/create.mako``
 | 
				
			||||||
 | 
					        * ``/widgets/edit.mako``
 | 
				
			||||||
 | 
					        * ``/widgets/delete.mako``
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The default logic will call :meth:`get_url_prefix()` and
 | 
				
			||||||
 | 
					        return that value as-is.  A subclass may override by assigning
 | 
				
			||||||
 | 
					        :attr:`template_prefix`.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(cls, 'template_prefix'):
 | 
				
			||||||
 | 
					            return cls.template_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cls.get_url_prefix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					    # configuration
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def defaults(cls, config):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Provide default Pyramid configuration for a master view.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        This is generally called from within the module's
 | 
				
			||||||
 | 
					        ``includeme()`` function, e.g.::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           from wuttaweb.views import MasterView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           class WidgetView(MasterView):
 | 
				
			||||||
 | 
					               model_name = 'Widget'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					           def includeme(config):
 | 
				
			||||||
 | 
					               WidgetView.defaults(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param config: Reference to the app's
 | 
				
			||||||
 | 
					           :class:`pyramid:pyramid.config.Configurator` instance.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        cls._defaults(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def _defaults(cls, config):
 | 
				
			||||||
 | 
					        route_prefix = cls.get_route_prefix()
 | 
				
			||||||
 | 
					        url_prefix = cls.get_url_prefix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # index view
 | 
				
			||||||
 | 
					        if cls.listable:
 | 
				
			||||||
 | 
					            config.add_route(route_prefix, f'{url_prefix}/')
 | 
				
			||||||
 | 
					            config.add_view(cls, attr='index',
 | 
				
			||||||
 | 
					                            route_name=route_prefix)
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,9 @@ class TestFieldList(TestCase):
 | 
				
			||||||
class TestForm(TestCase):
 | 
					class TestForm(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        self.config = WuttaConfig()
 | 
					        self.config = WuttaConfig(defaults={
 | 
				
			||||||
 | 
					            'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler',
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
        self.request = testing.DummyRequest(wutta_config=self.config)
 | 
					        self.request = testing.DummyRequest(wutta_config=self.config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.pyramid_config = testing.setUp(request=self.request, settings={
 | 
					        self.pyramid_config = testing.setUp(request=self.request, settings={
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -214,7 +214,9 @@ class TestNewRequestSetUser(TestCase):
 | 
				
			||||||
class TestBeforeRender(TestCase):
 | 
					class TestBeforeRender(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        self.config = WuttaConfig()
 | 
					        self.config = WuttaConfig(defaults={
 | 
				
			||||||
 | 
					            'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler',
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def make_request(self):
 | 
					    def make_request(self):
 | 
				
			||||||
        request = testing.DummyRequest()
 | 
					        request = testing.DummyRequest()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										11
									
								
								tests/utils.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tests/utils.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8; -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from wuttaweb.menus import MenuHandler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NullMenuHandler(MenuHandler):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Dummy menu handler for testing.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def make_menus(self, request, **kwargs):
 | 
				
			||||||
 | 
					        return []
 | 
				
			||||||
							
								
								
									
										282
									
								
								tests/views/test_master.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								tests/views/test_master.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,282 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8; -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from unittest import TestCase
 | 
				
			||||||
 | 
					from unittest.mock import MagicMock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pyramid import testing
 | 
				
			||||||
 | 
					from pyramid.response import Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from wuttjamaican.conf import WuttaConfig
 | 
				
			||||||
 | 
					from wuttaweb.views import master
 | 
				
			||||||
 | 
					from wuttaweb.subscribers import new_request_set_user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestMasterView(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        self.config = WuttaConfig(defaults={
 | 
				
			||||||
 | 
					            'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler',
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        self.app = self.config.get_app()
 | 
				
			||||||
 | 
					        self.request = testing.DummyRequest(wutta_config=self.config)
 | 
				
			||||||
 | 
					        self.pyramid_config = testing.setUp(request=self.request, settings={
 | 
				
			||||||
 | 
					            'wutta_config': self.config,
 | 
				
			||||||
 | 
					            'mako.directories': ['wuttaweb:templates'],
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        self.pyramid_config.include('pyramid_mako')
 | 
				
			||||||
 | 
					        self.pyramid_config.include('wuttaweb.static')
 | 
				
			||||||
 | 
					        self.pyramid_config.include('wuttaweb.views.essential')
 | 
				
			||||||
 | 
					        self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render',
 | 
				
			||||||
 | 
					                                           'pyramid.events.BeforeRender')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        event = MagicMock(request=self.request)
 | 
				
			||||||
 | 
					        new_request_set_user(event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def tearDown(self):
 | 
				
			||||||
 | 
					        testing.tearDown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_defaults(self):
 | 
				
			||||||
 | 
					        master.MasterView.model_name = 'Widget'
 | 
				
			||||||
 | 
					        # TODO: should inspect pyramid routes after this, to be certain
 | 
				
			||||||
 | 
					        master.MasterView.defaults(self.pyramid_config)
 | 
				
			||||||
 | 
					        del master.MasterView.model_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					    # class methods
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_model_class(self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # no model class by default
 | 
				
			||||||
 | 
					        self.assertIsNone(master.MasterView.get_model_class())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # subclass may specify
 | 
				
			||||||
 | 
					        MyModel = MagicMock()
 | 
				
			||||||
 | 
					        master.MasterView.model_class = MyModel
 | 
				
			||||||
 | 
					        self.assertIs(master.MasterView.get_model_class(), MyModel)
 | 
				
			||||||
 | 
					        del master.MasterView.model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_model_name(self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # error by default (since no model class)
 | 
				
			||||||
 | 
					        self.assertRaises(AttributeError, master.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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify model class
 | 
				
			||||||
 | 
					        MyModel = MagicMock(__name__='Blaster')
 | 
				
			||||||
 | 
					        master.MasterView.model_class = MyModel
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_model_name(), 'Blaster')
 | 
				
			||||||
 | 
					        del master.MasterView.model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_model_name_normalized(self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # error by default (since no model class)
 | 
				
			||||||
 | 
					        self.assertRaises(AttributeError, master.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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify model class
 | 
				
			||||||
 | 
					        MyModel = MagicMock(__name__='Dinosaur')
 | 
				
			||||||
 | 
					        master.MasterView.model_class = MyModel
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_model_name_normalized(), 'dinosaur')
 | 
				
			||||||
 | 
					        del master.MasterView.model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_model_title(self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # error by default (since no model class)
 | 
				
			||||||
 | 
					        self.assertRaises(AttributeError, master.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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify model name
 | 
				
			||||||
 | 
					        master.MasterView.model_name = 'Blaster'
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_model_title(), "Blaster")
 | 
				
			||||||
 | 
					        del master.MasterView.model_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify model class
 | 
				
			||||||
 | 
					        MyModel = MagicMock(__name__='Dinosaur')
 | 
				
			||||||
 | 
					        master.MasterView.model_class = MyModel
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_model_title(), "Dinosaur")
 | 
				
			||||||
 | 
					        del master.MasterView.model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_model_title_plural(self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # error by default (since no model class)
 | 
				
			||||||
 | 
					        self.assertRaises(AttributeError, master.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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify model class
 | 
				
			||||||
 | 
					        MyModel = MagicMock(__name__='Dinosaur')
 | 
				
			||||||
 | 
					        master.MasterView.model_class = MyModel
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_model_title_plural(), "Dinosaurs")
 | 
				
			||||||
 | 
					        del master.MasterView.model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_route_prefix(self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # error by default (since no model class)
 | 
				
			||||||
 | 
					        self.assertRaises(AttributeError, master.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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify model class
 | 
				
			||||||
 | 
					        MyModel = MagicMock(__name__='Truck')
 | 
				
			||||||
 | 
					        master.MasterView.model_class = MyModel
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_route_prefix(), 'trucks')
 | 
				
			||||||
 | 
					        del master.MasterView.model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_url_prefix(self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # error by default (since no model class)
 | 
				
			||||||
 | 
					        self.assertRaises(AttributeError, master.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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify route prefix
 | 
				
			||||||
 | 
					        master.MasterView.route_prefix = 'trucks'
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_url_prefix(), '/trucks')
 | 
				
			||||||
 | 
					        del master.MasterView.route_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify model class
 | 
				
			||||||
 | 
					        MyModel = MagicMock(__name__='Machine')
 | 
				
			||||||
 | 
					        master.MasterView.model_class = MyModel
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_url_prefix(), '/machines')
 | 
				
			||||||
 | 
					        del master.MasterView.model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_template_prefix(self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # error by default (since no model class)
 | 
				
			||||||
 | 
					        self.assertRaises(AttributeError, master.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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify url prefix
 | 
				
			||||||
 | 
					        master.MasterView.url_prefix = '/trees'
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_template_prefix(), '/trees')
 | 
				
			||||||
 | 
					        del master.MasterView.url_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify route prefix
 | 
				
			||||||
 | 
					        master.MasterView.route_prefix = 'trucks'
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_template_prefix(), '/trucks')
 | 
				
			||||||
 | 
					        del master.MasterView.route_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # or it may specify model class
 | 
				
			||||||
 | 
					        MyModel = MagicMock(__name__='Machine')
 | 
				
			||||||
 | 
					        master.MasterView.model_class = MyModel
 | 
				
			||||||
 | 
					        self.assertEqual(master.MasterView.get_template_prefix(), '/machines')
 | 
				
			||||||
 | 
					        del master.MasterView.model_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					    # support methods
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_index_title(self):
 | 
				
			||||||
 | 
					        master.MasterView.model_title_plural = "Wutta Widgets"
 | 
				
			||||||
 | 
					        view = master.MasterView(self.request)
 | 
				
			||||||
 | 
					        self.assertEqual(view.get_index_title(), "Wutta Widgets")
 | 
				
			||||||
 | 
					        del master.MasterView.model_title_plural
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_render_to_response(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # basic sanity check using /master/index.mako
 | 
				
			||||||
 | 
					        # (nb. it skips /widgets/index.mako since that doesn't exist)
 | 
				
			||||||
 | 
					        master.MasterView.model_name = 'Widget'
 | 
				
			||||||
 | 
					        view = master.MasterView(self.request)
 | 
				
			||||||
 | 
					        response = view.render_to_response('index', {})
 | 
				
			||||||
 | 
					        self.assertIsInstance(response, Response)
 | 
				
			||||||
 | 
					        del master.MasterView.model_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # basic sanity check using /appinfo/index.mako
 | 
				
			||||||
 | 
					        master.MasterView.model_name = 'AppInfo'
 | 
				
			||||||
 | 
					        master.MasterView.template_prefix = '/appinfo'
 | 
				
			||||||
 | 
					        view = master.MasterView(self.request)
 | 
				
			||||||
 | 
					        response = view.render_to_response('index', {})
 | 
				
			||||||
 | 
					        self.assertIsInstance(response, Response)
 | 
				
			||||||
 | 
					        del master.MasterView.model_name
 | 
				
			||||||
 | 
					        del master.MasterView.template_prefix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # bad template name causes error
 | 
				
			||||||
 | 
					        master.MasterView.model_name = 'Widget'
 | 
				
			||||||
 | 
					        self.assertRaises(IOError, view.render_to_response, 'nonexistent', {})
 | 
				
			||||||
 | 
					        del master.MasterView.model_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					    # view methods
 | 
				
			||||||
 | 
					    ##############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_index(self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # basic sanity check using /appinfo
 | 
				
			||||||
 | 
					        master.MasterView.model_name = 'AppInfo'
 | 
				
			||||||
 | 
					        master.MasterView.template_prefix = '/appinfo'
 | 
				
			||||||
 | 
					        view = master.MasterView(self.request)
 | 
				
			||||||
 | 
					        response = view.index()
 | 
				
			||||||
 | 
					        del master.MasterView.model_name
 | 
				
			||||||
 | 
					        del master.MasterView.template_prefix
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue