mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-26 01:22:25 +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
363 lines
9.2 KiB
Python
363 lines
9.2 KiB
Python
# coding: utf-8
|
|
|
|
import json
|
|
import time
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass, field as dataclass_field
|
|
from datetime import datetime
|
|
from typing import Any, TypeVar, List, Optional
|
|
|
|
from public.exceptions import HintException
|
|
|
|
__all__ = [
|
|
"StrField",
|
|
"IntField",
|
|
"FloatField",
|
|
"BlobField",
|
|
"BoolField",
|
|
"ListField",
|
|
"DictField",
|
|
"DateTimeStrField",
|
|
]
|
|
|
|
M = TypeVar("M", bound="aaModel")
|
|
|
|
|
|
def json_func(v_type: type, value: Any, forward: bool = True):
|
|
try:
|
|
if forward is True:
|
|
if isinstance(value, v_type):
|
|
return json.dumps(value)
|
|
else:
|
|
if isinstance(value, str):
|
|
return json.loads(value)
|
|
return value
|
|
except TypeError as t:
|
|
print("type error %s" % t)
|
|
return value
|
|
except Exception as e:
|
|
print("error %s" % e)
|
|
raise e
|
|
|
|
|
|
@dataclass
|
|
class aaField(object):
|
|
"""
|
|
字段基类
|
|
default 默认值
|
|
ps 字段说明
|
|
null 是否null
|
|
primary_key 是否主键
|
|
foreign_key 外键
|
|
field_name 字段key
|
|
field_type sql类型
|
|
py_type py类型
|
|
compare 比较
|
|
transform 转换工具
|
|
"""
|
|
default: Any = None
|
|
ps: str = None
|
|
null: bool = False
|
|
primary_key: bool = False
|
|
field_name: str = None
|
|
field_type: str = None
|
|
py_type: type = None
|
|
compare: tuple = None
|
|
serialized: Callable = None
|
|
# todo
|
|
# require: bool = False
|
|
# foreign_key: str = None
|
|
|
|
def __set_name__(self, owner: M, name: str):
|
|
self.__model = owner
|
|
self.field_name = str(name)
|
|
|
|
def __get__(self, instance: object, owner):
|
|
if instance is None:
|
|
return self
|
|
try:
|
|
return instance.__dict__[self.field_name]
|
|
except KeyError:
|
|
raise AttributeError(f'{self.field_name} is not set')
|
|
|
|
def __set__(self, instance, value):
|
|
instance.__dict__[self.field_name] = value
|
|
|
|
def __delete__(self, instance):
|
|
try:
|
|
del instance.__dict__[self.field_name]
|
|
except KeyError:
|
|
raise AttributeError(f"{instance} dont have attr '{self.field_name}'")
|
|
|
|
def _raise_error(self, raise_exp: bool = True) -> bool:
|
|
if raise_exp is True:
|
|
err = f"'{self.field_name}' TypeError! It should be '{self.py_type.__name__}'"
|
|
raise HintException(err)
|
|
else:
|
|
return False
|
|
|
|
def _check_type(self, target: Any, raise_exp=True) -> bool:
|
|
target = target if not isinstance(target, Callable) else target()
|
|
flag = True if any([isinstance(target, x) for x in self.py_types]) else False
|
|
return True if flag is True else self._raise_error(raise_exp=raise_exp)
|
|
|
|
@property
|
|
def default_val_sql(self) -> str:
|
|
default_v = self.get_default_val(check=True)
|
|
default_v = default_v if self.serialized is None else self.serialized(default_v)
|
|
return f"DEFAULT '{default_v}'" if isinstance(default_v, str) else f"DEFAULT {default_v}"
|
|
|
|
@property
|
|
def py_types(self) -> List[type]:
|
|
if self.null is True:
|
|
original = [type(self.default), self.py_type, type(None)]
|
|
else:
|
|
original = [type(self.default), self.py_type]
|
|
return list(set(original))
|
|
|
|
def get_default_val(self, check: bool = False) -> Optional[Any]:
|
|
if check:
|
|
self.check_org_type(raise_exp=check)
|
|
if self.default is not None:
|
|
return self.default if not isinstance(self.default, Callable) else self.default()
|
|
else:
|
|
if self.null is True:
|
|
return None
|
|
else:
|
|
raise TypeError(
|
|
f"\n1: field '{self.field_name}' is not null, must have a default value"
|
|
f"\n2: you can add the '{self.field_name}' field's params null=True"
|
|
)
|
|
|
|
def model_check_type(self, target: Any, raise_exp=True) -> bool:
|
|
"""
|
|
检查模型当前类型结构
|
|
"""
|
|
return self._check_type(target, raise_exp=raise_exp)
|
|
|
|
def check_org_type(self, raise_exp: bool = True) -> bool:
|
|
"""
|
|
检查初始化的类型结构
|
|
"""
|
|
return self._check_type(self.default, raise_exp=raise_exp)
|
|
|
|
|
|
@dataclass()
|
|
class StrField(aaField):
|
|
"""
|
|
String field
|
|
|
|
field__like="a",
|
|
field__ne="a",
|
|
field__in=["a", "b", "c"]
|
|
field__not_in=["a", "b", "c"]
|
|
field__startswith="a"
|
|
field__endswith="a"
|
|
"""
|
|
default: str | None = ""
|
|
field_type: str = "TEXT"
|
|
py_type: type = str
|
|
max_length: int = 255 # not limit now
|
|
min_length: int = 0 # not limit now
|
|
compare: tuple[str] = (
|
|
"like",
|
|
"ne",
|
|
"in",
|
|
"not_in",
|
|
"startswith",
|
|
"endswith",
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class IntField(aaField):
|
|
"""
|
|
Int field
|
|
|
|
field__gt=1,
|
|
field__gte=1,
|
|
field__lt=1,
|
|
field__lte=1,
|
|
field__ne=1,
|
|
field__in=[1, 2, 3]
|
|
field__not_in=[1, 2, 3]
|
|
"""
|
|
default: int | None | Any = 0
|
|
field_type: str = "INTEGER"
|
|
py_type: type = int
|
|
max: int = 0 # not limit now
|
|
min: int = 0 # not limit now
|
|
compare: tuple[str] = (
|
|
"gt",
|
|
"lt",
|
|
"gte",
|
|
"lte",
|
|
"ne",
|
|
"in",
|
|
"not_in",
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class FloatField(aaField):
|
|
"""
|
|
Float field
|
|
"""
|
|
default: float | None = 0.0
|
|
field_type: str = "REAL"
|
|
py_type: type = float
|
|
max: float = 0.0 # not limit now
|
|
min: float = 0.0 # not limit now
|
|
compare: tuple[str] = (
|
|
"gt",
|
|
"lt",
|
|
"gte",
|
|
"lte",
|
|
"ne",
|
|
"in",
|
|
"not_in",
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class BlobField(aaField):
|
|
"""
|
|
Blob field
|
|
"""
|
|
default: bytes | None = b''
|
|
field_type: str = "BLOB"
|
|
py_type: type = bytes
|
|
|
|
|
|
@dataclass
|
|
class BoolField(aaField):
|
|
"""
|
|
Bool field
|
|
"""
|
|
|
|
@staticmethod
|
|
def _serialized(value: bool | int, forward: bool = True) -> bool | int:
|
|
if forward is True:
|
|
if value is True:
|
|
return 1
|
|
elif value is False:
|
|
return 0
|
|
else:
|
|
if value == 1:
|
|
return True
|
|
elif value == 0:
|
|
return False
|
|
return value
|
|
|
|
default: bool = True
|
|
field_type: str = "INTEGER"
|
|
serialized: Callable = _serialized
|
|
py_type: type = bool
|
|
|
|
|
|
@dataclass
|
|
class ListField(aaField):
|
|
"""
|
|
List field
|
|
"""
|
|
|
|
@staticmethod
|
|
def _serialized(value: list | str, forward: bool = True) -> list | Any:
|
|
return json_func(list, value, forward)
|
|
|
|
default: list = dataclass_field(default_factory=list)
|
|
field_type: str = "TEXT"
|
|
serialized: Callable = _serialized
|
|
py_type: type = list
|
|
compare: tuple[str] = (
|
|
"has_element",
|
|
)
|
|
update: tuple[str] = (
|
|
"append",
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class DictField(aaField):
|
|
"""
|
|
Dict field
|
|
"""
|
|
|
|
@staticmethod
|
|
def _serialized(value: dict | str, forward: bool = True) -> dict | Any:
|
|
return json_func(dict, value, forward)
|
|
|
|
default: dict = dataclass_field(default_factory=dict)
|
|
field_type: str = "TEXT"
|
|
serialized: Callable = _serialized
|
|
py_type: type = dict
|
|
compare: tuple[str] = (
|
|
"has_key",
|
|
"has_value",
|
|
# "has_key_value",
|
|
)
|
|
update: tuple[str] = (
|
|
"update",
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class DateTimeStrField(aaField):
|
|
"""
|
|
时间戳
|
|
auto_now_add=True 创建时间自动添加
|
|
auto_now=True 更新时间自动更新
|
|
"""
|
|
|
|
@classmethod
|
|
def _current_timestamp(cls):
|
|
return time.strftime(cls.format, time.localtime())
|
|
|
|
@staticmethod
|
|
def _dynamic(obj, val):
|
|
if hasattr(obj, "auto_now_add") and obj.auto_now_add is True: # 创建时间
|
|
return val
|
|
elif hasattr(obj, "auto_now") and obj.auto_now is True: # 更新时间
|
|
return obj.get_default_val()
|
|
else:
|
|
return val
|
|
|
|
@staticmethod
|
|
def _serialized(value: int | str, forward: bool = True) -> str | int:
|
|
try:
|
|
if forward is True:
|
|
if isinstance(value, str): # save will be str
|
|
return int(
|
|
datetime.strptime(value, DateTimeStrField.format).timestamp() * DateTimeStrField.accuracy)
|
|
else:
|
|
if isinstance(value, int):
|
|
return datetime.fromtimestamp(value / DateTimeStrField.accuracy).strftime(DateTimeStrField.format)
|
|
return value
|
|
except Exception as e:
|
|
print("type error %s" % e)
|
|
return value
|
|
|
|
serialized: Callable = _serialized
|
|
default: str | Callable = ""
|
|
field_type: str = "INTEGER"
|
|
py_type: type = str
|
|
dynamic: bool = True
|
|
auto_now_add: bool = False
|
|
auto_now: bool = False
|
|
accuracy: int = 1000
|
|
format: str = "%Y-%m-%d %H:%M:%S"
|
|
compare: tuple[str] = (
|
|
"gt",
|
|
"lt",
|
|
"gte",
|
|
"lte",
|
|
)
|
|
|
|
def __post_init__(self):
|
|
if self.auto_now_add is True and self.auto_now is True:
|
|
raise TypeError("auto_now_add and auto_now can not be used at the same time")
|
|
if self.auto_now is True:
|
|
self.default = self._current_timestamp
|
|
elif self.auto_now_add is True:
|
|
self.default = self._current_timestamp
|