def post(self):
        self._require_admin()

        accept_keys = map(int, self.request.POST.getall("accept_keys[]"))
        reject_keys = map(int, self.request.POST.getall("reject_keys[]"))

        accepted_suggestion_futures = [Suggestion.get_by_id_async(key) for key in accept_keys]
        rejected_suggestion_futures = [Suggestion.get_by_id_async(key) for key in reject_keys]
        accepted_suggestions = map(lambda a: a.get_result(), accepted_suggestion_futures)
        rejected_suggestions = map(lambda a: a.get_result(), rejected_suggestion_futures)

        MatchSuggestionAccepter.accept_suggestions(accepted_suggestions)

        all_suggestions = accepted_suggestions
        all_suggestions.extend(rejected_suggestions)

        for suggestion in all_suggestions:
            if suggestion.key.id() in accept_keys:
                suggestion.review_state = Suggestion.REVIEW_ACCEPTED
            if suggestion.key.id() in reject_keys:
                suggestion.review_state = Suggestion.REVIEW_REJECTED
            suggestion.reviewer = self.user_bundle.account.key
            suggestion.reviewer_at = datetime.datetime.now()

        ndb.put_multi(all_suggestions)

        self.redirect("/admin/suggestions/match/video/review")
    def createApiWriteSuggestion(cls, author_account_key, event_key, affiliation, auth_types):
        """
        Create a suggestion for auth keys request.
        Returns status (success, no_affiliation, bad_event)
        """
        if not affiliation:
            return 'no_affiliation'

        if event_key:
            event = Event.get_by_id(event_key)
            if event:
                suggestion = Suggestion(
                    author=author_account_key,
                    target_model="api_auth_access",
                    target_key=event_key,
                )
                auth_types = [int(type) for type in auth_types]
                clean_auth_types = filter(lambda a: a in AuthType.write_type_names.keys(), auth_types)

                # If we're requesting keys for an official event, filter out everything but videos
                # Admin can still override this at review time, but it's unlikely
                if event.event_type_enum in EventType.SEASON_EVENT_TYPES:
                    clean_auth_types = filter(lambda a: a == AuthType.MATCH_VIDEO, clean_auth_types)

                suggestion.contents = {
                    'event_key': event_key,
                    'affiliation': affiliation,
                    'auth_types': clean_auth_types,
                }
                suggestion.put()
                return 'success'
            else:
                return 'bad_event'
        else:
            return 'bad_event'
    def post(self):
        self._require_login()

        match_key = self.request.get("match_key")
        youtube_url = self.request.get("youtube_url")

        youtube_id = None
        regex1 = re.match(r".*youtu\.be\/(.*)", youtube_url)
        if regex1 is not None:
            youtube_id = regex1.group(1)
        else:
            regex2 = re.match(r".*v=([a-zA-Z0-9_-]*)", youtube_url)
            if regex2 is not None:
                youtube_id = regex2.group(1)

        if youtube_id is not None:
            suggestion = Suggestion(
                author=self.user_bundle.account.key,
                target_key=match_key,
                target_model="match",
                )
            suggestion.contents = {"youtube_videos": [youtube_id]}
            suggestion.put()

        self.redirect('/suggest/match/video?match_key=%s&success=1' % match_key)
    def createMatchVideoSuggestion(self):
        user_bundle = UserBundle()
        match = Match.query().fetch(1)[0] #probably a cleaner way to do this

        suggestion = Suggestion(
            author=user_bundle.account.key,
            target_key=match.key_name,
            target_model="match")
        suggestion.contents = {"youtube_videos": [self.YOUTUBE_ID]}
        suggestion.put()
    def createEventWebcastSuggestion(self):
        user_bundle = UserBundle()
        event = Event.query().fetch(1)[0]

        suggestion = Suggestion(
            author=user_bundle.account.key,
            target_key=event.key_name,
            target_model="event",
            )
        suggestion.contents = {"webcast_url": self.YOUTUBE_URL}
        suggestion.put()
