tortoise / tortoise/tortoise-orm

Resolved reverse reference returning a QuerySet instead the ObjectType

Open
#967 4 comments 1 reaction 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

Given the model with foreignKey:

Toy --(builder)--> Person

With related name:

Person --(toys_built) --> Toy

Defined with this code:

class Toy(tortoise.models.Model):
    id = tortoise.fields.IntField(pk=True)
    name = tortoise.fields.CharField(max_length=32)
    builder = tortoise.fields.ForeignKeyField("models.Person", related_name='toys_built')

class Person(tortoise.models.Model):
    id = tortoise.fields.IntField(pk=True)
    name = tortoise.fields.CharField(max_length=32)

When resolving the given path

Person --(toys_built)--> Toy --(builder)--> ???

Returns a QuerySet instead a Person as expected.

To Reproduce

import tortoise

class Toy(tortoise.models.Model):
    id = tortoise.fields.IntField(pk=True)
    name = tortoise.fields.CharField(max_length=32)
    builder = tortoise.fields.ForeignKeyField("models.Person", related_name='toys_built')

class Person(tortoise.models.Model):
    id = tortoise.fields.IntField(pk=True)
    name = tortoise.fields.CharField(max_length=32)


async def main():
    await tortoise.Tortoise.init(db_url='sqlite://:memory:', modules={'models': ["__main__"]})
    await tortoise.Tortoise.generate_schemas()

    gepetto = await Person.create(name="Gepheto")
    pinocchio = await Toy.create(name="Pinocchio", builder=gepetto)

    gepetto_toys_built = await gepetto.toys_built.all()

    for toy in gepetto_toys_built:
        print(toy.name)

    print(f"EXPECTED PERSON {gepetto}")
    print(f"EXPECTED PERSON {pinocchio.builder}")
    for toy in gepetto_toys_built:
        print(type(toy), toy.name)
        print(f"EXPECTED PERSON {toy.builder}")

    await tortoise.Tortoise.close_connections()

if __name__ == '__main__':
    tortoise.run_async(main())

Expected behavior

Since the object returned is of type class Toy, I would expect to be able to resolve toy.builder, exactly like the instance pinocchio.builder. But for some reason it returns a querySet. As we can see, the last printf returns a QuerySet but I expected a Person, as the output shown below

Pinocchio
EXPECTED PERSON <Person>
EXPECTED PERSON <Person>
<class '__main__.Toy'> Pinocchio
EXPECTED PERSON <tortoise.queryset.QuerySet object at 0x7fab33055d00>

Additional context

Python 3.9.7
Tortoise 0.17.8

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

Run the provided SQLite reproduction with the Toy and Person models, then trace how the reverse relation toys_built and the forward builder relation are resolved. Add a regression test showing that toy.builder returns a Person rather than a QuerySet, and verify the existing relation tests still pass.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.