# -*- coding: utf-8 -*-
import json
import time

from datetime import datetime, timedelta

from django.conf import settings
from django.core import mail

import mock

from olympia import amo
from olympia.abuse.models import AbuseReport
from olympia.access.models import Group, GroupUser
from olympia.activity.models import ActivityLog
from olympia.addons.models import (
    Addon, AddonApprovalsCounter, AddonReviewerFlags, AddonUser)
from olympia.amo.tests import (
    TestCase, addon_factory, file_factory, user_factory, version_factory)
from olympia.files.models import File, FileValidation, WebextPermission
from olympia.ratings.models import Rating
from olympia.reviewers.models import (
    AutoApprovalNotEnoughFilesError, AutoApprovalNoValidationResultError,
    AutoApprovalSummary, RereviewQueueTheme, ReviewerScore,
    ReviewerSubscription, ViewFullReviewQueue, ViewPendingQueue,
    ViewUnlistedAllList, send_notifications, set_reviewing_cache)
from olympia.users.models import UserProfile
from olympia.versions.models import Version, version_uploaded

def create_search_ext(name, version_str, addon_status, file_status,
                      channel):
    addon, created_ = Addon.objects.get_or_create(
        name__localized_string=name,
        defaults={'type': amo.ADDON_SEARCH, 'name': name})
    version, created_ = Version.objects.get_or_create(
        addon=addon, version=version_str, defaults={'channel': channel})
    File.objects.create(version=version, filename=u"%s.xpi" % name,
                        platform=amo.PLATFORM_ALL.id, status=file_status)
    # Update status *after* there are files:
    addon = Addon.objects.get(pk=addon.id)
    addon.update(status=addon_status)
    return addon

class TestQueue(TestCase):
    """Tests common attributes and coercions that each view must support."""
    __test__ = False  # this is an abstract test case

    def test_latest_version(self):
        addon = self.new_addon()
        v1 = addon.find_latest_version(self.channel)
        v1.update(created=self.days_ago(2))
        v1.all_files[0].update(status=amo.STATUS_PUBLIC)
        version_factory(addon=addon, version='2.0', created=self.days_ago(1),
                        channel=self.channel,
                        file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=addon, version='3.0', created=self.days_ago(0),
                        channel=self.channel,
                        file_kw={'status': amo.STATUS_AWAITING_REVIEW})
        row = self.Queue.objects.get()
        assert row.latest_version == '3.0'

    def test_addons_disabled_by_user_are_hidden(self):
        self.new_addon(version=u'0.1').update(disabled_by_user=True)
        assert list(self.Queue.objects.all()) == []

    def test_addons_disabled_by_admin_are_hidden(self):
        self.new_addon(version=u'0.1').update(status=amo.STATUS_DISABLED)
        assert list(self.Queue.objects.all()) == []

    def test_reviewed_files_are_hidden(self):
        self.new_addon(name='Unreviewed')
        addon_factory(name='Already Reviewed')
        assert sorted(q.addon_name for q in self.Queue.objects.all()) == (
            ['Unreviewed'])

    def test_search_extensions(self):
        self.new_search_ext('Search Tool', '0.1')
        row = self.Queue.objects.get()
        assert row.addon_name == u'Search Tool'
        assert row.addon_type_id == amo.ADDON_SEARCH

    def test_count_all(self):
        # Create two new addons and give each another version.
        version_factory(addon=self.new_addon(), version=u'2.0',
                        channel=self.channel)
        version_factory(addon=self.new_addon(), version=u'2.0',
                        channel=self.channel)
        assert self.Queue.objects.all().count() == 2

class TestPendingQueue(TestQueue):
    __test__ = True
    Queue = ViewPendingQueue
    channel = amo.RELEASE_CHANNEL_LISTED

    def new_addon(self, name=u'Pending', version=u'1.0'):
        """Creates an approved addon with two listed versions, one approved,
        the second awaiting review."""
        addon = addon_factory(
            name=name,
            version_kw={'version': u'0.0.1', 'channel': self.channel,
                        'created': self.days_ago(1)})
        version_factory(
            addon=addon, version=version, channel=self.channel,
            file_kw={'status': amo.STATUS_AWAITING_REVIEW,
                     'is_restart_required': False})
        return addon

    def new_search_ext(self, name, version, **kw):
        return create_search_ext(name, version,
                                 amo.STATUS_PUBLIC, amo.STATUS_AWAITING_REVIEW,
                                 channel=self.channel, **kw)

    def test_waiting_time(self):
        self.new_addon()
        Version.objects.update(created=datetime.utcnow())
        row = self.Queue.objects.all()[0]
        assert row.waiting_time_days == 0
        # Time zone will be off, hard to test this.
        assert row.waiting_time_hours is not None

    def test_flags_needs_admin_code_review(self):
        AddonReviewerFlags.objects.create(
            addon=self.new_addon(), needs_admin_code_review=True)

        queue = self.Queue.objects.get()
        assert queue.flags == [
            ('needs-admin-code-review', 'Needs Admin Code Review')]

    def test_flags_info_request(self):
        AddonReviewerFlags.objects.create(
            addon=self.new_addon(),
            pending_info_request=datetime.now() + timedelta(days=6))
        queue = self.Queue.objects.get()
        assert queue.flags == [('info', 'More Information Requested')]

    def test_flags_jetpack(self):
        self.new_addon().find_latest_version(self.channel).all_files[0].update(
            jetpack_version='1.8')

        queue = self.Queue.objects.get()
        assert queue.flags == [('jetpack', 'Jetpack Add-on')]

    def test_flags_is_restart_required(self):
        self.new_addon().find_latest_version(self.channel).all_files[0].update(
            is_restart_required=True)

        queue = self.Queue.objects.get()
        assert queue.flags == [('is_restart_required', 'Requires Restart')]

    def test_flags_sources_provided(self):
        self.new_addon().find_latest_version(self.channel).update(
            source='/some/source/file')

        queue = self.Queue.objects.get()
        assert queue.flags == [('sources-provided', 'Sources provided')]

    def test_flags_webextension(self):
        self.new_addon().find_latest_version(self.channel).all_files[0].update(
            is_webextension=True)

        queue = self.Queue.objects.get()
        assert queue.flags == [('webextension', 'WebExtension')]

    def test_no_flags(self):
        self.new_addon()

        queue = self.Queue.objects.get()
        assert queue.flags == []

class TestFullReviewQueue(TestQueue):
    __test__ = True
    Queue = ViewFullReviewQueue
    channel = amo.RELEASE_CHANNEL_LISTED

    def new_addon(self, name=u'Nominated', version=u'1.0',
                  addon_status=amo.STATUS_NOMINATED,
                  file_status=amo.STATUS_AWAITING_REVIEW):
        addon = addon_factory(
            name=name, status=addon_status,
            version_kw={'version': version, 'channel': self.channel},
            file_kw={'status': file_status})
        return addon

    def new_search_ext(self, name, version, **kw):
        return create_search_ext(name, version,
                                 amo.STATUS_NOMINATED,
                                 amo.STATUS_AWAITING_REVIEW,
                                 channel=self.channel, **kw)

    def test_waiting_time(self):
        self.new_addon()
        Version.objects.update(nomination=datetime.utcnow())
        row = self.Queue.objects.all()[0]
        assert row.waiting_time_days == 0
        # Time zone will be off, hard to test this.
        assert row.waiting_time_hours is not None

class TestUnlistedAllList(TestCase):
    Queue = ViewUnlistedAllList
    channel = amo.RELEASE_CHANNEL_UNLISTED
    fixtures = ['base/users']

    def new_addon(self, name=u'Unlisted', version=u'1.0',
                  addon_status=amo.STATUS_NULL,
                  file_status=amo.STATUS_PUBLIC):
        addon = addon_factory(
            name=name, status=addon_status,
            version_kw={'version': version, 'channel': self.channel},
            file_kw={'status': file_status})
        return addon

    def test_all_addons_are_in_q(self):
        self.new_addon('Public', addon_status=amo.STATUS_PUBLIC,
                       file_status=amo.STATUS_PUBLIC)
        self.new_addon('Nominated', addon_status=amo.STATUS_NOMINATED,
                       file_status=amo.STATUS_AWAITING_REVIEW)
        self.new_addon('Deleted', addon_status=amo.STATUS_PUBLIC,
                       file_status=amo.STATUS_PUBLIC).delete()
        assert sorted(q.addon_name for q in self.Queue.objects.all()) == (
            ['Deleted', 'Nominated', 'Public'])

    def test_authors(self):
        addon = self.new_addon()
        bert = user_factory(username='bert')
        ernie = user_factory(username='ernie')
        AddonUser.objects.create(addon=addon, user=bert)
        AddonUser.objects.create(addon=addon, user=ernie)
        row = self.Queue.objects.all()[0]
        self.assertSetEqual(set(row.authors),
                            {(ernie.id, 'ernie'), (bert.id, 'bert')})

    def test_last_reviewed_version(self):
        today = datetime.today().date()
        addon = self.new_addon(version='1.0')
        v2 = version_factory(addon=addon, version='2.0', channel=self.channel)
        log = ActivityLog.create(amo.LOG.APPROVE_VERSION, v2, v2.addon,
                                 user=UserProfile.objects.get(pk=999))
        version_factory(addon=addon, version='3.0', channel=self.channel)
        row = self.Queue.objects.all()[0]
        assert row.review_date == today
        assert row.review_version_num == '2.0'
        assert row.review_log_id == log.id

    def test_no_developer_actions(self):
        addon = self.new_addon(version='1.0')
        ActivityLog.create(amo.LOG.ADD_VERSION, addon.latest_unlisted_version,
                           addon, user=UserProfile.objects.get(pk=999))
        row = self.Queue.objects.all()[0]
        assert row.review_version_num is None

        ver2 = version_factory(version='2.0', addon=addon,
                               channel=self.channel)
        ActivityLog.create(amo.LOG.APPROVE_VERSION, ver2, addon,
                           user=UserProfile.objects.get(pk=999))
        row = self.Queue.objects.all()[0]
        assert row.review_version_num == '2.0'

        ver3 = version_factory(version='3.0', addon=addon,
                               channel=self.channel)
        ActivityLog.create(amo.LOG.EDIT_VERSION, ver3, addon,
                           user=UserProfile.objects.get(pk=999))
        row = self.Queue.objects.all()[0]
        # v2.0 is still the last reviewed version.
        assert row.review_version_num == '2.0'

    def test_no_automatic_reviews(self):
        ver = self.new_addon(
            name='addon789', version='1.0').latest_unlisted_version
        ActivityLog.create(
            amo.LOG.APPROVE_VERSION, ver, ver.addon,
            user=UserProfile.objects.get(pk=settings.TASK_USER_ID))
        row = self.Queue.objects.all()[0]
        assert row.review_version_num is None

    def test_latest_version(self):
        addon = addon_factory(
            version_kw={'version': u'0.1', 'channel': self.channel,
                        'created': self.days_ago(2)},
            file_kw={'created': self.days_ago(2)})
        version_factory(
            addon=addon, version=u'0.2', channel=self.channel,
            created=self.days_ago(1), file_kw={'created': self.days_ago(1)})
        version_factory(
            addon=addon, version=u'0.3', channel=self.channel)
        row = self.Queue.objects.get()
        assert row.latest_version == '0.3'

    def test_addons_disabled_by_user_are_hidden(self):
        self.new_addon().update(disabled_by_user=True)
        assert list(self.Queue.objects.all()) == []

    def test_addons_disabled_by_admin_are_hidden(self):
        self.new_addon(version=u'0.1').update(status=amo.STATUS_DISABLED)
        assert list(self.Queue.objects.all()) == []

    def test_count_all(self):
        addon1 = self.new_addon()
        version_factory(addon=addon1, version=u'0.2')
        addon2 = self.new_addon()
        version_factory(addon=addon2, version=u'0.2')
        assert self.Queue.objects.all().count() == 2

    def test_mixed_listed(self):
        unlisted_listed = addon_factory(
            status=amo.STATUS_NULL, name=u'UnlistedListed',
            version_kw={'version': u'0.1',
                        'channel': amo.RELEASE_CHANNEL_UNLISTED},
            file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=unlisted_listed, version=u'0.2',
                        channel=amo.RELEASE_CHANNEL_LISTED,
                        file_kw={'status': amo.STATUS_PUBLIC})

        listed_unlisted = addon_factory(
            status=amo.STATUS_NULL, name=u'ListedUnlisted',
            version_kw={'version': u'0.1',
                        'channel': amo.RELEASE_CHANNEL_LISTED},
            file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=listed_unlisted, version=u'0.2',
                        channel=amo.RELEASE_CHANNEL_UNLISTED,
                        file_kw={'status': amo.STATUS_PUBLIC})

        just_unlisted = addon_factory(
            status=amo.STATUS_NULL, name=u'JustUnlisted',
            version_kw={'version': u'0.1',
                        'channel': amo.RELEASE_CHANNEL_UNLISTED},
            file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=just_unlisted, version=u'0.2',
                        channel=amo.RELEASE_CHANNEL_UNLISTED,
                        file_kw={'status': amo.STATUS_PUBLIC})

        just_listed = addon_factory(
            status=amo.STATUS_NULL, name=u'JustListed',
            version_kw={'version': u'0.1',
                        'channel': amo.RELEASE_CHANNEL_LISTED},
            file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=just_listed, version=u'0.2',
                        channel=amo.RELEASE_CHANNEL_LISTED,
                        file_kw={'status': amo.STATUS_PUBLIC})

        assert self.Queue.objects.all().count() == 3
        assert [addon.addon_name for addon in self.Queue.objects.all()] == [
            'UnlistedListed', 'ListedUnlisted', 'JustUnlisted']
        assert ([addon.latest_version for addon in self.Queue.objects.all()] ==
                ['0.1', '0.2', '0.2'])

