def complete_submission_period(submission_period_id): if not submission_period_id: app.logger.error('No submission period id for completion!') return try: from musicleague.submission_period.tasks.cancelers import cancel_round_completion # noqa from musicleague.submission_period.tasks.cancelers import cancel_vote_reminders league_id = select_league_id_for_round(submission_period_id) league = select_league(league_id) submission_period = next((r for r in league.submission_periods if r.id == submission_period_id), None) calculate_round_scoreboard(submission_period) update_round_status(submission_period, RoundStatus.COMPLETE) league = select_league(submission_period.league_id) calculate_league_scoreboard(league) user_all_voted_notification(submission_period) cancel_round_completion(submission_period) cancel_vote_reminders(submission_period) if league.is_complete: update_league_status(league.id, LeagueStatus.COMPLETE) else: from musicleague.submission_period.tasks.schedulers import schedule_new_round_notification schedule_new_round_notification(league.current_submission_period) except Exception as e: app.logger.exception( 'Error occurred while completing submission period!', exc_info=e, extra={'round': submission_period_id})
def r_remove_submission_period(league_id, submission_period_id, **kwargs): league = select_league(league_id) if league.has_owner(g.user): submission_period = remove_submission_period(submission_period_id) flash_success("<strong>{}</strong> removed.".format( submission_period.name)) return redirect(url_for('view_league', league_id=league_id))
def join_league(league_id): league = select_league(league_id) league.users.append(g.user) insert_membership(league, g.user) # If this URL is from an invitation email, delete the placeholder invite_id = request.args.get('invitation') if invite_id: delete_invited_user(invite_id) app.logger.debug('Deleted league invitation', extra={ 'league': league_id, 'user': g.user.id, 'invitation': invite_id }) track_user_joined_league(g.user.id, league) app.logger.info('User joined league', extra={ 'league': league_id, 'user': g.user.id, 'invitation': invite_id }) return redirect(url_for('view_league', league_id=league_id))
def complete_submission_process(submission_period_id): if not submission_period_id: app.logger.error('No submission period id for completion!') return try: from musicleague.submission_period.tasks.cancelers import cancel_playlist_creation # noqa from musicleague.submission_period.tasks.cancelers import cancel_submission_reminders # noqa league_id = select_league_id_for_round(submission_period_id) league = select_league( league_id, exclude_properties=['votes', 'scoreboard', 'invited_users']) submission_period = next((r for r in league.submission_periods if r.id == submission_period_id), None) update_round_status(submission_period, RoundStatus.ACCEPTING_VOTES) # Set new status so old status isn't persisted with playlist URL submission_period.status = RoundStatus.ACCEPTING_VOTES create_or_update_playlist(submission_period) cancel_playlist_creation(submission_period) cancel_submission_reminders(submission_period) except Exception as e: app.logger.exception( 'Error occurred while completing submission process!', exc_info=e, extra={'round': submission_period_id})
def view_submit(league_id, submission_period_id): league = select_league(league_id) if league.version == 2: return redirect( url_for('view_submit_2', league_id=league_id, submission_period_id=submission_period_id)) submission_period = next( (sp for sp in league.submission_periods if sp.id == submission_period_id), None) if not league.has_user(g.user): return redirect(url_for('view_league', league_id=league.id)) # Only allow submissions for the current round if (league.current_submission_period is None or league.current_submission_period.id != submission_period_id): return redirect(url_for('view_league', league_id=league.id)) if not submission_period.accepting_submissions: return redirect(url_for('view_league', league_id=league.id)) my_submission = get_my_submission(g.user, submission_period) return { 'user': g.user, 'league': league, 'round': submission_period, 'my_submission': my_submission, 'access_token': session['access_token'], }
def score_league(league_id, **kwargs): league = select_league(league_id) league = calculate_league_scoreboard(league) ret = { rank: [entry.user.id for entry in entries] for rank, entries in league.scoreboard.rankings.iteritems() } return json.dumps(ret), httplib.OK
def get_manage_league_v2(league_id): league = select_league(league_id) if not league or not league.has_owner(g.user): app.logger.warning('Unauthorized user attempted access', extra={ 'league': league.id, 'user': g.user.id }) return redirect(url_for('view_league', league_id=league_id)) return {'user': g.user, 'league': league}
def post_remove_league(league_id): league = select_league(league_id) if league and league.has_owner(g.user): remove_league(league_id, league=league) app.logger.debug('User deleted league', extra={ 'league': league.id, 'user': g.user.id }) return redirect(url_for('profile'))
def score_round(league_id, submission_period_id): league = select_league(league_id) submission_period = next( (sp for sp in league.submission_periods if sp.id == submission_period_id), None) submission_period = calculate_round_scoreboard(submission_period) calculate_league_scoreboard(league) ret = { rank: [entry.submission.user.id for entry in entries] for rank, entries in submission_period.scoreboard.rankings.iteritems() } return json.dumps(ret), 200
def view_vote(league_id, submission_period_id): league = select_league(league_id) submission_period = next( (sp for sp in league.submission_periods if sp.id == submission_period_id), None) if not league.has_user(g.user): return redirect(url_for('view_league', league_id=league.id)) if (league.current_submission_period is None or league.current_submission_period.id != submission_period_id): return redirect(url_for('view_league', league_id=league.id)) if not submission_period.accepting_votes: return redirect(url_for('view_league', league_id=league.id)) my_submission = get_my_submission(g.user, submission_period) # If this user didn't submit for this round, don't allow them to vote if not my_submission: return redirect(url_for('view_league', league_id=league.id)) # If this user already voted for this round, don't allow them to vote if get_my_vote(g.user, submission_period): return redirect(url_for('view_league', league_id=league.id)) # my_vote = get_my_vote(g.user, submission_period) my_vote = None tracks = [] if submission_period.all_tracks: tracks = g.spotify.tracks(submission_period.all_tracks).get('tracks') tracks_by_uri = OrderedDict() for track in tracks: if track: tracks_by_uri[track.get('uri')] = track # Remove user's own submitted songs from tracks shown on page if my_submission: for uri in my_submission.tracks: tracks_by_uri.pop(uri, None) return { 'user': g.user, 'league': league, 'round': submission_period, 'tracks_by_uri': tracks_by_uri, 'my_vote': my_vote, 'access_token': session['access_token'], }
def remove_league(league_id, league=None): if league is None: league = select_league(league_id) if not league or str(league.id) != str(league_id): return for submission_period in league.submission_periods: remove_submission_period(submission_period.id, submission_period=submission_period) delete_league(league) return league
def admin_generate_playlist(submission_period_id): if not submission_period_id: return league_id = select_league_id_for_round(submission_period_id) league = select_league(league_id, exclude_properties=['votes', 'scoreboard', 'invited_users']) submission_period = next((r for r in league.submission_periods if r.id == submission_period_id), None) if not submission_period: return create_or_update_playlist(submission_period) return redirect(request.referrer)
def schedule_vote_reminders(submission_period): if not is_deployed(): return # TODO Select preference instead of entire league league = select_league(submission_period.league_id) diff = league.preferences.vote_reminder_time notify_time = submission_period.vote_due_date - timedelta(hours=diff) job_id = '%s_%s' % (submission_period.id, TYPES.SEND_VOTE_REMINDERS) # If notification time would be in the past, cancel # any enqueued job instead of scheduling if notify_time < utc.localize(datetime.now()): logging.info('Not rescheduling vote reminder - datetime has passed', extra={ 'execute': notify_time, 'round': submission_period.id }) scheduler.cancel(job_id) return try: # If job has been previously scheduled, reschedule job = Job.fetch(job_id, connection=redis_conn) scheduler.change_execution_time(job, notify_time) logging.info('Vote reminder job updated', extra={ 'execute': notify_time, 'job': job.id }) except (NoSuchJobError, ValueError): # If job has not been previously scheduled or is no longer in queue, enqueue job = scheduler.enqueue_at(notify_time, send_vote_reminders, submission_period.id, job_id=job_id) logging.info('Vote reminder enqueued', extra={ 'execute': notify_time, 'job': job.id })
def admin_league_state(league_id): if not league_id: return league = select_league(league_id) for r in league.submission_periods: if r.accepting_submissions and not r.have_not_submitted: update_round_status(r, RoundStatus.ACCEPTING_VOTES) elif (r.accepting_submissions and (r.submission_due_date < utc.localize(datetime.utcnow()))): update_round_status(r, RoundStatus.ACCEPTING_VOTES) elif r.accepting_votes and not r.have_not_voted: update_round_status(r, RoundStatus.COMPLETE) elif (r.accepting_votes and (r.vote_due_date < utc.localize(datetime.utcnow()))): update_round_status(r, RoundStatus.COMPLETE) return redirect(request.referrer)
def create_playlist(submission_period_id): if not submission_period_id: app.logger.error('No submission period id for playlist creation!') return try: with app.app_context(): league_id = select_league_id_for_round(submission_period_id) league = select_league( league_id, exclude_properties=['votes', 'scoreboard', 'invited_users']) submission_period = next((r for r in league.submission_periods if r.id == submission_period_id), None) create_or_update_playlist(submission_period) except Exception as e: app.logger.exception('Error occurred while creating playlist!', exc_info=e, extra={'round': submission_period_id})
def view_submit(league_id, submission_period_id): league = select_league(league_id) submission_period = next( (sp for sp in league.submission_periods if sp.id == submission_period_id), None) if not league.has_user(g.user): return redirect(url_for('view_league', league_id=league.id)) if not submission_period.accepting_submissions: return redirect(url_for('view_league', league_id=league.id)) my_submission = get_my_submission(g.user, submission_period) return { 'user': g.user, 'league': league, 'round': submission_period, 'my_submission': my_submission, 'access_token': session['access_token'], }
def view_submission_period(league_id, submission_period_id): league = select_league(league_id) submission_period = next( (sp for sp in league.submission_periods if sp.id == submission_period_id), None) if not league or not submission_period: flash_error('Round not found') return redirect(url_for('view_league', league_id=league.id)) has_voted = submission_period.user_vote(g.user) is not None is_admin = g.user.is_admin can_view = submission_period.is_complete or is_admin or has_voted if not can_view: flash_warning('You do not have access to this page right now') return redirect(url_for('view_league', league_id=league.id)) # Get Spotify track objects tracks = submission_period.all_tracks if tracks: tracks = g.spotify.tracks(submission_period.all_tracks).get('tracks') tracks_by_uri = {track['uri']: track for track in tracks if track} # Make sure this round has an up-to-date scoreboard ctx = { 'user': g.user.id, 'league': league_id, 'round': submission_period_id } app.logger.info('User viewing round', extra=ctx) if not submission_period.scoreboard or not submission_period.is_complete: app.logger.info('Updating round scoreboard for user view', extra=ctx) submission_period = calculate_round_scoreboard(submission_period) return { 'user': g.user, 'league': league, 'round': submission_period, 'tracks_by_uri': tracks_by_uri }
def send_vote_reminders(submission_period_id): if not submission_period_id: app.logger.error('No submission period id for vote reminders!') return False try: league_id = select_league_id_for_round(submission_period_id) league = select_league( league_id, exclude_properties=['scoreboard', 'invited_users']) submission_period = next((r for r in league.submission_periods if r.id == submission_period_id), None) for user in submission_period.have_not_voted: app.logger.debug('User has not voted! Notifying.', extra={'user': str(user.id)}) user_vote_reminder_notification(user, submission_period) return True except Exception as e: app.logger.exception('Error while sending vote reminders!', exc_info=e, extra={'round': submission_period_id}) return False
def notify_new_round(submission_period_id): if not submission_period_id: app.logger.error('No submission period id for new round notification!') return try: with app.app_context(): league_id = select_league_id_for_round(submission_period_id) league = select_league(league_id, exclude_properties=[ 'submissions', 'votes', 'scoreboard', 'invited_users' ]) submission_period = next((r for r in league.submission_periods if r.id == submission_period_id), None) user_new_round_notification(submission_period) except Exception as e: app.logger.exception('Error occurred while notifying new round!', exc_info=e, extra={'round': submission_period_id})
def league_get(league_id): try: league = select_league(league_id) if not league: return json.dumps(None) except Exception: return json.dumps(None) return json.dumps({ 'name': league.name, 'active': league.is_active, 'complete': league.is_complete, 'public': league.is_public, 'users': [{ 'id': user.id, 'name': user.name, } for user in league.users], })
def view_league(league_id): league = select_league(league_id) if not league: app.logger.error('League not found', extra={ 'league': league_id, 'user': g.user.id }) return 'League not found', httplib.NOT_FOUND if league.submission_periods: lsp = league.submission_periods[-1] next_submission_due_date = lsp.submission_due_date + timedelta(weeks=1) next_vote_due_date = lsp.vote_due_date + timedelta(weeks=1) else: next_submission_due_date = datetime.utcnow() + timedelta(days=5) next_vote_due_date = datetime.utcnow() + timedelta(days=7) return render_template('league/view/page.html', user=g.user, league=league, next_submission_due_date=next_submission_due_date, next_vote_due_date=next_vote_due_date)
def post_create_submission_period(league_id, **kwargs): league = select_league(league_id) if league.has_owner(g.user): name = request.form.get('name') description = request.form.get('description') if not description or not description.strip(): description = None submission_due_date_str = request.form.get('submission_due_date_utc') submission_due_date = utc.localize( datetime.strptime(submission_due_date_str, '%m/%d/%y %I%p')) vote_due_date_str = request.form.get('voting_due_date_utc') vote_due_date = utc.localize( datetime.strptime(vote_due_date_str, '%m/%d/%y %I%p')) submission_period = create_submission_period(league, name, description, submission_due_date, vote_due_date) flash_success("<strong>{}</strong> created.".format( submission_period.name)) return redirect(url_for('view_league', league_id=league_id))
def vote(league_id, submission_period_id): try: league = select_league(league_id) submission_period = next((sp for sp in league.submission_periods if sp.id == submission_period_id), None) if not league or not submission_period: return "No submission period or league", httplib.INTERNAL_SERVER_ERROR if not league.has_user(g.user): return "Not a member of this league", httplib.UNAUTHORIZED # If this user didn't submit for this round, don't allow them to vote if not get_my_submission(g.user, submission_period): return redirect(url_for('view_league', league_id=league_id)) # If this user already voted for this round, don't allow them to vote if get_my_vote(g.user, submission_period): return redirect(url_for('view_league', league_id=league_id)) # If this round is no longer accepting votes, redirect if not submission_period.accepting_votes: return redirect(request.referrer) try: votes = json.loads(request.form.get('votes')) comments = json.loads(request.form.get('comments')) except Exception: app.logger.exception( "Failed to load JSON from form with votes: %s", request.form) return 'There was an error processing votes', 500 # Remove all unnecessary zero-values votes = {k: v for k, v in votes.iteritems() if v} comments = {k: v for k, v in comments.iteritems() if v} # Process votes vote = create_or_update_vote(votes, comments, submission_period, league, g.user) # If someone besides owner is voting, notify the owner if not league.has_owner(g.user): owner_user_voted_notification(vote) remaining = submission_period.have_not_voted if not remaining: complete_submission_period.delay(submission_period.id) elif vote.count < 2 and len(remaining) == 1: last_user = remaining[0] user_last_to_vote_notification(last_user, submission_period) track_user_voted(g.user.id, submission_period) if comments: track_user_voted_with_comments(g.user.id, submission_period, len(comments)) return redirect( url_for('view_submission_period', league_id=league_id, submission_period_id=submission_period_id)) except Exception: app.logger.exception('Failed to process votes', extra={ 'user': g.user.id, 'league': league.id, 'round': submission_period_id })
def post_manage_league(league_id): name = request.form.get('league-name') num_tracks = request.form.get('tracks-submitted') upvote_size = request.form.get('point-bank-size') limit_upvotes = request.form.get('limit-upvotes') max_up_per_song = request.form.get('max-points-per-song') allow_downvotes = request.form.get('allow-downvotes') downvote_size = request.form.get('downvote-bank-size') limit_downvotes = request.form.get('limit-downvotes') max_down_per_song = request.form.get('max-downvotes-per-song') user_ids = json.loads(request.form.get('added-members', [])) added_members = [select_user(uid) for uid in user_ids] emails = json.loads(request.form.get('invited-members', [])) deleted_members = json.loads(request.form.get('deleted-members', [])) added_rounds = json.loads(request.form.get('added-rounds', [])) edited_rounds = json.loads(request.form.get('edited-rounds', [])) deleted_rounds = json.loads(request.form.get('deleted-rounds', [])) league = select_league(league_id) if name != league.name: league.name = name update_league(league) league.preferences.track_count = int(num_tracks) league.preferences.point_bank_size = int(upvote_size) league.preferences.max_points_per_song = 0 if limit_upvotes == 'yes': league.preferences.max_points_per_song = int(max_up_per_song or 0) league.preferences.downvote_bank_size = 0 league.preferences.max_downvotes_per_song = 0 if allow_downvotes == 'yes': league.preferences.downvote_bank_size = int(downvote_size or 0) if limit_downvotes == 'yes': league.preferences.max_downvotes_per_song = int(max_down_per_song or 0) upsert_league_preferences(league) for added_member in added_members: add_user(league, added_member.email, notify=True, user=added_member) for email in emails: add_user(league, email, notify=True) for deleted_member in deleted_members: remove_user(league, deleted_member) for added_round in added_rounds: submission_due_date_str = added_round['submission-due-date-utc'] submission_due_date = utc.localize( datetime.strptime(submission_due_date_str, '%m/%d/%y %I%p')) vote_due_date_str = added_round['voting-due-date-utc'] vote_due_date = utc.localize( datetime.strptime(vote_due_date_str, '%m/%d/%y %I%p')) create_submission_period(league, added_round['name'], added_round['description'], submission_due_date, vote_due_date) for edited_round in edited_rounds: submission_due_date_str = edited_round['submission-due-date-utc'] submission_due_date = utc.localize( datetime.strptime(submission_due_date_str, '%m/%d/%y %I%p')) vote_due_date_str = edited_round['voting-due-date-utc'] vote_due_date = utc.localize( datetime.strptime(vote_due_date_str, '%m/%d/%y %I%p')) round = select_round(edited_round['id']) if not round: continue round.league = league update_submission_period(edited_round['id'], edited_round['name'], edited_round['description'], submission_due_date, vote_due_date, submission_period=round) for deleted_round in deleted_rounds: try: remove_submission_period(deleted_round) except Exception as e: app.logger.warning('Error while attempting to delete round %s: %s', deleted_round, str(e)) if league.scoreboard: league = select_league(league_id) calculate_league_scoreboard(league) app.logger.info('User modified league', extra={ 'league': league.id, 'user': g.user.id }) return redirect(url_for('view_league', league_id=league_id))
def submit(league_id, submission_period_id): try: league = select_league(league_id) submission_period = next((sp for sp in league.submission_periods if sp.id == submission_period_id), None) if not league or not submission_period: return "No submission period or league", httplib.INTERNAL_SERVER_ERROR if not league.has_user(g.user): return "Not a member of this league", httplib.UNAUTHORIZED if not submission_period.accepting_submissions: return redirect(request.referrer) try: tracks = json.loads(request.form.get('songs')) warned_artists = json.loads( request.form.get('duplicate-artists') or '[]') warned_repeats = json.loads( request.form.get('repeat-submissions') or '[]') except Exception: app.logger.exception( "Failed to load JSON from form with submit: %s", request.form) return 'There was an error processing your submission', 500 if len(filter(None, tracks)) != len(tracks): return redirect(request.referrer) # Don't allow user to submit duplicate tracks if len(tracks) != len(set(tracks)): return redirect(request.referrer) # Don't include user's own previous submission when checking duplicates my_submission = get_my_submission(g.user, submission_period) their_tracks = [] if submission_period.all_tracks: their_tracks = set(submission_period.all_tracks) if my_submission is not None: their_tracks.difference_update(set(my_submission.tracks)) their_tracks = list(their_tracks) s_tracks = tracks + their_tracks s_tracks = g.spotify.tracks(s_tracks).get('tracks') my_tracks, their_tracks = s_tracks, [] if len(s_tracks) > len(tracks): my_tracks = s_tracks[:len(tracks)] their_tracks = s_tracks[len(tracks):] # Don't allow user to submit already submitted track, album or artist duplicate_tracks = check_duplicate_tracks(my_tracks, their_tracks) duplicate_artists = check_duplicate_artists(my_tracks, their_tracks) repeat_submissions = check_repeat_submissions(g.user.id, tracks, league_id) proceeding_dups = set(warned_artists).intersection( set(duplicate_artists)) if proceeding_dups: duplicate_artists = list( set(duplicate_artists) - set(warned_artists)) track_user_proceeded_duplicate_artist(g.user.id, submission_period, list(proceeding_dups)) proceeding_repeats = set(warned_repeats).intersection( set(repeat_submissions)) if proceeding_repeats: repeat_submissions = list( set(repeat_submissions) - set(warned_repeats)) track_user_proceeded_repeat_submission(g.user.id, submission_period, list(proceeding_repeats)) if duplicate_tracks or duplicate_artists or repeat_submissions: if duplicate_tracks: track_user_submitted_duplicate_song(g.user.id, submission_period, duplicate_tracks) # elif duplicate_albums: # track_user_submitted_duplicate_album(g.user.id, submission_period, duplicate_albums) elif duplicate_artists: track_user_submitted_duplicate_artist(g.user.id, submission_period, duplicate_artists) elif repeat_submissions: track_user_submitted_repeat_submission(g.user.id, submission_period, repeat_submissions) return render_template('submit/page.html', user=g.user, league=league, round=submission_period, previous_tracks=tracks, duplicate_songs=duplicate_tracks, duplicate_albums=[], duplicate_artists=duplicate_artists, repeat_submissions=repeat_submissions, access_token=session['access_token']) # Create a new submission on the round as current user submission = create_or_update_submission(tracks, submission_period, league, g.user) # If someone besides owner is submitting, notify the owner if not league.has_owner(g.user): owner_user_submitted_notification(submission) remaining = submission_period.have_not_submitted if not remaining: app.logger.warning('No remaining members to submit', extra={'round': submission_period_id}) # If this is not the first round, roll forward if len(league.submission_periods) > 1: app.logger.warning('This is not the only round', extra={'round': submission_period_id}) if league.submission_periods[0].id != submission_period_id: # This makes the request a little heavy for the final submitter, # but asyncing means the submitter gets a playlist button but no playlist. app.logger.warning( 'This is not the first round, completing the submission process', extra={'round': submission_period_id}) complete_submission_process(submission_period.id) # Don't send submission reminder if this user is resubmitting. In this # case, the last user to submit will have already gotten a notification. elif submission.count < 2 and len(remaining) == 1: last_user = remaining[0] user_last_to_submit_notification(last_user, submission_period) track_user_submitted(g.user.id, submission_period) return redirect(url_for('view_league', league_id=league_id)) except Exception: app.logger.exception('Failed to process submissions', extra={ 'user': g.user.id, 'league': league_id, 'round': submission_period_id, 'form_values': request.form }) return 'There was an error processing your submission', 500
def admin_league(league_id): league = select_league(league_id) return {'user': g.user, 'league': league}
def view_leaderboard(league_id, **kwargs): league = select_league(league_id) return {'user': g.user, 'league': league}
def create_spotify_playlist(league_id): league = select_league(league_id) if league and league.has_owner(g.user): playlist = create_or_update_playlist(league.current_submission_period) return redirect(playlist.get('external_urls').get('spotify')) return redirect(url_for('view_league', league_id=league_id))
def view_playlist(league_id): league = select_league(league_id) if league and league.playlist_url: return redirect(league.playlist_url) return redirect(url_for('view_league', league_id=league_id))