def apply_comment(): """ Applies the supplied comment to the desired competition event for this user. """ # Extract JSON solve data, deserialize to dict, and verify that all expected fields are present solve_data = json.loads(request.data) if not all(key in solve_data for key in (COMP_EVENT_ID, COMMENT)): return (ERR_MSG_MISSING_INFO, HTTPStatus.BAD_REQUEST) # Extract all the specific fields out of the solve data dictionary comp_event_id = solve_data[COMP_EVENT_ID] comment = solve_data[COMMENT] # Retrieve the specified competition event comp_event = get_comp_event_by_id(comp_event_id) if not comp_event: return (ERR_MSG_NO_SUCH_EVENT.format(comp_event_id), HTTPStatus.NOT_FOUND) # Verify that the competition event belongs to the active competition. comp = comp_event.Competition if not comp.active: return (ERR_MSG_INACTIVE_COMP, HTTPStatus.BAD_REQUEST) # Retrieve the user's results record for this event user_event_results = get_event_results_for_user(comp_event_id, current_user) if (not user_event_results) or (not user_event_results.solves): return (ERR_MSG_NO_RESULTS.format(comp_event_id), HTTPStatus.NOT_FOUND) # Apply the new comment and save the results user_event_results.comment = comment save_event_results(user_event_results) return timer_page(comp_event_id, gather_info_for_live_refresh=True)
def toggle_prev_penalty(): """ Toggles the either the DNF or +2 status of the last solve for the specified user and competition event. """ # Extract JSON solve data, deserialize to dict, and verify that all expected fields are present solve_data = json.loads(request.data) if not all(key in solve_data for key in (COMP_EVENT_ID, )): return (ERR_MSG_MISSING_INFO, HTTPStatus.BAD_REQUEST) # Extract all the specific fields out of the solve data dictionary comp_event_id = solve_data[COMP_EVENT_ID] penalty_to_toggle = solve_data[PENALTY_TO_TOGGLE] # Retrieve the specified competition event comp_event = get_comp_event_by_id(comp_event_id) if not comp_event: return (ERR_MSG_NO_SUCH_EVENT.format(comp_event_id), HTTPStatus.NOT_FOUND) # If this is FMC, this isn't valid if comp_event.Event.name == 'FMC': return (ERR_MSG_NOT_VALID_FOR_FMC, HTTPStatus.BAD_REQUEST) # Verify that the competition event belongs to the active competition. comp = comp_event.Competition if not comp.active: return (ERR_MSG_INACTIVE_COMP, HTTPStatus.BAD_REQUEST) # Retrieve the user's results record for this event user_event_results = get_event_results_for_user(comp_event_id, current_user) if (not user_event_results) or (not user_event_results.solves): return (ERR_MSG_NO_RESULTS.format(comp_event_id), HTTPStatus.NOT_FOUND) # Grab the last completed solve previous_solve = user_event_results.solves[-1] if penalty_to_toggle == PENALTY_DNF: # Toggle DNF # If the solve now has DNF, ensure it doesn't also have +2 previous_solve.is_dnf = not previous_solve.is_dnf if previous_solve.is_dnf: previous_solve.is_plus_two = False else: # Toggle +2 # If the solve now has +2, ensure it doesn't also have DNF previous_solve.is_plus_two = not previous_solve.is_plus_two if previous_solve.is_plus_two: previous_solve.is_dnf = False # Process through the user's event results, ensuring PB flags, best single, average, overall # event result, etc are all up-to-date. process_event_results(user_event_results, comp_event, current_user) save_event_results(user_event_results) return timer_page(comp_event_id, gather_info_for_live_refresh=True)
def reprocess_results_for_user_and_comp_event(username, comp_event_id): """ Reprocesses the event results for the specified user and competition event. """ user = get_user_by_username(username) if not user: print("Oops, that user doesn't exist.") comp_event = get_comp_event_by_id(comp_event_id) if not comp_event: print("Oops, that comp event doesn't exist.") results = get_event_results_for_user(comp_event_id, user) results = process_event_results(results, comp_event, user) save_event_results(results)
def __retrieve_target_solve(solve_data, user): """ Utility method to retrieve the specified solve (by solve id and competition event id). Validates the solve belongs to an active competition, and belongs to the specified user. Returns the following shapes: ((target_solve, user_event_results, comp_event), None, None) if the solve exists, in the active comp, for the specified user. (None, err_msg, http_status_code) if something goes wrong during retrieval. """ # Extract JSON solve data, deserialize to dict, and verify that all expected fields are present if not all(key in solve_data for key in (SOLVE_ID, COMP_EVENT_ID)): return None, ERR_MSG_MISSING_INFO, HTTPStatus.BAD_REQUEST # Extract all the specific fields out of the solve data dictionary solve_id = solve_data[SOLVE_ID] comp_event_id = solve_data[COMP_EVENT_ID] # Retrieve the specified competition event comp_event = get_comp_event_by_id(comp_event_id) if not comp_event: return None, ERR_MSG_NO_SUCH_EVENT.format( comp_event_id), HTTPStatus.NOT_FOUND # Verify that the competition event belongs to the active competition. comp = comp_event.Competition if not comp.active: return ERR_MSG_INACTIVE_COMP, HTTPStatus.BAD_REQUEST # Retrieve the user's results record for this event user_event_results = get_event_results_for_user(comp_event_id, user) if (not user_event_results) or (not user_event_results.solves): return None, ERR_MSG_NO_RESULTS.format( comp_event_id), HTTPStatus.NOT_FOUND # Find the target solve in the user's results target_solve = None for solve in user_event_results.solves: if solve.id == solve_id: target_solve = solve break # If we didn't find the target solve, let the user know if not target_solve: return None, ERR_MSG_NO_SOLVE.format( solve_id, user.username), HTTPStatus.NOT_FOUND return (target_solve, user_event_results, comp_event), None, None
def delete_prev_solve(): """ Deletes the last completed solve of the specified competition event for this user. """ # Extract JSON solve data, deserialize to dict, and verify that all expected fields are present solve_data = json.loads(request.data) if not all(key in solve_data for key in (COMP_EVENT_ID, )): return (ERR_MSG_MISSING_INFO, HTTPStatus.BAD_REQUEST) # Extract all the specific fields out of the solve data dictionary comp_event_id = solve_data[COMP_EVENT_ID] # Retrieve the specified competition event comp_event = get_comp_event_by_id(comp_event_id) if not comp_event: return (ERR_MSG_NO_SUCH_EVENT.format(comp_event_id), HTTPStatus.NOT_FOUND) # Verify that the competition event belongs to the active competition. comp = comp_event.Competition if not comp.active: return (ERR_MSG_INACTIVE_COMP, HTTPStatus.BAD_REQUEST) # Retrieve the user's results record for this event user_event_results = get_event_results_for_user(comp_event_id, current_user) if (not user_event_results) or (not user_event_results.solves): return (ERR_MSG_NO_RESULTS.format(comp_event_id), HTTPStatus.NOT_FOUND) # If the results only have one solve (which we're about to delete), we need to delete the # results entirely do_delete_user_results_after_solve = len(user_event_results.solves) == 1 # Grab the last completed solve and delete it previous_solve = user_event_results.solves[-1] delete_user_solve(previous_solve) # If no more solves left, just delete the whole results record if do_delete_user_results_after_solve: delete_event_results(user_event_results) # Otherwise process through the user's event results, ensuring PB flags, best single, average, # overall event result, etc are all up-to-date. else: process_event_results(user_event_results, comp_event, current_user) save_event_results(user_event_results) return timer_page(comp_event_id, gather_info_for_live_refresh=True)
def post_solve(): """ Saves a solve. Ensures the user has UserEventResults for this event, associated this solve with those results, and processes the results to make sure all relevant data is up-to-date. """ # Extract JSON solve data, deserialize to dict, and verify that all expected fields are present solve_data = json.loads(request.data) if not all(key in solve_data for key in EXPECTED_FIELDS): return (ERR_MSG_MISSING_INFO, HTTPStatus.BAD_REQUEST) # Extract all the specific fields out of the solve data dictionary is_dnf = solve_data[IS_DNF] is_plus_two = solve_data[IS_PLUS_TWO] scramble_id = solve_data[SCRAMBLE_ID] comp_event_id = solve_data[COMP_EVENT_ID] centiseconds = solve_data[CENTISECONDS] is_inspection_dnf = solve_data.get(IS_INSPECTION_DNF, False) fmc_comment = solve_data.get(FMC_COMMENT, '') # If the solve time isn't positive, don't save the solve. Let the user know that a negative # time isn't allowed. if centiseconds <= 0: return (ERR_MSG_NON_POSITIVE_TIME, HTTPStatus.BAD_REQUEST) # If the submitted solve is for a scramble the user already has a solve for, # don't take any further action to persist a solve, just return. User probably # is user manual time entry and pressed enter twice accidentally in quick succession if get_user_solve_for_scramble_id(current_user.id, scramble_id): return timer_page(comp_event_id, gather_info_for_live_refresh=True) # Retrieve the specified competition event comp_event = get_comp_event_by_id(comp_event_id) if not comp_event: return (ERR_MSG_NO_SUCH_EVENT.format(comp_event_id), HTTPStatus.NOT_FOUND) # Verify that the competition event belongs to the active competition. comp = comp_event.Competition if not comp.active: return (ERR_MSG_INACTIVE_COMP, HTTPStatus.BAD_REQUEST) # Double-check that if the solve is MBLD, the number of attempted cubes is > 1 if comp_event.Event.name == "MBLD": _, num_attempted = get_mbld_successful_and_attempted(centiseconds) if num_attempted < 2: return (ERR_MSG_MBLD_TOO_FEW_ATTEMPTED, HTTPStatus.BAD_REQUEST) # Retrieve the user's results record for this event if they exist, or else create a new record user_event_results = get_event_results_for_user(comp_event_id, current_user) if not user_event_results: user_event_results = UserEventResults(comp_event_id=comp_event_id, user_id=current_user.id, comment='') # Create the record for this solve and associate it with the user's event results solve = UserSolve(time=centiseconds, is_dnf=is_dnf, is_plus_two=is_plus_two, scramble_id=scramble_id, is_inspection_dnf=is_inspection_dnf, fmc_explanation=fmc_comment) user_event_results.solves.append(solve) # Process through the user's event results, ensuring PB flags, best single, average, overall # event result, etc are all up-to-date. process_event_results(user_event_results, comp_event, current_user) save_event_results(user_event_results) return timer_page(comp_event_id, gather_info_for_live_refresh=True)
def timer_page(comp_event_id, gather_info_for_live_refresh=False): """ It's the freakin' timer page. """ # Retrieve the specified competition event comp_event = get_comp_event_by_id(comp_event_id) if not comp_event: return (ERR_MSG_NO_SUCH_EVENT.format(comp_event_id=comp_event_id), 404) # Verify it's for the actve competition comp = comp_event.Competition if not comp.active: return (ERR_MSG_INACTIVE_COMP, 400) event_name = comp_event.Event.name event_format = comp_event.Event.eventFormat event_description = comp_event.Event.description # Get the user's settings, and specifically pull the setting to determine # whether or not to hide the scramble preview settings = __get_user_settings(current_user) hide_scramble_preview = settings[SettingCode.HIDE_SCRAMBLE_PREVIEW] show_shapes_background = settings[SettingCode.ENABLE_MOVING_SHAPES_BG] # Grab the user's event results (if any) user_results = get_event_results_for_user(comp_event_id, current_user) # Get a list of user-readable user solve times user_solves, last_solve = __build_user_solves_list( user_results, comp_event.Event.totalSolves, comp_event.scrambles) # Split last_solve into min/seconds and centiseconds # TODO: comment this more thoroughly, and put into its own function last_seconds = DEFAULT_SECONDS last_centis = DEFAULT_CENTIS hide_timer_dot = False if last_solve: if event_name == EVENT_FMC.name: hide_timer_dot = True last_seconds = last_solve last_centis = '' elif event_name == EVENT_MBLD.name: pass else: if last_solve == DNF: hide_timer_dot = True last_seconds = DNF last_centis = '' else: last_seconds = last_solve.split('.')[0] last_centis = last_solve.split('.')[1] # Determine the scramble ID, scramble text, and index for the next unsolved scramble. # If all solves are done, substitute in some sort of message in place of the scramble text scramble_info = __determine_scramble_id_text_index(user_results, user_solves, comp_event.scrambles, event_name, event_format) scramble_id, scramble_text, scramble_index = scramble_info # Build up the page title, consisting of the event name and competition title alternative_title = PAGE_TITLE_TEMPLATE.format(event_name=event_name, comp_title=comp.title) # Determine if we should display a scramble preview for this event show_scramble_preview = event_name not in NO_SCRAMBLE_PREVIEW_EVENTS # Determine button states button_state_info = __determine_button_states(user_results, scramble_index) # Grab the comment (if any) from the user results (if any), otherwise default to an empty string comment = user_results.comment if user_results else '' # Determine if the event has been completed by this user is_complete_flag = user_results.is_complete if user_results else False num_solves_done = len(user_results.solves) if user_results else 0 is_complete = __determine_is_complete(is_complete_flag, event_format, num_solves_done) # Determine the timer page subtype (timer, manual time entry, FMC manual entry, or MBLD) page_subtype = __determine_page_subtype(event_name, settings) if gather_info_for_live_refresh: # Only a caller coming from one of the persistence routes should go through this path. # Build up a dictionary of relevant information so the timer page can be re-rendered # with up-to-date information about the state of the timer page timer_page_render_info = { 'button_state_info': button_state_info, 'scramble_text': scramble_text, 'scramble_id': scramble_id, 'user_solves': user_solves, 'last_seconds': last_seconds, 'last_centis': last_centis, 'hide_timer_dot': hide_timer_dot, 'is_complete': is_complete, 'comment': comment, 'last_solve': last_solve } return json.dumps(timer_page_render_info) return render_template(TIMER_TEMPLATE_MOBILE_MAP[request.MOBILE], scramble_text=scramble_text, scramble_id=scramble_id, comp_event_id=comp_event_id, event_name=event_name, alternative_title=alternative_title, user_solves=user_solves, button_states=button_state_info, show_scramble_preview=show_scramble_preview, last_solve=last_solve, last_seconds=last_seconds, last_centis=last_centis, hide_timer_dot=hide_timer_dot, comment=comment, is_complete=is_complete, settings=settings, page_subtype=page_subtype, hide_scramble_preview=hide_scramble_preview, show_shapes_background=show_shapes_background, event_description=event_description)