Skip to content

Commit

Permalink
Merge pull request #163 from Diary-workout-tracker/feature/recurring_…
Browse files Browse the repository at this point in the history
…achievements

Повторяющиеся ачивки
  • Loading branch information
holohup authored Apr 4, 2024
2 parents 9188969 + 548e0d8 commit a239d3a
Show file tree
Hide file tree
Showing 17 changed files with 314 additions and 81 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -171,5 +171,6 @@ history_images/
.vscode/

#logs
# logs/*
logs/django*
*minio

6 changes: 3 additions & 3 deletions backend/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from utils.authcode import AuthCode
from utils.users import get_user_by_email_or_404

from .constants import FORMAT_DATE, FORMAT_TIME, FORMAT_DATETIME
from .constants import FORMAT_DATE, FORMAT_DATETIME, FORMAT_TIME
from .fields import Base64ImageField
from .validators import CustomUniqueValidator

Expand Down Expand Up @@ -284,9 +284,9 @@ def validate_training_day(self, value: Day) -> Day:
if last_completed_training:
day_number_last_training = last_completed_training.training_day.day_number
if value.day_number - 1 != day_number_last_training:
raise serializers.ValidationError(f"День тренирвки должен быть равен {day_number_last_training+1}")
raise serializers.ValidationError(f"День тренировки должен быть равен {day_number_last_training+1}")
elif value.day_number != 1:
raise serializers.ValidationError("День тренирвки должен быть равен 1")
raise serializers.ValidationError("День тренировки должен быть равен 1")
return value

def validate_achievements(self, value: list) -> list:
Expand Down
3 changes: 2 additions & 1 deletion backend/api/v1/tasks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from config.celery import app
from django.conf import settings
from django.core.mail import send_mail

from config.celery import app


@app.task
def send_code(message, recipient_list, html_message):
Expand Down
2 changes: 1 addition & 1 deletion backend/api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
MyInfoView,
RegisterUserView,
ResendCodeView,
UpdateView,
TokenRefreshView,
TrainingView,
UpdateView,
)

urlpatterns = (
Expand Down
12 changes: 7 additions & 5 deletions backend/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from backend.utils.achievements import AchievementUpdater
from running.models import Achievement, Day, History, UserAchievement
from users.constants import DEFAULT_AMOUNT_OF_SKIPS
from users.models import User as ClassUser
Expand Down Expand Up @@ -188,13 +189,14 @@ class HistoryView(generics.ListCreateAPIView):

def get_queryset(self) -> QuerySet:
"""Формирует список историй тренировок пользователя."""
return self.request.user.user_history.all().order_by("training_day")
return self.request.user.user_history.order_by("training_day")

def perform_create(self, serializer: HistorySerializer) -> History:
"""Создаёт новую историю."""
return serializer.save()

def create(self, request: Request, *args, **kwargs) -> Response:
achievements = request.data.pop("achievements", None) # noqa
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if "achievements" in serializer._validated_data.keys():
Expand All @@ -203,13 +205,13 @@ def create(self, request: Request, *args, **kwargs) -> Response:
self.request.user.last_completed_training = history
self.request.user.total_m_run += history.distance
self.request.user.save()
achievements = request.data.pop("achievements", None) # noqa
updater = AchievementUpdater(self.request.user, achievements, history)
updater.update_achievements()
headers = self.get_success_headers(serializer.data)

new_achievements = AchievementEndTrainingSerializer(
Achievement.objects.all(), many=True, context={"request": request}
).data # XXX Тут в будущем вместо всех ачивок надо добавить новые.

updater.new_achievements, many=True, context={"request": request}
).data
return Response(new_achievements, status=status.HTTP_201_CREATED, headers=headers)


Expand Down
2 changes: 1 addition & 1 deletion backend/config/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"filename": PATH_TO_LOGS,
"formatter": "formatter",
"when": "midnight",
"backupCount": "30",
"backupCount": 30,
},
},
"loggers": {
Expand Down
6 changes: 3 additions & 3 deletions backend/config/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from distutils.util import strtobool
import os
from datetime import timedelta
from distutils.util import strtobool
from pathlib import Path

from dotenv import load_dotenv
Expand Down Expand Up @@ -171,7 +171,7 @@
],
"DEFAULT_THROTTLE_RATES": {
"user": "10000/day",
"anon": "1000/day",
"anon": "2000/day",
},
"EXCEPTION_HANDLER": "api.v1.exceptions.custom_exception_handler",
}
Expand Down Expand Up @@ -212,7 +212,7 @@

