Remove unused polls code to reduce query volume.

Due to use of Submission.{choices, options, bet_options} in realbody,
generating submission_listings resulted in extremely high volume of
SELECT queries.

In local testing with 6 posts, one of which had a poll with 2 options,
the removal of these calls reduced quantity of queries on the homepage
from 84 to 22.

Given that it was previously decided to remove the polls feature after
a regression while adding comment filtering, the remaining dead code
paths for polls were also removed.
This commit is contained in:
TLSM 2022-09-05 03:53:25 -04:00 committed by Ben Rog-Wilhelm
parent cf9819ca5b
commit dbaf0a1bfd
10 changed files with 8 additions and 359 deletions

View file

@ -149,52 +149,6 @@ function post(url) {
xhr.send(form); xhr.send(form);
}; };
function poll_vote(cid, parentid) {
for(let el of document.getElementsByClassName('presult-'+parentid)) {
el.classList.remove('d-none');
}
var type = document.getElementById(cid).checked;
var scoretext = document.getElementById('poll-' + cid);
var score = Number(scoretext.textContent);
if (type == true) scoretext.textContent = score + 1;
else scoretext.textContent = score - 1;
post('/vote/poll/' + cid + '?vote=' + type);
}
function choice_vote(cid, parentid) {
for(let el of document.getElementsByClassName('presult-'+parentid)) {
el.classList.remove('d-none');
}
let curr = document.getElementById(`current-${parentid}`)
if (curr && curr.value)
{
var scoretext = document.getElementById('choice-' + curr.value);
var score = Number(scoretext.textContent);
scoretext.textContent = score - 1;
}
var scoretext = document.getElementById('choice-' + cid);
var score = Number(scoretext.textContent);
scoretext.textContent = score + 1;
post('/vote/choice/' + cid);
curr.value = cid
}
function bet_vote(cid) {
for(let el of document.getElementsByClassName('bet')) {
el.disabled = true;
}
for(let el of document.getElementsByClassName('cost')) {
el.classList.add('d-none')
}
var scoretext = document.getElementById('bet-' + cid);
var score = Number(scoretext.textContent);
scoretext.textContent = score + 1;
post('/bet/' + cid);
document.getElementById("user-coins-amount").innerText = parseInt(document.getElementById("user-coins-amount").innerText) - 200;
}
function vote(type, id, dir, vid) { function vote(type, id, dir, vid) {
const upvotes = document.getElementsByClassName(type + '-' + id + '-up'); const upvotes = document.getElementsByClassName(type + '-' + id + '-up');
const downvotes = document.getElementsByClassName(type + '-' + id + '-down'); const downvotes = document.getElementsByClassName(type + '-' + id + '-down');

View file

@ -12,11 +12,6 @@ function collapse_comment(id, element) {
if (flags) flags.classList.add('d-none') if (flags) flags.classList.add('d-none')
}; };
function poll_vote_no_v() {
document.getElementById('toast-post-error-text').innerText = "Only logged-in users can vote!";
bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-error')).show();
}
function expandMarkdown(t,id) { function expandMarkdown(t,id) {
let ta = document.getElementById('markdown-'+id); let ta = document.getElementById('markdown-'+id);
ta.classList.toggle('d-none'); ta.classList.toggle('d-none');

View file

@ -90,38 +90,6 @@ class Comment(Base):
flags.remove(flag) flags.remove(flag)
return flags return flags
@lazy
def poll_voted(self, v):
if v:
vote = g.db.query(CommentVote.vote_type).filter_by(user_id=v.id, comment_id=self.id).one_or_none()
if vote: return vote[0]
return None
@property
@lazy
def options(self):
li = [x for x in self.child_comments if x.author_id == AUTOPOLLER_ID]
return sorted(li, key=lambda x: x.id)
@property
@lazy
def choices(self):
li = [x for x in self.child_comments if x.author_id == AUTOCHOICE_ID]
return sorted(li, key=lambda x: x.id)
def total_poll_voted(self, v):
if v:
for option in self.options:
if option.poll_voted(v): return True
return False
def total_choice_voted(self, v):
if v:
return g.db.query(CommentVote).filter(CommentVote.user_id == v.id, CommentVote.comment_id.in_([x.id for x in self.choices])).all()
return False
@property @property
@lazy @lazy
def controversial(self): def controversial(self):
@ -240,8 +208,7 @@ class Comment(Base):
return sorted((x for x in self.child_comments return sorted((x for x in self.child_comments
if x.author if x.author
and not x.author.shadowbanned and not x.author.shadowbanned
and (x.filter_state not in ('filtered', 'removed') or x.author_id == author_id) and (x.filter_state not in ('filtered', 'removed') or x.author_id == author_id)),
and x.author_id not in (AUTOPOLLER_ID, AUTOBETTER_ID, AUTOCHOICE_ID)),
key=lambda x: x.realupvotes, reverse=True) key=lambda x: x.realupvotes, reverse=True)
@property @property
@ -249,9 +216,7 @@ class Comment(Base):
if self.replies2 != None: return self.replies2 if self.replies2 != None: return self.replies2
if not self.parent_submission: if not self.parent_submission:
return sorted(self.child_comments, key=lambda x: x.created_utc) return sorted(self.child_comments, key=lambda x: x.created_utc)
return sorted((x for x in self.child_comments return sorted(self.child_comments, key=lambda x: x.realupvotes, reverse=True)
if x.author_id not in (AUTOPOLLER_ID, AUTOBETTER_ID, AUTOCHOICE_ID)),
key=lambda x: x.realupvotes, reverse=True)
@property @property
def replies2(self): def replies2(self):
@ -403,27 +368,6 @@ class Comment(Base):
g.db.add(self) g.db.add(self)
g.db.commit() g.db.commit()
for c in self.options:
body += f'<div class="custom-control"><input type="checkbox" class="custom-control-input" id="{c.id}" name="option"'
if c.poll_voted(v): body += " checked"
if v: body += f''' onchange="poll_vote('{c.id}', '{self.id}')"'''
else: body += f''' onchange="poll_vote_no_v('{c.id}', '{self.id}')"'''
body += f'''><label class="custom-control-label" for="{c.id}">{c.body_html}<span class="presult-{self.id}'''
if not self.total_poll_voted(v): body += ' d-none'
body += f'"> - <a href="/votes?link=t3_{c.id}"><span id="poll-{c.id}">{c.upvotes}</span> votes</a></span></label></div>'
curr = self.total_choice_voted(v)
if curr: curr = " value=" + str(curr[0].comment_id)
else: curr = ''
body += f'<input class="d-none" id="current-{self.id}"{curr}>'
for c in self.choices:
body += f'''<div class="custom-control"><input name="choice-{self.id}" autocomplete="off" class="custom-control-input" type="radio" id="{c.id}" onchange="choice_vote('{c.id}','{self.id}')"'''
if c.poll_voted(v): body += " checked "
body += f'''><label class="custom-control-label" for="{c.id}">{c.body_html}<span class="presult-{self.id}'''
if not self.total_choice_voted(v): body += ' d-none'
body += f'"> - <a href="/votes?link=t3_{c.id}"><span id="choice-{c.id}">{c.upvotes}</span> votes</a></span></label></div>'
if self.author.sig_html and (self.author_id == MOOSE_ID or (not self.ghost and not (v and v.sigs_disabled))): if self.author.sig_html and (self.author_id == MOOSE_ID or (not self.ghost and not (v and v.sigs_disabled))):
body += f"<hr>{self.author.sig_html}" body += f"<hr>{self.author.sig_html}"

View file

@ -224,11 +224,6 @@ ACTIONTYPES = {
"icon": 'fa-crown', "icon": 'fa-crown',
"color": 'bg-success' "color": 'bg-success'
}, },
'distribute': {
"str": 'distributed bet winnings to voters on {self.target_link}',
"icon": 'fa-dollar-sign',
"color": 'bg-success'
},
'dump_cache': { 'dump_cache': {
"str": 'dumped cache', "str": 'dumped cache',
"icon": 'fa-trash-alt', "icon": 'fa-trash-alt',

View file

@ -98,41 +98,6 @@ class Submission(Base):
if flag.user.shadowbanned: if flag.user.shadowbanned:
flags.remove(flag) flags.remove(flag)
return flags return flags
@property
@lazy
def options(self):
return g.db.query(Comment).filter_by(parent_submission = self.id, author_id = AUTOPOLLER_ID, level=1).order_by(Comment.id).all()
@property
@lazy
def choices(self):
return g.db.query(Comment).filter_by(parent_submission = self.id, author_id = AUTOCHOICE_ID, level=1).order_by(Comment.id).all()
@property
@lazy
def bet_options(self):
return g.db.query(Comment).filter_by(parent_submission = self.id, author_id = AUTOBETTER_ID, level=1).all()
def total_poll_voted(self, v):
if v:
for option in self.options:
if option.poll_voted(v): return True
return False
def total_choice_voted(self, v):
if v and self.choices:
return g.db.query(CommentVote).filter(CommentVote.user_id == v.id, CommentVote.comment_id.in_([x.id for x in self.choices])).all()
return False
def total_bet_voted(self, v):
if "closed" in self.body.lower(): return True
if v:
for option in self.bet_options:
if option.poll_voted(v): return True
return False
@property @property
@lazy @lazy
@ -405,41 +370,6 @@ class Submission(Base):
g.db.add(self) g.db.add(self)
g.db.commit() g.db.commit()
for c in self.options:
body += f'<div class="custom-control"><input type="checkbox" class="custom-control-input" id="{c.id}" name="option"'
if c.poll_voted(v): body += " checked"
if v: body += f''' onchange="poll_vote('{c.id}', '{self.id}')"'''
else: body += f''' onchange="poll_vote_no_v('{c.id}', '{self.id}')"'''
body += f'''><label class="custom-control-label" for="{c.id}">{c.body_html}<span class="presult-{self.id}'''
if not self.total_poll_voted(v): body += ' d-none'
body += f'"> - <a href="/votes?link=t3_{c.id}"><span id="poll-{c.id}">{c.upvotes}</span> votes</a></span></label></div>'
if self.choices:
curr = self.total_choice_voted(v)
if curr: curr = " value=" + str(curr[0].comment_id)
else: curr = ''
body += f'<input class="d-none" id="current-{self.id}"{curr}>'
for c in self.choices:
body += f'''<div class="custom-control"><input name="choice-{self.id}" autocomplete="off" class="custom-control-input" type="radio" id="{c.id}" onchange="choice_vote('{c.id}','{self.id}')"'''
if c.poll_voted(v): body += " checked "
body += f'''><label class="custom-control-label" for="{c.id}">{c.body_html}<span class="presult-{self.id}'''
if not self.total_choice_voted(v): body += ' d-none'
body += f'"> - <a href="/votes?link=t3_{c.id}"><span id="choice-{c.id}">{c.upvotes}</span> votes</a></span></label></div>'
for c in self.bet_options:
body += f'''<div class="custom-control mt-3"><input autocomplete="off" class="custom-control-input bet" type="radio" id="{c.id}" onchange="bet_vote('{c.id}')"'''
if c.poll_voted(v): body += " checked "
if not (v and v.coins > 200) or self.total_bet_voted(v): body += " disabled "
body += f'''><label class="custom-control-label" for="{c.id}">{c.body_html} - <a href="/votes?link=t3_{c.id}"><span id="bet-{c.id}">{c.upvotes}</span> bets</a>'''
if not self.total_bet_voted(v):
body += '''<span class="cost"> (cost of entry: 200 coins)</span>'''
body += "</label>"
if v and v.admin_level > 2:
body += f'''<button class="btn btn-primary px-2 mx-2" style="font-size:10px;padding:2px;margin-top:-5px" onclick="post_toast(this,'/distribute/{c.id}')">Declare winner</button>'''
body += "</div>"
if self.author.sig_html and (self.author_id == MOOSE_ID or (not self.ghost and not (v and v.sigs_disabled))): if self.author.sig_html and (self.author_id == MOOSE_ID or (not self.ghost and not (v and v.sigs_disabled))):
body += f"<hr>{self.author.sig_html}" body += f"<hr>{self.author.sig_html}"

View file

@ -493,10 +493,6 @@ valid_sub_regex = re.compile("^[a-zA-Z0-9_\-]{3,20}$", flags=re.A)
query_regex = re.compile("(\w+):(\S+)", flags=re.A) query_regex = re.compile("(\w+):(\S+)", flags=re.A)
poll_regex = re.compile("\s*\$\$([^\$\n]+)\$\$\s*", flags=re.A)
bet_regex = re.compile("\s*\$\$\$([^\$\n]+)\$\$\$\s*", flags=re.A)
choice_regex = re.compile("\s*&&([^\$\n]+)&&\s*", flags=re.A)
title_regex = re.compile("[^\w ]", flags=re.A) title_regex = re.compile("[^\w ]", flags=re.A)
based_regex = re.compile("based and (.{1,20}?)(-| )pilled", flags=re.I|re.A) based_regex = re.compile("based and (.{1,20}?)(-| )pilled", flags=re.I|re.A)

View file

@ -166,54 +166,6 @@ def remove_admin(v, username):
g.db.commit() g.db.commit()
return {"message": "Admin removed!"} return {"message": "Admin removed!"}
@app.post("/distribute/<comment>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(3)
def distribute(v, comment):
autobetter = g.db.query(User).filter_by(id=AUTOBETTER_ID).one_or_none()
if autobetter.coins == 0: return {"error": "@AutoBetter has 0 coins"}
try: comment = int(comment)
except: abort(400)
post = g.db.query(Comment.parent_submission).filter_by(id=comment).one_or_none()[0]
post = g.db.query(Submission).filter_by(id=post).one_or_none()
pool = 0
for option in post.bet_options: pool += option.upvotes
pool *= 200
autobetter.coins -= pool
if autobetter.coins < 0: autobetter.coins = 0
g.db.add(autobetter)
votes = g.db.query(CommentVote).filter_by(comment_id=comment)
coinsperperson = int(pool / votes.count())
cid = notif_comment(f"You won {coinsperperson} coins betting on [{post.title}]({post.shortlink}) :marseyparty:")
for vote in votes:
u = vote.user
u.coins += coinsperperson
add_notif(cid, u.id)
cid = notif_comment(f"You lost the 200 coins you bet on [{post.title}]({post.shortlink}) :marseylaugh:")
cids = [x.id for x in post.bet_options]
cids.remove(comment)
votes = g.db.query(CommentVote).filter(CommentVote.comment_id.in_(cids)).all()
for vote in votes: add_notif(cid, vote.user.id)
post.body += '\n\nclosed'
g.db.add(post)
ma = ModAction(
kind="distribute",
user_id=v.id,
target_comment_id=comment
)
g.db.add(ma)
g.db.commit()
return {"message": f"Each winner has received {coinsperperson} coins!"}
@app.post("/@<username>/delete_note/<id>") @app.post("/@<username>/delete_note/<id>")
@admin_level_required(3) @admin_level_required(3)
def delete_note(v,username,id): def delete_note(v,username,id):

View file

@ -123,8 +123,7 @@ def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None):
comments = comments.join(User, User.id == Comment.author_id).filter(User.shadowbanned == None) comments = comments.join(User, User.id == Comment.author_id).filter(User.shadowbanned == None)
comments=comments.filter( comments=comments.filter(
Comment.parent_submission == post.id, Comment.parent_submission == post.id
Comment.author_id.notin_((AUTOPOLLER_ID, AUTOBETTER_ID, AUTOCHOICE_ID))
).join( ).join(
votes, votes,
votes.c.comment_id == Comment.id, votes.c.comment_id == Comment.id,
@ -340,7 +339,7 @@ def api_comment(v):
for x in g.db.query(Subscription.user_id).filter_by(submission_id=c.parent_submission).all(): notify_users.add(x[0]) for x in g.db.query(Subscription.user_id).filter_by(submission_id=c.parent_submission).all(): notify_users.add(x[0])
if parent.author.id not in (v.id, BASEDBOT_ID, AUTOJANNY_ID, SNAPPY_ID, LONGPOSTBOT_ID, ZOZBOT_ID, AUTOPOLLER_ID, AUTOCHOICE_ID): if parent.author.id not in (v.id, BASEDBOT_ID, AUTOJANNY_ID, SNAPPY_ID, LONGPOSTBOT_ID, ZOZBOT_ID):
notify_users.add(parent.author.id) notify_users.add(parent.author.id)
for x in notify_users: for x in notify_users:

View file

@ -172,7 +172,7 @@ def post_id(pid, anything=None, v=None, sub=None):
filter_clause = filter_clause | (Comment.author_id == v.id) filter_clause = filter_clause | (Comment.author_id == v.id)
comments = comments.filter(filter_clause) comments = comments.filter(filter_clause)
comments=comments.filter(Comment.parent_submission == post.id, Comment.author_id.notin_((AUTOPOLLER_ID, AUTOBETTER_ID, AUTOCHOICE_ID))).join( comments=comments.filter(Comment.parent_submission == post.id).join(
votes, votes,
votes.c.comment_id == Comment.id, votes.c.comment_id == Comment.id,
isouter=True isouter=True
@ -215,7 +215,7 @@ def post_id(pid, anything=None, v=None, sub=None):
else: else:
pinned = g.db.query(Comment).filter(Comment.parent_submission == post.id, Comment.is_pinned != None).all() pinned = g.db.query(Comment).filter(Comment.parent_submission == post.id, Comment.is_pinned != None).all()
comments = g.db.query(Comment).join(User, User.id == Comment.author_id).filter(User.shadowbanned == None, Comment.parent_submission == post.id, Comment.author_id.notin_((AUTOPOLLER_ID, AUTOBETTER_ID, AUTOCHOICE_ID)), Comment.level == 1, Comment.is_pinned == None) comments = g.db.query(Comment).join(User, User.id == Comment.author_id).filter(User.shadowbanned == None, Comment.parent_submission == post.id, Comment.level == 1, Comment.is_pinned == None)
if sort == "new": if sort == "new":
comments = comments.order_by(Comment.created_utc.desc()) comments = comments.order_by(Comment.created_utc.desc())
@ -298,7 +298,7 @@ def viewmore(v, pid, sort, offset):
votes.c.vote_type, votes.c.vote_type,
blocking.c.target_id, blocking.c.target_id,
blocked.c.target_id, blocked.c.target_id,
).filter(Comment.parent_submission == pid, Comment.author_id.notin_((AUTOPOLLER_ID, AUTOBETTER_ID, AUTOCHOICE_ID)), Comment.is_pinned == None, Comment.id.notin_(ids)) ).filter(Comment.parent_submission == pid, Comment.is_pinned == None, Comment.id.notin_(ids))
if not (v and v.shadowbanned) and not (v and v.admin_level > 2): if not (v and v.shadowbanned) and not (v and v.admin_level > 2):
comments = comments.join(User, User.id == Comment.author_id).filter(User.shadowbanned == None) comments = comments.join(User, User.id == Comment.author_id).filter(User.shadowbanned == None)
@ -348,7 +348,7 @@ def viewmore(v, pid, sort, offset):
second = [c[0] for c in comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()] second = [c[0] for c in comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()]
comments = first + second comments = first + second
else: else:
comments = g.db.query(Comment).join(User, User.id == Comment.author_id).filter(User.shadowbanned == None, Comment.parent_submission == pid, Comment.author_id.notin_((AUTOPOLLER_ID, AUTOBETTER_ID, AUTOCHOICE_ID)), Comment.level == 1, Comment.is_pinned == None, Comment.id.notin_(ids)) comments = g.db.query(Comment).join(User, User.id == Comment.author_id).filter(User.shadowbanned == None, Comment.parent_submission == pid, Comment.level == 1, Comment.is_pinned == None, Comment.id.notin_(ids))
if sort == "new": if sort == "new":
comments = comments.order_by(Comment.created_utc.desc()) comments = comments.order_by(Comment.created_utc.desc())
@ -894,22 +894,6 @@ def submit_post(v, sub=None):
g.db.add(ma) g.db.add(ma)
return redirect("/notifications") return redirect("/notifications")
if v and v.admin_level > 2:
bet_options = []
for i in bet_regex.finditer(body):
bet_options.append(i.group(1))
body = body.replace(i.group(0), "")
options = []
for i in poll_regex.finditer(body):
options.append(i.group(1))
body = body.replace(i.group(0), "")
choices = []
for i in choice_regex.finditer(body):
choices.append(i.group(1))
body = body.replace(i.group(0), "")
if request.files.get("file2") and request.headers.get("cf-ipcountry") != "T1": if request.files.get("file2") and request.headers.get("cf-ipcountry") != "T1":
files = request.files.getlist('file2')[:4] files = request.files.getlist('file2')[:4]
for file in files: for file in files:

View file

@ -69,9 +69,6 @@ def api_vote_post(post_id, new, v):
except: abort(404) except: abort(404)
post = get_post(post_id) post = get_post(post_id)
# verify that the post is allowed to be voted on
if post.author_id in {AUTOPOLLER_ID,AUTOBETTER_ID,AUTOCHOICE_ID}: return {"error": "forbidden."}, 403
# get the old vote, if we have one # get the old vote, if we have one
vote = g.db.query(Vote).filter_by(user_id=v.id, submission_id=post.id).one_or_none() vote = g.db.query(Vote).filter_by(user_id=v.id, submission_id=post.id).one_or_none()
@ -142,9 +139,6 @@ def api_vote_comment(comment_id, new, v):
except: abort(404) except: abort(404)
comment = get_comment(comment_id) comment = get_comment(comment_id)
# verify that the comment is allowed to be voted on
if comment.author.id in {AUTOPOLLER_ID,AUTOBETTER_ID,AUTOCHOICE_ID}: return {"error": "forbidden."}, 403
# get the old vote, if we have one # get the old vote, if we have one
vote = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id).one_or_none() vote = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
@ -196,97 +190,3 @@ def api_vote_comment(comment_id, new, v):
g.db.add(comment) g.db.add(comment)
g.db.commit() g.db.commit()
return "", 204 return "", 204
@app.post("/vote/poll/<comment_id>")
@is_not_permabanned
def api_vote_poll(comment_id, v):
vote = request.values.get("vote")
if vote == "true": new = 1
elif vote == "false": new = 0
else: abort(400)
comment_id = int(comment_id)
comment = get_comment(comment_id)
existing = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
if existing and existing.vote_type == new: return "", 204
if existing:
if new == 1:
existing.vote_type = new
g.db.add(existing)
else: g.db.delete(existing)
elif new == 1:
vote = CommentVote(user_id=v.id, vote_type=new, comment_id=comment.id)
g.db.add(vote)
g.db.flush()
comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=1).count()
g.db.add(comment)
g.db.commit()
return "", 204
@app.post("/bet/<comment_id>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@is_not_permabanned
def bet(comment_id, v):
if v.coins < 200: return {"error": "You don't have 200 coins!"}
vote = request.values.get("vote")
comment_id = int(comment_id)
comment = get_comment(comment_id)
existing = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
if existing: return "", 204
vote = CommentVote(user_id=v.id, vote_type=1, comment_id=comment.id)
g.db.add(vote)
comment.upvotes += 1
g.db.add(comment)
v.coins -= 200
g.db.add(v)
autobetter = g.db.query(User).filter_by(id=AUTOBETTER_ID).one_or_none()
autobetter.coins += 200
g.db.add(autobetter)
g.db.commit()
return "", 204
@app.post("/vote/choice/<comment_id>")
@is_not_permabanned
def api_vote_choice(comment_id, v):
comment_id = int(comment_id)
comment = get_comment(comment_id)
existing = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
if existing and existing.vote_type == 1: return "", 204
if existing:
existing.vote_type = 1
g.db.add(existing)
else:
vote = CommentVote(user_id=v.id, vote_type=1, comment_id=comment.id)
g.db.add(vote)
if comment.parent_comment: parent = comment.parent_comment
else: parent = comment.post
for vote in parent.total_choice_voted(v):
vote.comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=vote.comment.id, vote_type=1).count() - 1
g.db.add(vote.comment)
g.db.delete(vote)
g.db.flush()
comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=1).count()
g.db.add(comment)
g.db.commit()
return "", 204