diff --git a/gvsdsdk/model_base.py b/gvsdsdk/model_base.py index 34b2d9f..9170247 100644 --- a/gvsdsdk/model_base.py +++ b/gvsdsdk/model_base.py @@ -352,14 +352,7 @@ class FieldExpressionDescriptor: class QModelBase(models.base.ModelBase): def __new__(mcs, name, bases, namespace, **kwargs): - from django.db import models as dj_models - for attr_name, attr_val in list(namespace.items()): - if isinstance(attr_val, dj_models.Field) and not attr_val.db_column: - # ForeignKey/OneToOneField 的数据库列名是 attr_name + '_id' - if isinstance(attr_val, (dj_models.ForeignKey, dj_models.OneToOneField)): - attr_val.db_column = attr_name + '_id' - else: - attr_val.db_column = attr_name + # 不再强制设置 db_column —— Django 会自动处理 # 动态设置 managed:子服务器可设 GVSDSDK_MANAGE_TABLES=True 让 gvsdsdk 管理表 meta = namespace.get('Meta') @@ -373,40 +366,70 @@ class QModelBase(models.base.ModelBase): cls = super().__new__(mcs, name, bases, namespace, **kwargs) - # 模型创建后,自动为所有字段添加描述符,支持 Model.field_name 链式 OOP + # 模型创建后,包装字段描述符以支持类级别 FieldExpression if not cls._meta.abstract: - _attach_field_descriptors(cls) + _wrap_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 +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 - # 检查是否已有同名描述符(避免覆盖手动定义的) - 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)) + + original_desc = cls.__dict__.get(fname) + if original_desc is None: continue - if isinstance(field, (dj_models.ForeignKey, dj_models.OneToOneField)): - # FK 字段:类级别访问返回 FieldExpression,实例级别访问返回关联对象 - setattr(cls, fname, ForeignKeyIdDescriptor(fname)) + # 只包装已知的 Django 描述符类型 + if isinstance(original_desc, (DeferredAttribute, ForwardManyToOneDescriptor, + ForwardOneToOneDescriptor)): + wrapper = _FieldExpressionWrapper(original_desc, cls, fname) + setattr(cls, fname, wrapper) + + +class _FieldExpressionWrapper: + """包装 Django 字段描述符,类级别返回 FieldExpression,实例级别委托给原生描述符。""" + + def __init__(self, original_descriptor, model_class, field_name): + self._original = original_descriptor + self._model = model_class + self._field_name = field_name + self._has_set = 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): + if self._has_set: + # FK/O2O 描述符有 __set__ + self._original.__set__(obj, value) else: - setattr(cls, fname, FieldExpressionDescriptor(fname)) + # 普通字段(DeferredAttribute)没有 __set__,直接写 __dict__ + obj.__dict__[self._field_name] = value class QModel(models.Model, metaclass=QModelBase):