Initial commit: UPS management system

This commit is contained in:
xiaji
2026-04-28 17:11:19 +08:00
commit d888500a97
53 changed files with 2595 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
# UPS 主机和电池管理系统 - 实现计划
## 一、项目概述
本项目是一个基于 Django 的本地部署管理系统,用于管理 UPS 主机和电池的相关信息。
## 二、需求分析
### 2.1 数据模型需求
| 数据类型 | 字段需求 |
|---------|---------|
| UPS 主机 | IP 地址、型号、数量、存放位置、上次维保时间 |
| 电池 | 品牌、存放位置、型号、重量、数量、安装日期(用于计算已使用时长)、上次维保时间 |
| 联系人 | 姓名、联系电话、邮箱 |
| 维保供应商 | 公司名称、联系人、联系电话、邮箱、地址、备注 |
| 维修记录 | 关联设备、维修日期、维修内容、维修人员、关联维保供应商 |
### 2.2 功能需求
| 功能 | 描述 |
|-----|------|
| 电池已使用时长计算 | 根据安装日期自动计算,按年为单位显示(如 2.3 年) |
| 上次维保时间 | UPS 主机和电池均需记录上次维保时间 |
| 列表页搜索 | 支持按型号、位置、联系人等字段进行搜索过滤 |
### 2.3 页面需求
| 页面 | 功能描述 |
|-----|---------|
| 主页 (Dashboard) | 显示重要统计信息概览 |
| UPS 主机管理 | UPS 主机列表(支持搜索)、新增、编辑、删除 |
| 电池管理 | 电池列表(支持搜索、显示已使用时长)、新增、编辑、删除 |
| 联系人管理 | 联系人列表(支持搜索)、新增、编辑、删除 |
| 维保供应商管理 | 维保供应商列表(支持搜索)、新增、编辑、删除 |
| 维修记录管理 | 维修记录列表(支持搜索)、新增、编辑、删除 |
## 三、技术方案
### 3.1 技术栈
- **框架**: Django 5.x
- **数据库**: SQLite本地部署
- **前端**: Bootstrap 5
- **语言**: Python 3.x
### 3.2 项目结构
```
ups_management/
├── manage.py
├── ups_management/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── ups_manager/
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── views.py
├── urls.py
└── templates/
└── ups_manager/
├── index.html
├── ups_list.html
├── ups_form.html
├── battery_list.html
├── battery_form.html
├── contact_list.html
├── contact_form.html
├── supplier_list.html
├── supplier_form.html
├── maintenance_list.html
└── maintenance_form.html
```
### 3.3 数据模型设计
#### 3.3.1 UPS 主机模型 (UPSHost)
| 字段名 | 类型 | 说明 |
|-------|------|------|
| ip_address | CharField | IP 地址 |
| model | CharField | 型号 |
| quantity | IntegerField | 数量 |
| location | CharField | 存放位置 |
| last_maintenance_date | DateField | 上次维保时间 |
| contact | ForeignKey | 关联联系人 |
| created_at | DateTimeField | 创建时间 |
| updated_at | DateTimeField | 更新时间 |
#### 3.3.2 电池模型 (Battery)
| 字段名 | 类型 | 说明 |
|-------|------|------|
| brand | CharField | 品牌 |
| model | CharField | 型号 |
| weight | FloatField | 重量 (kg) |
| quantity | IntegerField | 数量 |
| location | CharField | 存放位置 |
| install_date | DateField | 安装日期(用于计算已使用时长) |
| last_maintenance_date | DateField | 上次维保时间 |
| ups_host | ForeignKey | 关联 UPS 主机(可选) |
| created_at | DateTimeField | 创建时间 |
| updated_at | DateTimeField | 更新时间 |
**计算字段**`used_years` - 根据 `install_date` 自动计算已使用年数
#### 3.3.3 联系人模型 (Contact)
| 字段名 | 类型 | 说明 |
|-------|------|------|
| name | CharField | 姓名 |
| phone | CharField | 联系电话 |
| email | EmailField | 邮箱(可选) |
| created_at | DateTimeField | 创建时间 |
#### 3.3.4 维保供应商模型 (Supplier)
| 字段名 | 类型 | 说明 |
|-------|------|------|
| company_name | CharField | 公司名称 |
| contact_person | CharField | 联系人 |
| phone | CharField | 联系电话 |
| email | EmailField | 邮箱 |
| address | CharField | 地址 |
| remark | TextField | 备注(可选) |
| created_at | DateTimeField | 创建时间 |
#### 3.3.5 维修记录模型 (MaintenanceRecord)
| 字段名 | 类型 | 说明 |
|-------|------|------|
| ups_host | ForeignKey | 关联 UPS 主机 |
| battery | ForeignKey | 关联电池(可选) |
| supplier | ForeignKey | 关联维保供应商(可选) |
| maintenance_date | DateTimeField | 维修日期 |
| content | TextField | 维修内容 |
| technician | CharField | 维修人员 |
| created_at | DateTimeField | 创建时间 |
### 3.4 搜索功能设计
各列表页支持以下搜索字段:
| 页面 | 搜索字段 |
|-----|---------|
| UPS 主机列表 | 型号、位置、联系人 |
| 电池列表 | 型号、品牌、位置 |
| 联系人列表 | 姓名、电话 |
| 维保供应商列表 | 公司名称、联系人 |
| 维修记录列表 | 设备型号、维修人员、维保供应商 |
## 四、实现步骤
### 4.1 步骤一:创建 Django 项目
```bash
django-admin startproject ups_management
cd ups_management
python manage.py startapp ups_manager
```
### 4.2 步骤二:配置项目
1.`settings.py` 中注册 `ups_manager` 应用
2. 配置模板路径
3. 配置静态文件路径
4. 安装 django-bootstrap5
### 4.3 步骤三:创建数据模型
`ups_manager/models.py` 中定义五个模型:
- UPSHost: 包含 last_maintenance_date 字段
- Battery: 包含 install_date 和 last_maintenance_date 字段,添加 used_years 计算属性
- Contact
- Supplier: 维保供应商模型
- MaintenanceRecord: 关联维保供应商字段
### 4.4 步骤四:创建视图
`ups_manager/views.py` 中创建:
- DashboardView: 主页统计概览
- UPSHostListView: 支持搜索过滤
- UPSHostCreateView/UpdateView/DeleteView
- BatteryListView: 支持搜索过滤,显示已使用时长
- BatteryCreateView/UpdateView/DeleteView
- ContactListView: 支持搜索过滤
- ContactCreateView/UpdateView/DeleteView
- SupplierListView: 支持搜索过滤
- SupplierCreateView/UpdateView/DeleteView
- MaintenanceRecordListView: 支持搜索过滤
- MaintenanceRecordCreateView/UpdateView/DeleteView
### 4.5 步骤五:配置 URL 路由
`ups_manager/urls.py` 中配置各页面路由
### 4.6 步骤六:创建模板
使用 Bootstrap 5 创建响应式模板:
- `index.html`: 主页仪表盘
- `ups_list.html`: UPS 主机列表(带搜索框)
- `ups_form.html`: UPS 主机表单
- `battery_list.html`: 电池列表(带搜索框,显示已使用时长)
- `battery_form.html`: 电池表单
- `contact_list.html`: 联系人列表(带搜索框)
- `contact_form.html`: 联系人表单
- `supplier_list.html`: 维保供应商列表(带搜索框)
- `supplier_form.html`: 维保供应商表单
- `maintenance_list.html`: 维修记录列表(带搜索框)
- `maintenance_form.html`: 维修记录表单
### 4.7 步骤七:数据库迁移
```bash
python manage.py makemigrations
python manage.py migrate
```
### 4.8 步骤八:创建管理员用户
```bash
python manage.py createsuperuser
```
## 五、依赖与环境
### 5.1 依赖列表
| 依赖 | 版本 | 用途 |
|-----|------|------|
| Django | >=5.0 | Web 框架 |
| django-bootstrap5 | >=23.3 | Bootstrap 集成 |
### 5.2 安装命令
```bash
pip install django django-bootstrap5
```
## 六、运行方式
```bash
cd ups_management
python manage.py runserver
```
访问地址http://localhost:8000
## 七、风险评估
| 风险 | 描述 | 应对措施 |
|-----|------|---------|
| 数据库迁移失败 | 模型定义错误可能导致迁移失败 | 仔细检查模型定义,先测试迁移 |
| 模板渲染错误 | 模板语法错误导致页面无法显示 | 使用 Django 调试模式定位问题 |
| 数据关联问题 | 外键关联可能导致删除异常 | 设置合理的 on_delete 行为 |
| 日期计算错误 | 已使用时长计算可能出现精度问题 | 使用 datetime 模块精确计算 |
## 八、交付物
1. 完整的 Django 项目代码
2. SQLite 数据库文件(初始为空)
3. 可直接运行的本地服务
---
**计划完成后等待用户批准,然后执行实现。**

