Files
Django/gvsdsdk/model_base.py

541 lines
19 KiB
Python
Raw Permalink 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.
"""QModel 兼容层基类模块
提供 SQLAlchemy 风格的链式 OOP 查询接口,使 Django ORM 支持:
- ``Model.query.filter(...)`` — 链式 FluentQuery 查询
- ``Model.field_name`` — 类级别返回 FieldExpression支持 ``==``, ``>``, ``in_()`` 等运算符)
- ``Model.field_name`` — 实例级别返回字段值(与原生 Django Model 一致)
- ``Model[condition]`` — 快捷 filter 语法
典型用法::
# 链式查询
orders = Dingdan.query.filter(zhuangtai=8).order_by('-CreateTime').to_list()
# 字段表达式
Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1, 2, 3]))
# 聚合
Dingdan.query.aggregate(total=Sum('jine'))
# 分页
Dingdan.query.paginate(page=1, per_page=20)
"""
from __future__ import annotations
import re
from typing import Any, Callable, Dict, Generic, Optional, Tuple, Type, TypeVar, Union
try:
from typing import Self
except ImportError:
from typing_extensions import Self
from django.db import models
from django.db.models import Q
from .fluent import FluentQuery
# ---------------------------------------------------------------------------
# 工具函数
# ---------------------------------------------------------------------------
#: classproperty 返回值类型变量
_RT = TypeVar('_RT')
#: QModel 子类类型变量(仅用于 __class_getitem__ 兼容)
_QM = TypeVar('_QM', bound='QModel')
class classproperty(Generic[_RT]):
"""类属性描述符,支持 ``Model.query`` 等类级别属性访问。
泛型参数 ``_RT`` 传播函数返回类型,使 Pylance 能穿透描述符推断
``Model.query`` 的返回类型为 ``FluentQuery[Self]``。
"""
def __init__(self, func: Callable[[type], _RT]) -> None:
self.fget = func
def __get__(self, obj: Any, objtype: Optional[type] = None) -> _RT:
return self.fget(objtype if objtype is not None else type(obj))
ABBREVIATIONS: Dict[str, str] = {
'uuid': 'UUID', 'id': 'ID', 'ip': 'IP', 'url': 'URL', 'uri': 'URI',
'api': 'API', 'html': 'HTML', 'css': 'CSS', 'js': 'JS', 'json': 'JSON',
'xml': 'XML', 'sql': 'SQL', 'http': 'HTTP', 'https': 'HTTPS',
'smtp': 'SMTP', 'ssl': 'SSL', 'tls': 'TLS', 'ssh': 'SSH', 'ftp': 'FTP',
'2fa': '2FA', 'elt': 'ELT', 'sve': 'SVE', 't4': 'T4', 'mc': 'MC',
'crb': 'CRB', 'ccgo': 'CCGO', 'ugm': 'UGM', 'gerp': 'GERP',
'abac': 'ABAC', 'rbac': 'RBAC', 'smtm': 'SMTM', 'pwd': 'PWD',
}
def snake_to_pascal(name: str) -> str:
"""将 snake_case 转换为 PascalCase自动识别常见缩写。
>>> snake_to_pascal('user_uuid')
'UserUUID'
>>> snake_to_pascal('paifa_dingdan_shu')
'PaifaDingdanShu'
"""
if '_' not in name:
return name[0].upper() + name[1:] if name else name
parts = name.split('_')
result = []
for p in parts:
lower = p.lower()
if lower in ABBREVIATIONS:
result.append(ABBREVIATIONS[lower])
else:
result.append(p.capitalize())
return ''.join(result)
def pascal_to_snake(name: str) -> str:
"""将 PascalCase 转换为 snake_case。
>>> pascal_to_snake('UserUUID')
'user_uuid'
"""
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
s2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1)
return s2.lower()
# ---------------------------------------------------------------------------
# 字段表达式(类级别访问 Model.field 时返回)
# ---------------------------------------------------------------------------
class _InvertedFieldExpression:
"""取反字段表达式,由 ``~Model.field`` 产生,支持 ``~field.startswith(...)`` 等否定查询。"""
def __init__(self, field_expr: FieldExpression) -> None:
self._field_expr = field_expr
def startswith(self, value: str) -> Q:
return ~Q(**{f'{self._field_expr._django_name}__startswith': value})
def contains(self, value: str) -> Q:
return ~Q(**{f'{self._field_expr._django_name}__contains': value})
def like(self, pattern: str) -> Q:
clean = pattern.strip('%')
return ~Q(**{f'{self._field_expr._django_name}__icontains': clean})
def in_(self, values) -> Q:
return ~Q(**{f'{self._field_expr._django_name}__in': values})
def is_(self, other) -> Q:
if other is None:
return ~Q(**{f'{self._field_expr._django_name}__isnull': True})
return ~Q(**{self._field_expr._django_name: other})
def between(self, low, high) -> Q:
return ~(Q(**{f'{self._field_expr._django_name}__gte': low})
& Q(**{f'{self._field_expr._django_name}__lte': high}))
def __repr__(self) -> str:
return f'~{self._field_expr!r}'
class FieldExpression:
"""字段表达式,由类级别 ``Model.field_name`` 访问产生。
支持通过 Python 运算符构建 Django Q 对象::
Model.field == value → Q(field=value)
Model.field > value → Q(field__gt=value)
Model.field.in_([1,2,3]) → Q(field__in=[1,2,3])
Model.field.between(a, b) → Q(field__gte=a) & Q(field__lte=b)
~Model.field → _InvertedFieldExpression否定查询
"""
_resolve_cache: Dict[Tuple[str, str], str] = {}
def __init__(self, model_class: Type, field_name: str) -> None:
self.model_class = model_class
self.field_name = field_name
self._django_name: str = self._resolve_django_name()
def _resolve_django_name(self) -> str:
"""将 Python 字段名解析为 Django ORM 查询使用的字段名。"""
cache_key = (self.model_class.__name__, self.field_name)
if cache_key in FieldExpression._resolve_cache:
return FieldExpression._resolve_cache[cache_key]
if hasattr(self.model_class, '_meta'):
for f in self.model_class._meta.local_fields:
if f.name == self.field_name:
FieldExpression._resolve_cache[cache_key] = f.name
return f.name
FieldExpression._resolve_cache[cache_key] = self.field_name
return self.field_name
# ---- 比较运算符 ----
def __eq__(self, other) -> Q: # type: ignore[override]
if isinstance(other, FieldExpression):
return NotImplemented
if other is None:
return Q(**{f'{self._django_name}__isnull': True})
return Q(**{self._django_name: other})
def __ne__(self, other) -> Q: # type: ignore[override]
if isinstance(other, FieldExpression):
return NotImplemented
return ~Q(**{self._django_name: other})
def __gt__(self, other) -> Q:
return Q(**{f'{self._django_name}__gt': other})
def __ge__(self, other) -> Q:
return Q(**{f'{self._django_name}__gte': other})
def __lt__(self, other) -> Q:
return Q(**{f'{self._django_name}__lt': other})
def __le__(self, other) -> Q:
return Q(**{f'{self._django_name}__lte': other})
def __hash__(self) -> int:
return hash((self.model_class.__name__, self.field_name))
# ---- 逻辑运算符 ----
def __and__(self, other) -> Q:
if isinstance(other, Q):
return Q(self) & other
if isinstance(other, FieldExpression):
return Q(self) & Q(other)
return NotImplemented
def __or__(self, other) -> Q:
if isinstance(other, Q):
return Q(self) | other
if isinstance(other, FieldExpression):
return Q(self) | Q(other)
return NotImplemented
def __invert__(self) -> _InvertedFieldExpression:
return _InvertedFieldExpression(self)
# ---- 查询方法 ----
def is_(self, other) -> Q:
"""``field IS NULL`` 或 ``field IS value``。"""
if other is None:
return Q(**{f'{self._django_name}__isnull': True})
return Q(**{self._django_name: other})
def is_not(self, other) -> Q:
"""``field IS NOT NULL`` 或 ``field IS NOT value``。"""
if other is None:
return Q(**{f'{self._django_name}__isnull': False})
return ~Q(**{self._django_name: other})
def like(self, pattern: str) -> Q:
"""模糊匹配(不区分大小写),自动去除 ``%`` 通配符。"""
clean = pattern.strip('%')
return Q(**{f'{self._django_name}__icontains': clean})
def contains(self, value: str) -> Q:
"""包含子串(区分大小写)。"""
return Q(**{f'{self._django_name}__contains': value})
def ilike(self, pattern: str) -> Q:
"""like() 的别名。"""
return self.like(pattern)
def startswith(self, value: str) -> Q:
"""前缀匹配(区分大小写)。"""
return Q(**{f'{self._django_name}__startswith': value})
def istartswith(self, value: str) -> Q:
"""前缀匹配(不区分大小写)。"""
return Q(**{f'{self._django_name}__istartswith': value})
def endswith(self, value: str) -> Q:
"""后缀匹配(区分大小写)。"""
return Q(**{f'{self._django_name}__endswith': value})
def iendswith(self, value: str) -> Q:
"""后缀匹配(不区分大小写)。"""
return Q(**{f'{self._django_name}__iendswith': value})
def in_(self, values) -> Q:
"""``field IN (values)``,支持列表和 FluentQuery 子查询。"""
if hasattr(values, '_qs'):
return Q(**{f'{self._django_name}__in': values._qs.values(self._django_name)})
return Q(**{f'{self._django_name}__in': values})
def between(self, low, high) -> Q:
"""``field BETWEEN low AND high``。"""
return Q(**{f'{self._django_name}__gte': low}) & Q(**{f'{self._django_name}__lte': high})
def regex(self, pattern: str) -> Q:
"""正则匹配(区分大小写)。"""
return Q(**{f'{self._django_name}__regex': pattern})
def iregex(self, pattern: str) -> Q:
"""正则匹配(不区分大小写)。"""
return Q(**{f'{self._django_name}__iregex': pattern})
# ---- 排序辅助 ----
def desc(self) -> str:
"""返回降序排序字符串 ``'-field_name'``。"""
return f'-{self._django_name}'
def asc(self) -> str:
"""返回升序排序字符串 ``'field_name'``。"""
return self._django_name
# ---- JSON 字段访问 ----
def __getitem__(self, key: str) -> _JsonKeyExpression:
"""访问 JSON 字段内的键,如 ``Model.meta['key'] == value``。"""
return _JsonKeyExpression(self._django_name, key)
# ---- 显示 ----
def __repr__(self) -> str:
return f'FieldExpr({self.model_class.__name__}.{self.field_name})'
def __str__(self) -> str:
return self._django_name
class _JsonKeyExpression:
"""JSON 字段键表达式,由 ``Model.json_field['key']`` 产生。"""
def __init__(self, field_name: str, key: str) -> None:
self.field_name = field_name
self.key = key
def _lookup(self, suffix: str = '') -> str:
if suffix:
return f'{self.field_name}__{self.key}__{suffix}'
return f'{self.field_name}__{self.key}'
def cast(self, type_hint) -> _CastJsonExpression:
"""将 JSON 值转换为指定类型后再比较。"""
return _CastJsonExpression(self.field_name, self.key, type_hint)
def __eq__(self, other) -> Q: # type: ignore[override]
return Q(**{self._lookup(): other})
def __ne__(self, other) -> Q: # type: ignore[override]
return ~Q(**{self._lookup(): other})
def __gt__(self, other) -> Q:
return Q(**{self._lookup('gt'): other})
def __ge__(self, other) -> Q:
return Q(**{self._lookup('gte'): other})
def __lt__(self, other) -> Q:
return Q(**{self._lookup('lt'): other})
def __le__(self, other) -> Q:
return Q(**{self._lookup('lte'): other})
def between(self, low, high) -> Q:
return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high})
def __repr__(self) -> str:
return f'JsonKeyExpr({self.field_name}[{self.key!r}])'
class _CastJsonExpression:
"""类型转换后的 JSON 键表达式。"""
def __init__(self, field_name: str, key: str, type_hint) -> None:
self.field_name = field_name
self.key = key
self.type_hint = type_hint
def _lookup(self, suffix: str = '') -> str:
if suffix:
return f'{self.field_name}__{self.key}__{suffix}'
return f'{self.field_name}__{self.key}'
def __eq__(self, other) -> Q: # type: ignore[override]
return Q(**{self._lookup(): other})
def __ne__(self, other) -> Q: # type: ignore[override]
return ~Q(**{self._lookup(): other})
def __gt__(self, other) -> Q:
return Q(**{self._lookup('gt'): other})
def __ge__(self, other) -> Q:
return Q(**{self._lookup('gte'): other})
def __lt__(self, other) -> Q:
return Q(**{self._lookup('lt'): other})
def __le__(self, other) -> Q:
return Q(**{self._lookup('lte'): other})
def between(self, low, high) -> Q:
return Q(**{self._lookup('gte'): low}) & Q(**{self._lookup('lte'): high})
def __repr__(self) -> str:
type_name = getattr(self.type_hint, '__name__', str(self.type_hint))
return f'CastJsonExpr({self.field_name}[{self.key!r}]->{type_name})'
# ---------------------------------------------------------------------------
# QModel 元类与基类
# ---------------------------------------------------------------------------
class QModelBase(models.base.ModelBase):
"""QModel 元类,负责:
1. 根据 ``GVSDSDK_MANAGE_TABLES`` 设置动态控制 ``managed``
2. 包装字段描述符,使类级别访问返回 FieldExpression
"""
def __new__(mcs, name, bases, namespace, **kwargs):
# 动态设置 managed子服务器可设 GVSDSDK_MANAGE_TABLES=True 让 gvsdsdk 管理表
meta = namespace.get('Meta')
if meta and getattr(meta, 'app_label', None) == 'gvsdsdk':
try:
from django.conf import settings
if getattr(settings, 'GVSDSDK_MANAGE_TABLES', False):
meta.managed = True
except Exception:
pass
cls = super().__new__(mcs, name, bases, namespace, **kwargs)
# 模型创建后,包装字段描述符以支持类级别 FieldExpression
if not cls._meta.abstract:
_wrap_field_descriptors(cls)
return cls
def _wrap_field_descriptors(cls: Type) -> None:
"""包装 Django 字段描述符,使类级别访问返回 FieldExpression
实例级别完全委托给 Django 原生描述符。
Django 的字段描述符DeferredAttribute / ForwardManyToOneDescriptor
- ``__get__(None, cls)`` → 返回字段对象本身(类级别)
- ``__get__(instance, cls)`` → 返回字段值(实例级别)
我们包装后:
- ``__get__(None, cls)`` → 返回 FieldExpression类级别用于链式 OOP
- ``__get__(instance, cls)`` → 委托给原生描述符(实例级别,正常行为)
"""
from django.db.models.query_utils import DeferredAttribute
from django.db.models.fields.related_descriptors import (
ForwardManyToOneDescriptor,
ForwardOneToOneDescriptor,
)
for field in cls._meta.local_fields:
fname = field.name
if fname.startswith('_'):
continue
original_desc = cls.__dict__.get(fname)
if original_desc is None:
continue
# 只包装已知的 Django 描述符类型
if isinstance(original_desc, (DeferredAttribute, ForwardManyToOneDescriptor,
ForwardOneToOneDescriptor)):
wrapper = _FieldExpressionWrapper(original_desc, cls, fname)
setattr(cls, fname, wrapper)
class _FieldExpressionWrapper:
"""包装 Django 字段描述符,类级别返回 FieldExpression实例级别委托给原生描述符。
这是 QModel 字段表达式机制的核心:当你在类上访问 ``Model.field_name`` 时,
返回 ``FieldExpression`` 对象,支持 ``Model.field == value`` 等运算符构建 Q 对象;
当你在实例上访问 ``obj.field_name`` 时,行为与原生 Django 完全一致。
"""
def __init__(self, original_descriptor, model_class: Type, field_name: str) -> None:
self._original = original_descriptor
self._model = model_class
self._field_name = field_name
self._has_set: bool = hasattr(original_descriptor, '__set__')
def __get__(self, obj, objtype=None):
if obj is None:
# 类级别访问:返回 FieldExpression
return FieldExpression(self._model, self._field_name)
# 实例级别访问:委托给原生 Django 描述符
return self._original.__get__(obj, objtype)
def __set__(self, obj, value) -> None:
if self._has_set:
# FK/O2O 描述符有 __set__
self._original.__set__(obj, value)
else:
# 普通字段DeferredAttribute没有 __set__直接写 __dict__
obj.__dict__[self._field_name] = value
def __repr__(self) -> str:
return f'_FieldExpressionWrapper({self._model.__name__}.{self._field_name})'
class QModel(models.Model, metaclass=QModelBase):
"""QModel 兼容层基类,提供 SQLAlchemy 风格的链式 OOP 查询接口。
所有继承 QModel 的模型自动获得:
- ``Model.query`` : 返回 FluentQuery支持链式查询
- ``Model.field_name`` : 类级别访问返回 FieldExpression支持表达式查询
- ``Model.field_name`` : 实例级别访问返回字段值(与普通 Django Model 一致)
- ``Model[condition]`` : 快捷 filter 语法
用法示例::
# 链式查询
orders = Dingdan.query.filter(zhuangtai=8).order_by('-CreateTime').to_list()
# 字段表达式
Dingdan.query.filter(Dingdan.jine > 100, Dingdan.zhuangtai.in_([1,2,3]))
# 聚合
Dingdan.query.aggregate(total=Sum('jine'))
# values / annotate
Dingdan.query.values('zhuangtai').annotate(cnt=Count('id'))
# 分页
Dingdan.query.paginate(page=1, per_page=20)
# 快捷 filter
Dingdan[Dingdan.zhuangtai == 8].order_by('-CreateTime').to_list()
"""
class Meta:
abstract = True
@classproperty
def query(cls) -> FluentQuery[Self]:
"""返回 FluentQuery 兼容层查询构建器,支持链式 OOP 查询。"""
return FluentQuery(cls)
@classmethod
def __class_getitem__(cls, item) -> FluentQuery[Self]:
"""支持 ``Model[condition]`` 语法,返回 filter 后的 FluentQuery。
用法::
Dingdan[Dingdan.zhuangtai == 8].order_by('-CreateTime').to_list()
Dingdan[{'zhuangtai': 8}].to_list()
"""
if isinstance(item, Q):
return cls.query.filter(item)
if isinstance(item, dict):
return cls.query.filter(**item)
return cls.query.filter(item)