首页 > 编程问答 >在 Django 中构建动态任务和徽章评估系统

在 Django 中构建动态任务和徽章评估系统

时间:2024-07-28 05:32:57浏览次数:12  
标签:python django algorithm django-models

模型

任务

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)

我正在开发一个社交媒体平台,用户可以根据他们的活动获得奖励(任务和徽章)。我的目标是创建一个灵活的系统,可以动态评估这些奖励,而无需每次引入新任务或徽章时都硬编码新条件。

到目前为止我们所做的:

  1. 任务和徽章模型| ||:我们为 Quest Badge UserQuestProgress 定义了模型,用于跟踪用户的进度。 UserBadgeProgress 标准 JSON
  2. :我们使用 JSON 来定义每个任务或任务的标准徽章。例如: 评估函数
    {
        "Post": {
            "reaction_type_within_timeframe": ["dislike", 30, 50]
        }
    }
    
  3. :我们有一个 处理标准 JSON 来计算用户进度的函数: evaluate_quest Celery 任务
    def evaluate_quest(user, criteria):
        # Evaluate criteria and calculate progress
    
  4. :当用户导航到任务或徽章页面时,触发 Celery 任务来评估其进度并更新 UserQuestProgress UserBadgeProgress 当前挑战:

硬编码场景

  • :当前的实现仍然需要我们在 函数中硬编码新场景。我们的目标是动态处理各种复杂的情况,但正在努力应对未知的未来场景。 evaluate_quest 示例场景
  • 首次发布状态更新或任何其他帖子
    • 您已完成您的所有个人资料信息字段
      {
          "Post": {
              "first_post": true
          }
      }
      
    • : 对您关注者的帖子给出 50 条喜欢和/或喜爱的反应
      {
          "Profile": {
              "complete_profile": true
          }
      }
      
    • : 单个帖子收到 100 多个喜欢和 50 多条评论
      {
          "Reaction": {
              "reaction_type": ["like", "love"],
              "count": 50,
              "on_followers_posts": true
          }
      }
      
    • :|| |用户首先对 50 多个朋友的帖子做出反应
      {
          "Post": {
              "likes": 100,
              "comments": 50,
              "single_post": true
          }
      }
      
    • 工作流程: 当用户导航到任务或徽章页面时,Celery 任务启动。
      {
          "Reaction": {
              "first_reaction": true,
              "on_friends_posts": true,
              "count": 50
          }
      }
      

该任务使用

  1. 来确保有一个进度实例。
  2. get_or_create(user, quest|badge) 函数被调用来计算当前值和目标值。
  3. evaluate_quest
  4. 模型已覆盖 UserQuestProgress 如果当前值满足则标记完成的方法或超过目标值。 UserBadgeProgress 保存后,如果任务或徽章已完成, save 信号会通过 WebSocket 通知用户。
  5. 当前 post_save 功能:

关注点: evaluate_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

函数中对特定条件进行硬编码。

  • 我们需要一种更加动态的方法,可以处理未知的未来场景,而不需要更改代码。 evaluate_quest 请求帮助:
  • 我们怎样才能使| ||函数更加动态地处理各种复杂的条件,而无需对每个新场景进行硬编码?

Django 中是否有任何现有的库或模式可以帮助实现这种灵活性?

  • 我们需要处理的复杂条件的示例:|| |首次发布状态更新。 evaluate_quest 填写所有个人资料信息字段。
  • 在关注者的帖子上给予 50 个点赞和/或喜爱反应。
  • 收到 100 多个点赞和 50 多个
    • 评论单个帖子。
    • 首先对 50 多个朋友的帖子做出反应。
    • 当前工作流程:
    • 用户导航到任务或徽章页面,触发 Celery 任务。

该任务评估所有任务或徽章用户使用

调用

  1. 函数来计算进度。
  2. 进度根据 get_or_create(user, quest|badge) 函数的响应进行更新。
  3. evaluate_quest 中的
  4. 方法。如果当前值达到或超过目标值, evaluate_quest 自动标记完成。
  5. A save 如果任务或徽章已完成,则信号通过 WebSocket 通知用户。 UserQuestProgress 制作此系统的任何见解或建议更加强大和动态将不胜感激。谢谢你! UserBadgeProgress automatically marks completion if the current value meets or exceeds the target value.
  6. A post_save signal notifies the user via WebSocket if a quest or badge is completed.

