tortoise / tortoise/tortoise-orm

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

Open
#1,484 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
5.6k
Forks
516
Avg merge
2d 21h
Merged PRs (30d)
9

Description

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!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the BaseModel.create override in model.py, then trace the generated audit models in modelAudit.py and AuthUser in modelAuth.py. Reproduce the reported behavior with Tortoise ORM 0.20.0 and MySQL 8.0, focusing on the values passed to audit_model.create after super().create. Done means the primary-key behavior is explained and the supported create/audit flow is verified or documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
mysql, python
Domain
backend, database
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.