3
0
Fork 0

feat: add feature to edit email settings, basic message preview

This commit is contained in:
Lance Edgar 2024-12-23 19:24:17 -06:00
parent 3035d1f58a
commit 95ff87fbf3
19 changed files with 826 additions and 4 deletions

View file

@ -0,0 +1,6 @@
``wuttaweb.emails``
===================
.. automodule:: wuttaweb.emails
:members:

View file

@ -0,0 +1,6 @@
``wuttaweb.views.email``
========================
.. automodule:: wuttaweb.views.email
:members:

View file

@ -35,6 +35,7 @@ the narrative docs are pretty scant. That will eventually change.
api/wuttaweb.db
api/wuttaweb.db.continuum
api/wuttaweb.db.sess
api/wuttaweb.emails
api/wuttaweb.forms
api/wuttaweb.forms.base
api/wuttaweb.forms.schema
@ -54,6 +55,7 @@ the narrative docs are pretty scant. That will eventually change.
api/wuttaweb.views.base
api/wuttaweb.views.batch
api/wuttaweb.views.common
api/wuttaweb.views.email
api/wuttaweb.views.essential
api/wuttaweb.views.master
api/wuttaweb.views.people

View file

@ -43,9 +43,12 @@ log = logging.getLogger(__name__)
class WebAppProvider(AppProvider):
"""
The :term:`app provider` for WuttaWeb. This adds some methods to
the :term:`app handler`, which are specific to web apps.
the :term:`app handler`, which are specific to web apps. It also
registers some :term:`email templates <email template>` for the
app, etc.
"""
email_templates = 'wuttaweb:email/templates'
email_modules = ['wuttaweb.emails']
email_templates = ['wuttaweb:email-templates']
def get_web_handler(self, **kwargs):
"""

48
src/wuttaweb/emails.py Normal file
View file

@ -0,0 +1,48 @@
# -*- 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/>.
#
################################################################################
"""
:term:`Email Settings <email setting>` for WuttaWeb
"""
from wuttjamaican.email import EmailSetting
class feedback(EmailSetting):
"""
Sent when user submits feedback via the web app.
"""
default_subject = "User Feedback"
def sample_data(self):
""" """
model = self.app.model
person = model.Person(full_name="Barney Rubble")
user = model.User(username='barney', person=person)
return {
'user': user,
'user_name': str(person),
'user_url': '#',
'referrer': 'http://example.com/',
'client_ip': '127.0.0.1',
'message': "This app is cool but needs a new feature.\n\nAllow me to describe...",
}

View file

@ -30,9 +30,11 @@ import uuid as _uuid
import colander
import sqlalchemy as sa
from wuttjamaican.db.model import Person
from wuttjamaican.conf import parse_list
from wuttaweb.db import Session
from wuttaweb.forms import widgets
from wuttjamaican.db.model import Person
class WuttaDateTime(colander.DateTime):
@ -569,5 +571,36 @@ class FileDownload(colander.String):
return widgets.FileDownloadWidget(self.request, **kwargs)
class EmailRecipients(colander.String):
"""
Custom schema type for :term:`email setting` recipient fields
(``To``, ``Cc``, ``Bcc``).
"""
def serialize(self, node, appstruct):
if appstruct is colander.null:
return colander.null
return '\n'.join(parse_list(appstruct))
def deserialize(self, node, cstruct):
""" """
if cstruct is colander.null:
return colander.null
values = [value for value in parse_list(cstruct)
if value]
return ', '.join(values)
def widget_maker(self, **kwargs):
"""
Constructs a default widget for the field.
:returns: Instance of
:class:`~wuttaweb.forms.widgets.EmailRecipientsWidget`.
"""
return widgets.EmailRecipientsWidget(**kwargs)
# nb. colanderalchemy schema overrides
sa.DateTime.__colanderalchemy_config__ = {'typ': WuttaDateTime}

View file

@ -51,6 +51,8 @@ from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
DateTimeInputWidget, MoneyInputWidget)
from webhelpers2.html import HTML
from wuttjamaican.conf import parse_list
from wuttaweb.db import Session
from wuttaweb.grids import Grid
@ -423,6 +425,41 @@ class PermissionsWidget(WuttaCheckboxChoiceWidget):
return super().serialize(field, cstruct, **kw)
class EmailRecipientsWidget(TextAreaWidget):
"""
Widget for :term:`email setting` recipient fields (``To``, ``Cc``,
``Bcc``).
This is a subclass of
:class:`deform:deform.widget.TextAreaWidget`. It uses these
Deform templates:
* ``textarea``
* ``readonly/email_recips``
See also the :class:`~wuttaweb.forms.schema.EmailRecipients`
schema type, which uses this widget.
"""
readonly_template = 'readonly/email_recips'
def serialize(self, field, cstruct, **kw):
""" """
readonly = kw.get('readonly', self.readonly)
if readonly:
kw['recips'] = parse_list(cstruct or '')
return super().serialize(field, cstruct, **kw)
def deserialize(self, field, pstruct):
""" """
if pstruct is colander.null:
return colander.null
values = [value for value in parse_list(pstruct)
if value]
return ', '.join(values)
class BatchIdWidget(Widget):
"""
Widget for use with the