ACCESS_RESTORE_CODE_THROTTLING = {
"duration": timedelta(minutes=10),
"num_requests": 5,
"num_requests": 6,
"cooldown": timedelta(minutes=5),
}

Expand Down
22 changes: 11 additions & 11 deletions backend/fixture/achievements_fixture.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{"model": "running.achievement", "pk": 1, "fields": {"icon": "achievement_icons/persistent_C3ka8zy.png", "title": "Упорный", "description": "Отличная работа! Успешно завершив четвертую тренировку на текущей неделе вы получаете Дополнительные дни отдыха и награду “Упорный”.", "reward_points": 2}},
{"model": "running.achievement", "pk": 2, "fields": {"icon": "achievement_icons/machine_CyYVBAS.png", "title": "Машина", "description": "Поздравляем! Вы просто неудержимы! После успешного завершения пятой тренировки подряд на этой неделе вы стали настоящей беговой “Машиной”, и получаете дополнительную заморозку.", "reward_points": 1}},
{"model": "running.achievement", "pk": 1, "fields": {"icon": "achievement_icons/persistent_C3ka8zy.png", "title": "Упорный", "description": "Отличная работа! Успешно завершив четвертую тренировку на текущей неделе вы получаете Дополнительные дни отдыха и награду “Упорный”.", "reward_points": 2, "recurring": true}},
{"model": "running.achievement", "pk": 2, "fields": {"icon": "achievement_icons/machine_CyYVBAS.png", "title": "Машина", "description": "Поздравляем! Вы просто неудержимы! После успешного завершения пятой тренировки подряд на этой неделе вы стали настоящей беговой “Машиной”, и получаете дополнительную заморозку.", "reward_points": 1, "recurring": true}},
{"model": "running.achievement", "pk": 3, "fields": {"icon": "achievement_icons/equator_WkYKaUy.png", "title": "Экватор", "description": "Поздравляем Вас с преодолением половины пути! Вы получаете награду “Экватор” Продолжайте бежать к своим мечтам с таким же упорством и энтузиазмом!", "reward_points": 0}},
{"model": "running.achievement", "pk": 4, "fields": {"icon": "achievement_icons/20km_evvEj71.png", "title": "Клуб 20 км", "description": "Поздравляем Вас с присоединением к элитному клубу 20 км! Эта награда доступна после преодоления 20 километров за все время челленджа.", "reward_points": 0}},
{"model": "running.achievement", "pk": 5, "fields": {"icon": "achievement_icons/50km.png", "title": "Клуб 50 км", "description": "Поздравляем Вас с присоединением к элитному клубу 50 км! Эта награда доступна после преодоления 50 километров за все время челленджа.", "reward_points": 0}},
Expand All @@ -16,14 +16,14 @@
{"model": "running.achievement", "pk": 15, "fields": {"icon": "achievement_icons/Cup_4star_1.png", "title": "Кубок со звездами - 4", "description": "Поздравляем! Вы завершили 50 тренировок и заслужили награду.", "reward_points": 1}},
{"model": "running.achievement", "pk": 16, "fields": {"icon": "achievement_icons/Cup_5star.png", "title": "Кубок со звездами - 5", "description": "Поздравляем! Вы завершили 70 тренировок и заслужили награду.", "reward_points": 1}},
{"model": "running.achievement", "pk": 17, "fields": {"icon": "achievement_icons/Cup_bigstar_1.png", "title": "Кубок с большой звездой", "description": "Поздравляем! Вы завершили 100 тренировок и заслужили награду.", "reward_points": 0}},
{"model": "running.achievement", "pk": 18, "fields": {"icon": "achievement_icons/faster.png", "title": "Быстрее ветра", "description": "Поздравляем! Вы удостоились награды “Быстрее ветра”, более минуты вы бежали со скоростью 8 км/ч или быстрее.", "reward_points": 0}},
{"model": "running.achievement", "pk": 19, "fields": {"icon": "achievement_icons/stormbreaker.png", "title": "Штормбрейкер", "description": "Поздравляем вас с преодолением стихии! Награда “Штормбрейкер” доступна после тренировки в дождь.", "reward_points": 0}},
{"model": "running.achievement", "pk": 18, "fields": {"icon": "achievement_icons/faster.png", "title": "Быстрее ветра", "description": "Поздравляем! Вы удостоились награды “Быстрее ветра”, более минуты вы бежали со скоростью 8 км/ч или быстрее.", "reward_points": 0, "recurring": true}},
{"model": "running.achievement", "pk": 19, "fields": {"icon": "achievement_icons/stormbreaker.png", "title": "Штормбрейкер", "description": "Поздравляем вас с преодолением стихии! Награда “Штормбрейкер” доступна после тренировки в дождь.", "reward_points": 0, "recurring": true}},
{"model": "running.achievement", "pk": 20, "fields": {"icon": "achievement_icons/Pioner.png", "title": "Пионер", "description": "Поздравляем вас с началом пути, впереди много километров которые принесут вам удовольствие. Награда пионер доступна после первой тренировки челенджа .", "reward_points": 0}},
{"model": "running.achievement", "pk": 21, "fields": {"icon": "achievement_icons/Tourist.png", "title": "Турист", "description": "Поздравляем с приключением! Закончив тренировку в другом городе вы достойны Звания “Турист” Продолжайте открывать новые горизонты и наслаждаться каждым шагом в новых уголках этого мира!", "reward_points": 0}},
{"model": "running.achievement", "pk": 22, "fields": {"icon": "achievement_icons/Traveler.png", "title": "Путешественник", "description": "Вас невозможно удержать на месте, для вас нет рамок и препятствий. Вы достойны звания “Путешественник”.", "reward_points": 0}},
{"model": "running.achievement", "pk": 23, "fields": {"icon": "achievement_icons/early_bird.png", "title": "Ранняя пташка", "description": "Поздравляем с восходом солнца! Завершив тренировку, начатую между 3 и 6 утра, вы получаете звание \"Ранняя пташка\". Продолжайте просыпаться солнцем и вдохновляйте других своим примером активного образа жизни!", "reward_points": 0}},
{"model": "running.achievement", "pk": 24, "fields": {"icon": "achievement_icons/Sub-zero.png", "title": "Сабзиро", "description": "Поздравляем с бесстрашием перед холодом! Завершив тренировку в холодную погоду, вы заслуживаете ачивмент \"Сабзиро\".", "reward_points": 0}},
{"model": "running.achievement", "pk": 25, "fields": {"icon": "achievement_icons/Fire.png", "title": "Фаерстеп", "description": "Поздравляем с огненным ритмом! Завершив тренировку в жаркую погоду, вы заслуживаете звания \"Фаерстеп\". Ваши ноги горят огнем страсти к бегу, и вы проявляете выдающуюся выносливость, бегая даже в такую жару.", "reward_points": 0}},
{"model": "running.achievement", "pk": 26, "fields": {"icon": "achievement_icons/After_dark.png", "title": "Афтердарк", "description": "Темные улицы не пугают Вас когда речь заходит о тренировке, вам не страшны вурдалаки и оборотни, страшно пропустить тренировку. за пробежку после 21 часа вы получаете награду “Афтердарк”.", "reward_points": 0}},
{"model": "running.achievement", "pk": 27, "fields": {"icon": "achievement_icons/snow.png", "title": "Сноураннер", "description": "Да, надо было взять лыжи, но вы не из тех кто ищет легких путей. Тренируясь в Снег вы получаете награду “Сноураннер”. Не важно какая погода, важно завершить тренировку.", "reward_points": 0}}
{"model": "running.achievement", "pk": 21, "fields": {"icon": "achievement_icons/Tourist.png", "title": "Турист", "description": "Поздравляем с приключением! Закончив тренировку в другом городе вы достойны Звания “Турист” Продолжайте открывать новые горизонты и наслаждаться каждым шагом в новых уголках этого мира!", "reward_points": 0, "recurring": true}},
{"model": "running.achievement", "pk": 22, "fields": {"icon": "achievement_icons/Traveler.png", "title": "Путешественник", "description": "Вас невозможно удержать на месте, для вас нет рамок и препятствий. Вы достойны звания “Путешественник”.", "reward_points": 0, "recurring": true}},
{"model": "running.achievement", "pk": 23, "fields": {"icon": "achievement_icons/early_bird.png", "title": "Ранняя пташка", "description": "Поздравляем с восходом солнца! Завершив тренировку, начатую между 3 и 6 утра, вы получаете звание \"Ранняя пташка\". Продолжайте просыпаться солнцем и вдохновляйте других своим примером активного образа жизни!", "reward_points": 0, "recurring": true}},
{"model": "running.achievement", "pk": 24, "fields": {"icon": "achievement_icons/Sub-zero.png", "title": "Сабзиро", "description": "Поздравляем с бесстрашием перед холодом! Завершив тренировку в холодную погоду, вы заслуживаете ачивмент \"Сабзиро\".", "reward_points": 0, "recurring": true}},
{"model": "running.achievement", "pk": 25, "fields": {"icon": "achievement_icons/Fire.png", "title": "Фаерстеп", "description": "Поздравляем с огненным ритмом! Завершив тренировку в жаркую погоду, вы заслуживаете звания \"Фаерстеп\". Ваши ноги горят огнем страсти к бегу, и вы проявляете выдающуюся выносливость, бегая даже в такую жару.", "reward_points": 0, "recurring": true}},
{"model": "running.achievement", "pk": 26, "fields": {"icon": "achievement_icons/After_dark.png", "title": "Афтердарк", "description": "Темные улицы не пугают Вас когда речь заходит о тренировке, вам не страшны вурдалаки и оборотни, страшно пропустить тренировку. за пробежку после 21 часа вы получаете награду “Афтердарк”.", "reward_points": 0, "recurring": true}},
{"model": "running.achievement", "pk": 27, "fields": {"icon": "achievement_icons/snow.png", "title": "Сноураннер", "description": "Да, надо было взять лыжи, но вы не из тех кто ищет легких путей. Тренируясь в Снег вы получаете награду “Сноураннер”. Не важно какая погода, важно завершить тренировку.", "reward_points": 0, "recurring": true}}
]
2 changes: 2 additions & 0 deletions backend/running/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ class AchievementAdmin(admin.ModelAdmin):
"description",
"show_icon",
"reward_points",
"recurring",
)
readonly_fields = ("recurring",)
search_fields = (
"title",
"description",
Expand Down
18 changes: 18 additions & 0 deletions backend/running/migrations/0012_achievement_recurring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.3 on 2024-04-01 17:45

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('running', '0011_remove_achievement_black_white_icon_and_more'),
]

