修复了 QModel 链式表存在的一些问题
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user