Last active
July 30, 2024 21:58
-
-
Save ZwickVitaly/695b43011bf76311ce287c766dae2a53 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Приложение подразумевает ежедневный вход пользователя, начисление баллов за вход. | |
| Нужно отследить момент первого входа игрока для аналитики. | |
| Также у игрока имеются игровые бонусы в виде нескольких типов бустов. | |
| Нужно описать модели игрока и бустов с возможностью начислять игроку бусты за прохождение уровней или вручную. | |
| """ | |
| 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