operations = [
migrations.AddField(
model_name='achievement',
name='recurring',
field=models.BooleanField(db_comment='Повторяющееся ли достижение', default=False, help_text='Повторяющееся ли достижение', verbose_name='Повторяющееся'),
),
]
8 changes: 7 additions & 1 deletion backend/running/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ class Achievement(models.Model):
help_text=_("Количество заморозок за достижение."),
db_comment=_("Количество заморозок за достижение."),
)
recurring = models.BooleanField(
verbose_name=_("Повторяющееся"),
default=False,
help_text=_("Повторяющееся ли достижение"),
db_comment=_("Повторяющееся ли достижение"),
)

class Meta:
ordering = ("title",)
Expand Down Expand Up @@ -131,7 +137,7 @@ class Meta:
verbose_name_plural = _("Достижения пользователей")

def __str__(self) -> str:
return f"{self.user_id.username} - {self.achievement_id.title}"
return f"{self.user_id.email} - {self.achievement_id.title}"


class History(models.Model):
Expand Down
4 changes: 2 additions & 2 deletions backend/users/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)

if extra_fields.get("is_staff") is not True:
if extra_fields.get("is_staff") is False:
raise ValueError("У суперпользователя должно быть is_staff=True.")
if extra_fields.get("is_superuser") is not True:
if extra_fields.get("is_superuser") is False:
raise ValueError("У суперпользователя должно быть is_superuser=True.")

return self.create_user(email, password, **extra_fields)
Loading

0 comments on commit a239d3a

Please sign in to comment.