class PerformanceRequisicao(models.Model): data_hora_requisicao = DateTimeFieldTz('Data/hora da requisição') data_hora_resposta = DateTimeFieldTz('Data/hora da resposta') url = models.CharField('URL', max_length=255) user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
class PermissaoAumentoRange(models.Model): """Registro de permissão para aumentar range de desafio caso os desafiáveis não estejam presentes""" # Período de validade da permissão, em horas PERIODO_VALIDADE = 2 AUMENTO_RANGE = 3 MENSAGEM_SUCESSO_PERMISSAO_AUMENTO_RANGE = 'Permissão de aumento de range concedida com sucesso' MENSAGEM_ERRO_JOGADOR_IGUAL_ADMIN = 'Usuário não pode conceder permissão a si mesmo' MENSAGEM_ERRO_JOGADOR_JA_POSSUI_PERMISSAO_VALIDA = 'Jogador já possui permissão válida' MENSAGEM_ERRO_DESAFIANTE_MUITO_ABAIXO_DESAFIADO = f'Desafiante está mais de ' \ f'{AUMENTO_RANGE + DesafioLadder.LIMITE_POSICOES_DESAFIO} posições abaixo do desafiado' MENSAGEM_SUCESSO_REMOCAO_PERMISSAO = 'Permissão de aumento de range removida com sucesso' MENSAGEM_ERRO_DESAFIO_UTILIZANDO_PERMISSAO = 'Já existe um desafio válido utilizando a permissão' jogador = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE, related_name='permitido_aumento_range') admin_permissor = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE, related_name='permissor_aumento_range') data_hora = DateTimeFieldTz(u'Data e hora da permissão') def is_valida(self, data_hora=None): """Define se permissão é válida na data/hora""" if not data_hora: data_hora = timezone.localtime() valida = (self.data_hora + datetime.timedelta(hours=self.PERIODO_VALIDADE) >= data_hora) if valida: valida = (not DesafioLadder.validados.filter( desafiante=self.jogador, data_hora__gte=self.data_hora, data_hora__lt=data_hora).exists()) return valida
class RemocaoJogador(models.Model): """Registro de remoção de jogador da ladder""" jogador = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE) # Adicionado como datetime para facilitar comparações na hora de calcular ladder data = DateTimeFieldTz('Data da remoção') admin_removedor = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE, related_name='admin_removedor') posicao_jogador = models.SmallIntegerField('Posição durante remoção') remocao_por_inatividade = models.BooleanField( 'Remoção devido a inatividade?', default=False) class Meta(): unique_together = ('jogador', 'data') def __str__(self): return f'{self.jogador}: {self.data}' def is_historico(self): """Define se é histórico""" horario_atual = timezone.localtime() return self.data.month != horario_atual.month or self.data.year != horario_atual.year @property def mes_ano_ladder(self): """Retorna mes e ano de ladder da qual remoção faz parte""" if self.is_historico(): return (self.data.month, self.data.year) return (None, None)
class Feedback(models.Model): avaliador = models.ForeignKey('Jogador', on_delete=models.CASCADE, related_name='avaliador') avaliado = models.ForeignKey('Jogador', on_delete=models.CASCADE, related_name='avaliado') texto = models.CharField('Texto do feedback', max_length=250) data_hora = DateTimeFieldTz(u'Data e hora do feedback', auto_now_add=True)
class DecaimentoJogador(models.Model): """Registro de decaimento de jogador na ladder""" # Quantidade de dias inativo para decair try: PERIODO_INATIVIDADE = ConfiguracaoLadder.buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_PERIODO_INATIVIDADE,]) \ [ConfiguracaoLadder.CONFIGURACAO_PERIODO_INATIVIDADE] except: PERIODO_INATIVIDADE = 30 QTD_POSICOES_DECAIMENTO = 3 # Quantidade de posições a decair por vez # Permitir que primeiro decaimento seja perdoado? try: ABONAR_PRIMEIRO_DECAIMENTO = ConfiguracaoLadder.buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_ABONAR_PRIMEIRO_DECAIMENTO,]) \ [ConfiguracaoLadder.CONFIGURACAO_ABONAR_PRIMEIRO_DECAIMENTO] except: ABONAR_PRIMEIRO_DECAIMENTO = False jogador = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE) # Adicionado como datetime para facilitar comparações na hora de calcular ladder data = DateTimeFieldTz('Data do decaimento') posicao_inicial = models.SmallIntegerField('Posição da qual jogador caiu') qtd_periodos_inatividade = models.SmallIntegerField( 'Quantidade de períodos inativo', validators=[MinValueValidator(1), MaxValueValidator(2)]) class Meta(): unique_together = ('jogador', 'data') def __str__(self): return f'{self.jogador} cai de {self.posicao_inicial} em {self.data.strftime("%d/%m/%Y")}' @staticmethod def alterar_periodo_inatividade(): DecaimentoJogador.PERIODO_INATIVIDADE = ConfiguracaoLadder.buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_PERIODO_INATIVIDADE,]) \ [ConfiguracaoLadder.CONFIGURACAO_PERIODO_INATIVIDADE] @staticmethod def alterar_abonar_primeiro_decaimento(): DecaimentoJogador.ABONAR_PRIMEIRO_DECAIMENTO = ConfiguracaoLadder \ .buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_ABONAR_PRIMEIRO_DECAIMENTO,]) \ [ConfiguracaoLadder.CONFIGURACAO_ABONAR_PRIMEIRO_DECAIMENTO] def is_historico(self): """Define se é histórico""" horario_atual = timezone.localtime() return self.data.month != horario_atual.month or self.data.year != horario_atual.year @property def mes_ano_ladder(self): """Retorna mes e ano de ladder da qual remoção faz parte""" if self.is_historico(): return (self.data.month, self.data.year) return (None, None)
class HistoricoConfiguracaoLadder(models.Model): limite_posicoes_desafio = models.SmallIntegerField( 'Limite de posições para desafiar', blank=True, null=True) melhor_de = models.SmallIntegerField('Formato melhor de', blank=True, null=True) periodo_season = models.SmallIntegerField('Período de uma Season', blank=True, null=True) abonar_primeiro_decaimento = models.BooleanField( 'Abonar primeiro decaimento?', blank=True, null=True) periodo_inatividade = models.SmallIntegerField( 'Período de inatividade para cair', blank=True, null=True) uso_coringa = models.BooleanField('Permitido coringa?', blank=True, null=True) # Guardar responsável pela alteração responsavel = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE, related_name='responsavel_alteracao') data_hora = DateTimeFieldTz(u'Data e hora do resultado', auto_now_add=True)
class DesafioLadder(models.Model): """Desafio para ladder""" try: LIMITE_POSICOES_DESAFIO = ConfiguracaoLadder.buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_LIMITE_POSICOES_DESAFIO,]) \ [ConfiguracaoLadder.CONFIGURACAO_LIMITE_POSICOES_DESAFIO] # Diferença máxima de posições para haver desafio except: LIMITE_POSICOES_DESAFIO = 3 # É permitido usar coringa na Ladder? try: USO_CORINGA = ConfiguracaoLadder.buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_USO_CORINGA,]) \ [ConfiguracaoLadder.CONFIGURACAO_USO_CORINGA] # Diferença máxima de posições para haver desafio except: USO_CORINGA = True PERIODO_ESPERA_MESMOS_JOGADORES = 3 # Quantidade de dias a esperar para refazer um desafio PERIODO_ESPERA_DESAFIO_CORINGA = 60 # Quantidade de dias a esperar para utilizar um coringa try: MELHOR_DE = ConfiguracaoLadder.buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_MELHOR_DE,]) \ [ConfiguracaoLadder.CONFIGURACAO_MELHOR_DE] # Quantidade máxima de lutas que um desafio pode ter except: MELHOR_DE = 3 SCORE_VITORIA = ((MELHOR_DE // 2) + 1) # Score para vitória MENSAGEM_ERRO_DESAFIANTE_MUITO_ABAIXO_DESAFIADO = f'Desafiante está mais de {LIMITE_POSICOES_DESAFIO} posições abaixo do desafiado' MENSAGEM_ERRO_DESAFIANTE_ACIMA_DESAFIADO = 'Desafiante está à frente do desafiado' MENSAGEM_ERRO_MESMO_JOGADOR = 'Desafiante e desafiado não podem ser o mesmo jogador' MENSAGEM_ERRO_PERIODO_ESPERA_MESMOS_JOGADORES = f'Desafio não respeita período mínimo de ' \ f'{PERIODO_ESPERA_MESMOS_JOGADORES} dias entre mesmos jogadores' MENSAGEM_ERRO_PERIODO_ESPERA_CORINGA = f'Ainda não acabou o tempo de {PERIODO_ESPERA_DESAFIO_CORINGA} dias '\ f'para desafiante reutilizar coringa' desafiante = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE, related_name='desafiante') desafiado = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE, related_name='desafiado') desafio_coringa = models.BooleanField(u'É desafio coringa?') score_desafiante = models.SmallIntegerField( u'Vitórias do desafiante', validators=[MinValueValidator(0), MaxValueValidator(SCORE_VITORIA)]) score_desafiado = models.SmallIntegerField( u'Vitórias do desafiado', validators=[MinValueValidator(0), MaxValueValidator(SCORE_VITORIA)]) admin_validador = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE, blank=True, null=True, related_name='admin_validador') data_hora = DateTimeFieldTz(u'Data e hora do resultado') adicionado_por = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE, related_name='criador_desafio') posicao_desafiante = models.SmallIntegerField(u'Posição do desafiante', default=0) posicao_desafiado = models.SmallIntegerField(u'Posição do desafiado', default=0) class Meta(): unique_together = (('desafiante', 'data_hora'), ('desafiado', 'data_hora')) ordering = ('data_hora', ) def __str__(self): return f'{self.id}: {self.data_hora}' @staticmethod def alterar_limite_posicoes_desafio(): DesafioLadder.LIMITE_POSICOES_DESAFIO = ConfiguracaoLadder.buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_LIMITE_POSICOES_DESAFIO,]) \ [ConfiguracaoLadder.CONFIGURACAO_LIMITE_POSICOES_DESAFIO] # Atualizar mensagem DesafioLadder.MENSAGEM_ERRO_DESAFIANTE_MUITO_ABAIXO_DESAFIADO = f'Desafiante está mais de {DesafioLadder.LIMITE_POSICOES_DESAFIO} ' \ 'posições abaixo do desafiado' @staticmethod def alterar_melhor_de(): DesafioLadder.MELHOR_DE = ConfiguracaoLadder.buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_MELHOR_DE,]) \ [ConfiguracaoLadder.CONFIGURACAO_MELHOR_DE] DesafioLadder.SCORE_VITORIA = ((DesafioLadder.MELHOR_DE // 2) + 1) @staticmethod def alterar_uso_coringa(): DesafioLadder.USO_CORINGA = ConfiguracaoLadder.buscar_configuracao([ConfiguracaoLadder.CONFIGURACAO_USO_CORINGA,]) \ [ConfiguracaoLadder.CONFIGURACAO_USO_CORINGA] @property def lutas(self): """Lista de lutas que compõe o desafio""" return [ luta_ladder.luta for luta_ladder in self.lutaladder_set.all().select_related('luta__ganhador') ] def is_cancelado(self): """Define se é um desafio cancelado""" return hasattr(self, 'cancelamentodesafioladder') def is_historico(self): """Define se é histórico""" horario_atual = timezone.localtime() return self.data_hora.month != horario_atual.month or self.data_hora.year != horario_atual.year def is_validado(self): return self.admin_validador_id != None @property def mes_ano_ladder(self): """Retorna mes e ano de ladder da qual desafio faz parte""" if self.is_historico(): return (self.data_hora.month, self.data_hora.year) return (None, None) @property def ladder(self): """Ladder a qual desafio se refere""" if self.is_historico(): mes, ano = self.mes_ano_ladder return HistoricoLadder.objects.filter(ano=ano, mes=mes) return PosicaoLadder.objects def cancelavel_por_jogador(self, jogador): """Determina se jogador pode cancelar desafio""" if self.is_cancelado(): return False # Admin sempre pode cancelar, criador apenas se não validado return jogador.admin or (not self.is_validado() and jogador == self.adicionado_por) def editavel_por_jogador(self, jogador): """Determinar se jogador pode editar desafio""" if self.is_cancelado(): return False # Contanto que não esteja validado, jogador precisa ser admin ou criador return not self.is_validado() and (jogador.admin or jogador == self.adicionado_por) def desafiante_venceu(self): """Retorna se desafiante foi o ganhador""" return self.score_desafiante > self.score_desafiado def fora_da_season(self): """Retorna se desafio é de Season anterior""" season_atual = Season.objects.order_by('-data_inicio')[0] return self.data_hora < season_atual.data_hora_inicio class DesafioLadderManager(models.Manager): """Override do manager padrão""" def na_season(self, season): data_hora_inicio = datetime.datetime(season.data_inicio.year, season.data_inicio.month, season.data_inicio.day) # Se possui data de fim if season.data_fim: data_hora_fim = datetime.datetime(season.data_fim.year, season.data_fim.month, season.data_fim.day) \ + datetime.timedelta(days=1) return self.get_queryset().filter(data_hora__range=[ timezone.make_aware(data_hora_inicio), timezone.make_aware(data_hora_fim, timezone.get_current_timezone()) ]) else: # Se não, buscar apenas a partir da data inicial return self.get_queryset().filter( data_hora__gte=timezone.make_aware(data_hora_inicio)) # Managers class DesafiosValidadosManager(models.Manager): """Manager para trazer desafios validados e não cancelados""" def get_queryset(self): return super().get_queryset().filter( cancelamentodesafioladder__isnull=True, admin_validador__isnull=False) def na_season(self, season): # Se possui data de fim if season.data_fim: data_hora_fim = datetime.datetime(season.data_fim.year, season.data_fim.month, season.data_fim.day) \ + datetime.timedelta(days=1) return self.get_queryset().filter(data_hora__range=[ season.data_hora_inicio, timezone.make_aware(data_hora_fim) ]) else: # Se não, buscar apenas a partir da data inicial return self.get_queryset().filter( data_hora__gte=season.data_hora_inicio) # objects = models.Manager() objects = DesafioLadderManager() validados = DesafiosValidadosManager()
class RegistroTreinamento(models.Model): inicio = DateTimeFieldTz('Data/hora de início') fim = DateTimeFieldTz('Data/hora de fim') jogador = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE)
class Anotacao(models.Model): texto = models.CharField('Texto da anotação', max_length=250) jogador = models.ForeignKey('jogadores.Jogador', on_delete=models.CASCADE) data_hora = DateTimeFieldTz('Data/hora da anotação', auto_now_add=True)
class SugestaoLadder(models.Model): jogador = models.ForeignKey('Jogador', on_delete=models.CASCADE) texto = models.CharField('Texto da sugestão', max_length=1000) data_hora = DateTimeFieldTz(u'Data e hora da sugestão', auto_now_add=True)