Files
dtm-py-all/UI/models/dut_model.py

480 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DUT模型,处理试验样品(DUT: Device Under Test)信息的新建与编辑
支持通过全局配置切换直接访问 SQLite 或通过 HTTP API 访问。
"""
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QVariant, pyqtSlot
import sqlite3
import json
import os
import requests
# 尝试导入配置
try:
from config import USE_HTTP_API, HTTP_API_BASE_URL
except ImportError:
USE_HTTP_API = False
HTTP_API_BASE_URL = "http://127.0.0.1:5050"
class DUTModel(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._duts = []
self._current_dut = None
# HTTP API 配置
self.use_http_api = USE_HTTP_API
self.api_base_url = HTTP_API_BASE_URL
# 数据库路径
self.db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dtmgtDb.db')
self._load_duts()
# 信号定义
dutsChanged = pyqtSignal()
currentDUTChanged = pyqtSignal()
def _load_duts(self):
"""从数据库加载DUT数据"""
try:
# 尝试使用 HTTP API
if self.use_http_api:
try:
url = f"{self.api_base_url}/dbTableAccess"
# 尝试添加参数获取所有记录(避免后端分页限制)
response = requests.get(
url,
params={
"table": "dutList",
"limit": 1000, # 设置较大的限制值
"offset": 0
},
timeout=5
)
response.raise_for_status()
result = response.json()
data = result.get("data", [])
print(f"\n========== DUT 数据加载调试 ==========")
print(f"从 HTTP API 获取到 {len(data)} 条原始记录")
# 检查是否有分页信息
if 'total' in result:
print(f"后端数据总数: {result.get('total')}")
if 'limit' in result:
print(f"后端分页限制: limit={result.get('limit')}, offset={result.get('offset', 0)}")
if data is not None:
self._duts = []
skipped_count = 0
for idx, row in enumerate(data):
try:
# 保留原始的 stationAssigned JSON 字符串
# 安全处理:即使为 None 或空字符串也保留记录
station_assigned_str = row.get("stationAssigned") or ""
# 如果是 None转为空字符串
if station_assigned_str is None:
station_assigned_str = ""
dtm = ""
station = ""
# 解析 JSON 获取 dtMachine 和 station 字段供其他模块使用
# 只有当 stationAssigned 为非空且以 '{' 开头时才尝试解析
if station_assigned_str and isinstance(station_assigned_str, str) and station_assigned_str.strip().startswith('{'):
try:
obj = json.loads(station_assigned_str)
dtm = (obj.get('dtMachine') or '').strip()
station = (obj.get('station') or '').strip()
except Exception as e:
print(f" [警告] 记录 {idx+1} 解析 stationAssigned JSON 失败: {e}, 值: {station_assigned_str}")
dut = {
"SN": row.get("SN", ""),
"name": row.get("name", ""),
"project": row.get("project", ""),
"projectPhase": row.get("projectPhase", ""),
"projectType": row.get("projectType", ""),
"phase": row.get("phase", ""),
"weeks": row.get("weeks", 0) if row.get("weeks") is not None else 0,
"stationAssigned": station_assigned_str, # 保留原始 JSON 字符串(即使为空)
"dtMachine": dtm, # 解析出的机台编码
"station": station, # 解析出的通道编号
"itemOnGoing": row.get("itemOnGoing", ""),
"itemsFinished": row.get("itemsFinished", 0) if row.get("itemsFinished") is not None else 0,
"status": row.get("status", ""),
"testReq": row.get("testReq", ""),
"inspector": row.get("inspector", ""),
"id": row.get("id", 0) if row.get("id") is not None else 0,
"description": row.get("description", ""),
"deadLine": row.get("deadLine", ""),
"createdate": row.get("createdate", ""),
"workOrder": row.get("workOrder", ""),
"direction_codes": row.get("direction_codes", "")
}
self._duts.append(dut)
except Exception as e:
skipped_count += 1
print(f" [错误] 记录 {idx+1} 处理失败,已跳过: {e}")
print(f" 原始数据: {row}")
import traceback
traceback.print_exc()
print(f"\n总计: 获取 {len(data)} 条 -> 成功加载 {len(self._duts)} 条 -> 跳过 {skipped_count}")
print(f"========================================\n")
self.dutsChanged.emit()
return
except Exception as e:
print(f"HTTP API 加载DUT失败: {e},回退到直接访问 SQLite")
import traceback
traceback.print_exc()
# 直接访问 SQLite
if os.path.exists(self.db_path):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 查询 dutList 表中的所有 DUT
cursor.execute("""
SELECT SN, name, project,projectPhase, projectType, phase, weeks, stationAssigned,
itemOnGoing, itemsFinished, status, testReq, inspector, id,
description, deadLine, createdate, workOrder, direction_codes
FROM dutList
""")
rows = cursor.fetchall()
self._duts = []
for row in rows:
# 保留原始的 stationAssigned JSON 字符串
# 安全处理:即使为 None 或空字符串也保留记录
station_assigned_str = row[6] if row[6] else ""
# 如果是 None转为空字符串
if station_assigned_str is None:
station_assigned_str = ""
dtm = ""
station = ""
# 解析 JSON 获取 dtMachine 和 station 字段供其他模块使用
# 只有当 stationAssigned 为非空且以 '{' 开头时才尝试解析
if station_assigned_str and isinstance(station_assigned_str, str) and station_assigned_str.strip().startswith('{'):
try:
obj = json.loads(station_assigned_str)
dtm = (obj.get('dtMachine') or '').strip()
station = (obj.get('station') or '').strip()
except Exception as e:
print(f"解析 stationAssigned JSON 失败: {e}, 值: {station_assigned_str}")
dut = {
"SN": row[0] if row[0] else "",
"name": row[1] if row[1] else "",
"project": row[2] if row[2] else "",
"projectPhase": row[3] if row[3] else "",
"projectType": row[3] if row[3] else "",
"phase": row[4] if row[4] else "",
"weeks": row[5] if row[5] is not None else 0,
"stationAssigned": station_assigned_str, # 保留原始 JSON 字符串(即使为空)
"dtMachine": dtm, # 解析出的机台编码
"station": station, # 解析出的通道编号
"itemOnGoing": row[7] if row[7] else "",
"itemsFinished": row[8] if row[8] is not None else 0,
"status": row[9] if row[9] else "",
"testReq": row[10] if row[10] else "",
"inspector": row[11] if row[11] else "",
"id": row[12] if row[12] is not None else 0,
"description": row[13] if row[13] else "",
"deadLine": row[14] if row[14] else "",
"createdate": row[15] if row[15] else "",
"workOrder": row[16] if row[16] else "",
"direction_codes": row[17] if row[17] else ""
}
self._duts.append(dut)
print(f"加载了 {len(self._duts)} 条 DUT 记录SQLite")
conn.close()
else:
# 如果数据库不存在,使用空列表
self._duts = []
print("数据库文件不存在")
self.dutsChanged.emit()
except Exception as e:
print(f"加载DUT数据时出错: {e}")
self._duts = []
@pyqtProperty('QVariant', notify=dutsChanged)
def duts(self):
"""返回所有DUT数据"""
return self._duts
@pyqtProperty('QVariant', notify=currentDUTChanged)
def currentDUT(self):
"""返回当前选中的DUT"""
return self._current_dut
@currentDUT.setter
def setCurrentDUT(self, dut):
"""设置当前选中的DUT"""
if self._current_dut != dut:
self._current_dut = dut
self.currentDUTChanged.emit()
@pyqtSlot(int, result='QVariant')
def getDUTById(self, dut_id):
"""根据ID获取DUT信息"""
for dut in self._duts:
if dut.get("id") == dut_id:
return dut
return None
@pyqtSlot('QVariant')
def addDUT(self, dut):
"""添加新DUT
会将 dtMachine 和 station 字段合成为 JSON 字符串存入 stationAssigned
虚拟字段dtMachine, station不会传给后台
"""
if dut:
try:
# 合成 stationAssigned JSON 字符串
station_assigned_str = self._compose_station_assigned(dut)
# 准备数据库记录(移除虚拟字段)
db_record = self._prepare_db_record(dut, station_assigned_str)
# 尝试使用 HTTP API
if self.use_http_api:
try:
url = f"{self.api_base_url}/dbTableAccess"
response = requests.post(
url,
params={"table": "dutList"}, # 注意:表名必须是 dutList
json={"action": "insert", "records": [db_record]}, # 使用清理后的记录
timeout=5
)
response.raise_for_status()
result = response.json()
if result.get("status") == "success":
# 重新加载数据
self._load_duts()
return
except Exception as e:
print(f"HTTP API 添加DUT失败: {e},回退到直接访问 SQLite")
# 直接访问 SQLite
if os.path.exists(self.db_path):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 插入新DUT
cursor.execute("""
INSERT INTO dutList
(SN, name, project, projectType, phase, weeks, stationAssigned,
itemOnGoing, itemsFinished, status, testReq, inspector,
description, deadLine, createdate, workOrder, direction_codes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
db_record.get("SN", ""),
db_record.get("name", ""),
db_record.get("project", ""),
db_record.get("projectType", ""),
db_record.get("phase", ""),
db_record.get("weeks", 0),
db_record.get("stationAssigned", ""),
db_record.get("itemOnGoing", ""),
db_record.get("itemsFinished", 0),
db_record.get("status", ""),
db_record.get("testReq", ""),
db_record.get("inspector", ""),
db_record.get("description", ""),
db_record.get("deadLine", ""),
db_record.get("createdate", ""),
db_record.get("workOrder", ""),
db_record.get("direction_codes", "")
))
conn.commit()
conn.close()
# 重新加载数据
self._load_duts()
except Exception as e:
print(f"添加DUT时出错: {e}")
@pyqtSlot(int)
def removeDUT(self, dut_id):
"""删除DUT"""
try:
# 从数据库删除
db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dtmgtDb.db')
if os.path.exists(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM dutList WHERE id = ?", (dut_id,))
conn.commit()
conn.close()
# 从内存列表删除
self._duts = [d for d in self._duts if d.get("id") != dut_id]
if self._current_dut and self._current_dut.get("id") == dut_id:
self.setCurrentDUT = None
self.dutsChanged.emit()
except Exception as e:
print(f"删除DUT时出错: {e}")
def _compose_station_assigned(self, dut):
"""将 dtMachine 和 station 字段合成为 JSON 字符串
Args:
dut: DUT 数据字典,包含 dtMachine 和 station 字段
Returns:
str: JSON 格式字符串,如 '{"dtMachine": "SUN-LAB1-TOP-002", "station": "01"}'
"""
dtm = dut.get("dtMachine", "").strip()
station = dut.get("station", "").strip()
if dtm or station:
obj = {"dtMachine": dtm, "station": station}
return json.dumps(obj, ensure_ascii=False)
return ""
def _prepare_db_record(self, dut, station_assigned_str=None):
"""准备数据库记录移除虚拟字段dtMachine, station
Args:
dut: DUT 数据字典
station_assigned_str: 已合成的 stationAssigned JSON 字符串(可选)
Returns:
dict: 清理后的数据库记录
"""
# 虚拟字段列表(不存在于数据库表中)
virtual_fields = {"dtMachine", "station"}
# 复制 dut 字典并移除虚拟字段
db_record = {k: v for k, v in dut.items() if k not in virtual_fields}
# 如果提供了 stationAssigned 字符串,使用它
if station_assigned_str is not None:
db_record["stationAssigned"] = station_assigned_str
return db_record
@pyqtSlot('QVariant')
def updateDUT(self, dut):
"""更新DUT信息
重要:
1. 只更新传入的非 None 字段,避免误清空数据
2. 如果传入了 dtMachine 或 station 字段,会自动合成 stationAssigned JSON 字符串
3. 虚拟字段dtMachine, station不会传给数据库
"""
if not dut or "id" not in dut:
return
try:
# 如果传入了 dtMachine 或 station合成 stationAssigned
station_assigned_str = None
if "dtMachine" in dut or "station" in dut:
station_assigned_str = self._compose_station_assigned(dut)
dut["stationAssigned"] = station_assigned_str
# 准备数据库记录(移除虚拟字段)
db_record = self._prepare_db_record(dut)
# 尝试使用 HTTP API
if self.use_http_api:
try:
url = f"{self.api_base_url}/dbTableAccess"
response = requests.post(
url,
params={"table": "dutList"}, # 注意:表名必须是 dutList
json={"action": "update", "records": [db_record]}, # 使用清理后的记录
timeout=5
)
response.raise_for_status()
result = response.json()
if result.get("status") == "success":
# 重新加载数据
self._load_duts()
return
except Exception as e:
print(f"HTTP API 更新DUT失败: {e},回退到直接访问 SQLite")
# 直接访问 SQLite
if os.path.exists(self.db_path):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 动态构建 UPDATE 语句,只更新传入的非 None 字段
update_fields = []
update_values = []
# 数据库真实字段(排除虚拟字段)
db_fields = [
"SN", "name", "project", "projectType", "phase",
"weeks", "stationAssigned", "itemOnGoing", "itemsFinished",
"status", "testReq", "inspector", "description",
"deadLine", "createdate", "workOrder", "direction_codes"
]
for field in db_fields:
if field in db_record and db_record[field] is not None:
update_fields.append(f"{field}=?")
# weeks 和 itemsFinished 需要转换为整数
if field in ["weeks", "itemsFinished"]:
update_values.append(int(db_record[field]))
else:
update_values.append(db_record[field])
if update_fields:
update_values.append(dut["id"])
sql = f"UPDATE dutList SET {', '.join(update_fields)} WHERE id=?"
cursor.execute(sql, tuple(update_values))
conn.commit()
conn.close()
# 重新加载数据
self._load_duts()
except Exception as e:
print(f"更新DUT时出错: {e}")
@pyqtSlot(result=int)
def getDUTCount(self):
"""获取DUT总数"""
return len(self._duts)
def getAllDUTs(self):
"""获取所有DUT列表
Returns:
list: 所有DUT数据列表
"""
print(f"getAllDUTs() 调用,当前共 {len(self._duts)} 条记录")
return self._duts
def filterByStatus(self, status):
"""按状态过滤DUT
Args:
status: 状态字符串,为空或'全部'时返回全部
Returns:
list: 过滤后的DUT列表
"""
if not status or status == '全部':
return self._duts
return [d for d in self._duts if (d.get('status') or '').strip() == status]