View file

@ -174,6 +174,12 @@ class MenuHandler(GenericHandler):
'perm': 'permissions.list',
},
{'type': 'sep'},
{
'title': "Email Settings",
'route': 'email_settings',
'perm': 'email_settings.list',
},
{'type': 'sep'},
{
'title': "App Info",
'route': 'appinfo',

View file

@ -0,0 +1,5 @@
<ul>
<tal:loop tal:repeat="recip recips">
<li>${recip}</li>
</tal:loop>
</ul>

View file

@ -0,0 +1,39 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/view.mako" />
<%def name="tool_panels()">
${parent.tool_panels()}
${self.tool_panel_preview()}
</%def>
<%def name="tool_panel_preview()">
<wutta-tool-panel heading="Email Preview">
<b-button type="is-primary"
% if has_html_template:
tag="a" target="_blank"
href="${master.get_action_url('preview', setting)}?mode=html"
% else:
disabled
title="HTML template not found"
% endif
icon-pack="fas"
icon-left="external-link-alt">
Preview HTML
</b-button>
<b-button type="is-primary"
% if has_txt_template:
tag="a" target="_blank"
href="${master.get_action_url('preview', setting)}?mode=txt"
% else:
disabled
title="TXT template not found"
% endif
icon-pack="fas"
icon-left="external-link-alt">
Preview TXT
</b-button>
</wutta-tool-panel>
</%def>

298
src/wuttaweb/views/email.py Normal file
View file

@ -0,0 +1,298 @@
# -*- 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/>.
#
################################################################################
"""
Views for email settings
"""
import colander
from wuttaweb.views import MasterView
from wuttaweb.forms.schema import EmailRecipients
class EmailSettingView(MasterView):
"""
Master view for :term:`email settings <email setting>`.
"""
model_name = 'email_setting'
model_key = 'key'
model_title = "Email Setting"
url_prefix = '/email/settings'
filterable = False
sortable = True
sort_on_backend = False
paginated = False
creatable = False
deletable = False
labels = {
'key': "Email Key",
'replyto': "Reply-To",
}
grid_columns = [
'key',
'subject',
'to',
'enabled',
]
# TODO: why does this not work?
sort_defaults = 'key'
form_fields = [
'key',
'description',
'subject',
'sender',
'replyto',
'to',
'cc',
'bcc',
'notes',
'enabled',
]
def __init__(self, request, context=None):
super().__init__(request, context=context)
self.email_handler = self.app.get_email_handler()
def get_grid_data(self, columns=None, session=None):
"""
This view calls
:meth:`~wuttjamaican:wuttjamaican.email.EmailHandler.get_email_settings()`
on the :attr:`email_handler` to obtain its grid data.
"""
data = []
for setting in self.email_handler.get_email_settings().values():
data.append(self.normalize_setting(setting))
return data
def normalize_setting(self, setting):
""" """
key = setting.__name__
return {
'key': key,
'description': setting.__doc__,
'subject': self.email_handler.get_auto_subject(key, rendered=False, setting=setting),
'sender': self.email_handler.get_auto_sender(key),
'replyto': self.email_handler.get_auto_replyto(key) or colander.null,
'to': self.email_handler.get_auto_to(key),
'cc': self.email_handler.get_auto_cc(key),
'bcc': self.email_handler.get_auto_bcc(key),
'notes': self.email_handler.get_notes(key) or colander.null,
'enabled': self.email_handler.is_enabled(key),
}
def configure_grid(self, g):
""" """
super().configure_grid(g)
# key
g.set_searchable('key')
g.set_link('key')
# subject
g.set_searchable('subject')
g.set_link('subject')
# to
g.set_renderer('to', self.render_to_short)
def render_to_short(self, setting, field, value):
""" """
recips = value
if not recips:
return
if len(recips) < 3:
return ', '.join(recips)
recips = ', '.join(recips[:2])
return f"{recips}, ..."
def get_instance(self):
""" """
key = self.request.matchdict['key']
setting = self.email_handler.get_email_setting(key, instance=False)
if setting:
return self.normalize_setting(setting)
raise self.notfound()
def get_instance_title(self, setting):
""" """
return setting['subject']
def configure_form(self, f):
""" """
super().configure_form(f)
# description
f.set_readonly('description')
# replyto
f.set_required('replyto', False)
# to
f.set_node('to', EmailRecipients())
# cc
f.set_node('cc', EmailRecipients())
# bcc
f.set_node('bcc', EmailRecipients())
# notes
f.set_widget('notes', 'notes')
f.set_required('notes', False)
# enabled
f.set_node('enabled', colander.Boolean())
def persist(self, setting):
""" """
session = self.Session()
key = self.request.matchdict['key']
def save(name, value):
self.app.save_setting(session, f'{self.config.appname}.email.{key}.{name}', value)
def delete(name):
self.app.delete_setting(session, f'{self.config.appname}.email.{key}.{name}')
# subject
if setting['subject']:
save('subject', setting['subject'])
else:
delete('subject')
# sender
if setting['sender']:
save('sender', setting['sender'])
else:
delete('sender')
# replyto
if setting['replyto']:
save('replyto', setting['replyto'])
else:
delete('replyto')
# to
if setting['to']:
save('to', setting['to'])
else:
delete('to')
# cc
if setting['cc']:
save('cc', setting['cc'])
else:
delete('cc')
# bcc
if setting['bcc']:
save('bcc', setting['bcc'])
else:
delete('bcc')
# notes
if setting['notes']:
save('notes', setting['notes'])
else:
delete('notes')
# enabled
save('enabled', 'true' if setting['enabled'] else 'false')
def render_to_response(self, template, context):
""" """
if self.viewing:
setting = context['instance']
context['setting'] = setting
context['has_html_template'] = self.email_handler.get_auto_body_template(
setting['key'], 'html')
context['has_txt_template'] = self.email_handler.get_auto_body_template(
setting['key'], 'txt')
return super().render_to_response(template, context)
def preview(self):
"""
View for showing a rendered preview of a given email template.
This will render the email template according to the "mode"
requested - i.e. HTML or TXT.
"""
key = self.request.matchdict['key']
setting = self.email_handler.get_email_setting(key)
context = setting.sample_data()
mode = self.request.params.get('mode', 'html')
if mode == 'txt':
body = self.email_handler.get_auto_txt_body(key, context)
self.request.response.content_type = 'text/plain'
else: # html
body = self.email_handler.get_auto_html_body(key, context)
self.request.response.text = body
return self.request.response
@classmethod
def defaults(cls, config):
""" """
cls._email_defaults(config)
cls._defaults(config)
@classmethod
def _email_defaults(cls, config):
""" """
route_prefix = cls.get_route_prefix()
permission_prefix = cls.get_permission_prefix()
model_title_plural = cls.get_model_title_plural()
instance_url_prefix = cls.get_instance_url_prefix()
# fix permission group
config.add_wutta_permission_group(permission_prefix,
model_title_plural,
overwrite=False)
# preview
config.add_route(f'{route_prefix}.preview',
f'{instance_url_prefix}/preview')
config.add_view(cls, attr='preview',
route_name=f'{route_prefix}.preview',
permission=f'{permission_prefix}.view')
def defaults(config, **kwargs):
base = globals()
EmailSettingView = kwargs.get('EmailSettingView', base['EmailSettingView'])
EmailSettingView.defaults(config)
def includeme(config):
defaults(config)

View file

@ -31,6 +31,7 @@ That will in turn include the following modules:
* :mod:`wuttaweb.views.common`
* :mod:`wuttaweb.views.auth`
* :mod:`wuttaweb.views.email`
* :mod:`wuttaweb.views.settings`
* :mod:`wuttaweb.views.progress`
* :mod:`wuttaweb.views.people`
@ -45,6 +46,7 @@ def defaults(config, **kwargs):
config.include(mod('wuttaweb.views.common'))
config.include(mod('wuttaweb.views.auth'))
config.include(mod('wuttaweb.views.email'))
config.include(mod('wuttaweb.views.settings'))
config.include(mod('wuttaweb.views.progress'))
config.include(mod('wuttaweb.views.people'))

View file

@ -392,3 +392,47 @@ class TestFileDownload(DataTestCase):
widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.FileDownloadWidget)
self.assertEqual(widget.url, '/foo')
class TestEmailRecipients(TestCase):
def test_serialize(self):
typ = mod.EmailRecipients()
node = colander.SchemaNode(typ)
recips = [
'alice@example.com',
'bob@example.com',
]
recips_str = ', '.join(recips)
# values
result = typ.serialize(node, recips_str)
self.assertEqual(result, '\n'.join(recips))
# null
result = typ.serialize(node, colander.null)
self.assertIs(result, colander.null)
def test_deserialize(self):
typ = mod.EmailRecipients()
node = colander.SchemaNode(typ)
recips = [
'alice@example.com',
'bob@example.com',
]
recips_str = ', '.join(recips)
# values
result = typ.deserialize(node, recips_str)
self.assertEqual(result, recips_str)
# null
result = typ.deserialize(node, colander.null)
self.assertIs(result, colander.null)
def test_widget_maker(self):
typ = mod.EmailRecipients()
widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.EmailRecipientsWidget)

