91 lines
3.4 KiB
Python
91 lines
3.4 KiB
Python
import uuid
|
|
|
|
from django.contrib.auth.models import AbstractUser
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
|
|
|
|
class User(AbstractUser):
|
|
class Track(models.TextChoices):
|
|
BEGINNER = "beginner", "入门"
|
|
STANDARD = "standard", "标准"
|
|
ADVANCED = "advanced", "进阶"
|
|
OPEN = "open", "Open"
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
nickname = models.CharField(max_length=40)
|
|
avatar_url = models.URLField(blank=True)
|
|
bio = models.CharField(max_length=200, blank=True)
|
|
track = models.CharField(max_length=16, choices=Track.choices, default=Track.STANDARD)
|
|
rating = models.PositiveIntegerField(default=1000)
|
|
|
|
def __str__(self):
|
|
return self.nickname or self.username
|
|
|
|
|
|
class InviteCode(models.Model):
|
|
code = models.CharField(max_length=32, unique=True)
|
|
group = models.CharField(max_length=80, blank=True)
|
|
max_uses = models.PositiveIntegerField(default=1)
|
|
used_count = models.PositiveIntegerField(default=0)
|
|
expires_at = models.DateTimeField(null=True, blank=True)
|
|
is_active = models.BooleanField(default=True)
|
|
created_by = models.ForeignKey(
|
|
User, null=True, blank=True, on_delete=models.SET_NULL, related_name="invite_codes"
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
@property
|
|
def is_available(self):
|
|
return (
|
|
self.is_active
|
|
and self.used_count < self.max_uses
|
|
and (self.expires_at is None or self.expires_at > timezone.now())
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.code
|
|
|
|
|
|
class InviteCodeUsage(models.Model):
|
|
invite_code = models.ForeignKey(InviteCode, on_delete=models.PROTECT, related_name="usages")
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="invite_usage")
|
|
used_at = models.DateTimeField(auto_now_add=True)
|
|
ip_address = models.GenericIPAddressField(null=True, blank=True)
|
|
|
|
|
|
class UserSession(models.Model):
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="login_sessions")
|
|
session_key = models.CharField(max_length=40, unique=True)
|
|
user_agent = models.CharField(max_length=300, blank=True)
|
|
ip_address = models.GenericIPAddressField(null=True, blank=True)
|
|
last_seen_at = models.DateTimeField(auto_now=True)
|
|
revoked_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
|
|
class VisitorMigration(models.Model):
|
|
class Status(models.TextChoices):
|
|
PENDING = "pending", "待确认"
|
|
APPLIED = "applied", "已迁移"
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="visitor_migration")
|
|
payload = models.JSONField(default=dict)
|
|
summary = models.JSONField(default=dict)
|
|
status = models.CharField(max_length=16, choices=Status.choices, default=Status.PENDING)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
applied_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
|
|
class AuditLog(models.Model):
|
|
actor = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
|
|
action = models.CharField(max_length=80)
|
|
target_type = models.CharField(max_length=80)
|
|
target_id = models.CharField(max_length=80, blank=True)
|
|
reason = models.CharField(max_length=300, blank=True)
|
|
metadata = models.JSONField(default=dict, blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ["-created_at"]
|