import os from datetime import date from django.db import models from django.utils.translation import gettext_lazy as _ from PIL import Image from io import BytesIO from django.core.files.base import ContentFile from loguru import logger class DeviceStatus(models.TextChoices): NORMAL = 'normal', _('正常') WARNING = 'warning', _('告警') OFFLINE = 'offline', _('离线') REPAIR = 'repair', _('维修中') SCRAP = 'scrap', _('已报废') def device_attachment_path(instance, filename): return f'devices/{instance.id}/attachments/{filename}' def device_thumbnail_path(instance, filename): return f'devices/{instance.id}/thumbnails/{filename}' class Device(models.Model): location = models.CharField(max_length=100, verbose_name='地点') building = models.CharField(max_length=50, blank=True, null=True, verbose_name='楼栋') floor = models.CharField(max_length=20, blank=True, null=True, verbose_name='楼层') cabinet = models.CharField(max_length=50, blank=True, null=True, verbose_name='机柜') device_name = models.CharField(max_length=100, verbose_name='设备名称') model = models.CharField(max_length=100, blank=True, null=True, verbose_name='型号') brand = models.CharField(max_length=100, blank=True, null=True, verbose_name='品牌') mac_address = models.CharField(max_length=17, blank=True, null=True, verbose_name='MAC地址') enable_date = models.DateField(blank=True, null=True, verbose_name='启用日期') last_inspection_date = models.DateField(blank=True, null=True, verbose_name='最后巡检日期') thumbnail = models.ImageField(upload_to=device_thumbnail_path, blank=True, null=True, verbose_name='缩略图') status = models.CharField( max_length=20, choices=DeviceStatus.choices, default=DeviceStatus.NORMAL, verbose_name='状态' ) responsible_person = models.CharField(max_length=50, blank=True, null=True, verbose_name='运维人员') warranty_expire = models.DateField(blank=True, null=True, verbose_name='保修到期日期') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'device' verbose_name = '设备' verbose_name_plural = '设备' indexes = [ models.Index(fields=['location', 'building']), models.Index(fields=['status']), ] def __str__(self): return f'{self.device_name} - {self.location}' @property def service_duration_days(self): if self.enable_date: return (date.today() - self.enable_date).days return 0 @property def is_warranty_expired(self): if self.warranty_expire: return date.today() > self.warranty_expire return False def generate_thumbnail(self, image_field, size=(200, 200)): if not image_field: return try: img = Image.open(image_field) if img.mode in ('RGBA', 'P'): img = img.convert('RGB') img.thumbnail(size) thumb_io = BytesIO() img.save(thumb_io, format='JPEG', quality=85) thumb_name = f'thumb_{os.path.basename(image_field.name)}' self.thumbnail.save(thumb_name, ContentFile(thumb_io.getvalue()), save=False) logger.info(f'Generated thumbnail for device {self.id}') except Exception as e: logger.error(f'Failed to generate thumbnail: {e}') def save(self, *args, **kwargs): super().save(*args, **kwargs) class DeviceSerial(models.Model): device = models.ForeignKey(Device, on_delete=models.CASCADE, related_name='serials', verbose_name='设备') serial_number = models.CharField(max_length=100, unique=True, verbose_name='序列号') serial_type = models.CharField(max_length=50, default='main', verbose_name='序列号类型') is_primary = models.BooleanField(default=False, verbose_name='是否主序列号') remark = models.CharField(max_length=255, blank=True, null=True, verbose_name='备注') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'device_serial' verbose_name = '设备序列号' verbose_name_plural = '设备序列号' def __str__(self): return f'{self.serial_number} ({self.serial_type})' class DeviceIP(models.Model): device = models.ForeignKey(Device, on_delete=models.CASCADE, related_name='ips', verbose_name='设备') ip_address = models.CharField(max_length=45, unique=True, verbose_name='IP地址') ip_type = models.CharField(max_length=20, default='management', verbose_name='IP类型') is_primary = models.BooleanField(default=False, verbose_name='是否主IP') subnet_mask = models.CharField(max_length=15, blank=True, null=True, verbose_name='子网掩码') gateway = models.CharField(max_length=45, blank=True, null=True, verbose_name='网关') vlan_id = models.CharField(max_length=10, blank=True, null=True, verbose_name='VLAN ID') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'device_ip' verbose_name = '设备IP地址' verbose_name_plural = '设备IP地址' def __str__(self): return f'{self.ip_address} ({self.ip_type})' class MaintenanceRecord(models.Model): device = models.ForeignKey(Device, on_delete=models.CASCADE, related_name='maintenance_records', verbose_name='设备') maintenance_date = models.DateField(verbose_name='维修日期') fault_description = models.TextField(blank=True, null=True, verbose_name='故障描述') repair_content = models.TextField(blank=True, null=True, verbose_name='维修内容') replaced_parts = models.CharField(max_length=255, blank=True, null=True, verbose_name='更换配件') maintenance_by = models.CharField(max_length=50, blank=True, null=True, verbose_name='维修人') cost = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, verbose_name='费用') remark = models.TextField(blank=True, null=True, verbose_name='备注') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: db_table = 'maintenance_record' verbose_name = '维修记录' verbose_name_plural = '维修记录' ordering = ['-maintenance_date'] def __str__(self): return f'{self.device.device_name} - {self.maintenance_date}' class DeviceAttachment(models.Model): device = models.ForeignKey(Device, on_delete=models.CASCADE, related_name='attachments', verbose_name='设备') file = models.FileField(upload_to=device_attachment_path, verbose_name='文件') file_name = models.CharField(max_length=255, verbose_name='文件名') file_type = models.CharField(max_length=50, blank=True, null=True, verbose_name='文件类型') uploaded_at = models.DateTimeField(auto_now_add=True, verbose_name='上传时间') class Meta: db_table = 'device_attachment' verbose_name = '设备附件' verbose_name_plural = '设备附件' def __str__(self): return self.file_name