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
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 17:41:20 +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 17:41:20 +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 17:41:20 +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 17:41:20 +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,53 @@
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)).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+
277 class Message(object):
278 __storm_table__ = "message"
279 message_id = Int(primary = True,default = AutoReload)
280
281=== modified file 'bin/rmlist'
282--- bin/rmlist 2009-07-31 10:49:01 +0000
283+++ bin/rmlist 2012-08-09 17:41:20 +0000
284@@ -152,12 +152,7 @@
285
286 # Remove the database in PostgreSQL if it was a dynamic list. Added by Systers.
287 try:
288- conn = DlistUtils.getConn(None)
289- old_iso_lvl = conn.isolation_level
290- conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
291- cursor = conn.cursor()
292- cursor.execute('DROP DATABASE "%s"' % listname)
293- conn.set_isolation_level(old_iso_lvl)
294+ DlistUtils.remove_dlist(listname)
295 except:
296 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)
297

Subscribers

People subscribed via source and target branches