class TestReviewerSubscription(TestCase):
    fixtures = ['base/addon_3615', 'base/users']

    def setUp(self):
        super(TestReviewerSubscription, self).setUp()
        self.addon = Addon.objects.get(pk=3615)
        self.version = self.addon.current_version
        self.user_one = UserProfile.objects.get(pk=55021)
        self.user_two = UserProfile.objects.get(pk=999)
        self.reviewer_group = Group.objects.create(
            name='Reviewers: Legacy', rules='Addons:Review')
        GroupUser.objects.create(
            group=self.reviewer_group, user=self.user_one)
        self.post_reviewer_group = Group.objects.create(
            name='Reviewers: Add-ons', rules='Addons:PostReview')
        GroupUser.objects.create(
            group=self.post_reviewer_group, user=self.user_two)
        ReviewerSubscription.objects.create(
            addon=self.addon, user=self.user_one)
        ReviewerSubscription.objects.create(
            addon=self.addon, user=self.user_two)

    def test_email(self):
        es = ReviewerSubscription.objects.get(user=self.user_one)
        es.send_notification(self.version)
        assert len(mail.outbox) == 1
        assert mail.outbox[0].to == [u'del@icio.us']
        assert mail.outbox[0].subject == (
            'Mozilla Add-ons: Delicious Bookmarks Updated')

    def test_notifications(self):
        send_notifications(sender=self.version)
        assert len(mail.outbox) == 2
        emails = sorted([o.to for o in mail.outbox])
        assert emails == [[u'del@icio.us'], [u'regular@mozilla.com']]

    def test_notifications_setting_persists(self):
        send_notifications(Version, self.version)
        assert ReviewerSubscription.objects.count() == 2
        mail.outbox = []
        send_notifications(Version, self.version)
        assert len(mail.outbox) == 2

    def test_dont_send_notifications_unlisted(self):
        self.version.update(channel=amo.RELEASE_CHANNEL_UNLISTED)
        version_uploaded.send(sender=self.version)
        assert len(mail.outbox) == 0

    def test_signal_edit(self):
        self.version.save()
        assert len(mail.outbox) == 0

    def test_signal_create(self):
        v = Version.objects.create(addon=self.addon)
        version_uploaded.send(sender=v)
        assert len(mail.outbox) == 2
        assert mail.outbox[0].subject == (
            'Mozilla Add-ons: Delicious Bookmarks Updated')

    def test_signal_create_twice(self):
        v = Version.objects.create(addon=self.addon)
        version_uploaded.send(sender=v)
        mail.outbox = []
        v = Version.objects.create(addon=self.addon)
        version_uploaded.send(sender=v)
        assert len(mail.outbox) == 2

    def test_no_email_for_ex_reviewers(self):
        self.user_one.delete()
        # Remove user_one from reviewers.
        GroupUser.objects.get(
            group=self.reviewer_group, user=self.user_one).delete()
        send_notifications(sender=self.version)
        assert len(mail.outbox) == 1  # Only notification for user_two remains.

    def test_no_email_address_for_reviewer(self):
        self.user_one.update(email=None)
        send_notifications(sender=self.version)
        assert len(mail.outbox) == 1  # Only notification for user_two remains.

