Skip to content

Commit

Permalink
Dev (#172)
Browse files Browse the repository at this point in the history
* Fixed task result percentage

* superuser и staff имеют полный доступ к навыкам даже без подписки
исправлены баги связанные с прогрессом
  • Loading branch information
pavuchara authored Dec 18, 2024
1 parent 97efa0c commit e5c09a1
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 30 deletions.
2 changes: 2 additions & 0 deletions apps/courses/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ class AvailableForUser(PublishedManager):

def only_awailable_weeks(self, available_week: int, user: CustomUser = None) -> QuerySet:
"""Фильтрация по доступным для пользователя неделям."""
if user and (user.is_superuser or user.is_staff):
return super().for_user(user)
return super().for_user(user).filter(Q(week__lte=available_week))
2 changes: 1 addition & 1 deletion apps/courses/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

def get_stats(skill_id: int, profile_id: int, request_user: CustomUser | None = None) -> GetStatsDict:
available_week, user = get_user_available_week(profile_id)
skill_status_filter = DBObjectStatusFilters().get_skill_status_for_for_user(request_user)
skill_status_filter = DBObjectStatusFilters().get_skill_status_for_user(request_user)

tasks_of_skill: QuerySet[Task] = (
Task.available
Expand Down
59 changes: 38 additions & 21 deletions apps/courses/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
from django.shortcuts import get_object_or_404

from django.db.models import Count, Sum, Q, Exists, OuterRef, Subquery, Case, When, Value, BooleanField
from django.db.models import (
Sum, Q,
BooleanField,
Case,
Count,
Exists,
OuterRef,
Prefetch,
QuerySet,
Value,
Subquery,
When,
)

from rest_framework import generics, status
from rest_framework import permissions
Expand Down Expand Up @@ -211,33 +223,38 @@ def get(self, request, *args, **kwargs) -> Response:
),
id=task_id,
)
task_status_filter = DBObjectStatusFilters().get_many_tasks_status_filter_for_user(self.request.user)
skill: Skill = get_object_or_404(
Skill.published.for_user(self.request.user).annotate(
# Общее кол-во опубликованных вопросов навыка.
total_num_questions=Count(
"tasks__task_objects", filter=(task_status_filter & Q(tasks__week__lte=available_week))
),
# Общее кол-во ответов юзера на опубликованные вопросы в рамках навыка.
total_user_answers=Count(
"tasks__task_objects__user_results",
filter=(
task_status_filter
& Q(tasks__task_objects__user_results__user_profile_id=self.profile_id)
& Q(tasks__week__lte=available_week) # Только доступыне недели.
),
),
),
id=task.skill.id,
skill_status_filter = DBObjectStatusFilters().get_skill_status_for_user(self.request.user)
tasks_of_skill: QuerySet[Task] = (
Task.available
.only_awailable_weeks(available_week, self.request.user)
.prefetch_related(
Prefetch(
"task_objects__user_results",
queryset=TaskObjUserResult.objects.filter(user_profile_id=self.profile_id),
to_attr="filtered_user_results",
)
).prefetch_related("task_objects")
.annotate(task_objects_count=Count("task_objects"))
.filter(skill_id=task.skill.id)
.filter(skill_status_filter)
)
progress: int = get_rounded_percentage(skill.total_user_answers, skill.total_num_questions)

user_done_task_objects: list[int] = []
all_task_objects: list[int] = []
for task_on_skill in tasks_of_skill:
user_results_count = sum(1 for obj in task_on_skill.task_objects.all() if obj.filtered_user_results)
user_done_task_objects.append(user_results_count)
all_task_objects.append(task_on_skill.task_objects_count)

progress = get_rounded_percentage(sum(user_done_task_objects), sum(all_task_objects))

data = TaskResultData(
points_gained=task.points_gained if task.points_gained else 0,
quantity_done_correct=task.total_answers,
quantity_all=task.total_questions,
level=task.level,
progress=progress,
skill_name=skill.name,
skill_name=task.skill.name,
next_task_id=task.next_task_id if task.next_task_id else None,
)

Expand Down
6 changes: 5 additions & 1 deletion apps/procollab_skills/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

class IfSubscriptionOutdatedPermission(permissions.BasePermission):
"""
Проверка пользователя на то, не просрочена ли у него подписка
Проверка пользователя на то, не просрочена ли у него подписка.
Персоналу (`is_staff`, `is_superuser` на skills доступ есть без подписки).
"""

def has_permission(self, request, view) -> bool:
if request.user and (request.user.is_superuser or request.user.is_staff):
return True

user_sub_date = view.user_profile.last_subscription_date
if not user_sub_date:
raise PermissionDenied("Подписка просрочена.")
Expand Down
9 changes: 7 additions & 2 deletions apps/progress/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,15 @@ def get_user_available_week(profile_id: int) -> tuple[int, CustomUser]:
"""
Получение доступных пользователю недель, все что <= `available_week` доступно.
Если подписка вообще не оформлена, то -> 0, соотв. ничего не доступно.
Для `superuser` и `staff` доступно все (4 недели).
Пример запроса для Task:
`Q(week__lte=available_week)`
"""
user_profile = UserProfile.objects.get(pk=profile_id)
user_profile = UserProfile.objects.select_related("user").get(pk=profile_id)

if user_profile.user.is_superuser or user_profile.user.is_staff:
return 4, user_profile.user

subscription_date: datetime = user_profile.last_subscription_date

if subscription_date:
Expand Down Expand Up @@ -224,7 +229,7 @@ def get_task_skill_status_for_for_user(user: CustomUser):
return skill_status

@staticmethod
def get_skill_status_for_for_user(user: CustomUser):
def get_skill_status_for_user(user: CustomUser):
skill_status = Q(skill__status="published")
if user and user.is_superuser:
skill_status = Q()
Expand Down
20 changes: 15 additions & 5 deletions apps/progress/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def check_week_stat(task_obj_id: int) -> None:
.for_user(user_profile.user)
.filter(skill__pk=skill.pk, week=week)
.annotate(
task_objects_count=Count("task_objects"), # Общее кол-во вопросов.
task_objects_count=Count("task_objects", distinct=True), # Общее кол-во вопросов.
task_objects_done=Count( # Общее кол-во ответов.
"task_objects__user_results",
filter=Q(task_objects__user_results__user_profile_id=user_profile.id),
Expand All @@ -57,7 +57,10 @@ def check_week_stat(task_obj_id: int) -> None:

# Баллы начисляются только при условии, что все сделано
# и сделано вовремя (текущая доступная неделя == неделя задания).
additional_points: int = AdditionalPoints.MONTH.value if (all_done and week == available_week) else 0
if user_profile.user.is_superuser or user_profile.user.is_staff:
additional_points: int = AdditionalPoints.MONTH.value
else:
additional_points: int = AdditionalPoints.MONTH.value if (all_done and week == available_week) else 0

# Создание/Обновление записи результата недели.
UserWeekStat.objects.update_or_create(
Expand All @@ -81,8 +84,8 @@ def check_skill_done(task_obj_id: int) -> None:
skill: Skill = task.skill
user_profile: UserProfile = instance.user_profile
# Данные для проверки ответа вовремя от даты начала подписки, до + 30 дней.
subscription_date = user_profile.last_subscription_date
deadline = subscription_date + timedelta(days=30)
subscription_date = user_profile.last_subscription_date if user_profile.last_subscription_date else timezone.now()
deadline = subscription_date + timedelta(days=30) if subscription_date else timezone.now()

task_status_filter = DBObjectStatusFilters().get_many_tasks_status_filter_for_user(user_profile.user)

Expand All @@ -93,6 +96,7 @@ def check_skill_done(task_obj_id: int) -> None:
total_num_questions=Count( # Общее количество заданий.
"tasks__task_objects",
filter=task_status_filter,
distinct=True,
),
total_user_answers=Count( # Общее количество ответов.
"tasks__task_objects__user_results",
Expand All @@ -114,8 +118,14 @@ def check_skill_done(task_obj_id: int) -> None:
.get(pk=skill.pk)
)
# Навык считается пройденным если сделаны все части задачи, доп. баллы - если сделан вовремя.
# Для `superuser` и `staff` таймлайн не учитывается.
if skill.total_user_answers == skill.total_num_questions:
additional_points = AdditionalPoints.SKILL.value if skill.total_num_questions == skill.timely_responses else 0
if user_profile.user.is_superuser or user_profile.user.is_staff:
additional_points = AdditionalPoints.SKILL.value
else:
additional_points = (
AdditionalPoints.SKILL.value if skill.total_num_questions == skill.timely_responses else 0
)
UserSkillDone.objects.update_or_create(
user_profile=user_profile,
skill=skill,
Expand Down
3 changes: 3 additions & 0 deletions apps/subscription/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class Meta:
]

def get_is_subscribed(self, obj: UserProfile) -> bool:
if obj.user.is_superuser or obj.user.is_staff:
return True

user_sub_date = obj.last_subscription_date
thirty_days_ago = datetime.now().date() - timedelta(days=30)

Expand Down
3 changes: 3 additions & 0 deletions apps/subscription/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ def list(self, request, *args, **kwargs) -> Response:
is_logged_in = isinstance(self.user, CustomUser)
profile: UserProfile = self.user_profile if hasattr(self, "user_profile") else None

if request.user and (request.user.is_superuser or request.user.is_staff):
return Response("subscription is active", status=200)

if (
profile
and profile.last_subscription_date
Expand Down

0 comments on commit e5c09a1

Please sign in to comment.