BIN
ups_management/db.sqlite3 Normal file

Binary file not shown.

View File

@@ -0,0 +1,41 @@
import os
import django
import re
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ups_management.settings')
django.setup()
from ups_manager.models import UPSHost, Battery
data = [
{'ups_model': '维谛/ITA40KVA', 'ups_quantity': 2, 'battery_brand': '松下蓄电池', 'location': '金融街C座18层', 'battery_model': '12V100AH', 'battery_weight': 25, 'battery_quantity': 32},
{'ups_model': '山特/CASTLE3C 10KVA', 'ups_quantity': 1, 'battery_brand': '松下蓄电池', 'location': '金融街C座18层', 'battery_model': '12V100AH', 'battery_weight': 25, 'battery_quantity': 16},
{'ups_model': '维谛/ITA40KVA', 'ups_quantity': 2, 'battery_brand': 'EXOP蓄电池', 'location': '金融街B座18层', 'battery_model': '12V100AH', 'battery_weight': 25, 'battery_quantity': 32},
{'ups_model': '维谛/ITA40KVA', 'ups_quantity': 3, 'battery_brand': 'EXOP蓄电池', 'location': '金融街B座17层', 'battery_model': '12V100AH', 'battery_weight': 25, 'battery_quantity': 64},
{'ups_model': '山特/CASTLE3C 10KVA', 'ups_quantity': 1, 'battery_brand': '松下蓄电池', 'location': '金融街B座7层', 'battery_model': '12V65AH', 'battery_weight': 25, 'battery_quantity': 32},
{'ups_model': '科华/YTR3340 40KVA', 'ups_quantity': 1, 'battery_brand': '理士蓄电池', 'location': '新盛大厦12/15层', 'battery_model': '12V100AH', 'battery_weight': 25, 'battery_quantity': 32},
]
for item in data:
ups_brand, ups_model = item['ups_model'].split('/', 1)
ups = UPSHost.objects.create(
brand=ups_brand.strip(),
model=ups_model.strip(),
ip_address='',
quantity=item['ups_quantity'],
location=item['location'],
)
Battery.objects.create(
brand=item['battery_brand'],
model=item['battery_model'],
weight=item['battery_weight'],
quantity=item['battery_quantity'],
location=item['location'],
ups_host=ups,
)
print(f"已导入: {ups.brand} {ups.model} - {item['battery_brand']} {item['battery_model']}")
print("\n数据导入完成!")

22
ups_management/manage.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ups_management.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,60 @@
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ups_management.settings')
django.setup()
from ups_manager.models import UPSHost, Battery
print("=== 开始拆分UPS主机记录 ===")
ups_to_split = UPSHost.objects.filter(quantity__gt=1)
print(f"\n找到 {ups_to_split.count()} 条数量大于1的UPS主机记录")
for ups in ups_to_split:
print(f"\n处理: {ups.brand} {ups.model} - {ups.location} - 数量: {ups.quantity}")
original_quantity = ups.quantity
ups.quantity = 1
ups.save()
print(f" 保留第一条记录: IP={ups.ip_address}, 数量=1")
batteries = list(ups.battery_set.all())
battery_per_ups = {}
for battery in batteries:
if battery.quantity > original_quantity:
battery_per_ups[battery] = battery.quantity // original_quantity
battery.quantity = battery.quantity // original_quantity
battery.save()
else:
battery_per_ups[battery] = 1
for i in range(1, original_quantity):
new_ups = UPSHost.objects.create(
brand=ups.brand,
model=ups.model,
ip_address=f"{ups.ip_address}-{i+1}",
quantity=1,
location=ups.location,
last_maintenance_date=ups.last_maintenance_date,
contact=ups.contact
)
for battery, qty in battery_per_ups.items():
Battery.objects.create(
brand=battery.brand,
model=battery.model,
weight=battery.weight,
quantity=qty,
location=battery.location,
install_date=battery.install_date,
last_maintenance_date=battery.last_maintenance_date,
ups_host=new_ups
)
print(f" 创建第 {i+1} 条记录: IP={new_ups.ip_address}, 数量=1")
print(f"\n=== 拆分完成 ===")
print(f"当前UPS主机总数: {UPSHost.objects.count()}")
print(f"当前电池总数: {Battery.objects.count()}")

View File

@@ -0,0 +1,34 @@
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ups_management.settings')
django.setup()
from ups_manager.models import UPSHost, Battery, Contact
print("=== 开始处理任务 ===")
print("\n1. 创建联系人 '夏骥'...")
contact, created = Contact.objects.get_or_create(
name='夏骥',
defaults={
'phone': '',
'email': ''
}
)
print(f" 联系人 {'已创建' if created else '已存在'}: {contact.name}")
print("\n2. 更新所有UPS主机的上次维保时间和联系人...")
ups_count = UPSHost.objects.all().count()
UPSHost.objects.update(
last_maintenance_date='2026-03-06',
contact=contact
)
print(f" 已更新 {ups_count} 台UPS主机")
print("\n3. 更新所有电池的上次维保时间...")
battery_count = Battery.objects.all().count()
Battery.objects.update(last_maintenance_date='2026-03-06')
print(f" 已更新 {battery_count} 块电池")
print("\n=== 任务完成 ===")

View File

