Quest
Код: Выделить всё
class Quest(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
criteria = models.JSONField() # Store criteria as JSON
reward_points = models.IntegerField(default=0)
def __str__(self):
return self.name
Значок
Код: Выделить всё
class Badge(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
criteria = models.JSONField() # Store criteria as JSON
reward_points = models.IntegerField(default=0)
def __str__(self):
return self.name
UserQuestProgress
Код: Выделить всё
class UserQuestProgress(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
quest = models.ForeignKey(Quest, on_delete=models.CASCADE)
current_value = models.IntegerField(default=0)
target_value = models.IntegerField()
completed = models.BooleanField(default=False)
completed_at = models.DateTimeField(null=True, blank=True)
def save(self, *args, **kwargs):
if self.current_value >= self.target_value:
self.completed = True
if not self.completed_at:
self.completed_at = timezone.now()
super().save(*args, **kwargs)
UserBadgeProgress
Код: Выделить всё
class UserBadgeProgress(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
badge = models.ForeignKey(Badge, on_delete=models.CASCADE)
current_value = models.IntegerField(default=0)
target_value = models.IntegerField()
completed = models.BooleanField(default=False)
completed_at = models.DateTimeField(null=True, blank=True)
def save(self, *args, **kwargs):
if self.current_value >= self.target_value:
self.completed = True
if not self.completed_at:
self.completed_at = timezone.now()
super().save(*args, **kwargs)
Что мы сделали? Далеко:
- Модели для квестов и значков: мы определили модели для квестов, значков< /code>, UserQuestProgress и UserBadgeProgress, которые отслеживают прогресс пользователя.
- Критерий JSON: мы используем JSON для определения критериев. за каждый квест или значок. Например:
Код: Выделить всё
{ "Post": { "reaction_type_within_timeframe": ["dislike", 30, 50] } } - Функция оценки: у нас есть функция Assessment_quest, которая обрабатывает критерии JSON для вычисления прогресса пользователя:
Код: Выделить всё
def evaluate_quest(user, criteria): # Evaluate criteria and calculate progress - Задача «Сельдерей»: когда пользователь переходит на страницу квеста или значка, запускается задача «Сельдерей», чтобы оценить его прогресс и обновить UserQuestProgress или UserBadgeProgress.
- Сценарии жесткого кодирования. Текущая реализация по-прежнему требует от нас жестко запрограммировать новые сценарии в функции Assessment_quest. Мы стремимся динамически обрабатывать различные сложные условия, но сталкиваемся с неизвестными будущими сценариями.
- Примеры сценариев:
Публикуйте обновление статуса или любое другое сообщение впервые:Код: Выделить всё
{ "Post": { "first_post": true } } - Вы заполнили все поля информации своего профиля:
Код: Выделить всё
{ "Profile": { "complete_profile": true } } - Поставьте 50 лайков и/или лайков на посты своих подписчиков:
Код: Выделить всё
{ "Reaction": { "reaction_type": ["like", "love"], "count": 50, "on_followers_posts": true } } - Получено более 100 лайков и более 50 комментариев к одному сообщению:
Код: Выделить всё
{ "Post": { "likes": 100, "comments": 50, "single_post": true } } - Пользователь первым отреагировал на сообщения более чем 50 друзей:
Код: Выделить всё
{ "Reaction": { "first_reaction": true, "on_friends_posts": true, "count": 50 } }
Рабочий процесс:
- Когда пользователь переходит на страницу квеста или значка, запускается задача Celery.
- Задача использует get_or_create(user, quest|badge), чтобы убедиться в наличии экземпляра прогресса.
- Функция Assessment_quest вызывается для вычисления текущего и целевого значений.
Модели UserQuestProgress и UserBadgeProgress переопределили методы save, чтобы отметить завершение, если текущее значение соответствует целевому значению или превышает его. - При сохранении сигнал post_save уведомляет пользователя через WebSocket, если квест или значок выполнен.
Текущая функция Assessment_quest:
Код: Выделить всё
def evaluate_quest(user, criteria):
criteria = json.loads(criteria)
progress = 0
total_conditions = 0
for model_name, conditions in criteria.items():
Model = apps.get_model('your_app_name', model_name) # Dynamically get the model
queryset = Model.objects.filter(user=user)
for field, condition in conditions.items():
if field == 'time_range':
start_time, end_time, target = condition
queryset = queryset.filter(
created_at__time__gte=start_time,
created_at__time__lte=end_time
)
current_value = queryset.count()
elif field == 'reaction_type_within_timeframe':
reaction_type, timeframe, target = condition
time_delta = timedelta(minutes=timeframe)
queryset = queryset.annotate(
reactions_within_timeframe=Count(
Case(
When(
reactions__reaction_type=reaction_type,
reactions__created_at__lte=ExpressionWrapper(
F('created_at') + time_delta,
output_field=DurationField()
),
then='reactions'
)
)
)
)
current_value = queryset.filter(reactions_within_timeframe__gte=target).count()
elif field == 'consecutive_days':
target_days = condition
dates = queryset.values_list('created_at', flat=True)
dates = sorted(dates)
current_streak = 0
max_streak = 0
for i in range(1, len(dates)):
if (dates[i] - dates[i - 1]).days == 1:
current_streak += 1
else:
max_streak = max(max_streak, current_streak)
current_streak = 0
max_streak = max(max_streak, current_streak)
current_value = max_streak
target = target_days
elif field == 'linked_social_networks':
target_value = condition
social_fields = [
'facebook_username', 'twitter_username', 'instagram_username',
'twitch_username', 'google_username', 'youtube_username',
'patreon_username', 'discord_username', 'deviantart_username',
'behance_username', 'dribble_username', 'artstation_username'
]
current_value = sum(bool(getattr(queryset.first(), field)) for field in social_fields)
target = target_value
else:
value, target = condition
if isinstance(value, str):
queryset = queryset.filter(**{field: value})
elif isinstance(value, dict) and 'range' in value:
start, end = value['range']
queryset = queryset.filter(**{f"{field}__gte": start, f"{field}__lte": end})
current_value = queryset.count()
# Calculate progress for this condition
if target > 0:
progress += current_value / target
total_conditions += 1
# Average progress across all conditions
if total_conditions > 0:
progress = progress / total_conditions
return progress
- Текущая реализация по-прежнему требует от нас жестко запрограммировать конкретные условия в Assessment_quest.
- Нам нужен более динамичный подход, позволяющий обрабатывать неизвестные будущие сценарии без необходимости изменения кода.
- Как мы можем сделать функцию Assessment_quest более динамичной для обработки различных сложных условиях без жесткого кодирования каждого нового сценария?
- Существуют ли в Django какие-либо библиотеки или шаблоны, которые могут помочь достичь такой гибкости?
- Примеры сложных условий, которые нам нужно обработать:
Первую публикацию обновления статуса. - Заполнение всех полей с информацией профиля.
- Поставление 50 лайков и/или лайков на посты подписчиков.
- Получение 100+ лайков и 50+
- Реагирует первым на 50 + сообщения друзей.
- < li>Пользователь переходит на страницу квеста или значка, запуская задачу Celery.
- Задача оценивает все квесты или значки для пользователя с помощью get_or_create(user, quest|badge) .
- Функция Assessment_quest вызывается для расчета прогресса.
- Прогресс обновляется на основе ответа от Assessment_quest функция.
- Метод save в UserQuestProgress и UserBadgeProgress автоматически отмечает завершение, если текущее значение соответствует целевому значению или превышает его.
- Сигнал post_save уведомляет пользователя через WebSocket о выполнении квеста или значка.
Подробнее здесь: https://stackoverflow.com/questions/788 ... -in-django