import logging from django.db import models, transaction from django.conf import settings from django.utils import timezone logger = logging.getLogger(__name__) class PointValue(models.Model): key = models.CharField(max_length=255, unique=True) point_value = models.IntegerField() experience_value = models.IntegerField() digest = models.CharField(max_length=64) reason = models.CharField(max_length=255) comment = models.TextField() user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'pingcap_point_value' class UserPointStat(models.Model): user = models.OneToOneField( settings.AUTH_USER_MODEL, related_name="point_stat", on_delete=models.CASCADE) points = models.IntegerField(default=0) experience = models.IntegerField(default=0) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'pingcap_user_point_stat' permissions = ( ('manage_points_system', 'Manage points system'), ) @property def level(self): if self.experience < 50: return 1 elif self.experience < 100: return 2 elif self.experience < 200: return 3 elif self.experience < 500: return 4 elif self.experience < 1000: return 5 elif self.experience < 2000: return 6 elif self.experience < 5000: return 7 elif self.experience < 10000: return 8 elif self.experience < 20000: return 9 elif self.experience < 50000: return 10 else: return 11 @property def rank(self): return UserPointStat.objects.filter(experience__gt=self.experience).count() + 1 def get_current_level_desc(self): levels = { 1: [0, 49], 2: [50, 99], 3: [100, 199], 4: [200, 499], 5: [500, 999], 6: [1000, 1999], 7: [2000, 4999], 8: [5000, 9999], 9: [10000, 19999], 10: [20000, 49999], 11: [50000, 1000000], } min_exps, max_exps = levels.get(self.level, [0, 49]) progress_at_current_level = round((self.experience - min_exps) / (max_exps - min_exps), 4) return progress_at_current_level, min_exps, max_exps @classmethod def update_points_and_exps(cls, target_user, points, exps): return cls._default_manager.filter(user=target_user).update( points=models.F('points') + points, experience=models.F('experience') + exps, updated_at=timezone.now() ) @classmethod def get_stat(cls, target_user): try: stat = cls._default_manager.get(user=target_user) except UserPointStat.DoesNotExist: stat = cls._default_manager.create( user=target_user, points=0, experience=0 ) return stat class UserAwardedPoint(models.Model): target_user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="awardedpoint_records", on_delete=models.CASCADE ) acting_user = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, related_name="awardedpoint_acting_records", on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) value = models.ForeignKey(PointValue, null=True, on_delete=models.CASCADE) digest = models.CharField(max_length=64) reason = models.CharField(max_length=255) comment = models.CharField(max_length=255) points = models.IntegerField() experience = models.IntegerField() award_type = models.CharField(max_length=32, db_index=True, blank=True, null=True, default=None) discourse_topic_id = models.IntegerField(blank=True, null=True) discourse_post_id = models.IntegerField(blank=True, null=True) discourse_user_action_id = models.IntegerField(blank=True, null=True) revoked_by = models.ForeignKey('self', on_delete=models.DO_NOTHING, blank=True, null=True) class Meta: db_table = 'pingcap_user_awarded_point' class PointProduct(models.Model): name = models.CharField(max_length=128) cover_url = models.URLField() points = models.IntegerField(default=1) amount = models.IntegerField() remains = models.IntegerField() description = models.TextField() sort_order = models.IntegerField(default=0) pe_exchange_code_id = models.IntegerField( blank=True, null=True, help_text='If this product is a PE exchange code, please fill in the ID of the exchange code' ) deleted_by_user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name="deleted_point_products", on_delete=models.DO_NOTHING, blank=True, null=True ) deleted_at = models.DateTimeField(blank=True, null=True) class Meta: db_table = 'pingcap_point_products' class PointProductRedemptionRecord(models.Model): user = models.ForeignKey( settings.AUTH_USER_MODEL, related_name='pointredemptionrecords', on_delete=models.CASCADE) product = models.ForeignKey(PointProduct, on_delete=models.PROTECT) points = models.IntegerField() consignee_name = models.CharField(max_length=128) consignee_phone = models.CharField(max_length=128) consignee_addrss = models.CharField(max_length=255) comment = models.CharField(max_length=255) courier_company = models.CharField(max_length=64, default='') tracking_number = models.CharField(max_length=255, default='') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'pingcap_point_product_redemption_records' class PointDailyCheckIn(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) date = models.DateField() class Meta: db_table = 'pingcap_point_daily_checkin' unique_together = ('user', 'date') def award_points_by_value(target_user, points, exps, reason='', digest='', acting_user=None, point_value=None): with transaction.atomic(): UserAwardedPoint.objects.create( target_user=target_user, acting_user=acting_user, points=points, experience=exps, value=point_value, reason=reason, digest=digest ) if not UserPointStat.update_points_and_exps(target_user, points, exps): UserPointStat._default_manager.create( user=target_user, points=points, experience=exps ) def bulk_award_points_by_value(target_users, points, exps, reason='', digest='', acting_user=None, point_value=None): with transaction.atomic(): for target_user in target_users: UserAwardedPoint.objects.create( target_user=target_user, acting_user=acting_user, points=points, experience=exps, value=point_value, reason=reason, digest=digest ) if not UserPointStat.update_points_and_exps(target_user, points, exps): UserPointStat._default_manager.create( user=target_user, points=points, experience=exps ) def award_points_by_key(target_user, key, reason='', digest='', acting_user=None): point_value = PointValue.objects.get(key=key) award_points_by_value( target_user, point_value.points, point_value.experience_value, reason or point_value.reason, digest or point_value.digest, acting_user, point_value)