Opened 12 years ago
Last modified 5 years ago
#10703 new enhancement
[PATCH] support of fully anonymous polls
Reported by: | falkb | Owned by: | |
---|---|---|---|
Priority: | high | Component: | PollMacro |
Severity: | normal | Keywords: | |
Cc: | Trac Release: |
Description
This patch adds the ability to make fully anonymous polls, simply by the use of pattern (*)
at the end of the poll question:
-
pollmacro/trunk/tracpoll/tracpoll.py
1 import hashlib 1 2 import os 2 3 import re 3 4 import pickle … … 49 50 finally: 50 51 fd.close() 51 52 52 def populate(self, req ):53 def populate(self, req, isAnonymousPoll): 53 54 """ Update poll based on HTTP request. """ 54 55 if req.args.get('poll', '') == self.key: 55 56 vote = req.args.get('vote', '') … … 58 59 if vote not in self.votes: 59 60 raise TracError('No such vote %s' % vote) 60 61 username = req.authname or 'anonymous' 62 if isAnonymousPoll: 63 username = hashlib.sha1(username).hexdigest() 61 64 for v, voters in self.votes.items(): 62 65 if username in voters: 63 66 self.votes[v].remove(username) 64 67 self.votes[vote] = self.votes[vote] + [username] 65 68 self.save() 66 69 67 def render(self, env, req ):70 def render(self, env, req, isAnonymousPoll): 68 71 out = StringIO() 69 72 can_vote = req.perm.has_permission('POLL_VOTE') 70 73 if can_vote: … … 76 79 ' <ul>\n' 77 80 % escape(self.title)) 78 81 username = req.authname or 'anonymous' 82 if isAnonymousPoll: 83 username = hashlib.sha1(username).hexdigest() 79 84 for id, style, vote in self.vote_defs: 80 85 hid = escape(str(id)) 81 86 out.write('<li%s>\n' % (style and ' class="%s"' % style or '')) … … 90 95 else: 91 96 out.write(vote) 92 97 if self.votes[id]: 93 out.write(' <span class="voters">(<span class="voter">' + 94 '</span>, <span class="voter">'.join(self.votes[id]) + 95 '</span>)</span>') 98 if isAnonymousPoll: 99 out.write(' <span class="voters">(%s)</span>' % len(self.votes[id])) 100 else: 101 out.write(' <span class="voters">(<span class="voter">' + 102 '</span>, <span class="voter">'.join(self.votes[id]) + 103 '</span>)</span>') 96 104 out.write('</li>\n') 97 105 if can_vote: 98 106 out.write('<input type="submit" value="Vote"/>') 99 107 else: 100 108 out.write("<br/><i>You don't have permission to vote. You may need to login.</i>") 101 109 out.write(' </ul>\n</fieldset>\n') … … 128 136 'Path where poll pickle dumps should be stored.') 129 137 130 138 def expand_macro(self, formatter, name, content): 139 isAnonymousPoll = False 131 140 req = formatter.req 132 141 if not content: 133 142 return system_message("A title must be provided as the first argument to the poll macro") … … 136 145 if len(content) < 2: 137 146 return system_message("One or more options must be provided to vote on.") 138 147 title = content.pop(0) 139 return self.render_poll(req, title, content) 148 if title.endswith('(*)'): 149 isAnonymousPoll = True 150 return self.render_poll(req, title, content, isAnonymousPoll) 140 151 141 def render_poll(self, req, title, votes ):152 def render_poll(self, req, title, votes, isAnonymousPoll): 142 153 add_stylesheet(req, 'poll/css/poll.css') 143 154 if not req.perm.has_permission('POLL_VIEW'): 144 155 return '' … … 184 195 185 196 poll = Poll(self.base_dir, title, all_votes) 186 197 if req.perm.has_permission('POLL_VOTE'): 187 poll.populate(req )188 return poll.render(self.env, req )198 poll.populate(req, isAnonymousPoll) 199 return poll.render(self.env, req, isAnonymousPoll) 189 200 190 201 # IPermissionRequestor methods 191 202 def get_permission_actions(self):
Attachments (0)
Change History (12)
comment:1 follow-up: 3 Changed 12 years ago by
comment:2 Changed 12 years ago by
Priority: | normal → high |
---|---|
Status: | new → assigned |
comment:3 follow-up: 9 Changed 12 years ago by
Replying to rjollos:
Looks like a nice feature. I think we should consider using a positional or kw arg rather than appending a pattern to the title.
I wonder how such arg can be programmed in a macro, even if the whole string is splitted in a string list by separator ';'.
comment:4 Changed 12 years ago by
Good point. I seem to have remember dealing with this for another plugin. I'll do some research and get back to you.
comment:5 Changed 12 years ago by
I also have another small patch that introduces a second pattern "(-)" which means the poll is closed. If one changes from "(*)" to "(-)", the anonymous polling is closed and no input is possible henceforward. I also somehow dislike that pattern trick but it's compatible with the ';'-separator divide mechanism.
comment:6 Changed 12 years ago by
Please do go ahead and post your new patch then. If we come up with a good way to handle kw args, we can always modify it. Could you take care of the style issue not in comment:1 as well?
comment:7 Changed 12 years ago by
Here we go, with closed-poll support now. Hope you like it. This replaces this first patch here:
-
pollmacro/trunk/tracpoll/tracpoll.py
1 import hashlib 1 2 import os 2 3 import re 3 4 import pickle … … 31 32 poll = pickle.load(fd) 32 33 finally: 33 34 fd.close() 35 if self.title.endswith('(-)'): 36 self.title = self.title.replace('(-)', '(*)') 34 37 assert self.title == poll['title'], \ 35 38 'Stored poll is not the same as this one.' 36 39 self.votes = dict([(k, v) for k, v in poll['votes'].iteritems() … … 49 52 finally: 50 53 fd.close() 51 54 52 def populate(self, req ):55 def populate(self, req, is_anonymous_poll): 53 56 """ Update poll based on HTTP request. """ 54 57 if req.args.get('poll', '') == self.key: 55 58 vote = req.args.get('vote', '') … … 58 61 if vote not in self.votes: 59 62 raise TracError('No such vote %s' % vote) 60 63 username = req.authname or 'anonymous' 64 if is_anonymous_poll: 65 username = hashlib.sha1(username).hexdigest() 61 66 for v, voters in self.votes.items(): 62 67 if username in voters: 63 68 self.votes[v].remove(username) 64 69 self.votes[vote] = self.votes[vote] + [username] 65 70 self.save() 66 71 67 def render(self, env, req ):72 def render(self, env, req, is_anonymous_poll, is_closed_poll): 68 73 out = StringIO() 69 74 can_vote = req.perm.has_permission('POLL_VOTE') 75 if is_closed_poll: 76 can_vote = False 70 77 if can_vote: 71 78 out.write('<form id="%(id)s" method="get" action="%(href)s#%(id)s">\n' 72 79 '<input type="hidden" name="poll" value="%(id)s"/>\n' … … 76 83 ' <ul>\n' 77 84 % escape(self.title)) 78 85 username = req.authname or 'anonymous' 86 if is_anonymous_poll: 87 username = hashlib.sha1(username).hexdigest() 79 88 for id, style, vote in self.vote_defs: 80 89 hid = escape(str(id)) 81 90 out.write('<li%s>\n' % (style and ' class="%s"' % style or '')) … … (this hunk was shorter than expected) 90 99 else: 91 100 out.write(vote) 92 101 if self.votes[id]: 93 out.write(' <span class="voters">(<span class="voter">' + 94 '</span>, <span class="voter">'.join(self.votes[id]) + 95 '</span>)</span>') 102 if is_anonymous_poll: 103 out.write(' <span class="voters">(%s)</span>' % len(self.votes[id])) 104 else: 105 out.write(' <span class="voters">(<span class="voter">' + 106 '</span>, <span class="voter">'.join(self.votes[id]) + 107 '</span>)</span>') 96 108 out.write('</li>\n') 97 109 if can_vote: 98 el se:110 elif not is_closed_poll: 99 111 out.write("<br/><i>You don't have permission to vote. You may need to login.</i>") 100 112 out.write(' </ul>\n</fieldset>\n') 101 113 can_vote and out.write('</form>\n') … … 128 140 'Path where poll pickle dumps should be stored.') 129 141 130 142 def expand_macro(self, formatter, name, content): 143 is_anonymous_poll = False 144 is_closed_poll = False 131 145 req = formatter.req 132 146 if not content: 133 147 return system_message("A title must be provided as the first argument to the poll macro") … … 136 150 if len(content) < 2: 137 151 return system_message("One or more options must be provided to vote on.") 138 152 title = content.pop(0) 139 return self.render_poll(req, title, content) 153 if title.endswith('(*)'): 154 is_anonymous_poll = True 155 if title.endswith('(-)'): 156 is_anonymous_poll = True 157 is_closed_poll = True 158 return self.render_poll(req, title, content, is_anonymous_poll, is_closed_poll) 140 159 141 def render_poll(self, req, title, votes ):160 def render_poll(self, req, title, votes, is_anonymous_poll, is_closed_poll): 142 161 add_stylesheet(req, 'poll/css/poll.css') 143 162 if not req.perm.has_permission('POLL_VIEW'): 144 163 return '' … … 184 203 185 204 poll = Poll(self.base_dir, title, all_votes) 186 205 if req.perm.has_permission('POLL_VOTE'): 187 poll.populate(req )188 return poll.render(self.env, req )206 poll.populate(req, is_anonymous_poll) 207 return poll.render(self.env, req, is_anonymous_poll, is_closed_poll) 189 208 190 209 # IPermissionRequestor methods 191 210 def get_permission_actions(self):
comment:8 Changed 12 years ago by
Addon: Not tested with closing of non-anonymous polls. Could be a TODO but could give you an idea with the given patch.
comment:9 follow-ups: 10 11 Changed 12 years ago by
Replying to anonymous:
Replying to rjollos:
Looks like a nice feature. I think we should consider using a positional or kw arg rather than appending a pattern to the title.
AFAICT +1
I wonder how such arg can be programmed in a macro, even if the whole string is splitted in a string list by separator ';'.
If I understood correctly your implicit question , that's what trac.wiki.api.parse_args
is for .
PS: Notice that commas in argument values have to be escaped
comment:10 Changed 12 years ago by
Replying to olemis:
PS: Notice that commas in argument values have to be escaped
Yeah, I think this is the best way to go. I've been trying to think about how we might better handle cases that the user hasn't escaped the args, since it will particularly cause problems for users that are upgrading and never took care to escape the poll question.
I haven't tested this out yet, but I was thinking of employing one of the following solutions:
- Define the variable that determines
is_anonymous_poll
as a kwarg and then append all of the regular args together, since it's safe to assume that if more than one regular arg exists then poll question wasn't escaped properly. - Define
is_anonymous_poll
as a regular arg, but when evaluating the output ofparse_args
, if there isn't a case-insensitive match totrue
, assume that the poll question wasn't escaped properly and append the arg to the 0th arg.
A prerequisite to setting up the above behavior is to wire up unit tests, which will be very valuable here.
comment:11 Changed 12 years ago by
Replying to olemis:
Replying to anonymous:
Replying to rjollos:
Looks like a nice feature. I think we should consider using a positional or kw arg rather than appending a pattern to the title.
AFAICT +1
Would it be compatible to older versions of poll, if we first split by ';', get a resulting string list, and then analyze the last one in the string list if it contains existing args?
Let's talk with the help of an example:
For instance, [[Poll(Please, tell me what do you think about foo?;Yes, it's OK;No, I don't like it;is_anonymous_poll=true)]]
will then be split into 4 strings where the 4th one contains the arguments that we then analyze with trac.wiki.api.parse_args
, and the check of the analyzing will have success on checking for known arguments.
If we just have [[Poll(Please, tell me what do you think about foo?;Yes, it's OK;No, I don't like it)]]
, after the ';'-split, trac.wiki.api.parse_args
will read the last partial string as (['No', 'I don't like it']), and then it can be decided as another poll answer instead of an argument list, because at least one of the strings is not known as valid argument.
Will this approach work?
comment:12 Changed 5 years ago by
Owner: | Ryan J Ollos deleted |
---|---|
Status: | assigned → new |
Looks like a nice feature. I think we should consider using a positional or kw arg rather than appending a pattern to the title.
isAnonymousPoll
should beis_anonymous_poll
or justis_anonymous
per the style used in the code.