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

Proposed by Ana Cutillas
Status: Superseded
Proposed branch: lp:~acoconut/systers/flexible_essays
Merge into: lp:systers
Diff against target: 293 lines (+130/-38)
5 files modified
Mailman/Cgi/admindb.py (+10/-7)
Mailman/Cgi/listinfo.py (+20/-7)
Mailman/Cgi/subscribe.py (+46/-7)
Mailman/DlistUtils.py (+53/-11)
bin/rmlist (+1/-6)
To merge this branch: bzr merge lp:~acoconut/systers/flexible_essays
Reviewer Review Type Date Requested Status
Robin J Needs Fixing
beachbrake Pending
Review via email: mp+118537@code.launchpad.net

This proposal has been superseded by a proposal from 2012-08-09.

Description of the change

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.

To post a comment you must log in.
Revision history for this message
Robin J (robin-jeffries) wrote :

for this line
              if (question[2] != 'checkbox'):

This implies that you want to do this for every kind of widget except checkboxes. No, you are doing it for every kind of widget you currently have except checkboxes. Make it clearer for the next person and have it say
        if (question[2] == 'small text' or question[2] == 'large text'):

you also want to rethink your structure a bit more -- you have two tests for not being a checkbox, and you should be able to combine them into 1. (again, my guess is that if we add new widgets that are not texty, you may want to treat them differently, so explicitly list the question types you are using.

If these lines aren't useful to you, delete them; it's no longer accurate since you changed the utf-8
 command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8')).set(deleted = False)"
           if DEBUG_MODE:
              syslog('info', 'DlistUtils(subscribeToList):Executing query:\n%s', command)

You defined acceptAnswer, but earlier you have almost identical code, why? Why not call acceptAnswer there?

And why aren't these identical? why do you have
    unicode_key = unicode(key, "utf-8")
in one place and
    userID = addr.decode('utf-8')
in the other

review: Needs Fixing
Revision history for this message
Ana Cutillas (acoconut) wrote :

On Thu, Aug 9, 2012 at 6:08 AM, Robin J <email address hidden> wrote:

> Review: Needs Fixing
>
> for this line
> if (question[2] != 'checkbox'):
>
> This implies that you want to do this for every kind of widget except
> checkboxes. No, you are doing it for every kind of widget you currently
> have except checkboxes. Make it clearer for the next person and have it say
> if (question[2] == 'small text' or question[2] == 'large text'):
>
> you also want to rethink your structure a bit more -- you have two tests
> for not being a checkbox, and you should be able to combine them into 1.
> (again, my guess is that if we add new widgets that are not texty, you may
> want to treat them differently, so explicitly list the question types you
> are using.
>
> If these lines aren't useful to you, delete them; it's no longer accurate
> since you changed the utf-8
> command = "self.store.find(Subscriber,Subscriber.mailman_key ==
> unicode(key,'utf-8')).set(deleted = False)"
> if DEBUG_MODE:
> syslog('info', 'DlistUtils(subscribeToList):Executing
> query:\n%s', command)
>
> You defined acceptAnswer, but earlier you have almost identical code, why?
> Why not call acceptAnswer there?
>
> And why aren't these identical? why do you have
> unicode_key = unicode(key, "utf-8")
> in one place and
> userID = addr.decode('utf-8')
> in the other
>

Because I had to figure out how to decode strings and found the addr.decode
way and then I saw somebody else had done it using the unicode function.

I changed all the other things you suggested and added the ordering by
subscription time idea to a tasks list so that I remember to do it later.

I'm pushing the new code now.

> --
> https://code.launchpad.net/~acoconut/systers/flexible_essays/+merge/118537
> You are the owner of lp:~acoconut/systers/flexible_essays.
>

lp:~acoconut/systers/flexible_essays updated
98. By Ana Cutillas <email address hidden>

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

99. By Ana Cutillas <email address hidden>

Added support for setting answers to rejected in the database.

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
=== modified file 'Mailman/Cgi/admindb.py'
--- Mailman/Cgi/admindb.py 2009-07-31 10:49:01 +0000
+++ Mailman/Cgi/admindb.py 2012-08-09 17:41:20 +0000
@@ -285,11 +285,13 @@
285 form.AddItem(Center(Header(2, _('Subscription Requests'))))285 form.AddItem(Center(Header(2, _('Subscription Requests'))))
286 table = Table(border=2)286 table = Table(border=2)
287 # The Essay is there to support subscription with essay, along with below references to "essay"287 # The Essay is there to support subscription with essay, along with below references to "essay"
288 table.AddRow([Center(Bold(_('Address/name'))),288 table_headers = ([Center(Bold(_('Address/name'))),
289 Center(Bold(_('Your decision'))),289 Center(Bold(_('Your decision'))),
290 Center(Bold(_('Reason for refusal'))),290 Center(Bold(_('Reason for refusal')))
291 Center(Bold(_('Essay')))
292 ])291 ])
292 for question in mlist.questions:
293 table_headers.append(Center(Bold(question[0])))
294 table.AddRow(table_headers)
293 # Alphabetical order by email address295 # Alphabetical order by email address
294 byaddrs = {}296 byaddrs = {}
295 for id in pendingsubs:297 for id in pendingsubs:
@@ -321,11 +323,12 @@
321 # While the address may be a unicode, it must be ascii323 # While the address may be a unicode, it must be ascii
322 paddr = addr.encode('us-ascii', 'replace')324 paddr = addr.encode('us-ascii', 'replace')
323 # Added by Systers to support essay feature. Lets the list admin see the essay the subscriber has entered.325 # Added by Systers to support essay feature. Lets the list admin see the essay the subscriber has entered.
324 table.AddRow(['%s<br><em>%s</em>' % (paddr, Utils.websafe(fullname)),326 content_row = (['%s<br><em>%s</em>' % (paddr, Utils.websafe(fullname)),
325 radio,327 radio,
326 TextBox('comment-%d' % id, size=40),328 TextBox('comment-%d' % id, size=40)])
327 essay329 for answer in essay:
328 ])330 content_row.append(answer[1])
331 table.AddRow(content_row)
329 num += 1332 num += 1
330 if num > 0:333 if num > 0:
331 form.AddItem(table)334 form.AddItem(table)
332335
=== modified file 'Mailman/Cgi/listinfo.py'
--- Mailman/Cgi/listinfo.py 2009-08-14 15:41:17 +0000
+++ Mailman/Cgi/listinfo.py 2012-08-09 17:41:20 +0000
@@ -22,6 +22,7 @@
22import os22import os
23import cgi23import cgi
2424
25from Mailman import DlistUtils
25from Mailman import mm_cfg26from Mailman import mm_cfg
26from Mailman import Utils27from Mailman import Utils
27from Mailman import MailList28from Mailman import MailList
@@ -36,7 +37,6 @@
36i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)37i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
3738
3839
39
4040
41def main():41def main():
42 parts = Utils.GetPathPieces()42 parts = Utils.GetPathPieces()
43 if not parts:43 if not parts:
@@ -62,7 +62,6 @@
62 list_listinfo(mlist, language)62 list_listinfo(mlist, language)
6363
6464
65
6665
67def listinfo_overview(msg=''):66def listinfo_overview(msg=''):
68 # Present the general listinfo overview67 # Present the general listinfo overview
69 hostname = Utils.get_domain()68 hostname = Utils.get_domain()
@@ -152,7 +151,6 @@
152 print doc.Format()151 print doc.Format()
153152
154153
155
156154
157def list_listinfo(mlist, lang):155def list_listinfo(mlist, lang):
158 # Generate list specific listinfo156 # Generate list specific listinfo
159 doc = HeadlessDocument()157 doc = HeadlessDocument()
@@ -189,10 +187,25 @@
189187
190 # If activated, the content and presence of questions and a field to answer them. Added by Systers.188 # If activated, the content and presence of questions and a field to answer them. Added by Systers.
191 try:189 try:
192 if mlist.essay_enabled:190 if mlist.essay_enabled: # Added to support subscribe essays
193 replacements['<mm-questions>'] = """<TR>191 html_questions = []
194 <TD COLSPAN="3">""" + mlist.questions192 for question in mlist.questions:
195 replacements['<mm-essay-box>'] = TextArea('essay', '', rows=8, cols=60).Format() + """ </TD></TR>"""# Added to support subscribe essays193 question_name = "'"+question[1]+"'"
194 html_questions.append("""<TR>
195 <TD COLSPAN="3">""" + question[0] + """</TD>""")
196 if (question[2] == 'smallbox'):
197 html_questions.append("""<TD>""" + TextArea(question_name,\
198 '', rows=1, cols=60).Format() + """</TD></TR>""")
199 elif (question[2] == 'largebox'):
200 html_questions.append("""<TD>""" + TextArea(question_name, \
201 '', rows=8, cols=60).Format() + """</TD></TR>""")
202 elif (question[2] == 'checkbox'):
203 #TODO (Ana Cutillas): consider creating a class like TextArea for checkboxes?
204 html_questions.append("""<TD><input type='checkbox' name='""" \
205 + question[1] + """' value =' """\
206 + question[1] + """'></TD></TR>""")
207 replacements['<mm-questions>'] = ''.join(html_questions)
208 replacements['<mm-essay-box>'] = ''
196 else:209 else:
197 replacements['<mm-questions>'] = ''210 replacements['<mm-questions>'] = ''
198 replacements['<mm-essay-box>'] = ''211 replacements['<mm-essay-box>'] = ''
199212
=== modified file 'Mailman/Cgi/subscribe.py'
--- Mailman/Cgi/subscribe.py 2009-08-14 15:41:17 +0000
+++ Mailman/Cgi/subscribe.py 2012-08-09 17:41:20 +0000
@@ -26,6 +26,7 @@
26from Mailman import MailList26from Mailman import MailList
27from Mailman import Errors27from Mailman import Errors
28from Mailman import i18n28from Mailman import i18n
29from Mailman import DlistUtils # to write to the database
29from Mailman import Message30from Mailman import Message
30from Mailman.DlistUserDesc import DlistUserDesc # to support subscribe essays31from Mailman.DlistUserDesc import DlistUserDesc # to support subscribe essays
31from Mailman.htmlformat import *32from Mailman.htmlformat import *
@@ -135,14 +136,42 @@
135 os.environ.get('REMOTE_ADDR',136 os.environ.get('REMOTE_ADDR',
136 'unidentified origin'))137 'unidentified origin'))
137138
138 # Added to support mailing lists that require essays to join139 # Added to support mailing lists that require answers to questions to join
139 if mlist.essay_enabled:140 if mlist.essay_enabled:
140 essay = cgidata.getvalue('essay', '')141 # We need to retrieve the questions to be able to
141 if not essay: # Check for empty essays.142 # retrieve the content from cgi.
142 if not error_msg:143 essay = []
143 print_error(mlist, doc)144 # This implementation allows for checkboxes to be left unchecked but text boxes must be filled.
144 error_msg = True145 for question in mlist.questions:
145 results.append(_('To subscribe to this list, you must answer the questions asked directly above the large text box.'))146 # A question is a tuple that contains this information:
147 # (question, question_name, type_of_box, format_info, position, version)
148 answer = cgidata.getvalue(question[1], '')
149 # The question has been answered
150 if ((question[2] == 'largebox') or (question[2] == 'smallbox')):
151 if (answer):
152 if (len(answer) <= 1500):
153 # In the database we have set the essay max length to 1500
154 essay.append((question[1], answer, question[5]))
155 else:
156 if not error_msg:
157 print_error(mlist, doc)
158 error_msg = True
159 results_error = "Your answer to the question \"" + question[0] + "\" is too long. Your answers must be less than 1500 characters long."
160 results.append(_(results_error))
161 else:
162 # If the answer is empty and it's not a checkbox
163 # the user left fields empty
164 if not error_msg:
165 print_error(mlist, doc)
166 error_msg = True
167 results.append(_('To subscribe to this list, you must answer all the questions asked on this page.'))
168 elif (question[2] == 'checkbox'):
169 if (answer):
170 # The answer is a checkbox
171 essay.append((question[1], "True", question[5]))
172 else:
173 # The answer is an unchecked checkbox
174 essay.append((question[1], 'False', question[5]))
146 else: # has to be here, else error message when calling DlistUserDesc175 else: # has to be here, else error message when calling DlistUserDesc
147 essay = cgidata.getvalue('essay', 'None')176 essay = cgidata.getvalue('essay', 'None')
148 177
@@ -215,6 +244,11 @@
215may have to be first confirmed by you via email, or approved by the list244may have to be first confirmed by you via email, or approved by the list
216moderator. If confirmation is required, you will soon get a confirmation245moderator. If confirmation is required, you will soon get a confirmation
217email which contains further instructions.""")246email which contains further instructions.""")
247 for answer in essay:
248 form = DlistUtils.FormQuestion(mlist)
249 form_question_id = form.recordAnswer(email, answer[0], answer[1], answer[2])
250 # If form_question_id is -1, the user has a pending subscription
251 # An appropriate message is giving to the user later
218252
219 try:253 try:
220 userdesc = DlistUserDesc(email, fullname, password, digest, lang, essay)254 userdesc = DlistUserDesc(email, fullname, password, digest, lang, essay)
@@ -303,6 +337,11 @@
303 else:337 else:
304 results = _("""\338 results = _("""\
305You have been successfully subscribed to the %(realname)s mailing list.""")339You have been successfully subscribed to the %(realname)s mailing list.""")
340 if (form_question_id == -1):
341 if not error_msg:
342 print_error(mlist, doc)
343 error_msg = True
344 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.""")
306 # Show the results345 # Show the results
307 ## Not sure why this is needed346 ## Not sure why this is needed
308 if results == []:347 if results == []:
309348
=== modified file 'Mailman/DlistUtils.py'
--- Mailman/DlistUtils.py 2010-01-31 20:29:48 +0000
+++ Mailman/DlistUtils.py 2012-08-09 17:41:20 +0000
@@ -245,22 +245,17 @@
245 def subscribeToList(self, key):245 def subscribeToList(self, key):
246 """Add a member to the subscriber database, or change the record from deleted if it was already present."""246 """Add a member to the subscriber database, or change the record from deleted if it was already present."""
247247
248 command = "count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8'))).count()\n"248 unicode_key = unicode(key, "utf-8")
249 if DEBUG_MODE:249 form_question = FormQuestion(self.mlist)
250 syslog('info', 'DlistUtils(subscribeToList):Executing query:\n%s', command)250 form_question.acceptAnswer(key)
251
251 # First see if member is subscribed with deleted field = false252 # First see if member is subscribed with deleted field = false
252 count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8"))).count()253 count = (self.store.find(Subscriber,Subscriber.mailman_key == unicode_key)).count()
253254
254 if count:255 if count:
255 command = "self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,'utf-8')).set(deleted = False)"256 self.store.find(Subscriber,Subscriber.mailman_key == unicode_key).set(deleted = False)
256 if DEBUG_MODE:
257 syslog('info', 'DlistUtils(subscribeToList):Executing query:\n%s', command)
258 self.store.find(Subscriber,Subscriber.mailman_key == unicode(key,"utf-8")).set(deleted = False)
259 else:257 else:
260 # format is text-only (1) by default258 # format is text-only (1) by default
261 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"
262 if DEBUG_MODE:
263 syslog('info', 'DlistUtils(subscribeToList)Executing query:\n%s', command)
264 self.mailman_key = unicode(key,"utf-8")259 self.mailman_key = unicode(key,"utf-8")
265 self.preference = 1260 self.preference = 1
266 self.deleted = False261 self.deleted = False
@@ -289,6 +284,53 @@
289 self.store.find(Alias,Alias.subscriber_id == obsolete_id).set(subscriber_id = new_id)284 self.store.find(Alias,Alias.subscriber_id == obsolete_id).set(subscriber_id = new_id)
290 self.store.find(Subscriber,Subscriber.subscriber_id == obsolete_id).remove() 285 self.store.find(Subscriber,Subscriber.subscriber_id == obsolete_id).remove()
291286
287class FormQuestion(object):
288 __storm_table__ = "form_question"
289 form_question_id = Int(primary = True,default = AutoReload)
290 content = Unicode()
291 user_email = Unicode()
292 version = Int()
293 question_id = Unicode()
294 status = Unicode()
295
296 def __init__(self,mlist):
297 self.mlist = mlist
298 self.database = getConn(mlist)
299
300 @decfunc
301 def recordAnswer(self, addr, question, answer, version):
302 """Save the answer for a question of the subscribe form, returning its id."""
303 subscriber = Subscriber(self.mlist)
304 userID = addr.decode('utf-8')
305 question_unicode = question.decode('utf-8')
306 count = (self.store.find(FormQuestion, FormQuestion.user_email == userID, FormQuestion.question_id == question_unicode)).count()
307 if count:
308 # The user has already tried to subscribe.
309 return -1
310 status = u"pending"
311 question_unicode = question.decode('utf-8')
312 answer_unicode = answer.decode('utf-8')
313 #form_question_id has autoreload set, its value will be serially updated in database
314 self.user_email = userID
315 self.content = answer_unicode
316 self.version = version
317 self.question_id = question_unicode
318 self.status = status
319 self.store.add(self)
320 formQuestionID = self.store.find(FormQuestion).max(FormQuestion.form_question_id)
321 if DEBUG_MODE:
322 syslog('info', 'Result of query(formQuestionID) is: %s\n', formQuestionID)
323 return formQuestionID
324
325 @decfunc
326 def acceptAnswer(self, addr):
327 """Sets the status of the answers to accepted"""
328 userID = addr.decode('utf-8')
329 count = (self.store.find(FormQuestion, FormQuestion.user_email == userID).count())
330 for i in range(count):
331 self.store.find(FormQuestion, FormQuestion.user_email == userID, FormQuestion.status == u"pending").set(status = u"accepted")
332
333
292class Message(object):334class Message(object):
293 __storm_table__ = "message"335 __storm_table__ = "message"
294 message_id = Int(primary = True,default = AutoReload)336 message_id = Int(primary = True,default = AutoReload)
295337
=== modified file 'bin/rmlist'
--- bin/rmlist 2009-07-31 10:49:01 +0000
+++ bin/rmlist 2012-08-09 17:41:20 +0000
@@ -152,12 +152,7 @@
152152
153 # Remove the database in PostgreSQL if it was a dynamic list. Added by Systers.153 # Remove the database in PostgreSQL if it was a dynamic list. Added by Systers.
154 try:154 try:
155 conn = DlistUtils.getConn(None)155 DlistUtils.remove_dlist(listname)
156 old_iso_lvl = conn.isolation_level
157 conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
158 cursor = conn.cursor()
159 cursor.execute('DROP DATABASE "%s"' % listname)
160 conn.set_isolation_level(old_iso_lvl)
161 except:156 except:
162 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)157 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)
163158

Subscribers

People subscribed via source and target branches