mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-04-27 03:00:12 -04:00
vote: Refactor vote casting to prepare for pgvote removal.
The intent of this patch is to refactor the existing vote-casting code into a state that's ready for pgvotes to be dropped and replaced with reads from VoteDetailsByThing. Ideally, no behaviour is changed by this patch (hence "refactor") and then the later data model change will be lower risk as a result of less code shuffling. Once this patch is applied, the `Vote` class/rel is considered an implementation detail of the vote module and hidden from public view.
This commit is contained in:
@@ -20,7 +20,8 @@
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
from r2.models import Account, Link, Comment, Vote, Report
|
||||
from r2.models import Account, Link, Comment, Report
|
||||
from r2.models.vote import cast_vote, get_votes
|
||||
from r2.models import Message, Inbox, Subreddit, ModContribSR, ModeratorInbox, MultiReddit
|
||||
from r2.lib.db.thing import Thing, Merge
|
||||
from r2.lib.db.operators import asc, desc, timeago
|
||||
@@ -544,18 +545,16 @@ def rel_query(rel, thing_id, name, filters = []):
|
||||
|
||||
return q
|
||||
|
||||
vote_rel = Vote.rel(Account, Link)
|
||||
|
||||
cached_userrel_query = cached_query(UserQueryCache, filter_thing2)
|
||||
cached_srrel_query = cached_query(SubredditQueryCache, filter_thing2)
|
||||
|
||||
@cached_userrel_query
|
||||
def get_liked(user):
|
||||
return rel_query(vote_rel, user, '1')
|
||||
return FakeQuery(sort=[desc("_date")])
|
||||
|
||||
@cached_userrel_query
|
||||
def get_disliked(user):
|
||||
return rel_query(vote_rel, user, '-1')
|
||||
return FakeQuery(sort=[desc("_date")])
|
||||
|
||||
@cached_query(UserQueryCache)
|
||||
def get_hidden_links(user_id):
|
||||
@@ -1697,7 +1696,7 @@ def get_likes(user, items):
|
||||
else False if v == '-1'
|
||||
else None)
|
||||
|
||||
likes = Vote.likes(user, [i for i in items if (user, i) not in res])
|
||||
likes = get_votes(user, [i for i in items if (user, i) not in res])
|
||||
|
||||
res.update(likes)
|
||||
|
||||
@@ -1711,7 +1710,7 @@ def handle_vote(user, thing, dir, ip, vote_info,
|
||||
from r2.lib.db import tdb_sql
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
try:
|
||||
v = Vote.vote(user, thing, dir, ip, vote_info=vote_info,
|
||||
v = cast_vote(user, thing, dir, ip, vote_info=vote_info,
|
||||
cheater=cheater, timer=timer, date=date)
|
||||
except (tdb_sql.CreationError, IntegrityError):
|
||||
g.log.error("duplicate vote for: %s" % str((user, thing, dir)))
|
||||
|
||||
@@ -224,37 +224,6 @@ def pushup_permacache(verbosity=1000):
|
||||
populate(keys)
|
||||
|
||||
|
||||
def port_cassavotes():
|
||||
from r2.models import Vote, Account, Link, Comment
|
||||
from r2.models.vote import CassandraVote, CassandraLinkVote, CassandraCommentVote
|
||||
from r2.lib.db.tdb_cassandra import CL
|
||||
from r2.lib.utils import fetch_things2, to36, progress
|
||||
|
||||
ts = [(Vote.rel(Account, Link), CassandraLinkVote),
|
||||
(Vote.rel(Account, Comment), CassandraCommentVote)]
|
||||
|
||||
dataattrs = set(['valid_user', 'valid_thing', 'ip', 'organic'])
|
||||
|
||||
for prel, crel in ts:
|
||||
vq = prel._query(sort=desc('_date'),
|
||||
data=True,
|
||||
eager_load=False)
|
||||
vq = fetch_things2(vq)
|
||||
vq = progress(vq, persec=True)
|
||||
for v in vq:
|
||||
t1 = to36(v._thing1_id)
|
||||
t2 = to36(v._thing2_id)
|
||||
cv = crel(thing1_id = t1,
|
||||
thing2_id = t2,
|
||||
date=v._date,
|
||||
name=v._name)
|
||||
for dkey, dval in v._t.iteritems():
|
||||
if dkey in dataattrs:
|
||||
setattr(cv, dkey, dval)
|
||||
|
||||
cv._commit(write_consistency_level=CL.ONE)
|
||||
|
||||
|
||||
def port_cassaurls(after_id=None, estimate=15231317):
|
||||
from r2.models import Link, LinksByUrl
|
||||
from r2.lib.db import tdb_cassandra
|
||||
|
||||
@@ -26,7 +26,6 @@ from r2.lib.db.thing import Thing, Relation, MultiRelation, thing_prefix
|
||||
from r2.lib.utils import tup
|
||||
from r2.lib.memoize import memoize
|
||||
from r2.models import Link, Comment, Message, Subreddit, Account
|
||||
from r2.models.vote import score_changes
|
||||
from datetime import datetime
|
||||
|
||||
from pylons import g, c
|
||||
|
||||
@@ -27,7 +27,7 @@ from r2.lib.db.thing import MultiRelation, Relation
|
||||
from r2.lib.db import tdb_cassandra
|
||||
from r2.lib.db.tdb_cassandra import TdbException, ASCII_TYPE, UTF8_TYPE
|
||||
from r2.lib.db.sorts import epoch_seconds
|
||||
from r2.lib.utils import SimpleSillyStub, Storage
|
||||
from r2.lib.utils import Storage
|
||||
|
||||
from account import Account
|
||||
from link import Link, Comment
|
||||
@@ -36,26 +36,14 @@ import pytz
|
||||
|
||||
from pycassa.types import CompositeType, AsciiType
|
||||
from pylons import g
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
|
||||
__all__ = ['Vote', 'score_changes']
|
||||
__all__ = ['cast_vote', 'get_votes']
|
||||
|
||||
|
||||
VOTE_TIMEZONE = pytz.timezone("America/Los_Angeles")
|
||||
|
||||
|
||||
def score_changes(amount, old_amount):
|
||||
uc = dc = 0
|
||||
a, oa = amount, old_amount
|
||||
if oa == 0 and a > 0: uc = a
|
||||
elif oa == 0 and a < 0: dc = -a
|
||||
elif oa > 0 and a == 0: uc = -oa
|
||||
elif oa < 0 and a == 0: dc = oa
|
||||
elif oa > 0 and a < 0: uc = -oa; dc = -a
|
||||
elif oa < 0 and a > 0: dc = oa; uc = a
|
||||
return uc, dc
|
||||
|
||||
|
||||
class VotesByAccount(tdb_cassandra.DenormalizedRelation):
|
||||
_use_db = False
|
||||
_thing1_cls = Account
|
||||
@@ -234,124 +222,145 @@ class VoterIPByThing(tdb_cassandra.View):
|
||||
class Vote(MultiRelation('vote',
|
||||
Relation(Account, Link),
|
||||
Relation(Account, Comment))):
|
||||
@classmethod
|
||||
def vote(cls, sub, obj, dir, ip, vote_info = None, cheater = False,
|
||||
timer=None, date=None):
|
||||
from admintools import valid_user, valid_thing, update_score
|
||||
from r2.lib.count import incr_sr_count
|
||||
from r2.lib.db import queries
|
||||
pass
|
||||
|
||||
if timer is None:
|
||||
timer = SimpleSillyStub()
|
||||
|
||||
sr = obj.subreddit_slow
|
||||
kind = obj.__class__.__name__.lower()
|
||||
karma = sub.karma(kind, sr)
|
||||
def cast_vote(sub, obj, dir, ip, vote_info, cheater, timer, date):
|
||||
from r2.models.admintools import valid_user, valid_thing, update_score
|
||||
from r2.lib.count import incr_sr_count
|
||||
from r2.lib.db import queries
|
||||
|
||||
is_self_link = (kind == 'link'
|
||||
and getattr(obj,'is_self',False))
|
||||
names_by_dir = {True: "1", None: "0", False: "-1"}
|
||||
|
||||
#check for old vote
|
||||
rel = cls.rel(sub, obj)
|
||||
oldvote = rel._fast_query(sub, obj, ['-1', '0', '1']).values()
|
||||
oldvote = filter(None, oldvote)
|
||||
# `vote` mimics the old pg vote rel interface so downstream code doesn't
|
||||
# need to change. (but it totally needn't stay that way forever!)
|
||||
vote = Storage(
|
||||
_thing1=sub,
|
||||
_thing2=obj,
|
||||
_name=names_by_dir[dir],
|
||||
_date=date,
|
||||
valid_thing=True,
|
||||
valid_user=True,
|
||||
ip=ip,
|
||||
)
|
||||
|
||||
timer.intermediate("pg_read_vote")
|
||||
# these track how much ups/downs should change on `obj`
|
||||
ups_delta = 1 if int(vote._name) > 0 else 0
|
||||
downs_delta = 1 if int(vote._name) < 0 else 0
|
||||
|
||||
amount = 1 if dir is True else 0 if dir is None else -1
|
||||
# see if the user has voted on this thing before
|
||||
pgrel = Vote.rel(sub, obj)
|
||||
pgoldvote = pgrel._fast_query(sub, obj, ["-1", "0", "1"]).values()
|
||||
try:
|
||||
pgoldvote = filter(None, pgoldvote)[0]
|
||||
except IndexError:
|
||||
pgoldvote = None
|
||||
timer.intermediate("pg_read_vote")
|
||||
|
||||
is_new = False
|
||||
#old vote
|
||||
if len(oldvote):
|
||||
v = oldvote[0]
|
||||
oldamount = int(v._name)
|
||||
if amount == oldamount:
|
||||
return v
|
||||
if pgoldvote:
|
||||
# old_vote is mimicking `{Link,Comment}VoteDetailsByThing` here because
|
||||
# that will eventually be exactly what it is
|
||||
old_vote = {
|
||||
"direction": pgoldvote._name,
|
||||
"valid_thing": pgoldvote.valid_thing,
|
||||
"valid_user": pgoldvote.valid_user,
|
||||
"ip": getattr(pgoldvote, "ip", None),
|
||||
}
|
||||
vote.valid_thing = old_vote["valid_thing"]
|
||||
vote.valid_user = old_vote["valid_user"]
|
||||
|
||||
v._name = str(amount)
|
||||
if vote._name == old_vote["direction"]:
|
||||
# the old vote and new vote are the same. bail out.
|
||||
return vote
|
||||
|
||||
#these still need to be recalculated
|
||||
old_valid_thing = getattr(v, 'valid_thing', False)
|
||||
v.valid_thing = (old_valid_thing and
|
||||
valid_thing(
|
||||
v, karma, cheater=cheater, vote_info=vote_info)
|
||||
)
|
||||
v.valid_user = (getattr(v, 'valid_user', False)
|
||||
and v.valid_thing
|
||||
and valid_user(v, sr, karma))
|
||||
#new vote
|
||||
else:
|
||||
is_new = True
|
||||
oldamount = 0
|
||||
v = rel(sub, obj, str(amount), date=date)
|
||||
v.ip = ip
|
||||
v.valid_thing = valid_thing(
|
||||
v, karma, cheater=cheater, vote_info=vote_info)
|
||||
old_valid_thing = v.valid_thing
|
||||
v.valid_user = (v.valid_thing and valid_user(v, sr, karma)
|
||||
and not is_self_link)
|
||||
# remove the old vote from the score
|
||||
old_direction = int(old_vote["direction"])
|
||||
ups_delta -= 1 if old_direction > 0 else 0
|
||||
downs_delta -= 1 if old_direction < 0 else 0
|
||||
else:
|
||||
old_vote = None
|
||||
|
||||
v._commit()
|
||||
# calculate valid_thing and valid_user
|
||||
sr = obj.subreddit_slow
|
||||
kind = obj.__class__.__name__.lower()
|
||||
karma = sub.karma(kind, sr)
|
||||
|
||||
timer.intermediate("pg_write_vote")
|
||||
if vote.valid_thing:
|
||||
vote.valid_thing = valid_thing(vote, karma, cheater, vote_info)
|
||||
|
||||
g.stats.simple_event("vote.valid_thing." + str(v.valid_thing).lower())
|
||||
g.stats.simple_event("vote.valid_user." + str(v.valid_user).lower())
|
||||
if vote.valid_user:
|
||||
vote.valid_user = vote.valid_thing and valid_user(vote, sr, karma)
|
||||
|
||||
up_change, down_change = score_changes(amount, oldamount)
|
||||
if kind == "link" and getattr(obj, "is_self", False):
|
||||
# self-posts do not generate karma
|
||||
vote.valid_user = False
|
||||
|
||||
if not (is_new and obj.author_id == sub._id and amount == 1):
|
||||
# we don't do this if it's the author's initial automatic
|
||||
# vote, because we checked it in with _ups == 1
|
||||
update_score(obj, up_change, down_change,
|
||||
v, old_valid_thing)
|
||||
timer.intermediate("pg_update_score")
|
||||
g.stats.simple_event("vote.valid_thing." + str(vote.valid_thing).lower())
|
||||
g.stats.simple_event("vote.valid_user." + str(vote.valid_user).lower())
|
||||
|
||||
if v.valid_user:
|
||||
author = Account._byID(obj.author_id, data=True)
|
||||
author.incr_karma(kind, sr, up_change - down_change)
|
||||
timer.intermediate("pg_incr_karma")
|
||||
# write out the new/modified vote to postgres
|
||||
if pgoldvote:
|
||||
pgvote = pgoldvote
|
||||
pgvote._name = vote._name
|
||||
else:
|
||||
pgvote = pgrel(sub, obj, vote._name, date=vote._date, ip=ip)
|
||||
pgvote.valid_thing = vote.valid_thing
|
||||
pgvote.valid_user = vote.valid_user
|
||||
pgvote._commit()
|
||||
timer.intermediate("pg_write_vote")
|
||||
|
||||
#update the sr's valid vote count
|
||||
if is_new and v.valid_thing and kind == 'link':
|
||||
if sub._id != obj.author_id:
|
||||
incr_sr_count(sr)
|
||||
# update various score/karma/vote counts
|
||||
if not (not old_vote and obj.author_id == sub._id and vote._name == "1"):
|
||||
# newly created objects start out with _ups = 1, so we skip updating
|
||||
# their score here if this is the author's own initial vote on it.
|
||||
old_valid_thing = old_vote["valid_thing"] if old_vote else True
|
||||
update_score(obj, ups_delta, downs_delta, vote, old_valid_thing)
|
||||
timer.intermediate("pg_update_score")
|
||||
|
||||
if vote.valid_user:
|
||||
author = Account._byID(obj.author_id, data=True)
|
||||
author.incr_karma(kind, sr, ups_delta - downs_delta)
|
||||
timer.intermediate("pg_incr_karma")
|
||||
|
||||
if not old_vote and vote.valid_thing and kind == "link":
|
||||
if sub._id != obj.author_id:
|
||||
incr_sr_count(sr)
|
||||
timer.intermediate("incr_sr_counts")
|
||||
|
||||
# now write it out to Cassandra. We'll write it out to both
|
||||
# this way for a while
|
||||
VotesByAccount.copy_from(v, vote_info)
|
||||
timer.intermediate("cassavotes")
|
||||
# write the vote to cassandra
|
||||
VotesByAccount.copy_from(vote, vote_info)
|
||||
timer.intermediate("cassavotes")
|
||||
|
||||
queries.changed(v._thing2, True)
|
||||
timer.intermediate("changed")
|
||||
# update the search index
|
||||
queries.changed(vote._thing2, boost_only=True)
|
||||
timer.intermediate("changed")
|
||||
|
||||
return v
|
||||
return vote
|
||||
|
||||
@classmethod
|
||||
def likes(cls, sub, objs):
|
||||
if not sub or not objs:
|
||||
return {}
|
||||
|
||||
from r2.models import Account
|
||||
assert isinstance(sub, Account)
|
||||
def get_votes(sub, objs):
|
||||
if not sub or not objs:
|
||||
return {}
|
||||
|
||||
rels = {}
|
||||
for obj in objs:
|
||||
try:
|
||||
types = VotesByAccount.rel(sub.__class__, obj.__class__)
|
||||
except TdbException:
|
||||
# for types for which we don't have a vote rel, we'll
|
||||
# skip them
|
||||
continue
|
||||
from r2.models import Account
|
||||
assert isinstance(sub, Account)
|
||||
|
||||
rels.setdefault(types, []).append(obj)
|
||||
rels = {}
|
||||
for obj in objs:
|
||||
try:
|
||||
types = VotesByAccount.rel(sub.__class__, obj.__class__)
|
||||
except TdbException:
|
||||
# for types for which we don't have a vote rel, we'll
|
||||
# skip them
|
||||
continue
|
||||
|
||||
dirs_by_name = {"1": True, "0": None, "-1": False}
|
||||
rels.setdefault(types, []).append(obj)
|
||||
|
||||
ret = {}
|
||||
for relcls, items in rels.iteritems():
|
||||
votes = relcls.fast_query(sub, items)
|
||||
for cross, name in votes.iteritems():
|
||||
ret[cross] = dirs_by_name[name]
|
||||
return ret
|
||||
dirs_by_name = {"1": True, "0": None, "-1": False}
|
||||
|
||||
ret = {}
|
||||
for relcls, items in rels.iteritems():
|
||||
votes = relcls.fast_query(sub, items)
|
||||
for cross, name in votes.iteritems():
|
||||
ret[cross] = dirs_by_name[name]
|
||||
return ret
|
||||
|
||||
Reference in New Issue
Block a user