source: tailbone/tailbone/views/common.py @ 3789e4b

Last change on this file since 3789e4b was 3789e4b, checked in by Lance Edgar <ledgar@…>, 11 months ago

Don't require user name for anonymous feedback msg

  • Property mode set to 100644
File size: 8.2 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 feedback(self):
147        """
148        Generic view to handle the user feedback form.
149        """
150        form = forms.Form(schema=Feedback(), request=self.request)
151        if form.validate(newstyle=True):
152            data = dict(form.validated)
153            if data['user']:
154                data['user_url'] = self.request.route_url('users.view', uuid=data['user'].uuid)
155            data['client_ip'] = self.request.client_addr
156            send_email(self.rattail_config, 'user_feedback', data=data)
157            return {'ok': True}
158        return {'error': "Form did not validate!"}
159
160    def mobile_feedback(self):
161        """
162        Generic view to handle the user feedback form on mobile.
163        """
164        return self.feedback()
165
166    def consume_batch_id(self):
167        """
168        Consume next batch ID from the PG sequence, and display via flash message.
169        """
170        batch_id = consume_batch_id(Session())
171        self.request.session.flash("Batch ID has been consumed: {:08d}".format(batch_id))
172        return self.redirect(self.request.get_referrer())
173
174    def bogus_error(self):
175        """
176        A special view which simply raises an error, for the sake of testing
177        uncaught exception handling.
178        """
179        raise Exception("Congratulations, you have triggered a bogus error.")
180
181    @classmethod
182    def defaults(cls, config):
183        cls._defaults(config)
184
185    @classmethod
186    def _defaults(cls, config):
187        rattail_config = config.registry.settings.get('rattail_config')
188
189        # auto-correct URLs which require trailing slash
190        config.add_notfound_view(cls, attr='notfound', append_slash=True)
191
192        # exception
193        if rattail_config and rattail_config.production():
194            config.add_exception_view(cls, attr='exception', renderer='/exception.mako')
195
196        # permissions
197        config.add_tailbone_permission_group('common', "(common)", overwrite=False)
198
199        # home
200        config.add_route('home', '/')
201        config.add_view(cls, attr='home', route_name='home', renderer='/home.mako')
202        config.add_route('mobile.home', '/mobile/')
203        config.add_view(cls, attr='mobile_home', route_name='mobile.home', renderer='/mobile/home.mako')
204
205        # robots.txt
206        config.add_route('robots.txt', '/robots.txt')
207        config.add_view(cls, attr='robots_txt', route_name='robots.txt')
208
209        # about
210        config.add_route('about', '/about')
211        config.add_view(cls, attr='about', route_name='about', renderer='/about.mako')
212        config.add_route('mobile.about', '/mobile/about')
213        config.add_view(cls, attr='about', route_name='mobile.about', renderer='/mobile/about.mako')
214
215        # change theme
216        config.add_tailbone_permission('common', 'common.change_app_theme',
217                                       "Change global App Template Theme")
218        config.add_route('change_theme', '/change-theme', request_method='POST')
219        config.add_view(cls, attr='change_theme', route_name='change_theme')
220
221        # feedback
222        config.add_route('feedback', '/feedback', request_method='POST')
223        config.add_view(cls, attr='feedback', route_name='feedback', renderer='json')
224        config.add_route('mobile.feedback', '/mobile/feedback', request_method='POST')
225        config.add_view(cls, attr='mobile_feedback', route_name='mobile.feedback', renderer='json')
226
227        # consume batch ID
228        config.add_tailbone_permission('common', 'common.consume_batch_id',
229                                       "Consume new Batch ID")
230        config.add_route('consume_batch_id', '/consume-batch-id')
231        config.add_view(cls, attr='consume_batch_id', route_name='consume_batch_id')
232
233        # bogus error
234        config.add_route('bogus_error', '/bogus-error')
235        config.add_view(cls, attr='bogus_error', route_name='bogus_error', permission='errors.bogus')
236
237
238def includeme(config):
239    CommonView.defaults(config)
Note: See TracBrowser for help on using the repository browser.