Files
aaPanel/class/public/aaModel/model.py
T
Jack 6f212ce3d6 Update to 7.27.0
1. Added Domains menu to manage Domains and SSL Certificates
2. Added Add Site automatic record creation when adding websites (Only supported by IPv4)
3. Added Vietnamese language
4. Added Indonesian language
5. Added HTTPS Protection function for Website (disables automatic HTTP to HTTPS redirection when enabled)
6. Added Mail Marketing -- Automation trigger tasks
7. Added Mail Marketing -- Groups Import, Export, Merge
8. Added Mail Marketing -- Subscribers to support paste import
9. Added Mail Marketing -- Template Import Export Duplicate
10. Added Mail server -- Other Settings add timed Auto Responder
11. Added Mail Marketing -- Suspend List to detect abnormal mailboxes
12. Added Mail Marketing -- Marketing Task to export error mail logs
13. Added Mail Domain -- SSL certificate expiration alert
14. Optimized Webmail management to display based on domain additions
15. Optimize Website Interface Button Integration
16. Optimize the response speed of WP Toolkit interface
2025-03-26 09:04:29 +08:00

261 lines
8.7 KiB
Python

# coding: utf-8
import copy
from typing import Self, Generator, Optional, Dict, Any
from .fields import aaField
from .manager import aaManager
__all__ = ["aaModel"]
from public.exceptions import HintException
def generate_table_name(class_name: str) -> str:
"""
驼峰名转表名
"""
return ''.join(['_' + c.lower() if c.isupper() else c for c in class_name]).lstrip('_')
class aaMetaClass(type):
__abstract__: bool
__db_name__: str
__table_name__: str
__fields__: dict
__primary_key__: str
__serializes__: dict
__index_keys__: list
# __foreign_keys__: object
def __new__(cls, name, bases, attrs):
if attrs.get("__abstract__") is True:
return super().__new__(cls, name, bases, attrs)
attrs.update({"__abstract__": False})
new_class = super().__new__(cls, name, bases, attrs)
cls.__fields_process(obj=new_class, name=name, attrs=attrs)
cls.__database_process(obj=new_class, name=name, attrs=attrs)
return new_class
def __setattr__(cls, key, value):
if key == '__abstract__':
raise AttributeError("can't set attribute '__abstract__'")
return super().__setattr__(key, value)
@classmethod
def __fields_process(cls, obj: "aaMetaClass", name: str, attrs: dict):
pk = ""
fields = {}
for k, v in attrs.items():
if isinstance(v, aaField):
if k in fields:
raise HintException(f"model {name} field '{k}' is already defined")
fields[k] = v
if v.primary_key:
if pk:
raise HintException(f"model {name} can only have one primary key")
else:
pk = k
if not pk:
raise HintException(f"sth wrong with {name}'s primary key, please check the model")
setattr(obj, "__primary_key__", pk)
setattr(obj, "__fields__", fields)
setattr(obj, "__serializes__", cls.__get_serialized(fields))
@classmethod
def __database_process(cls, obj: "aaMetaClass", name: str, attrs: dict):
db_name, tb_name, idx = "default", generate_table_name(name), []
meta = attrs.get("_Meta")
if meta:
if hasattr(meta, "db_name"):
db_name = meta.db_name
if hasattr(meta, "table_name"):
tb_name = meta.table_name
if hasattr(meta, "index"):
idx = meta.index
setattr(obj, "__db_name__", db_name)
setattr(obj, "__table_name__", tb_name)
setattr(obj, "__index_keys__", idx)
@staticmethod
def __get_serialized(fields: Dict[str, aaField]) -> Dict[str, aaField]:
return dict(filter(
lambda x: x[1].serialized is not None, {**fields}.items()
))
class aaCusModel(metaclass=aaMetaClass):
__abstract__ = True
objects = aaManager()
def __init__(self, **kwargs):
if self.__abstract__ is True:
raise RuntimeError(f'{self.__class__.__name__} class can not be init')
for f, v in self._generate_init(kwargs):
setattr(self, f.field_name, v)
def __repr__(self):
return f"<'{self.__class__.__name__}' Model Object, {self.__dict__}>"
def _generate_init(self, val_data: dict) -> Generator:
for name, field in {**self.__class__.__fields__}.items():
val = val_data.pop(name) if name in val_data else field.get_default_val()
if field.primary_key is True and val == 0:
continue # skip default id val
if field.primary_key is True and val != 0:
try:
val = int(val)
except Exception:
pass
yield field, val
if val_data: # other field
# raise AttributeError(f"model '{self.__class__.__name__}' has no field {val_data}")
pass
class aaModel(aaCusModel):
"""
基础模型
:example:
class MyTestModel(aaModel):
id = IntField(primary_key=True)
name = StrField(ps="名字")
status = BoolField(default=True, ps="状态")
float_number = FloatField(default=0.05, ps="浮点")
class _Meta:
db_name = "default" 默认为 default.db 文件
table_name = "my_table" 默认为类名驼峰转表名 my_test_model
index = [("status", 1)] 索引
"""
__abstract__: bool = True
__destroyed: bool = False
id: int = None
@staticmethod
def check_destroyed(func):
def wrapper(self, *args, **kwargs):
if getattr(self, "__destroyed", False):
raise RuntimeError(f"Cannot call {func.__name__}() on destroyed object")
return func(self, *args, **kwargs)
return wrapper
@classmethod
@check_destroyed
def _output(cls, data: dict) -> dict:
return {
k: v if not cls.__serializes__.get(k) else cls.__serializes__.get(k).serialized(v, False) for k, v in
data.items()
}
@classmethod
@check_destroyed
def _serialized_data(cls, data: Optional[dict | list]) -> Optional[dict | list]:
if not data or not hasattr(cls, "__serializes__"):
return data
if isinstance(data, list):
return [cls._output(d) for d in data]
elif isinstance(data, dict):
return cls._output(data)
else:
return data
@check_destroyed
def _validate(self, raise_exp: bool = True) -> Optional[Dict[str, Any]]:
"""
模型验证
"""
body = {}
for f, cur_val in self._generate_init(copy.deepcopy(self.__dict__)):
try:
f.model_check_type(target=cur_val, raise_exp=True)
# 1, dynamic generated
if hasattr(f, "dynamic") and f.dynamic is True:
cur_val = f._dynamic(f, cur_val)
setattr(self, f.field_name, cur_val)
# 2, serialized
body[f.field_name] = f.serialized(cur_val, True) if f.serialized else cur_val
except TypeError as t:
if raise_exp:
raise t
else:
return {}
except Exception as e:
raise e
return body
def _before_save(self) -> bool:
# override
return True
def _after_save(self) -> None:
# override
pass
@check_destroyed
def save(self, raise_exp: bool = True) -> Optional[Self]:
"""
模型数据, 不存在则 保存 , 存在则 更新
:raise_exp 抛异常
:return: model object 字段类型异常等问题返回 None
"""
if self.__class__.__abstract__ is True:
raise RuntimeError(f'{self.__class__.__name__} class can not be save')
try:
cls = self.__class__
validate = self._validate(raise_exp=raise_exp)
primary_key = cls.__primary_key__
if validate and primary_key:
if primary_key in validate:
target_id = validate.pop(primary_key)
exist = cls.objects._query.where(f"{primary_key}=?", (target_id,)).exists()
if exist: # update
res = cls.objects._query.where(f"{primary_key}=?", (target_id,)).update(validate)
if res == 1:
self._after_save()
return self
else:
if raise_exp:
raise HintException("update error")
else:
return None
else:
validate[primary_key] = target_id
# save
if not self._before_save():
return None
new_id = cls.objects._query.insert(validate)
self._after_save()
return cls(**{primary_key: new_id, **self.__dict__})
return None
except TypeError as t:
if raise_exp:
raise t
else:
return None
except Exception as e:
raise HintException(e)
@check_destroyed
def delete(self) -> int:
try:
self.__class__.objects._query.where(
f"{self.__class__.__primary_key__}=?", (self.id,)
).delete()
setattr(self, "__destroyed", True)
except Exception as e:
print(e)
return 0
return 1
@check_destroyed
def as_dict(self) -> dict:
"""
转字典
"""
return self.__dict__ if self.__dict__ is not None else {}