-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBoardMemberMailerReqForm.py
355 lines (291 loc) · 11.3 KB
/
BoardMemberMailerReqForm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
#!/usr/bin/env python
"""Request form for generating RTF letters to board members.
"""
from functools import cached_property
from json import dumps, loads
from cdr import getControlValue
from cdrcgi import Controller
from cdrapi.docs import Doc
from cdrapi.publishing import Job
class Control(Controller):
"""Logic for generating board member mailer letters."""
LOGNAME = "mailer"
SUBTITLE = "PDQ Board Member Correspondence Mailers"
SYSTEM = "Mailers"
SUBSYSTEM = "PDQ Board Member Correspondence Mailer"
LETTERS = "board-member-letters"
STATUS_PAGE = "Status Page"
def populate_form(self, page):
"""Add the parameters for mailer generation.
Pass:
page - HTMLPage object to which we add the form fields.
"""
page.form.append(page.B.P(
"Note: Invitation letters for prospective Pediatric Treatment "
"editorial board members cannot be generated from this interface.",
page.B.CLASS("error")
))
fieldset = page.fieldset("Board")
fieldset.set("id", "boards")
checked = True
for board in self.boards:
opts = dict(value=board.id, label=board.name, checked=checked)
fieldset.append(page.radio_button("board", **opts))
checked = False
page.form.append(fieldset)
fieldset = page.fieldset("Letter")
fieldset.set("id", "letters")
page.form.append(fieldset)
fieldset = page.fieldset("Board Members")
fieldset.set("id", "members")
page.form.append(fieldset)
fieldset = page.fieldset("Notification")
opts = dict(value=self.user.email)
fieldset.append(page.text_field("email", **opts))
page.form.append(fieldset)
page.add_script(f"var boards = {self.boards.json};")
page.add_script(f"var letters = {self.letters_json};")
page.head.append(page.B.SCRIPT(src="/js/BoardMemberMailerReqForm.js"))
def show_report(self):
"""Overridden so we can create the mailers."""
if self.request == self.STATUS_PAGE:
return self.redirect("PubStatus.py", id=self.job_id)
opts = dict(
system=self.SYSTEM,
subsystem=self.SUBSYSTEM,
parms=self.parms,
docs=self.docs,
email=self.email,
permissive=True,
)
try:
job = Job(self.session, **opts)
job.create()
except Exception as e:
self.logger.exception("Mailer creation failed")
self.bail(str(e))
opts = dict(
action=self.script,
subtitle=self.subtitle,
session=self.session,
)
page = self.HTMLPage(self.title, **opts)
legend = f"Queued {len(self.recipients)} Mailer(s)"
fieldset = page.fieldset(legend)
ul = page.B.UL()
for recipient in self.recipients:
ul.append(page.B.LI(recipient))
fieldset.append(ul)
page.form.append(fieldset)
button = self.form_page.button(self.STATUS_PAGE)
page.form.append(button)
page.form.append(page.hidden_field("job-id", job.id))
page.send()
@cached_property
def board(self):
"""Which board was selected?"""
return self.fields.getvalue("board")
@cached_property
def boards(self):
"""All active boards in the CDR, as id/title tuples."""
return Boards(self)
@cached_property
def board_members(self):
"""Dictionary of all active PDQ board members in the CDR."""
path = "/PDQBoardMemberInfo/BoardMemberName/@cdr:ref"
query = self.Query("active_doc d", "p.doc_id", "d.title")
query.join("query_term p", "p.int_val = d.id")
query.where(f"p.path = '{path}'")
board_members = {}
for id, title in query.execute(self.cursor):
title = title.split(";")[0].strip()
if title.lower() != "inactive":
board_members[id] = title
return board_members
@cached_property
def docs(self):
"""Sequence of `Doc` objects for the selected board members."""
docs = []
for member_id in self.selected_members:
doc = Doc(self.session, id=member_id, version="lastv")
docs.append(doc)
return docs
@cached_property
def email(self):
"""Where should we send the notification?"""
return self.fields.getvalue("email")
@cached_property
def job_id(self):
"""The ID of the job we just created."""
return self.fields.getvalue("job-id")
@cached_property
def letter(self):
"""Machine name for the type of letter to be sent."""
return self.fields.getvalue("letter")
@cached_property
def letters(self):
"""Display names for letter types indexed by machine names."""
letters = {}
for letter_type in ("advisory", "editorial"):
for name, key in loads(self.letters_json)[letter_type]:
letters[key] = name
return letters
@cached_property
def letters_json(self):
"""Letter type information usable by client-side scripting."""
return getControlValue("Mailers", self.LETTERS)
@cached_property
def parms(self):
"""Parameters to be fed to the publishing job."""
return dict(Board=self.board, Letter=self.letter)
@cached_property
def recipients(self):
"""Sorted sequence of selected board member names."""
recipients = []
for member_id in self.selected_members:
recipients.append(self.board_members[member_id])
return sorted(recipients, key=str.lower)
@cached_property
def selected_members(self):
"""Document IDs for the board members which have been chosen."""
members = []
for id in self.fields.getlist("member"):
if id.isdigit():
members.append(int(id))
return members
@cached_property
def user(self):
"""Object for the currently logged-on CDR user."""
opts = dict(id=self.session.user_id)
return self.session.User(self.session, **opts)
class Boards:
"""Collection of all of the active PDQ boards."""
IACT = "Integrative, Alternative, and Complementary Therapies"
BOARD_TYPES = "PDQ Editorial Board", "PDQ Advisory Board"
@cached_property
def json(self):
"""Information about the board in a form client-side scripting uses."""
boards = {}
for board in self.boards:
members = [(m.id, m.name) for m in board.members]
values = dict(
id=board.id,
type=board.type,
members=members
)
boards[board.id] = values
return dumps(boards, indent=2)
def __init__(self, control):
"""Save the control object, and let properties do the heavy lifting."""
self.__control = control
def __len__(self):
"""Support Boolean testing."""
return len(self.boards)
def __iter__(self):
"""Allow this object to be used like an iterable sequence."""
class Iter:
def __init__(self, boards):
self.__index = 0
self.__boards = boards
def __next__(self):
if self.__index >= len(self.__boards):
raise StopIteration
board = self.__boards[self.__index]
self.__index += 1
return board
return Iter(self.boards)
@cached_property
def control(self):
"""Access to the database cursor."""
return self.__control
@cached_property
def cursor(self):
"""Access to the database."""
return self.control.cursor
@cached_property
def boards(self):
"""Assemble the sequence of `Board` objects."""
fields = "d.id", "d.title"
query = self.control.Query("active_doc d", *fields)
query.order("d.title")
query.join("query_term t", "t.doc_id = d.id")
query.where("t.path = '/Organization/OrganizationType'")
query.where(query.Condition("t.value", self.BOARD_TYPES, "IN"))
boards = []
for id, title in query.execute(self.cursor).fetchall():
title = title.split(";")[0].strip()
title = title.replace(self.IACT, "IACT")
boards.append(self.Board(self.control, id, title))
return boards
class Board:
"""Information about a PDQ board and its members."""
BOARD = "/PDQBoardMemberInfo/BoardMembershipDetails/BoardName/@cdr:ref"
def __init__(self, control, id, name):
"""Remember the caller's values, let the properties do the work.
Pass:
control - access to the database
id - primary key for the board's Organization CDR document
name - string for the name of the board
"""
self.__control = control
self.__id = id
self.__name = name
@cached_property
def control(self):
"""Access to the database cursor."""
return self.__control
@cached_property
def cursor(self):
"""Access to the database."""
return self.control.cursor
@cached_property
def id(self):
"""Primary key for the board's Organization CDR document."""
return self.__id
@cached_property
def name(self):
"""String for the name of the board."""
return self.__name
@cached_property
def members(self):
"""Sorted sequence of the board's members."""
query = self.control.Query("active_doc d", "d.id").unique()
query.join("query_term m", "m.doc_id = d.id")
query.where(query.Condition("m.path", self.BOARD))
query.where(query.Condition("m.int_val", self.id))
members = []
for row in query.execute(self.cursor).fetchall():
member = self.Member(self, row.id)
if member.name:
members.append(member)
return sorted(members)
@cached_property
def type(self):
"""String for the board's type ("advisory" or "editorial")."""
if "advisory" in self.name.lower():
return "advisory"
return "editorial"
class Member:
"""One of the members of a PDQ board."""
def __init__(self, board, id):
"""Save the caller's values."""
self.__board = board
self.__id = id
def __lt__(self, other):
"""Make the board member objects sortable by member name."""
return self.name.lower() < other.name.lower()
@cached_property
def id(self):
"""Primary key for the member's PDQBoardMemberInfo doc."""
return self.__id
@cached_property
def board(self):
"""Access to the `Control` object."""
return self.__board
@cached_property
def name(self):
"""Board member's name in Surname, Given Name format."""
return self.board.control.board_members.get(self.id)
if __name__ == "__main__":
"""Don't execute the script if loaded as a module."""
Control().run()