tortoise / tortoise/tortoise-orm
[Bug] ForeignKey ignores to_field and forces lookup on default PK field, causing type/mapping errors
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:
-
Define a model (Application) with a primary key (id) and a secondary unique field (app_id).
-
Define a second model (TableB) with a ForeignKeyField pointing to Application, setting
to_field="app_id". -
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:
The logic flaw:
- The filter_value is hardcoded to retrieve
value.pk. - It fails to check if the ForeignKeyField has a custom
to_fieldattribute 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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