Merge lp:~acoconut/systers/flexible_essays into lp:systers

Proposed by Ana Cutillas
Status: Needs review
Proposed branch: lp:~acoconut/systers/flexible_essays
Merge into: lp:systers
Diff against target: 336 lines (+151/-40)
6 files modified
Mailman/Cgi/admindb.py (+10/-7)
Mailman/Cgi/listinfo.py (+20/-7)
Mailman/Cgi/subscribe.py (+46/-7)
Mailman/DlistUtils.py (+68/-11)
Mailman/ListAdmin.py (+6/-2)
bin/rmlist (+1/-6)
To merge this branch: bzr merge lp:~acoconut/systers/flexible_essays
Reviewer Review Type Date Requested Status
Robin J Pending
beachbrake Pending
Review via email: mp+119042@code.launchpad.net

Description of the change

I found where rejections are handled and added the functionality to mark answers as rejected or discarded. We never talked about discarded and I don't know if we want to have a "discarded" status or if we want both of them to be "rejected".

I created the new functions to do this in DlistUtils.py. I could have one function to change the status and pass the new status as an argument or I could have three different functions the way I have it now. What do you think?

To post a comment you must log in.

Unmerged revisions

99. By Ana Cutillas <email address hidden>

Added support for setting answers to rejected in the database.

98. By Ana Cutillas <email address hidden>

Changed the logic in the checking of answers in subscribe.py
Cleaned up DlistUtils.py

97. By Ana Cutillas <email address hidden>

Made the changes suggested by Robin: concerning error messages and
asserting the size of the answers.

Added the code to mark answers as accepted in the database when a user
is accepted.

96. By Ana Cutillas <email address hidden>

Added the error message saying that the user hasn't been subscribe to
the list when a user tries to subscribe with a pending subscription.

95. By Ana Cutillas <email address hidden>

Added the functionality to check that the answers aren't longer than
what the database can hold.

94. By Ana Cutillas <email address hidden>

Added changes to show the new essays in the pending subscriptions table.

Handle the possibility of a user trying to subscribe twice before the
subscription is reviewed.

93. By Ana Cutillas <email address hidden>

Removed the unused function get_answer from DlistUtils.py

92. By Ana Cutillas <email address hidden>

Cleaned up DlistUtils and changed the error message in subscribe. Added
comments.

91. By Ana Cutillas <email address hidden>

Cleaned up.

90. By Ana Cutillas <email address hidden>

