liquid_feedback_frontend
changeset 879:ea3d3757ddc3
Added support for voting comments
author | bsw |
---|---|
date | Mon Aug 20 01:00:09 2012 +0200 (2012-08-20) |
parents | a75d64b432ec |
children | fe39c1fb541b |
files | app/main/_filter/21_auth.lua app/main/initiative/_show.lua app/main/issue/_show.lua app/main/member/_show_thumb.lua app/main/vote/_action/update.lua app/main/vote/list.lua env/model/has_rendered_content.lua model/direct_voter.lua model/rendered_voter_comment.lua static/icons/16/comment.png static/js/voting.js static/style.css |
line diff
1.1 --- a/app/main/_filter/21_auth.lua Sat Aug 18 23:22:20 2012 +0200 1.2 +++ b/app/main/_filter/21_auth.lua Mon Aug 20 01:00:09 2012 +0200 1.3 @@ -51,7 +51,8 @@ 1.4 end 1.5 1.6 if app.session:has_access("everything") then 1.7 - if module == "member" and (view == "show" or view == "history") then 1.8 + if module == "member" and (view == "show" or view == "history") 1.9 + or module == "vote" and view == "list" then 1.10 auth_needed = false 1.11 end 1.12 end
2.1 --- a/app/main/initiative/_show.lua Sat Aug 18 23:22:20 2012 +0200 2.2 +++ b/app/main/initiative/_show.lua Mon Aug 20 01:00:09 2012 +0200 2.3 @@ -427,6 +427,7 @@ 2.4 :left_join("vote", nil, { "vote.initiative_id = ? AND vote.member_id = member.id", initiative.id }) 2.5 :add_field("direct_voter.weight as voter_weight") 2.6 :add_field("coalesce(vote.grade, 0) as grade") 2.7 + :add_field("direct_voter.comment as voter_comment") 2.8 :left_join("initiative", nil, "initiative.id = vote.initiative_id") 2.9 :left_join("issue", nil, "issue.id = initiative.issue_id") 2.10
3.1 --- a/app/main/issue/_show.lua Sat Aug 18 23:22:20 2012 +0200 3.2 +++ b/app/main/issue/_show.lua Mon Aug 20 01:00:09 2012 +0200 3.3 @@ -13,7 +13,14 @@ 3.4 local voteable = app.session.member_id and issue.state == 'voting' and 3.5 app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) 3.6 3.7 -local vote_link_text = direct_voter and _"Change vote" or _"Vote now" 3.8 +local vote_comment_able = app.session.member_id and issue.closed and direct_voter 3.9 + 3.10 +local vote_link_text 3.11 +if voteable then 3.12 + vote_link_text = direct_voter and _"Change vote" or _"Vote now" 3.13 +elseif vote_comment_able then 3.14 + vote_link_text = direct_voter and _"Change voting comment" 3.15 +end 3.16 3.17 3.18 local class = "issue" 3.19 @@ -87,7 +94,7 @@ 3.20 3.21 local links = {} 3.22 3.23 - if voteable then 3.24 + if vote_link_text then 3.25 links[#links+1] ={ 3.26 content = vote_link_text, 3.27 module = "vote",
4.1 --- a/app/main/member/_show_thumb.lua Sat Aug 18 23:22:20 2012 +0200 4.2 +++ b/app/main/member/_show_thumb.lua Mon Aug 20 01:00:09 2012 +0200 4.3 @@ -59,6 +59,16 @@ 4.4 member_id = member.id, 4.5 }, 4.6 content = function() 4.7 + if (member.voter_comment) then 4.8 + ui.image{ 4.9 + attr = { 4.10 + alt = _"Voting comment available", 4.11 + title = _"Voting comment available" 4.12 + }, 4.13 + static = "icons/16/comment.png" 4.14 + } 4.15 + end 4.16 + 4.17 if member.grade > 0 then 4.18 ui.image{ 4.19 attr = { 4.20 @@ -121,7 +131,7 @@ 4.21 } 4.22 } 4.23 end 4.24 - 4.25 + 4.26 if initiator and initiator.accepted then 4.27 if member.accepted == nil then 4.28 slot.put(_"Invited")
5.1 --- a/app/main/vote/_action/update.lua Sat Aug 18 23:22:20 2012 +0200 5.2 +++ b/app/main/vote/_action/update.lua Mon Aug 20 01:00:09 2012 +0200 5.3 @@ -1,15 +1,22 @@ 5.4 +local cancel = param.get("cancel") and true or false 5.5 +if cancel then return end 5.6 + 5.7 local issue = Issue:new_selector():add_where{ "id = ?", param.get("issue_id", atom.integer) }:for_share():single_object_mode():exec() 5.8 5.9 +local preview = param.get("preview") or param.get("preview2") == "1" and true or false 5.10 + 5.11 if not app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then 5.12 error("access denied") 5.13 end 5.14 5.15 -if issue.closed then 5.16 +local update_comment = param.get("update_comment") == "1" and true or false 5.17 + 5.18 +if issue.closed and not update_comment then 5.19 slot.put_into("error", _"This issue is already closed.") 5.20 return false 5.21 end 5.22 5.23 -if issue.state ~= "voting" then 5.24 +if issue.state ~= "voting" and not issue.closed then 5.25 slot.put_into("error", _"Voting has not started yet.") 5.26 return false 5.27 end 5.28 @@ -30,44 +37,84 @@ 5.29 local tempvoting_string = param.get("scoring") 5.30 5.31 local tempvotings = {} 5.32 -for match in tempvoting_string:gmatch("([^;]+)") do 5.33 - for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do 5.34 - tempvotings[tonumber(initiative_id)] = tonumber(grade) 5.35 - if param.get("move_up_" .. initiative_id .. ".x", atom.integer) then 5.36 - move_up = tonumber(initiative_id) 5.37 - elseif param.get("move_down_" .. initiative_id .. ".x", atom.integer) then 5.38 - move_down = tonumber(initiative_id) 5.39 +if not update_comment then 5.40 + for match in tempvoting_string:gmatch("([^;]+)") do 5.41 + for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do 5.42 + tempvotings[tonumber(initiative_id)] = tonumber(grade) 5.43 + if param.get("move_up_" .. initiative_id .. ".x", atom.integer) then 5.44 + move_up = tonumber(initiative_id) 5.45 + elseif param.get("move_down_" .. initiative_id .. ".x", atom.integer) then 5.46 + move_down = tonumber(initiative_id) 5.47 + end 5.48 end 5.49 end 5.50 end 5.51 5.52 if not move_down and not move_up then 5.53 - if not direct_voter then 5.54 - direct_voter = DirectVoter:new() 5.55 - direct_voter.issue_id = issue.id 5.56 - direct_voter.member_id = app.session.member_id 5.57 + if not preview then 5.58 + if not direct_voter then 5.59 + if issue.closed then 5.60 + slot.put_into("error", _"This issue is already closed.") 5.61 + return false 5.62 + else 5.63 + direct_voter = DirectVoter:new() 5.64 + direct_voter.issue_id = issue.id 5.65 + direct_voter.member_id = app.session.member_id 5.66 + direct_voter:save() 5.67 + 5.68 + direct_voter = DirectVoter:by_pk(issue.id, app.session.member_id) 5.69 + end 5.70 + end 5.71 + 5.72 + local formatting_engine = param.get("formatting_engine") 5.73 + local comment = util.trim(param.get("comment")) 5.74 + 5.75 + if comment ~= direct_voter.comment then 5.76 + if #comment > 0 then 5.77 + direct_voter.formatting_engine = formatting_engine 5.78 + direct_voter.comment = comment 5.79 + direct_voter.comment_changed = 'now' 5.80 + direct_voter:render_content(true) 5.81 + else 5.82 + direct_voter.formatting_engine = null 5.83 + direct_voter.comment = null 5.84 + direct_voter.comment_changed = 'now' 5.85 + end 5.86 + end 5.87 + direct_voter:save() 5.88 + 5.89 end 5.90 5.91 - direct_voter:save() 5.92 - 5.93 - local scoring = param.get("scoring") 5.94 + if not update_comment then 5.95 + local scoring = param.get("scoring") 5.96 5.97 - for initiative_id, grade in scoring:gmatch("([^:;]+):([^:;]+)") do 5.98 - local initiative_id = tonumber(initiative_id) 5.99 - local grade = tonumber(grade) 5.100 - local initiative = Initiative:by_id(initiative_id) 5.101 - if initiative.issue.id ~= issue.id then 5.102 - error("initiative from wrong issue") 5.103 + for initiative_id, grade in scoring:gmatch("([^:;]+):([^:;]+)") do 5.104 + local initiative_id = tonumber(initiative_id) 5.105 + local grade = tonumber(grade) 5.106 + local initiative = Initiative:by_id(initiative_id) 5.107 + if initiative.issue.id ~= issue.id then 5.108 + error("initiative from wrong issue") 5.109 + end 5.110 + if not preview and not issue.closed then 5.111 + local vote = Vote:by_pk(initiative_id, app.session.member.id) 5.112 + if not vote then 5.113 + vote = Vote:new() 5.114 + vote.issue_id = issue.id 5.115 + vote.initiative_id = initiative.id 5.116 + vote.member_id = app.session.member.id 5.117 + end 5.118 + vote.grade = grade 5.119 + vote:save() 5.120 + end 5.121 end 5.122 - local vote = Vote:by_pk(initiative_id, app.session.member.id) 5.123 - if not vote then 5.124 - vote = Vote:new() 5.125 - vote.issue_id = issue.id 5.126 - vote.initiative_id = initiative.id 5.127 - vote.member_id = app.session.member.id 5.128 - end 5.129 - vote.grade = grade 5.130 - vote:save() 5.131 + end 5.132 + 5.133 + if not preview and not cancel then 5.134 + request.redirect{ 5.135 + module = "issue", 5.136 + view = "show", 5.137 + id = issue.id 5.138 + } 5.139 end 5.140 5.141 else
6.1 --- a/app/main/vote/list.lua Sat Aug 18 23:22:20 2012 +0200 6.2 +++ b/app/main/vote/list.lua Mon Aug 20 01:00:09 2012 +0200 6.3 @@ -3,7 +3,7 @@ 6.4 local member_id = param.get("member_id", atom.integer) 6.5 local member 6.6 6.7 -local readonly = false 6.8 +local preview = param.get("preview") or param.get("preview2") == "1" and true or false 6.9 6.10 if member_id then 6.11 if not issue.closed then 6.12 @@ -15,15 +15,21 @@ 6.13 6.14 if issue.closed then 6.15 if not member then 6.16 - slot.put_into("error", _"This issue is already closed.") 6.17 - end 6.18 - if not member then 6.19 member = app.session.member 6.20 end 6.21 readonly = true 6.22 end 6.23 6.24 +local submit_button_text = _"Finish voting" 6.25 + 6.26 +if issue.closed then 6.27 + submit_button_text = _"Update voting comment" 6.28 +end 6.29 + 6.30 +local direct_voter 6.31 + 6.32 if member then 6.33 + direct_voter = DirectVoter:by_pk(issue.id, member.id) 6.34 local str = _("Ballot of '#{member_name}' for issue ##{issue_id}", 6.35 {member_name = string.format('<a href="%s">%s</a>', 6.36 encode.url{ 6.37 @@ -44,6 +50,9 @@ 6.38 ui.title(str) 6.39 else 6.40 member = app.session.member 6.41 + 6.42 + direct_voter = DirectVoter:by_pk(issue.id, member.id) 6.43 + 6.44 ui.title(_"Voting") 6.45 6.46 ui.actions(function() 6.47 @@ -53,28 +62,31 @@ 6.48 view = "show", 6.49 id = issue.id 6.50 } 6.51 - slot.put(" · ") 6.52 - ui.link{ 6.53 - text = _"Discard voting", 6.54 - module = "vote", 6.55 - action = "update", 6.56 - params = { 6.57 - issue_id = issue.id, 6.58 - discard = true 6.59 - }, 6.60 - routing = { 6.61 - default = { 6.62 - mode = "redirect", 6.63 - module = "issue", 6.64 - view = "show", 6.65 - id = issue.id 6.66 + if direct_voter then 6.67 + slot.put(" · ") 6.68 + ui.link{ 6.69 + text = _"Discard voting", 6.70 + module = "vote", 6.71 + action = "update", 6.72 + params = { 6.73 + issue_id = issue.id, 6.74 + discard = true 6.75 + }, 6.76 + routing = { 6.77 + default = { 6.78 + mode = "redirect", 6.79 + module = "issue", 6.80 + view = "show", 6.81 + id = issue.id 6.82 + } 6.83 } 6.84 } 6.85 - } 6.86 + end 6.87 end) 6.88 end 6.89 6.90 6.91 + 6.92 local tempvoting_string = param.get("scoring") 6.93 6.94 local tempvotings = {} 6.95 @@ -167,6 +179,7 @@ 6.96 } 6.97 6.98 ui.form{ 6.99 + record = direct_voter, 6.100 attr = { 6.101 id = "voting_form", 6.102 class = readonly and "voting_form_readonly" or "voting_form_active" 6.103 @@ -174,16 +187,8 @@ 6.104 module = "vote", 6.105 action = "update", 6.106 params = { issue_id = issue.id }, 6.107 - routing = { 6.108 - default = { 6.109 - mode = "redirect", 6.110 - module = "issue", 6.111 - view = "show", 6.112 - id = issue.id 6.113 - } 6.114 - }, 6.115 content = function() 6.116 - if not readonly then 6.117 + if not readonly or preview then 6.118 local scoring = param.get("scoring") 6.119 if not scoring then 6.120 for i, initiative in ipairs(initiatives) do 6.121 @@ -210,8 +215,8 @@ 6.122 tag = "input", 6.123 attr = { 6.124 type = "submit", 6.125 - class = "voting_done", 6.126 - value = _"Finish voting" 6.127 + class = "voting_done1", 6.128 + value = submit_button_text 6.129 } 6.130 } 6.131 end 6.132 @@ -423,15 +428,62 @@ 6.133 end 6.134 end 6.135 } 6.136 - if not readonly then 6.137 - ui.tag{ 6.138 - tag = "input", 6.139 - attr = { 6.140 - type = "submit", 6.141 - class = "voting_done", 6.142 - value = _"Finish voting" 6.143 + if app.session.member_id and preview then 6.144 + local formatting_engine = param.get("formatting_engine") 6.145 + local comment = param.get("comment") 6.146 + local rendered_comment = format.wiki_text(comment, formatting_engine) 6.147 + slot.put(rendered_comment) 6.148 + end 6.149 + if (readonly or direct_voter.comment) and not preview then 6.150 + ui.heading{ level = "2", content = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) }) } 6.151 + if direct_voter.comment then 6.152 + local rendered_comment = direct_voter:get_content('html') 6.153 + ui.container{ attr = { class = "member_statement" }, content = function() 6.154 + slot.put(rendered_comment) 6.155 + end } 6.156 + slot.put("<br />") 6.157 + end 6.158 + end 6.159 + if app.session.member_id and app.session.member_id == member.id then 6.160 + if not readonly or direct_voter then 6.161 + ui.field.hidden{ name = "update_comment", value = param.get("update_comment") or issue.closed and "1" } 6.162 + ui.field.select{ 6.163 + label = _"Wiki engine for statement", 6.164 + name = "formatting_engine", 6.165 + foreign_records = { 6.166 + { id = "rocketwiki", name = "RocketWiki" }, 6.167 + { id = "compat", name = _"Traditional wiki syntax" } 6.168 + }, 6.169 + attr = {id = "formatting_engine"}, 6.170 + foreign_id = "id", 6.171 + foreign_name = "name", 6.172 + value = param.get("formatting_engine") or direct_voter and direct_voter.formatting_engine 6.173 } 6.174 - } 6.175 + ui.field.text{ 6.176 + label = _"Voting comment (optional)", 6.177 + name = "comment", 6.178 + multiline = true, 6.179 + value = param.get("comment") or direct_voter and direct_voter.comment, 6.180 + attr = { style = "height: 20ex;" }, 6.181 + } 6.182 + ui.field.hidden{ name = "preview2", attr = { id = "preview2" }, value = "0" } 6.183 + ui.submit{ 6.184 + name = "preview", 6.185 + value = _"Preview voting comment", 6.186 + attr = { class = "preview" } 6.187 + } 6.188 + end 6.189 + if not readonly or preview or direct_voter then 6.190 + slot.put(" ") 6.191 + ui.tag{ 6.192 + tag = "input", 6.193 + attr = { 6.194 + type = "submit", 6.195 + class = "voting_done2", 6.196 + value = submit_button_text 6.197 + } 6.198 + } 6.199 + end 6.200 end 6.201 end 6.202 }
7.1 --- a/env/model/has_rendered_content.lua Sat Aug 18 23:22:20 2012 +0200 7.2 +++ b/env/model/has_rendered_content.lua Mon Aug 20 01:00:09 2012 +0200 7.3 @@ -5,15 +5,30 @@ 7.4 -- render content to html, save it as rendered_class and return it 7.5 function class.object:render_content(force_rendering) 7.6 -- local draft for update 7.7 - local lock = class:new_selector() 7.8 - :add_where{ "id = ?", self.id } 7.9 - :single_object_mode() 7.10 - :for_update() 7.11 - :exec() 7.12 + 7.13 + local selector = class:new_selector() 7.14 + 7.15 + if class.primary_key then 7.16 + for i, key in ipairs(class.primary_key) do 7.17 + selector:add_where{ "$ = ?", { key }, self[key] } 7.18 + trace.debug(key, self[key], self.id) 7.19 + end 7.20 + else 7.21 + selector:add_where{ "id = ?", self.id } 7.22 + end 7.23 + 7.24 + local lock = selector:single_object_mode():for_update():exec() 7.25 + 7.26 -- check if there is already a rendered content 7.27 - local rendered = rendered_class:new_selector() 7.28 - :add_where{ class.table .. "_id = ?", self.id } 7.29 - :add_where{ "format = 'html'" } 7.30 + local selector = rendered_class:new_selector() 7.31 + if type(class.primary_key) == "table" then 7.32 + for i, key in ipairs(class.primary_key) do 7.33 + selector:add_where{ "$.$ = ?", { rendered_class.table }, { key }, self[key] } 7.34 + end 7.35 + else 7.36 + selector:add_where{ "$.id = ?", { rendered_class.table }, self.id } 7.37 + end 7.38 + local rendered = selector:add_where{ "format = 'html'" } 7.39 :optional_object_mode() 7.40 :exec() 7.41 if rendered then 7.42 @@ -25,7 +40,13 @@ 7.43 end 7.44 -- create rendered_class record 7.45 local rendered = rendered_class:new() 7.46 - rendered[class.table .. "_id"] = self.id 7.47 + if type(class.primary_key) == "table" then 7.48 + for i, key in ipairs(class.primary_key) do 7.49 + rendered[key] = self[key] 7.50 + end 7.51 + else 7.52 + rendered[class.table .. "_id"] = self.id 7.53 + end 7.54 rendered.format = "html" 7.55 rendered.content = format.wiki_text(self[content_field_name], self.formatting_engine) 7.56 rendered:save() 7.57 @@ -36,9 +57,15 @@ 7.58 -- returns rendered version for specific format 7.59 function class.object:get_content(format) 7.60 -- Fetch rendered_class record for specified format 7.61 - local rendered = rendered_class:new_selector() 7.62 - :add_where{ class.table .. "_id = ?", self.id } 7.63 - :add_where{ "format = ?", format } 7.64 + local selector = rendered_class:new_selector() 7.65 + if type(class.primary_key) == "table" then 7.66 + for i, key in ipairs(class.primary_key) do 7.67 + selector:add_where{ "$.$ = ?", { rendered_class.table }, { key }, self.id } 7.68 + end 7.69 + else 7.70 + selector:add_where{ class.table .. "_id = ?", self.id } 7.71 + end 7.72 + local rendered = selector:add_where{ "format = ?", format } 7.73 :optional_object_mode() 7.74 :exec() 7.75 -- If this format isn't rendered yet, render it
8.1 --- a/model/direct_voter.lua Sat Aug 18 23:22:20 2012 +0200 8.2 +++ b/model/direct_voter.lua Mon Aug 20 01:00:09 2012 +0200 8.3 @@ -18,6 +18,8 @@ 8.4 ref = 'member', 8.5 } 8.6 8.7 +model.has_rendered_content(DirectVoter, RenderedVoterComment, "comment") 8.8 + 8.9 function DirectVoter:by_pk(issue_id, member_id) 8.10 return self:new_selector() 8.11 :add_where{ "issue_id = ? AND member_id = ?", issue_id, member_id }
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 9.2 +++ b/model/rendered_voter_comment.lua Mon Aug 20 01:00:09 2012 +0200 9.3 @@ -0,0 +1,3 @@ 9.4 +RenderedVoterComment = mondelefant.new_class() 9.5 +RenderedVoterComment.table = 'rendered_voter_comment' 9.6 +RenderedVoterComment.primary_key = { "issue_id", "member_id", "format" }
10.1 Binary file static/icons/16/comment.png has changed
11.1 --- a/static/js/voting.js Sat Aug 18 23:22:20 2012 +0200 11.2 +++ b/static/js/voting.js Mon Aug 20 01:00:09 2012 +0200 11.3 @@ -287,8 +287,13 @@ 11.4 var elements = document.getElementsByTagName("input"); 11.5 for (var i=0; i<elements.length; i++) { 11.6 var element = elements[i]; 11.7 - if (element.className == "voting_done") { 11.8 + if (element.className == "voting_done1" || 11.9 + element.className == "voting_done2" || 11.10 + element.name == "preview") { 11.11 element.addEventListener("click", function(event) { 11.12 + if (event.srcElement.name == "preview") { 11.13 + document.getElementById("preview2").value = "1"; 11.14 + } 11.15 var scoringString = ""; 11.16 var approvalCount = 0; 11.17 var disapprovalCount = 0;
12.1 --- a/static/style.css Sat Aug 18 23:22:20 2012 +0200 12.2 +++ b/static/style.css Mon Aug 20 01:00:09 2012 +0200 12.3 @@ -727,6 +727,7 @@ 12.4 .login input[type=password], 12.5 .vertical input[type=password], 12.6 .vertical textarea, 12.7 +#voting_form textarea, 12.8 .vertical select { 12.9 font-family: sans-serif; 12.10 font-size: 100%; 12.11 @@ -745,6 +746,7 @@ 12.12 padding-right: 0; 12.13 } 12.14 12.15 +#voting_form .ui_field_label, 12.16 .login .ui_field_label, 12.17 .vertical .ui_field_label { 12.18 line-height: 180%; 12.19 @@ -788,6 +790,7 @@ 12.20 margin-bottom: 0; 12.21 } 12.22 12.23 +#voting_form input[type=submit], 12.24 .login input[type=submit], 12.25 .vertical input[type=submit] { 12.26 font-size: 100%; 12.27 @@ -798,6 +801,11 @@ 12.28 padding: 0.75ex; 12.29 } 12.30 12.31 +#voting_form input[type=submit] { 12.32 + margin-left: 0; 12.33 +} 12.34 + 12.35 + 12.36 .login input[type=submit]:hover, 12.37 .vertical input[type=submit]:hover, 12.38 .login input[type=submit]:focus,