Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ManyToManyField.through no longer has _default_manager attribute #1742

Closed
intgr opened this issue Sep 28, 2023 · 1 comment · Fixed by #1745
Closed

ManyToManyField.through no longer has _default_manager attribute #1742

intgr opened this issue Sep 28, 2023 · 1 comment · Fixed by #1745
Labels
bug Something isn't working regression Behavior has changed for worse with a release

Comments

@intgr
Copy link
Collaborator

intgr commented Sep 28, 2023

Bug report

Following the call for testing in #1740, I tried django-stubs git master and ran into this issue.

For example if MyModel has relation manytomany = models.ManyToManyField(OtherModel)

What's wrong

When accessing MyModel.manytomany.through._default_manager, I'm getting error:

"type[MyModel_manytomany]" has no attribute "_default_manager" [attr-defined]

MyModel.manytomany.through.objects works fine, however.

I can also reproduce this after these changes in django-stubs test suite:

diff --git a/tests/typecheck/fields/test_related.yml b/tests/typecheck/fields/test_related.yml
index 8f21557e..8d0ce289 100644
--- a/tests/typecheck/fields/test_related.yml
+++ b/tests/typecheck/fields/test_related.yml
@@ -337,6 +337,7 @@
         from myapp.models import User
         reveal_type(User().friends)  # N: Revealed type is "django.db.models.fields.related_descriptors.ManyRelatedManager[myapp.models.User]"
         reveal_type(User.friends.through.objects.get())  # N: Revealed type is "myapp.models.User_friends"
+        reveal_type(User.friends.through._default_manager)
         reveal_type(User.friends.through().from_user)  # N: Revealed type is "myapp.models.User"
         reveal_type(User.friends.through().from_user_id)  # N: Revealed type is "builtins.int"
         reveal_type(User.friends.through().to_user)  # N: Revealed type is "myapp.models.User"
E   pytest_mypy_plugins.utils.TypecheckAssertionError: Invalid output: 
E   Actual:
E     ...
E     main:4: error: "Type[User_friends]" has no attribute "_default_manager" (diff)

System information

  • OS: macOS 13.6
  • python version: 3.11.5
  • django version: 4.1.11
  • mypy version: 1.5.1
  • django-stubs version: (git master, commit 84a085c)
  • django-stubs-ext version: 4.2.2
@intgr intgr added bug Something isn't working regression Behavior has changed for worse with a release labels Sep 28, 2023
@flaeppe
Copy link
Member

flaeppe commented Sep 28, 2023

Aha, nice find! _default_manager is a forgotten symbol:

through_model = self.add_new_class_for_current_module(
through_model_name, bases=[Instance(model_base, [])]
)
# We attempt to be a bit clever here and store the generated through model's fullname in
# the metadata of the class containing the 'ManyToManyField' call expression, where its
# identifier is the field name of the 'ManyToManyField'. This would allow the containing
# model to always find the implicit through model, so that it doesn't get lost.
model_metadata = helpers.get_django_metadata(self.model_classdef.info)
model_metadata.setdefault("m2m_throughs", {})
model_metadata["m2m_throughs"][m2m_field_name] = through_model.fullname
# Add a 'pk' symbol to the model class
helpers.add_new_sym_for_info(
through_model, name="pk", sym_type=self.default_pk_instance.copy_modified()
)
# Add an 'id' symbol to the model class
helpers.add_new_sym_for_info(
through_model, name="id", sym_type=self.default_pk_instance.copy_modified()
)
# Add the foreign key to the model containing the 'ManyToManyField' call:
# <containing_model> or from_<model>
from_name = (
f"from_{self.model_classdef.name.lower()}" if args.to.self else self.model_classdef.name.lower()
)
helpers.add_new_sym_for_info(
through_model,
name=from_name,
sym_type=Instance(
fk_field,
[
helpers.convert_any_to_type(fk_set_type, Instance(self.model_classdef.info, [])),
helpers.convert_any_to_type(fk_get_type, Instance(self.model_classdef.info, [])),
],
),
)
# Add the foreign key's '_id' field: <containing_model>_id or from_<model>_id
helpers.add_new_sym_for_info(through_model, name=f"{from_name}_id", sym_type=from_pk.copy_modified())
# Add the foreign key to the model on the opposite side of the relation
# i.e. the model given as 'to' argument to the 'ManyToManyField' call:
# <other_model> or to_<model>
to_name = f"to_{args.to.model.type.name.lower()}" if args.to.self else args.to.model.type.name.lower()
helpers.add_new_sym_for_info(
through_model,
name=to_name,
sym_type=Instance(
fk_field,
[
helpers.convert_any_to_type(fk_set_type, args.to.model),
helpers.convert_any_to_type(fk_get_type, args.to.model),
],
),
)
# Add the foreign key's '_id' field: <other_model>_id or to_<model>_id
other_pk = self.get_pk_instance(args.to.model.type)
helpers.add_new_sym_for_info(through_model, name=f"{to_name}_id", sym_type=other_pk.copy_modified())
# Add a manager named 'objects'
helpers.add_new_sym_for_info(
through_model,
name="objects",
sym_type=Instance(manager_info, [Instance(through_model, [])]),
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working regression Behavior has changed for worse with a release
Development

Successfully merging a pull request may close this issue.

2 participants