View file

@ -10,7 +10,7 @@ from pyramid import testing
from wuttaweb import grids
from wuttaweb.forms import widgets as mod
from wuttaweb.forms.schema import (FileDownload, PersonRef, RoleRefs, UserRefs, Permissions,
WuttaDateTime)
WuttaDateTime, EmailRecipients)
from tests.util import WebTestCase
@ -304,6 +304,55 @@ class TestPermissionsWidget(WebTestCase):
self.assertIn("Polish the widgets", html)
class TestEmailRecipientsWidget(WebTestCase):
def make_field(self, node, **kwargs):
# TODO: not sure why default renderer is in use even though
# pyramid_deform was included in setup? but this works..
kwargs.setdefault('renderer', deform.Form.default_renderer)
return deform.Field(node, **kwargs)
def test_serialize(self):
node = colander.SchemaNode(EmailRecipients())
field = self.make_field(node)
widget = mod.EmailRecipientsWidget()
recips = [
'alice@example.com',
'bob@example.com',
]
recips_str = ', '.join(recips)
# readonly
result = widget.serialize(field, recips_str, readonly=True)
self.assertIn('<ul>', result)
self.assertIn('<li>alice@example.com</li>', result)
# editable
result = widget.serialize(field, recips_str)
self.assertIn('<b-input', result)
self.assertIn('type="textarea"', result)
def test_deserialize(self):
node = colander.SchemaNode(EmailRecipients())
field = self.make_field(node)
widget = mod.EmailRecipientsWidget()
recips = [
'alice@example.com',
'bob@example.com',
]
recips_str = ', '.join(recips)
# values
result = widget.deserialize(field, recips_str)
self.assertEqual(result, recips_str)
# null
result = widget.deserialize(field, colander.null)
self.assertIs(result, colander.null)
class TestBatchIdWidget(WebTestCase):
def make_field(self, node, **kwargs):

