from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator, RegexValidator from django.db import models, transaction class DownloadRelease(models.Model): class Platform(models.TextChoices): WINDOWS = "windows", "Windows" MACOS = "macos", "macOS" LINUX = "linux", "Linux" platform = models.CharField("平台", max_length=32, choices=Platform.choices) version = models.CharField("版本", max_length=64) file = models.FileField("安装包文件", upload_to="downloads/", blank=True) external_url = models.URLField("外部下载地址", blank=True) size_bytes = models.PositiveBigIntegerField( "文件大小字节数", null=True, blank=True, validators=[MinValueValidator(1)], help_text="客户端下载文件大小,单位字节;桌面端用于下载后校验。", ) sha256 = models.CharField( "SHA256", max_length=64, blank=True, validators=[ RegexValidator( regex=r"^[A-Fa-f0-9]{64}$", message="SHA256 必须是 64 位十六进制字符。", ) ], ) is_current = models.BooleanField("当前版本", default=False) force_update = models.BooleanField("强制更新", default=False) release_notes = models.TextField("发布说明", blank=True) created_at = models.DateTimeField("创建时间", auto_now_add=True) updated_at = models.DateTimeField("更新时间", auto_now=True) class Meta: db_table = "download_release" verbose_name = "客户端下载版本" verbose_name_plural = "客户端下载版本" ordering = ("platform", "-is_current", "-created_at", "-id") indexes = [ models.Index( fields=("platform", "is_current"), name="dl_platform_current_idx", ), ] def __str__(self): return f"{self.get_platform_display()} {self.version}" @property def download_url(self) -> str: if self.external_url: return self.external_url if self.file: return self.file.url return "" @property def has_download(self) -> bool: return bool(self.download_url) def clean(self): super().clean() if not self.file and not self.external_url: raise ValidationError("安装包文件和外部下载地址至少填写一个。") def save(self, *args, **kwargs): with transaction.atomic(): super().save(*args, **kwargs) if self.is_current: type(self).objects.filter( platform=self.platform, is_current=True, ).exclude(pk=self.pk).update(is_current=False) class ImportTemplate(models.Model): name = models.CharField("模板名称", max_length=128) file = models.FileField("模板文件", upload_to="import_templates/", blank=True) external_url = models.URLField("外部下载地址", blank=True) sha256 = models.CharField( "SHA256", max_length=64, blank=True, validators=[ RegexValidator( regex=r"^[A-Fa-f0-9]{64}$", message="SHA256 必须是 64 位十六进制字符。", ) ], ) is_current = models.BooleanField("当前模板", default=False) notes = models.TextField("说明", blank=True) created_at = models.DateTimeField("创建时间", auto_now_add=True) updated_at = models.DateTimeField("更新时间", auto_now=True) class Meta: db_table = "import_template" verbose_name = "导入模板" verbose_name_plural = "导入模板" ordering = ("-is_current", "-created_at", "-id") indexes = [ models.Index( fields=("is_current",), name="it_current_idx", ), ] def __str__(self): return self.name @property def download_url(self) -> str: if self.external_url: return self.external_url if self.file: return self.file.url return "" @property def has_download(self) -> bool: return bool(self.download_url) def clean(self): super().clean() if not self.file and not self.external_url: raise ValidationError("模板文件和外部下载地址至少填写一个。") def save(self, *args, **kwargs): with transaction.atomic(): super().save(*args, **kwargs) if self.is_current: type(self).objects.filter(is_current=True).exclude(pk=self.pk).update( is_current=False )