tortoise / tortoise-orm

Familiar asyncio ORM for python, built with relations in mind
https://tortoise.github.io
Apache License 2.0
4.58k stars 378 forks source link

Why I can't get pk value after Model create ? #1484

Open miss85246 opened 1 year ago

miss85246 commented 1 year ago

Describe the bug I'm write a BaseModel and rewrite the create method, because I want auto create audit record after I create a new record. but I found I can't get instance's pk after I create it. It's will be null ever for my audit record id field. even stranger is I can print instance's id before I create audit instance. why and what should I do ?

To Reproduce This is a simple case:

# model.py

class BaseModel(models.Model):
    """
    基础模型, 所有的模型都应该继承自该模型
    """
    audit_model = None
    all_objects = models.Manager()

    id = BigIntField(pk=True, description="主键ID")
    create_time = StrDatetimeField(auto_now_add=True, description="创建时间")
    update_time = StrDatetimeField(auto_now=True, description="更新时间")
    delete_time = StrDatetimeField(null=True, description="删除时间")

    class Meta:
        abstract = True
        auditable = False
        manager = AuditGenerateManager()

    @classmethod
    async def create(cls: Type[MODEL], using_db: Optional[BaseDBAsyncClient] = None, **kwargs: Any) -> MODEL:
        instance = await super().create(**kwargs)

        if cls.audit_model:

            await cls.audit_model.create(
                **kwargs,
                id=instance.pk,
                operation="create",
                operation_user="system",
                operation_ip="localhost",
                operation_device="system script",
            )
        return instance

this is auditModel generate file:

# modelAudit.py
from copy import deepcopy

from tortoise.fields.relational import ForeignKeyFieldInstance, ManyToManyFieldInstance, OneToOneFieldInstance

from . import modelApplication, modelAuth
from .model import BaseAuditModel
from .model import CharField

for model_module in [modelAuth, modelApplication]:
    for model in model_module.__dict__.values():
        if meta := getattr(model, "Meta", None):
            if getattr(meta, "auditable", False) and not getattr(model, 'abstract', False):
                class_args, meta_class_args = {}, {}
                audit_name, meta_name = f'Audit{model.__name__}', f'Audit{model.__name__}Meta'
                meta_table, meta_table_description = f"tortoise_audit_{model._meta.db_table}", f"{model._meta.table_description}'s audit model"
                meta_class_args.update({'table': meta_table, 'table_description': meta_table_description})
                for name, field in model._meta.fields_map.items():
                    new_field = deepcopy(field)
                    new_field.unique, new_field.index, new_field.pk, new_field.null = False, False, False, True
                    if isinstance(field, (ForeignKeyFieldInstance, ManyToManyFieldInstance, OneToOneFieldInstance)) or name == "password":
                        new_field = CharField(max_length=255, default="", description=f"{field.description}")
                    class_args.update({name: new_field})

                meta_class = type(meta_name, (), meta_class_args)
                class_args.update({'Meta': meta_class})
                audit_model = type(audit_name, (BaseAuditModel,), class_args)
                model.audit_model = audit_model
                globals().update({audit_name: audit_model})

at last, this is my business logic Model :

# modelAuth.py

class AuthUser(BaseModel):
    name = CharField(max_length=16, default="", description="用户姓名")
    email = CharField(max_length=128, null=True, description="用户邮箱")
    mobile = CharField(max_length=16, null=True, description="用户手机号")
    password = PasswordField(max_length=255, null=True, description="用户密码")
    is_super = BooleanField(default=False, description="是否为超级管理员")
    last_login = DatetimeField(null=True, description="最后登录时间")

    class Meta:
        table = 'tortoise_auth_user'
        table_description = "用户表, 用于记录用户信息"
        auditable = True
        constraints = ["CHECK (email IS NOT NULL OR mobile IS NOT NULL)"]
        unique_together = [("mobile", "delete_time"), ("email", "delete_time")]

# ... more other models 

Expected behavior In my mind, after I await super create instance, I should got this pk, and pass it to my audit model.

Additional context python version: 3.11.4 system os : MacOS tortoise-orm version: 0.20.0 database: mysql 8.0

I really need help, look forward your response, thanks!

i701 commented 1 year ago

I am curious. Where is StrDatetimeField coming from here?

miss85246 commented 1 year ago

I am curious. Where is StrDatetimeField coming from here?

just a custom field, I do some custom serialize base on datetime field 🥹

i701 commented 1 year ago

I am curious. Where is StrDatetimeField coming from here?

just a custom field, I do some custom serialize base on datetime field 🥹

Oooh. Wait do you think a custom field like that will fix this issue? #1482