class TestReviewerScore(TestCase):
    fixtures = ['base/users']

    def setUp(self):
        super(TestReviewerScore, self).setUp()
        self.addon = amo.tests.addon_factory(status=amo.STATUS_NOMINATED)
        self.user = UserProfile.objects.get(email='reviewer@mozilla.com')

    def _give_points(self, user=None, addon=None, status=None):
        user = user or self.user
        addon = addon or self.addon
        ReviewerScore.award_points(
            user, addon, status or addon.status, version=addon.current_version)

    def check_event(self, type, status, event, **kwargs):
        self.addon.type = type
        assert ReviewerScore.get_event(self.addon, status, **kwargs) == event

    def test_events_addons(self):
        types = {
            amo.ADDON_ANY: None,
            amo.ADDON_EXTENSION: 'ADDON',
            amo.ADDON_THEME: 'XUL_THEME',
            amo.ADDON_DICT: 'DICT',
            amo.ADDON_SEARCH: 'SEARCH',
            amo.ADDON_LPAPP: 'LP',
            amo.ADDON_LPADDON: 'LP',
            amo.ADDON_PLUGIN: 'ADDON',
            amo.ADDON_API: 'ADDON',
            amo.ADDON_PERSONA: 'PERSONA',
            amo.ADDON_STATICTHEME: 'STATICTHEME',
        }
        statuses = {
            amo.STATUS_NULL: None,
            amo.STATUS_PENDING: None,
            amo.STATUS_NOMINATED: 'FULL',
            amo.STATUS_PUBLIC: 'UPDATE',
            amo.STATUS_DISABLED: None,
            amo.STATUS_DELETED: None,
            amo.STATUS_REJECTED: None,
            amo.STATUS_REVIEW_PENDING: None,
        }
        for tk, tv in types.items():
            for sk, sv in statuses.items():
                try:
                    event = getattr(amo, 'REVIEWED_%s_%s' % (tv, sv))
                except AttributeError:
                    try:
                        event = getattr(amo, 'REVIEWED_%s' % tv)
                    except AttributeError:
                        event = None
                self.check_event(tk, sk, event)

    def test_events_post_review(self):
        self.addon.update(status=amo.STATUS_PUBLIC)
        base_args = (self.addon, self.addon.status)
        # No version.
        assert ReviewerScore.get_event(
            *base_args, version=None,
            post_review=True) == amo.REVIEWED_EXTENSION_LOW_RISK
        # No autoapprovalsummary.
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) == amo.REVIEWED_EXTENSION_LOW_RISK
        # Now with a summary... low risk.
        summary = AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            weight=-10)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_LOW_RISK
        # Medium risk.
        summary.update(weight=91)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_MEDIUM_RISK
        # High risk.
        summary.update(weight=176)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_HIGH_RISK
        # Highest risk.
        summary.update(weight=276)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_HIGHEST_RISK
        # Highest risk again.
        summary.update(weight=65535)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_HIGHEST_RISK
        # Content review is always the same.
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version, post_review=True,
            content_review=True) == amo.REVIEWED_CONTENT_REVIEW

    def test_award_points(self):
        self._give_points()
        assert ReviewerScore.objects.all()[0].score == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])

    def test_award_points_with_extra_note(self):
        ReviewerScore.award_points(
            self.user, self.addon, self.addon.status, extra_note=u'ÔMG!')
        reviewer_score = ReviewerScore.objects.all()[0]
        assert reviewer_score.note_key == amo.REVIEWED_ADDON_FULL
        assert reviewer_score.score == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])
        assert reviewer_score.note == u'ÔMG!'

    def test_award_points_bonus(self):
        user2 = UserProfile.objects.get(email='admin@mozilla.com')
        bonus_days = 2
        days = amo.REVIEWED_OVERDUE_LIMIT + bonus_days

        bonus_addon = addon_factory(
            status=amo.STATUS_NOMINATED,
            file_kw={'status': amo.STATUS_AWAITING_REVIEW})
        bonus_addon.current_version.update(
            nomination=(datetime.now() - timedelta(days=days, minutes=5))
        )
        self._give_points(user2, bonus_addon, amo.STATUS_NOMINATED)
        score = ReviewerScore.objects.get(user=user2)
        expected = (amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL] +
                    (amo.REVIEWED_OVERDUE_BONUS * bonus_days))

        assert score.score == expected

    def test_award_points_no_bonus_for_content_review(self):
        self.addon.update(status=amo.STATUS_PUBLIC)
        self.addon.current_version.update(nomination=self.days_ago(28))
        AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            weight=100)
        ReviewerScore.award_points(
            self.user, self.addon, self.addon.status,
            version=self.addon.current_version,
            post_review=False, content_review=True)
        score = ReviewerScore.objects.get(user=self.user)
        assert score.score == amo.REVIEWED_SCORES[amo.REVIEWED_CONTENT_REVIEW]

    def test_award_points_no_bonus_for_post_review(self):
        self.addon.update(status=amo.STATUS_PUBLIC)
        self.addon.current_version.update(nomination=self.days_ago(29))
        AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            weight=101)
        ReviewerScore.award_points(
            self.user, self.addon, self.addon.status,
            version=self.addon.current_version,
            post_review=True, content_review=False)
        score = ReviewerScore.objects.get(user=self.user)
        assert score.score == amo.REVIEWED_SCORES[
            amo.REVIEWED_EXTENSION_MEDIUM_RISK]
        assert score.version == self.addon.current_version

    def test_award_moderation_points(self):
        ReviewerScore.award_moderation_points(self.user, self.addon, 1)
        score = ReviewerScore.objects.all()[0]
        assert score.score == (
            amo.REVIEWED_SCORES.get(amo.REVIEWED_ADDON_REVIEW))
        assert score.note_key == amo.REVIEWED_ADDON_REVIEW
        assert not score.version

    def test_get_total(self):
        user2 = UserProfile.objects.get(email='admin@mozilla.com')
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        assert ReviewerScore.get_total(self.user) == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL] +
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_UPDATE])
        assert ReviewerScore.get_total(user2) == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])

    def test_get_recent(self):
        user2 = UserProfile.objects.get(email='admin@mozilla.com')
        self._give_points()
        time.sleep(1)  # Wait 1 sec so ordering by created is checked.
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2)
        scores = ReviewerScore.get_recent(self.user)
        assert len(scores) == 2
        assert scores[0].score == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_UPDATE])
        assert scores[1].score == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])

    def test_get_leaderboards(self):
        user2 = UserProfile.objects.get(email='persona_reviewer@mozilla.com')
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        leaders = ReviewerScore.get_leaderboards(self.user)
        assert leaders['user_rank'] == 1
        assert leaders['leader_near'] == []
        assert leaders['leader_top'][0]['rank'] == 1
        assert leaders['leader_top'][0]['user_id'] == self.user.id
        assert leaders['leader_top'][0]['total'] == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL] +
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_UPDATE])
        assert leaders['leader_top'][1]['rank'] == 2
        assert leaders['leader_top'][1]['user_id'] == user2.id
        assert leaders['leader_top'][1]['total'] == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])

        self._give_points(
            user=user2, addon=amo.tests.addon_factory(type=amo.ADDON_PERSONA))
        leaders = ReviewerScore.get_leaderboards(
            self.user, addon_type=amo.ADDON_PERSONA)
        assert len(leaders['leader_top']) == 1
        assert leaders['leader_top'][0]['user_id'] == user2.id

    def test_only_active_reviewers_in_leaderboards(self):
        user2 = UserProfile.objects.create(username='former-reviewer')
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        leaders = ReviewerScore.get_leaderboards(self.user)
        assert leaders['user_rank'] == 1
        assert leaders['leader_near'] == []
        assert leaders['leader_top'][0]['user_id'] == self.user.id
        assert len(leaders['leader_top']) == 1  # Only the reviewer is here.
        assert user2.id not in [l['user_id'] for l in leaders['leader_top']], (
            'Unexpected non-reviewer user found in leaderboards.')

    def test_no_admins_or_staff_in_leaderboards(self):
        user2 = UserProfile.objects.get(email='admin@mozilla.com')
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        leaders = ReviewerScore.get_leaderboards(self.user)
        assert leaders['user_rank'] == 1
        assert leaders['leader_near'] == []
        assert leaders['leader_top'][0]['user_id'] == self.user.id
        assert len(leaders['leader_top']) == 1  # Only the reviewer is here.
        assert user2.id not in [l['user_id'] for l in leaders['leader_top']], (
            'Unexpected admin user found in leaderboards.')

    def test_get_leaderboards_last(self):
        users = []
        for i in range(6):
            user = UserProfile.objects.create(username='user-%s' % i)
            GroupUser.objects.create(group_id=50002, user=user)
            users.append(user)
        last_user = users.pop(len(users) - 1)
        for u in users:
            self._give_points(user=u)
        # Last user gets lower points by reviewing a persona.
        addon = self.addon
        addon.type = amo.ADDON_PERSONA
        self._give_points(user=last_user, addon=addon)
        leaders = ReviewerScore.get_leaderboards(last_user)
        assert leaders['user_rank'] == 6
        assert len(leaders['leader_top']) == 3
        assert len(leaders['leader_near']) == 2

    def test_leaderboard_score_when_in_multiple_reviewer_groups(self):
        group_reviewers = Group.objects.create(
            name='Reviewers: Addons', rules='Addons:Review')
        group_content_reviewers = Group.objects.create(
            name='Reviewers: Content', rules='Addons:ContentReview')
        GroupUser.objects.create(group=group_reviewers, user=self.user)
        GroupUser.objects.create(group=group_content_reviewers, user=self.user)

        AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            weight=101)
        ReviewerScore.award_points(
            self.user, self.addon, self.addon.status,
            version=self.addon.current_version,
            post_review=True, content_review=False)
        assert ReviewerScore._leaderboard_list() == [(
            self.user.id, self.user.name, amo.REVIEWED_SCORES[
                amo.REVIEWED_EXTENSION_MEDIUM_RISK])]

    def test_all_users_by_score(self):
        user2 = UserProfile.objects.create(
            username='otherreviewer', email='otherreviewer@mozilla.com')
        self.grant_permission(
            user2, 'Personas:Review', name='Reviewers: Themes')
        amo.REVIEWED_LEVELS[0]['points'] = 180
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        users = ReviewerScore.all_users_by_score()
        assert len(users) == 2
        # First user.
        assert users[0]['total'] == 200
        assert users[0]['user_id'] == self.user.id
        assert users[0]['level'] == amo.REVIEWED_LEVELS[0]['name']
        # Second user.
        assert users[1]['total'] == 120
        assert users[1]['user_id'] == user2.id
        assert users[1]['level'] == ''

    def test_caching(self):
        self._give_points()

        with self.assertNumQueries(1):
            ReviewerScore.get_total(self.user)
        with self.assertNumQueries(0):
            ReviewerScore.get_total(self.user)

        with self.assertNumQueries(1):
            ReviewerScore.get_recent(self.user)
        with self.assertNumQueries(0):
            ReviewerScore.get_recent(self.user)

        with self.assertNumQueries(2):
            ReviewerScore.get_leaderboards(self.user)
        with self.assertNumQueries(0):
            ReviewerScore.get_leaderboards(self.user)

        with self.assertNumQueries(1):
            ReviewerScore.get_breakdown(self.user)
        with self.assertNumQueries(0):
            ReviewerScore.get_breakdown(self.user)

        # New points invalidates all caches.
        self._give_points()

        with self.assertNumQueries(1):
            ReviewerScore.get_total(self.user)
        with self.assertNumQueries(1):
            ReviewerScore.get_recent(self.user)
        with self.assertNumQueries(2):
            ReviewerScore.get_leaderboards(self.user)
        with self.assertNumQueries(1):
            ReviewerScore.get_breakdown(self.user)

class TestRereviewQueueTheme(TestCase):

    def test_manager_soft_delete_addons(self):
        """Test manager excludes soft delete add-ons."""
        # Normal RQT object.
        RereviewQueueTheme.objects.create(
            theme=addon_factory(type=amo.ADDON_PERSONA).persona, header='')

        # Deleted add-on RQT object.
        addon = addon_factory(type=amo.ADDON_PERSONA)
        RereviewQueueTheme.objects.create(theme=addon.persona, header='')
        addon.delete()

        assert RereviewQueueTheme.objects.count() == 1
        assert RereviewQueueTheme.unfiltered.count() == 2

    def test_filter_for_many_to_many(self):
        # Check https://bugzilla.mozilla.org/show_bug.cgi?id=1142035.
        addon = addon_factory(type=amo.ADDON_PERSONA)
        rqt = RereviewQueueTheme.objects.create(theme=addon.persona)
        assert addon.persona.rereviewqueuetheme_set.get() == rqt

        # Delete the addon: it shouldn't be listed anymore.
        addon.update(status=amo.STATUS_DELETED)
        assert addon.persona.rereviewqueuetheme_set.all().count() == 0