@@ -0,0 +1,16 @@
"""
ASGI config for ups_management project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ups_management.settings')
application = get_asgi_application()

View File

@@ -0,0 +1,125 @@
"""
Django settings for ups_management project.
Generated by 'django-admin startproject' using Django 5.0.6.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-_p+9z_03g%&-402erqwtd!=(d1s=^j5l9(!h$5fw2*q7ta_841'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_bootstrap5',
'ups_manager',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'ups_management.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'ups_management.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@@ -0,0 +1,23 @@
"""
URL configuration for ups_management project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('ups_manager.urls')),
]

View File

@@ -0,0 +1,16 @@
"""
WSGI config for ups_management project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ups_management.settings')
application = get_wsgi_application()

View File

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class UpsManagerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'ups_manager'

View File

@@ -0,0 +1,105 @@
# Generated by Django 5.0.6 on 2026-04-28 03:22
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Battery',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('brand', models.CharField(max_length=100, verbose_name='品牌')),
('model', models.CharField(max_length=100, verbose_name='型号')),
('weight', models.FloatField(verbose_name='重量(kg)')),
('quantity', models.IntegerField(default=1, verbose_name='数量')),
('location', models.CharField(max_length=200, verbose_name='存放位置')),
('install_date', models.DateField(blank=True, null=True, verbose_name='安装日期')),
('last_maintenance_date', 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='更新时间')),
],
options={
'verbose_name': '电池',
'verbose_name_plural': '电池',
},
),
migrations.CreateModel(
name='Contact',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='姓名')),
('phone', models.CharField(max_length=20, verbose_name='联系电话')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='邮箱')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
],
options={
'verbose_name': '联系人',
'verbose_name_plural': '联系人',
},
),
migrations.CreateModel(
name='Supplier',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('company_name', models.CharField(max_length=200, verbose_name='公司名称')),
('contact_person', models.CharField(max_length=100, verbose_name='联系人')),
('phone', models.CharField(max_length=20, verbose_name='联系电话')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='邮箱')),
('address', models.CharField(blank=True, max_length=500, null=True, verbose_name='地址')),
('remark', models.TextField(blank=True, null=True, verbose_name='备注')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
],
options={
'verbose_name': '维保供应商',
'verbose_name_plural': '维保供应商',
},
),
migrations.CreateModel(
name='UPSHost',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip_address', models.CharField(max_length=50, verbose_name='IP地址')),
('model', models.CharField(max_length=100, verbose_name='型号')),
('quantity', models.IntegerField(default=1, verbose_name='数量')),
('location', models.CharField(max_length=200, verbose_name='存放位置')),
('last_maintenance_date', 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='更新时间')),
('contact', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ups_manager.contact', verbose_name='联系人')),
],
options={
'verbose_name': 'UPS主机',
'verbose_name_plural': 'UPS主机',
},
),
migrations.CreateModel(
name='MaintenanceRecord',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('maintenance_date', models.DateField(verbose_name='维修日期')),
('content', models.TextField(verbose_name='维修内容')),
('technician', models.CharField(max_length=100, verbose_name='维修人员')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('battery', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ups_manager.battery', verbose_name='电池')),
('supplier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ups_manager.supplier', verbose_name='维保供应商')),
('ups_host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ups_manager.upshost', verbose_name='UPS主机')),
],
options={
'verbose_name': '维修记录',
'verbose_name_plural': '维修记录',
},
),
migrations.AddField(
model_name='battery',
name='ups_host',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ups_manager.upshost', verbose_name='关联UPS主机'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2026-04-28 03:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ups_manager', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='upshost',
name='brand',
field=models.CharField(default='', max_length=100, verbose_name='品牌'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2026-04-28 03:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ups_manager', '0002_upshost_brand'),
]
operations = [
migrations.AlterField(
model_name='upshost',
name='brand',
field=models.CharField(max_length=100, verbose_name='品牌'),
),
]

View File

@@ -0,0 +1,99 @@
from django.db import models
from datetime import date
class Contact(models.Model):
name = models.CharField(max_length=100, verbose_name='姓名')
phone = models.CharField(max_length=20, verbose_name='联系电话')
email = models.EmailField(blank=True, null=True, verbose_name='邮箱')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
def __str__(self):
return self.name
class Meta:
verbose_name = '联系人'
verbose_name_plural = '联系人'
class Supplier(models.Model):
company_name = models.CharField(max_length=200, verbose_name='公司名称')
contact_person = models.CharField(max_length=100, verbose_name='联系人')
phone = models.CharField(max_length=20, verbose_name='联系电话')
email = models.EmailField(blank=True, null=True, verbose_name='邮箱')
address = models.CharField(max_length=500, 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='创建时间')
def __str__(self):
return self.company_name
class Meta:
verbose_name = '维保供应商'
verbose_name_plural = '维保供应商'
class UPSHost(models.Model):
brand = models.CharField(max_length=100, verbose_name='品牌')
model = models.CharField(max_length=100, verbose_name='型号')
ip_address = models.CharField(max_length=50, verbose_name='IP地址')
quantity = models.IntegerField(default=1, verbose_name='数量')
location = models.CharField(max_length=200, verbose_name='存放位置')
last_maintenance_date = models.DateField(blank=True, null=True, verbose_name='上次维保时间')
contact = models.ForeignKey(Contact, on_delete=models.SET_NULL, 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='更新时间')
def __str__(self):
if self.ip_address and not self.ip_address.startswith('-'):
return f'{self.brand} {self.model} - {self.ip_address}'
return f'{self.brand} {self.model}'
class Meta:
verbose_name = 'UPS主机'
verbose_name_plural = 'UPS主机'
class Battery(models.Model):
brand = models.CharField(max_length=100, verbose_name='品牌')
model = models.CharField(max_length=100, verbose_name='型号')
weight = models.FloatField(verbose_name='重量(kg)')
quantity = models.IntegerField(default=1, verbose_name='数量')
location = models.CharField(max_length=200, verbose_name='存放位置')
install_date = models.DateField(blank=True, null=True, verbose_name='安装日期')
last_maintenance_date = models.DateField(blank=True, null=True, verbose_name='上次维保时间')
ups_host = models.ForeignKey(UPSHost, on_delete=models.SET_NULL, blank=True, null=True, verbose_name='关联UPS主机')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
@property
def used_years(self):
if not self.install_date:
return None
today = date.today()
delta = today - self.install_date
return round(delta.days / 365.25, 1)
def __str__(self):
return f'{self.brand} {self.model}'
class Meta:
verbose_name = '电池'
verbose_name_plural = '电池'
class MaintenanceRecord(models.Model):
ups_host = models.ForeignKey(UPSHost, on_delete=models.CASCADE, verbose_name='UPS主机')
battery = models.ForeignKey(Battery, on_delete=models.SET_NULL, blank=True, null=True, verbose_name='电池')
supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, blank=True, null=True, verbose_name='维保供应商')
maintenance_date = models.DateField(verbose_name='维修日期')
content = models.TextField(verbose_name='维修内容')
technician = models.CharField(max_length=100, verbose_name='维修人员')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
def __str__(self):
return f'{self.ups_host} - {self.maintenance_date}'
class Meta:
verbose_name = '维修记录'
verbose_name_plural = '维修记录'

View File

