Files
chunyu_project/user/models.py
T
2026-08-05 23:59:15 +08:00

311 lines
13 KiB
Python

from django.contrib.auth.models import AbstractUser
from django.db import models
class FUser(AbstractUser):
phone_number = models.CharField(max_length=15, blank=True, verbose_name='手机号')
avatar = models.ImageField(upload_to='avatars/', blank=True, null=True, verbose_name='头像')
bio = models.TextField(max_length=500, blank=True, verbose_name='个人简介')
isSetPassword = models.BooleanField(default=False, verbose_name='是否设置过密码')
gender = models.IntegerField(
choices=[(0, '保密'), (1, '男'), (2, '女')],
default=0,
verbose_name='性别'
)
location = models.CharField(max_length=100, blank=True, verbose_name='所在地区')
points = models.IntegerField(default=0, verbose_name='积分')
coins = models.IntegerField(default=0, verbose_name='y币')
cover_image = models.ImageField(upload_to='covers/', blank=True, null=True, verbose_name='个人主页封面')
PRIVACY_CHOICES = [
('public', '公开'),
('friends_only', '仅好友'),
('private', '仅自己'),
]
privacy_profile = models.CharField(max_length=20, choices=PRIVACY_CHOICES, default='public', verbose_name='主页可见性')
privacy_articles = models.CharField(max_length=20, choices=PRIVACY_CHOICES, default='public', verbose_name='文章可见性')
privacy_friends = models.CharField(max_length=20, choices=PRIVACY_CHOICES, default='public', verbose_name='好友列表可见性')
notify_email = models.BooleanField(default=True, verbose_name='邮件通知')
notify_browser = models.BooleanField(default=True, verbose_name='浏览器通知')
notify_reply = models.BooleanField(default=True, verbose_name='回复通知')
notify_like = models.BooleanField(default=True, verbose_name='点赞通知')
notify_follow = models.BooleanField(default=True, verbose_name='关注通知')
notify_system = models.BooleanField(default=True, verbose_name='系统通知')
class Meta:
verbose_name = '用户'
verbose_name_plural = '用户'
def save(self, *args, **kwargs):
if isinstance(self.gender, str):
try:
self.gender = int(self.gender)
except (ValueError, TypeError):
self.gender = 0
if self.gender not in (0, 1, 2):
self.gender = 0
super().save(*args, **kwargs)
def __str__(self):
return self.username
class PointTransaction(models.Model):
TRANSACTION_TYPES = [
('earn', '获得'),
('spend', '消费'),
]
CURRENCY_TYPES = [
('points', '积分'),
('coins', 'y币'),
]
user = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='transactions', verbose_name='用户')
transaction_type = models.CharField(max_length=10, choices=TRANSACTION_TYPES, verbose_name='交易类型')
currency_type = models.CharField(max_length=10, choices=CURRENCY_TYPES, verbose_name='货币类型')
amount = models.IntegerField(verbose_name='金额')
balance_after = models.IntegerField(verbose_name='交易后余额')
description = models.CharField(max_length=200, blank=True, verbose_name='描述')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
verbose_name = '积分交易记录'
verbose_name_plural = '积分交易记录'
ordering = ['-created_at']
def __str__(self):
return f"{self.user.username} - {self.get_transaction_type_display()} {self.amount} {self.get_currency_type_display()}"
class DailyCheckin(models.Model):
user = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='checkins', verbose_name='用户')
checkin_date = models.DateField(verbose_name='签到日期')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='签到时间')
class Meta:
verbose_name = '每日签到'
verbose_name_plural = '每日签到'
unique_together = ('user', 'checkin_date')
ordering = ['-checkin_date']
def __str__(self):
return f"{self.user.username} - {self.checkin_date}"
class Blacklist(models.Model):
user = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='blacklist_entries')
blocked_user = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='blocked_by')
reason = models.CharField(max_length=500, default='', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = '黑名单'
verbose_name_plural = '黑名单'
unique_together = ('user', 'blocked_user')
ordering = ['-created_at']
def __str__(self):
return f"{self.user.username} blocked {self.blocked_user.username}"
class Follow(models.Model):
follower = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='following', verbose_name='关注者')
following = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='followers', verbose_name='被关注者')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='关注时间')
class Meta:
verbose_name = '关注关系'
verbose_name_plural = '关注关系'
unique_together = ('follower', 'following')
ordering = ['-created_at']
def __str__(self):
return f"{self.follower.username} follows {self.following.username}"
class LoginRecord(models.Model):
user = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='login_records')
device = models.CharField(max_length=200, default='')
ip_address = models.GenericIPAddressField(null=True, blank=True)
location = models.CharField(max_length=200, default='')
login_time = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20, choices=[('success', '成功'), ('failed', '失败'), ('suspicious', '异常')], default='success')
class Meta:
ordering = ['-login_time']
verbose_name = '登录记录'
verbose_name_plural = '登录记录'
def __str__(self):
return f"{self.user.username} - {self.login_time}"
class Invitation(models.Model):
inviter = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='invitations_sent')
invitee = models.ForeignKey(FUser, on_delete=models.SET_NULL, null=True, blank=True, related_name='invitations_received')
invite_code = models.CharField(max_length=20, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
is_used = models.BooleanField(default=False)
class Meta:
ordering = ['-created_at']
class TaskDefinition(models.Model):
TASK_TYPE_CHOICES = [
('daily', '每日任务'),
('weekly', '每周任务'),
('special', '特别任务'),
]
ACTION_TYPE_CHOICES = [
('browse', '浏览页面'),
('learn', '学习时长'),
('post', '发表内容'),
('share', '分享内容'),
('checkin', '签到'),
('profile', '完善资料'),
('bind', '绑定手机/邮箱'),
('invite', '邀请新用户'),
('custom', '自定义'),
]
name = models.CharField(max_length=100, verbose_name='任务名称')
task_type = models.CharField(max_length=10, choices=TASK_TYPE_CHOICES, verbose_name='任务类型')
action_type = models.CharField(max_length=10, choices=ACTION_TYPE_CHOICES, verbose_name='行为类型')
description = models.TextField(blank=True, verbose_name='任务描述')
target_count = models.IntegerField(default=1, verbose_name='目标次数/时长(分钟)')
reward_points = models.IntegerField(default=0, verbose_name='奖励积分')
reward_coins = models.IntegerField(default=0, verbose_name='奖励y币')
reward_xp = models.IntegerField(default=0, verbose_name='奖励经验值')
is_active = models.BooleanField(default=True, verbose_name='是否启用')
sort_order = models.IntegerField(default=0, verbose_name='排序')
icon = models.CharField(max_length=50, default='TrophyOutlined', verbose_name='图标名称')
redirect_url = models.CharField(max_length=200, blank=True, verbose_name='跳转链接')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
class Meta:
verbose_name = '任务定义'
verbose_name_plural = '任务定义'
ordering = ['task_type', 'sort_order', 'id']
def __str__(self):
return f"[{self.get_task_type_display()}] {self.name}"
class UserTaskProgress(models.Model):
user = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='task_progress', verbose_name='用户')
task = models.ForeignKey(TaskDefinition, on_delete=models.CASCADE, related_name='user_progress', verbose_name='任务')
current_count = models.IntegerField(default=0, verbose_name='当前进度')
is_completed = models.BooleanField(default=False, verbose_name='是否完成')
is_claimed = models.BooleanField(default=False, verbose_name='是否已领取')
completed_at = models.DateTimeField(null=True, blank=True, verbose_name='完成时间')
claimed_at = models.DateTimeField(null=True, blank=True, verbose_name='领取时间')
period_key = models.CharField(max_length=20, verbose_name='周期标识')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
verbose_name = '用户任务进度'
verbose_name_plural = '用户任务进度'
unique_together = ('user', 'task', 'period_key')
ordering = ['-updated_at']
def __str__(self):
return f"{self.user.username} - {self.task.name} ({self.current_count}/{self.task.target_count})"
class LevelThreshold(models.Model):
level = models.IntegerField(unique=True, verbose_name='等级')
xp_required = models.IntegerField(verbose_name='所需经验值')
title = models.CharField(max_length=50, verbose_name='等级称号')
class Meta:
verbose_name = '等级阈值'
verbose_name_plural = '等级阈值'
ordering = ['level']
def __str__(self):
return f"Lv.{self.level} {self.title} ({self.xp_required} XP)"
class UserLevel(models.Model):
user = models.OneToOneField(FUser, on_delete=models.CASCADE, related_name='user_level', verbose_name='用户')
xp = models.IntegerField(default=0, verbose_name='当前经验值')
level = models.IntegerField(default=1, verbose_name='当前等级')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
verbose_name = '用户等级'
verbose_name_plural = '用户等级'
def __str__(self):
return f"{self.user.username} - Lv.{self.level} ({self.xp} XP)"
@property
def current_threshold(self):
return LevelThreshold.objects.filter(level__lte=self.level).order_by('-level').first()
@property
def next_threshold(self):
return LevelThreshold.objects.filter(level__gt=self.level).order_by('level').first()
class Region(models.Model):
"""地区三级联动表 - 省/市/区"""
REGION_LEVEL_CHOICES = [
(1, '省份'),
(2, '城市'),
(3, '区县'),
]
name = models.CharField(max_length=100, verbose_name='地区名称')
code = models.CharField(max_length=20, unique=True, verbose_name='地区编码')
level = models.IntegerField(choices=REGION_LEVEL_CHOICES, verbose_name='级别')
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children', verbose_name='上级地区')
pinyin = models.CharField(max_length=200, blank=True, verbose_name='拼音')
sort_order = models.IntegerField(default=0, verbose_name='排序')
class Meta:
verbose_name = '地区'
verbose_name_plural = '地区'
ordering = ['level', 'sort_order', 'code']
indexes = [
models.Index(fields=['parent', 'level']),
models.Index(fields=['code']),
]
def __str__(self):
return self.name
class UserDevice(models.Model):
DEVICE_TYPE_CHOICES = [
('desktop', '桌面端'),
('mobile', '移动端'),
('tablet', '平板'),
('other', '其他'),
]
user = models.ForeignKey(FUser, on_delete=models.CASCADE, related_name='devices', verbose_name='用户')
device_name = models.CharField(max_length=100, verbose_name='设备名称')
device_type = models.CharField(max_length=20, choices=DEVICE_TYPE_CHOICES, default='desktop', verbose_name='设备类型')
browser = models.CharField(max_length=100, blank=True, verbose_name='浏览器')
os = models.CharField(max_length=100, blank=True, verbose_name='操作系统')
ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP地址')
location = models.CharField(max_length=200, blank=True, verbose_name='登录地点')
user_agent = models.TextField(blank=True, verbose_name='User-Agent')
last_active = models.DateTimeField(auto_now=True, verbose_name='最后活跃时间')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
is_current = models.BooleanField(default=False, verbose_name='是否当前设备')
session_key = models.CharField(max_length=100, blank=True, verbose_name='会话标识')
class Meta:
verbose_name = '登录设备'
verbose_name_plural = '登录设备'
ordering = ['-last_active']
def __str__(self):
return f"{self.user.username} - {self.device_name} ({self.get_device_type_display()})"