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
api/wuttaweb.db.continuum api/wuttaweb.db.continuum
api/wuttaweb.db.sess api/wuttaweb.db.sess
api/wuttaweb.emails
api/wuttaweb.forms api/wuttaweb.forms
api/wuttaweb.forms.base api/wuttaweb.forms.base
api/wuttaweb.forms.schema 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.base
api/wuttaweb.views.batch api/wuttaweb.views.batch
api/wuttaweb.views.common api/wuttaweb.views.common
api/wuttaweb.views.email
api/wuttaweb.views.essential api/wuttaweb.views.essential
api/wuttaweb.views.master api/wuttaweb.views.master
api/wuttaweb.views.people api/wuttaweb.views.people

View file

@ -43,9 +43,12 @@ log = logging.getLogger(__name__)
class WebAppProvider(AppProvider): class WebAppProvider(AppProvider):
""" """
The :term:`app provider` for WuttaWeb. This adds some methods to 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): 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 colander
import sqlalchemy as sa import sqlalchemy as sa
from wuttjamaican.db.model import Person
from wuttjamaican.conf import parse_list
from wuttaweb.db import Session from wuttaweb.db import Session
from wuttaweb.forms import widgets from wuttaweb.forms import widgets
from wuttjamaican.db.model import Person
class WuttaDateTime(colander.DateTime): class WuttaDateTime(colander.DateTime):
@ -569,5 +571,36 @@ class FileDownload(colander.String):
return widgets.FileDownloadWidget(self.request, **kwargs) 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 # nb. colanderalchemy schema overrides
sa.DateTime.__colanderalchemy_config__ = {'typ': WuttaDateTime} sa.DateTime.__colanderalchemy_config__ = {'typ': WuttaDateTime}

View file

@ -51,6 +51,8 @@ from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
DateTimeInputWidget, MoneyInputWidget) DateTimeInputWidget, MoneyInputWidget)
from webhelpers2.html import HTML from webhelpers2.html import HTML
from wuttjamaican.conf import parse_list
from wuttaweb.db import Session from wuttaweb.db import Session
from wuttaweb.grids import Grid from wuttaweb.grids import Grid
@ -423,6 +425,41 @@ class PermissionsWidget(WuttaCheckboxChoiceWidget):
return super().serialize(field, cstruct, **kw) 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): class BatchIdWidget(Widget):
""" """
Widget for use with the Widget for use with the

View file

@ -174,6 +174,12 @@ class MenuHandler(GenericHandler):
'perm': 'permissions.list', 'perm': 'permissions.list',
}, },
{'type': 'sep'}, {'type': 'sep'},
{
'title': "Email Settings",
'route': 'email_settings',
'perm': 'email_settings.list',
},
{'type': 'sep'},
{ {
'title': "App Info", 'title': "App Info",
'route': 'appinfo', '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.common`
* :mod:`wuttaweb.views.auth` * :mod:`wuttaweb.views.auth`
* :mod:`wuttaweb.views.email`
* :mod:`wuttaweb.views.settings` * :mod:`wuttaweb.views.settings`
* :mod:`wuttaweb.views.progress` * :mod:`wuttaweb.views.progress`
* :mod:`wuttaweb.views.people` * :mod:`wuttaweb.views.people`
@ -45,6 +46,7 @@ def defaults(config, **kwargs):
config.include(mod('wuttaweb.views.common')) config.include(mod('wuttaweb.views.common'))
config.include(mod('wuttaweb.views.auth')) config.include(mod('wuttaweb.views.auth'))
config.include(mod('wuttaweb.views.email'))
config.include(mod('wuttaweb.views.settings')) config.include(mod('wuttaweb.views.settings'))
config.include(mod('wuttaweb.views.progress')) config.include(mod('wuttaweb.views.progress'))
config.include(mod('wuttaweb.views.people')) config.include(mod('wuttaweb.views.people'))

View file

@ -392,3 +392,47 @@ class TestFileDownload(DataTestCase):
widget = typ.widget_maker() widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.FileDownloadWidget) self.assertIsInstance(widget, widgets.FileDownloadWidget)
self.assertEqual(widget.url, '/foo') 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 import grids
from wuttaweb.forms import widgets as mod from wuttaweb.forms import widgets as mod
from wuttaweb.forms.schema import (FileDownload, PersonRef, RoleRefs, UserRefs, Permissions, from wuttaweb.forms.schema import (FileDownload, PersonRef, RoleRefs, UserRefs, Permissions,
WuttaDateTime) WuttaDateTime, EmailRecipients)
from tests.util import WebTestCase from tests.util import WebTestCase
@ -304,6 +304,55 @@ class TestPermissionsWidget(WebTestCase):
self.assertIn("Polish the widgets", html) 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): class TestBatchIdWidget(WebTestCase):
def make_field(self, node, **kwargs): 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')