diff --git a/main/management/commands/change_models.py b/main/management/commands/change_models.py index bdb3ec4c..947679f7 100644 --- a/main/management/commands/change_models.py +++ b/main/management/commands/change_models.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand -from main.models import Sran_Level, Music +from main.models import Music, Sran_Level class Command(BaseCommand): diff --git a/main/models.py b/main/models.py index 2dfecee2..2d9c68ff 100644 --- a/main/models.py +++ b/main/models.py @@ -1,6 +1,7 @@ +from datetime import datetime, timedelta + from django.db import models from django.utils import timezone -from datetime import datetime, timedelta from users.models import CustomUser @@ -31,7 +32,6 @@ class Meta: class Level(models.Model): level = models.IntegerField('レベル') - # 整数型で返す def int(self): return self.level @@ -79,16 +79,40 @@ class Meta: class Medal(models.Model): + class ClearStatus(models.TextChoices): + NO_PLAY = 'no-play', 'No Play' + PERFECT = 'perfect', 'Perfect' + FULL_COMBO = 'fullcombo', 'Full Combo' + HARD_CLEARED = 'hard-cleared', 'Hard Cleared' + CLEARED = 'cleared', 'Cleared' + FAILED = 'failed', 'Failed' + EASY_CLEARED = 'easy-cleared', 'Easy Cleared' + medal = models.IntegerField('クリアメダル') music = models.ForeignKey(Music, verbose_name='曲', on_delete=models.CASCADE) user = models.ForeignKey(CustomUser, verbose_name='ユーザー', on_delete=models.PROTECT) updated_at = models.DateTimeField('更新日時', default=datetime.now) - # int型で返す + def get_clear_status(self, bad_count: 'Bad_Count', extra_option: 'Extra_Option') -> ClearStatus: + if self.medal == 1 and bad_count.bad_count == 0: + return self.ClearStatus.PERFECT + elif 2 <= self.medal <= 4 and bad_count.bad_count == 0: + return self.ClearStatus.FULL_COMBO + elif 5 <= self.medal <= 7: + if extra_option and extra_option.is_hard(): + return self.ClearStatus.HARD_CLEARED + else: + return self.ClearStatus.CLEARED + elif 8 <= self.medal <= 10: + return self.ClearStatus.FAILED + elif self.medal == 11: + return self.ClearStatus.EASY_CLEARED + else: + return self.ClearStatus.NO_PLAY + def int(self): return self.medal - # 文字列で返す def output_str(self) -> str: if self.medal == 1: return '金' @@ -117,7 +141,6 @@ def output_str(self) -> str: else: return '' - # 更新日時をJSTで返す def updated_at_jst(self): return self.updated_at + timedelta(hours=9) @@ -140,14 +163,12 @@ class Bad_Count(models.Model): user = models.ForeignKey(CustomUser, verbose_name='ユーザー', on_delete=models.PROTECT) updated_at = models.DateTimeField('更新日時', default=datetime.now) - # 整数型で返す def int(self): return self.bad_count def __str__(self): return str(self.bad_count) - # 更新日時をJSTで返す def updated_at_jst(self): return self.updated_at + timedelta(hours=9) @@ -167,13 +188,11 @@ class Extra_Option(models.Model): user = models.ForeignKey(CustomUser, verbose_name='ユーザー', on_delete=models.PROTECT) updated_at = models.DateTimeField('更新日時', default=datetime.now) - # ハードしているかを判定 def is_hard(self) -> bool: if self.hard: return True return False - # 更新日時をJSTで返す def updated_at_jst(self): return self.updated_at + timedelta(hours=9) diff --git a/main/templates/main/ranking_detail.html b/main/templates/main/ranking_detail.html index ff38e066..908fa1b0 100644 --- a/main/templates/main/ranking_detail.html +++ b/main/templates/main/ranking_detail.html @@ -99,23 +99,7 @@ {% for result in results %} - {% if result.medal.medal == 1 and result.bad_count.bad_count == 0 %} - - {% elif result.bad_count.bad_count == 0 %} - - {% elif result.extra_option.hard %} - - {% else %} - {% if result.medal.medal == 7 or result.medal.medal == 6 or result.medal.medal == 5 %} - - {% elif result.medal.medal == 10 or result.medal.medal == 9 or result.medal.medal == 8 %} - - {% elif result.medal.medal == 11 %} - - {% else %} - - {% endif %} - {% endif %} + {% if result.user == user %} {{ result.rank|default:'-' }} diff --git a/main/views/api.py b/main/views/api.py index 21dd8014..083b1101 100644 --- a/main/views/api.py +++ b/main/views/api.py @@ -27,24 +27,12 @@ def get_clear_status(request: HttpRequest, music_id: int) -> JsonResponse: raise PermissionDenied bad_count = Bad_Count.objects.filter(music_id=music_id, user_id=user_id).first() - medal_obj = Medal.objects.filter(music_id=music_id, user_id=user_id).first() - medal = medal_obj.medal if medal_obj else None - extra_option_exists = Extra_Option.objects.filter(music_id=music_id, user_id=user_id, hard=True).exists() - - clear_status = 'no-play' - if medal is not None: - if medal == 1 and bad_count == 0: - clear_status = 'perfect' - elif medal in [2, 3, 4] and bad_count == 0: - clear_status = 'fullcombo' - elif 1 <= medal <= 7 and extra_option_exists: - clear_status = 'hard-cleared' - elif medal in [5, 6, 7]: - clear_status = 'cleared' - elif medal in [8, 9, 10]: - clear_status = 'failed' - elif medal == 11: - clear_status = 'easy-cleared' + medal = Medal.objects.filter(music_id=music_id, user_id=user_id).first() + extra_option = Extra_Option.objects.filter(music_id=music_id, user_id=user_id, hard=True).first() + + clear_status = Medal.ClearStatus.NO_PLAY.value + if medal: + clear_status = medal.get_clear_status(bad_count=bad_count, extra_option=extra_option).value return JsonResponse({'clear_status': clear_status}) diff --git a/main/views/ranking.py b/main/views/ranking.py index 7277b424..e59a0622 100644 --- a/main/views/ranking.py +++ b/main/views/ranking.py @@ -1,5 +1,5 @@ from django.contrib.auth.decorators import login_required -from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage +from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.http import Http404, HttpRequest, HttpResponse from django.shortcuts import render diff --git a/main/views/ranking_detail.py b/main/views/ranking_detail.py index 6e8e1b32..d8a6fdd7 100644 --- a/main/views/ranking_detail.py +++ b/main/views/ranking_detail.py @@ -1,98 +1,46 @@ -from django.core.exceptions import ObjectDoesNotExist from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, render -from main.models import Music, Medal, Bad_Count, Extra_Option -from users.models import CustomUser +from main.models import Bad_Count, Extra_Option, Medal, Music def ranking_detail(request: HttpRequest, music_id: int) -> HttpResponse: - """ ランキング: 詳細 """ - - def bad_count_rank(bad_count_list_ordered: list[Bad_Count], user: CustomUser) -> int | None: - """ - 指定されたユーザーの順位を返す - @param bad_count_list_ordered: 曲で絞込済みのBAD数リスト(昇順) - @param user: 指定されたユーザー - @return rank: 順位 - """ - if not bad_count_list_ordered: - return None - - bad_count_num = 0 # BAD数の個数 - bad_count_now = -1 # 現在のBAD数 - rank = -1 # ランク - found = False # BAD数を登録済であればTrueを返す - tmp_rank = 0 - - for bad_count in bad_count_list_ordered: - bad_count_before = bad_count_now - bad_count_now = bad_count.bad_count + music = get_object_or_404(Music, pk=music_id) - # BAD数が前後で重複した場合 - if bad_count_now == bad_count_before: - # 指定されたユーザーの記録が見つかれば rank にランクを格納 - if bad_count.user.id == user.id: - found = True - rank = tmp_rank + medals = Medal.objects.filter(music=music).select_related('user') + extra_options = Extra_Option.objects.filter(music=music, hard=True).select_related('user') - bad_count_num += 1 + medals_dict = {medal.user.id: medal for medal in medals} + extra_options_dict = {option.user.id: option for option in extra_options} - # BAD数が重複しなかった場合 - else: - bad_count_num += 1 + bad_count_list = list(Bad_Count.objects.filter(music=music).order_by('bad_count', 'updated_at')) + rank, last_bad_count, results = 1, None, [] - # 一時ランクを更新 - tmp_rank = bad_count_num + for i, bad_count in enumerate(bad_count_list, start=1): + if bad_count.bad_count != last_bad_count: + rank = i + last_bad_count = bad_count.bad_count - # 自分の記録が見つかれば rank にランクを格納 - if bad_count.user.id == user.id: - found = True - rank = bad_count_num + medal = medals_dict.get(bad_count.user.id) + extra_option = extra_options_dict.get(bad_count.user.id) - if found: - return rank + if medal is None: + status = Medal.ClearStatus.NO_PLAY.value else: - return None - - # 曲を取得 - music = get_object_or_404(Music, pk=music_id) - - medal_list = Medal.objects.filter(music=music) - bad_count_list = Bad_Count.objects.filter(music=music).order_by('bad_count', 'updated_at') - extra_option_list = Extra_Option.objects.filter(music=music) - - # 対象曲を記録しているユーザーを取得 - user_id_list = list(bad_count_list.values_list('user', flat=True)) - users = CustomUser.objects.filter(pk__in=user_id_list, is_active=True) + status = medal.get_clear_status(bad_count, extra_option).value - # ランキングを生成 - results = [] - for bad_count in bad_count_list: - selected_user = users.get(pk=bad_count.user.id) - try: - medal = medal_list.get(user=selected_user) - if medal.medal == 12: - medal = None - except ObjectDoesNotExist: - medal = None - try: - extra_option = extra_option_list.get(user=selected_user) - except ObjectDoesNotExist: - extra_option = None - rank = bad_count_rank(bad_count_list, selected_user) results.append({ 'rank': rank, - 'user': selected_user, + 'user': bad_count.user, 'medal': medal, 'bad_count': bad_count, - 'extra_option': extra_option + 'extra_option': extra_option, + 'status': status, }) context = { 'music': music, - 'bad_count_list': bad_count_list, - 'results': results + 'results': results, } return render(request, 'main/ranking_detail.html', context) diff --git a/users/models.py b/users/models.py index da034f72..725aafd4 100644 --- a/users/models.py +++ b/users/models.py @@ -1,5 +1,5 @@ -from django.db import models from django.contrib.auth.models import AbstractUser +from django.db import models class Location(models.Model): @@ -25,13 +25,16 @@ class Meta: class CustomUser(AbstractUser): - player_name = models.CharField('プレイヤー名', max_length=6, null=True, blank=True, help_text='全角ひらがなカタカナ英数字6文字以内') + player_name = models.CharField('プレイヤー名', max_length=6, null=True, blank=True, + help_text='全角ひらがなカタカナ英数字6文字以内') poputomo_id = models.CharField('ポプともID', max_length=12, null=True, blank=True, help_text='半角数字12文字') location = models.ForeignKey(Location, verbose_name='都道府県', null=True, blank=True, on_delete=models.PROTECT) profile = models.TextField('プロフィール', null=True, blank=True) - player_name_privacy = models.IntegerField('プレイヤー名', default=1, help_text='ランキングにプレイヤー名とプロフィールページへのリンクが掲載されます。(非公開の場合は"匿名希望さん"と表示されます)') + player_name_privacy = models.IntegerField('プレイヤー名', default=1, + help_text='ランキングにプレイヤー名とプロフィールページへのリンクが掲載されます。(非公開の場合は"匿名希望さん"と表示されます)') cleardata_privacy = models.IntegerField('クリアデータ', default=1, help_text='プロフィールページにクリアデータを表示します。') - updated_recently_privacy = models.IntegerField('最近更新した曲', default=1, help_text='プロフィールページに最近更新した曲を表示します。') + updated_recently_privacy = models.IntegerField('最近更新した曲', default=1, + help_text='プロフィールページに最近更新した曲を表示します。') theme = models.ForeignKey(Theme, verbose_name='テーマ', default=1, on_delete=models.PROTECT) premium = models.BooleanField('プレミアムユーザー', default=False, help_text='Amazonギフト券を買ってくれた人')