mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-26 09:32:26 +02:00
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
261 lines
8.7 KiB
Python
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 {}
|