23
tests/test_emails.py Normal file
View file

@ -0,0 +1,23 @@
# -*- coding: utf-8; -*-
from wuttjamaican.testing import DataTestCase
from wuttjamaican.email import EmailSetting
from wuttaweb import emails as mod
class TestAllSettings(DataTestCase):
def check_setting(self, setting):
self.assertIsNotNone(setting.default_subject)
setting = setting(self.config)
context = setting.sample_data()
self.assertIsInstance(context, dict)
def test_all(self):
for name in dir(mod):
obj = getattr(mod, name)
if (isinstance(obj, type)
and obj is not EmailSetting
and issubclass(obj, EmailSetting)):
self.check_setting(obj)

211
tests/views/test_email.py Normal file
View file

@ -0,0 +1,211 @@
# -*- coding: utf-8; -*-
from unittest.mock import patch
from wuttjamaican.email import EmailSetting
import colander
from pyramid.httpexceptions import HTTPNotFound
from pyramid.response import Response
from wuttaweb.views import email as mod
from tests.util import WebTestCase
class TestEmailSettingViews(WebTestCase):
def make_view(self):
return mod.EmailSettingView(self.request)
def test_includeme(self):
self.pyramid_config.include('wuttaweb.views.email')
def test_get_grid_data(self):
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
view = self.make_view()
data = view.get_grid_data()
self.assertIsInstance(data, list)
self.assertTrue(data) # 1+ items
setting = data[0]
self.assertIn('key', setting)
self.assertIn('subject', setting)
self.assertIn('sender', setting)
self.assertIn('to', setting)
self.assertIn('cc', setting)
self.assertIn('notes', setting)
def test_configure_grid(self):
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
view = self.make_view()
grid = view.make_model_grid()
self.assertIn('key', grid.searchable_columns)
self.assertIn('subject', grid.searchable_columns)
def test_render_to_short(self):
view = self.make_view()
setting = EmailSetting(self.config)
# more than 2 recips
result = view.render_to_short(setting, 'to', [
'alice@example.com',
'bob@example.com',
'charlie@example.com',
'diana@example.com',
])
self.assertEqual(result, 'alice@example.com, bob@example.com, ...')
# just 2 recips
result = view.render_to_short(setting, 'to', [
'alice@example.com',
'bob@example.com',
])
self.assertEqual(result, 'alice@example.com, bob@example.com')
# just 1 recip
result = view.render_to_short(setting, 'to', ['alice@example.com'])
self.assertEqual(result, 'alice@example.com')
# no recips
result = view.render_to_short(setting, 'to', [])
self.assertIsNone(result)
def test_get_instance(self):
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
view = self.make_view()
# normal
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
setting = view.get_instance()
self.assertIsInstance(setting, dict)
self.assertIn('key', setting)
self.assertIn('sender', setting)
self.assertIn('subject', setting)
self.assertIn('to', setting)
self.assertIn('cc', setting)
self.assertIn('notes', setting)
self.assertIn('enabled', setting)
# not found
with patch.object(self.request, 'matchdict', new={'key': 'this-should_notEXIST'}):
self.assertRaises(HTTPNotFound, view.get_instance)
def test_get_instance_title(self):
view = self.make_view()
result = view.get_instance_title({'subject': 'whatever'})
self.assertEqual(result, 'whatever')
def test_configure_form(self):
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
view = self.make_view()
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
setting = view.get_instance()
form = view.make_model_form(setting)
self.assertIn('description', form.readonly_fields)
self.assertFalse(form.required_fields['replyto'])
def test_persist(self):
model = self.app.model
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
view = self.make_view()
# start w/ no settings in db
self.assertEqual(self.session.query(model.Setting).count(), 0)
# "edit" settings for feedback email
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
setting = view.get_instance()
setting['subject'] = 'Testing Feedback'
setting['sender'] = 'feedback@example.com'
setting['replyto'] = 'feedback4@example.com'
setting['to'] = 'feedback@example.com'
setting['cc'] = 'feedback2@example.com'
setting['bcc'] = 'feedback3@example.com'
setting['notes'] = "did this work?"
setting['enabled'] = True
# persist email settings
with patch.object(view, 'Session', return_value=self.session):
view.persist(setting)
self.session.commit()
# check settings in db
self.assertEqual(self.session.query(model.Setting).count(), 8)
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.subject'),
"Testing Feedback")
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.sender'),
'feedback@example.com')
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.replyto'),
'feedback4@example.com')
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.to'),
'feedback@example.com')
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.cc'),
'feedback2@example.com')
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.bcc'),
'feedback3@example.com')
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.notes'),
"did this work?")
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.enabled'),
'true')
# "edit" settings for feedback email
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
setting = view.get_instance()
setting['subject'] = None
setting['sender'] = None
setting['replyto'] = None
setting['to'] = None
setting['cc'] = None
setting['bcc'] = None
setting['notes'] = None
setting['enabled'] = False
# persist email settings
with patch.object(view, 'Session', return_value=self.session):
view.persist(setting)
self.session.commit()
# check settings in db
self.assertEqual(self.session.query(model.Setting).count(), 1)
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.subject'))
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.sender'))
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.replyto'))
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.to'))
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.cc'))
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.bcc'))
self.assertIsNone(self.app.get_setting(self.session, 'wutta.email.feedback.notes'))
self.assertEqual(self.app.get_setting(self.session, 'wutta.email.feedback.enabled'),
'false')
def test_render_to_response(self):
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
self.pyramid_config.add_route('home', '/')
self.pyramid_config.add_route('login', '/auth/login')
self.pyramid_config.add_route('email_settings', '/email/settings')
self.pyramid_config.add_route('email_settings.preview', '/email/settings/{key}/preview')
view = self.make_view()
# nb. this gives coverage, but tests nothing..
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
setting = view.get_instance()
with patch.object(view, 'viewing', new=True):
context = {'instance': setting}
response = view.render_to_response('view', context)
self.assertIsInstance(response, Response)
def test_preview(self):
self.config.setdefault('wutta.email.default.sender', 'test@example.com')
view = self.make_view()
# nb. this gives coverage, but tests nothing..
with patch.object(self.request, 'matchdict', new={'key': 'feedback'}):
# html
with patch.object(self.request, 'params', new={'mode': 'html'}):
response = view.preview()
self.assertEqual(response.content_type, 'text/html')
# txt
with patch.object(self.request, 'params', new={'mode': 'txt'}):
response = view.preview()
self.assertEqual(response.content_type, 'text/plain')

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8; -*-
from wuttaweb.views import essential as mod
from tests.util import WebTestCase
class TestEssentialViews(WebTestCase):
def test_includeme(self):
self.pyramid_config.include('wuttaweb.views.essential')