class Inbox( MultiRelation('inbox', Relation(Account, Comment), Relation(Account, Message))): @classmethod def _add(cls, to, obj, *a, **kw): i = Inbox(to, obj, *a, **kw) i._commit() if not to._loaded: to._load() #if there is not msgtime, or it's false, set it if not hasattr(to, 'msgtime') or not to.msgtime: to.msgtime = obj._date to._commit() return i
class Inbox(MultiRelation('inbox', Relation(Account, Comment), Relation(Account, Message))): _defaults = dict(new = False) @classmethod def _add(cls, to, obj, *a, **kw): orangered = kw.pop("orangered", True) i = Inbox(to, obj, *a, **kw) i.new = True i._commit() if not to._loaded: to._load() #if there is not msgtime, or it's false, set it if orangered and (not hasattr(to, 'msgtime') or not to.msgtime): to.msgtime = obj._date to._commit() return i @classmethod def set_unread(cls, things, unread, to=None): things = tup(things) if len(set(type(x) for x in things)) != 1: raise TypeError('things must only be of a single type') thing_ids = [x._id for x in things] inbox_rel = cls.rel(Account, things[0].__class__) if to: inbox = inbox_rel._query(inbox_rel.c._thing2_id == thing_ids, inbox_rel.c._thing1_id == to._id, eager_load=True) else: inbox = inbox_rel._query(inbox_rel.c._thing2_id == thing_ids, eager_load=True) res = [] for i in inbox: if i: i.new = unread i._commit() res.append(i) return res
class Inbox( MultiRelation('inbox', Relation(Account, Comment), Relation(Account, Message))): _defaults = dict(new=False) @classmethod def _add(cls, to, obj, *a, **kw): orangered = kw.pop("orangered", True) i = Inbox(to, obj, *a, **kw) i.new = True i._commit() if not to._loaded: to._load() #if there is not msgtime, or it's false, set it if orangered and (not hasattr(to, 'msgtime') or not to.msgtime): to.msgtime = obj._date to._commit() return i @classmethod def set_unread(cls, thing, unread, to=None): inbox_rel = cls.rel(Account, thing.__class__) if to: inbox = inbox_rel._query(inbox_rel.c._thing2_id == thing._id, eager_load=True) else: inbox = inbox_rel._query(inbox_rel.c._thing2_id == thing._id, inbox_rel.c._thing1_id == to._id, eager_load=True) res = [] for i in inbox: if i: i.new = unread i._commit() res.append(i) return res
class Report( MultiRelation('report', Relation(Account, Link), Relation(Account, Comment), Relation(Account, Subreddit), Relation(Account, Message))): _field = 'reported' @classmethod def new(cls, user, thing, reason=None): from r2.lib.db import queries # check if this report exists already! rel = cls.rel(user, thing) q = rel._fast_query(user, thing, ['-1', '0', '1']) q = [report for (tupl, report) in q.iteritems() if report] if q: # stop if we've seen this before, so that we never get the # same report from the same user twice oldreport = q[0] g.log.debug("Ignoring duplicate report %s" % oldreport) return oldreport kw = {} if reason: kw['reason'] = reason r = Report(user, thing, '0', **kw) if not thing._loaded: thing._load() # mark item as reported try: thing._incr(cls._field) except (ValueError, TypeError): g.log.error("%r has bad field %r = %r" % (thing, cls._field, getattr(thing, cls._field, "(nonexistent)"))) raise r._commit() if hasattr(thing, 'author_id'): author = Account._byID(thing.author_id, data=True) author._incr('reported') if not getattr(thing, "ignore_reports", False): # update the reports queue if it exists queries.new_report(thing, r) # if the thing is already marked as spam, accept the report if thing._spam: cls.accept(thing) return r @classmethod def for_thing(cls, thing): rel = cls.rel(Account, thing.__class__) rels = rel._query(rel.c._thing2_id == thing._id, data=True) return list(rels) @classmethod def accept(cls, things, correct=True): from r2.lib.db import queries things = tup(things) things_by_cls = {} for thing in things: things_by_cls.setdefault(thing.__class__, []).append(thing) for thing_cls, cls_things in things_by_cls.iteritems(): to_clear = [] # look up all of the reports for each thing rel_cls = cls.rel(Account, thing_cls) thing_ids = [t._id for t in cls_things] rels = rel_cls._query(rel_cls.c._thing2_id == thing_ids) for r in rels: if r._name == '0': r._name = '1' if correct else '-1' r._commit() for thing in cls_things: if thing.reported > 0: thing.reported = 0 thing._commit() to_clear.append(thing) queries.clear_reports(to_clear, rels) @classmethod def get_reports(cls, wrapped, max_user_reasons=20): """Get two lists of mod and user reports on the item.""" if (wrapped.reported > 0 and (wrapped.can_ban or getattr(wrapped, "promoted", None) and c.user_is_sponsor)): from r2.models import SRMember reports = cls.for_thing(wrapped.lookups[0]) query = SRMember._query(SRMember.c._thing1_id == wrapped.sr_id, SRMember.c._name == "moderator") mod_dates = {rel._thing2_id: rel._date for rel in query} mod_reports = [] user_reports = [] for report in reports: # include in mod reports if made after the user became a mod if (report._thing1_id in mod_dates and report._date >= mod_dates[report._thing1_id]): mod_reports.append(report) else: user_reports.append(report) # mod reports return as tuples with (reason, name) mods = Account._byID([report._thing1_id for report in mod_reports], data=True, return_dict=True) mod_reports = [(getattr(report, "reason", None), mods[report._thing1_id].name) for report in mod_reports] # user reports return as tuples with (reason, count) user_reports = Counter( [getattr(report, "reason", None) for report in user_reports]) user_reports = user_reports.most_common(max_user_reasons) return mod_reports, user_reports else: return [], [] @classmethod def get_reasons(cls, wrapped): """Transition method in case API clients were already using this.""" if wrapped.can_ban and wrapped.reported > 0: return [("This attribute is deprecated. Please use mod_reports " "and user_reports instead.")] else: return []
class Vote( MultiRelation('vote', Relation(Account, Link), Relation(Account, Comment))): _defaults = {'organic': False} @classmethod def vote(cls, sub, obj, dir, ip, organic=False, cheater=False): from admintools import valid_user, valid_thing, update_score from r2.lib.count import incr_sr_count from r2.lib.db import queries sr = obj.subreddit_slow kind = obj.__class__.__name__.lower() karma = sub.karma(kind, sr) is_self_link = (kind == 'link' and getattr(obj, 'is_self', False)) #check for old vote rel = cls.rel(sub, obj) oldvote = rel._fast_query(sub, obj, ['-1', '0', '1']).values() oldvote = filter(None, oldvote) amount = 1 if dir is True else 0 if dir is None else -1 is_new = False #old vote if len(oldvote): v = oldvote[0] oldamount = int(v._name) v._name = str(amount) #these still need to be recalculated old_valid_thing = getattr(v, 'valid_thing', False) v.valid_thing = (valid_thing(v, karma, cheater=cheater) and getattr(v, 'valid_thing', False)) 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)) v.ip = ip old_valid_thing = v.valid_thing = valid_thing(v, karma, cheater=cheater) v.valid_user = (v.valid_thing and valid_user(v, sr, karma) and not is_self_link) if organic: v.organic = organic v._commit() up_change, down_change = score_changes(amount, oldamount) 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) if v.valid_user: author = Account._byID(obj.author_id, data=True) author.incr_karma(kind, sr, up_change - down_change) #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) # now write it out to Cassandra. We'll write it out to both # this way for a while CassandraVote._copy_from(v) queries.changed(v._thing2, True) return v @classmethod def likes(cls, sub, objs): # generalise and put on all abstract relations? if not sub or not objs: return {} from r2.models import Account assert isinstance(sub, Account) rels = {} for obj in objs: try: types = CassandraVote._rel(sub.__class__, obj.__class__) except TdbException: # for types for which we don't have a vote rel, we'll # skip them continue rels.setdefault(types, []).append(obj) ret = {} for relcls, items in rels.iteritems(): votes = relcls._fast_query(sub, items, properties=['name']) for cross, rel in votes.iteritems(): ret[cross] = (True if rel.name == '1' else False if rel.name == '-1' else None) return ret
class Vote( MultiRelation('vote', Relation(Account, Link), Relation(Account, Comment))): _defaults = {'organic': False} @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 if timer is None: timer = SimpleSillyStub() sr = obj.subreddit_slow kind = obj.__class__.__name__.lower() karma = sub.karma(kind, sr) is_self_link = (kind == 'link' and getattr(obj, 'is_self', False)) #check for old vote rel = cls.rel(sub, obj) oldvote = rel._fast_query(sub, obj, ['-1', '0', '1']).values() oldvote = filter(None, oldvote) timer.intermediate("pg_read_vote") amount = 1 if dir is True else 0 if dir is None else -1 is_new = False #old vote if len(oldvote): v = oldvote[0] oldamount = int(v._name) v._name = str(amount) #these still need to be recalculated old_valid_thing = getattr(v, 'valid_thing', False) v.valid_thing = (valid_thing(v, karma, cheater=cheater) and getattr(v, 'valid_thing', False)) 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 old_valid_thing = v.valid_thing = valid_thing(v, karma, cheater=cheater) v.valid_user = (v.valid_thing and valid_user(v, sr, karma) and not is_self_link) v._commit() timer.intermediate("pg_write_vote") up_change, down_change = score_changes(amount, oldamount) 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") 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") #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) 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") queries.changed(v._thing2, True) timer.intermediate("changed") return v @classmethod def likes(cls, sub, objs): if not sub or not objs: return {} from r2.models import Account assert isinstance(sub, Account) 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 rels.setdefault(types, []).append(obj) 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
class Vote(MultiRelation('vote', Relation(Account, Link), Relation(Account, Comment))): pass
class Vote( MultiRelation('vote', Relation(Account, Link), Relation(Account, Comment))): @classmethod def vote(cls, sub, obj, dir, ip, organic=False, cheater=False): from admintools import valid_user, valid_thing, update_score from r2.lib.count import incr_counts from r2.lib.db import queries sr = obj.subreddit_slow kind = obj.__class__.__name__.lower() karma = sub.karma(kind, sr) is_self_link = (kind == 'link' and hasattr(obj, 'is_self') and obj.is_self) #check for old vote rel = cls.rel(sub, obj) oldvote = rel._fast_query(sub, obj, ['-1', '0', '1']).values() oldvote = filter(None, oldvote) amount = 1 if dir is True else 0 if dir is None else -1 is_new = False #old vote if len(oldvote): v = oldvote[0] oldamount = int(v._name) v._name = str(amount) #these still need to be recalculated old_valid_thing = v.valid_thing v.valid_thing = (valid_thing(v, karma, cheater=cheater) and v.valid_thing) v.valid_user = (v.valid_user and v.valid_thing and valid_user(v, sr, karma)) #new vote else: is_new = True oldamount = 0 v = rel(sub, obj, str(amount)) v.author_id = obj.author_id v.sr_id = sr._id v.ip = ip old_valid_thing = v.valid_thing = \ valid_thing(v, karma, cheater = cheater) v.valid_user = (v.valid_thing and valid_user(v, sr, karma) and not is_self_link) if organic: v.organic = organic v._commit() v._fast_query_timestamp_touch(sub) up_change, down_change = score_changes(amount, oldamount) 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.valid_thing, old_valid_thing) if v.valid_user: author = Account._byID(obj.author_id, data=True) author.incr_karma(kind, sr, up_change - down_change) #update the sr's valid vote count if is_new and v.valid_thing and kind == 'link': if sub._id != obj.author_id: incr_counts([sr]) # now write it out to Cassandra. We'll write it out to both # this way for a while voter = v._thing1 votee = v._thing2 cvc = CassandraVote._rel(Account, votee.__class__) try: cv = cvc._fast_query(voter._id36, votee._id36) except tdb_cassandra.NotFound: cv = cvc(thing1_id=voter._id36, thing2_id=votee._id36) cv.name = v._name cv.valid_user, cv.valid_thing = v.valid_user, v.valid_thing cv.ip = v.ip if getattr(v, 'organic', False) or hasattr(cv, 'organic'): cv.organic = getattr(v, 'organic', False) cv._commit() queries.changed(votee, True) return v #TODO make this generic and put on multirelation? @classmethod def likes(cls, sub, obj): votes = cls._fast_query(sub, obj, ('1', '-1'), data=False, eager_load=False, timestamp_optimize=True) votes = dict((tuple(k[:2]), v) for k, v in votes.iteritems() if v) return votes
class Vote( MultiRelation('vote', Relation(Account, Link), Relation(Account, Comment))): @classmethod def vote(cls, sub, obj, dir, ip, spam=False, organic=False): from admintools import valid_user, valid_thing, update_score from r2.lib.count import incr_counts # An account can only perform 1 voting operation at a time. with g.make_lock('account_%s_voting' % sub._id) as lock: kind = obj.__class__.__name__.lower() lock.log('voting checkpoint A') # If downvoting ensure that the user has enough karma, it # will raise an exception if not. if dir == False: sub.check_downvote(kind) lock.log('voting checkpoint B') # Do the voting. sr = obj.subreddit_slow karma = sub.karma(kind, sr) lock.log('voting checkpoint C') #check for old vote rel = cls.rel(sub, obj) oldvote = list( rel._query(rel.c._thing1_id == sub._id, rel.c._thing2_id == obj._id, data=True)) amount = 1 if dir is True else 0 if dir is None else -1 # Users have a vote_multiplier which effects how much their votes are worth if isinstance(sub, Account): amount *= sub.vote_multiplier lock.log('voting checkpoint D') is_new = False #old vote if len(oldvote): v = oldvote[0] oldamount = int(v._name) v._name = str(amount) #these still need to be recalculated old_valid_thing = getattr(v, 'valid_thing', True) v.valid_thing = (valid_thing(v, karma) and v.valid_thing and not spam) v.valid_user = (v.valid_user and v.valid_thing and valid_user(v, sr, karma)) #new vote else: is_new = True oldamount = 0 v = rel(sub, obj, str(amount)) v.author_id = obj.author_id v.ip = ip old_valid_thing = v.valid_thing = (valid_thing(v, karma) and not spam) v.valid_user = v.valid_thing and valid_user(v, sr, karma) if organic: v.organic = organic lock.log('voting checkpoint E') v._commit() lock.log('voting checkpoint F') # Record that this account has made a downvote. up_change, down_change = score_changes(amount, oldamount) if down_change: sub.incr_downvote(down_change, kind) lock.log('voting checkpoint G') # Release the lock since both the downvote count and the vote count # have been updated, and then continue by updating karmas. update_score(obj, up_change, down_change, v.valid_thing, old_valid_thing) if v.valid_user: author = Account._byID(obj.author_id, data=True) author.incr_karma(kind, sr, up_change, down_change) #update the sr's valid vote count if is_new and v.valid_thing and kind == 'link': if sub._id != obj.author_id: incr_counts([sr]) return v #TODO make this generic and put on multirelation? @classmethod def likes(cls, sub, obj): votes = cls._fast_query(sub, obj, None, data=False) votes = dict((tuple(k[:2]), v) for k, v in votes.iteritems() if v) return votes
class Report( MultiRelation('report', Relation(Account, Link), Relation(Account, Comment), Relation(Account, Subreddit), Relation(Account, Message))): _field = 'reported' @classmethod def new(cls, user, thing): from r2.lib.db import queries # check if this report exists already! rel = cls.rel(user, thing) q = rel._fast_query(user, thing, ['-1', '0', '1']) q = [report for (tupl, report) in q.iteritems() if report] if q: # stop if we've seen this before, so that we never get the # same report from the same user twice oldreport = q[0] g.log.debug("Ignoring duplicate report %s" % oldreport) return oldreport r = Report(user, thing, '0') if not thing._loaded: thing._load() # mark item as reported try: thing._incr(cls._field) except (ValueError, TypeError): g.log.error("%r has bad field %r = %r" % (thing, cls._field, getattr(thing, cls._field, "(nonexistent)"))) raise r._commit() if hasattr(thing, 'author_id'): author = Account._byID(thing.author_id, data=True) author._incr('reported') # update the reports queue if it exists queries.new_report(thing) # if the thing is already marked as spam, accept the report if thing._spam: cls.accept(thing) return r @classmethod def for_thing(cls, thing): rel = cls.rel(Account, thing.__class__) rels = rel._query(rel.c._thing2_id == thing._id) return list(rels) @classmethod def accept(cls, things, correct=True): from r2.lib.db import queries things = tup(things) things_by_cls = {} for thing in things: things_by_cls.setdefault(thing.__class__, []).append(thing) to_clear = [] for thing_cls, cls_things in things_by_cls.iteritems(): # look up all of the reports for each thing rel_cls = cls.rel(Account, thing_cls) rels = rel_cls._query( rel_cls.c._thing2_id == [x._id for x in cls_things], rel_cls.c._name == '0') for r in rels: r._name = '1' if correct else '-1' r._commit() for thing in cls_things: if thing.reported > 0: thing.reported = 0 thing._commit() to_clear.append(thing) if to_clear: queries.clear_reports(to_clear)
class Report( MultiRelation('report', Relation(Account, Link), Relation(Account, Comment), Relation(Account, Subreddit), Relation(Account, Message))): _field = 'reported' @property def _user(self): return self._thing1 @property def _thing(self): return self._thing2 @classmethod def new(cls, user, thing): # check if this report exists already! rel = cls.rel(user, thing) oldreport = list( rel._query(rel.c._thing1_id == user._id, rel.c._thing2_id == thing._id, data=True)) # stop if we've seen this before, so that we never get the # same report from the same user twice if oldreport: return oldreport[0] r = Report(user, thing, '0', amount=0) if not thing._loaded: thing._load() # mark item as reported thing._incr(cls._field) # mark author as reported if hasattr(thing, 'author_id'): aid = thing.author_id author = Account._byID(aid) author._incr(cls._field) # mark user as having made a report user._incr('report_made') r._commit() admintools.report(thing) # if the thing is already marked as spam, accept the report if thing._spam: cls.accept(r) else: # set the report amount to 0, updating the cache in the process cls.set_amount(r, 0) return r @classmethod def set_amount(cls, r, amount): old_amount = int(r._name) if old_amount != amount: r._name = str(amount) r._commit() #update the cache for the amount = 0 and amount = None cases rel = cls.rels[(r._thing1.__class__, r._thing2.__class__)] for a in set((old_amount, amount, None)): # clear memoizing around this thing's author if not r._thing2._loaded: r._thing2._load() if hasattr(r._thing2, "author_id"): clear_memo('report._by_author', cls, r._thing2.author_id, amount=a) for t in (r._thing1, r._thing2): thing_key = cls._cache_prefix(rel, t.__class__, amount=a) + str(t._id) v = cache.get(thing_key) if v is not None: if a == old_amount and old_amount != amount and r._id in v: v.remove(r._id) elif r._id not in v: v.append(r._id) cache.set(thing_key, v) @classmethod def accept(cls, r, correct=True): ''' sets the various reporting fields, but does nothing to the corresponding spam fields (handled by unreport)''' amount = 1 if correct else -1 oldamount = int(r._name) # do nothing if nothing has changed if amount == oldamount: return up_change, down_change = score_changes(amount, oldamount) # update the user who made the report r._thing1._incr('report_correct', up_change) r._thing1._incr('report_ignored', down_change) # update the amount cls.set_amount(r, amount) # update the thing's number of reports only if we made no # decision prior to this if oldamount == 0: # update the author and thing field if getattr(r._thing2, Report._field) > 0: r._thing2._incr(Report._field, -1) if hasattr(r._thing2, "author_id"): aid = r._thing2.author_id author = Account._byID(aid) if getattr(author, Report._field) > 0: author._incr(Report._field, -1) admintools.report(r._thing2, -1) @classmethod @memoize('report._by_author') def _by_author_cache(cls, author_id, amount=None): res = {} for types, rel in cls.rels.iteritems(): # grab the proper thing table thing_type = types[1] thing_dict = tdb.types_id[thing_type._type_id] dtable, table = thing_dict.data_table # and the proper relationship table rel_table = tdb.rel_types_id[rel._type_id].rel_table[0] rel_dtable = tdb.rel_types_id[rel._type_id].rel_table[-1] where = [ dtable.c.key == 'author_id', sa.func.substring(dtable.c.value, 1, 1000) == str(author_id), dtable.c.thing_id == rel_table.c.thing2_id ] if amount is not None: where.extend([ rel_table.c.name == str(amount), rel_table.c.rel_id == rel_dtable.c.thing_id ]) s = sa.select([rel_table.c.rel_id], sa.and_(*where)) rids = [x[0] for x in s.execute().fetchall()] if rids: res[types] = rids return res @classmethod def _by_author(cls, author, amount=None): res = [] rdict = cls._by_author_cache(author._id, amount=amount) for types, rids in rdict.iteritems(): res.extend(cls.rels[types]._byID(rids, data=True, return_dict=False)) return res @classmethod def fastreported(cls, users, things, amount=None): if amount is None: amount = ('1', '0', '-1') res = cls._fast_query(users, things, amount) res = dict((tuple(k[:2]), v) for k, v in res.iteritems() if v) return res @classmethod def reported(cls, users=None, things=None, return_dict=True, amount=None): # nothing given, nothing to give back if not users and not things: return {} if return_dict else [] if users: users = tup(users) if things: things = tup(things) # if both are given, we can use fast_query if users and things: return cls.fastreported(users, things) # type_dict stores id keyed on (type, rel_key) type_dict = {} # if users, we have to search all the rel types on thing1_id if users: db_key = '_thing1_id' uid = [t._id for t in users] for key in cls.rels.keys(): type_dict[(Account, key)] = uid # if things, we have to search only on types present in the list if things: db_key = '_thing2_id' for t in things: key = (t.__class__, (Account, t.__class__)) type_dict.setdefault(key, []).append(t._id) def db_func(rel, db_key, amount): def _db_func(ids): q = rel._query(getattr(rel.c, db_key) == ids, data=True) if amount is not None: q._filter(rel.c._name == str(amount)) r_ids = {} # fill up the report listing from the query for r in q: key = getattr(r, db_key) r_ids.setdefault(key, []).append(r._id) # add blanks where no results were returned for i in ids: if i not in r_ids: r_ids[i] = [] return r_ids return _db_func rval = [] for (thing_class, rel_key), ids in type_dict.iteritems(): rel = cls.rels[rel_key] prefix = cls._cache_prefix(rel, thing_class, amount=amount) # load from cache res = sgm(cache, ids, db_func(rel, db_key, amount), prefix) # append *objects* to end of list res1 = [] for x in res.values(): res1.extend(x) if res1: rval.extend(rel._byID(res1, data=True, return_dict=False)) if return_dict: return dict(((r._thing1, r._thing2, cls._field), r) for r in rval) return rval @classmethod def _cache_prefix(cls, rel, t_class, amount=None): # encode the amount keyword on the prefix prefix = thing_prefix(rel.__name__) + '_' + \ thing_prefix(t_class.__name__) if amount is not None: prefix += ("_amount_%d" % amount) return prefix @classmethod def get_reported_authors(cls, time=None, sort=None): reports = {} for t_cls in (Link, Comment, Message): q = t_cls._query(t_cls.c._spam == False, t_cls.c.reported != 0, data=True) q._sort = desc("_date") if time: q._filter(time) reports.update(Report.reported(things=list(q), amount=0)) # at this point, we have a full list of reports made on the interval specified # build up an author to report list authors = Account._byID( [k[1].author_id for k, v in reports.iteritems()], data=True) if reports else [] # and build up a report on each author author_rep = {} for (tattler, thing, amount), r in reports.iteritems(): aid = thing.author_id if not author_rep.get(aid): author_rep[aid] = Storage(author=authors[aid]) author_rep[aid].num_reports = 1 author_rep[aid].acct_correct = tattler.report_correct author_rep[aid].acct_wrong = tattler.report_ignored author_rep[aid].most_recent = r._date author_rep[aid].reporters = set([tattler]) else: author_rep[aid].num_reports += 1 author_rep[aid].acct_correct += tattler.report_correct author_rep[aid].acct_wrong += tattler.report_ignored if author_rep[aid].most_recent < r._date: author_rep[aid].most_recent = r._date author_rep[aid].reporters.add(tattler) authors = author_rep.values() if sort == "hot": def report_hotness(a): return a.acct_correct / max(a.acct_wrong + a.acct_correct, 1) def better_reporter(a, b): q = report_hotness(b) - report_hotness(a) if q == 0: return b.acct_correct - a.acct_correct else: return 1 if q > 0 else -1 authors.sort(better_reporter) if sort == "top": authors.sort(lambda x, y: y.num_reports - x.num_reports) elif sort == "new": def newer_reporter(a, b): t = b.most_recent - a.most_recent t0 = datetime.timedelta(0) return 1 if t > t0 else -1 if t < t0 else 0 authors.sort(newer_reporter) return authors @classmethod def get_reporters(cls, time=None, sort=None): query = cls._query(cls.c._name == '0', eager_load=False, data=False, thing_data=False) if time: query._filter(time) query._sort = desc("_date") account_dict = {} min_report_time = {} for r in query: account_dict[r._thing1_id] = account_dict.get(r._thing1_id, 0) + 1 if min_report_time.get(r._thing1_id): min_report_time[r._thing1_id] = min( min_report_time[r._thing1_id], r._date) else: min_report_time[r._thing1_id] = r._date # grab users in chunks of 50 c_size = 50 accounts = account_dict.keys() accounts = [ Account._byID(accounts[i:i + c_size], return_dict=False, data=True) for i in xrange(0, len(accounts), c_size) ] accts = [] for a in accounts: accts.extend(a) if sort == "hot" or sort == "top": def report_hotness(a): return a.report_correct / max( a.report_ignored + a.report_correct, 1) def better_reporter(a, b): q = report_hotness(b) - report_hotness(a) if q == 0: return b.report_correct - a.report_correct else: return 1 if q > 0 else -1 accts.sort(better_reporter) elif sort == "new": def newer_reporter(a, b): t = (min_report_time[b._id] - min_report_time[a._id]) t0 = datetime.timedelta(0) return 1 if t > t0 else -1 if t < t0 else 0 accts.sort(newer_reporter) return accts
class Jury(MultiRelation('jury', Relation(Account, Link))): @classmethod def _new(cls, account, defendant): j = Jury(account, defendant, "0") j._commit() Jury.by_account(account, _update=True) Jury.by_defendant(defendant, _update=True) return j @classmethod @memoize('jury.by_account') def by_account_cache(cls, account_id): q = cls._query(cls.c._thing1_id == account_id) q._limit = 100 return [j._fullname for j in q] @classmethod def by_account(cls, account, _update=False): rel_ids = cls.by_account_cache(account._id, _update=_update) juries = DataThing._by_fullname(rel_ids, data=True, return_dict=False) if juries: load_things(juries, load_data=True) return juries @classmethod @memoize('jury.by_defendant') def by_defendant_cache(cls, defendant_id): q = cls._query(cls.c._thing2_id == defendant_id) q._limit = 1000 return [j._fullname for j in q] @classmethod def by_defendant(cls, defendant, _update=False): rel_ids = cls.by_defendant_cache(defendant._id, _update=_update) juries = DataThing._by_fullname(rel_ids, data=True, return_dict=False) if juries: load_things(juries, load_data=True) return juries @classmethod def by_account_and_defendant(cls, account, defendant): q = cls._fast_query(account, defendant, ("-1", "0", "1")) v = filter(None, q.values()) if v: return v[0] @classmethod def delete_old(cls, age="3 days", limit=500, verbose=False): cutoff = timeago(age) q = cls._query(cls.c._date < cutoff) q._limit = limit accounts = set() defendants = set() for j in q: accounts.add(j._thing1) defendants.add(j._thing2) j._delete() for a in accounts: Jury.by_account(a, _update=True) for d in defendants: if verbose: print "Deleting juries for defendant %s" % d._fullname Jury.by_defendant(d, _update=True)