Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

migrate the logic for determining clear status to model #747

Merged
merged 1 commit into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion main/management/commands/change_models.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
37 changes: 28 additions & 9 deletions main/models.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -31,7 +32,6 @@ class Meta:
class Level(models.Model):
level = models.IntegerField('レベル')

# 整数型で返す
def int(self):
return self.level

Expand Down Expand Up @@ -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 '金'
Expand Down Expand Up @@ -117,7 +141,6 @@ def output_str(self) -> str:
else:
return ''

# 更新日時をJSTで返す
def updated_at_jst(self):
return self.updated_at + timedelta(hours=9)

Expand All @@ -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)

Expand All @@ -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)

Expand Down
18 changes: 1 addition & 17 deletions main/templates/main/ranking_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,7 @@ <h2 class="page-header">ランキング</h2>
</thead>
<tbody>
{% for result in results %}
{% if result.medal.medal == 1 and result.bad_count.bad_count == 0 %}
<tr class="perfect">
{% elif result.bad_count.bad_count == 0 %}
<tr class="fullcombo">
{% elif result.extra_option.hard %}
<tr class="hard-cleared">
{% else %}
{% if result.medal.medal == 7 or result.medal.medal == 6 or result.medal.medal == 5 %}
<tr class="cleared">
{% elif result.medal.medal == 10 or result.medal.medal == 9 or result.medal.medal == 8 %}
<tr class="failed">
{% elif result.medal.medal == 11 %}
<tr class="easy-cleared">
{% else %}
<tr class="no-play">
{% endif %}
{% endif %}
<tr class="{{ result.status }}">
<td class="rank">
{% if result.user == user %}
<b>{{ result.rank|default:'-' }}</b>
Expand Down
24 changes: 6 additions & 18 deletions main/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})

Expand Down
2 changes: 1 addition & 1 deletion main/views/ranking.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down
94 changes: 21 additions & 73 deletions main/views/ranking_detail.py
Original file line number Diff line number Diff line change
@@ -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)
11 changes: 7 additions & 4 deletions users/models.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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ギフト券を買ってくれた人')

Expand Down