Skip to content

Conversation

@asukaminato0721
Copy link
Contributor

@asukaminato0721 asukaminato0721 commented Jan 12, 2026

Summary

Fixes #2071

Implemented Django reverse relationship synthesis by scanning model field bindings in the module, inferring reverse accessors for ForeignKey/ManyToManyField (and OneToOneField), parsing related_name (including %(class)s/%(app_label)s), and adding a RelatedManager helper for reverse FK accessors.

Notes/limits:

Reverse accessors are synthesized only from relation fields declared in the same module and with literal related_name/None (dynamic names are skipped).

Cross-module reverse inference isn’t covered yet. supported.

Test Plan

Added assertions for reverse accessors

@meta-cla meta-cla bot added the cla signed label Jan 12, 2026
@asukaminato0721 asukaminato0721 marked this pull request as ready for review January 12, 2026 12:12
Copilot AI review requested due to automatic review settings January 12, 2026 12:12
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements support for Django reverse relationships, addressing issue #2071. The implementation synthesizes reverse accessors for ForeignKey, ManyToManyField, and OneToOneField by scanning model field bindings, inferring reverse accessors, parsing related_name (including template substitution for %(class)s and %(app_label)s), and adding a RelatedManager helper for reverse ForeignKey accessors.

Changes:

  • Added reverse relationship field synthesis for Django models
  • Implemented related_name template parsing with %(class)s and %(app_label)s support
  • Added RelatedManager type support for ForeignKey reverse accessors

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
pyrefly/lib/alt/class/django.rs Core implementation of reverse relationship synthesis including relation kind detection, related_name parsing with template substitution, and manager type resolution
pyrefly/lib/test/django/foreign_key.rs Added test assertion for ForeignKey reverse accessor (article_set) on Reporter model
pyrefly/lib/test/django/many_to_many.rs Added test assertion for ManyToManyField reverse accessor (books) on Author model

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

This comment has been minimized.

@migeed-z
Copy link
Contributor

Thank you so much! I will import and take a closer look.

@meta-codesync
Copy link

meta-codesync bot commented Jan 12, 2026

@migeed-z has imported this pull request. If you are a Meta employee, you can view this in D90513475.

Copy link
Contributor

@rchen152 rchen152 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review automatically exported from Phabricator review in Meta.

@migeed-z
Copy link
Contributor

Hello! we got to review the diff in more details. Here are some open questions:

  • The implementation requires iterating over every single ClassField binding. Can we do better? this would be expensive for large django models. Can we store do one pass in the bindings phase and collect ForeignKey, ManyToManyField, and OneToOneField assignments and store the names of those fields in class metadata and go from there?
  • Cross module support seems important to me on realistic codebases and ultimately the goal. Is this approach going in the right direction towards that?

@asukaminato0721
Copy link
Contributor Author

Moved Django reverse-relationship synthesis to a module-level binding/index so we compute reverse fields once per module (instead of scanning every KeyClassField for each model).

The binder now records candidate relation fields during the class-body pass, and the solver builds a reverse-relation index keyed by target class that get_django_model_synthesized_fields can query.

@github-actions

This comment has been minimized.

@asukaminato0721 asukaminato0721 marked this pull request as draft January 15, 2026 03:12
@asukaminato0721
Copy link
Contributor Author

Started cross‑module support by building a global reverse‑relation index once per solve and caching it in the thread state. The index merges each module’s KeyDjangoRelations result, so reverse accessors now come from all modules loaded in the current transaction (not just the current module).

This required exporting KeyDjangoRelations and adding LookupAnswer::modules() so the solver can enumerate available modules and combine their per‑module indexes.

@asukaminato0721 asukaminato0721 marked this pull request as ready for review January 15, 2026 03:28
@github-actions

This comment has been minimized.

@github-actions
Copy link

Diff from mypy_primer, showing the effect of this PR on open source code:

