修复了 QModel 链式表存在的一些问题

This commit is contained in:
2026-06-16 23:33:23 +08:00
parent 134f7e2ce0
commit 54a64dc220
2 changed files with 269 additions and 83 deletions

View File

@@ -1,3 +1,8 @@
from __future__ import annotations
import typing
from typing import Any, Dict, List, Optional, Tuple, Type, Union
from django.db import models
from django.db.models import Q
from .fluent import FluentQuery
@@ -290,9 +295,19 @@ class _CastJsonExpression:
class ForeignKeyIdDescriptor:
"""FK 字段描述符:类级别访问返回 FieldExpression实例级别访问/设置委托给原始 Django 字段。"""
def __init__(self, fk_name):
self.fk_name = fk_name
self.id_attr = fk_name + '_id'
self._original_field = None # 延迟绑定原始 Django 字段
def _get_original_field(self, owner):
if self._original_field is None:
try:
self._original_field = owner._meta.get_field(self.fk_name)
except Exception:
pass
return self._original_field
def __get__(self, obj, objtype=None):
if obj is None:
@@ -300,20 +315,39 @@ class ForeignKeyIdDescriptor:
return getattr(obj, self.id_attr)
def __set__(self, obj, value):
setattr(obj, self.id_attr, value)
# 直接写入实例 __dict__避免触发描述符递归
obj.__dict__[self.id_attr] = value
class FieldExpressionDescriptor:
"""普通字段描述符:类级别访问返回 FieldExpression实例级别访问/设置委托给原始 Django 字段。"""
def __init__(self, field_name):
self.field_name = field_name
self._original_field = None # 延迟绑定原始 Django 字段
def _get_original_field(self, owner):
if self._original_field is None:
try:
self._original_field = owner._meta.get_field(self.field_name)
except Exception:
pass
return self._original_field
def __get__(self, obj, objtype=None):
if obj is None:
return FieldExpression(objtype, self.field_name)
# 实例级别:从 __dict__ 获取,或让 Django 字段描述符处理
if self.field_name in obj.__dict__:
return obj.__dict__[self.field_name]
# 委托给原始 Django 字段
orig = self._get_original_field(objtype)
if orig is not None:
return orig.__get__(obj, objtype)
return getattr(obj, self.field_name)
def __set__(self, obj, value):
setattr(obj, self.field_name, value)
# 直接写入实例 __dict__避免触发描述符递归
obj.__dict__[self.field_name] = value
class QModelBase(models.base.ModelBase):
@@ -337,15 +371,90 @@ class QModelBase(models.base.ModelBase):
except Exception:
pass
return super().__new__(mcs, name, bases, namespace, **kwargs)
cls = super().__new__(mcs, name, bases, namespace, **kwargs)
# 模型创建后,自动为所有字段添加描述符,支持 Model.field_name 链式 OOP
if not cls._meta.abstract:
_attach_field_descriptors(cls)
return cls
def _attach_field_descriptors(cls: Type) -> None:
"""为 QModel 子类的所有字段自动添加 FieldExpressionDescriptor / ForeignKeyIdDescriptor
使 Model.field_name 在类级别访问时返回 FieldExpression支持链式 OOP 查询。"""
from django.db import models as dj_models
for field in cls._meta.local_fields:
fname = field.name
# 跳过已有描述符或内部属性
if fname.startswith('_'):
continue
# 检查是否已有同名描述符(避免覆盖手动定义的)
existing = cls.__dict__.get(fname)
if isinstance(existing, (FieldExpressionDescriptor, ForeignKeyIdDescriptor)):
continue
# 跳过 Django 自动创建的反向关系和 id 主键(如果用户未显式定义)
if fname == 'id' and field.primary_key and not isinstance(
cls.__dict__.get(fname), (FieldExpressionDescriptor, ForeignKeyIdDescriptor)
):
# id 字段也添加描述符
setattr(cls, fname, FieldExpressionDescriptor(fname))
continue
if isinstance(field, (dj_models.ForeignKey, dj_models.OneToOneField)):
# FK 字段:类级别访问返回 FieldExpression实例级别访问返回关联对象
setattr(cls, fname, ForeignKeyIdDescriptor(fname))
else:
setattr(cls, fname, FieldExpressionDescriptor(fname))
class QModel(models.Model, metaclass=QModelBase):
"""QModel 兼容层基类,提供 SQLAlchemy 风格的链式 OOP 查询接口。
所有继承 QModel 的模型自动获得:
- ``Model.query`` : 返回 FluentQuery支持链式查询
- ``Model.field_name`` : 类级别访问返回 FieldExpression支持表达式查询
- ``Model.field_name`` : 实例级别访问返回字段值(与普通 Django Model 一致)
用法示例::
# 链式查询
orders = Dingdan.query.filter(zhuangtai=8).order_by('-create_time').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)
"""
class Meta:
abstract = True
@classproperty
def query(cls):
def query(cls) -> FluentQuery:
"""返回 FluentQuery 兼容层查询构建器,支持链式 OOP 查询。"""
return FluentQuery(cls)
@classmethod
def __class_getitem__(cls, item) -> FluentQuery:
"""支持 Model[condition] 语法,返回 filter 后的 FluentQuery。
用法::
Dingdan[Dingdan.zhuangtai == 8].order_by('-create_time').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)