tortoise / tortoise/tortoise-orm

[Bug] ForeignKey ignores to_field and forces lookup on default PK field, causing type/mapping errors

Open
#2,225 0 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
When using a ForeignKeyField with to_field pointing to a non-primary key field (e.g., app_id which is a UUID/CharField), Tortoise ORM fails to correctly resolve the column for lookups.

Instead of using the specified to_field (e.g., app_id), the ORM incorrectly defaults to the model's primary key name (e.g., id) and generates SQL using the ${fk_name}_id pattern. This causes the generated query to filter by the wrong column or even use the wrong data type (e.g., passing a UUID string to an integer-based ID column), leading to runtime type errors or incorrect results.

To Reproduce
Steps to reproduce:

  1. Define a model (Application) with a primary key (id) and a secondary unique field (app_id).

  2. Define a second model (TableB) with a ForeignKeyField pointing to Application, setting to_field="app_id".

  3. Perform a filter on TableB using an Application instance.

Minimal code snippet:

class Application(Model):
    id = fields.IntField(pk=True) # Default auto-increment PK
    app_id = fields.CharField(unique=True, max_length=50) # The custom field we want to link to

class TableB(Model):
    app = fields.ForeignKeyField(
        "models.Application", 
        to_field="app_id", 
        db_constraint=False
    )

# Execution
app_instance = await Application.get(app_id="550e8400-e29b-41d4-a716-446655440000")
# The query incorrectly generates: ... WHERE app_id = 1 (or tries to use the internal pk)
# instead of: ... WHERE app_app_id = '550e8400-e29b-41d4-a716-446655440000'
await TableB.filter(app=app_instance)

Expected behavior
The ORM should respect the to_field configuration. When filtering by a related model instance, it should resolve the link using the column mapping associated with to_field(app_app_id in the example above) rather than defaulting to ${fk_name}_id.

Additional context
This issue effectively makes using to_field for non-primary key associations impossible for query lookups.

It appears that the query builder incorrectly assumes that the related field is always the Primary Key of the target model, ignoring the to_field metadata in the relationship.

Tortoise ORM version: 1.1.7

Technical Analysis of the Bug
My initial investigation points to the _get_actual_filter_params method as a likely culprit, where the logic seems to prioritize pk over the explicitly defined to_field.

Specifically, when processing a ForeignKeyField or OneToOneField, the ORM uses the following logic to resolve the filter value:

https://github.com/tortoise/tortoise-orm/blob/bc4f971bb840c90af422108b8cf07a68c04cbd65/tortoise/expressions.py#L432-L462

The logic flaw:

  • The filter_value is hardcoded to retrieve value.pk.
  • It fails to check if the ForeignKeyField has a custom to_field attribute defined.

When to_field is specified (e.g., pointing to a UUID column), the ORM ignores this configuration and incorrectly extracts the primary key (id) instead, leading to type mismatches and invalid SQL filters.

Suggested Fix:
The implementation should inspect the field_object for a to_field attribute and use that to resolve the value from the value instance, falling back to .pk only if to_field is not defined.

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 _get_actual_filter_params in tortoise/expressions.py at the linked lines, then run the Application and TableB reproduction from the issue. Confirm that filtering with an Application instance uses the ForeignKeyField's to_field column rather than the target model's primary key, and add or update coverage for the expected query mapping.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.