class TestAutoApprovalSummary(TestCase):
    def setUp(self):
        self.addon = addon_factory(
            average_daily_users=666, version_kw={'version': '1.0'})
        AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            confirmed=True)
        self.current_file_validation = FileValidation.objects.create(
            file=self.addon.current_version.all_files[0], validation=u'{}')
        self.version = version_factory(
            addon=self.addon, version='1.1', file_kw={
                'status': amo.STATUS_AWAITING_REVIEW,
                'is_webextension': True})
        self.file = self.version.all_files[0]
        self.file_validation = FileValidation.objects.create(
            file=self.version.all_files[0], validation=u'{}')
        AddonApprovalsCounter.objects.create(addon=self.addon, counter=1)

    def test_negative_weight(self):
        summary = AutoApprovalSummary.objects.create(
            version=self.version, weight=-300)
        summary = AutoApprovalSummary.objects.get(pk=summary.pk)
        assert summary.weight == -300

    def test_calculate_weight(self):
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        expected_result = {
            'abuse_reports': 0,
            'admin_code_review': 0,
            'average_daily_users': 0,
            'negative_ratings': 0,
            'reputation': 0,
            'past_rejection_history': 0,
            'uses_custom_csp': 0,
            'uses_eval_or_document_write': 0,
            'uses_implied_eval': 0,
            'uses_innerhtml': 0,
            'uses_native_messaging': 0,
            'size_of_code_changes': 0,
            'uses_remote_scripts': 0,
            'uses_unknown_minified_code': 0,
            'violates_mozilla_conditions': 0,
        }
        assert weight_info == expected_result

    def test_calculate_weight_admin_code_review(self):
        AddonReviewerFlags.objects.create(
            addon=self.addon, needs_admin_code_review=True)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['admin_code_review'] == 100

    def test_calculate_weight_abuse_reports(self):
        # Extra abuse report for a different add-on, does not count.
        AbuseReport.objects.create(addon=addon_factory())

        # Extra abuse report for a different user, does not count.
        AbuseReport.objects.create(user=user_factory())

        # Extra old abuse report, does not count either.
        old_report = AbuseReport.objects.create(addon=self.addon)
        old_report.update(created=self.days_ago(43))

        # Recent abuse reports.
        AbuseReport.objects.create(addon=self.addon)
        recent_report = AbuseReport.objects.create(addon=self.addon)
        recent_report.update(created=self.days_ago(41))

        # Recent abuse report for one of the developers of the add-on.
        author = user_factory()
        AddonUser.objects.create(addon=self.addon, user=author)
        AbuseReport.objects.create(user=author)

        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 45
        assert weight_info['abuse_reports'] == 45

        # Should be capped at 100. We're already at 45, adding 4 more should
        # result in a weight of 100 instead of 105.
        for i in range(0, 4):
            AbuseReport.objects.create(addon=self.addon)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['abuse_reports'] == 100

    def test_calculate_weight_abuse_reports_use_created_from_instance(self):
        # Create an abuse report 60 days in the past. It should be ignored it
        # we were calculating from today, but use an AutoApprovalSummary
        # instance that is 20 days old, making the abuse report count.
        report = AbuseReport.objects.create(addon=self.addon)
        report.update(created=self.days_ago(60))

        summary = AutoApprovalSummary.objects.create(version=self.version)
        summary.update(created=self.days_ago(20))

        weight_info = summary.calculate_weight()
        assert summary.weight == 15
        assert weight_info['abuse_reports'] == 15

    def test_calculate_weight_negative_ratings(self):
        # Positive rating, does not count.
        Rating.objects.create(
            user=user_factory(), addon=self.addon, version=self.version,
            rating=5)

        # Negative rating, but too old, does not count.
        old_rating = Rating.objects.create(
            user=user_factory(), addon=self.addon, version=self.version,
            rating=1)
        old_rating.update(created=self.days_ago(370))

        # Negative review on a different add-on, does not count either.
        extra_addon = addon_factory()
        Rating.objects.create(
            user=user_factory(), addon=extra_addon,
            version=extra_addon.current_version, rating=1)

        # Recent negative ratings.
        ratings = [Rating(
            user=user_factory(), addon=self.addon,
            version=self.version, rating=3) for i in range(0, 49)]
        Rating.objects.bulk_create(ratings)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0  # Not enough negative ratings yet...
        assert weight_info['negative_ratings'] == 0

        # Create one more to get to weight == 1.
        Rating.objects.create(
            user=user_factory(), addon=self.addon, version=self.version,
            rating=2)
        weight_info = summary.calculate_weight()
        assert summary.weight == 1
        assert weight_info['negative_ratings'] == 1

        # Create 5000 more (sorry!) to make sure it's capped at 100.
        ratings = [Rating(
            user=user_factory(), addon=self.addon,
            version=self.version, rating=3) for i in range(0, 5000)]
        Rating.objects.bulk_create(ratings)

        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['negative_ratings'] == 100

    def test_calculate_weight_reputation(self):
        summary = AutoApprovalSummary(version=self.version)
        self.addon.update(reputation=0)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['reputation'] == 0

        self.addon.update(reputation=3)
        weight_info = summary.calculate_weight()
        assert summary.weight == -300
        assert weight_info['reputation'] == -300

        self.addon.update(reputation=1000)
        weight_info = summary.calculate_weight()
        assert summary.weight == -300
        assert weight_info['reputation'] == -300

        self.addon.update(reputation=-1000)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['reputation'] == 0

    def test_calculate_weight_average_daily_users(self):
        self.addon.update(average_daily_users=142444)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 14
        assert weight_info['average_daily_users'] == 14

        self.addon.update(average_daily_users=1756567658)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['average_daily_users'] == 100

    def test_calculate_weight_past_rejection_history(self):
        # Old rejected version, does not count.
        version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(370),
                     'status': amo.STATUS_DISABLED})

        # Version disabled by the developer, not Mozilla (original_status
        # is set to something different than STATUS_NULL), does not count.
        version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(15),
                     'status': amo.STATUS_DISABLED,
                     'original_status': amo.STATUS_PUBLIC})

        # Rejected version.
        version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(14),
                     'status': amo.STATUS_DISABLED})

        # Another rejected version, with multiple files. Only counts once.
        version_with_multiple_files = version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(13),
                     'status': amo.STATUS_DISABLED,
                     'platform': amo.PLATFORM_WIN.id})
        file_factory(
            reviewed=self.days_ago(13),
            version=version_with_multiple_files,
            status=amo.STATUS_DISABLED,
            platform=amo.PLATFORM_MAC.id)

        # Rejected version on a different add-on, does not count.
        version_factory(
            addon=addon_factory(),
            file_kw={'reviewed': self.days_ago(12),
                     'status': amo.STATUS_DISABLED})

        # Approved version, does not count.
        new_approved_version = version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(11)})
        FileValidation.objects.create(
            file=new_approved_version.all_files[0], validation=u'{}')

        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 20
        assert weight_info['past_rejection_history'] == 20

        # Should be capped at 100.
        for i in range(0, 10):
            version_factory(
                addon=self.addon,
                file_kw={'reviewed': self.days_ago(10),
                         'status': amo.STATUS_DISABLED})

        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['past_rejection_history'] == 100

    def test_calculate_weight_uses_eval_or_document_write(self):
        validation_data = {
            'messages': [{
                'id': ['DANGEROUS_EVAL'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 50
        assert weight_info['uses_eval_or_document_write'] == 50

        validation_data = {
            'messages': [{
                'id': ['NO_DOCUMENT_WRITE'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 50
        assert weight_info['uses_eval_or_document_write'] == 50

        # Still only 20 if both appear.
        validation_data = {
            'messages': [{
                'id': ['DANGEROUS_EVAL'],
            }, {
                'id': ['NO_DOCUMENT_WRITE'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 50
        assert weight_info['uses_eval_or_document_write'] == 50

    def test_calculate_weight_uses_implied_eval(self):
        validation_data = {
            'messages': [{
                'id': ['NO_IMPLIED_EVAL'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 5
        assert weight_info['uses_implied_eval'] == 5

    def test_calculate_weight_uses_innerhtml(self):
        validation_data = {
            'messages': [{
                'id': ['UNSAFE_VAR_ASSIGNMENT'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 50
        assert weight_info['uses_innerhtml'] == 50

    def test_calculate_weight_uses_innerhtml_multiple_times(self):
        validation_data = {
            'messages': [{
                'id': ['UNSAFE_VAR_ASSIGNMENT'],
            }, {
                'id': ['IGNORE_ME'],
            }, {
                'id': ['UNSAFE_VAR_ASSIGNMENT'],
            }, {
                'id': ['UNSAFE_VAR_ASSIGNMENT'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        # 50 base, + 10 per additional instance.
        assert summary.weight == 70
        assert weight_info['uses_innerhtml'] == 70

    def test_calculate_weight_uses_custom_csp(self):
        validation_data = {
            'messages': [{
                'id': ['MANIFEST_CSP'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 90
        assert weight_info['uses_custom_csp'] == 90

    def test_calculate_weight_uses_native_messaging(self):
        WebextPermission.objects.create(
            file=self.file, permissions=['nativeMessaging'])

        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['uses_native_messaging'] == 100

    def test_calculate_weight_uses_remote_scripts(self):
        validation_data = {
            'messages': [{
                'id': ['REMOTE_SCRIPT'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['uses_remote_scripts'] == 100

    def test_calculate_weight_violates_mozilla_conditions_of_use(self):
        validation_data = {
            'messages': [{
                'id': ['MOZILLA_COND_OF_USE'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 20
        assert weight_info['violates_mozilla_conditions'] == 20

    def test_calculate_weight_uses_unknown_minified_code_nothing(self):
        validation_data = {
            'metadata': {
                'unknownMinifiedFiles': []  # Empty list: no weight.
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['uses_unknown_minified_code'] == 0

        validation_data = {
            'metadata': {
                # Missing property: no weight.
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['uses_unknown_minified_code'] == 0

        validation_data = {
            # Missing metadata: no weight.
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['uses_unknown_minified_code'] == 0

    def test_calculate_weight_uses_unknown_minified_code(self):
        validation_data = {
            'metadata': {
                'unknownMinifiedFiles': ['something']
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['uses_unknown_minified_code'] == 100

    def test_calculate_weight_uses_unknown_minified_code_multiple_times(self):
        validation_data = {
            'metadata': {
                'unknownMinifiedFiles': ['something', 'foobar', 'another']
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        # 100 base, + 20 per additional instance.
        assert summary.weight == 120
        assert weight_info['uses_unknown_minified_code'] == 120

    def test_calculate_size_of_code_changes_no_current_validation(self):
        # Delete the validation for the previously confirmed version and reload
        # the version we're testing (otherwise the file validation has already
        # been loaded and is still attached to the instance...)
        self.current_file_validation.delete()
        self.version = Version.objects.get(pk=self.version.pk)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 500
        assert weight_info['no_validation_result'] == 500

    def test_calculate_size_of_code_changes_no_new_validation(self):
        # Delete the validation for the new version and reload that version.
        # (otherwise the file validation has already been loaded and is still
        # attached to the instance...)
        self.file_validation.delete()
        self.version = Version.objects.get(pk=self.version.pk)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 500
        assert weight_info['no_validation_result'] == 500

    def test_calculate_size_of_code_changes_no_reported_size(self):
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.calculate_size_of_code_changes() == 0
        assert summary.weight == 0
        assert weight_info['size_of_code_changes'] == 0

    def test_calculate_size_of_code_changes_no_previous_version_size(self):
        validation_data = {
            'metadata': {
                'totalScannedFileSize': 15000,
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        assert summary.calculate_size_of_code_changes() == 15000
        weight_info = summary.calculate_weight()
        assert summary.weight == 3
        assert weight_info['size_of_code_changes'] == 3

    def test_calculate_size_of_code_changes(self):
        old_validation_data = {
            'metadata': {
                'totalScannedFileSize': 5000,
            }
        }
        self.current_file_validation.update(
            validation=json.dumps(old_validation_data))
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 15000,
            }
        }
        self.file_validation.update(
            validation=json.dumps(new_validation_data))
        summary = AutoApprovalSummary(version=self.version)
        assert summary.calculate_size_of_code_changes() == 10000
        weight_info = summary.calculate_weight()
        assert summary.weight == 2
        assert weight_info['size_of_code_changes'] == 2

    def test_calculate_size_of_code_change_use_previously_confirmed(self):
        old_validation_data = {
            'metadata': {
                'totalScannedFileSize': 5000,
            }
        }
        self.current_file_validation.update(
            validation=json.dumps(old_validation_data))
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 15000,
            }
        }
        self.file_validation.update(
            validation=json.dumps(new_validation_data))

        # Add a new current_version, unconfirmed. This version will be ignored
        # for the comparison as all we care about is the previous confirmed
        # version.
        self.addon.current_version.update(created=self.days_ago(2))
        new_version = version_factory(addon=self.addon)
        self.addon.reload()
        assert self.addon.current_version == new_version
        AutoApprovalSummary.objects.create(
            version=new_version, verdict=amo.AUTO_APPROVED)
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 14999,
            }
        }
        FileValidation.objects.create(
            file=new_version.all_files[0],
            validation=json.dumps(new_validation_data))

        summary = AutoApprovalSummary(version=self.version)
        # Size of code changes should be 10000 and not 1, proving that it
        # compared with the old, confirmed version.
        assert summary.calculate_size_of_code_changes() == 10000
        weight_info = summary.calculate_weight()
        assert summary.weight == 2
        assert weight_info['size_of_code_changes'] == 2

    def test_calculate_size_of_code_changes_no_negative(self):
        old_validation_data = {
            'metadata': {
                'totalScannedFileSize': 20000,
            }
        }
        self.current_file_validation.update(
            validation=json.dumps(old_validation_data))
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 5000,
            }
        }
        self.file_validation.update(
            validation=json.dumps(new_validation_data))
        summary = AutoApprovalSummary(version=self.version)
        assert summary.calculate_size_of_code_changes() == 15000
        weight_info = summary.calculate_weight()
        assert summary.weight == 3
        assert weight_info['size_of_code_changes'] == 3

    def test_calculate_size_of_code_changes_max(self):
        old_validation_data = {
            'metadata': {
                'totalScannedFileSize': 50000000,
            }
        }
        self.current_file_validation.update(
            validation=json.dumps(old_validation_data))
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 0,
            }
        }
        self.file_validation.update(
            validation=json.dumps(new_validation_data))
        summary = AutoApprovalSummary(version=self.version)
        assert summary.calculate_size_of_code_changes() == 50000000
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['size_of_code_changes'] == 100

    def test_calculate_weight_sum(self):
        validation_data = {
            'messages': [
                {'id': ['MANIFEST_CSP']},
                {'id': ['UNSAFE_VAR_ASSIGNMENT']},
                {'id': ['NO_IMPLIED_EVAL']},
                {'id': ['DANGEROUS_EVAL']},
                {'id': ['UNSAFE_VAR_ASSIGNMENT']},  # Another one.
                {'id': ['NOTHING_TO_SEE_HERE_MOVE_ON']},
            ]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 205
        expected_result = {
            'abuse_reports': 0,
            'admin_code_review': 0,
            'average_daily_users': 0,
            'negative_ratings': 0,
            'reputation': 0,
            'past_rejection_history': 0,
            'uses_custom_csp': 90,
            'uses_eval_or_document_write': 50,
            'uses_implied_eval': 5,
            'uses_innerhtml': 60,  # There is one extra.
            'uses_native_messaging': 0,
            'size_of_code_changes': 0,
            'uses_remote_scripts': 0,
            'uses_unknown_minified_code': 0,
            'violates_mozilla_conditions': 0,
        }
        assert weight_info == expected_result

    def test_count_uses_custom_csp(self):
        assert AutoApprovalSummary.count_uses_custom_csp(self.version) == 0

        validation_data = {
            'messages': [{
                'id': ['MANIFEST_CSP'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        assert AutoApprovalSummary.count_uses_custom_csp(self.version) == 1

    def test_count_uses_custom_csp_file_validation_missing(self):
        self.file_validation.delete()
        del self.version.all_files
        with self.assertRaises(AutoApprovalNoValidationResultError):
            AutoApprovalSummary.count_uses_custom_csp(self.version)

        # Also happens if only one file is missing validation info.
        self.file_validation = FileValidation.objects.create(
            file=self.version.all_files[0], validation=u'{}')
        del self.version.all_files
        file_factory(version=self.version, status=amo.STATUS_AWAITING_REVIEW)
        with self.assertRaises(AutoApprovalNoValidationResultError):
            AutoApprovalSummary.count_uses_custom_csp(self.version)

    def test_check_uses_native_messaging(self):
        assert (
            AutoApprovalSummary.check_uses_native_messaging(self.version) == 0)

        webext_permissions = WebextPermission.objects.create(
            file=self.file, permissions=['foobar'])
        del self.file.webext_permissions_list
        assert (
            AutoApprovalSummary.check_uses_native_messaging(self.version) == 0)

        webext_permissions.update(permissions=['nativeMessaging', 'foobar'])
        del self.file.webext_permissions_list
        assert (
            AutoApprovalSummary.check_uses_native_messaging(self.version) == 1)

    def test_check_has_auto_approval_disabled(self):
        assert AutoApprovalSummary.check_has_auto_approval_disabled(
            self.version) == 0

        flags = AddonReviewerFlags.objects.create(addon=self.addon)
        assert AutoApprovalSummary.check_has_auto_approval_disabled(
            self.version) == 0

        flags.update(auto_approval_disabled=True)
        assert AutoApprovalSummary.check_has_auto_approval_disabled(
            self.version) is True

    def test_check_is_locked(self):
        assert AutoApprovalSummary.check_is_locked(self.version) is False

        set_reviewing_cache(self.version.addon.pk, settings.TASK_USER_ID)
        assert AutoApprovalSummary.check_is_locked(self.version) is False

        set_reviewing_cache(self.version.addon.pk, settings.TASK_USER_ID + 42)
        assert AutoApprovalSummary.check_is_locked(self.version) is True

    @mock.patch.object(AutoApprovalSummary, 'calculate_weight', spec=True)
    @mock.patch.object(AutoApprovalSummary, 'calculate_verdict', spec=True)
    def test_create_summary_for_version(
            self, calculate_verdict_mock, calculate_weight_mock):
        calculate_verdict_mock.return_value = {'dummy_verdict': True}
        summary, info = AutoApprovalSummary.create_summary_for_version(
            self.version,)
        assert calculate_weight_mock.call_count == 1
        assert calculate_verdict_mock.call_count == 1
        assert calculate_verdict_mock.call_args == ({
            'dry_run': False,
        },)
        assert summary.pk
        assert summary.version == self.version
        assert info == {'dummy_verdict': True}

    @mock.patch.object(AutoApprovalSummary, 'calculate_verdict', spec=True)
    def test_create_summary_no_previously_approved_versions(
            self, calculate_verdict_mock):
        AddonApprovalsCounter.objects.all().delete()
        self.version.reload()
        calculate_verdict_mock.return_value = {'dummy_verdict': True}
        summary, info = AutoApprovalSummary.create_summary_for_version(
            self.version)
        assert summary.pk
        assert info == {'dummy_verdict': True}

    def test_create_summary_already_existing(self):
        # Create a dummy summary manually, then call the method to create a
        # real one. It should have just updated the previous instance.
        summary = AutoApprovalSummary.objects.create(
            version=self.version, is_locked=True)
        assert summary.pk
        assert summary.version == self.version
        assert summary.verdict == amo.NOT_AUTO_APPROVED

        previous_summary_pk = summary.pk

        summary, info = AutoApprovalSummary.create_summary_for_version(
            self.version)

        assert summary.pk == previous_summary_pk
        assert summary.version == self.version
        assert summary.is_locked is False
        assert summary.verdict == amo.AUTO_APPROVED
        assert info == {
            'has_auto_approval_disabled': False,
            'is_locked': False,
        }

    def test_create_summary_no_files(self):
        self.file.delete()
        del self.version.all_files
        with self.assertRaises(AutoApprovalNotEnoughFilesError):
            AutoApprovalSummary.create_summary_for_version(self.version)

    def test_calculate_verdict_failure_dry_run(self):
        summary = AutoApprovalSummary.objects.create(
            version=self.version, is_locked=True)
        info = summary.calculate_verdict(dry_run=True)
        assert info == {
            'is_locked': True,
            'has_auto_approval_disabled': False,
        }
        assert summary.verdict == amo.WOULD_NOT_HAVE_BEEN_AUTO_APPROVED

    def test_calculate_verdict_failure(self):
        summary = AutoApprovalSummary.objects.create(
            version=self.version, is_locked=True)
        info = summary.calculate_verdict()
        assert info == {
            'is_locked': True,
            'has_auto_approval_disabled': False,
        }
        assert summary.verdict == amo.NOT_AUTO_APPROVED

    def test_calculate_verdict_success(self):
        summary = AutoApprovalSummary.objects.create(version=self.version)
        info = summary.calculate_verdict()
        assert info == {
            'is_locked': False,
            'has_auto_approval_disabled': False,
        }
        assert summary.verdict == amo.AUTO_APPROVED

    def test_calculate_verdict_success_dry_run(self):
        summary = AutoApprovalSummary.objects.create(version=self.version)
        info = summary.calculate_verdict(dry_run=True)
        assert info == {
            'is_locked': False,
            'has_auto_approval_disabled': False,
        }
        assert summary.verdict == amo.WOULD_HAVE_BEEN_AUTO_APPROVED

    def test_calculate_verdict_has_auto_approval_disabled(self):
        summary = AutoApprovalSummary.objects.create(
            version=self.version, has_auto_approval_disabled=True)
        info = summary.calculate_verdict()
        assert info == {
            'is_locked': False,
            'has_auto_approval_disabled': True,
        }
        assert summary.verdict == amo.NOT_AUTO_APPROVED

    def test_verdict_info_prettifier(self):
        verdict_info = {
            'is_locked': True,
            'has_auto_approval_disabled': True,
        }
        result = list(
            AutoApprovalSummary.verdict_info_prettifier(verdict_info))
        assert result == [
            u'Has auto-approval disabled flag set.',
            u'Is locked by a reviewer.',
        ]

        result = list(AutoApprovalSummary.verdict_info_prettifier({}))
        assert result == []


# Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

# Integers in each row are sorted from left to right.
# The first integer of each row is greater than the last integer of the previous row.

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # check input
        if not matrix:
            return False

        # init
        n_col = len(matrix[0])
        n_row = len(matrix)
        start, end = 0, n_row -1

        # main loop (binary search)
        while start + 1 < end:
            mid = (start + end) // 2
            v_mid = matrix[mid][0]
            if v_mid < target:
                start = mid
            elif v_mid > target:
                end = mid
            else:
                return True
                #end = mid

        # evaluate the results
        idx_search = 0
        if target < matrix[start][0]: # too small
            return False
        elif target == matrix[start][0]:
            return True
        elif target > matrix[start][0] and target < matrix[end][0]:
            idx_search = start
        elif target == matrix[end][0]:
            return True
        elif target > matrix[end][0] and target <= matrix[end][n_col-1]:
            idx_search = end
        else:
            return False

        # second round of search
        start, end = 0, n_col - 1

        # main loop (binary search)
        nums = matrix[idx_search]
        while start + 1 < end:
            mid = (start + end) // 2
            v_mid = nums[mid]
            if v_mid < target:
                start = mid
            elif v_mid > target:
                end = mid
            else:
                return True
                #end = mid
        # evaluate
        if target == nums[start]: # too small
            return True
        if target == nums[end]:
            return True
        return False

class Solution:
    # @param matrix, a list of lists of integers
    # @param target, an integer
    # @return a boolean
    # 8:21
    def searchMatrix(self, matrix, target):
        if not matrix or target is None:
            return False

        rows, cols = len(matrix), len(matrix[0])
        low, high = 0, rows * cols - 1

        while low <= high:
            mid = (low + high) / 2
            num = matrix[mid / cols][mid % cols]

            if num == target:
                return True
            elif num < target:
                low = mid + 1
            else:
                high = mid - 1

        return False


import requests
from pprint import pprint
from tqdm import tqdm

class FotosYD:
    def __init__(self, token_YD):
        self.token = token_YD

    def get_headers(self):
        return {
            "Content-Type": "application/json",
            "Authorization": "OAuth {}".format(self.token),
        }

    def get_upload_dir(self):
        upload_url = "https://cloud-api.yandex.net/v1/disk/resources/"
        headers = self.get_headers()
        params = {"path": "PYTHON2"}
        return requests.put(url=upload_url, headers=headers, params=params)

    def upload_file_to_disk(self, VK_fotos_dict):
        yandex_upload_url = "https://cloud-api.yandex.net/v1/disk/resources/upload/"
        headers = self.get_headers()

        for foto_name, i in zip(VK_fotos_dict.keys(), tqdm(range(len(VK_fotos_dict)))):
            params_upload = {
                "path": f"PYTHON2/{foto_name}",
                "url": VK_fotos_dict[foto_name],
            }
            response = requests.post(url=yandex_upload_url, params=params_upload, headers=headers)
            if response.status_code == 201:
                print("Success")
            else:
                pprint(response.status_code)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Jun  6 14:01:34 2019

@author: thibault
Just Model
Architecture of unet
"""

import numpy as np

from keras.layers.convolutional import Conv2D, Conv2DTranspose
from keras.layers.pooling import MaxPooling2D
from keras.layers.merge import concatenate
from keras.models import Model
from keras.layers import Input, BatchNormalization, Activation
from keras.layers.core import Dropout, Lambda
import keras.backend as K

def conv_bn_relu(s, features, kernel):

    c = Conv2D(features, kernel, kernel_initializer='he_normal', padding='same')(s)
    c = BatchNormalization(axis=3)(c)
    c = Activation('relu')(c)
    return c

def UnAxSeg(img_size, dropout = True):
    inputs = Input((img_size, img_size, 1))
    s = Lambda(lambda x: x / 255)(inputs)
    s = Lambda(lambda x: (x-K.mean(x)) /K.std(x))(s)

    c1 = conv_bn_relu (s, 16, (3,3))
    if dropout: c1 = Dropout(0.2)(c1)
    c1 = conv_bn_relu (c1, 16, (3,3))
    p1 = Conv2D(16, (2,2), strides = (2,2), kernel_initializer='he_normal')(c1)

    c2 = conv_bn_relu (p1, 32, (3,3))
    if dropout: c2 = Dropout(0.3)(c2)
    c2 = conv_bn_relu (c2, 32, (3,3))
    p2 = Conv2D(32, (2,2), strides =(2,2), kernel_initializer='he_normal')(c2)

    c3 = conv_bn_relu (p2, 64, (3,3))
    if dropout: c3 = Dropout(0.4)(c3)
    c3 = conv_bn_relu (c3, 64, (3,3))
    p3 = Conv2D(64, (2,2), strides =(2,2), kernel_initializer='he_normal')(c3)

    c4 = conv_bn_relu (p3, 128, (3,3))
    if dropout: c4 = Dropout(0.5)(c4)
    c4 = conv_bn_relu (c4, 128, (3,3))
    p4 = Conv2D(128, (2,2), strides =(2,2), kernel_initializer='he_normal')(c4)

    c5 = conv_bn_relu (p4, 256, (3,3))
    if dropout: c5 = Dropout(0.6)(c5)
    c5 = conv_bn_relu (c5, 256, (3,3))

    u6 = Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(c5)
    u6 = concatenate([u6, c4])
    c6 = conv_bn_relu (u6, 128, (3,3))
    if dropout: c6 = Dropout(0.5)(c6)
    c6 = conv_bn_relu (c6, 128, (3,3))

    u7 = Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(c6)
    u7 = concatenate([u7, c3])
    c7 = conv_bn_relu (u7, 64, (3,3))
    if dropout: c7 = Dropout(0.4)(c7)
    c7 = conv_bn_relu (c7, 64, (3,3))

    u8 = Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same')(c7)
    u8 = concatenate([u8, c2])
    c8 = conv_bn_relu (u8, 32, (3,3))
    if dropout: c8 = Dropout(0.3)(c8)
    c8 = conv_bn_relu (c8, 32, (3,3))

    u9 = Conv2DTranspose(16, (2, 2), strides=(2, 2), padding='same')(c8)
    u9 = concatenate([u9, c1], axis=3)
    c9 = conv_bn_relu (u9, 16, (3,3))
    if dropout: c9 = Dropout(0.2)(c9)
    c9 = conv_bn_relu (c9, 16, (3,3))

    outputs = Conv2D(3, (1, 1), activation='softmax')(c9)

    model = Model(inputs=[inputs], outputs=[outputs])

    return model


from setuptools import setup

def get_version(filename):
    """
    Parse the value of the __version__ var from a Python source file
    without running/importing the file.
    """
    import re
    version_pattern = r"^ *__version__ *= *['\"](\d+\.\d+\.\d+)['\"] *$"
    match = re.search(version_pattern, open(filename).read(), re.MULTILINE)

    assert match, ("No version found in file: {!r} matching pattern: {!r}"
                   .format(filename, version_pattern))

    return match.group(1)

setup(
    name="jsonlogging",
    description="jsonlogging provides structured log output from the "
                "logging module in JSON format",
    author="Hal Blackburn",
    author_email="hwtb2@cam.ac.uk",
    url="https://github.com/ucamhal/ravenpy",
    version=get_version("jsonlogging/__init__.py"),
    packages=["jsonlogging"],
    license="BSD",
    classifiers=[
        "Development Status :: 5 - Production/Stable",
        "Intended Audience :: Developers",
        "Intended Audience :: System Administrators",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
        "Programming Language :: Python :: 2.6",
        "Programming Language :: Python :: 2.7",
        "Programming Language :: Python",
        "Topic :: Software Development",
        "Topic :: System :: Logging"
    ],
    long_description=open("README.md").read(),
    test_suite="jsonlogging.tests.test_all",
    tests_require="mock >= 1.0.0, < 2.0.0"
)


#!/usr/bin/env python

# Comparisons:
# Equal: ==
# Not Equal: !=
# Greater Than: >
# Less Than: <
# Greater or Equal: >=
# Less or Equal: <=
# Object Identity: is

# and
# or
# not

user = 'Admin'
logged_in = False

if not logged_in:
    print('Please Log In')
else:
    print('Welcome')


""" XVM (c) https://modxvm.com 2013-2018 """

def get_url(url, callback):
    _fileCache.get_url(url, callback)

def save(name, bytes):
    _fileCache.save(name, bytes)

# PRIVATE

import os
import shutil
import traceback

import game
from account_helpers import CustomFilesCache

from logger import *

class _FileCache():
    def __init__(self):
        try:
            self.cache_dir = 'res_mods/mods/shared_resources/xvm/cache'
            self.clean()
            self.customFilesCache = CustomFilesCache.CustomFilesCache('xvm/custom_data')
        except Exception:
            err(traceback.format_exc())

    def fin(self):
        self.customFilesCache.close()
        self.customFilesCache = None
        self.clean()

    def clean(self):
        if os.path.isdir(self.cache_dir):
            shutil.rmtree(self.cache_dir)

    def get_url(self, url, callback):
        try:
            if self.customFilesCache:
                self.customFilesCache.get(url, callback)
        except Exception:
            err('filecache.get_url: ' + str(url))
            err(traceback.format_exc())

    def save(self, name, bytes):
        try:
            path = os.path.join(self.cache_dir, name)
            dirname = os.path.dirname(path)
            if not os.path.isdir(dirname):
                os.makedirs(dirname)
            with open(path, 'wb') as f:
                f.write(bytes)
        except Exception:
            err('filecache.save: ' + str(name))
            err(traceback.format_exc())

_fileCache = _FileCache()

@registerEvent(game, 'fini')
def fini():
    _fileCache.fin()


import os
import shutil

from invoke import Collection, task

default_venv_python = "python3"
precommit_config_basename = ".pre-commit-config.yaml"
ignore_revs_basename = ".git-blame-ignore-revs"

# setting up some directory paths
this_dir = os.path.dirname(os.path.abspath(__file__))
venv_dir = os.path.join(this_dir, "venv")
venv_bin = os.path.join(venv_dir, "bin")
venv_python = os.path.join(venv_bin, "python")

def _ask_overwrite_y_or_n():
    """Return True for 'yes' and False for 'no'"""
    result = input("Overwrite? [y/n]: ").casefold()
    while result not in ("y", "n"):
        print("Please enter 'y' or 'n'")
        result = input("Overwrite? [y/n]: ").casefold()
    if result == "n":
        return False
    assert result == "y"
    return True

def _validate_venv(c):
    if not os.path.isfile(venv_python):
        raise EnvironmentError("bootstrap virtual environment is not setup")

@task(
    help={
        "python": (
            "the python executable to create the venv from "
            + f"(default: {default_venv_python})"
        ),
        "yes": "answer yes to any intermediate steps",
    },
)
def create_venv(c, python=default_venv_python, yes=False):
    """Build the main python virtual environment from scratch."""
    if os.path.exists(venv_dir):
        print(f"venv directory exists: '{venv_dir}'")
        if not yes:
            if not _ask_overwrite_y_or_n():
                exit()
        print(f"deleting directory '{venv_dir}'")
        shutil.rmtree(venv_dir)
    rc = c.run(f"{python} -m venv {venv_dir}")
    assert rc.ok
    print(f"venv created at '{venv_dir}'")
    rc = c.run(f"{venv_python} -m pip install -U pip setuptools wheel", echo=True)
    assert rc.ok
    rc = c.run(
        f"{venv_python} -m pip install -U pre-commit pip-tools",
        echo=True,
    )

@task
def install_git_hooks(c):
    """Install project-local git pre-commit hooks and blame configurations."""
    _validate_venv(c)
    # install the pre-commit hooks if there is a config file
    if os.path.exists(os.path.join(this_dir, precommit_config_basename)):
        with c.cd(this_dir):
            rc = c.run(f"{venv_python} -m pre_commit install", echo=True)
            assert rc.ok
    # install the --ignore-rev defaults if there is a file tracking them
    if os.path.exists(os.path.join(this_dir, ignore_revs_basename)):
        with c.cd(this_dir):
            rc = c.run(
                f"git config --local blame.ignoreRevsFile {ignore_revs_basename}",
                echo=True,
            )
            assert rc.ok

@task
def install_requirements(c):
    """Install project requirements into the virtual environment."""
    _validate_venv(c)
    rc = c.run(f"{venv_python} -m pip install -r requirements.txt", echo=True)
    assert rc.ok

@task(
    help={
        "python": (
            "the python executable to create the venv from "
            + f"(default: {default_venv_python})"
        ),
        "yes": "answer yes to any intermediate steps",
    },
)
def bootstrap_default(c, python=default_venv_python, yes=False):
    """Bootstrap the development environment (runs all sub-commands)."""
    create_venv(c, python=python, yes=yes)
    install_git_hooks(c)
    install_requirements(c)
    print("\n\nBootstrap complete. Activate your virtual environment with:")
    print(f"    source {venv_bin}/activate\n\n")

@task
def upgrade_pre_commit(c):
    """Upgrade and reinstall the git pre-commit hooks."""
    _validate_venv(c)
    rc = c.run(f"{venv_python} -m pip install -U pre-commit", echo=True)
    assert rc.ok
    with c.cd(this_dir):
        if os.path.exists(os.path.join(this_dir, precommit_config_basename)):
            rc = c.run(f"{venv_python} -m pre_commit autoupdate", echo=True)
    install_git_hooks(c)

@task(
    help={
        "yes": "answer yes to any intermediate steps",
    },
)
def install(c, yes=False):
    """Install systemd and cron files and restart services."""
    _validate_venv(c)
    system_files_dir = os.path.join(this_dir, "system")
    for basename, dst_dir in [
        ("home-energy-dte-daily", "/etc/cron.d"),
        ("energy_bridge-influxdb.service", "/etc/systemd/system"),
    ]:
        src = os.path.join(system_files_dir, basename)
        dst = os.path.join(dst_dir, basename)
        if os.path.exists(dst):
            print(f"destination exists: '{dst}'")
            if not yes:
                if not _ask_overwrite_y_or_n():
                    exit()
        c.run(f"sudo cp {src} {dst}", echo=True)
    # restart service
    c.run(
        "sudo systemctl daemon-reload",
        echo=True,
    )
    c.run(
        "sudo systemctl restart energy_bridge-influxdb.service",
        echo=True,
    )

ns = Collection()
bootstrap = Collection("bootstrap")
bootstrap.add_task(bootstrap_default, name="_default_", default=True)
bootstrap.add_task(create_venv)
bootstrap.add_task(install_git_hooks)
bootstrap.add_task(install_requirements)
ns.add_collection(bootstrap)
dev = Collection("dev")
dev.add_task(upgrade_pre_commit)
ns.add_collection(dev)
ns.add_task(install)


import glob
import os
import time
import traceback
import win32api
import win32gui
from datetime import datetime
from threading import Thread

from ScreenTools import findAndClick, findPic, try_find_and_click, click_when_second_image_not_exist, any_pic_exist, \
    sleep, log, click_when_second_image_exist

is_logout = False

def do_chain_operations(image_name1=r"fuben.bmp", image_name2=None, image_name3=None,
                        image_name4=None, image_name5=None, try_times=3, start_y=None, end_y=None, sleep_time=0.3,
                        match_num=0.75):
    ret = findAndClick(imageName=image_name1, match_num=match_num, start_y=start_y, end_y=end_y)
    if ret < 1:
        return 0
    if image_name2 is not None:
        sleep(sleep_time)
        try_find_and_click(image_name2, match_num=0.7)
    if image_name3 is not None:
        sleep(sleep_time)
        try_find_and_click(image_name3, match_num=0.7)
    if image_name4 is not None:
        sleep(sleep_time)
        try_find_and_click(image_name4, match_num=0.7)
    if image_name5 is not None:
        sleep(sleep_time)
        try_find_and_click(image_name=image_name5, try_times=try_times, start_y=start_y, end_y=end_y, match_num=0.7)

    return ret

def pic_is_exists(image_name=None):
    max_loc, max_val, theight, twidth = any_pic_exist(image_name = image_name)
    return max_val

def in_counterpart():
    exist = pic_is_exists(image_name=r"tuichu.bmp|tuichu1.bmp") > 0.75
    must_not_exist = pic_is_exists(image_name=r"sure.bmp|sure1.bmp|home_tips.png") < 0.75
    return exist and must_not_exist

def async_do_assist():
    while True:
        do_assist()

def main_method():
    global is_logout
    pre_do = datetime(2019, 10, 1)
    pre_go_home_time = datetime(2019, 10, 1)
    i = 10000000

    while i > 0:
        try:
            pre_do_time = datetime.now()
            minute = pre_do_time.minute
            hour = pre_do_time.hour
            seconds = pre_do_time.second

            not_exceed_max_time = (datetime.now() - pre_do).total_seconds() < get_login_duration()
            not_exceed_max_in_counterpart_time = (datetime.now() - pre_do).total_seconds() <= 300

            # 副本中
            if in_counterpart() and not_exceed_max_in_counterpart_time:
                log("副本中，不退出")
                time.sleep(1)
                continue

            # 被退出，如果是刚被退出要等一段时间才能继续操作
            if pic_is_exists(image_name=r"unlogin.bmp") > 0.8 and not_exceed_max_time:
                log("not exceed logout max time")
                sleep(1)
                is_logout = True
                continue
            is_logout = False

            i = i - 1
            pre_do = datetime.now()
            # 点击确认按钮
            findAndClick(imageName=r"sure.bmp|sure1.bmp|queren.bmp|queren2.bmp|dianji.bmp|login.bmp")
            # 尝试关闭窗口
            close_window()

            log("find click use time:"+str((datetime.now() - pre_do_time).microseconds))

            # 留给抢龙魂时间
            if minute >= 58 or (minute < 2 and seconds < 30) and hour <= 18:
                # 如果当前不是归属自己，自动攻击
                log("抢龙魂")
                continue

            if minute < 5 and findAndClick(imageName=r"attack.png", end_y=500) > 0:
                log("自动攻击")
                continue

            # # 异域boss
            # add_assist_times = add_assist_times - 1
            # if do_assist() > 0:
            #     add_assist_times = 10
            #     continue
            # # 异域boss，出现过后增加监控时长
            # if add_assist_times > 0:
            #     continue

            if pic_is_exists(image_name=r"home_tips.png") > 0.75:
                findAndClick(imageName=r"tuichu.bmp|tuichu1.bmp")

            findAndClick(imageName=r"activity_home_collecting.png")
            # 三界boss
            if minute <= 1 and (19 > hour > 11 or hour > 21 or hour <= 5):
                log("三界boss")
                do_chain_operations(image_name1=r"boss.bmp", image_name2=r"boss_temple.png",
                                    image_name3=r"three_world_boss.png",
                                    image_name5=r"go_ahead_beat_boss2.bmp",
                                    try_times=8)
                if in_counterpart():
                    continue

            choose = i % 8
           # choose = 4
            # 每日挑战  and choose == 4
            if choose == 4 and (minute < 6 or 40 < minute < 50):
                log("每日挑战")
                do_flag = do_chain_operations(image_name1=r"fuben.bmp", image_name2=r"instance_daily_trials.bmp")
                if do_flag > 0:
                    daily_sweep()
                    continue

            # 奇遇
            if choose == 5 and minute % 10 == 2:
                do_flag = do_chain_operations(image_name1=r"adventure.bmp",
                                              image_name2=r"accept.bmp|go_ahead1.png|"
                                                          r"adventure_jiechu.bmp",
                                              image_name5=r"smelt.png|beat.bmp|seek.bmp|tiaozhan.bmp|"
                                                          r"receive_award.bmp",
                                              try_times=5)
                # try_find_and_click(image_name=r"adventure_du_leave.bmp")
                # try_find_and_click(image_name=r"adventure_du_leave_1.bmp")
                if do_flag > 0 and do_chain_operations(image_name1=r"smile.png", image_name2=r"smile1.png",
                                                       image_name3=r"send_msg.png") > 0:
                    findAndClick(imageName=r"close_chat_window.png")
                    continue

            # 多人副本
            if choose == 6 and (5 <= hour < 6 or 3 < minute <= 8):
                log("多人副本")
                if do_chain_operations(image_name1=r"boss.bmp",
                                       image_name2=r"boss_this_server.bmp|boss_this_server_1.bmp",
                                       image_name3=r"boss_multi_person.bmp", image_name5=r"boss_beat.bmp",
                                       try_times=5) > 0:
                    continue

            # 天降财宝
            if choose == 7 and (30 < minute < 59) and (hour == 11 or hour == 18):
                log("参与天降财宝")
                if do_limit_time_activity() > 0:
                    sleep(2)
                    if try_find_and_click(image_name=r"activity_treasure_collect_chest.png",match_num=0.7) < 1:
                        # 不是天降财宝，尝试仙府活动
                        activity_home()
                if in_counterpart():
                    continue

            # 云梦秘境
            if choose == 7 and (hour == 12 or hour == 21) and minute < 15:
                log("云梦秘境")
                if do_limit_time_activity() > 0:
                    try_find_and_click(image_name=r"activity_go_ahead_immediately.png", try_times=5)
                if in_counterpart():
                    continue
            if choose == 7 and (hour == 12 or hour == 21) and (minute >= 15):
                log("云梦秘境")
                max_loc, max_val, theight, twidth = findPic(imageName=r"activity_secret_area_in.png")
                if max_val > 0:
                    exit_activity()
                    continue

            # 池瑶
            if hour == 19 and minute < 20:
                log("参与池瑶活动")
                do_limit_time_activity()
                sleep(2)
                if in_counterpart():
                    max_click = 1000
                    while max_click > 0:
                        max_click = max_click - 1
                        click_when_second_image_exist(second_image_name=r"activity_limit_time_swim_match.png",
                                                      image_name=r"activity_limit_time_swim_click.png")
                    continue
            # 巅峰斗法
            if hour == 19 and 30 <= minute < 40:
                log("参与巅峰斗法")
                do_flag = do_limit_time_activity()
                if do_flag > 0:
                    findAndClick(imageName=r"activity_auto_match.png")
                    time.sleep(2)
                if in_counterpart():
                    continue

            # 玄火争夺
            if hour == 19 and minute >= 45:
                log("参与玄火争夺")
                do_flag = do_limit_time_activity()
                if do_flag > 0:
                    sleep(2)
                    try_find_and_click(image_name=r"activity_go_ahead_1.png", try_times=5,match_num=0.7)
                    time.sleep(5)
                    while in_counterpart():
                        try_find_and_click(
                            image_name=r"activity_limit_time_search_person.png|activity_limit_time_auto_search.png",
                            match_num=0.7)
                        time.sleep(5)
                    continue
            # 仙魔对决
            if hour == 20 and minute < 20:
                log("参与仙魔对决")
                do_flag = do_limit_time_activity()
                if do_flag > 0:
                    sleep(0.5)
                    do_chain_operations(image_name1=r"activity_limit_time_make_team.png",
                                        image_name2=r"activity_limit_time_team_create.png",
                                        image_name5=r"activity_quit_match.png")
                    try_find_and_click(image_name=r"activity_limit_time_begin_beat.png")
                    continue
            # 九天之巅
            if hour == 20 and 30 > minute >= 20:
                log("参与九天之巅")
                do_flag = do_limit_time_activity()
                if do_flag > 0:
                    sleep(0.5)
                    try_find_and_click(image_name=r"activity_go_ahead_1.png")
                if in_counterpart():
                    continue
            # 仙府事件
            if choose == 7 and (
                    hour == 15 or hour == 18 or hour == 23) and (
                    datetime.now() - pre_go_home_time).total_seconds() > 300 and minute < 50:
                pre_go_home_time = datetime.now()
                log("参与仙府事件")
                do_flag = do_limit_time_activity()
                if do_flag > 0:
                    if activity_home() < 1:
                        # 不是在仙府中，尝试收集财宝
                        findAndClick(r"activity_treasure_collect_chest.png")
                if in_counterpart():
                    continue
            # 仙缘竞技
            if choose == 7 and minute < 10:
                log("仙缘竞技")
                do_chain_operations(image_name1=r"athletics.bmp", image_name2=r"athletics_army.bmp",
                                    image_name3=r"athletics_phantastes.bmp")
                max_count = 3
                while findAndClick(imageName=r"athletics_phantastes_challenge.bmp", start_y=500,
                                   end_y=567) < 1 and max_count > 0:
                    max_count = max_count - 1
                    if findAndClick(imageName=r"athletics_phantastes_refresh.bmp") < 1:
                        break

            # 挂boss之家
            if choose == 23 and hour < 6:
                f = findAndClick(imageName=r"boss_home_refreshed.bmp")
                if f > 0:
                    sleep(18)

            # 自动闯关
            # if choose < 3:
            if choose < 3 and minute % 5 <= 1:
                log("自动闯关")
                findAndClick(imageName=r"zidongchuanguan.bmp", match_num=0.65)
            else:
                log("暂不执行自动闯关")
        except Exception as e:
            log(repr(e))
            traceback.print_exc()

def close_window():
    global do_assist_pre
    if (datetime.now() - do_assist_pre).total_seconds() <= 3:
        return
    ff = 1
    while ff > 0:
        ff = click_when_second_image_not_exist(image_name=r"X.bmp",
                                               second_image_name=r"unlogin.bmp|chatting.png|"
                                                                 r"activity_limit_time_match.png|"
                                                                 r"activity_limit_time_match2.png|"
                                                                 r"activity_limit_time_quit.png", matchNum=0.72)

# 仙府事件
def activity_home():
    sleep()
    ff = r"activity_home_collect.png|activity_home_collect_box.png|home_beat_sirdar.png|activity_home_collecting.png"
    ret = click_util_disappear(ff)
    sleep(3)
    # 炼制崖
    if findAndClick(imageName=r"home_3.png") > 0:
        sleep(1)
        click_util_disappear(ff)
    if findAndClick(imageName=r"home_2.png") > 0:
        sleep(1)
        click_util_disappear(ff)
    if findAndClick(imageName=r"home_1.png") > 0:
        sleep(1)
        click_util_disappear(ff)
    exit_activity()
    return ret

def do_limit_time_activity():
    do_chain_operations(image_name1=r"activity_daily.png",
                        image_name2=r"activity_limit_time.png", sleep_time=0.3)
    return try_find_and_click(image_name=r"activity_go_ahead.png", end_y=300)

def click_util_disappear(ff):
    max_times = 30
    while try_find_and_click(image_name=ff, try_times=10, match_num=0.7) > 0 and max_times > 0:
        sleep(1)
        max_times = max_times - 1
    if max_times == 30:
        return 0
    return 1

def daily_sweep():
    max_click = 3
    while max_click > 0:
        max_click = max_click - 1
        ff = try_find_and_click(image_name=r"tiaozhan.bmp|saodang.bmp", try_times=8, end_y=300, match_num=0.7)
        if ff < 1:
            break
        else:
            sleep(0.2)

do_assist_pre_print = datetime.now()
do_assist_pre = datetime.now()

def do_assist(times=50):
    global do_assist_pre_print
    global do_assist_pre
    if is_logout or datetime.now().hour < 6:
        if (datetime.now() - do_assist_pre_print).total_seconds() > 5:
            do_assist_pre_print = datetime.now()
            print("do_assist exit")
        return 0
    # if datetime.now().hour < 5:
    #     return 0
    log("异域boss支援")
    ii = times
    # |assist_gold.png
    while ii > 0:
        ff = findAndClick(imageName=r"assist.png|assist_gold.png", start_y=350, end_y=480, match_num=0.72)
        if ff > 0:
            do_assist_pre = datetime.now()
            try_find_and_click(image_name=r"assist_go.png|assist_gold_go.png", try_times=2)
            return 1
        ii = ii - 1
    return 0

def exit_activity():
    try_find_and_click(r"tuichu.bmp")
    try_find_and_click(r"tuichu1.bmp")

def get_login_duration():
    if datetime.now().hour < 6:
        loginDuration = 120
    elif datetime.now().hour < 10:
        loginDuration = 1200
    elif datetime.now().hour < 18:
        loginDuration = 600
    else:
        loginDuration = 800
    return loginDuration

class assistThread (Thread):
    def __init__(self, threadID, name, delay):
        Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.delay = delay

    def run(self):
        print("开始线程：" + self.name)
        async_do_assist()
        print("退出线程：" + self.name)

def delete_image():
    for file in glob.glob("my_screenshot_*.png"):
        os.remove(file)
        print("Deleted " + str(file))

dm = win32api.EnumDisplaySettings(None, 0)
dm.PelsHeight = 1080
dm.PelsWidth = 1920
dm.BitsPerPel = 32
dm.DisplayFixedOutput = 0
win32api.ChangeDisplaySettings(dm, 0)
hwnd = win32gui.FindWindow(None, '万剑诀')
win32gui.MoveWindow(hwnd, 20, 20, 488, 932, True)

delete_image()
print("do thread")
assistThread(1, "assistThread", 1).start()
# assist_thread = Thread(target=async_do_assist)
# assist_thread.start()
# time.sleep(1)
print("do main")
while True:
    main_method()
    # pass


#
# @lc app=leetcode.cn id=410 lang=python3
#
# [410] 分割数组的最大值
#

# @lc code=start
class Solution:
    def isFeasible(self, nums: List[int], m: int, target: int) -> bool:
        cnt = 1
        curr = 0
        for num in nums:
            if curr + num <= target:
                curr += num
            else:
                cnt += 1
                curr = num
        return cnt <= m

    def splitArray(self, nums: List[int], m: int) -> int:
        left = max(nums)
        right = sum(nums)
        while left + 1 < right:
            mid = left + (right - left) // 2
            if self.isFeasible(nums, m, mid):
                right = mid
            else:
                left = mid
        if self.isFeasible(nums, m, left):
            return left
        else:
            return right

# @lc code=end

410.分割数组的最大值.py

410.分割数组的最大值.py


import os
from fastapi import FastAPI
from fastapi.responses import FileResponse
import uvicorn
import json

app = FastAPI()

@app.get("/")
async def main():
    return FileResponse('img/map.jpg')

@app.get("/origin")
async def original_img():
    return FileResponse('img/out.jpg')

@app.get("/slots")
async def free_slots():
    with open('img/slots.json', 'r') as f:
        slots = json.load(f)
    return slots

if __name__ == "__main__":
    uvicorn.run("app:app", host="0.0.0.0", port=os.getenv("PORT", 8000))


from django.contrib import admin
from .models import Curso, Docente
# Register your models here.

#1 admin.site.register(Curso)
@admin.register(Curso) #3
class CursoAdmin(admin.ModelAdmin): #2 y #3

    #ordering=('-nombre',) #tupla indicarlo con la coma
    #ordering=('creditos', 'nombre')
    #search_fields=('nombre', 'creditos')
     #list_display=('id','nombre','creditos') #+columnas,sobrescribe __str__ de model
    #list_editable = ('nombre','creditos') #lista y edita(va con list_display)
    #list_display_links = ('nombre',)
    #list_filter =('creditos',)
    #list_per_page=3 #3 registros por pagina
    #exclude = ('creditos',) #desaparecen
    '''
    fieldsets = (
        (None,{
            'fields':('nombre',)
        }),
        ('Advanced options',{
            'classes':('collapse', 'wide', 'extrapretty'),
            'fields': ('creditos',)
        })

    )
    '''

#2 admin.site.register(Curso, CursoAdmin)
    list_display=('id','coloreado','creditos') # cambio nombre x datos, luego datos x
    def datos(self,obj):
        return obj.nombre.upper()
    datos.short_description="CURSO (MAYUS)"
    datos.empty_value_display = "???"
    datos.admin_order_field = 'nombre'

admin.site.register(Docente)


from heroku_connect.db import models as hc_models

class User(hc_models.HerokuConnectModel):
    sf_object_name = 'User'

    username = hc_models.Text(
        sf_field_name='Username', max_length=80)
    email = hc_models.Email(sf_field_name='Email')
    department = hc_models.Text(
        sf_field_name='Department', max_length=80)
    title = hc_models.Text(sf_field_name='Title', max_length=80)

    def __str__(self):
        return self.username