@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UPS管理系统</title>
{% load django_bootstrap5 %}
{% bootstrap_css %}
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'index' %}">UPS管理系统</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'index' %}active{% endif %}" href="{% url 'index' %}">主页</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name|slice:':3' == 'ups' %}active{% endif %}" href="{% url 'ups_list' %}">UPS主机</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name|slice:':7' == 'battery' %}active{% endif %}" href="{% url 'battery_list' %}">电池管理</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name|slice:':7' == 'contact' %}active{% endif %}" href="{% url 'contact_list' %}">联系人</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name|slice:':8' == 'supplier' %}active{% endif %}" href="{% url 'supplier_list' %}">维保供应商</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name|slice:':11' == 'maintenance' %}active{% endif %}" href="{% url 'maintenance_list' %}">维修记录</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% block content %}{% endblock %}
</div>
{% bootstrap_javascript %}
</body>
</html>

View File

@@ -0,0 +1,13 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">确认删除</h2>
<p>您确定要删除电池: <strong>{{ object.brand }} {{ object.model }}</strong> 吗?</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">确认删除</button>
<a href="{% url 'battery_list' %}" class="btn btn-secondary">取消</a>
</form>
{% endblock %}

View File

@@ -0,0 +1,188 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg">
<div class="card-header bg-success text-white">
<h2 class="mb-0">{% if object %}编辑{% else %}添加{% endif %}电池</h2>
</div>
<div class="card-body">
<form method="post" class="needs-validation" novalidate>
{% csrf_token %}
<div class="row g-4">
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.brand.id_for_label }}" class="form-label font-weight-semibold">
{{ form.brand.label }} <span class="text-danger">*</span>
</label>
{{ form.brand }}
{% if form.brand.errors %}
{% for error in form.brand.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.model.id_for_label }}" class="form-label font-weight-semibold">
{{ form.model.label }} <span class="text-danger">*</span>
</label>
{{ form.model }}
{% if form.model.errors %}
{% for error in form.model.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.weight.id_for_label }}" class="form-label font-weight-semibold">
{{ form.weight.label }} <span class="text-danger">*</span>
</label>
{{ form.weight }}
{% if form.weight.errors %}
{% for error in form.weight.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.quantity.id_for_label }}" class="form-label font-weight-semibold">
{{ form.quantity.label }} <span class="text-danger">*</span>
</label>
{{ form.quantity }}
{% if form.quantity.errors %}
{% for error in form.quantity.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.location.id_for_label }}" class="form-label font-weight-semibold">
{{ form.location.label }} <span class="text-danger">*</span>
</label>
{{ form.location }}
{% if form.location.errors %}
{% for error in form.location.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.install_date.id_for_label }}" class="form-label font-weight-semibold">
{{ form.install_date.label }}
</label>
<input type="text"
id="{{ form.install_date.id_for_label }}"
name="{{ form.install_date.name }}"
class="form-control datepicker"
value="{{ form.install_date.value|date:'Y-m-d' }}"
placeholder="选择日期">
{% if form.install_date.errors %}
{% for error in form.install_date.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.last_maintenance_date.id_for_label }}" class="form-label font-weight-semibold">
{{ form.last_maintenance_date.label }}
</label>
<input type="text"
id="{{ form.last_maintenance_date.id_for_label }}"
name="{{ form.last_maintenance_date.name }}"
class="form-control datepicker"
value="{{ form.last_maintenance_date.value|date:'Y-m-d' }}"
placeholder="选择日期">
{% if form.last_maintenance_date.errors %}
{% for error in form.last_maintenance_date.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.ups_host.id_for_label }}" class="form-label font-weight-semibold">
{{ form.ups_host.label }}
</label>
{{ form.ups_host }}
{% if form.ups_host.errors %}
{% for error in form.ups_host.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
<div class="mt-5 d-flex justify-content-end gap-3">
<a href="{% url 'battery_list' %}" class="btn btn-secondary px-6">
<i class="fas fa-arrow-left mr-2"></i>取消
</a>
<button type="submit" class="btn btn-success px-6">
<i class="fas fa-save mr-2"></i>保存
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
flatpickr('.datepicker', {
dateFormat: 'Y-m-d',
locale: 'zh',
allowInput: true,
todayButton: true,
clearButton: true,
minDate: '1900-01-01',
maxDate: new Date()
});
});
</script>
<style>
.form-control:focus {
border-color: #198754;
box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
}
.card {
border-radius: 12px;
}
.card-header {
border-radius: 12px 12px 0 0 !important;
}
.font-weight-semibold {
font-weight: 600;
}
.flatpickr-calendar {
border-radius: 8px;
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
}
</style>
{% endblock %}

View File

@@ -0,0 +1,85 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">电池管理</h2>
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-4">
<input type="text" name="model" class="form-control" placeholder="型号" value="{{ request.GET.model }}">
</div>
<div class="col-md-4">
<input type="text" name="brand" class="form-control" placeholder="品牌" value="{{ request.GET.brand }}">
</div>
<div class="col-md-4">
<input type="text" name="location" class="form-control" placeholder="位置" value="{{ request.GET.location }}">
</div>
<div class="col-md-12">
<button type="submit" class="btn btn-primary">搜索</button>
<a href="{% url 'battery_add' %}" class="btn btn-success float-end">添加电池</a>
</div>
</form>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>品牌</th>
<th>型号</th>
<th>重量(kg)</th>
<th>数量</th>
<th>存放位置</th>
<th>已使用时长</th>
<th>上次维保时间</th>
<th>关联UPS</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for battery in battery_list %}
<tr>
<td>{{ battery.brand }}</td>
<td>{{ battery.model }}</td>
<td>{{ battery.weight }}</td>
<td>{{ battery.quantity }}</td>
<td>{{ battery.location }}</td>
<td>{{ battery.used_years|default:"-" }} 年</td>
<td>{{ battery.last_maintenance_date|default:"-" }}</td>
<td>{{ battery.ups_host|default:"-" }}</td>
<td>
<a href="{% url 'battery_edit' battery.pk %}" class="btn btn-sm btn-info">编辑</a>
<a href="{% url 'battery_delete' battery.pk %}" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center text-muted">暂无电池记录</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">上一页</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
<li class="page-item {% if page_obj.number == num %}active{% endif %}">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">下一页</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">确认删除</h2>
<p>您确定要删除联系人: <strong>{{ object.name }}</strong> 吗?</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">确认删除</button>
<a href="{% url 'contact_list' %}" class="btn btn-secondary">取消</a>
</form>
{% endblock %}

View File

