# HG changeset patch # User bsw # Date 1345417209 -7200 # Node ID ea3d3757ddc34715a29c871694c30eb6ab9573e5 # Parent a75d64b432ec54b0fef6e92f48c15083988eae01 Added support for voting comments diff -r a75d64b432ec -r ea3d3757ddc3 app/main/_filter/21_auth.lua --- a/app/main/_filter/21_auth.lua Sat Aug 18 23:22:20 2012 +0200 +++ b/app/main/_filter/21_auth.lua Mon Aug 20 01:00:09 2012 +0200 @@ -51,7 +51,8 @@ end if app.session:has_access("everything") then - if module == "member" and (view == "show" or view == "history") then + if module == "member" and (view == "show" or view == "history") + or module == "vote" and view == "list" then auth_needed = false end end diff -r a75d64b432ec -r ea3d3757ddc3 app/main/initiative/_show.lua --- a/app/main/initiative/_show.lua Sat Aug 18 23:22:20 2012 +0200 +++ b/app/main/initiative/_show.lua Mon Aug 20 01:00:09 2012 +0200 @@ -427,6 +427,7 @@ :left_join("vote", nil, { "vote.initiative_id = ? AND vote.member_id = member.id", initiative.id }) :add_field("direct_voter.weight as voter_weight") :add_field("coalesce(vote.grade, 0) as grade") + :add_field("direct_voter.comment as voter_comment") :left_join("initiative", nil, "initiative.id = vote.initiative_id") :left_join("issue", nil, "issue.id = initiative.issue_id") diff -r a75d64b432ec -r ea3d3757ddc3 app/main/issue/_show.lua --- a/app/main/issue/_show.lua Sat Aug 18 23:22:20 2012 +0200 +++ b/app/main/issue/_show.lua Mon Aug 20 01:00:09 2012 +0200 @@ -13,7 +13,14 @@ local voteable = app.session.member_id and issue.state == 'voting' and app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) -local vote_link_text = direct_voter and _"Change vote" or _"Vote now" +local vote_comment_able = app.session.member_id and issue.closed and direct_voter + +local vote_link_text +if voteable then + vote_link_text = direct_voter and _"Change vote" or _"Vote now" +elseif vote_comment_able then + vote_link_text = direct_voter and _"Change voting comment" +end local class = "issue" @@ -87,7 +94,7 @@ local links = {} - if voteable then + if vote_link_text then links[#links+1] ={ content = vote_link_text, module = "vote", diff -r a75d64b432ec -r ea3d3757ddc3 app/main/member/_show_thumb.lua --- a/app/main/member/_show_thumb.lua Sat Aug 18 23:22:20 2012 +0200 +++ b/app/main/member/_show_thumb.lua Mon Aug 20 01:00:09 2012 +0200 @@ -59,6 +59,16 @@ member_id = member.id, }, content = function() + if (member.voter_comment) then + ui.image{ + attr = { + alt = _"Voting comment available", + title = _"Voting comment available" + }, + static = "icons/16/comment.png" + } + end + if member.grade > 0 then ui.image{ attr = { @@ -121,7 +131,7 @@ } } end - + if initiator and initiator.accepted then if member.accepted == nil then slot.put(_"Invited") diff -r a75d64b432ec -r ea3d3757ddc3 app/main/vote/_action/update.lua --- a/app/main/vote/_action/update.lua Sat Aug 18 23:22:20 2012 +0200 +++ b/app/main/vote/_action/update.lua Mon Aug 20 01:00:09 2012 +0200 @@ -1,15 +1,22 @@ +local cancel = param.get("cancel") and true or false +if cancel then return end + local issue = Issue:new_selector():add_where{ "id = ?", param.get("issue_id", atom.integer) }:for_share():single_object_mode():exec() +local preview = param.get("preview") or param.get("preview2") == "1" and true or false + if not app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then error("access denied") end -if issue.closed then +local update_comment = param.get("update_comment") == "1" and true or false + +if issue.closed and not update_comment then slot.put_into("error", _"This issue is already closed.") return false end -if issue.state ~= "voting" then +if issue.state ~= "voting" and not issue.closed then slot.put_into("error", _"Voting has not started yet.") return false end @@ -30,44 +37,84 @@ local tempvoting_string = param.get("scoring") local tempvotings = {} -for match in tempvoting_string:gmatch("([^;]+)") do - for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do - tempvotings[tonumber(initiative_id)] = tonumber(grade) - if param.get("move_up_" .. initiative_id .. ".x", atom.integer) then - move_up = tonumber(initiative_id) - elseif param.get("move_down_" .. initiative_id .. ".x", atom.integer) then - move_down = tonumber(initiative_id) +if not update_comment then + for match in tempvoting_string:gmatch("([^;]+)") do + for initiative_id, grade in match:gmatch("([^:;]+):([^:;]+)") do + tempvotings[tonumber(initiative_id)] = tonumber(grade) + if param.get("move_up_" .. initiative_id .. ".x", atom.integer) then + move_up = tonumber(initiative_id) + elseif param.get("move_down_" .. initiative_id .. ".x", atom.integer) then + move_down = tonumber(initiative_id) + end end end end if not move_down and not move_up then - if not direct_voter then - direct_voter = DirectVoter:new() - direct_voter.issue_id = issue.id - direct_voter.member_id = app.session.member_id + if not preview then + if not direct_voter then + if issue.closed then + slot.put_into("error", _"This issue is already closed.") + return false + else + direct_voter = DirectVoter:new() + direct_voter.issue_id = issue.id + direct_voter.member_id = app.session.member_id + direct_voter:save() + + direct_voter = DirectVoter:by_pk(issue.id, app.session.member_id) + end + end + + local formatting_engine = param.get("formatting_engine") + local comment = util.trim(param.get("comment")) + + if comment ~= direct_voter.comment then + if #comment > 0 then + direct_voter.formatting_engine = formatting_engine + direct_voter.comment = comment + direct_voter.comment_changed = 'now' + direct_voter:render_content(true) + else + direct_voter.formatting_engine = null + direct_voter.comment = null + direct_voter.comment_changed = 'now' + end + end + direct_voter:save() + end - direct_voter:save() - - local scoring = param.get("scoring") + if not update_comment then + local scoring = param.get("scoring") - for initiative_id, grade in scoring:gmatch("([^:;]+):([^:;]+)") do - local initiative_id = tonumber(initiative_id) - local grade = tonumber(grade) - local initiative = Initiative:by_id(initiative_id) - if initiative.issue.id ~= issue.id then - error("initiative from wrong issue") + for initiative_id, grade in scoring:gmatch("([^:;]+):([^:;]+)") do + local initiative_id = tonumber(initiative_id) + local grade = tonumber(grade) + local initiative = Initiative:by_id(initiative_id) + if initiative.issue.id ~= issue.id then + error("initiative from wrong issue") + end + if not preview and not issue.closed then + local vote = Vote:by_pk(initiative_id, app.session.member.id) + if not vote then + vote = Vote:new() + vote.issue_id = issue.id + vote.initiative_id = initiative.id + vote.member_id = app.session.member.id + end + vote.grade = grade + vote:save() + end end - local vote = Vote:by_pk(initiative_id, app.session.member.id) - if not vote then - vote = Vote:new() - vote.issue_id = issue.id - vote.initiative_id = initiative.id - vote.member_id = app.session.member.id - end - vote.grade = grade - vote:save() + end + + if not preview and not cancel then + request.redirect{ + module = "issue", + view = "show", + id = issue.id + } end else diff -r a75d64b432ec -r ea3d3757ddc3 app/main/vote/list.lua --- a/app/main/vote/list.lua Sat Aug 18 23:22:20 2012 +0200 +++ b/app/main/vote/list.lua Mon Aug 20 01:00:09 2012 +0200 @@ -3,7 +3,7 @@ local member_id = param.get("member_id", atom.integer) local member -local readonly = false +local preview = param.get("preview") or param.get("preview2") == "1" and true or false if member_id then if not issue.closed then @@ -15,15 +15,21 @@ if issue.closed then if not member then - slot.put_into("error", _"This issue is already closed.") - end - if not member then member = app.session.member end readonly = true end +local submit_button_text = _"Finish voting" + +if issue.closed then + submit_button_text = _"Update voting comment" +end + +local direct_voter + if member then + direct_voter = DirectVoter:by_pk(issue.id, member.id) local str = _("Ballot of '#{member_name}' for issue ##{issue_id}", {member_name = string.format('%s', encode.url{ @@ -44,6 +50,9 @@ ui.title(str) else member = app.session.member + + direct_voter = DirectVoter:by_pk(issue.id, member.id) + ui.title(_"Voting") ui.actions(function() @@ -53,28 +62,31 @@ view = "show", id = issue.id } - slot.put(" · ") - ui.link{ - text = _"Discard voting", - module = "vote", - action = "update", - params = { - issue_id = issue.id, - discard = true - }, - routing = { - default = { - mode = "redirect", - module = "issue", - view = "show", - id = issue.id + if direct_voter then + slot.put(" · ") + ui.link{ + text = _"Discard voting", + module = "vote", + action = "update", + params = { + issue_id = issue.id, + discard = true + }, + routing = { + default = { + mode = "redirect", + module = "issue", + view = "show", + id = issue.id + } } } - } + end end) end + local tempvoting_string = param.get("scoring") local tempvotings = {} @@ -167,6 +179,7 @@ } ui.form{ + record = direct_voter, attr = { id = "voting_form", class = readonly and "voting_form_readonly" or "voting_form_active" @@ -174,16 +187,8 @@ module = "vote", action = "update", params = { issue_id = issue.id }, - routing = { - default = { - mode = "redirect", - module = "issue", - view = "show", - id = issue.id - } - }, content = function() - if not readonly then + if not readonly or preview then local scoring = param.get("scoring") if not scoring then for i, initiative in ipairs(initiatives) do @@ -210,8 +215,8 @@ tag = "input", attr = { type = "submit", - class = "voting_done", - value = _"Finish voting" + class = "voting_done1", + value = submit_button_text } } end @@ -423,15 +428,62 @@ end end } - if not readonly then - ui.tag{ - tag = "input", - attr = { - type = "submit", - class = "voting_done", - value = _"Finish voting" + if app.session.member_id and preview then + local formatting_engine = param.get("formatting_engine") + local comment = param.get("comment") + local rendered_comment = format.wiki_text(comment, formatting_engine) + slot.put(rendered_comment) + end + if (readonly or direct_voter.comment) and not preview then + ui.heading{ level = "2", content = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) }) } + if direct_voter.comment then + local rendered_comment = direct_voter:get_content('html') + ui.container{ attr = { class = "member_statement" }, content = function() + slot.put(rendered_comment) + end } + slot.put("
") + end + end + if app.session.member_id and app.session.member_id == member.id then + if not readonly or direct_voter then + ui.field.hidden{ name = "update_comment", value = param.get("update_comment") or issue.closed and "1" } + ui.field.select{ + label = _"Wiki engine for statement", + name = "formatting_engine", + foreign_records = { + { id = "rocketwiki", name = "RocketWiki" }, + { id = "compat", name = _"Traditional wiki syntax" } + }, + attr = {id = "formatting_engine"}, + foreign_id = "id", + foreign_name = "name", + value = param.get("formatting_engine") or direct_voter and direct_voter.formatting_engine } - } + ui.field.text{ + label = _"Voting comment (optional)", + name = "comment", + multiline = true, + value = param.get("comment") or direct_voter and direct_voter.comment, + attr = { style = "height: 20ex;" }, + } + ui.field.hidden{ name = "preview2", attr = { id = "preview2" }, value = "0" } + ui.submit{ + name = "preview", + value = _"Preview voting comment", + attr = { class = "preview" } + } + end + if not readonly or preview or direct_voter then + slot.put(" ") + ui.tag{ + tag = "input", + attr = { + type = "submit", + class = "voting_done2", + value = submit_button_text + } + } + end end end } diff -r a75d64b432ec -r ea3d3757ddc3 env/model/has_rendered_content.lua --- a/env/model/has_rendered_content.lua Sat Aug 18 23:22:20 2012 +0200 +++ b/env/model/has_rendered_content.lua Mon Aug 20 01:00:09 2012 +0200 @@ -5,15 +5,30 @@ -- render content to html, save it as rendered_class and return it function class.object:render_content(force_rendering) -- local draft for update - local lock = class:new_selector() - :add_where{ "id = ?", self.id } - :single_object_mode() - :for_update() - :exec() + + local selector = class:new_selector() + + if class.primary_key then + for i, key in ipairs(class.primary_key) do + selector:add_where{ "$ = ?", { key }, self[key] } + trace.debug(key, self[key], self.id) + end + else + selector:add_where{ "id = ?", self.id } + end + + local lock = selector:single_object_mode():for_update():exec() + -- check if there is already a rendered content - local rendered = rendered_class:new_selector() - :add_where{ class.table .. "_id = ?", self.id } - :add_where{ "format = 'html'" } + local selector = rendered_class:new_selector() + if type(class.primary_key) == "table" then + for i, key in ipairs(class.primary_key) do + selector:add_where{ "$.$ = ?", { rendered_class.table }, { key }, self[key] } + end + else + selector:add_where{ "$.id = ?", { rendered_class.table }, self.id } + end + local rendered = selector:add_where{ "format = 'html'" } :optional_object_mode() :exec() if rendered then @@ -25,7 +40,13 @@ end -- create rendered_class record local rendered = rendered_class:new() - rendered[class.table .. "_id"] = self.id + if type(class.primary_key) == "table" then + for i, key in ipairs(class.primary_key) do + rendered[key] = self[key] + end + else + rendered[class.table .. "_id"] = self.id + end rendered.format = "html" rendered.content = format.wiki_text(self[content_field_name], self.formatting_engine) rendered:save() @@ -36,9 +57,15 @@ -- returns rendered version for specific format function class.object:get_content(format) -- Fetch rendered_class record for specified format - local rendered = rendered_class:new_selector() - :add_where{ class.table .. "_id = ?", self.id } - :add_where{ "format = ?", format } + local selector = rendered_class:new_selector() + if type(class.primary_key) == "table" then + for i, key in ipairs(class.primary_key) do + selector:add_where{ "$.$ = ?", { rendered_class.table }, { key }, self.id } + end + else + selector:add_where{ class.table .. "_id = ?", self.id } + end + local rendered = selector:add_where{ "format = ?", format } :optional_object_mode() :exec() -- If this format isn't rendered yet, render it diff -r a75d64b432ec -r ea3d3757ddc3 model/direct_voter.lua --- a/model/direct_voter.lua Sat Aug 18 23:22:20 2012 +0200 +++ b/model/direct_voter.lua Mon Aug 20 01:00:09 2012 +0200 @@ -18,6 +18,8 @@ ref = 'member', } +model.has_rendered_content(DirectVoter, RenderedVoterComment, "comment") + function DirectVoter:by_pk(issue_id, member_id) return self:new_selector() :add_where{ "issue_id = ? AND member_id = ?", issue_id, member_id } diff -r a75d64b432ec -r ea3d3757ddc3 model/rendered_voter_comment.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/model/rendered_voter_comment.lua Mon Aug 20 01:00:09 2012 +0200 @@ -0,0 +1,3 @@ +RenderedVoterComment = mondelefant.new_class() +RenderedVoterComment.table = 'rendered_voter_comment' +RenderedVoterComment.primary_key = { "issue_id", "member_id", "format" } diff -r a75d64b432ec -r ea3d3757ddc3 static/icons/16/comment.png Binary file static/icons/16/comment.png has changed diff -r a75d64b432ec -r ea3d3757ddc3 static/js/voting.js --- a/static/js/voting.js Sat Aug 18 23:22:20 2012 +0200 +++ b/static/js/voting.js Mon Aug 20 01:00:09 2012 +0200 @@ -287,8 +287,13 @@ var elements = document.getElementsByTagName("input"); for (var i=0; i