Any insights or suggestions to make this system more robust and dynamic would be greatly appreciated. Thank you!


好的,以下是构建动态任务和徽章评估系统的方法,该系统解决了的担忧并最大程度地减少了 Django 中的硬编码逻辑:

1. 基于规则引擎的标准

与其直接在 evaluate_quest 函数中对条件进行硬编码,不如使用基于规则的引擎来实现更大的灵活性。可以使用 Python 库(如 rules )或开发自己的简单引擎。

  • 定义规则: 为常见场景创建可重用的规则函数。

```python from rules import predicate, Rule

@predicate def has_posted(user): return user.posts.exists()

@predicate def profile_complete(user): # 检查个人资料字段 pass

@predicate def reaction_count_on_followers_posts(user, reaction_type, min_count): return user.reactions_given.filter( reaction_type=reaction_type, post__author__in=user.following.all() ).count() >= min_count

# ... 为其他条件添加更多规则

# 定义规则 first_post_rule = Rule(has_posted) complete_profile_rule = Rule(profile_complete) likes_on_followers_posts_rule = Rule(reaction_count_on_followers_posts) # ... 更多规则 ```

  • 将规则存储在标准中: criteria JSON 字段中存储规则名称和必要参数。

json { "rules": [ {"name": "first_post"}, {"name": "complete_profile"}, {"name": "reaction_count_on_followers_posts", "args": ["like", 50]} ] }

  • 评估规则: 修改 evaluate_quest 以动态获取和应用规则。

```python def evaluate_quest(user, criteria): criteria = json.loads(criteria) progress = 0 total_rules = len(criteria['rules'])

   for rule_data in criteria['rules']:
       rule_name = rule_data['name']
       args = rule_data.get('args', [])

       # 通过名称动态获取规则函数
       rule_function = globals().get(rule_name + '_rule') 
       if rule_function:
           if rule_function.test(user, *args): 
               progress += 1

   return progress / total_rules if total_rules else 0

```

2. 自定义管理器或查询集

为常用操作创建自定义管理器或查询集,以使代码更清晰、更可重用。

```python class PostQuerySet(models.QuerySet): def within_timeframe(self, timeframe): return self.filter(created_at__gte=timezone.now() - timedelta(minutes=timeframe))

   def with_reaction_type(self, reaction_type):
       return self.filter(reactions__reaction_type=reaction_type)

class PostManager(models.Manager): def get_queryset(self): return PostQuerySet(self.model, using=self._db)

   # ... 添加更多方法以简化常见查询

class Post(models.Model): # ... objects = PostManager() ```

3. 信号和任务队列

  • 信号: 继续使用信号(例如 post_save m2m_changed )来检测触发任务或徽章评估的用户操作。这可以防止对数据库进行不必要的查询。
  • 任务队列(Celery): 使用 Celery 等任务队列在后台异步处理评估。这将提高性能并提供更好的用户体验。

4. 改进的标准结构

考虑使用更结构化的格式来定义标准,以实现更好的可扩展性。例如,可以使用类似 JSON Schema 的方法来定义可用的条件、它们的预期数据类型以及对 evaluate_quest 函数的任何验证。

示例场景实现

让我们看看如何使用基于规则的方法实现“对关注者的帖子给予 50 个点赞”场景:

# rules.py 或类似文件
@predicate
def reaction_count_on_followers_posts(user, reaction_type, min_count):
   return user.reactions_given.filter(
       reaction_type=reaction_type,
       post__author__in=user.following.all()
   ).count() >= min_count

likes_on_followers_posts_rule = Rule(reaction_count_on_followers_posts)

# models.py 中的任务/徽章标准
{
   "rules": [
       {"name": "reaction_count_on_followers_posts", "args": ["like", 50]} 
   ]
}

# evaluate_quest 函数
# ...(如前所述)