@@ -0,0 +1,88 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-lg">
<div class="card-header bg-info text-white">
<h2 class="mb-0">{% if object %}编辑{% else %}添加{% endif %}联系人</h2>
</div>
<div class="card-body">
<form method="post" class="needs-validation" novalidate>
{% csrf_token %}
<div class="row g-4">
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.name.id_for_label }}" class="form-label font-weight-semibold">
{{ form.name.label }} <span class="text-danger">*</span>
</label>
{{ form.name }}
{% if form.name.errors %}
{% for error in form.name.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.phone.id_for_label }}" class="form-label font-weight-semibold">
{{ form.phone.label }} <span class="text-danger">*</span>
</label>
{{ form.phone }}
{% if form.phone.errors %}
{% for error in form.phone.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.email.id_for_label }}" class="form-label font-weight-semibold">
{{ form.email.label }}
</label>
{{ form.email }}
{% if form.email.errors %}
{% for error in form.email.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
<div class="mt-5 d-flex justify-content-end gap-3">
<a href="{% url 'contact_list' %}" class="btn btn-secondary px-6">
<i class="fas fa-arrow-left mr-2"></i>取消
</a>
<button type="submit" class="btn btn-info px-6">
<i class="fas fa-save mr-2"></i>保存
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<style>
.form-control:focus {
border-color: #0dcaf0;
box-shadow: 0 0 0 0.25rem rgba(13, 202, 240, 0.25);
}
.card {
border-radius: 12px;
}
.card-header {
border-radius: 12px 12px 0 0 !important;
}
.font-weight-semibold {
font-weight: 600;
}
</style>
{% endblock %}

View File

@@ -0,0 +1,74 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">联系人管理</h2>
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-6">
<input type="text" name="name" class="form-control" placeholder="姓名" value="{{ request.GET.name }}">
</div>
<div class="col-md-6">
<input type="text" name="phone" class="form-control" placeholder="联系电话" value="{{ request.GET.phone }}">
</div>
<div class="col-md-12">
<button type="submit" class="btn btn-primary">搜索</button>
<a href="{% url 'contact_add' %}" class="btn btn-success float-end">添加联系人</a>
</div>
</form>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>姓名</th>
<th>联系电话</th>
<th>邮箱</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for contact in contact_list %}
<tr>
<td>{{ contact.name }}</td>
<td>{{ contact.phone }}</td>
<td>{{ contact.email|default:"-" }}</td>
<td>{{ contact.created_at }}</td>
<td>
<a href="{% url 'contact_edit' contact.pk %}" class="btn btn-sm btn-info">编辑</a>
<a href="{% url 'contact_delete' contact.pk %}" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center text-muted">暂无联系人记录</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">上一页</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
<li class="page-item {% if page_obj.number == num %}active{% endif %}">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">下一页</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,180 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>欢迎使用 UPS 管理系统</h2>
<a href="/admin/" class="btn btn-dark">后台管理</a>
</div>
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<h5 class="card-title">UPS 主机数量</h5>
<h2 class="card-text">{{ ups_count }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<h5 class="card-title">电池数量</h5>
<h2 class="card-text">{{ battery_count }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<h5 class="card-title">联系人数量</h5>
<h2 class="card-text">{{ contact_count }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body">
<h5 class="card-title">维修记录</h5>
<h2 class="card-text">{{ maintenance_count }}</h2>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h5>UPS主机与电池汇总</h5>
</div>
<div class="card-body">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>UPS主机型号</th>
<th>数量(台)</th>
<th>电池品牌</th>
<th>电池存放地址</th>
<th>在用电池型号</th>
<th>电池单块重量</th>
<th>数量(块)</th>
</tr>
</thead>
<tbody>
{% for item in ups_with_batteries %}
<tr>
<td>{{ item.ups_brand }} {{ item.ups_model }}</td>
<td>{{ item.ups_quantity }}</td>
<td>{{ item.battery_brand }}</td>
<td>{{ item.battery_location }}</td>
<td>{{ item.battery_model }}</td>
<td>{{ item.battery_weight }} kg</td>
<td>{{ item.battery_quantity }}</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center text-muted">暂无数据</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-dark text-white">
<h5>按存放位置汇总</h5>
</div>
<div class="card-body">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>存放位置</th>
<th>UPS数量</th>
<th>电池数量(块)</th>
<th>总重量kg</th>
<th>UPS型号</th>
</tr>
</thead>
<tbody>
{% for location, data in location_summary %}
<tr>
<td>{{ location }}</td>
<td>{{ data.ups_count }}</td>
<td>{{ data.battery_count }}</td>
<td>{{ data.total_weight }}</td>
<td>{{ data.ups_models|join:', ' }}</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center text-muted">暂无数据</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>最近维修记录</h5>
</div>
<div class="card-body">
{% if recent_maintenances %}
<table class="table table-sm">
<thead>
<tr>
<th>UPS主机</th>
<th>维修日期</th>
<th>维修人员</th>
</tr>
</thead>
<tbody>
{% for record in recent_maintenances %}
<tr>
<td>{{ record.ups_host.model }}</td>
<td>{{ record.maintenance_date }}</td>
<td>{{ record.technician }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-center text-muted">暂无维修记录</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>最近添加的电池</h5>
</div>
<div class="card-body">
{% if recent_batteries %}
<table class="table table-sm">
<thead>
<tr>
<th>品牌/型号</th>
<th>位置</th>
<th>已使用</th>
</tr>
</thead>
<tbody>
{% for battery in recent_batteries %}
<tr>
<td>{{ battery.brand }} {{ battery.model }}</td>
<td>{{ battery.location }}</td>
<td>{{ battery.used_years|default:"-" }} 年</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-center text-muted">暂无电池记录</p>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">确认删除</h2>
<p>您确定要删除维修记录: <strong>{{ object.ups_host }} - {{ object.maintenance_date }}</strong> 吗?</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">确认删除</button>
<a href="{% url 'maintenance_list' %}" class="btn btn-secondary">取消</a>
</form>
{% endblock %}

View File

@@ -0,0 +1,155 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg">
<div class="card-header bg-danger text-white">
<h2 class="mb-0">{% if object %}编辑{% else %}添加{% endif %}维修记录</h2>
</div>
<div class="card-body">
<form method="post" class="needs-validation" novalidate>
{% csrf_token %}
<div class="row g-4">
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.ups_host.id_for_label }}" class="form-label font-weight-semibold">
{{ form.ups_host.label }} <span class="text-danger">*</span>
</label>
{{ form.ups_host }}
{% if form.ups_host.errors %}
{% for error in form.ups_host.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.battery.id_for_label }}" class="form-label font-weight-semibold">
{{ form.battery.label }}
</label>
{{ form.battery }}
{% if form.battery.errors %}
{% for error in form.battery.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.supplier.id_for_label }}" class="form-label font-weight-semibold">
{{ form.supplier.label }}
</label>
{{ form.supplier }}
{% if form.supplier.errors %}
{% for error in form.supplier.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.maintenance_date.id_for_label }}" class="form-label font-weight-semibold">
{{ form.maintenance_date.label }} <span class="text-danger">*</span>
</label>
<input type="text"
id="{{ form.maintenance_date.id_for_label }}"
name="{{ form.maintenance_date.name }}"
class="form-control datepicker"
value="{{ form.maintenance_date.value|date:'Y-m-d' }}"
placeholder="选择日期">
{% if form.maintenance_date.errors %}
{% for error in form.maintenance_date.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.technician.id_for_label }}" class="form-label font-weight-semibold">
{{ form.technician.label }} <span class="text-danger">*</span>
</label>
{{ form.technician }}
{% if form.technician.errors %}
{% for error in form.technician.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.content.id_for_label }}" class="form-label font-weight-semibold">
{{ form.content.label }} <span class="text-danger">*</span>
</label>
{{ form.content }}
{% if form.content.errors %}
{% for error in form.content.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
<div class="mt-5 d-flex justify-content-end gap-3">
<a href="{% url 'maintenance_list' %}" class="btn btn-secondary px-6">
<i class="fas fa-arrow-left mr-2"></i>取消
</a>
<button type="submit" class="btn btn-danger px-6">
<i class="fas fa-save mr-2"></i>保存
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
flatpickr('.datepicker', {
dateFormat: 'Y-m-d',
locale: 'zh',
allowInput: true,
todayButton: true,
clearButton: true,
minDate: '1900-01-01',
maxDate: new Date()
});
});
</script>
<style>
.form-control:focus {
border-color: #dc3545;
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}
.card {
border-radius: 12px;
}
.card-header {
border-radius: 12px 12px 0 0 !important;
}
.font-weight-semibold {
font-weight: 600;
}
.flatpickr-calendar {
border-radius: 8px;
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
}
</style>
{% endblock %}

