Skip to content

Conversation

@Hercilio1
Copy link

I want to add new interfaces to a Page model, but the problem is that those interfaces import model files to collect different sets of data from them.

The issue is that graphql_interfaces = [CustomInterface] requires importing CustomInterface inside the model file, which causes the error AppRegistryNotReady("Models aren't loaded yet.").

The load_type_fields runs at the right time, but I can’t define graphql_interfaces without importing the interface in the models file.

The fix would be something like graphql_interfaces = ["apps.custom.graphql.object_types.CustomInterface"], so I implemented it this way.

@zerolab
Copy link
Member

zerolab commented Mar 26, 2025

Thank you. Can you add a test or two?

@Hercilio1
Copy link
Author

@zerolab , done :)

@Hercilio1
Copy link
Author

@zerolab can we merge this feat?

@Hercilio1 Hercilio1 force-pushed the feat/model_interfaces_flexibility branch from 579ae8b to 874e3d3 Compare August 14, 2025 22:19
@zerolab zerolab requested a review from jams2 August 15, 2025 08:49
Copy link
Collaborator

@jams2 jams2 left a comment

Choose a reason for hiding this comment

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

Thanks @Hercilio1, some feedback follows.

If possible, it would be helpful to hear more about your use-case. When you say your interfaces depend on data in models, what exactly are you referring to? Could you share a reproducible example?

I don't have a strong objection to allowing interfaces to be declared as strings (or even callables), but I wonder if the issue might be solved by different architecture or looser coupling between components.

Comment on lines 272 to 278
interface_classes = list(getattr(cls, "graphql_interfaces", ()))
for i, interface_class in enumerate(interface_classes):
if isinstance(interface_class, str):
interface_classes[i] = import_string(interface_class)
interface_classes = tuple(interface_classes)

interfaces = {interface, *interface_classes}
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's some unnecessary construction of intermediate containers here:

  1. cast interface_classes to a list;
  2. mutate it;
  3. cast it to a tuple;
  4. use the tuple elements in a new set.

The desired behaviour might be expressed more simply, something like:

Suggested change
interface_classes = list(getattr(cls, "graphql_interfaces", ()))
for i, interface_class in enumerate(interface_classes):
if isinstance(interface_class, str):
interface_classes[i] = import_string(interface_class)
interface_classes = tuple(interface_classes)
interfaces = {interface, *interface_classes}
interface_classes = getattr(cls, "graphql_interfaces", ())
interfaces = {
import_string(i) if isinstance(i, str) else i for i in interface_classes
}
interfaces.add(interface)

settings, "WAGTAILDOCS_DOCUMENT_MODEL", "wagtaildocs.Document"
)


Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion: rather than amending the existing tests to include the new functionality, could we add new tests that cover new behaviour and its edge cases? E.g. what happens when a string doesn't resolve?

@Hercilio1
Copy link
Author

bj

@jams2 , this issue arises because the project I’m integrating with wagtail-grapple has a large number of GraphQL interfaces. Many of these interfaces are not directly tied to a single model but are complementary features that extend the functionality of Wagtail pages.

The challenge is that some of these interfaces need to import their related models to implement custom business logic. If I import those interfaces directly inside the models.py file (so I can list them in the graphql_interfaces attribute), Django attempts to load the model before the app registry is ready, which raises the AppRegistryNotReady("Models aren't loaded yet.") exception.

By allowing interfaces to be referenced as strings (e.g. "apps.custom.graphql.object_types.CustomInterface"), we avoid the premature import problem. This approach is harmless, keeps the architecture flexible, and provides a clean solution for projects with multiple, interdependent interfaces.

I also addressed all your reviews :)

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.

4 participants