Coverage for .tox / coverage / lib / python3.11 / site-packages / wuttaweb / views / email.py: 100%
134 statements
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 15:23 -0600
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 15:23 -0600
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# wuttaweb -- Web App for Wutta Framework
5# Copyright © 2024-2025 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 def render_to_short( # pylint: disable=empty-docstring,unused-argument
130 self, setting, field, value
131 ):
132 """ """
133 recips = value
134 if not recips:
135 return None
137 if len(recips) < 3:
138 return ", ".join(recips)
140 recips = ", ".join(recips[:2])
141 return f"{recips}, ..."
143 def get_instance( # pylint: disable=empty-docstring,arguments-differ,unused-argument
144 self, **kwargs
145 ):
146 """ """
147 key = self.request.matchdict["key"]
148 setting = self.email_handler.get_email_setting(key, instance=False)
149 if setting:
150 return self.normalize_setting(setting)
152 raise self.notfound()
154 def get_instance_title(self, instance): # pylint: disable=empty-docstring
155 """ """
156 setting = instance
157 return setting["subject"]
159 def configure_form(self, form): # pylint: disable=empty-docstring
160 """ """
161 f = form
162 super().configure_form(f)
164 # fallback_key
165 f.set_readonly("fallback_key")
167 # description
168 f.set_readonly("description")
169 f.set_widget("description", "notes")
171 # replyto
172 f.set_required("replyto", False)
174 # to
175 f.set_node("to", EmailRecipients())
177 # cc
178 f.set_node("cc", EmailRecipients())
180 # bcc
181 f.set_node("bcc", EmailRecipients())
183 # notes
184 f.set_widget("notes", "notes")
185 f.set_required("notes", False)
187 # enabled
188 f.set_node("enabled", colander.Boolean())
190 def persist( # pylint: disable=too-many-branches,empty-docstring,arguments-differ,unused-argument
191 self, setting, **kwargs
192 ):
193 """ """
194 session = self.Session()
195 key = self.request.matchdict["key"]
197 def save(name, value):
198 self.app.save_setting(
199 session, f"{self.config.appname}.email.{key}.{name}", value
200 )
202 def delete(name):
203 self.app.delete_setting(
204 session, f"{self.config.appname}.email.{key}.{name}"
205 )
207 # subject
208 if setting["subject"]:
209 save("subject", setting["subject"])
210 else:
211 delete("subject")
213 # sender
214 if setting["sender"]:
215 save("sender", setting["sender"])
216 else:
217 delete("sender")
219 # replyto
220 if setting["replyto"]:
221 save("replyto", setting["replyto"])
222 else:
223 delete("replyto")
225 # to
226 if setting["to"]:
227 save("to", setting["to"])
228 else:
229 delete("to")
231 # cc
232 if setting["cc"]:
233 save("cc", setting["cc"])
234 else:
235 delete("cc")
237 # bcc
238 if setting["bcc"]:
239 save("bcc", setting["bcc"])
240 else:
241 delete("bcc")
243 # notes
244 if setting["notes"]:
245 save("notes", setting["notes"])
246 else:
247 delete("notes")
249 # enabled
250 save("enabled", "true" if setting["enabled"] else "false")
252 def render_to_response(self, template, context): # pylint: disable=empty-docstring
253 """ """
254 if self.viewing:
255 setting = context["instance"]
256 context["setting"] = setting
258 context["has_html_template"] = self.email_handler.get_auto_body_template(
259 setting["key"], "html", fallback_key=setting["fallback_key"]
260 )
261 context["has_txt_template"] = self.email_handler.get_auto_body_template(
262 setting["key"], "txt", fallback_key=setting["fallback_key"]
263 )
265 return super().render_to_response(template, context)
267 def preview(self):
268 """
269 View for showing a rendered preview of a given email template.
271 This will render the email template according to the "mode"
272 requested - i.e. HTML or TXT.
273 """
274 key = self.request.matchdict["key"]
275 setting = self.email_handler.get_email_setting(key)
276 context = setting.sample_data()
277 mode = self.request.params.get("mode", "html")
279 if mode == "txt":
280 body = self.email_handler.get_auto_txt_body(
281 key, context, fallback_key=setting.fallback_key
282 )
283 self.request.response.content_type = "text/plain"
285 else: # html
286 body = self.email_handler.get_auto_html_body(
287 key, context, fallback_key=setting.fallback_key
288 )
290 self.request.response.text = body
291 return self.request.response
293 @classmethod
294 def defaults(cls, config): # pylint: disable=empty-docstring
295 """ """
296 cls._email_defaults(config)
297 cls._defaults(config)
299 @classmethod
300 def _email_defaults(cls, config):
301 """ """
302 route_prefix = cls.get_route_prefix()
303 permission_prefix = cls.get_permission_prefix()
304 model_title_plural = cls.get_model_title_plural()
305 instance_url_prefix = cls.get_instance_url_prefix()
307 # fix permission group
308 config.add_wutta_permission_group(
309 permission_prefix, model_title_plural, overwrite=False
310 )
312 # preview
313 config.add_route(f"{route_prefix}.preview", f"{instance_url_prefix}/preview")
314 config.add_view(
315 cls,
316 attr="preview",
317 route_name=f"{route_prefix}.preview",
318 permission=f"{permission_prefix}.view",
319 )
322def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
323 base = globals()
325 EmailSettingView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
326 "EmailSettingView", base["EmailSettingView"]
327 )
328 EmailSettingView.defaults(config)
331def includeme(config): # pylint: disable=missing-function-docstring
332 defaults(config)