View File

@@ -0,0 +1,81 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">维修记录管理</h2>
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-4">
<input type="text" name="ups_model" class="form-control" placeholder="UPS型号" value="{{ request.GET.ups_model }}">
</div>
<div class="col-md-4">
<input type="text" name="technician" class="form-control" placeholder="维修人员" value="{{ request.GET.technician }}">
</div>
<div class="col-md-4">
<input type="text" name="supplier" class="form-control" placeholder="维保供应商" value="{{ request.GET.supplier }}">
</div>
<div class="col-md-12">
<button type="submit" class="btn btn-primary">搜索</button>
<a href="{% url 'maintenance_add' %}" class="btn btn-success float-end">添加维修记录</a>
</div>
</form>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>UPS主机</th>
<th>电池</th>
<th>维保供应商</th>
<th>维修日期</th>
<th>维修内容</th>
<th>维修人员</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for record in maintenance_list %}
<tr>
<td>{{ record.ups_host }}</td>
<td>{{ record.battery|default:"-" }}</td>
<td>{{ record.supplier|default:"-" }}</td>
<td>{{ record.maintenance_date }}</td>
<td>{{ record.content|truncatechars:50 }}</td>
<td>{{ record.technician }}</td>
<td>
<a href="{% url 'maintenance_edit' record.pk %}" class="btn btn-sm btn-info">编辑</a>
<a href="{% url 'maintenance_delete' record.pk %}" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center text-muted">暂无维修记录</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">上一页</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
<li class="page-item {% if page_obj.number == num %}active{% endif %}">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">下一页</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">确认删除</h2>
<p>您确定要删除维保供应商: <strong>{{ object.company_name }}</strong> 吗?</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">确认删除</button>
<a href="{% url 'supplier_list' %}" class="btn btn-secondary">取消</a>
</form>
{% endblock %}

View File

@@ -0,0 +1,130 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg">
<div class="card-header bg-warning text-white">
<h2 class="mb-0">{% if object %}编辑{% else %}添加{% endif %}维保供应商</h2>
</div>
<div class="card-body">
<form method="post" class="needs-validation" novalidate>
{% csrf_token %}
<div class="row g-4">
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.company_name.id_for_label }}" class="form-label font-weight-semibold">
{{ form.company_name.label }} <span class="text-danger">*</span>
</label>
{{ form.company_name }}
{% if form.company_name.errors %}
{% for error in form.company_name.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.contact_person.id_for_label }}" class="form-label font-weight-semibold">
{{ form.contact_person.label }} <span class="text-danger">*</span>
</label>
{{ form.contact_person }}
{% if form.contact_person.errors %}
{% for error in form.contact_person.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.phone.id_for_label }}" class="form-label font-weight-semibold">
{{ form.phone.label }} <span class="text-danger">*</span>
</label>
{{ form.phone }}
{% if form.phone.errors %}
{% for error in form.phone.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.email.id_for_label }}" class="form-label font-weight-semibold">
{{ form.email.label }}
</label>
{{ form.email }}
{% if form.email.errors %}
{% for error in form.email.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.address.id_for_label }}" class="form-label font-weight-semibold">
{{ form.address.label }}
</label>
{{ form.address }}
{% if form.address.errors %}
{% for error in form.address.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.remark.id_for_label }}" class="form-label font-weight-semibold">
{{ form.remark.label }}
</label>
{{ form.remark }}
{% if form.remark.errors %}
{% for error in form.remark.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
<div class="mt-5 d-flex justify-content-end gap-3">
<a href="{% url 'supplier_list' %}" class="btn btn-secondary px-6">
<i class="fas fa-arrow-left mr-2"></i>取消
</a>
<button type="submit" class="btn btn-warning px-6 text-white">
<i class="fas fa-save mr-2"></i>保存
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<style>
.form-control:focus {
border-color: #ffc107;
box-shadow: 0 0 0 0.25rem rgba(255, 193, 7, 0.25);
}
.card {
border-radius: 12px;
}
.card-header {
border-radius: 12px 12px 0 0 !important;
}
.font-weight-semibold {
font-weight: 600;
}
</style>
{% endblock %}

View File

@@ -0,0 +1,78 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">维保供应商管理</h2>
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-6">
<input type="text" name="company_name" class="form-control" placeholder="公司名称" value="{{ request.GET.company_name }}">
</div>
<div class="col-md-6">
<input type="text" name="contact_person" class="form-control" placeholder="联系人" value="{{ request.GET.contact_person }}">
</div>
<div class="col-md-12">
<button type="submit" class="btn btn-primary">搜索</button>
<a href="{% url 'supplier_add' %}" class="btn btn-success float-end">添加维保供应商</a>
</div>
</form>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>公司名称</th>
<th>联系人</th>
<th>联系电话</th>
<th>邮箱</th>
<th>地址</th>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for supplier in supplier_list %}
<tr>
<td>{{ supplier.company_name }}</td>
<td>{{ supplier.contact_person }}</td>
<td>{{ supplier.phone }}</td>
<td>{{ supplier.email|default:"-" }}</td>
<td>{{ supplier.address|default:"-" }}</td>
<td>{{ supplier.remark|default:"-"|truncatechars:30 }}</td>
<td>
<a href="{% url 'supplier_edit' supplier.pk %}" class="btn btn-sm btn-info">编辑</a>
<a href="{% url 'supplier_delete' supplier.pk %}" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center text-muted">暂无维保供应商记录</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">上一页</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
<li class="page-item {% if page_obj.number == num %}active{% endif %}">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">下一页</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">确认删除</h2>
<p>您确定要删除 UPS主机: <strong>{{ object.model }} - {{ object.ip_address }}</strong> 吗?</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">确认删除</button>
<a href="{% url 'ups_list' %}" class="btn btn-secondary">取消</a>
</form>
{% endblock %}

View File

