class ControlledView(View): http_method_names = ['post', 'get'] access_manager = acl.AccessManager() def get(self, request): return self.pre_process(request, request.GET) def post(self, request): return self.pre_process(request, request.POST) def pre_process(self, request, input_data: dict): """ Validates input and attempts to preform work() logic. Returns the correct JsonResponse """ # Authenticate Access if not self.authenticate(request): error_response = self.return_error_response(request) if error_response is None: raise Http404() return error_response try: return self.process(request, input_data) except Exception as e: error = InternalServerError(e) request_info = self.format_request_info(request, input_data) logging.error(request_info) error.log() if not settings.DEBUG: error.email_log_to_dev(request_info=request_info, user=request.user) return error.json_response(include_message=False) def authenticate(self, request): """ To be overridden if necessary. Should still be called with super """ return self.access_manager.check_user(request.user) def return_error_response(self, request) -> HttpResponse: pass def process(self, request, input_data): pass @staticmethod def format_request_info(request, input_data): return '\tRequest: {}\n\tRaw Input: {}'.format(str(request), str(input_data))
class DefendStartView(ApiView): request_form_class = RequestForm access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request, req: dict, res: dict): # Get the attempt attempt = StealAttempt.objects.filter( id=req['steal_attempt_id'], status=StealAttempt.Status.WAITING_FOR_DEFENSE_START, victim__player__user=request.user).all() if len(attempt) == 0: raise ValidationError('Invalid steal_attempt_id') attempt = attempt[0] # Update the status attempt.status = StealAttempt.Status.WAITING_FOR_DEFENSE_END attempt.save()
class AttackEndView(ApiView): request_form_class = RequestForm access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request, req: dict, res: dict): # Get the attempt attempt = StealAttempt.objects.filter( id=req['steal_attempt_id'], status=StealAttempt.Status.WAITING_FOR_THIEF_END, thief__player__user=request.user).all() if len(attempt) == 0: raise ValidationError('Invalid steal_attempt_id') attempt = attempt[0] # Save the results attempt.thief_score = req['score'] attempt.status = StealAttempt.Status.WAITING_FOR_DEFENSE_START attempt.save()
class PageView(View): template_name = None context = None access_manager = acl.AccessManager() allowed_after_current_hackathon_ends = True def __init__(self, **kwargs): super().__init__(**kwargs) self.args = list() self.kwargs = dict() def get(self, request, *args, **kwargs): self.args = args self.kwargs = kwargs # Authenticate Access if not self.authenticate(request): # Access denied return redirect(self.get_access_denied_redirect_url(request)) self.work(request) return render(request, template_name=self.template_name, context=None) def work(self, request): """ To be overridden. Preform any extra logic here. Fill context here for rendering. If any context data could be added to the template later using the API with javascript, do that instead of grabbing it here. This should only be needed when it determines a major page layout feature that would look weird not initialized on html load. """ pass def authenticate(self, request): """ To be overridden if necessary. Should still be called with super """ return self.access_manager.check_user(request.user) @staticmethod def get_access_denied_redirect_url(request): """ May be overwritten if desired """ if request.user.is_authenticated: return '/user/profile/?accessDenied=true' else: return '/user/login/?accessDenied=true&path=' + request.path
class StartView(ApiView): request_form_class = RequestForm access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request: HttpRequest, req: dict, res: dict): # Retrieve the game game = Game.objects.filter(id=req['game_id'], creator__user=request.user, status=Game.Status.START_PENDING).all() if len(game) == 0: raise ValidationError('Invalid game_id') game = game[0] # Start the game game.status = Game.Status.ACTIVE game.start_time = timezone.now() game.end_time = game.start_time + timedelta( minutes=game.duration_in_minutes) game.save()
class DefendEndView(ApiView): request_form_class = RequestForm access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request, req: dict, res: dict): # Get the attempt attempt = StealAttempt.objects.filter( id=req['steal_attempt_id'], status=StealAttempt.Status.WAITING_FOR_DEFENSE_END, victim__player__user=request.user).all() if len(attempt) == 0: raise ValidationError('Invalid steal_attempt_id') attempt = attempt[0] # Finalize the attempt attempt.victim_score = req['score'] attempt.coins_stolen = attempt.calculate_coins_stolen() attempt.save() finalize_steal_attempt(attempt)
class PlayersView(ApiView): request_form_class = RequestForm response_form_class = ResponseForm access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request, req: dict, res: dict): # Get the game game = Game.objects.filter(id=req['game_id']).all() if len(game) == 0: raise ValidationError('Invalid game_id') game = game[0] player_instances = [] for pi in PlayerInstance.objects.filter(game=game).all(): player_instances.append({ 'id': pi.id, 'coins': pi.coins, 'username': pi.player.user.username }) res['player_instances'] = player_instances
class GoalView(ApiView): request_form_class = RequestForm response_form_class = ResponseForm access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request, req: dict, res: dict): goal = Goal.objects.filter(id=req['goal_id'], user=request.user).all() if len(goal) == 0: raise ValidationError('Invalid goal_id') goal = goal[0] # Breadth-first sum of child goals' tasks total_child_tasks = 0 total_child_tasks_completed = 0 goal_q = Queue() goal_q.put(goal) while not goal_q.empty(): curr_goal = goal_q.get() # Add counts for this goal tasks = Task.objects.filter(parent_goal=curr_goal) total_child_tasks += tasks.count() total_child_tasks_completed += tasks.filter(completed=True).count() # Add any sub goals to queue for new_goal in Goal.objects.filter(parent_goal=curr_goal).all(): goal_q.put(new_goal) # Make sure completed is accurate completed = total_child_tasks == 0 or total_child_tasks_completed /total_child_tasks == 1 if goal.completed is not completed: goal.completed = completed goal.save() # Set response res['title'] = goal.title res['completed'] = completed res['total_child_tasks'] = total_child_tasks res['total_child_tasks_completed'] = total_child_tasks_completed
class GoalView(ApiView): request_form_class = RequestForm response_form_class = ResponseForm access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request, req: dict, res: dict): parent_goal = None if req['parent_goal_id'] is not None: parent_goal = Goal.objects.filter(id=req['parent_goal_id'], user=request.user).all() if len(parent_goal) == 0: raise ValidationError('Invalid parent_goal_id') parent_goal = parent_goal[0] goal = Goal.objects.create(parent_goal=parent_goal, user=request.user, title=req['title'], description=req['description'], default_location=req['default_location'], default_priority=req['default_priority']) res['goal_id'] = goal.id
class AttackStartView(ApiView): request_form_class = RequestForm response_form_class = ResponseForm access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request, req: dict, res: dict): # Get the game and players game = get_valid_game_with_status(game_id=req['game_id'], status=Game.Status.ACTIVE) victim = get_valid_player_instance(game, req['victim_instance_id'], 'Invalid victim_instance_id') thief = get_valid_player_instance_from_user( game, request.user, 'Invalid thief, player not in game') # Make sure not self if victim.id == thief.id: raise ValidationError('Cannot steal from self self') # Make sure victim is in range of attacker dist = location.distance_in_meters(thief.player, victim.player) if dist > game.maximum_steal_distance_in_meters: raise ValidationError('Victim out of steal range') # Make sure thief hasn't already attacked them recently if has_attacked_player_recently(game=game, thief=thief, victim=victim): raise ValidationError( 'Cannot steal from player, you have already attempted a steal from them recently' ) # All good, start the steal attempt = StealAttempt.objects.create(game=game, thief=thief, victim=victim) res['steal_attempt_id'] = attempt.id
class LogOutView(ApiView): http_method_names = ['post', 'get'] access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request, req, res): logout(request=request)
class ApiView(View): http_method_names = ['post'] # Override to allow GET request_form_class = forms.Form # Override each time response_form_class = forms.Form # Override each time access_manager = acl.AccessManager() allowed_after_current_hackathon_ends = True validate_response = False def __init__(self, **kwargs): super().__init__(**kwargs) self.kwargs = list() self.args = list() def get(self, request: HttpRequest, *args, **kwargs): self.args = args self.kwargs = kwargs return self.process(request, request.GET) def post(self, request: HttpRequest, *args, **kwargs): self.args = args self.kwargs = kwargs return self.process(request, request.POST) def process(self, request: HttpRequest, input_data: dict): """ Validates input and attempts to preform work() logic. Returns the correct JsonResponse """ # Authenticate Access if not self.authenticate(request): return JsonResponse({'cause': _('Unauthorized')}, status=401) req = None res = None # Preform request try: # Validate & clean request request_form = self.request_form_class(input_data, request.FILES) if not request_form.is_valid(): raise ExternalUserError( ValidationError(request_form.errors.as_data())) req = request_form.cleaned_data res = {} # Preform desired api task and populate response (res) object try: self.work(request, req, res) except ValidationError as e: raise ExternalUserError(e) # Validate response response_form = self.response_form_class(res) if not response_form.is_valid() and self.validate_response: raise InternalServerError( ValidationError(response_form.errors.as_data())) res = response_form.cleaned_data # Successful api call! return JsonResponse(res) except ExternalUserError as error: if settings.DEBUG: logging.error( format_request_info(request, input_data, res, req)) error.log() return error.json_response() except InternalServerError as error: request_info = format_request_info(request, input_data, res, req) logging.error(request_info) error.log() if not settings.DEBUG: error.email_log_to_dev(request_info=request_info, user=request.user) return error.json_response(include_message=False) except Exception as e: error = InternalServerError(e) request_info = format_request_info(request, input_data, res, req) logging.error(request_info) error.log() if not settings.DEBUG: error.email_log_to_dev(request_info=request_info, user=request.user) return error.json_response(include_message=False) def work(self, request: HttpRequest, req: dict, res: dict): """ Preforms api request logic :param request Django request object (only use this if necessary) :param req cleaned and valid request data :param res response data to be checked after this call """ pass def authenticate(self, request): """ To be overridden if necessary. Should still be called with super """ return self.access_manager.check_user(request.user)
class FindNearbyPlayersView(ApiView): request_form_class = RequestForm response_form_class = ResponseForm access_manager = acl.AccessManager(acl_accept=[acl.groups.USER]) def work(self, request, req: dict, res: dict): # Get the game game = Game.objects.filter(id=req['game_id']).all() if len(game) == 0: raise ValidationError('Invalid game_id') game = game[0] if game.status != Game.Status.ACTIVE: raise ValidationError('Game not active') # Get player & update location player = Player.objects.get(user=request.user) player.location_lat = req['location_lat'] player.location_lng = req['location_lng'] player.location_updated_at = timezone.now() player.save() # Get pi thief_instance = PlayerInstance.objects.filter(game=game, player=player).all() if len(thief_instance) == 0: raise ValidationError('Invalid game, not a player in game ' + game.name) thief_instance = thief_instance[0] mvd = game.max_view_distance_in_meters() msd = game.maximum_steal_distance_in_meters # Only pull nearby players from database, then check each more precisely spread = location.CardinalSpread(origin_lat=float(player.location_lat), origin_lng=float(player.location_lng), radius_in_meters=float(mvd)) nearby_pis = PlayerInstance.objects.filter( game=game, player__location_lat__lt=spread.north.lat, player__location_lat__gt=spread.south.lat, player__location_lng__lt=spread.east.lng, player__location_lng__gt=spread.west.lng).all() pis_in_steal_range = [] pis_in_view_range = [] pis_in_cool_down = [] for pi_obj in nearby_pis: if not pi_obj.player.has_valid_location(): continue dist = location.distance_in_meters(player, pi_obj.player) if dist > mvd: continue pi = { 'distance_in_meters': round(dist, 3), 'game_id': pi_obj.game.id, 'player_instance_id': pi_obj.id, 'username': pi_obj.player.user.username, 'location_lat': float(pi_obj.player.location_lat), 'location_lng': float(pi_obj.player.location_lng) } if dist <= msd: # Check for a cool down # min_recent_steal_time = timezone.now() - timedelta(seconds=game.steal_cool_down_seconds) # recent_attempts = StealAttempt.objects.filter( # game=game, thief__player=player, victim=pi, created__gte=min_recent_steal_time # ).all() # if len(recent_attempts) > 0: # cde = recent_attempts[0].created + timedelta(seconds=game.steal_cool_down_seconds) # pi['cool_down_end_time'] = cde.isoformat() # pis_in_cool_down.append(pi) # else: pis_in_steal_range.append(pi) else: pis_in_view_range.append(pi) res['pis_in_steal_range'] = pis_in_steal_range res['pis_in_view_range'] = pis_in_view_range res['steal_radius_in_meters'] = msd res['cardinal_spread'] = spread.to_dict()