# 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