class Flair(Relation(Subreddit, Account)): @classmethod def store(cls, sr, account, text = None, css_class = None): flair = cls(sr, account, 'flair', text = text, css_class = css_class) flair._commit() setattr(account, 'flair_%s_text' % sr._id, text) setattr(account, 'flair_%s_css_class' % sr._id, css_class) account._commit() @classmethod @memoize('flair.all_flair_by_sr') def all_flair_by_sr_cache(cls, sr_id): q = cls._query(cls.c._thing1_id == sr_id) return [t._id for t in q] @classmethod def all_flair_by_sr(cls, sr_id, _update=False): relids = cls.all_flair_by_sr_cache(sr_id, _update=_update) return cls._byID(relids).itervalues() @classmethod def flair_id_query(cls, sr, limit, after, reverse=False): extra_rules = [ cls.c._thing1_id == sr._id, cls.c._name == 'flair', ] if after: if reverse: extra_rules.append(cls.c._thing2_id < after._id) else: extra_rules.append(cls.c._thing2_id > after._id) sort = (desc if reverse else asc)('_thing2_id') return cls._query(*extra_rules, sort=sort, limit=limit)
class Ballot(Relation(Account, Poll)): @classmethod def submitballot(cls, user, comment, pollobj, response, anonymous, ip, spam): with g.make_lock('voting_on_%s' % pollobj._id): pollid = pollobj._id oldballot = list( cls._query(cls.c._thing1_id == user._id, cls.c._thing2_id == pollid)) if len(oldballot): raise PollError('You already voted on this poll') ballot = Ballot(user, pollobj, response) ballot.ip = ip ballot.anonymous = anonymous ballot.date = datetime.datetime.now().isoformat() ballot.response = response ballot._commit() pollobj.add_response(response) return ballot def export_row(self, aliases): userid = self._thing1_id pollid = self._thing2_id if hasattr(self, 'anonymous') and self.anonymous: if not userid in aliases: aliases[userid] = aliases['next_alias'] aliases['next_alias'] = aliases['next_alias'] + 1 username = aliases[userid] else: username = Account._byID(userid, data=True).name return "\"{0}\",\"{1}\",\"{2}\",\"{3}\"".format( username, pollid, self.response, self.date)
class ModeratorInbox(Relation(Subreddit, Message)): #TODO: shouldn't dupe this @classmethod def _add(cls, sr, obj, *a, **kw): i = ModeratorInbox(sr, obj, *a, **kw) i.new = True i._commit() if not sr._loaded: sr._load() moderators = Account._byID(sr.moderator_ids(), data=True, return_dict=False) for m in moderators: if obj.author_id != m._id and not getattr(m, 'modmsgtime', None): m.modmsgtime = obj._date m._commit() return i @classmethod def set_unread(cls, thing, unread): inbox = cls._query(cls.c._thing2_id == thing._id, 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))): @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 Trophy(Relation(Account, Award)): @classmethod def _new(cls, recipient, award, description = None, url = None, cup_info = None): # The "name" column of the relation can't be a constant or else a # given account would not be allowed to win a given award more than # once. So we're setting it to the string form of the timestamp. # Still, we won't have that date just yet, so for a moment we're # setting it to "trophy". t = Trophy(recipient, award, "trophy") t._name = str(t._date) if description: t.description = description if url: t.url = url if cup_info: recipient.set_cup(cup_info) t._commit() Trophy.by_account(recipient, _update=True) Trophy.by_award(award, _update=True) @classmethod @memoize('trophy.by_account2') def by_account_cache(cls, account_id): q = Trophy._query(Trophy.c._thing1_id == account_id, sort = desc('_date')) q._limit = 500 return [ t._id for t in q ] @classmethod def by_account(cls, account, _update=False): rel_ids = cls.by_account_cache(account._id, _update=_update) trophies = Trophy._byID_rel(rel_ids, data=True, eager_load=True, thing_data=True, return_dict = False) return trophies @classmethod @memoize('trophy.by_award2') def by_award_cache(cls, award_id): q = Trophy._query(Trophy.c._thing2_id == award_id, sort = desc('_date')) q._limit = 500 return [ t._id for t in q ] @classmethod def by_award(cls, award, _update=False): rel_ids = cls.by_award_cache(award._id, _update=_update) trophies = Trophy._byID_rel(rel_ids, data=True, eager_load=True, thing_data=True, return_dict = False) return trophies
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 Flair(Relation(Subreddit, Account)): @classmethod def store(cls, sr, account, text=None, css_class=None): flair = cls(sr, account, 'flair', text=text, css_class=css_class) flair._commit() setattr(account, 'flair_%s_text' % sr._id, text) setattr(account, 'flair_%s_css_class' % sr._id, css_class) account._commit() @classmethod @memoize('flair.all_flair_by_sr') def all_flair_by_sr_cache(cls, sr_id): q = cls._query(cls.c._thing1_id == sr_id) return [t._id for t in q] @classmethod def all_flair_by_sr(cls, sr_id, _update=False): relids = cls.all_flair_by_sr_cache(sr_id, _update=_update) return cls._byID(relids).itervalues()
class SRMember(Relation(Subreddit, Account)): _defaults = dict(encoded_permissions=None) _permission_class = None def has_permission(self, perm): """Returns whether this member has explicitly been granted a permission. """ return self.get_permissions().get(perm, False) def get_permissions(self): """Returns permission set for this member (or None if N/A).""" if not self._permission_class: raise NotImplementedError return self._permission_class.loads(self.encoded_permissions) def update_permissions(self, **kwargs): """Grants or denies permissions to this member. Args are named parameters with bool or None values (use None to disable granting or denying the permission). After calling this method, the relation will be _dirty until _commit is called. """ if not self._permission_class: raise NotImplementedError perm_set = self._permission_class.loads(self.encoded_permissions) if perm_set is None: perm_set = self._permission_class() for k, v in kwargs.iteritems(): if v is None: if k in perm_set: del perm_set[k] else: perm_set[k] = v self.encoded_permissions = perm_set.dumps() def set_permissions(self, perm_set): """Assigns a permission set to this relation.""" self.encoded_permissions = perm_set.dumps() def is_superuser(self): return self.get_permissions().is_superuser()
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 Friend(Relation(Account, Account)): pass Account.__bases__ += (UserRel('friend', Friend),)
class LinkTag(Relation(Link, Tag)): pass
class Friend(Relation(Account, Account)): _cache = g.thingcache @classmethod def _cache_prefix(cls): return "friend:"
class Trophy(Relation(Account, Award)): @classmethod def _new(cls, recipient, award, description=None, url=None, cup_info=None): # The "name" column of the relation can't be a constant or else a # given account would not be allowed to win a given award more than # once. So we're setting it to the string form of the timestamp. # Still, we won't have that date just yet, so for a moment we're # setting it to "trophy". t = Trophy(recipient, award, "trophy") t._name = str(t._date) if description: t.description = description if url: t.url = url if cup_info: recipient.set_cup(cup_info) t._commit() t.update_caches() return t def update_caches(self): self.by_account(self._thing1, _update=True) self.by_award(self._thing2, _update=True) @classmethod @memoize('trophy.by_account2') def by_account_cache(cls, account_id): q = Trophy._query(Trophy.c._thing1_id == account_id, sort=desc('_date')) q._limit = 500 return [t._id for t in q] @classmethod def by_account(cls, account, _update=False): rel_ids = cls.by_account_cache(account._id, _update=_update) trophies = Trophy._byID_rel(rel_ids, data=True, eager_load=True, thing_data=True, return_dict=False) return trophies @classmethod @memoize('trophy.by_award2') def by_award_cache(cls, award_id): q = Trophy._query(Trophy.c._thing2_id == award_id, sort=desc('_date')) q._limit = 50 return [t._id for t in q] @classmethod def by_award(cls, award, _update=False): rel_ids = cls.by_award_cache(award._id, _update=_update) trophies = Trophy._byID_rel(rel_ids, data=True, eager_load=True, thing_data=True, return_dict=False) return trophies @classmethod def claim(cls, user, uid, award, description, url): with g.make_lock("claim_award", str("%s_%s" % (user.name, uid))): existing_trophy_id = user.get_trophy_id(uid) if existing_trophy_id: trophy = cls._byID(existing_trophy_id) preexisting = True else: preexisting = False trophy = cls._new(user, award, description=description, url=url) user.set_trophy_id(uid, trophy._id) user._commit() return trophy, preexisting @property def trophy_url(self): return getattr(self, "url", getattr(self._thing2, "url", None))
class Friend(Relation(Account, Account)): pass Account.__bases__ += (UserRel('friend', Friend, disable_reverse_ids_fn=True),
class Flair(Relation(Subreddit, Account)): pass
class Click(Relation(Account, Link)): pass class SimpleRelation(tdb_cassandra.Relation):
class Vote(MultiRelation('vote', Relation(Account, Link), Relation(Account, Comment))): pass
class AdSR(Relation(Ad, Subreddit)): @classmethod def _new(cls, ad, sr, weight=100): t = AdSR(ad, sr, "adsr") t.weight = weight t._commit() AdSR.by_ad(ad, _update=True) AdSR.by_sr(sr, _update=True) @classmethod @memoize('adsr.by_ad') def by_ad_cache(cls, ad_id): q = AdSR._query(AdSR.c._thing1_id == ad_id, sort=desc('_date')) q._limit = 500 return [t._id for t in q] @classmethod def by_ad(cls, ad, _update=False): rel_ids = cls.by_ad_cache(ad._id, _update=_update) adsrs = AdSR._byID_rel(rel_ids, data=True, eager_load=True, thing_data=True, return_dict=False) return adsrs @classmethod @memoize('adsr.by_sr') def by_sr_cache(cls, sr_id): q = AdSR._query(AdSR.c._thing2_id == sr_id, sort=desc('_date')) q._limit = 500 return [t._id for t in q] @classmethod def by_sr(cls, sr, _update=False): rel_ids = cls.by_sr_cache(sr._id, _update=_update) adsrs = AdSR._byID_rel(rel_ids, data=True, eager_load=True, thing_data=True, return_dict=False) return adsrs @classmethod def by_sr_merged(cls, sr, _update=False): if sr.name == g.default_sr: return cls.by_sr(sr) my_adsrs = cls.by_sr(sr) global_adsrs = cls.by_sr(Subreddit._by_name(g.default_sr)) seen = {} for adsr in my_adsrs: seen[adsr._thing1.codename] = True for adsr in global_adsrs: if adsr._thing1.codename not in seen: my_adsrs.append(adsr) return my_adsrs @classmethod def by_ad_and_sr(cls, ad, sr): q = cls._fast_query(ad, sr, "adsr") return q.values()[0]
class SRMember(Relation(Subreddit, Account)): pass Subreddit.__bases__ += (UserRel('moderator', SRMember),
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 LinkAuthor(Relation(Account, Link)): pass
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 Friend(Relation(Account, Account)): pass
class Flair(Relation(Subreddit, Account)): _cache = g.thingcache @classmethod def _cache_prefix(cls): return "flair:"
class Tag(Relation(Account, Link)): pass
class SaveHide(Relation(Account, Link)): pass
class Friend(Relation(Account, Account)): _int_props = ('extra', )
class Click(Relation(Account, Link)): pass
class SRMember(Relation(Subreddit, Account)): pass
# All portions of the code written by reddit are Copyright (c) 2006-2015 reddit # Inc. All Rights Reserved. ############################################################################### from collections import Counter from r2.lib import hooks from r2.lib.db.thing import Relation, MultiRelation from r2.lib.utils import tup from r2.models import Link, Comment, Message, Subreddit, Account from pylons import tmpl_context as c from pylons import app_globals as g _LinkReport = Relation(Account, Link) _CommentReport = Relation(Account, Comment) _SubredditReport = Relation(Account, Subreddit) _MessageReport = Relation(Account, Message) REPORT_RELS = (_LinkReport, _CommentReport, _SubredditReport, _MessageReport) for report_cls in REPORT_RELS: report_cls._cache = g.thingcache _LinkReport._cache_prefix = classmethod(lambda cls: "reportlink:") _CommentReport._cache_prefix = classmethod(lambda cls: "reportcomment:") _SubredditReport._cache_prefix = classmethod(lambda cls: "reportsr:") _MessageReport._cache_prefix = classmethod(lambda cls: "reportmessage:") class Report(MultiRelation('report', *REPORT_RELS)):