Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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 []
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
class Vote(MultiRelation('vote',
                         Relation(Account, Link),
                         Relation(Account, Comment))):
    pass
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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)