@@ -0,0 +1,169 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg">
<div class="card-header bg-primary text-white">
<h2 class="mb-0">{% if object %}编辑{% else %}添加{% endif %}UPS主机</h2>
</div>
<div class="card-body">
<form method="post" class="needs-validation" novalidate>
{% csrf_token %}
<div class="row g-4">
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.brand.id_for_label }}" class="form-label font-weight-semibold">
{{ form.brand.label }} <span class="text-danger">*</span>
</label>
{{ form.brand }}
{% if form.brand.errors %}
{% for error in form.brand.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.model.id_for_label }}" class="form-label font-weight-semibold">
{{ form.model.label }} <span class="text-danger">*</span>
</label>
{{ form.model }}
{% if form.model.errors %}
{% for error in form.model.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.ip_address.id_for_label }}" class="form-label font-weight-semibold">
{{ form.ip_address.label }}
</label>
{{ form.ip_address }}
{% if form.ip_address.errors %}
{% for error in form.ip_address.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.quantity.id_for_label }}" class="form-label font-weight-semibold">
{{ form.quantity.label }} <span class="text-danger">*</span>
</label>
{{ form.quantity }}
{% if form.quantity.errors %}
{% for error in form.quantity.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="{{ form.location.id_for_label }}" class="form-label font-weight-semibold">
{{ form.location.label }} <span class="text-danger">*</span>
</label>
{{ form.location }}
{% if form.location.errors %}
{% for error in form.location.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.last_maintenance_date.id_for_label }}" class="form-label font-weight-semibold">
{{ form.last_maintenance_date.label }}
</label>
<input type="text"
id="{{ form.last_maintenance_date.id_for_label }}"
name="{{ form.last_maintenance_date.name }}"
class="form-control datepicker"
value="{{ form.last_maintenance_date.value|date:'Y-m-d' }}"
placeholder="选择日期">
{% if form.last_maintenance_date.errors %}
{% for error in form.last_maintenance_date.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="{{ form.contact.id_for_label }}" class="form-label font-weight-semibold">
{{ form.contact.label }}
</label>
{{ form.contact }}
{% if form.contact.errors %}
{% for error in form.contact.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
<div class="mt-5 d-flex justify-content-end gap-3">
<a href="{% url 'ups_list' %}" class="btn btn-secondary px-6">
<i class="fas fa-arrow-left mr-2"></i>取消
</a>
<button type="submit" class="btn btn-primary px-6">
<i class="fas fa-save mr-2"></i>保存
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
flatpickr('.datepicker', {
dateFormat: 'Y-m-d',
locale: 'zh',
allowInput: true,
todayButton: true,
clearButton: true,
minDate: '1900-01-01',
maxDate: new Date()
});
});
</script>
<style>
.form-control:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.card {
border-radius: 12px;
}
.card-header {
border-radius: 12px 12px 0 0 !important;
}
.font-weight-semibold {
font-weight: 600;
}
.flatpickr-calendar {
border-radius: 8px;
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
}
</style>
{% endblock %}

View File

@@ -0,0 +1,88 @@
{% extends 'ups_manager/base.html' %}
{% block content %}
<h2 class="mb-4">UPS主机管理</h2>
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-3">
<input type="text" name="brand" class="form-control" placeholder="品牌" value="{{ request.GET.brand }}">
</div>
<div class="col-md-3">
<input type="text" name="model" class="form-control" placeholder="型号" value="{{ request.GET.model }}">
</div>
<div class="col-md-3">
<input type="text" name="location" class="form-control" placeholder="位置" value="{{ request.GET.location }}">
</div>
<div class="col-md-3">
<input type="text" name="contact" class="form-control" placeholder="联系人" value="{{ request.GET.contact }}">
</div>
<div class="col-md-12">
<button type="submit" class="btn btn-primary">搜索</button>
<a href="{% url 'ups_add' %}" class="btn btn-success float-end">添加UPS主机</a>
</div>
</form>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>主机名称</th>
<th>品牌</th>
<th>型号</th>
<th>IP地址</th>
<th>数量</th>
<th>存放位置</th>
<th>上次维保时间</th>
<th>联系人</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for ups in ups_list %}
<tr>
<td>{{ ups.brand }} {{ ups.model }}</td>
<td>{{ ups.brand }}</td>
<td>{{ ups.model }}</td>
<td>{{ ups.ip_address }}</td>
<td>{{ ups.quantity }}</td>
<td>{{ ups.location }}</td>
<td>{{ ups.last_maintenance_date|default:"-" }}</td>
<td>{{ ups.contact|default:"-" }}</td>
<td>
<a href="{% url 'ups_edit' ups.pk %}" class="btn btn-sm btn-info">编辑</a>
<a href="{% url 'ups_delete' ups.pk %}" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center text-muted">暂无UPS主机记录</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">上一页</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
<li class="page-item {% if page_obj.number == num %}active{% endif %}">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">下一页</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,31 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.DashboardView.as_view(), name='index'),
path('ups/', views.UPSHostListView.as_view(), name='ups_list'),
path('ups/add/', views.UPSHostCreateView.as_view(), name='ups_add'),
path('ups/<int:pk>/edit/', views.UPSHostUpdateView.as_view(), name='ups_edit'),
path('ups/<int:pk>/delete/', views.UPSHostDeleteView.as_view(), name='ups_delete'),
path('battery/', views.BatteryListView.as_view(), name='battery_list'),
path('battery/add/', views.BatteryCreateView.as_view(), name='battery_add'),
path('battery/<int:pk>/edit/', views.BatteryUpdateView.as_view(), name='battery_edit'),
path('battery/<int:pk>/delete/', views.BatteryDeleteView.as_view(), name='battery_delete'),
path('contact/', views.ContactListView.as_view(), name='contact_list'),
path('contact/add/', views.ContactCreateView.as_view(), name='contact_add'),
path('contact/<int:pk>/edit/', views.ContactUpdateView.as_view(), name='contact_edit'),
path('contact/<int:pk>/delete/', views.ContactDeleteView.as_view(), name='contact_delete'),
path('supplier/', views.SupplierListView.as_view(), name='supplier_list'),
path('supplier/add/', views.SupplierCreateView.as_view(), name='supplier_add'),
path('supplier/<int:pk>/edit/', views.SupplierUpdateView.as_view(), name='supplier_edit'),
path('supplier/<int:pk>/delete/', views.SupplierDeleteView.as_view(), name='supplier_delete'),
path('maintenance/', views.MaintenanceRecordListView.as_view(), name='maintenance_list'),
path('maintenance/add/', views.MaintenanceRecordCreateView.as_view(), name='maintenance_add'),
path('maintenance/<int:pk>/edit/', views.MaintenanceRecordUpdateView.as_view(), name='maintenance_edit'),
path('maintenance/<int:pk>/delete/', views.MaintenanceRecordDeleteView.as_view(), name='maintenance_delete'),
]

View File