Cleaned up listinfo.py

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Mailman/Cgi/admindb.py'
2--- Mailman/Cgi/admindb.py 2009-07-31 10:49:01 +0000
3+++ Mailman/Cgi/admindb.py 2012-08-09 20:44:23 +0000
4@@ -285,11 +285,13 @@
5 form.AddItem(Center(Header(2, _('Subscription Requests'))))
6 table = Table(border=2)
7 # The Essay is there to support subscription with essay, along with below references to "essay"
8- table.AddRow([Center(Bold(_('Address/name'))),
9+ table_headers = ([Center(Bold(_('Address/name'))),
10 Center(Bold(_('Your decision'))),
11- Center(Bold(_('Reason for refusal'))),
12- Center(Bold(_('Essay')))
13+ Center(Bold(_('Reason for refusal')))
14 ])
15+ for question in mlist.questions:
16+ table_headers.append(Center(Bold(question[0])))
17+ table.AddRow(table_headers)
18 # Alphabetical order by email address
19 byaddrs = {}
20 for id in pendingsubs:
21@@ -321,11 +323,12 @@
22 # While the address may be a unicode, it must be ascii
23 paddr = addr.encode('us-ascii', 'replace')
24 # Added by Systers to support essay feature. Lets the list admin see the essay the subscriber has entered.
25- table.AddRow(['%s<br><em>%s</em>' % (paddr, Utils.websafe(fullname)),
26+ content_row = (['%s<br><em>%s</em>' % (paddr, Utils.websafe(fullname)),
27 radio,
28- TextBox('comment-%d' % id, size=40),
29- essay
30- ])
31+ TextBox('comment-%d' % id, size=40)])
32+ for answer in essay:
33+ content_row.append(answer[1])
34+ table.AddRow(content_row)
35 num += 1
36 if num > 0:
37 form.AddItem(table)
38
39=== modified file 'Mailman/Cgi/listinfo.py'
40--- Mailman/Cgi/listinfo.py 2009-08-14 15:41:17 +0000
41+++ Mailman/Cgi/listinfo.py 2012-08-09 20:44:23 +0000
42@@ -22,6 +22,7 @@
43 import os
44 import cgi
45
46+from Mailman import DlistUtils
47 from Mailman import mm_cfg
48 from Mailman import Utils
49 from Mailman import MailList
50@@ -36,7 +37,6 @@
51 i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
52
53
54-
55
56 def main():
57 parts = Utils.GetPathPieces()
58 if not parts:
59@@ -62,7 +62,6 @@
60 list_listinfo(mlist, language)
61
62
63-
64
65 def listinfo_overview(msg=''):
66 # Present the general listinfo overview
67 hostname = Utils.get_domain()
68@@ -152,7 +151,6 @@
69 print doc.Format()
70
71
72-
73
74 def list_listinfo(mlist, lang):
75 # Generate list specific listinfo
76 doc = HeadlessDocument()
77@@ -189,10 +187,25 @@
78
79 # If activated, the content and presence of questions and a field to answer them. Added by Systers.
80 try:
81- if mlist.essay_enabled:
82- replacements['<mm-questions>'] = """<TR>
83- <TD COLSPAN="3">""" + mlist.questions
84- replacements['<mm-essay-box>'] = TextArea('essay', '', rows=8, cols=60).Format() + """ </TD></TR>"""# Added to support subscribe essays
85+ if mlist.essay_enabled: # Added to support subscribe essays
86+ html_questions = []
87+ for question in mlist.questions:
88+ question_name = "'"+question[1]+"'"
89+ html_questions.append("""<TR>
90+ <TD COLSPAN="3">""" + question[0] + """</TD>""")
91+ if (question[2] == 'smallbox'):
92+ html_questions.append("""<TD>""" + TextArea(question_name,\
93+ '', rows=1, cols=60).Format() + """</TD></TR>""")
94+ elif (question[2] == 'largebox'):
95+ html_questions.append("""<TD>""" + TextArea(question_name, \
96+ '', rows=8, cols=60).Format() + """</TD></TR>""")
97+ elif (question[2] == 'checkbox'):
98+ #TODO (Ana Cutillas): consider creating a class like TextArea for checkboxes?
99+ html_questions.append("""<TD><input type='checkbox' name='""" \
100+ + question[1] + """' value =' """\
101+ + question[1] + """'></TD></TR>""")
102+ replacements['<mm-questions>'] = ''.join(html_questions)
103+ replacements['<mm-essay-box>'] = ''
104 else:
105 replacements['<mm-questions>'] = ''
106 replacements['<mm-essay-box>'] = ''
107
108=== modified file 'Mailman/Cgi/subscribe.py'
109--- Mailman/Cgi/subscribe.py 2009-08-14 15:41:17 +0000
110+++ Mailman/Cgi/subscribe.py 2012-08-09 20:44:23 +0000
111@@ -26,6 +26,7 @@
112 from Mailman import MailList
113 from Mailman import Errors
114 from Mailman import i18n
115+from Mailman import DlistUtils # to write to the database
116 from Mailman import Message
117 from Mailman.DlistUserDesc import DlistUserDesc # to support subscribe essays
118 from Mailman.htmlformat import *
119@@ -135,14 +136,42 @@
120 os.environ.get('REMOTE_ADDR',
121 'unidentified origin'))
122
123- # Added to support mailing lists that require essays to join
124+ # Added to support mailing lists that require answers to questions to join
125 if mlist.essay_enabled:
126- essay = cgidata.getvalue('essay', '')
127- if not essay: # Check for empty essays.
128- if not error_msg:
129- print_error(mlist, doc)
130- error_msg = True
131- results.append(_('To subscribe to this list, you must answer the questions asked directly above the large text box.'))
132+ # We need to retrieve the questions to be able to
133+ # retrieve the content from cgi.
134+ essay = []
135+ # This implementation allows for checkboxes to be left unchecked but text boxes must be filled.
136+ for question in mlist.questions:
137+ # A question is a tuple that contains this information:
138+ # (question, question_name, type_of_box, format_info, position, version)
139+ answer = cgidata.getvalue(question[1], '')
140+ # The question has been answered
141+ if ((question[2] == 'largebox') or (question[2] == 'smallbox')):
142+ if (answer):
143+ if (len(answer) <= 1500):
144+ # In the database we have set the essay max length to 1500
145+ essay.append((question[1], answer, question[5]))
146+ else:
147+ if not error_msg:
148+ print_error(mlist, doc)
149+ error_msg = True
150+ results_error = "Your answer to the question \"" + question[0] + "\" is too long. Your answers must be less than 1500 characters long."
151+ results.append(_(results_error))
152+ else:
153+ # If the answer is empty and it's not a checkbox
154+ # the user left fields empty
155+ if not error_msg:
156+ print_error(mlist, doc)
157+ error_msg = True
158+ results.append(_('To subscribe to this list, you must answer all the questions asked on this page.'))
159+ elif (question[2] == 'checkbox'):
160+ if (answer):
161+ # The answer is a checkbox
162+ essay.append((question[1], "True", question[5]))
163+ else:
164+ # The answer is an unchecked checkbox
165+ essay.append((question[1], 'False', question[5]))
166 else: # has to be here, else error message when calling DlistUserDesc
167 essay = cgidata.getvalue('essay', 'None')
168
169@@ -215,6 +244,11 @@
170 may have to be first confirmed by you via email, or approved by the list
171 moderator. If confirmation is required, you will soon get a confirmation
172 email which contains further instructions.""")
173+ for answer in essay:
174+ form = DlistUtils.FormQuestion(mlist)
175+ form_question_id = form.recordAnswer(email, answer[0], answer[1], answer[2])
176+ # If form_question_id is -1, the user has a pending subscription
177+ # An appropriate message is giving to the user later
178
179 try:
180 userdesc = DlistUserDesc(email, fullname, password, digest, lang, essay)
181@@ -303,6 +337,11 @@
182 else:
183 results = _("""\
184 You have been successfully subscribed to the %(realname)s mailing list.""")
185+ if (form_question_id == -1):
186+ if not error_msg:
187+ print_error(mlist, doc)
188+ error_msg = True
189+ results = _("""There is a pending subscription with this email. You may not subscribe a second time. The moderator will contact you if there are any questions about the answers you gave originally.""")
190 # Show the results
191 ## Not sure why this is needed
192 if results == []:
193
194=== modified file 'Mailman/DlistUtils.py'
195--- Mailman/DlistUtils.py 2010-01-31 20:29:48 +0000
196+++ Mailman/DlistUtils.py 2012-08-09 20:44:23 +0000
197@@ -245,22 +245,17 @@
198 def subscribeToList(self, key):
199 """Add a member to the subscriber database, or change the record from deleted if it was already present."""
200
201- command = "count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8'))).count()\n"
202- if DEBUG_MODE:
203- syslog('info', 'DlistUtils(subscribeToList):Executing query:\n%s', command)
204+ unicode_key = unicode(key, "utf-8")
205+ form_question = FormQuestion(self.mlist)
206+ form_question.acceptAnswer(key)
207+
208 # First see if member is subscribed with deleted field = false
209- count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8"))).count()
210+ count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode_key)).count()
211
212 if count:
213- command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8')).set(deleted = False)"
214- if DEBUG_MODE:
215- syslog('info', 'DlistUtils(subscribeToList):Executing query:\n%s', command)
216- self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8")).set(deleted = False)
217+ self.store.find(Subscriber,Subscriber.mailman_key == unicode_key).set(deleted = False)
218 else:
219 # format is text-only (1) by default
220- command = "subscriber = self.store.add(Subscriber())\nsubscriber.mailman_key = unicode(key,'utf-8')\nsubscriber.preference = 1\nsubscriber.deleted = False\nsubscriber.format = 1\nsubscriber.suppress = 0"
221- if DEBUG_MODE:
222- syslog('info', 'DlistUtils(subscribeToList)Executing query:\n%s', command)
223 self.mailman_key = unicode(key,"utf-8")
224 self.preference = 1
225 self.deleted = False
226@@ -289,6 +284,68 @@
227 self.store.find(Alias,Alias.subscriber_id == obsolete_id).set(subscriber_id = new_id)
228 self.store.find(Subscriber,Subscriber.subscriber_id == obsolete_id).remove()
229
230+class FormQuestion(object):
231+ __storm_table__ = "form_question"
232+ form_question_id = Int(primary = True,default = AutoReload)
233+ content = Unicode()
234+ user_email = Unicode()
235+ version = Int()
236+ question_id = Unicode()
237+ status = Unicode()
238+
239+ def __init__(self,mlist):
240+ self.mlist = mlist
241+ self.database = getConn(mlist)
242+
243+ @decfunc
244+ def recordAnswer(self, addr, question, answer, version):
245+ """Save the answer for a question of the subscribe form, returning its id."""
246+ subscriber = Subscriber(self.mlist)
247+ userID = addr.decode('utf-8')
248+ question_unicode = question.decode('utf-8')
249+ count = (self.store.find(FormQuestion, FormQuestion.user_email == userID, FormQuestion.question_id == question_unicode, FormQuestion.status == u"pending")).count()
250+ if count:
251+ # The user has already tried to subscribe.
252+ return -1
253+ status = u"pending"
254+ question_unicode = question.decode('utf-8')
255+ answer_unicode = answer.decode('utf-8')
256+ #form_question_id has autoreload set, its value will be serially updated in database
257+ self.user_email = userID
258+ self.content = answer_unicode
259+ self.version = version
260+ self.question_id = question_unicode
261+ self.status = status
262+ self.store.add(self)
263+ formQuestionID = self.store.find(FormQuestion).max(FormQuestion.form_question_id)
264+ if DEBUG_MODE:
265+ syslog('info', 'Result of query(formQuestionID) is: %s\n', formQuestionID)
266+ return formQuestionID
267+
268+ @decfunc
269+ def acceptAnswer(self, addr):
270+ """Sets the status of the answers to accepted"""
271+ userID = addr.decode('utf-8')
272+ count = (self.store.find(FormQuestion, FormQuestion.user_email == userID).count())
273+ for i in range(count):
274+ self.store.find(FormQuestion, FormQuestion.user_email == userID, FormQuestion.status == u"pending").set(status = u"accepted")
275+
276+ @decfunc
277+ def rejectAnswer(self, addr):
278+ """Sets the status of the answers to rejected"""
279+ userID = addr.decode('utf-8')
280+ count = (self.store.find(FormQuestion, FormQuestion.user_email == userID).count())
281+ for i in range(count):
282+ self.store.find(FormQuestion, FormQuestion.user_email == userID, FormQuestion.status == u"pending").set(status = u"rejected")
283+
284+ @decfunc
285+ def discardAnswer(self, addr):
286+ """Sets the status of the answers to discarded"""
287+ userID = addr.decode('utf-8')
288+ count = (self.store.find(FormQuestion, FormQuestion.user_email == userID).count())
289+ for i in range(count):
290+ self.store.find(FormQuestion, FormQuestion.user_email == userID, FormQuestion.status == u"pending").set(status = u"discarded")
291+
292 class Message(object):
293 __storm_table__ = "message"
294 message_id = Int(primary = True,default = AutoReload)
295
296=== modified file 'Mailman/ListAdmin.py'
297--- Mailman/ListAdmin.py 2009-01-29 18:54:13 +0000
298+++ Mailman/ListAdmin.py 2012-08-09 20:44:23 +0000
299@@ -39,8 +39,9 @@
300 from Mailman import Utils
301 from Mailman import Message
302 from Mailman import Errors
303+from Mailman import DlistUtils # to write to the database
304
305-from Mailman.DlistUserDesc import DlistUserDesc #enable support for subscribe essays
306+from Mailman.DlistUserDesc import DlistUserDesc #enable support for subscribe forms
307 from Mailman.Queue.sbcache import get_switchboard
308 from Mailman.Logging.Syslog import syslog
309 from Mailman import i18n
310@@ -410,8 +411,11 @@
311 if value == mm_cfg.DEFER:
312 return DEFER
313 elif value == mm_cfg.DISCARD:
314- pass
315+ form = DlistUtils.FormQuestion(mlist=self)
316+ form.discardAnswer(addr)
317 elif value == mm_cfg.REJECT:
318+ form = DlistUtils.FormQuestion(mlist=self)
319+ form.rejectAnswer(addr)
320 self.__refuse(_('Subscription request'), addr,
321 comment or _('[No reason given]'),
322 lang=lang)
323
324=== modified file 'bin/rmlist'
325--- bin/rmlist 2009-07-31 10:49:01 +0000
326+++ bin/rmlist 2012-08-09 20:44:23 +0000
327@@ -152,12 +152,7 @@
328
329 # Remove the database in PostgreSQL if it was a dynamic list. Added by Systers.
330 try:
331- conn = DlistUtils.getConn(None)
332- old_iso_lvl = conn.isolation_level
333- conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
334- cursor = conn.cursor()
335- cursor.execute('DROP DATABASE "%s"' % listname)
336- conn.set_isolation_level(old_iso_lvl)
337+ DlistUtils.remove_dlist(listname)
338 except:
339 syslog('error', 'Tried to remove the database for %s, no such database (ok if it was a non dlist). If it was a dlist, the postgreSQL database for this list needs to be removed manually.' % listname)
340

Subscribers

People subscribed via source and target branches