class TestMatchSuggestionAccepter(unittest2.TestCase):
    def setUp(self):
        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_memcache_stub()
        ndb.get_context().clear_cache()  # Prevent data from leaking between tests

        self.testbed.init_taskqueue_stub(root_path=".")

        self.account = Account(
            email="*****@*****.**",
        )
        self.account.put()

        self.suggestion = Suggestion(
            author=self.account.key,
            contents_json="{\"youtube_videos\":[\"123456\"]}",
            target_key="2012ct_qm1",
            target_model="match"
        )
        self.suggestion.put()

        self.event = Event(
          id="2012ct",
          event_short="ct",
          year=2012,
          event_type_enum=EventType.REGIONAL,
        )
        self.event.put()

        self.match = Match(
            id="2012ct_qm1",
            alliances_json="""{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="qm",
            event=self.event.key,
            year=2012,
            set_number=1,
            match_number=1,
            team_key_names=[u'frc69', u'frc571', u'frc176', u'frc3464', u'frc20', u'frc1073'],
            youtube_videos=["abcdef"]
        )
        self.match.put()

    def tearDown(self):
        self.testbed.deactivate()

    def test_accept_suggestions(self):
        MatchSuggestionAccepter.accept_suggestion(self.match, self.suggestion)

        match = Match.get_by_id("2012ct_qm1")
        self.assertTrue("abcdef" in match.youtube_videos)
        self.assertTrue("123456" in match.youtube_videos)
    def createOffseasonEventSuggestion(cls, author_account_key, name, start_date, end_date, website, address):
        """
        Create a suggestion for offseason event. Returns (status, failures):
        ('success', None)
        ('validation_failure', failures)
        """
        failures = {}
        if not name:
            failures['name'] = "Missing event name"
        if not start_date:
            failures['start_date'] = "Missing start date"
        if not end_date:
            failures['end_date'] = "Missing end date"
        if not website:
            failures['website'] = "Missing website"
        if not address:
            failures['venue_address'] = "Missing address"

        start_datetime = None
        end_datetime = None
        if start_date:
            try:
                start_datetime = datetime.strptime(start_date, "%Y-%m-%d")
            except ValueError:
                failures['start_date'] = "Invalid start date format (year-month-date)"

        if end_date:
            try:
                end_datetime = datetime.strptime(end_date, "%Y-%m-%d")
            except ValueError:
                failures['end_date'] = "Invalid end date format (year-month-date)"

        if start_datetime and end_datetime and end_datetime < start_datetime:
            failures['end_date'] = "End date must not be before the start date"

        if failures:
            return 'validation_failure', failures

        # Note that we don't specify an explicit key for event suggestions
        # We don't trust users to input correct event keys (that's for the moderator to do)
        suggestion = Suggestion(
            author=author_account_key,
            target_model="offseason-event",
        )
        suggestion.contents = {
            'name': name,
            'start_date': start_date,
            'end_date': end_date,
            'website': website,
            'address': address}
        suggestion.put()
        return 'success', None
    def post(self):
        self._require_admin()

        if self.request.get("verdict") == "accept":
            webcast = dict()
            webcast["type"] = self.request.get("webcast_type")
            webcast["channel"] = self.request.get("webcast_channel")
            if self.request.get("webcast_file"):
                webcast["file"] = self.request.get("webcast_file")

            event = Event.get_by_id(self.request.get("event_key"))
            suggestion = Suggestion.get_by_id(int(self.request.get("suggestion_key")))

            EventWebcastAdder.add_webcast(event, webcast)
            MemcacheWebcastFlusher.flush()

            suggestion.review_state = Suggestion.REVIEW_ACCEPTED
            suggestion.reviewer = self.user_bundle.account.key
            suggestion.reviewer_at = datetime.datetime.now()
            suggestion.put()

            self.redirect("/admin/suggestions/event/webcast/review?success=accept&event_key=%s" % event.key.id())
            return

        elif self.request.get("verdict") == "reject":
            suggestion = Suggestion.get_by_id(int(self.request.get("suggestion_key")))

            suggestion.review_state = Suggestion.REVIEW_REJECTED
            suggestion.reviewer = self.user_bundle.account.key
            suggestion.reviewer_at = datetime.datetime.now()
            suggestion.put()

            self.redirect("/admin/suggestions/event/webcast/review?success=reject")
            return

        elif self.request.get("verdict") == "reject_all":
            suggestion_keys = self.request.get("suggestion_keys").split(",")

            suggestions = [Suggestion.get_by_id(int(suggestion_key)) for suggestion_key in suggestion_keys]

            for suggestion in suggestions:
                event_key = suggestion.target_key
                suggestion.review_state = Suggestion.REVIEW_REJECTED
                suggestion.reviewer = self.user_bundle.account.key
                suggestion.reviewer_at = datetime.datetime.now()
                suggestion.put()

            self.redirect("/admin/suggestions/event/webcast/review?success=reject_all&event_key=%s" % event_key)
            return


        self.redirect("/admin/suggestions/event/webcast/review")
    def testCleanUrl(self):
        status = SuggestionCreator.createTeamMediaSuggestion(
            self.account.key,
            " http://imgur.com/ruRAxDm?foo=bar#meow ",
            "frc1124",
            "2016")
        self.assertEqual(status, 'success')

        # Ensure the Suggestion gets created
        suggestion_id = Suggestion.render_media_key_name('2016', 'team', 'frc1124', 'imgur', 'ruRAxDm')
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_PENDING)
        self.assertEqual(suggestion.author, self.account.key)
        self.assertEqual(suggestion.target_model, 'media')
    def get(self):
        self._require_registration()

        push_sitevar = Sitevar.get_by_id('notifications.enable')
        if push_sitevar is None or not push_sitevar.values_json == "true":
            ping_enabled = "disabled"
        else:
            ping_enabled = ""

        # Compute myTBA statistics
        user = self.user_bundle.account.key
        num_favorites = Favorite.query(ancestor=user).count()
        num_subscriptions = Subscription.query(ancestor=user).count()

        # Compute suggestion statistics
        submissions_pending = Suggestion.query(Suggestion.review_state==Suggestion.REVIEW_PENDING, Suggestion.author==user).count()
        submissions_accepted = Suggestion.query(Suggestion.review_state==Suggestion.REVIEW_ACCEPTED, Suggestion.author==user).count()

        # Suggestion review statistics
        review_permissions = False
        num_reviewed = 0
        total_pending = 0
        if self.user_bundle.account.permissions:
            review_permissions = True
            num_reviewed = Suggestion.query(Suggestion.reviewer==user).count()
            total_pending = Suggestion.query(Suggestion.review_state==Suggestion.REVIEW_PENDING).count()

        # Fetch trusted API keys
        api_keys = ApiAuthAccess.query(ApiAuthAccess.owner == user).fetch()
        write_keys = filter(lambda key: key.is_write_key, api_keys)
        read_keys = filter(lambda key: key.is_read_key, api_keys)

        self.template_values['status'] = self.request.get('status')
        self.template_values['webhook_verification_success'] = self.request.get('webhook_verification_success')
        self.template_values['ping_sent'] = self.request.get('ping_sent')
        self.template_values['ping_enabled'] = ping_enabled
        self.template_values['num_favorites'] = num_favorites
        self.template_values['num_subscriptions'] = num_subscriptions
        self.template_values['submissions_pending'] = submissions_pending
        self.template_values['submissions_accepted'] = submissions_accepted
        self.template_values['review_permissions'] = review_permissions
        self.template_values['num_reviewed'] = num_reviewed
        self.template_values['total_pending'] = total_pending
        self.template_values['read_keys'] = read_keys
        self.template_values['write_keys'] = write_keys
        self.template_values['auth_write_type_names'] = AuthType.write_type_names

        self.response.out.write(jinja2_engine.render('account_overview.html', self.template_values))
    def get(self):
        self._require_admin()

        self.template_values['memcache_stats'] = memcache.get_stats()
        self.template_values['databasequery_stats'] = {
            'hits': sum(filter(None, [memcache.get(key) for key in DatabaseQuery.DATABASE_HITS_MEMCACHE_KEYS])),
            'misses': sum(filter(None, [memcache.get(key) for key in DatabaseQuery.DATABASE_MISSES_MEMCACHE_KEYS]))
        }

        # Gets the 5 recently created users
        users = Account.query().order(-Account.created).fetch(5)
        self.template_values['users'] = users

        # Retrieves the number of pending suggestions
        video_suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
            Suggestion.target_model == "match").count()
        self.template_values['video_suggestions'] = video_suggestions

        webcast_suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
            Suggestion.target_model == "event").count()
        self.template_values['webcast_suggestions'] = webcast_suggestions

        media_suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
            Suggestion.target_model == "media").count()
        self.template_values['media_suggestions'] = media_suggestions

        # version info
        try:
            fname = os.path.join(os.path.dirname(__file__), '../../version_info.json')

            with open(fname, 'r') as f:
                data = json.loads(f.read().replace('\r\n', '\n'))

            self.template_values['git_branch_name'] = data['git_branch_name']
            self.template_values['build_time'] = data['build_time']

            commit_parts = re.split("[\n]+", data['git_last_commit'])
            self.template_values['commit_hash'] = commit_parts[0].split(" ")
            self.template_values['commit_author'] = commit_parts[1]
            self.template_values['commit_date'] = commit_parts[2]
            self.template_values['commit_msg'] = commit_parts[3]

        except Exception, e:
            logging.warning("version_info.json parsing failed: %s" % e)
            pass
    def get(self):
        self._require_registration()

        push_sitevar = Sitevar.get_by_id("notifications.enable")
        if push_sitevar is None or not push_sitevar.values_json == "true":
            ping_enabled = "disabled"
        else:
            ping_enabled = ""

        # Compute myTBA statistics
        user = self.user_bundle.account.key
        num_favorites = Favorite.query(ancestor=user).count()
        num_subscriptions = Subscription.query(ancestor=user).count()

        # Compute suggestion statistics
        submissions_pending = Suggestion.query(
            Suggestion.review_state == Suggestion.REVIEW_PENDING, Suggestion.author == user
        ).count()
        submissions_accepted = Suggestion.query(
            Suggestion.review_state == Suggestion.REVIEW_ACCEPTED, Suggestion.author == user
        ).count()

        # Suggestion review statistics
        review_permissions = False
        num_reviewed = 0
        total_pending = 0
        if self.user_bundle.account.permissions:
            review_permissions = True
            num_reviewed = Suggestion.query(Suggestion.reviewer == user).count()
            total_pending = Suggestion.query(Suggestion.review_state == Suggestion.REVIEW_PENDING).count()

        # Fetch trusted API keys
        trusted_keys = ApiAuthAccess.query(ApiAuthAccess.owner == user).fetch()

        self.template_values["status"] = self.request.get("status")
        self.template_values["webhook_verification_success"] = self.request.get("webhook_verification_success")
        self.template_values["ping_enabled"] = ping_enabled
        self.template_values["num_favorites"] = num_favorites
        self.template_values["num_subscriptions"] = num_subscriptions
        self.template_values["submissions_pending"] = submissions_pending
        self.template_values["submissions_accepted"] = submissions_accepted
        self.template_values["review_permissions"] = review_permissions
        self.template_values["num_reviewed"] = num_reviewed
        self.template_values["total_pending"] = total_pending
        self.template_values["trusted_keys"] = trusted_keys
        self.template_values["auth_type_names"] = AuthType.type_names

        self.response.out.write(jinja2_engine.render("account_overview.html", self.template_values))
 def createSuggestion(self):
     status = SuggestionCreator.createTeamMediaSuggestion(self.account.key,
                                                          'http://imgur.com/foobar',
                                                          'frc1124',
                                                          2016)
     self.assertEqual(status[0], 'success')
     return Suggestion.query().fetch(keys_only=True)[0].id()
    def get(self):
        self._require_admin()

        self.template_values['memcache_stats'] = memcache.get_stats()

        # Gets the 5 recently created users
        users = Account.query().order(-Account.created).fetch(5)
        self.template_values['users'] = users

        # Retrieves the number of pending suggestions
        video_suggestions = Suggestion.query().filter(Suggestion.review_state == Suggestion.REVIEW_PENDING).count()
        self.template_values['video_suggestions'] = video_suggestions

        # version info
        try:
            fname = os.path.join(os.path.dirname(__file__), '../../version_info.json')

            with open(fname, 'r') as f:
                data = json.loads(f.read().replace('\r\n', '\n'))

            self.template_values['git_branch_name'] = data['git_branch_name']
            self.template_values['build_time'] = data['build_time']

            commit_parts = re.split("[\n]+", data['git_last_commit'])
            self.template_values['commit_hash'] = commit_parts[0].split(" ")
            self.template_values['commit_author'] = commit_parts[1]
            self.template_values['commit_date'] = commit_parts[2]
            self.template_values['commit_msg'] = commit_parts[3]

        except Exception, e:
            logging.warning("version_info.json parsing failed: %s" % e)
            pass
    def _process_accepted(self, accept_key):
        """
        Performs all actions for an accepted Suggestion in a Transaction.
        Suggestions are processed one at a time (instead of in batch) in a
        Transaction to prevent possible race conditions.

        Actions include:
        - Creating and saving a new Media for the Suggestion
        - Removing a reference from another Media's preferred_references
        - Marking the Suggestion as accepted and saving it
        """
        # Async get
        suggestion_future = Suggestion.get_by_id_async(accept_key)

        # Resolve async Futures
        suggestion = suggestion_future.get_result()

        # Make sure Suggestion hasn't been processed (by another thread)
        if suggestion.review_state != Suggestion.REVIEW_PENDING:
            return

        team_reference = Media.create_reference(
            suggestion.contents['reference_type'],
            suggestion.contents['reference_key'])

        media = MediaCreator.create_media(suggestion, team_reference)

        # Mark Suggestion as accepted
        suggestion.review_state = Suggestion.REVIEW_ACCEPTED
        suggestion.reviewer = self.user_bundle.account.key
        suggestion.reviewed_at = datetime.datetime.now()

        # Do all DB writes
        MediaManipulator.createOrUpdate(media)
        suggestion.put()
    def setUp(self):
        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub()
        self.testbed.init_memcache_stub()
        self.testbed.init_taskqueue_stub(root_path=".")

        self.account = Account(email="*****@*****.**")
        self.account.put()

        self.suggestion = Suggestion(
            author=self.account.key,
            contents_json='{"youtube_videos":["123456"]}',
            target_key="2012ct_qm1",
            target_model="match",
        )
        self.suggestion.put()

        self.event = Event(id="2012ct", event_short="ct", year=2012, event_type_enum=EventType.REGIONAL)
        self.event.put()

        self.match = Match(
            id="2012ct_qm1",
            alliances_json="""{"blue": {"score": -1, "teams": ["frc3464", "frc20", "frc1073"]}, "red": {"score": -1, "teams": ["frc69", "frc571", "frc176"]}}""",
            comp_level="qm",
            event=self.event.key,
            game="frc_2012_rebr",
            set_number=1,
            match_number=1,
            team_key_names=[u"frc69", u"frc571", u"frc176", u"frc3464", u"frc20", u"frc1073"],
            youtube_videos=["abcdef"],
        )
        self.match.put()
    def test_create_suggestion_banned(self):
        status, _ = SuggestionCreator.createOffseasonEventSuggestion(
            self.account_banned.key,
            "Test Event",
            "2016-5-1",
            "2016-5-2",
            "http://foo.bar.com",
            "The Venue",
            "123 Fake Street",
            "New York", "NY", "USA")
        self.assertEqual(status, 'success')

        # Ensure the Suggestion gets created
        suggestions = Suggestion.query().fetch()
        self.assertIsNotNone(suggestions)
        self.assertEqual(len(suggestions), 1)

        suggestion = suggestions[0]
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.contents['name'], "Test Event")
        self.assertEqual(suggestion.contents['start_date'], '2016-5-1')
        self.assertEqual(suggestion.contents['end_date'], '2016-5-2')
        self.assertEqual(suggestion.contents['website'], 'http://foo.bar.com')
        self.assertEqual(suggestion.contents['address'], '123 Fake Street')
        self.assertEqual(suggestion.contents['city'], 'New York')
        self.assertEqual(suggestion.contents['state'], 'NY')
        self.assertEqual(suggestion.contents['country'], 'USA')
        self.assertEqual(suggestion.contents['venue_name'], 'The Venue')
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_AUTOREJECTED)
    def test_webcast_good_date(self):
        event = Event(id="2016test", name="Test Event", event_short="Test Event", year=2016, event_type_enum=EventType.OFFSEASON)
        event.put()

        status = SuggestionCreator.createEventWebcastSuggestion(
            self.account.key,
            "http://twitch.tv/frcgamesense",
            "2017-02-28",
            "2016test")

        self.assertEqual(status, 'success')
        suggestions = Suggestion.query().fetch()
        self.assertIsNotNone(suggestions)
        self.assertEqual(len(suggestions), 1)

        suggestion = suggestions[0]

        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.target_key, "2016test")
        self.assertEqual(suggestion.author, self.account.key)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_PENDING)
        self.assertIsNotNone(suggestion.contents)
        self.assertEqual(suggestion.contents.get('webcast_url'), "http://twitch.tv/frcgamesense")
        self.assertIsNotNone(suggestion.contents.get('webcast_dict'))
        self.assertEqual(suggestion.contents.get('webcast_date'), "2017-02-28")
    def post(self):
        self.verify_permissions()
        suggestion_id = int(self.request.get("suggestion_id"))
        verdict = self.request.get("verdict")
        message = self.request.get("user_message")

        admin_email_body = None
        email_body = None
        user = None
        event_key = None
        status = ''
        if verdict == "accept":
            status = 'accept'
            auth_id, user, event_key, email_body = self._process_accepted(suggestion_id, message)
            admin_email_body = """{} ({}) has accepted the request with the following message:
{}

View the key: https://www.thebluealliance.com/admin/api_auth/edit/{}

""".format(self.user_bundle.account.display_name, self.user_bundle.account.email, message, auth_id)

        elif verdict == "reject":
            suggestion = Suggestion.get_by_id(suggestion_id)
            event_key = suggestion.contents['event_key']
            user = suggestion.author.get()
            event = Event.get_by_id(event_key)
            suggestion.review_state = Suggestion.REVIEW_REJECTED
            suggestion.reviewer = self.user_bundle.account.key
            suggestion.reviewed_at = datetime.now()
            suggestion.put()

            status = 'reject'
            email_body = """Hi {},

We have reviewer your request for auth tokens for {} {} and have regretfully declined with the following message:

{}

If you have any questions, please don't hesitate to reach out to us at [email protected]

Thanks,
TBA Admins
""".format(user.display_name, event.year, event.name, message)

            admin_email_body = """{} ({}) has rejected this request with the following reason:
{}
""".format(self.user_bundle.account.display_name, self.user_bundle.account.email, message)

        # Notify the user their keys are available
        if email_body:
            mail.send_mail(sender="The Blue Alliance Contact <*****@*****.**>",
                           to=user.email,
                           subject="The Blue Alliance Auth Tokens for {}".format(event_key),
                           body=email_body)
        if admin_email_body:
            # Subject should match the one in suggest_apiwrite_controller
            subject = "Trusted API Key Request for {}".format(event_key)
            SuggestionNotifier.send_admin_alert_email(subject, admin_email_body)

        self.redirect("/suggest/apiwrite/review?success={}".format(status))
    def get(self):
        suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
            Suggestion.target_model == "media")

        reference_keys = []
        for suggestion in suggestions:
            reference_keys.append(Media.create_reference(
                suggestion.contents['reference_type'],
                suggestion.contents['reference_key']))
            if 'details_json' in suggestion.contents:
                suggestion.details = json.loads(suggestion.contents['details_json'])
                if 'image_partial' in suggestion.details:
                    suggestion.details['thumbnail'] = suggestion.details['image_partial'].replace('_l', '_m')

        reference_futures = ndb.get_multi_async(reference_keys)
        references = map(lambda r: r.get_result(), reference_futures)

        suggestions_and_references = zip(suggestions, references)

        self.template_values.update({
            "suggestions_and_references": suggestions_and_references,
        })

        path = os.path.join(os.path.dirname(__file__), '../../templates/suggest_team_media_review_list.html')
        self.response.out.write(template.render(path, self.template_values))
    def post(self):
        preferred_keys = self.request.POST.getall("preferred_keys[]")

        accept_keys = []
        reject_keys = []
        for value in self.request.POST.values():
            logging.debug(value)
            split_value = value.split('::')
            if len(split_value) == 2:
                key = split_value[1]
            else:
                continue
            if value.startswith('accept'):
                accept_keys.append(key)
            elif value.startswith('reject'):
                reject_keys.append(key)

        # Process accepts
        for accept_key in accept_keys:
            self._process_accepted(accept_key, preferred_keys)

        # Process rejects
        rejected_suggestion_futures = [Suggestion.get_by_id_async(key) for key in reject_keys]
        rejected_suggestions = map(lambda a: a.get_result(), rejected_suggestion_futures)
        for suggestion in rejected_suggestions:
            if suggestion.review_state == Suggestion.REVIEW_PENDING:
                suggestion.review_state = Suggestion.REVIEW_REJECTED
                suggestion.reviewer = self.user_bundle.account.key
                suggestion.reviewed_at = datetime.datetime.now()

        ndb.put_multi(rejected_suggestions)

        self.redirect("/suggest/team/media/review")
    def get(self):
        suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
            Suggestion.target_model == "offseason-event")

        year = datetime.now().year
        year_events_future = EventListQuery(year).fetch_async()
        last_year_events_future = EventListQuery(year - 1).fetch_async()
        events_and_ids = [self._create_candidate_event(suggestion) for suggestion in suggestions]

        year_events = year_events_future.get_result()
        year_offseason_events = [e for e in year_events if e.event_type_enum == EventType.OFFSEASON]
        last_year_events = last_year_events_future.get_result()
        last_year_offseason_events = [e for e in last_year_events if e.event_type_enum == EventType.OFFSEASON]

        similar_events = [self._get_similar_events(event[1], year_offseason_events) for event in events_and_ids]
        similar_last_year = [self._get_similar_events(event[1], last_year_offseason_events) for event in events_and_ids]

        self.template_values.update({
            'success': self.request.get("success"),
            'event_key': self.request.get("event_key"),
            'events_and_ids': events_and_ids,
            'similar_events': similar_events,
            'similar_last_year': similar_last_year,
        })
        self.response.out.write(
            jinja2_engine.render('suggestions/suggest_offseason_event_review_list.html', self.template_values))
    def get(self):
        super(SuggestDesignsReviewController, self).get()
        if self.request.get('action') and self.request.get('id'):
            # Fast-path review
            self._fastpath_review()

        suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
            Suggestion.target_model == "robot").fetch(limit=50)

        reference_keys = []
        for suggestion in suggestions:
            reference_key = suggestion.contents['reference_key']
            reference = Media.create_reference(
                suggestion.contents['reference_type'],
                reference_key)
            reference_keys.append(reference)

        reference_futures = ndb.get_multi_async(reference_keys)
        references = map(lambda r: r.get_result(), reference_futures)
        suggestions_and_references = zip(suggestions, references)

        self.template_values.update({
            "suggestions_and_references": suggestions_and_references,
        })

        self.response.out.write(jinja2_engine.render('suggestions/suggest_designs_review.html', self.template_values))
    def get(self):
        suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
            Suggestion.target_model == "event")

        suggestions_by_event_key = {}
        for suggestion in suggestions:
            if 'webcast_dict' in suggestion.contents:
                suggestion.webcast_template = 'webcast/{}.html'.format(suggestion.contents['webcast_dict']['type'])
            suggestions_by_event_key.setdefault(suggestion.target_key, []).append(suggestion)

        suggestion_sets = []
        for event_key, suggestions in suggestions_by_event_key.items():
            suggestion_sets.append({
                "event": Event.get_by_id(event_key),
                "suggestions": suggestions
                })

        self.template_values.update({
            "event_key": self.request.get("event_key"),
            "success": self.request.get("success"),
            "suggestion_sets": suggestion_sets
        })

        path = os.path.join(os.path.dirname(__file__), '../../templates/suggest_event_webcast_review_list.html')
        self.response.out.write(template.render(path, self.template_values))
    def test_accept_with_different_details(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm('review_{}'.format(suggestion_id))
        form['webcast_type'] = 'youtube'
        form['webcast_channel'] = 'foobar'
        form['webcast_file'] = 'meow'
        response = form.submit('verdict', value='accept').follow()
        self.assertEqual(response.status_int, 200)

        request = response.request
        self.assertEqual(request.GET.get('success'), 'accept')

        # Process task queue
        tasks = self.testbed.get_stub(testbed.TASKQUEUE_SERVICE_NAME).get_filtered_tasks()
        for task in tasks:
            deferred.run(task.payload)

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)

        # Make sure the Event has no webcasts
        event = Event.get_by_id('2016necmp')
        self.assertIsNotNone(event.webcast)
        self.assertEqual(len(event.webcast), 1)

        webcast = event.webcast[0]
        self.assertEqual(webcast['type'], 'youtube')
        self.assertEqual(webcast['channel'], 'foobar')
        self.assertEqual(webcast['file'], 'meow')
Example #26
0
    def testAcceptWithDifferentDetails(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm('review_{}'.format(suggestion_id))
        form['webcast_type'] = 'youtube'
        form['webcast_channel'] = 'foobar'
        form['webcast_file'] = 'meow'
        response = form.submit('verdict', value='accept').follow()
        self.assertEqual(response.status_int, 200)

        request = response.request
        self.assertEqual(request.GET.get('success'), 'accept')

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)

        # Make sure the Event has no webcasts
        event = Event.get_by_id('2016necmp')
        self.assertIsNotNone(event.webcast)
        self.assertEqual(len(event.webcast), 1)

        webcast = event.webcast[0]
        self.assertEqual(webcast['type'], 'youtube')
        self.assertEqual(webcast['channel'], 'foobar')
        self.assertEqual(webcast['file'], 'meow')
    def testAcceptNewKey(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm()
        form.set('accept_keys[]', suggestion_id)
        form.set('key-{}'.format(suggestion_id), '2016necmp_f1m2')
        response = form.submit().follow()
        self.assertEqual(response.status_int, 200)

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)

        # Make sure the video gets associated
        match = Match.get_by_id(self.match2.key_name)
        self.assertIsNotNone(match)
        self.assertIsNotNone(match.youtube_videos)
        self.assertTrue('H-54KMwMKY0' in match.youtube_videos)

        # Make sure we don't add it to the first match
        match = Match.get_by_id(self.match.key_name)
        self.assertIsNotNone(match)
        self.assertIsNotNone(match.youtube_videos)
        self.assertFalse('H-54KMwMKY0' in match.youtube_videos)
    def test_create_suggestion_banned(self):
        status, _ = SuggestionCreator.createEventMediaSuggestion(
            self.account_banned.key,
            "https://www.youtube.com/watch?v=H-54KMwMKY0",
            "2016nyny")
        self.assertEqual(status, 'success')

        # Ensure the Suggestion gets created
        suggestion_id = Suggestion.render_media_key_name('2016', 'event', '2016nyny', 'youtube', 'H-54KMwMKY0')
        suggestion = Suggestion.get_by_id(suggestion_id)
        expected_dict = MediaParser.partial_media_dict_from_url("https://www.youtube.com/watch?v=H-54KMwMKY0")
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_AUTOREJECTED)
        self.assertEqual(suggestion.author, self.account_banned.key)
        self.assertEqual(suggestion.target_model, 'event_media')
        self.assertDictContainsSubset(expected_dict, suggestion.contents)
Example #29
0
    def _process_accepted(self, accept_key):
        """
        Performs all actions for an accepted Suggestion in a Transaction.
        Suggestions are processed one at a time (instead of in batch) in a
        Transaction to prevent possible race conditions.
        """
        # Async get
        suggestion_future = Suggestion.get_by_id_async(accept_key)

        # Resolve async Futures
        suggestion = suggestion_future.get_result()

        # Make sure Suggestion hasn't been processed (by another thread)
        if suggestion.review_state != Suggestion.REVIEW_PENDING:
            return

        # Mark Suggestion as accepted
        suggestion.review_state = Suggestion.REVIEW_ACCEPTED
        suggestion.reviewer = self.user_bundle.account.key
        suggestion.reviewed_at = datetime.datetime.now()

        # Do all DB writes
        ret = self.create_target_model(suggestion)
        suggestion.put()
        return ret
    def get(self):
        suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
            Suggestion.target_model == "event_media").fetch(limit=50)

        # Quick and dirty way to group images together
        suggestions = sorted(suggestions, key=lambda x: 0 if x.contents['media_type_enum'] in MediaType.image_types else 1)

        reference_keys = []
        for suggestion in suggestions:
            reference_key = suggestion.contents['reference_key']
            reference = Media.create_reference(
                suggestion.contents['reference_type'],
                reference_key)
            reference_keys.append(reference)

            if 'details_json' in suggestion.contents:
                suggestion.details = json.loads(suggestion.contents['details_json'])
                if 'image_partial' in suggestion.details:
                    suggestion.details['thumbnail'] = suggestion.details['image_partial'].replace('_l', '_m')

        reference_futures = ndb.get_multi_async(reference_keys)
        references = map(lambda r: r.get_result(), reference_futures)

        suggestions_and_references = zip(suggestions, references)

        self.template_values.update({
            "suggestions_and_references": suggestions_and_references,
        })

        self.response.out.write(jinja2_engine.render('suggestions/suggest_event_media_review_list.html', self.template_values))
    def testUnknownUrlScheme(self):
        event = Event(id="2016test", name="Test Event", event_short="Test Event", year=2016, event_type_enum=EventType.OFFSEASON)
        event.put()

        status = SuggestionCreator.createEventWebcastSuggestion(
            self.account.key,
            "http://myweb.site/somewebcast",
            "2016test")
        self.assertEqual(status, 'success')
        suggestions = Suggestion.query().fetch()
        self.assertIsNotNone(suggestions)
        self.assertEqual(len(suggestions), 1)

        suggestion = suggestions[0]

        self.assertIsNotNone(suggestion)
        self.assertIsNotNone(suggestion.contents)
        self.assertIsNone(suggestion.contents.get('webcast_dict'))
        self.assertEqual(suggestion.contents.get('webcast_url'), "http://myweb.site/somewebcast")
Example #32
0
    def test_remove_media_preferred(self):
        self.loginUser()
        self.giveTeamAdminAccess()

        team_reference = Media.create_reference('team', 'frc1124')
        suggestion_id = self.createMediaSuggestion()
        suggestion = Suggestion.get_by_id(suggestion_id)
        media = MediaCreator.create_media_model(suggestion, team_reference)
        media.preferred_references.append(team_reference)
        media_id = media.put()
        self.assertTrue(ndb.Key(Team, 'frc1124') in media.references)

        form = self.getMediaAdminForm('remove_preferred', media_id.id())
        response = form.submit().follow()
        self.assertEqual(response.status_int, 301)

        media = media_id.get()
        self.assertTrue(team_reference in media.references)
        self.assertFalse(team_reference in media.preferred_references)
    def get(self):
        suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
                Suggestion.target_model == "offseason-event")

        year = datetime.now().year
        year_events_future = EventListQuery(year).fetch_async()
        last_year_events_future = EventListQuery(year - 1).fetch_async()
        events_and_ids = [
            self._create_candidate_event(suggestion)
            for suggestion in suggestions
        ]

        year_events = year_events_future.get_result()
        year_offseason_events = [
            e for e in year_events if e.event_type_enum == EventType.OFFSEASON
        ]
        last_year_events = last_year_events_future.get_result()
        last_year_offseason_events = [
            e for e in last_year_events
            if e.event_type_enum == EventType.OFFSEASON
        ]

        similar_events = [
            self._get_similar_events(event[1], year_offseason_events)
            for event in events_and_ids
        ]
        similar_last_year = [
            self._get_similar_events(event[1], last_year_offseason_events)
            for event in events_and_ids
        ]

        self.template_values.update({
            'success': self.request.get("success"),
            'event_key': self.request.get("event_key"),
            'events_and_ids': events_and_ids,
            'similar_events': similar_events,
            'similar_last_year': similar_last_year,
        })
        self.response.out.write(
            jinja2_engine.render(
                'suggestions/suggest_offseason_event_review_list.html',
                self.template_values))
Example #34
0
    def testCreateSuggestion(self):
        status, _ = SuggestionCreator.createOffseasonEventSuggestion(
            self.account.key, "Test Event", "2016-5-1", "2016-5-2",
            "http://foo.bar.com", "123 Fake Street, New York, NY")
        self.assertEqual(status, 'success')

        # Ensure the Suggestion gets created
        suggestions = Suggestion.query().fetch()
        self.assertIsNotNone(suggestions)
        self.assertEqual(len(suggestions), 1)

        suggestion = suggestions[0]
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.contents['name'], "Test Event")
        self.assertEqual(suggestion.contents['start_date'], '2016-5-1')
        self.assertEqual(suggestion.contents['end_date'], '2016-5-2')
        self.assertEqual(suggestion.contents['website'], 'http://foo.bar.com')
        self.assertEqual(suggestion.contents['address'],
                         '123 Fake Street, New York, NY')
    def testRejectSingleWebcast(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm('review_{}'.format(suggestion_id))
        response = form.submit('verdict', value='reject').follow()
        self.assertEqual(response.status_int, 200)

        request = response.request
        self.assertEqual(request.GET.get('success'), 'reject')

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_REJECTED)

        # Make sure the Event has no webcasts
        event = Event.get_by_id('2016necmp')
        self.assertIsNone(event.webcast)
Example #36
0
    def test_reject_robot_design(self):
        self.loginUser()
        self.giveTeamAdminAccess()

        suggestion_id = self.createDesignSuggestion()
        form = self.getSuggestionForm('robot')
        form['accept_reject-{}'.format(suggestion_id)] = 'reject::{}'.format(
            suggestion_id)
        response = form.submit().follow()
        self.assertEqual(response.status_int, 200)

        # Make sure the Media object doesn't get created
        medias = Media.query().fetch(keys_only=True)
        self.assertEqual(len(medias), 0)

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_REJECTED)
Example #37
0
    def testAcceptSuggestion(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm()
        form.set('accept_keys[]', suggestion_id)
        response = form.submit().follow()
        self.assertEqual(response.status_int, 200)

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)

        # Make sure the video gets associated
        match = Match.get_by_id(self.match.key_name)
        self.assertIsNotNone(match)
        self.assertIsNotNone(match.youtube_videos)
        self.assertTrue('H-54KMwMKY0' in match.youtube_videos)
    def testCreateSuggestion(self):
        event = Event(id="2016test", name="Test Event", event_short="Test Event", year=2016, event_type_enum=EventType.OFFSEASON)
        event.put()

        status = SuggestionCreator.createEventWebcastSuggestion(
            self.account.key,
            "http://twitch.tv/frcgamesense",
            "2016test")
        self.assertEqual(status, 'success')

        # Ensure the Suggestion gets created
        expected_key = "webcast_2016test_twitch_frcgamesense_None"
        suggestion = Suggestion.get_by_id(expected_key)

        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.target_key, "2016test")
        self.assertEqual(suggestion.author, self.account.key)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_PENDING)
        self.assertIsNotNone(suggestion.contents)
        self.assertEqual(suggestion.contents.get('webcast_url'), "http://twitch.tv/frcgamesense")
        self.assertIsNotNone(suggestion.contents.get('webcast_dict'))
Example #39
0
    def createTeamMediaSuggestion(cls,
                                  author_account_key,
                                  media_url,
                                  team_key,
                                  year_str,
                                  private_details_json=None):
        """Create a Team Media Suggestion. Returns status (success, suggestion_exists, media_exists, bad_url)"""

        media_dict = MediaParser.partial_media_dict_from_url(media_url.strip())
        if media_dict is not None:
            existing_media = Media.get_by_id(
                Media.render_key_name(media_dict['media_type_enum'],
                                      media_dict['foreign_key']))
            if existing_media is None or team_key not in [
                    reference.id() for reference in existing_media.references
            ]:
                foreign_type = Media.SLUG_NAMES[media_dict['media_type_enum']]
                suggestion_id = Suggestion.render_media_key_name(
                    year_str, 'team', team_key, foreign_type,
                    media_dict['foreign_key'])
                suggestion = Suggestion.get_by_id(suggestion_id)
                if not suggestion or suggestion.review_state != Suggestion.REVIEW_PENDING:
                    media_dict['year'] = int(year_str)
                    media_dict['reference_type'] = 'team'
                    media_dict['reference_key'] = team_key
                    if private_details_json is not None:
                        media_dict[
                            'private_details_json'] = private_details_json

                    suggestion = Suggestion(
                        id=suggestion_id,
                        author=author_account_key,
                        target_model="media",
                    )
                    suggestion.contents = media_dict
                    suggestion.put()
                    return 'success'
                else:
                    return 'suggestion_exists'
            else:
                return 'media_exists'
        else:
            return 'bad_url'
    def test_accept_bad_key(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm()
        form.set('accept_keys[]', suggestion_id)
        form.set('key-{}'.format(suggestion_id),
                 '2016necmp_f1m3')  # This match doesn't exist
        response = form.submit().follow()
        self.assertEqual(response.status_int, 200)

        # Make sure we don't mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_PENDING)

        # Make sure the video doesn't get associated
        match = Match.get_by_id(self.match.key_name)
        self.assertIsNotNone(match)
        self.assertIsNotNone(match.youtube_videos)
        self.assertFalse('H-54KMwMKY0' in match.youtube_videos)
    def testUndefinedAuthType(self):
        event = Event(id="2016test", name="Test Event", event_short="Test Event", year=2016, event_type_enum=EventType.OFFSEASON)
        event.put()

        status = SuggestionCreator.createApiWriteSuggestion(
            self.account.key,
            "2016test",
            "Event Organizer",
            [1, 2, -1, -2])  # -1 and -2 should be filtered out
        self.assertEqual(status, 'success')

        # Ensure the Suggestion gets created
        suggestions = Suggestion.query().fetch()
        self.assertIsNotNone(suggestions)
        self.assertEqual(len(suggestions), 1)

        suggestion = suggestions[0]
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.contents['event_key'], "2016test")
        self.assertEqual(suggestion.contents['affiliation'], "Event Organizer")
        self.assertListEqual(suggestion.contents['auth_types'], [1, 2])
    def test_accept_suggestion(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm()
        form['accept_reject-{}'.format(suggestion_id)] = 'accept::{}'.format(
            suggestion_id)
        response = form.submit().follow()
        self.assertEqual(response.status_int, 200)

        # Make sure the Media object gets created
        media = Media.query().fetch()[0]
        self.assertIsNotNone(media)
        self.assertEqual(media.media_type_enum, MediaType.GRABCAD)
        self.assertEqual(media.year, 2016)
        self.assertListEqual(media.references, [self.team.key])

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)
Example #43
0
    def testAcceptSuggestion(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm(suggestion_id)
        response = form.submit('verdict', value='accept').follow()
        self.assertEqual(response.status_int, 200)

        # Make sure the ApiWrite object gets created
        auth = ApiAuthAccess.query().fetch()[0]
        self.assertIsNotNone(auth)
        self.assertEqual(auth.owner, self.account.key)
        self.assertListEqual(auth.event_list, [self.event.key])
        self.assertListEqual(auth.auth_types_enum, [AuthType.EVENT_MATCHES])
        self.assertIsNotNone(auth.secret)
        self.assertIsNotNone(auth.expiration)

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)
    def test_fast_path_accept(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()

        response = self.testapp.get(
            '/suggest/cad/review?action=accept&id={}'.format(suggestion_id))
        response = response.follow()
        self.assertEqual(response.status_int, 200)
        self.assertEqual(response.request.GET.get('status'), 'accepted')

        # Make sure the Media object gets created
        media = Media.query().fetch()[0]
        self.assertIsNotNone(media)
        self.assertEqual(media.media_type_enum, MediaType.GRABCAD)
        self.assertEqual(media.year, 2016)
        self.assertListEqual(media.references, [self.team.key])

        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)
    def testOfficialEvent(self):
        event = Event(id="2016test", name="Test Event", event_short="Test Event", year=2016, event_type_enum=EventType.REGIONAL)
        event.put()

        status = SuggestionCreator.createApiWriteSuggestion(
            self.account.key,
            "2016test",
            "Event Organizer",
            [AuthType.MATCH_VIDEO, AuthType.EVENT_MATCHES, AuthType.EVENT_ALLIANCES])
        self.assertEqual(status, 'success')

        # Ensure the Suggestion gets created with only MATCH_VIDEO permission
        suggestions = Suggestion.query().fetch()
        self.assertIsNotNone(suggestions)
        self.assertEqual(len(suggestions), 1)

        suggestion = suggestions[0]
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.contents['event_key'], "2016test")
        self.assertEqual(suggestion.contents['affiliation'], "Event Organizer")
        self.assertListEqual(suggestion.contents['auth_types'], [AuthType.MATCH_VIDEO])
    def test_accept_suggestion(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm()
        form['accept_reject-{}'.format(suggestion_id)] = 'accept::{}'.format(
            suggestion_id)
        response = form.submit().follow()
        self.assertEqual(response.status_int, 200)

        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)

        medias = Media.query().fetch()
        self.assertEqual(len(medias), 1)
        media = medias[0]
        self.assertIsNotNone(media)
        self.assertEqual(media.foreign_key, 'foobar')
        self.assertEqual(media.media_type_enum, MediaType.YOUTUBE_VIDEO)
        self.assertTrue(ndb.Key(Event, '2016nyny') in media.references)
Example #47
0
    def _process_rejected(self, reject_keys):
        """
        Do everything we need to reject a batch of suggestions
        We can batch these, because we're just rejecting everything
        """
        if not isinstance(reject_keys, list):
            reject_keys = [reject_keys]

        rejected_suggestion_futures = [
            Suggestion.get_by_id_async(key) for key in reject_keys
        ]
        rejected_suggestions = map(lambda a: a.get_result(),
                                   rejected_suggestion_futures)

        for suggestion in rejected_suggestions:
            if suggestion.review_state == Suggestion.REVIEW_PENDING:
                suggestion.review_state = Suggestion.REVIEW_REJECTED
                suggestion.reviewer = self.user_bundle.account.key
                suggestion.reviewed_at = datetime.datetime.now()

        ndb.put_multi(rejected_suggestions)
    def _fastpath_review(self):
        self.verify_permissions()
        suggestion = Suggestion.get_by_id(self.request.get('id'))
        status = None
        if suggestion and suggestion.target_model == 'robot':
            if suggestion.review_state == Suggestion.REVIEW_PENDING:
                slack_message = None
                if self.request.get('action') == 'accept':
                    self._process_accepted(suggestion.key.id())
                    status = 'accepted'
                    slack_message = "{0} ({1}) accepted the <https://grabcad.com/library/{2}|suggestion> for team <https://thebluealliance.com/team/{3}/{4}|{3} in {4}>".format(
                        self.user_bundle.account.display_name,
                        self.user_bundle.account.email,
                        suggestion.contents['foreign_key'],
                        suggestion.contents['reference_key'][3:],
                        suggestion.contents['year'])
                elif self.request.get('action') == 'reject':
                    self._process_rejected(suggestion.key.id())
                    status = 'rejected'
                    slack_message = "{0} ({1}) rejected the <https://grabcad.com/library/{2}|suggestion> for team <https://thebluealliance.com/team/{3}/{4}|{3} in {4}>".format(
                        self.user_bundle.account.display_name,
                        self.user_bundle.account.email,
                        suggestion.contents['foreign_key'],
                        suggestion.contents['reference_key'][3:],
                        suggestion.contents['year'])

                if slack_message:
                    slack_sitevar = Sitevar.get_or_insert('slack.hookurls')
                    if slack_sitevar:
                        slack_url = slack_sitevar.contents.get('tbablog', '')
                        OutgoingNotificationHelper.send_slack_alert(
                            slack_url, slack_message)
            else:
                status = 'already_reviewed'
        else:
            status = 'bad_suggestion'

        if status:
            self.redirect('/suggest/review?status={}'.format(status),
                          abort=True)
Example #49
0
    def get(self):
        suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
            Suggestion.target_model == "social-media").fetch(limit=50)

        reference_keys = []
        for suggestion in suggestions:
            reference_key = suggestion.contents['reference_key']
            reference = Media.create_reference(
                suggestion.contents['reference_type'],
                reference_key)
            reference_keys.append(reference)

        reference_futures = ndb.get_multi_async(reference_keys)
        references = map(lambda r: r.get_result(), reference_futures)
        suggestions_and_references = zip(suggestions, references)

        self.template_values.update({
            "suggestions_and_references": suggestions_and_references,
        })

        self.response.out.write(jinja2_engine.render('suggestions/suggest_team_social_review.html', self.template_values))
    def test_accept_social_media(self):
        self.loginUser()
        self.giveTeamAdminAccess()

        suggestion_id = self.createSocialMediaSuggestion()
        form = self.getSuggestionForm('social-media')
        form['accept_reject-{}'.format(suggestion_id)] = 'accept::{}'.format(
            suggestion_id)
        response = form.submit().follow()
        self.assertEqual(response.status_int, 200)

        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)

        medias = Media.query().fetch()
        self.assertEqual(len(medias), 1)
        media = medias[0]
        self.assertIsNotNone(media)
        self.assertEqual(media.foreign_key, 'frc1124')
        self.assertEqual(media.media_type_enum, MediaType.TWITTER_PROFILE)
        self.assertTrue(ndb.Key(Team, 'frc1124') in media.references)
Example #51
0
    def testSuggestApiWrite(self):
        self.loginUser()
        form = self.getSuggestionForm()
        form['event_key'] = '2016necmp'
        form['role'] = 'Test Code'
        form.get('auth_types', index=0).checked = True
        form.get('auth_types', index=1).checked = True
        response = form.submit().follow()
        self.assertEqual(response.status_int, 200)

        # Make sure the Suggestion gets created
        suggestion = Suggestion.query().fetch()[0]
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_PENDING)
        self.assertEqual(suggestion.contents['event_key'], '2016necmp')
        self.assertEqual(suggestion.contents['affiliation'], 'Test Code')
        self.assertListEqual(suggestion.contents['auth_types'],
                             [AuthType.MATCH_VIDEO, AuthType.EVENT_TEAMS])

        # Ensure we show a success message on the page
        request = response.request
        self.assertEqual(request.GET.get('status'), 'success')
    def test_accept_suggestion_as_preferred(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm()
        form['accept_reject-{}'.format(suggestion_id)] = 'accept::{}'.format(
            suggestion_id)
        form['preferred_keys[]'] = ['preferred::{}'.format(suggestion_id)]
        response = form.submit().follow()
        self.assertEqual(response.status_int, 200)

        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)

        medias = Media.query().fetch()
        self.assertEqual(len(medias), 1)
        media = medias[0]
        self.assertIsNotNone(media)
        self.assertEqual(media.foreign_key, 'foobar')
        self.assertEqual(media.media_type_enum, MediaType.IMGUR)
        self.assertTrue(ndb.Key(Team, 'frc1124') in media.preferred_references)
Example #53
0
    def get(self):
        suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
                Suggestion.target_model == "event_media").fetch(limit=50)

        # Quick and dirty way to group images together
        suggestions = sorted(
            suggestions,
            key=lambda x: 0
            if x.contents['media_type_enum'] in MediaType.image_types else 1)

        reference_keys = []
        for suggestion in suggestions:
            reference_key = suggestion.contents['reference_key']
            reference = Media.create_reference(
                suggestion.contents['reference_type'], reference_key)
            reference_keys.append(reference)

            if 'details_json' in suggestion.contents:
                suggestion.details = json.loads(
                    suggestion.contents['details_json'])
                if 'image_partial' in suggestion.details:
                    suggestion.details['thumbnail'] = suggestion.details[
                        'image_partial'].replace('_l', '_m')

        reference_futures = ndb.get_multi_async(reference_keys)
        references = map(lambda r: r.get_result(), reference_futures)

        suggestions_and_references = zip(suggestions, references)

        self.template_values.update({
            "suggestions_and_references":
            suggestions_and_references,
        })

        self.response.out.write(
            jinja2_engine.render(
                'suggestions/suggest_event_media_review_list.html',
                self.template_values))
    def get(self):
        suggestions = Suggestion.query().filter(
            Suggestion.review_state == Suggestion.REVIEW_PENDING).filter(
                Suggestion.target_model == "match").fetch(limit=50)

        # Roughly sort by event and match for easier review
        suggestions = sorted(suggestions, key=lambda s: s.target_key)

        event_futures = [
            Event.get_by_id_async(suggestion.target_key.split("_")[0])
            for suggestion in suggestions
        ]
        events = [event_future.get_result() for event_future in event_futures]

        self.template_values.update({
            "suggestions_and_events":
            zip(suggestions, events),
        })

        self.response.out.write(
            jinja2_engine.render(
                'suggestions/suggest_match_video_review_list.html',
                self.template_values))
    def test_create_suggestion_banned(self):
        event = Event(id="2016test",
                      name="Test Event",
                      event_short="Test Event",
                      year=2016,
                      event_type_enum=EventType.OFFSEASON)
        event.put()

        status = SuggestionCreator.createApiWriteSuggestion(
            self.account_banned.key, "2016test", "Event Organizer", [1, 2, 3])
        self.assertEqual(status, 'success')

        # Ensure the Suggestion gets created
        suggestions = Suggestion.query().fetch()
        self.assertIsNotNone(suggestions)
        self.assertEqual(len(suggestions), 1)

        suggestion = suggestions[0]
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.contents['event_key'], "2016test")
        self.assertEqual(suggestion.contents['affiliation'], "Event Organizer")
        self.assertListEqual(suggestion.contents['auth_types'], [1, 2, 3])
        self.assertEqual(suggestion.review_state,
                         Suggestion.REVIEW_AUTOREJECTED)
    def testAcceptWithDefaultDetails(self):
        self.loginUser()
        self.givePermission()
        suggestion_id = self.createSuggestion()
        form = self.getSuggestionForm('review_{}'.format(suggestion_id))
        response = form.submit('verdict', value='accept').follow()
        self.assertEqual(response.status_int, 200)

        request = response.request
        self.assertEqual(request.GET.get('success'), 'accept')

        # Make sure we mark the Suggestion as REVIEWED
        suggestion = Suggestion.get_by_id(suggestion_id)
        self.assertIsNotNone(suggestion)
        self.assertEqual(suggestion.review_state, Suggestion.REVIEW_ACCEPTED)

        # Make sure the Event has no webcasts
        event = Event.get_by_id('2016necmp')
        self.assertIsNotNone(event.webcast)
        self.assertEqual(len(event.webcast), 1)

        webcast = event.webcast[0]
        self.assertEqual(webcast['type'], 'twitch')
        self.assertEqual(webcast['channel'], 'frcgamesense')
    def createOffseasonEventSuggestion(cls,
                                       author_account_key,
                                       name,
                                       start_date,
                                       end_date,
                                       website,
                                       venue_name,
                                       address,
                                       city,
                                       state,
                                       country,
                                       first_code=None,
                                       suggestion_id=None):
        """
        Create a suggestion for offseason event. Returns (status, failures):
        ('success', None)
        ('validation_failure', failures)
        """
        failures = {}
        if not name:
            failures['name'] = "Missing event name"
        if not start_date:
            failures['start_date'] = "Missing start date"
        if not end_date:
            failures['end_date'] = "Missing end date"
        if not website:
            failures['website'] = "Missing website"
        if not address:
            failures['venue_address'] = "Missing address"
        if not venue_name:
            failures['venue_name'] = "Missing venue name"
        if not city:
            failures['venue_city'] = "Missing city"
        if not state:
            failures['venue_state'] = "Missing state"
        if not country:
            failures['venue_country'] = "Missing country"

        start_datetime = None
        end_datetime = None
        if start_date:
            try:
                start_datetime = datetime.strptime(start_date, "%Y-%m-%d")
            except ValueError:
                failures[
                    'start_date'] = "Invalid start date format (year-month-date)"

        if end_date:
            try:
                end_datetime = datetime.strptime(end_date, "%Y-%m-%d")
            except ValueError:
                failures[
                    'end_date'] = "Invalid end date format (year-month-date)"

        if start_datetime and end_datetime and end_datetime < start_datetime:
            failures['end_date'] = "End date must not be before the start date"

        if failures and not suggestion_id:
            # Be more lenient with auto-added suggestions
            return 'validation_failure', failures

        # Note that we don't typically specify an explicit key for event suggestions
        # We don't trust users to input correct event keys (that's for the moderator to do)
        suggestion = Suggestion(
            id=suggestion_id) if suggestion_id else Suggestion()
        suggestion.author = author_account_key
        suggestion.target_model = "offseason-event"
        suggestion.contents = {
            'name': name,
            'start_date': start_date,
            'end_date': end_date,
            'website': website,
            'venue_name': venue_name,
            'address': address,
            'city': city,
            'state': state,
            'country': country,
            'first_code': first_code,
        }
        suggestion.put()
        return 'success', None
    def createTeamMediaSuggestion(cls,
                                  author_account_key,
                                  media_url,
                                  team_key,
                                  year_str,
                                  private_details_json=None,
                                  is_social=False,
                                  default_preferred=False):
        """Create a Team Media Suggestion. Returns status (success, suggestion_exists, media_exists, bad_url)"""

        media_dict = MediaParser.partial_media_dict_from_url(media_url)
        if media_dict is not None:
            if media_dict.get("is_social", False) != is_social:
                return 'bad_url', None

            existing_media = Media.get_by_id(
                Media.render_key_name(media_dict['media_type_enum'],
                                      media_dict['foreign_key']))
            if existing_media is None or team_key not in [
                    reference.id() for reference in existing_media.references
            ]:
                foreign_type = Media.SLUG_NAMES[media_dict['media_type_enum']]
                suggestion_id = Suggestion.render_media_key_name(
                    year_str, 'team', team_key, foreign_type,
                    media_dict['foreign_key'])
                suggestion = Suggestion.get_by_id(suggestion_id)
                if not suggestion or suggestion.review_state != Suggestion.REVIEW_PENDING:
                    media_dict['year'] = int(year_str) if year_str else None
                    media_dict['reference_type'] = 'team'
                    media_dict['reference_key'] = team_key
                    media_dict['default_preferred'] = default_preferred
                    if private_details_json is not None:
                        media_dict[
                            'private_details_json'] = private_details_json

                    target_model = "media"
                    if media_dict.get("is_social", False):
                        target_model = "social-media"

                    if media_dict.get('media_type',
                                      '') in MediaType.robot_types:
                        target_model = "robot"

                    if Event.validate_key_name(team_key):
                        target_model = 'event_media'
                        media_dict['reference_type'] = 'event'

                    suggestion = Suggestion(
                        id=suggestion_id,
                        author=author_account_key,
                        target_model=target_model,
                    )
                    suggestion.contents = media_dict
                    suggestion.put()
                    return 'success', suggestion
                else:
                    return 'suggestion_exists', None
            else:
                return 'media_exists', None
        else:
            return 'bad_url', None
class SuggestionCreator(object):
    @classmethod
    def createTeamMediaSuggestion(cls,
                                  author_account_key,
                                  media_url,
                                  team_key,
                                  year_str,
                                  private_details_json=None,
                                  is_social=False,
                                  default_preferred=False):
        """Create a Team Media Suggestion. Returns status (success, suggestion_exists, media_exists, bad_url)"""

        media_dict = MediaParser.partial_media_dict_from_url(media_url)
        if media_dict is not None:
            if media_dict.get("is_social", False) != is_social:
                return 'bad_url', None

            existing_media = Media.get_by_id(
                Media.render_key_name(media_dict['media_type_enum'],
                                      media_dict['foreign_key']))
            if existing_media is None or team_key not in [
                    reference.id() for reference in existing_media.references
            ]:
                foreign_type = Media.SLUG_NAMES[media_dict['media_type_enum']]
                suggestion_id = Suggestion.render_media_key_name(
                    year_str, 'team', team_key, foreign_type,
                    media_dict['foreign_key'])
                suggestion = Suggestion.get_by_id(suggestion_id)
                if not suggestion or suggestion.review_state != Suggestion.REVIEW_PENDING:
                    media_dict['year'] = int(year_str) if year_str else None
                    media_dict['reference_type'] = 'team'
                    media_dict['reference_key'] = team_key
                    media_dict['default_preferred'] = default_preferred
                    if private_details_json is not None:
                        media_dict[
                            'private_details_json'] = private_details_json

                    target_model = "media"
                    if media_dict.get("is_social", False):
                        target_model = "social-media"

                    if media_dict.get('media_type',
                                      '') in MediaType.robot_types:
                        target_model = "robot"

                    if Event.validate_key_name(team_key):
                        target_model = 'event_media'
                        media_dict['reference_type'] = 'event'

                    suggestion = Suggestion(
                        id=suggestion_id,
                        author=author_account_key,
                        target_model=target_model,
                    )
                    suggestion.contents = media_dict
                    suggestion.put()
                    return 'success', suggestion
                else:
                    return 'suggestion_exists', None
            else:
                return 'media_exists', None
        else:
            return 'bad_url', None

    @classmethod
    def createEventMediaSuggestion(cls,
                                   author_account_key,
                                   media_url,
                                   event_key,
                                   private_details_json=None):
        """Create an Event Media Suggestion. Returns status (success, suggestion_exists, media_exists, bad_url)"""

        media_dict = MediaParser.partial_media_dict_from_url(media_url)
        if media_dict is not None:
            if media_dict[
                    'media_type_enum'] != MediaType.YOUTUBE_VIDEO and media_dict[
                        'media_type_enum'] != MediaType.INTERNET_ARCHIVE_VIDEO:
                return 'bad_url', None

            existing_media = Media.get_by_id(
                Media.render_key_name(media_dict['media_type_enum'],
                                      media_dict['foreign_key']))
            if existing_media is None or event_key not in [
                    reference.id() for reference in existing_media.references
            ]:
                foreign_type = Media.SLUG_NAMES[media_dict['media_type_enum']]
                suggestion_id = Suggestion.render_media_key_name(
                    event_key[:4], 'event', event_key, foreign_type,
                    media_dict['foreign_key'])
                suggestion = Suggestion.get_by_id(suggestion_id)
                if not suggestion or suggestion.review_state != Suggestion.REVIEW_PENDING:
                    media_dict['year'] = event_key[:4]
                    media_dict['reference_type'] = 'event'
                    media_dict['reference_key'] = event_key
                    target_model = 'event_media'
                    if private_details_json is not None:
                        media_dict[
                            'private_details_json'] = private_details_json

                    suggestion = Suggestion(
                        id=suggestion_id,
                        author=author_account_key,
                        target_model=target_model,
                    )
                    suggestion.contents = media_dict
                    suggestion.put()
                    return 'success', suggestion
                else:
                    return 'suggestion_exists', None
            else:
                return 'media_exists', None
        else:
            return 'bad_url', None

    @classmethod
    def createEventWebcastSuggestion(cls, author_account_key, webcast_url,
                                     webcast_date, event_key):
        """Create a Event Webcast Suggestion. Returns status string"""

        webcast_url = WebsiteHelper.format_url(webcast_url)

        webcast_date = webcast_date.strip()
        if webcast_date:
            try:
                datetime.strptime(webcast_date, "%Y-%m-%d")
            except ValueError:
                return 'invalid_date'
        else:
            webcast_date = None

        try:
            webcast_dict = WebcastParser.webcast_dict_from_url(webcast_url)
        except Exception, e:
            logging.exception(e)
            webcast_dict = None

        if webcast_dict is not None:
            # Check if webcast already exists in event
            event = Event.get_by_id(event_key)
            if not event:
                return 'bad_event'
            if event.webcast and webcast_dict in event.webcast:
                return 'webcast_exists'
            else:
                suggestion_id = Suggestion.render_webcast_key_name(
                    event_key, webcast_dict)
                suggestion = Suggestion.get_by_id(suggestion_id)
                # Check if suggestion exists
                if not suggestion or suggestion.review_state != Suggestion.REVIEW_PENDING:
                    suggestion = Suggestion(
                        id=suggestion_id,
                        author=author_account_key,
                        target_model="event",
                        target_key=event_key,
                    )
                    suggestion.contents = {
                        "webcast_dict": webcast_dict,
                        "webcast_url": webcast_url,
                        "webcast_date": webcast_date
                    }
                    suggestion.put()
                    return 'success'
                else:
                    return 'suggestion_exists'
        else:  # Can't parse URL -- could be an obscure webcast. Save URL and let a human deal with it.
            contents = {
                "webcast_url": webcast_url,
                "webcast_date": webcast_date
            }

            # Check if suggestion exists
            existing_suggestions = Suggestion.query(
                Suggestion.target_model == 'event',
                Suggestion.target_key == event_key).fetch()
            for existing_suggestion in existing_suggestions:
                if existing_suggestion.contents == contents:
                    return 'suggestion_exists'

            suggestion = Suggestion(
                author=author_account_key,
                target_model="event",
                target_key=event_key,
            )
            suggestion.contents = contents
            suggestion.put()
            return 'success'
class SuggestionCreator(object):
    @classmethod
    def createTeamMediaSuggestion(cls,
                                  author_account_key,
                                  media_url,
                                  team_key,
                                  year_str,
                                  private_details_json=None,
                                  is_social=False):
        """Create a Team Media Suggestion. Returns status (success, suggestion_exists, media_exists, bad_url)"""

        media_dict = MediaParser.partial_media_dict_from_url(media_url)
        if media_dict is not None:
            if media_dict.get("is_social", False) != is_social:
                return 'bad_url', None

            existing_media = Media.get_by_id(
                Media.render_key_name(media_dict['media_type_enum'],
                                      media_dict['foreign_key']))
            if existing_media is None or team_key not in [
                    reference.id() for reference in existing_media.references
            ]:
                foreign_type = Media.SLUG_NAMES[media_dict['media_type_enum']]
                suggestion_id = Suggestion.render_media_key_name(
                    year_str, 'team', team_key, foreign_type,
                    media_dict['foreign_key'])
                suggestion = Suggestion.get_by_id(suggestion_id)
                if not suggestion or suggestion.review_state != Suggestion.REVIEW_PENDING:
                    media_dict['year'] = int(year_str) if year_str else None
                    media_dict['reference_type'] = 'team'
                    media_dict['reference_key'] = team_key
                    if private_details_json is not None:
                        media_dict[
                            'private_details_json'] = private_details_json

                    target_model = "media"
                    if media_dict.get("is_social", False):
                        target_model = "social-media"

                    if media_dict.get('media_type',
                                      '') in MediaType.robot_types:
                        target_model = "robot"

                    suggestion = Suggestion(
                        id=suggestion_id,
                        author=author_account_key,
                        target_model=target_model,
                    )
                    suggestion.contents = media_dict
                    suggestion.put()
                    return 'success', suggestion
                else:
                    return 'suggestion_exists', None
            else:
                return 'media_exists', None
        else:
            return 'bad_url', None

    @classmethod
    def createEventWebcastSuggestion(cls, author_account_key, webcast_url,
                                     event_key):
        """Create a Event Webcast Suggestion. Returns status string"""

        webcast_url = webcast_url.strip()
        if not webcast_url.startswith(
                'http://') and not webcast_url.startswith('https://'):
            webcast_url = 'http://' + webcast_url

        try:
            webcast_dict = WebcastParser.webcast_dict_from_url(webcast_url)
        except Exception, e:
            logging.exception(e)
            webcast_dict = None

        if webcast_dict is not None:
            # Check if webcast already exists in event
            event = Event.get_by_id(event_key)
            if not event:
                return 'bad_event'
            if event.webcast and webcast_dict in event.webcast:
                return 'webcast_exists'
            else:
                suggestion_id = Suggestion.render_webcast_key_name(
                    event_key, webcast_dict)
                suggestion = Suggestion.get_by_id(suggestion_id)
                # Check if suggestion exists
                if not suggestion or suggestion.review_state != Suggestion.REVIEW_PENDING:
                    suggestion = Suggestion(
                        id=suggestion_id,
                        author=author_account_key,
                        target_model="event",
                        target_key=event_key,
                    )
                    suggestion.contents = {
                        "webcast_dict": webcast_dict,
                        "webcast_url": webcast_url
                    }
                    suggestion.put()
                    return 'success'
                else:
                    return 'suggestion_exists'
        else:  # Can't parse URL -- could be an obscure webcast. Save URL and let a human deal with it.
            contents = {"webcast_url": webcast_url}

            # Check if suggestion exists
            existing_suggestions = Suggestion.query(
                Suggestion.target_model == 'event',
                Suggestion.target_key == event_key).fetch()
            for existing_suggestion in existing_suggestions:
                if existing_suggestion.contents == contents:
                    return 'suggestion_exists'

            suggestion = Suggestion(
                author=author_account_key,
                target_model="event",
                target_key=event_key,
            )
            suggestion.contents = contents
            suggestion.put()
            return 'success'