Coverage for .tox / coverage / lib / python3.11 / site-packages / wuttaweb / views / email.py: 100%
135 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-17 14:42 -0600
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-17 14:42 -0600
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# wuttaweb -- Web App for Wutta Framework
5# Copyright © 2024-2026 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Views for email settings
25"""
27import colander
29from wuttaweb.views import MasterView
30from wuttaweb.forms.schema import EmailRecipients
33class EmailSettingView(MasterView): # pylint: disable=abstract-method
34 """
35 Master view for :term:`email settings <email setting>`.
36 """
38 model_name = "email_setting"
39 model_key = "key"
40 model_title = "Email Setting"
41 url_prefix = "/email/settings"
42 filterable = False
43 sortable = True
44 sort_on_backend = False
45 paginated = False
46 creatable = False
47 deletable = False
49 labels = {
50 "key": "Email Key",
51 "replyto": "Reply-To",
52 }
54 grid_columns = [
55 "key",
56 "subject",
57 "to",
58 "enabled",
59 ]
61 # TODO: why does this not work?
62 sort_defaults = "key"
64 form_fields = [
65 "key",
66 "fallback_key",
67 "description",
68 "subject",
69 "sender",
70 "replyto",
71 "to",
72 "cc",
73 "bcc",
74 "notes",
75 "enabled",
76 ]
78 def __init__(self, request, context=None):
79 super().__init__(request, context=context)
80 self.email_handler = self.app.get_email_handler()
82 def get_grid_data(self, columns=None, session=None):
83 """
84 This view calls
85 :meth:`~wuttjamaican:wuttjamaican.email.EmailHandler.get_email_settings()`
86 on the :attr:`email_handler` to obtain its grid data.
87 """
88 data = []
89 for setting in self.email_handler.get_email_settings().values():
90 data.append(self.normalize_setting(setting))
91 return data
93 def normalize_setting(self, setting): # pylint: disable=empty-docstring
94 """ """
95 key = setting.__name__
96 setting = setting(self.config)
97 return {
98 "key": key,
99 "fallback_key": setting.fallback_key or "",
100 "description": setting.get_description() or "",
101 "subject": self.email_handler.get_auto_subject(
102 key, rendered=False, setting=setting
103 ),
104 "sender": self.email_handler.get_auto_sender(key),
105 "replyto": self.email_handler.get_auto_replyto(key) or colander.null,
106 "to": self.email_handler.get_auto_to(key),
107 "cc": self.email_handler.get_auto_cc(key),
108 "bcc": self.email_handler.get_auto_bcc(key),
109 "notes": self.email_handler.get_notes(key) or colander.null,
110 "enabled": self.email_handler.is_enabled(key),
111 }
113 def configure_grid(self, grid): # pylint: disable=empty-docstring
114 """ """
115 g = grid
116 super().configure_grid(g)
118 # key
119 g.set_searchable("key")
120 g.set_link("key")
122 # subject
123 g.set_searchable("subject")
124 g.set_link("subject")
126 # to
127 g.set_renderer("to", self.render_to_short)
129 # enabled
130 g.set_renderer("enabled", "boolean")
132 def render_to_short( # pylint: disable=empty-docstring,unused-argument
133 self, setting, field, value
134 ):
135 """ """
136 recips = value
137 if not recips:
138 return None
140 if len(recips) < 3:
141 return ", ".join(recips)
143 recips = ", ".join(recips[:2])
144 return f"{recips}, ..."
146 def get_instance( # pylint: disable=empty-docstring,arguments-differ,unused-argument
147 self, **kwargs
148 ):
149 """ """
150 key = self.request.matchdict["key"]
151 setting = self.email_handler.get_email_setting(key, instance=False)
152 if setting:
153 return self.normalize_setting(setting)
155 raise self.notfound()
157 def get_instance_title(self, instance): # pylint: disable=empty-docstring
158 """ """
159 setting = instance
160 return setting["subject"]
162 def configure_form(self, form): # pylint: disable=empty-docstring
163 """ """
164 f = form
165 super().configure_form(f)
167 # fallback_key
168 f.set_readonly("fallback_key")
170 # description
171 f.set_readonly("description")
172 f.set_widget("description", "notes")
174 # replyto
175 f.set_required("replyto", False)
177 # to
178 f.set_node("to", EmailRecipients())
180 # cc
181 f.set_node("cc", EmailRecipients())
183 # bcc
184 f.set_node("bcc", EmailRecipients())
186 # notes
187 f.set_widget("notes", "notes")
188 f.set_required("notes", False)
190 # enabled
191 f.set_node("enabled", colander.Boolean())
193 def persist( # pylint: disable=too-many-branches,empty-docstring,arguments-differ,unused-argument
194 self, setting, **kwargs
195 ):
196 """ """
197 session = self.Session()
198 key = self.request.matchdict["key"]
200 def save(name, value):
201 self.app.save_setting(
202 session, f"{self.config.appname}.email.{key}.{name}", value
203 )
205 def delete(name):
206 self.app.delete_setting(
207 session, f"{self.config.appname}.email.{key}.{name}"
208 )
210 # subject
211 if setting["subject"]:
212 save("subject", setting["subject"])
213 else:
214 delete("subject")
216 # sender
217 if setting["sender"]:
218 save("sender", setting["sender"])
219 else:
220 delete("sender")
222 # replyto
223 if setting["replyto"]:
224 save("replyto", setting["replyto"])
225 else:
226 delete("replyto")
228 # to
229 if setting["to"]:
230 save("to", setting["to"])
231 else:
232 delete("to")
234 # cc
235 if setting["cc"]:
236 save("cc", setting["cc"])
237 else:
238 delete("cc")
240 # bcc
241 if setting["bcc"]:
242 save("bcc", setting["bcc"])
243 else:
244 delete("bcc")
246 # notes
247 if setting["notes"]:
248 save("notes", setting["notes"])
249 else:
250 delete("notes")
252 # enabled
253 save("enabled", "true" if setting["enabled"] else "false")
255 def render_to_response(self, template, context): # pylint: disable=empty-docstring
256 """ """
257 if self.viewing:
258 setting = context["instance"]
259 context["setting"] = setting
261 context["has_html_template"] = self.email_handler.get_auto_body_template(
262 setting["key"], "html", fallback_key=setting["fallback_key"]
263 )
264 context["has_txt_template"] = self.email_handler.get_auto_body_template(
265 setting["key"], "txt", fallback_key=setting["fallback_key"]
266 )
268 return super().render_to_response(template, context)
270 def preview(self):
271 """
272 View for showing a rendered preview of a given email template.
274 This will render the email template according to the "mode"
275 requested - i.e. HTML or TXT.
276 """
277 key = self.request.matchdict["key"]
278 setting = self.email_handler.get_email_setting(key)
279 context = setting.sample_data()
280 mode = self.request.params.get("mode", "html")
282 if mode == "txt":
283 body = self.email_handler.get_auto_txt_body(
284 key, context, fallback_key=setting.fallback_key
285 )
286 self.request.response.content_type = "text/plain"
288 else: # html
289 body = self.email_handler.get_auto_html_body(
290 key, context, fallback_key=setting.fallback_key
291 )
293 self.request.response.text = body
294 return self.request.response
296 @classmethod
297 def defaults(cls, config): # pylint: disable=empty-docstring
298 """ """
299 cls._email_defaults(config)
300 cls._defaults(config)
302 @classmethod
303 def _email_defaults(cls, config):
304 """ """
305 route_prefix = cls.get_route_prefix()
306 permission_prefix = cls.get_permission_prefix()
307 model_title_plural = cls.get_model_title_plural()
308 instance_url_prefix = cls.get_instance_url_prefix()
310 # fix permission group
311 config.add_wutta_permission_group(
312 permission_prefix, model_title_plural, overwrite=False
313 )
315 # preview
316 config.add_route(f"{route_prefix}.preview", f"{instance_url_prefix}/preview")
317 config.add_view(
318 cls,
319 attr="preview",
320 route_name=f"{route_prefix}.preview",
321 permission=f"{permission_prefix}.view",
322 )
325def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
326 base = globals()
328 EmailSettingView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
329 "EmailSettingView", base["EmailSettingView"]
330 )
331 EmailSettingView.defaults(config)
334def includeme(config): # pylint: disable=missing-function-docstring
335 defaults(config)