fix: 修复资产编辑无法保存的bug
- 恢复Asset模型STATUS_CHOICES定义,状态字段使用下拉选择而非文本输入 - 修复status默认值从中文'在用'改为英文key'in_use',与数据库存储一致 - 编辑表单新增资产面值(asset_value)字段 - excel_utils适配字段类型变更(DecimalField→CharField, DateField→CharField等)
Bu işleme şunda yer alıyor:
@@ -173,41 +173,26 @@ def import_assets_from_excel(ws, category_map, operator=None):
|
|||||||
# 处理状态
|
# 处理状态
|
||||||
status = STATUS_MAP_REVERSE.get(data.get('status', '在用'), 'in_use')
|
status = STATUS_MAP_REVERSE.get(data.get('status', '在用'), 'in_use')
|
||||||
|
|
||||||
# 处理日期
|
# 处理日期 - 直接存字符串
|
||||||
from datetime import datetime as dt
|
purchase_date = data.get('purchase_date', '').strip() or ''
|
||||||
purchase_date = None
|
warranty_expire = data.get('warranty_expire', '').strip() or ''
|
||||||
warranty_expire = None
|
|
||||||
if data.get('purchase_date'):
|
|
||||||
try:
|
|
||||||
purchase_date = dt.strptime(data['purchase_date'], '%Y-%m-%d').date()
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if data.get('warranty_expire'):
|
|
||||||
try:
|
|
||||||
warranty_expire = dt.strptime(data['warranty_expire'], '%Y-%m-%d').date()
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 处理IP
|
# 处理IP
|
||||||
bmc_address = data.get('bmc_address') or None
|
bmc_address = data.get('bmc_address', '').strip() or ''
|
||||||
ip_address = data.get('ip_address') or None
|
ip_address = data.get('ip_address', '').strip() or ''
|
||||||
|
|
||||||
# 处理资产面值
|
# 资产面值 - 直接存字符串
|
||||||
from decimal import Decimal, InvalidOperation
|
asset_value = data.get('asset_value', '').strip() or ''
|
||||||
asset_value = None
|
|
||||||
if data.get('asset_value'):
|
|
||||||
try:
|
|
||||||
asset_value = Decimal(str(data['asset_value']).replace(',', ''))
|
|
||||||
except (InvalidOperation, ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 处理ID - 如果提供了ID且已存在,则更新该记录
|
# 处理ID - 如果提供了ID且已存在,则更新该记录
|
||||||
import_id = data.get('id', '').strip()
|
import_id = data.get('id', '').strip()
|
||||||
asset = None
|
asset = None
|
||||||
is_update = False
|
is_update = False
|
||||||
if import_id:
|
if import_id and import_id.isdigit():
|
||||||
try:
|
try:
|
||||||
asset = Asset.objects.get(id=int(import_id))
|
id_val = int(import_id) if str(import_id).strip().isdigit() else None
|
||||||
|
if id_val:
|
||||||
|
asset = Asset.objects.get(id=id_val)
|
||||||
is_update = True
|
is_update = True
|
||||||
except (Asset.DoesNotExist, ValueError):
|
except (Asset.DoesNotExist, ValueError):
|
||||||
asset = None
|
asset = None
|
||||||
@@ -228,7 +213,7 @@ def import_assets_from_excel(ws, category_map, operator=None):
|
|||||||
asset.ip_address = ip_address
|
asset.ip_address = ip_address
|
||||||
asset.gpu_type = data.get('gpu_type', '')
|
asset.gpu_type = data.get('gpu_type', '')
|
||||||
gpu_count_str = data.get('gpu_count', '').strip()
|
gpu_count_str = data.get('gpu_count', '').strip()
|
||||||
asset.gpu_count = int(gpu_count_str) if gpu_count_str else None
|
asset.gpu_count = gpu_count_str if gpu_count_str else ''
|
||||||
asset.purchase_date = purchase_date
|
asset.purchase_date = purchase_date
|
||||||
asset.warranty_expire = warranty_expire
|
asset.warranty_expire = warranty_expire
|
||||||
asset.supplier = data.get('supplier', '')
|
asset.supplier = data.get('supplier', '')
|
||||||
@@ -250,7 +235,7 @@ def import_assets_from_excel(ws, category_map, operator=None):
|
|||||||
else:
|
else:
|
||||||
# 创建新记录
|
# 创建新记录
|
||||||
gpu_count_str = data.get('gpu_count', '').strip()
|
gpu_count_str = data.get('gpu_count', '').strip()
|
||||||
gpu_count = int(gpu_count_str) if gpu_count_str else None
|
gpu_count = gpu_count_str if gpu_count_str else ''
|
||||||
|
|
||||||
# 创建参数
|
# 创建参数
|
||||||
create_kwargs = {
|
create_kwargs = {
|
||||||
@@ -281,9 +266,10 @@ def import_assets_from_excel(ws, category_map, operator=None):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 如果Excel提供了ID,使用该ID创建
|
# 如果Excel提供了ID,使用该ID创建
|
||||||
if import_id:
|
if import_id and import_id.isdigit():
|
||||||
try:
|
try:
|
||||||
create_kwargs['id'] = int(import_id)
|
if str(import_id).strip().isdigit():
|
||||||
|
create_kwargs['id'] = int(import_id)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
+49
-40
@@ -21,6 +21,41 @@ class Category(models.Model):
|
|||||||
class Asset(models.Model):
|
class Asset(models.Model):
|
||||||
"""硬件资产"""
|
"""硬件资产"""
|
||||||
|
|
||||||
|
# 基本信息
|
||||||
|
asset_number = models.CharField('资产编号', max_length=50, db_index=True)
|
||||||
|
name = models.CharField('设备名称', max_length=100)
|
||||||
|
category = models.ForeignKey(Category, on_delete=models.PROTECT, verbose_name='设备分类', related_name='assets')
|
||||||
|
|
||||||
|
# 硬件信息
|
||||||
|
brand = models.CharField('品牌', max_length=200, blank=True, default='')
|
||||||
|
model = models.CharField('型号', max_length=200, blank=True, default='')
|
||||||
|
asset_value = models.CharField('资产面值', max_length=50, blank=True, default='')
|
||||||
|
serial_number = models.CharField('序列号', max_length=200, blank=True, default='', db_index=True)
|
||||||
|
|
||||||
|
# 位置信息
|
||||||
|
location = models.CharField('机房', max_length=200, blank=True, default='', help_text='楼层/房间/区域')
|
||||||
|
cabinet = models.CharField('机柜', max_length=200, blank=True, default='')
|
||||||
|
cabinet_position = models.CharField('机柜位置', max_length=200, blank=True, default='', help_text='U位')
|
||||||
|
|
||||||
|
# 网络信息
|
||||||
|
bmc_address = models.CharField('BMC地址', max_length=200, blank=True, default='')
|
||||||
|
ip_address = models.CharField('IP地址', max_length=200, blank=True, default='')
|
||||||
|
|
||||||
|
# 显卡信息
|
||||||
|
has_gpu = models.CharField('是否带卡', max_length=50, blank=True, default='')
|
||||||
|
gpu_type = models.CharField('显卡类型', max_length=200, blank=True, default='')
|
||||||
|
gpu_count = models.CharField('卡数', max_length=50, blank=True, default='')
|
||||||
|
|
||||||
|
# 采购与质保
|
||||||
|
purchase_date = models.CharField('采购日期', max_length=50, blank=True, default='')
|
||||||
|
warranty_expire = models.CharField('质保到期', max_length=50, blank=True, default='')
|
||||||
|
supplier = models.CharField('供应商', max_length=200, blank=True, default='')
|
||||||
|
|
||||||
|
# 管理信息
|
||||||
|
responsible_person = models.CharField('负责人', max_length=200, blank=True, default='')
|
||||||
|
department = models.CharField('使用部门', max_length=200, blank=True, default='')
|
||||||
|
user = models.CharField('维护人', max_length=200, blank=True, default='')
|
||||||
|
business_type = models.CharField('业务类型', max_length=200, blank=True, default='')
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
('in_use', '在用'),
|
('in_use', '在用'),
|
||||||
('idle', '闲置'),
|
('idle', '闲置'),
|
||||||
@@ -28,43 +63,8 @@ class Asset(models.Model):
|
|||||||
('scrapped', '已报废'),
|
('scrapped', '已报废'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# 基本信息
|
status = models.CharField('状态', max_length=50, choices=STATUS_CHOICES, default='in_use')
|
||||||
asset_number = models.CharField('资产编号', max_length=50, db_index=True)
|
remark = models.CharField('备注', max_length=500, blank=True, default='')
|
||||||
name = models.CharField('设备名称', max_length=100)
|
|
||||||
category = models.ForeignKey(Category, on_delete=models.PROTECT, verbose_name='设备分类', related_name='assets')
|
|
||||||
|
|
||||||
# 硬件信息
|
|
||||||
brand = models.CharField('品牌', max_length=50, blank=True, default='')
|
|
||||||
model = models.CharField('型号', max_length=100, blank=True, default='')
|
|
||||||
asset_value = models.DecimalField('资产面值', max_digits=12, decimal_places=2, blank=True, null=True)
|
|
||||||
serial_number = models.CharField('序列号', max_length=100, blank=True, default='', db_index=True)
|
|
||||||
|
|
||||||
# 位置信息
|
|
||||||
location = models.CharField('机房', max_length=200, blank=True, default='', help_text='楼层/房间/区域')
|
|
||||||
cabinet = models.CharField('机柜', max_length=50, blank=True, default='')
|
|
||||||
cabinet_position = models.CharField('机柜位置', max_length=50, blank=True, default='', help_text='U位')
|
|
||||||
|
|
||||||
# 网络信息
|
|
||||||
bmc_address = models.GenericIPAddressField('BMC地址', blank=True, null=True)
|
|
||||||
ip_address = models.GenericIPAddressField('IP地址', blank=True, null=True)
|
|
||||||
|
|
||||||
# 显卡信息
|
|
||||||
has_gpu = models.CharField('是否带卡', max_length=20, blank=True, default='')
|
|
||||||
gpu_type = models.CharField('显卡类型', max_length=100, blank=True, default='')
|
|
||||||
gpu_count = models.IntegerField('卡数', blank=True, null=True)
|
|
||||||
|
|
||||||
# 采购与质保
|
|
||||||
purchase_date = models.DateField('采购日期', blank=True, null=True)
|
|
||||||
warranty_expire = models.DateField('质保到期', blank=True, null=True)
|
|
||||||
supplier = models.CharField('供应商', max_length=100, blank=True, default='')
|
|
||||||
|
|
||||||
# 管理信息
|
|
||||||
responsible_person = models.CharField('负责人', max_length=50, blank=True, default='')
|
|
||||||
department = models.CharField('使用部门', max_length=100, blank=True, default='')
|
|
||||||
user = models.CharField('维护人', max_length=50, blank=True, default='')
|
|
||||||
business_type = models.CharField('业务类型', max_length=100, blank=True, default='')
|
|
||||||
status = models.CharField('状态', max_length=20, choices=STATUS_CHOICES, default='in_use')
|
|
||||||
remark = models.TextField('备注', blank=True, default='')
|
|
||||||
|
|
||||||
# 系统字段
|
# 系统字段
|
||||||
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='创建人')
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='创建人')
|
||||||
@@ -82,14 +82,23 @@ class Asset(models.Model):
|
|||||||
@property
|
@property
|
||||||
def is_expired(self):
|
def is_expired(self):
|
||||||
if self.warranty_expire:
|
if self.warranty_expire:
|
||||||
return self.warranty_expire < date.today()
|
try:
|
||||||
|
from datetime import datetime
|
||||||
|
expire_date = datetime.strptime(self.warranty_expire, '%Y-%m-%d').date()
|
||||||
|
return expire_date < date.today()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_expiring_soon(self):
|
def is_expiring_soon(self):
|
||||||
if self.warranty_expire:
|
if self.warranty_expire:
|
||||||
from datetime import timedelta
|
try:
|
||||||
return date.today() <= self.warranty_expire <= date.today() + timedelta(days=30)
|
from datetime import datetime, timedelta
|
||||||
|
expire_date = datetime.strptime(self.warranty_expire, '%Y-%m-%d').date()
|
||||||
|
return date.today() <= expire_date <= date.today() + timedelta(days=30)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,14 +37,18 @@
|
|||||||
{{ form.category }}
|
{{ form.category }}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label class="form-label text-muted">{{ form.brand.label }}</label>
|
<label class="form-label text-muted">{{ form.brand.label }}</label>
|
||||||
{{ form.brand }}
|
{{ form.brand }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label class="form-label text-muted">{{ form.model.label }}</label>
|
<label class="form-label text-muted">{{ form.model.label }}</label>
|
||||||
{{ form.model }}
|
{{ form.model }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label text-muted">{{ form.asset_value.label }}</label>
|
||||||
|
{{ form.asset_value }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label text-muted">{{ form.serial_number.label }}</label>
|
<label class="form-label text-muted">{{ form.serial_number.label }}</label>
|
||||||
|
|||||||
Yeni konuda referans
Bir kullanıcı engelle