@@ -0,0 +1,276 @@
from django.shortcuts import render, redirect
from django.views.generic import ListView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import UPSHost, Battery, Contact, Supplier, MaintenanceRecord
class DashboardView(ListView):
template_name = 'ups_manager/index.html'
context_object_name = 'dashboard_data'
def get_queryset(self):
return None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['ups_count'] = UPSHost.objects.count()
context['battery_count'] = Battery.objects.count()
context['contact_count'] = Contact.objects.count()
context['supplier_count'] = Supplier.objects.count()
context['maintenance_count'] = MaintenanceRecord.objects.count()
context['recent_maintenances'] = MaintenanceRecord.objects.order_by('-maintenance_date')[:5]
context['recent_batteries'] = Battery.objects.order_by('-created_at')[:5]
ups_with_batteries = []
for ups in UPSHost.objects.prefetch_related('battery_set').all():
batteries = ups.battery_set.all()
if batteries:
for battery in batteries:
ups_with_batteries.append({
'ups_brand': ups.brand,
'ups_model': ups.model,
'ups_quantity': ups.quantity,
'battery_brand': battery.brand,
'battery_location': battery.location,
'battery_model': battery.model,
'battery_weight': battery.weight,
'battery_quantity': battery.quantity,
})
else:
ups_with_batteries.append({
'ups_brand': ups.brand,
'ups_model': ups.model,
'ups_quantity': ups.quantity,
'battery_brand': '-',
'battery_location': ups.location,
'battery_model': '-',
'battery_weight': '-',
'battery_quantity': '-',
})
context['ups_with_batteries'] = ups_with_batteries
locations = {}
for ups in UPSHost.objects.all():
if ups.location not in locations:
locations[ups.location] = {
'ups_count': 0,
'battery_count': 0,
'total_weight': 0,
'ups_models': []
}
locations[ups.location]['ups_count'] += ups.quantity
locations[ups.location]['ups_models'].append(f"{ups.brand} {ups.model}")
for battery in ups.battery_set.all():
locations[ups.location]['battery_count'] += battery.quantity
locations[ups.location]['total_weight'] += battery.weight * battery.quantity
context['location_summary'] = sorted(locations.items(), key=lambda x: x[0])
return context
class UPSHostListView(ListView):
model = UPSHost
template_name = 'ups_manager/ups_list.html'
context_object_name = 'ups_list'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
brand = self.request.GET.get('brand')
model = self.request.GET.get('model')
location = self.request.GET.get('location')
contact = self.request.GET.get('contact')
if brand:
queryset = queryset.filter(brand__icontains=brand)
if model:
queryset = queryset.filter(model__icontains=model)
if location:
queryset = queryset.filter(location__icontains=location)
if contact:
queryset = queryset.filter(contact__name__icontains=contact)
return queryset.order_by('-created_at')
class UPSHostCreateView(CreateView):
model = UPSHost
template_name = 'ups_manager/ups_form.html'
fields = ['brand', 'model', 'ip_address', 'quantity', 'location', 'last_maintenance_date', 'contact']
success_url = reverse_lazy('ups_list')
class UPSHostUpdateView(UpdateView):
model = UPSHost
template_name = 'ups_manager/ups_form.html'
fields = ['brand', 'model', 'ip_address', 'quantity', 'location', 'last_maintenance_date', 'contact']
success_url = reverse_lazy('ups_list')
class UPSHostDeleteView(DeleteView):
model = UPSHost
template_name = 'ups_manager/ups_confirm_delete.html'
success_url = reverse_lazy('ups_list')
class BatteryListView(ListView):
model = Battery
template_name = 'ups_manager/battery_list.html'
context_object_name = 'battery_list'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
model = self.request.GET.get('model')
brand = self.request.GET.get('brand')
location = self.request.GET.get('location')
if model:
queryset = queryset.filter(model__icontains=model)
if brand:
queryset = queryset.filter(brand__icontains=brand)
if location:
queryset = queryset.filter(location__icontains=location)
return queryset.order_by('-created_at')
class BatteryCreateView(CreateView):
model = Battery
template_name = 'ups_manager/battery_form.html'
fields = ['brand', 'model', 'weight', 'quantity', 'location', 'install_date', 'last_maintenance_date', 'ups_host']
success_url = reverse_lazy('battery_list')
class BatteryUpdateView(UpdateView):
model = Battery
template_name = 'ups_manager/battery_form.html'
fields = ['brand', 'model', 'weight', 'quantity', 'location', 'install_date', 'last_maintenance_date', 'ups_host']
success_url = reverse_lazy('battery_list')
class BatteryDeleteView(DeleteView):
model = Battery
template_name = 'ups_manager/battery_confirm_delete.html'
success_url = reverse_lazy('battery_list')
class ContactListView(ListView):
model = Contact
template_name = 'ups_manager/contact_list.html'
context_object_name = 'contact_list'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
name = self.request.GET.get('name')
phone = self.request.GET.get('phone')
if name:
queryset = queryset.filter(name__icontains=name)
if phone:
queryset = queryset.filter(phone__icontains=phone)
return queryset.order_by('-created_at')
class ContactCreateView(CreateView):
model = Contact
template_name = 'ups_manager/contact_form.html'
fields = ['name', 'phone', 'email']
success_url = reverse_lazy('contact_list')
class ContactUpdateView(UpdateView):
model = Contact
template_name = 'ups_manager/contact_form.html'
fields = ['name', 'phone', 'email']
success_url = reverse_lazy('contact_list')
class ContactDeleteView(DeleteView):
model = Contact
template_name = 'ups_manager/contact_confirm_delete.html'
success_url = reverse_lazy('contact_list')
class SupplierListView(ListView):
model = Supplier
template_name = 'ups_manager/supplier_list.html'
context_object_name = 'supplier_list'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
company_name = self.request.GET.get('company_name')
contact_person = self.request.GET.get('contact_person')
if company_name:
queryset = queryset.filter(company_name__icontains=company_name)
if contact_person:
queryset = queryset.filter(contact_person__icontains=contact_person)
return queryset.order_by('-created_at')
class SupplierCreateView(CreateView):
model = Supplier
template_name = 'ups_manager/supplier_form.html'
fields = ['company_name', 'contact_person', 'phone', 'email', 'address', 'remark']
success_url = reverse_lazy('supplier_list')
class SupplierUpdateView(UpdateView):
model = Supplier
template_name = 'ups_manager/supplier_form.html'
fields = ['company_name', 'contact_person', 'phone', 'email', 'address', 'remark']
success_url = reverse_lazy('supplier_list')
class SupplierDeleteView(DeleteView):
model = Supplier
template_name = 'ups_manager/supplier_confirm_delete.html'
success_url = reverse_lazy('supplier_list')
class MaintenanceRecordListView(ListView):
model = MaintenanceRecord
template_name = 'ups_manager/maintenance_list.html'
context_object_name = 'maintenance_list'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
ups_model = self.request.GET.get('ups_model')
technician = self.request.GET.get('technician')
supplier = self.request.GET.get('supplier')
if ups_model:
queryset = queryset.filter(ups_host__model__icontains=ups_model)
if technician:
queryset = queryset.filter(technician__icontains=technician)
if supplier:
queryset = queryset.filter(supplier__company_name__icontains=supplier)
return queryset.order_by('-maintenance_date')
class MaintenanceRecordCreateView(CreateView):
model = MaintenanceRecord
template_name = 'ups_manager/maintenance_form.html'
fields = ['ups_host', 'battery', 'supplier', 'maintenance_date', 'content', 'technician']
success_url = reverse_lazy('maintenance_list')
class MaintenanceRecordUpdateView(UpdateView):
model = MaintenanceRecord
template_name = 'ups_manager/maintenance_form.html'
fields = ['ups_host', 'battery', 'supplier', 'maintenance_date', 'content', 'technician']
success_url = reverse_lazy('maintenance_list')
class MaintenanceRecordDeleteView(DeleteView):
model = MaintenanceRecord
template_name = 'ups_manager/maintenance_confirm_delete.html'
success_url = reverse_lazy('maintenance_list')