zulip (https://github.com/zulip/zulip)
- ERROR corporate/views/support.py:943:13-42: Object of class `RemoteZulipServer` has no attribute `remoterealm_set` [missing-attribute]
- ERROR zerver/actions/create_user.py:820:26-52: Object of class `UserProfile` has no attribute `direct_groups` [missing-attribute]
+ ERROR zerver/actions/create_user.py:833:28-55: Object of class `UserGroup` has no attribute `named_user_group` [missing-attribute]
- ERROR zerver/actions/uploads.py:136:44-66: Object of class `Message` has no attribute `attachment_set`
+ ERROR zerver/actions/uploads.py:136:44-66: Object of class `ScheduledMessage` has no attribute `attachment_set` [missing-attribute]
- Object of class `ScheduledMessage` has no attribute `attachment_set` [missing-attribute]
- ERROR zerver/actions/uploads.py:145:9-31: Object of class `Message` has no attribute `attachment_set`
+ ERROR zerver/actions/uploads.py:145:9-31: Object of class `ScheduledMessage` has no attribute `attachment_set` [missing-attribute]
- Object of class `ScheduledMessage` has no attribute `attachment_set` [missing-attribute]
- ERROR zerver/actions/uploads.py:157:35-57: Object of class `Message` has no attribute `attachment_set`
+ ERROR zerver/actions/uploads.py:157:35-57: Object of class `ScheduledMessage` has no attribute `attachment_set` [missing-attribute]
- Object of class `ScheduledMessage` has no attribute `attachment_set` [missing-attribute]
- ERROR zerver/actions/users.py:433:35-61: Object of class `UserProfile` has no attribute `direct_groups` [missing-attribute]
- ERROR zerver/lib/user_groups.py:778:17-43: Object of class `UserProfile` has no attribute `direct_groups` [missing-attribute]
- ERROR zerver/lib/user_groups.py:862:21-47: Object of class `UserProfile` has no attribute `direct_groups` [missing-attribute]
- ERROR zerver/lib/widget.py:142:12-34: Object of class `Message` has no attribute `submessage_set` [missing-attribute]
- ERROR zerver/management/commands/export_search.py:300:35-57: Object of class `Message` has no attribute `attachment_set` [missing-attribute]
- ERROR zerver/models/realms.py:1377:17-38: Object of class `Realm` has no attribute `realmdomain_set` [missing-attribute]
+ ERROR zerver/models/realms.py:1377:16-76: No matching overload found for function `list.__init__` called with arguments: (QuerySet[RealmDomain, dict[str, Any]]) [no-matching-overload]
- ERROR zerver/tests/test_custom_profile_data.py:460:26-66: Object of class `UserProfile` has no attribute `customprofilefieldvalue_set` [missing-attribute]
- ERROR zerver/tests/test_custom_profile_data.py:465:26-66: Object of class `UserProfile` has no attribute `customprofilefieldvalue_set` [missing-attribute]
- ERROR zerver/tests/test_message_fetch.py:5655:25-43: Object of class `Message` has no attribute `attachment_set` [missing-attribute]
- ERROR zerver/tests/test_message_fetch.py:5656:26-44: Object of class `Message` has no attribute `attachment_set` [missing-attribute]
- ERROR zerver/tests/test_message_fetch.py:5660:25-43: Object of class `Message` has no attribute `attachment_set` [missing-attribute]
- ERROR zerver/tests/test_message_fetch.py:5661:26-44: Object of class `Message` has no attribute `attachment_set` [missing-attribute]
- ERROR zerver/tests/test_message_fetch.py:5665:26-44: Object of class `Message` has no attribute `attachment_set` [missing-attribute]
- ::error file=corporate/views/support.py,line=943,col=13,endLine=943,endColumn=42,title=Pyrefly missing-attribute::Object of class `RemoteZulipServer` has no attribute `remoterealm_set`
- ::error file=zerver/actions/create_user.py,line=820,col=26,endLine=820,endColumn=52,title=Pyrefly missing-attribute::Object of class `UserProfile` has no attribute `direct_groups`
+ ::error file=zerver/actions/create_user.py,line=833,col=28,endLine=833,endColumn=55,title=Pyrefly missing-attribute::Object of class `UserGroup` has no attribute `named_user_group`
- ::error file=zerver/actions/uploads.py,line=136,col=44,endLine=136,endColumn=66,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `attachment_set`%0AObject of class `ScheduledMessage` has no attribute `attachment_set`
+ ::error file=zerver/actions/uploads.py,line=136,col=44,endLine=136,endColumn=66,title=Pyrefly missing-attribute::Object of class `ScheduledMessage` has no attribute `attachment_set`
- ::error file=zerver/actions/uploads.py,line=145,col=9,endLine=145,endColumn=31,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `attachment_set`%0AObject of class `ScheduledMessage` has no attribute `attachment_set`
+ ::error file=zerver/actions/uploads.py,line=145,col=9,endLine=145,endColumn=31,title=Pyrefly missing-attribute::Object of class `ScheduledMessage` has no attribute `attachment_set`
- ::error file=zerver/actions/uploads.py,line=157,col=35,endLine=157,endColumn=57,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `attachment_set`%0AObject of class `ScheduledMessage` has no attribute `attachment_set`
+ ::error file=zerver/actions/uploads.py,line=157,col=35,endLine=157,endColumn=57,title=Pyrefly missing-attribute::Object of class `ScheduledMessage` has no attribute `attachment_set`
- ::error file=zerver/actions/users.py,line=433,col=35,endLine=433,endColumn=61,title=Pyrefly missing-attribute::Object of class `UserProfile` has no attribute `direct_groups`
- ::error file=zerver/lib/user_groups.py,line=778,col=17,endLine=778,endColumn=43,title=Pyrefly missing-attribute::Object of class `UserProfile` has no attribute `direct_groups`
- ::error file=zerver/lib/user_groups.py,line=862,col=21,endLine=862,endColumn=47,title=Pyrefly missing-attribute::Object of class `UserProfile` has no attribute `direct_groups`
- ::error file=zerver/lib/widget.py,line=142,col=12,endLine=142,endColumn=34,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `submessage_set`
- ::error file=zerver/management/commands/export_search.py,line=300,col=35,endLine=300,endColumn=57,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `attachment_set`
- ::error file=zerver/models/realms.py,line=1377,col=17,endLine=1377,endColumn=38,title=Pyrefly missing-attribute::Object of class `Realm` has no attribute `realmdomain_set`
+ ::error file=zerver/models/realms.py,line=1377,col=16,endLine=1377,endColumn=76,title=Pyrefly no-matching-overload::No matching overload found for function `list.__init__` called with arguments: (QuerySet[RealmDomain, dict[str, Any]])%0A  Possible overloads:%0A  () -> None%0A  (iterable: Iterable[RealmDomainDict], /) -> None [closest match]
- ::error file=zerver/tests/test_custom_profile_data.py,line=460,col=26,endLine=460,endColumn=66,title=Pyrefly missing-attribute::Object of class `UserProfile` has no attribute `customprofilefieldvalue_set`
- ::error file=zerver/tests/test_custom_profile_data.py,line=465,col=26,endLine=465,endColumn=66,title=Pyrefly missing-attribute::Object of class `UserProfile` has no attribute `customprofilefieldvalue_set`
- ::error file=zerver/tests/test_message_fetch.py,line=5655,col=25,endLine=5655,endColumn=43,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `attachment_set`
- ::error file=zerver/tests/test_message_fetch.py,line=5656,col=26,endLine=5656,endColumn=44,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `attachment_set`
- ::error file=zerver/tests/test_message_fetch.py,line=5660,col=25,endLine=5660,endColumn=43,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `attachment_set`
- ::error file=zerver/tests/test_message_fetch.py,line=5661,col=26,endLine=5661,endColumn=44,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `attachment_set`
- ::error file=zerver/tests/test_message_fetch.py,line=5665,col=26,endLine=5665,endColumn=44,title=Pyrefly missing-attribute::Object of class `Message` has no attribute `attachment_set`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Django reverse relationships

3 participants