Skip to content

Instantly share code, notes, and snippets.

@ZwickVitaly
Last active July 30, 2024 21:58
Show Gist options
  • Select an option

  • Save ZwickVitaly/695b43011bf76311ce287c766dae2a53 to your computer and use it in GitHub Desktop.

Select an option

Save ZwickVitaly/695b43011bf76311ce287c766dae2a53 to your computer and use it in GitHub Desktop.
"""
Приложение подразумевает ежедневный вход пользователя, начисление баллов за вход.
Нужно отследить момент первого входа игрока для аналитики.
Также у игрока имеются игровые бонусы в виде нескольких типов бустов.
Нужно описать модели игрока и бустов с возможностью начислять игроку бусты за прохождение уровней или вручную.
"""
from django.contrib.auth import get_user_model
# Начнём с того, что ежедневный вход и бусты(сущности ограниченные по времени жизни) - это лучше redis.
# Но задача этого не подразумевает, так что напишу на моделях.
from django.db import models
from django.db.models import functions
class Player(models.Model):
# связываем с пользователем auth-модели
# дабы потом через signals создавать модель игрока при регистрации пользователя
# собственно player.user.date_joined можно использовать как трекер первого входа в игру
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
# но если нужен прямой трекер на модели
created_at = models.DateTimeField(default=functions.Now())
class Boost(models.Model):
# название буста
title = models.CharField(max_length=128, unique=True)
# эффект буста. для примера взято целое число, по значению которого накладывается эффект.
effect = models.IntegerField()
# описание эффекта
effect_description = models.CharField(max_length=300)
class PlayerBoost(models.Model):
# прокси модель игрока и буста
player_id = models.ForeignKey(Player, on_delete=models.CASCADE, related_name="players_boosts")
boost_id = models.ForeignKey(Boost, on_delete=models.CASCADE, related_name="boosted_players")
# активен буст или нет
active = models.BooleanField(default=True)
"""
Написать два метода:
1. Присвоение игроку приза за прохождение уровня.
2. Выгрузку в csv следующих данных: id игрока, название уровня, пройден ли уровень, полученный приз за уровень. Учесть, что записей может быть 100 000 и более.
"""
# models.py
from django.db import models
# Начнём с того, что связи выстроены отвратительно
# Как узнать какой приз соответствует прохождению какого уровня?
# Нооо задача написать МЕТОДЫ. Моя писать методы.
class Player(models.Model):
# и зачем player_id как charfield? django сам создаёт id :\
player_id = models.CharField(max_length=100)
class Level(models.Model):
title = models.CharField(max_length=100)
order = models.IntegerField(default=0)
class Prize(models.Model):
title = models.CharField()
class PlayerLevel(models.Model):
# а за неиспользование related_name вообще на каторгу в Сибирь нужно отправлять
# или бить палкой, попутно выпрямляя длинную белую бороду на своём лице
# как в Убить Билла делал старый мастер.
player = models.ForeignKey(Player, on_delete=models.CASCADE)
level = models.ForeignKey(Level, on_delete=models.CASCADE)
completed = models.DateField()
is_completed = models.BooleanField(default=False)
score = models.PositiveIntegerField(default=0)
class LevelPrize(models.Model):
level = models.ForeignKey(Level, on_delete=models.CASCADE)
prize = models.ForeignKey(Prize, on_delete=models.CASCADE)
received = models.DateField()
# views.py
from django.http import StreamingHttpResponse, HttpResponse
from datetime import datetime
import csv
# разбираемся с выгрузкой .csv
# подсмотрено в тырнетах
class Echo:
__slots__ = ()
def write(self, value):
return value
# госсподи кто-то ещё пользуется .csv вместо .json?
# а я думал я динозавр...
# кстати 100к записей я бы лучше отдал на обработку celery
# или перешёл уже на асинхронщину
def streaming_csv_view(request):
echo_buffer = Echo()
csv_writer = csv.writer(echo_buffer)
# values list для более быстрой обработки запроса
queryset = PlayerLevel.objects.select_related(
"player__player_id", "level__title"
).order_by("player__player_id").values_list("player__player_id", "level__title", "is_completed")
csv_rows = (csv_writer.writerow(row) for row in queryset)
# стрим респонс, возвращаем генератор, для генерации "на лету"
response = StreamingHttpResponse(csv_rows, content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="users.csv"'
return response
# добавляем игроку приз за прохождение уровня
def add_prize(request):
player_level_id = request.get("player_level_id") # очень условно получаем player_level_id,
player_level = PlayerLevel.objects.select_related("level").filter(id=player_level_id).first()
player_level.is_completed = True
player_level.completed = datetime.now()
# РАНДОООМНЫЙ ПРРИИИИЗ
new_prize = Prize(
level=player_level.level, prize=random.choice(Prize.objects.all()), recieved=datetime.now()
)
player_level.save()
new_prize.save()
return HttpResponse(content={"new_prize_id": new_prize.pk}, status=200)
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
import random
# Более удобная логика, но в таком случае при прохождении уровня нельзя использовать
# PlayerLevel.objects.filter(id=1).update(completed=True)
# только через
# player_level = PlayerLevel.objects.get(id=1)
# player_level.completed = True
# player_level.save()
@receiver(post_save, sender=PlayerLevel)
def do_something_when_user_updated(sender, instance: PlayerLevel, created, **kwargs):
if instance.is_completed is True:
# А ВОТ БУДЕТ ТЕПЕРЬ РАНДОМНЫЙ ПРИЗ. СПАСИБО ТОМУ КТО ПИСАЛ ЛОГИКУ АХАХАХАХХАХА.
new_prize = LevelPrize(
level=PlayerLevel.level, prize=random.choice(Prize.objects.all()), recieved=datetime.now()
)
new_prize.save()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment