Skip to content

Commit

Permalink
Merge pull request #161 from Diary-workout-tracker/feature/logic_proj…
Browse files Browse the repository at this point in the history
…ect_timezone_user

Feature/logic project timezone user
  • Loading branch information
VladislavYar authored Apr 3, 2024
2 parents d341504 + 77c65ce commit 9188969
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 63 deletions.
81 changes: 74 additions & 7 deletions backend/api/v1/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from collections import OrderedDict
from datetime import datetime
import pytz

from django.contrib.auth import get_user_model
from django.utils import timezone
from rest_framework import serializers
from rest_framework_simplejwt.tokens import RefreshToken

Expand All @@ -22,6 +24,16 @@ class UserSerializer(serializers.ModelSerializer):
"""Сериализатор кастомного пользователя."""

email = serializers.EmailField(validators=(CustomUniqueValidator(queryset=User.objects.all()),))

class Meta:
model = User
fields = ("email",)


class MeSerializer(serializers.ModelSerializer):
"""Сериализатор Me пользователя."""

email = serializers.EmailField(read_only=True)
name = serializers.CharField(required=False)
gender = serializers.ChoiceField(choices=GENDER_CHOICES, required=False)
height_cm = serializers.IntegerField(allow_null=True, required=False)
Expand All @@ -32,7 +44,6 @@ class UserSerializer(serializers.ModelSerializer):
date_last_skips = serializers.DateTimeField(required=False)
amount_of_skips = serializers.IntegerField(required=False)
avatar = Base64ImageField(allow_null=True, required=False)
timezone = serializers.CharField(required=False)

class Meta:
model = User
Expand All @@ -46,9 +57,49 @@ class Meta:
"date_last_skips",
"amount_of_skips",
"avatar",
"timezone",
)

def to_representation(self, instance: ClassUser) -> dict:
representation = super().to_representation(instance)
date_last_skips = instance.date_last_skips
if date_last_skips:
user_timezone = pytz.timezone(self.context["request"].user.timezone)
representation["date_last_skips"] = date_last_skips.astimezone(user_timezone).strftime(FORMAT_DATE)
return representation

def validate(self, data: OrderedDict) -> OrderedDict:
date_last_skips = data.get("date_last_skips")
amount_of_skips = data.get("amount_of_skips")
if [date_last_skips, amount_of_skips].count(None) == 1:
raise serializers.ValidationError(
{"date_last_skips_amount_of_skips": ["Поля должны присутствовать одновременно."]}
)
return data

def validate_amount_of_skips(self, value: int) -> int:
amount_of_skips = self.context["request"].user.amount_of_skips
if not amount_of_skips:
raise serializers.ValidationError("У пользователя отсутсвуют заморозки.")
new_amount_of_skips = amount_of_skips - 1
if new_amount_of_skips != value:
raise serializers.ValidationError(f"Заморозки должны быть равны {new_amount_of_skips}.")
return value

def validate_date_last_skips(self, value: datetime) -> datetime:
user = self.context["request"].user
date_last_skips = user.date_last_skips
user_timezone = pytz.timezone(user.timezone)
localdate = timezone.localdate(timezone=user_timezone)
user_timezone_value = value.astimezone(user_timezone).date()
if not date_last_skips:
if localdate != user_timezone_value:
raise serializers.ValidationError("День заморозки должен быть равен текущему дню.")
return value
date_last_skips = date_last_skips.astimezone(user_timezone).date()
if date_last_skips >= user_timezone_value:
raise serializers.ValidationError(f"День заморозки должен быть больше {date_last_skips}.")
return value

def create(self, validated_data: dict) -> ClassUser:
"""Создаёт нового пользователя."""
avatar_data = validated_data.pop("avatar", None)
Expand Down Expand Up @@ -131,6 +182,14 @@ class Meta:
"received",
)

def to_representation(self, instance: Achievement) -> dict:
representation = super().to_representation(instance)
achievement_date = instance.achievement_date
if achievement_date:
user_timezone = pytz.timezone(self.context["request"].user.timezone)
representation["achievement_date"] = achievement_date.astimezone(user_timezone).strftime(FORMAT_DATE)
return representation


class AchievementEndTrainingSerializer(serializers.ModelSerializer):
"""Сериализатор достижения конца тренировки."""
Expand Down Expand Up @@ -179,12 +238,14 @@ class Meta:
"route": {"required": False},
}

def to_representation(self, instance):
def to_representation(self, instance: History) -> dict:
representation = super().to_representation(instance)
training_start = instance.training_start
user = self.context["request"].user
user_timezone = pytz.timezone(user.timezone)
representation["training_start"] = [
training_start.strftime(FORMAT_DATE),
training_start.strftime(FORMAT_DATETIME),
training_start.astimezone(user_timezone).strftime(FORMAT_DATE),
training_start.astimezone(user_timezone).strftime(FORMAT_DATETIME),
]
return representation

Expand All @@ -196,8 +257,14 @@ def validate(self, data: OrderedDict) -> OrderedDict:
return data

def _validate_date(self, value: datetime, name_field: str) -> datetime:
last_completed_training = self.context["request"].user.last_completed_training
if last_completed_training and value.date() <= getattr(last_completed_training, name_field).date():
user = self.context["request"].user
last_completed_training = user.last_completed_training
user_timezone = pytz.timezone(user.timezone)
if (
last_completed_training
and value.astimezone(user_timezone).date()
<= getattr(last_completed_training, name_field).astimezone(user_timezone).date()
):
raise serializers.ValidationError("Дата должна быть больше прошлой тренировки.")
return value

Expand Down
25 changes: 13 additions & 12 deletions backend/api/v1/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime, timedelta
import pytz

from django.contrib.auth import get_user_model
from django.db.models import Case, DateTimeField, Exists, F, OuterRef, When
Expand All @@ -22,6 +23,7 @@
CustomTokenObtainSerializer,
HistorySerializer,
TrainingSerializer,
MeSerializer,
UserSerializer,
)
from .throttling import DurationCooldownRequestThrottle
Expand Down Expand Up @@ -85,7 +87,7 @@ def post(self, request, format=None):


class MyInfoView(APIView):
serializer_class = UserSerializer
serializer_class = MeSerializer

def get(self, request, *args, **kwargs):
user = request.user
Expand Down Expand Up @@ -131,7 +133,6 @@ def get_queryset(self) -> QuerySet:
.order_by("day_number")
)
dynamic_motivation_phrase = motivation_phrase.get_dynamic_list_motivation_phrase(user)
print(len(queryset))
for i in range(len(queryset)):
queryset[i].motivation_phrase = dynamic_motivation_phrase[i]
return queryset
Expand Down Expand Up @@ -196,7 +197,7 @@ def perform_create(self, serializer: HistorySerializer) -> History:
def create(self, request: Request, *args, **kwargs) -> Response:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if serializer._validated_data.get("achievements"):
if "achievements" in serializer._validated_data.keys():
serializer._validated_data.pop("achievements")
history = self.perform_create(serializer)
self.request.user.last_completed_training = history
Expand All @@ -221,11 +222,12 @@ def create(self, request: Request, *args, **kwargs) -> Response:
),
)
class UpdateView(APIView):
def _get_date_activity(self, user: ClassUser) -> datetime:
def _get_date_activity(self, user: ClassUser, user_timezone: str) -> datetime:
"""Отдаёт дату последней активности в виде тренировки или заморозки."""
return max(
date_activity = max(
[date for date in [user.date_last_skips, user.last_completed_training.training_start] if date is not None]
)
return date_activity.astimezone(pytz.timezone(user_timezone))

def _updates_skip_data(
self, user: ClassUser, amount_of_skips: int, days_missed: int, date_day_ago: datetime
Expand All @@ -243,11 +245,10 @@ def _clearing_user_training_data(self, user: ClassUser) -> None:
History.objects.filter(user_id=user).delete()

def _update_user_timezone_data(self, user: ClassUser, user_timezone: str) -> None:
"""Обновзляет timezone ползователя."""
if user.timezone == user_timezone:
return
user.timezone = user_timezone
user.save()
"""Обновляет timezone ползователя."""
if user.timezone != user_timezone:
user.timezone = user_timezone
user.save()

def post(self, request: Request, *args, **kwargs) -> Response:
user_timezone = request.data.get("timezone")
Expand All @@ -260,9 +261,9 @@ def post(self, request: Request, *args, **kwargs) -> Response:
self._update_user_timezone_data(user, user_timezone)
return response

date_activity = self._get_date_activity(user)
date_activity = self._get_date_activity(user, user_timezone)
amount_of_skips = user.amount_of_skips
date_day_ago = timezone.localtime() - timedelta(days=1)
date_day_ago = timezone.localtime(timezone=pytz.timezone(user_timezone)) - timedelta(days=1)
days_missed = (date_day_ago.date() - date_activity.date()).days
if days_missed <= 0:
self._update_user_timezone_data(user, user_timezone)
Expand Down
7 changes: 4 additions & 3 deletions backend/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@

WSGI_APPLICATION = "config.wsgi.application"

TIME_ZONE = "UTC"

DATABASES = {
"default": {
"ENGINE": os.getenv("DB_ENGINE", default="django.db.backends.postgresql"),
Expand All @@ -80,7 +82,7 @@
"HOST": os.getenv("DB_HOST", default="localhost"),
"PORT": os.getenv("DB_PORT", default=5432),
"PG_USER": os.getenv("PG_USER", default="user"),
"TIME_ZONE": os.getenv("TIME_ZONE", default="Europe/Moscow"),
"TIME_ZONE": TIME_ZONE,
}
}

Expand All @@ -103,8 +105,6 @@

LANGUAGE_CODE = "ru-RU"

TIME_ZONE = os.getenv("TIME_ZONE", default="Europe/Moscow")

USE_I18N = True

USE_TZ = True
Expand Down Expand Up @@ -159,6 +159,7 @@
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"

REST_FRAMEWORK = {
"DATETIME_INPUT_FORMATS": ["%Y-%m-%d %H:%M:%S"],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
Expand Down
4 changes: 2 additions & 2 deletions backend/utils/achievements.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import timedelta
from functools import partial
import pytz

from django.utils import timezone
from running.models import Achievement, UserAchievement, History
Expand Down Expand Up @@ -36,12 +37,11 @@ def equator(user: User) -> bool:

def get_count_training_current_week(user: User) -> int:
"""Возвращает количество пройденных тренировок на текущей неделе."""
date_now = timezone.localtime()
date_now = timezone.localtime(timezone=pytz.timezone(user.timezone))
date_now_number_day_week = date_now.weekday()
date_start_current_week = (date_now - timedelta(days=date_now_number_day_week)).replace(
hour=0, minute=0, second=0, microsecond=0
)

return History.objects.filter(user_id=user, training_start__range=[date_start_current_week, date_now]).count()


Expand Down
10 changes: 7 additions & 3 deletions backend/utils/motivation_phrase.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import timedelta
import pytz

from django.db.models.query import QuerySet
from django.utils import timezone
Expand All @@ -9,7 +10,9 @@

def get_count_training_last_week(user: User) -> int:
"""Возвращает кол-во тренировок на прошлой неделе."""
date_now = timezone.localtime().replace(hour=0, minute=0, second=0, microsecond=0)
date_now = timezone.localtime(timezone=pytz.timezone(user.timezone)).replace(
hour=0, minute=0, second=0, microsecond=0
)
date_now_number_day_week = date_now.weekday() + 1
date_end_last_week = date_now - timedelta(days=date_now_number_day_week)
date_start_last_week = date_end_last_week - timedelta(days=6)
Expand Down Expand Up @@ -61,7 +64,8 @@ def get_dynamic_list_motivation_phrase(user: User) -> list:
count_training = get_count_training_last_week(user)
if count_training < 4:
return motivational_phrases
date_now_number_day_week = timezone.localtime().weekday() + 1
user_timezone = pytz.timezone(user.timezone)
date_now_number_day_week = timezone.localtime(timezone=user_timezone).weekday() + 1
day_last_training = last_training.training_day.day_number
if count_training == 4:
shift_wednesday = 3 - date_now_number_day_week
Expand All @@ -79,7 +83,7 @@ def get_dynamic_list_motivation_phrase(user: User) -> list:
shift_first_rest = 1 - date_now_number_day_week
shift_second_rest = 3 - date_now_number_day_week
shift_third_rest = 5 - date_now_number_day_week
date_last_training_week = last_training.training_start.weekday() + 1
date_last_training_week = last_training.training_start.astimezone(user_timezone).weekday() + 1
if date_last_training_week == 7:
shift_first_rest += 1
shift_second_rest += 1
Expand Down
Loading

0 comments on commit 9188969

Please sign in to comment.