通过此设置,可以在不修改代码库的情况下轻松定义新规则和条件,从而使的任务和徽章系统更加灵活和可维护。

其他提示

  • 彻底测试: 为规则评估逻辑实施全面的单元测试,以确保在添加新功能时系统的正确性。
  • 缓存: 考虑缓存任务和徽章进度,尤其是当计算成本很高时。
  • 管理员界面: 如果需要,请创建一个用户友好的管理员界面,以供非技术用户定义新任务、徽章及其标准。

通过遵循这些原则,可以构建一个健壮、灵活和可扩展的 Django 任务和徽章评估系统,该系统可以适应不断变化的需求,而无需持续的硬编码修改。

标签:python,django,algorithm,django-models
From: 78802500

相关文章

  • Django-React 应用程序中的静态文件未在生产环境中加载
    我正在Docker容器中运行Django应用程序,但在生产中提供静态文件时遇到问题。本地一切工作正常,但是当我部署到生产环境时,静态文件不会加载,并且出现404错误。以下是我的设置的相关部分:Djangosettings.py:TEMPLATES=[{'BACKEND':......
  • Python 中的“样板”代码?
    Google有一个Python教程,他们将样板代码描述为“不幸的”,并提供了以下示例:#!/usr/bin/python#importmodulesusedhere--sysisaverystandardoneimportsys#Gatherourcodeinamain()functiondefmain():print'Hellothere',sys.argv[1]#Command......
  • Python 3.9.1 中的 collections.abc.Callable 是否有 bug?
    Python3.9包含PEP585并弃用typing模块中的许多类型,转而支持collections.abc中的类型,现在它们支持__class_getitem__例如Callable就是这种情况。对我来说,typing.Callable和collections.abc.Ca......
  • 列表子类的 Python 类型
    我希望能够定义列表子类的内容必须是什么。该类如下所示。classA(list):def__init__(self):list.__init__(self)我想包含键入内容,以便发生以下情况。importtypingclassA(list:typing.List[str]):#Maybesomethinglikethisdef__init__(self):......
  • Python 中类型友好的委托
    考虑以下代码示例defsum(a:int,b:int):returna+bdefwrap(*args,**kwargs):#delegatetosumreturnsum(*args,**kwargs)该代码运行良好,只是类型提示丢失了。在Python中使用*args,**kwargs来实现​​委托模式是很常见的。如果有一种方法可......
  • 使用 python 支持构建自定义 vim 二进制文件
    背景Debian11vim软件包不包含python3支持。请参阅标题为“Debian11vim中不支持python-证据”的部分下面我需要vim支持python3YouCompleteMevim插件为了构建一个新的,我将vim9.0tarball下载到v......
  • 如何在Python 3.12+中正确使用泛型来提高代码质量?
    我正在尝试使用泛型来改进FastAPI应用程序中的类型注释。我有一个抽象存储库类,在其中使用泛型:fromabcimportABC,abstractmethodfromtypingimportListclassAbstractRepository[T](ABC):@abstractmethodasyncdefadd_one(self,data:dict)->T:......
  • python中的while循环不退出
    我试图完成第一年的python商业课程作业,但我的while循环无法退出,有人能帮忙吗?commisionTable=[{"admin_fee":100,"comm_rate":0.10},{"admin_fee":125,"comm_rate":0.12},{"admin_fee":150,"comm_rate":......
  • python---json文件写入
    ​ 使用到的知识点:os模块执行linux指令、json.dump()、withopenasf代码实现importsysimportosimportjson #向json文件file中添加内容data,其中data的类型为字典defwrite_json(file,data):    #如果文件存在,则删除    if(os.path.exists(fi......
  • python错题记录:布尔运算与逻辑值检测
    一前言环境:python3.10win10二布尔运算与逻辑值检测1案例案例1如上,在布尔运算时,有些时候代码只会运算前面的一部分,剩下的部分根本不会运算。以前在练习算法代码时,就利用这个规则来减少代码的工作量案例2如上,之前好长一段时间,上面的布尔运算总是让我感到困惑布尔运......