source: tailbone/tailbone/views/common.py @ 839f6af

Last change on this file since 839f6af was 839f6af, checked in by Lance Edgar <ledgar@…>, 13 months ago

Add basic "DB picker" support, for views which allow multiple engines

i.e. whichever engine is "current" will determine where data comes from

  • Property mode set to 100644
File size: 9.3 KB
Line 
1# -*- coding: utf-8; -*-
2################################################################################
3#
4#  Rattail -- Retail Software Framework
5#  Copyright © 2010-2019 Lance Edgar
6#
7#  This file is part of Rattail.
8#
9#  Rattail is free software: you can redistribute it and/or modify it under the
10#  terms of the GNU General Public License as published by the Free Software
11#  Foundation, either version 3 of the License, or (at your option) any later
12#  version.
13#
14#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
15#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17#  details.
18#
19#  You should have received a copy of the GNU General Public License along with
20#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Various common views
25"""
26
27from __future__ import unicode_literals, absolute_import
28
29import six
30
31import rattail
32from rattail.batch import consume_batch_id
33from rattail.mail import send_email
34from rattail.util import OrderedDict
35from rattail.files import resource_path
36
37import colander
38from pyramid import httpexceptions
39from pyramid.response import Response
40
41import tailbone
42from tailbone import forms
43from tailbone.db import Session
44from tailbone.views import View
45from tailbone.util import set_app_theme
46
47
48class Feedback(colander.Schema):
49    """
50    Form schema for user feedback.
51    """
52    referrer = colander.SchemaNode(colander.String())
53
54    user = colander.SchemaNode(forms.types.UserType())
55
56    user_name = colander.SchemaNode(colander.String(),
57                                    missing=colander.null)
58
59    message = colander.SchemaNode(colander.String())
60
61
62class CommonView(View):
63    """
64    Base class for common views; override as needed.
65    """
66    project_title = "Tailbone"
67    project_version = tailbone.__version__
68    robots_txt_path = resource_path('tailbone.static:robots.txt')
69
70    def home(self, mobile=False):
71        """
72        Home page view.
73        """
74        if not mobile and not self.request.user:
75            if self.rattail_config.getbool('tailbone', 'login_is_home', default=True):
76                raise self.redirect(self.request.route_url('login'))
77
78        image_url = self.rattail_config.get(
79            'tailbone', 'main_image_url',
80            default=self.request.static_url('tailbone:static/img/home_logo.png'))
81        return {'image_url': image_url}
82
83    def robots_txt(self):
84        """
85        Returns a basic 'robots.txt' response
86        """
87        with open(self.robots_txt_path, 'rt') as f:
88            content = f.read()
89        response = self.request.response
90        if six.PY3:
91            response.text = content
92            response.content_type = 'text/plain'
93        else:
94            response.body = content
95            response.content_type = b'text/plain'
96        return response
97
98    def mobile_home(self):
99        """
100        Home page view for mobile.
101        """
102        return self.home(mobile=True)
103
104    def exception(self):
105        """
106        Generic exception view
107        """
108        return {'project_title': self.project_title}
109
110    def about(self):
111        """
112        Generic view to show "about project" info page.
113        """
114        return {
115            'project_title': self.project_title,
116            'project_version': self.project_version,
117            'packages': self.get_packages(),
118        }
119
120    def get_packages(self):
121        """
122        Should return the full set of packages which should be displayed on the
123        'about' page.
124        """
125        return OrderedDict([
126            ('rattail', rattail.__version__),
127            ('Tailbone', tailbone.__version__),
128        ])
129
130    def change_theme(self):
131        """
132        Simple view which can change user's visible UI theme, then redirect
133        user back to referring page.
134        """
135        theme = self.request.params.get('theme')
136        if theme:
137            try:
138                set_app_theme(self.request, theme, session=Session())
139            except Exception as error:
140                msg = "Failed to set theme: {}: {}".format(error.__class__.__name__, error)
141                self.request.session.flash(msg, 'error')
142            else:
143                self.request.session.flash("App theme has been changed to: {}".format(theme))
144        return self.redirect(self.request.get_referrer())
145
146    def change_db_engine(self):
147        """
148        Simple view which can change user's "current" database engine, of a
149        given type, then redirect back to referring page.
150        """
151        engine_type = self.request.POST.get('engine_type')
152        if engine_type:
153            dbkey = self.request.POST.get('dbkey')
154            if dbkey:
155                self.request.session['tailbone.engines.{}.current'.format(engine_type)] = dbkey
156                if self.rattail_config.getbool('tailbone', 'engines.flash_after_change', default=True):
157                    self.request.session.flash("Switched '{}' database to: {}".format(engine_type, dbkey))
158        return self.redirect(self.request.get_referrer())
159
160    def feedback(self):
161        """
162        Generic view to handle the user feedback form.
163        """
164        form = forms.Form(schema=Feedback(), request=self.request)
165        if form.validate(newstyle=True):
166            data = dict(form.validated)
167            if data['user']:
168                data['user_url'] = self.request.route_url('users.view', uuid=data['user'].uuid)
169            data['client_ip'] = self.request.client_addr
170            send_email(self.rattail_config, 'user_feedback', data=data)
171            return {'ok': True}
172        return {'error': "Form did not validate!"}
173
174    def mobile_feedback(self):
175        """
176        Generic view to handle the user feedback form on mobile.
177        """
178        return self.feedback()
179
180    def consume_batch_id(self):
181        """
182        Consume next batch ID from the PG sequence, and display via flash message.
183        """
184        batch_id = consume_batch_id(Session())
185        self.request.session.flash("Batch ID has been consumed: {:08d}".format(batch_id))
186        return self.redirect(self.request.get_referrer())
187
188    def bogus_error(self):
189        """
190        A special view which simply raises an error, for the sake of testing
191        uncaught exception handling.
192        """
193        raise Exception("Congratulations, you have triggered a bogus error.")
194
195    @classmethod
196    def defaults(cls, config):
197        cls._defaults(config)
198
199    @classmethod
200    def _defaults(cls, config):
201        rattail_config = config.registry.settings.get('rattail_config')
202
203        # auto-correct URLs which require trailing slash
204        config.add_notfound_view(cls, attr='notfound', append_slash=True)
205
206        # exception
207        if rattail_config and rattail_config.production():
208            config.add_exception_view(cls, attr='exception', renderer='/exception.mako')
209
210        # permissions
211        config.add_tailbone_permission_group('common', "(common)", overwrite=False)
212
213        # home
214        config.add_route('home', '/')
215        config.add_view(cls, attr='home', route_name='home', renderer='/home.mako')
216        config.add_route('mobile.home', '/mobile/')
217        config.add_view(cls, attr='mobile_home', route_name='mobile.home', renderer='/mobile/home.mako')
218
219        # robots.txt
220        config.add_route('robots.txt', '/robots.txt')
221        config.add_view(cls, attr='robots_txt', route_name='robots.txt')
222
223        # about
224        config.add_route('about', '/about')
225        config.add_view(cls, attr='about', route_name='about', renderer='/about.mako')
226        config.add_route('mobile.about', '/mobile/about')
227        config.add_view(cls, attr='about', route_name='mobile.about', renderer='/mobile/about.mako')
228
229        # change db engine
230        config.add_tailbone_permission('common', 'common.change_db_engine',
231                                       "Change which Database Engine is active (for user)")
232        config.add_route('change_db_engine', '/change-db-engine', request_method='POST')
233        config.add_view(cls, attr='change_db_engine', route_name='change_db_engine')
234
235        # change theme
236        config.add_tailbone_permission('common', 'common.change_app_theme',
237                                       "Change global App Template Theme")
238        config.add_route('change_theme', '/change-theme', request_method='POST')
239        config.add_view(cls, attr='change_theme', route_name='change_theme')
240
241        # feedback
242        config.add_route('feedback', '/feedback', request_method='POST')
243        config.add_view(cls, attr='feedback', route_name='feedback', renderer='json')
244        config.add_route('mobile.feedback', '/mobile/feedback', request_method='POST')
245        config.add_view(cls, attr='mobile_feedback', route_name='mobile.feedback', renderer='json')
246
247        # consume batch ID
248        config.add_tailbone_permission('common', 'common.consume_batch_id',
249                                       "Consume new Batch ID")
250        config.add_route('consume_batch_id', '/consume-batch-id')
251        config.add_view(cls, attr='consume_batch_id', route_name='consume_batch_id')
252
253        # bogus error
254        config.add_route('bogus_error', '/bogus-error')
255        config.add_view(cls, attr='bogus_error', route_name='bogus_error', permission='errors.bogus')
256
257
258def includeme(config):
259    CommonView.defaults(config)
Note: See TracBrowser for help on using the repository browser.