1

In my application's models I need a way of linking Problems and Solutions -- every Problem can have multiple Solutions and a given Solution can map back to multiple Problems.

Solution is an abstract base class, since there can be many varieties of Solutions. So, I figured out I need a mapping table ProblemSolutionMapping which uses a GenericForeignKey to accommodate all those child classes. But I'm trying to figure out how to limit the classes to just the children of Solutions and not all the classes available in the whole application, which is what is currently happening.

# Thanks to http://stackoverflow.com/a/23555691/1149759
class Solution(models.Model):
    ...
    @classmethod
    def get_subclasses(cls):
        content_types = ContentType.objects.filter(app_label=cls._meta.app_label)
        models = [ct.model_class() for ct in content_types]
        return [model for model in models
                if (model is not None and
                    issubclass(model, cls) and
                    model is not cls)]

    class Meta:
        abstract = True


class ProblemSolutionMapping(models.Model):
    problem = models.ForeignKey(Problem)
    content_type = models.ForeignKey(ContentType,
        limit_choices_to=Solution.get_subclasses()) # <==== This is the issue
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

The issue is that when I start up my Django app, the call to ContentType.objects.filter(app_label=cls._meta.app_label) throws the error:

django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.

Not sure what to do -- I tried making the mapping table the last one in the relevant models file (all the child classes are defined above it in the same file), but it made no difference. Is this something that I have to move into the admin form? Or is there some other way to do this at the model level?

(Django 1.9, in case it matters.)

Thanks in advance for your help!

TAH
  • 1,658
  • 1
  • 19
  • 37

2 Answers2

1

Refrecing to a model during import time is no longer supported from django 1.7. You should use your models after all application are loaded. So you should either Staticly pass a list to your limit_choices_to or Use Q object like this:

limit_choices_to=models.Q(app_label = 'app', model = 'a') | models.Q(app_label = 'app', model = 'b')

Also you can limit what shows to user in form level

Community
  • 1
  • 1
Mehran Meidani
  • 341
  • 1
  • 13
  • The issue is that I can't statically pass a list to `limit_choice_to` bc of the way the child classes of `Solution` are handled. Thanks, though. – TAH Jan 05 '16 at 06:13
1

So I arrived here looking for the answer. Based on Mehran's post, I developed the below approach which is similar to yours. Instead the limit_choice_to calls a method that returns a runtime created Q object.

Below is the part that is similar to your get_subclasses.

def get_subclasses(cls, *args, **kwargs):
    for app_config in apps.get_app_configs():
        for app_model in app_config.get_models():
            model_classes = [c.__name__ for c in inspect.getmro(app_model)]
            if cls.__name__ in model_classes:
                yield app_model

This creates the Q filter(s) for us (in my implementation, this is just a plain old method not attached to any class, but I suppose it could be):

def get_content_choices():
    query_filter = None

    for cls in Solution.get_subclasses():

        app_label, model = cls._meta.label_lower.split('.')
        current_filter = models.Q(app_label=app_label, model=model)

        if query_filter is None:
            query_filter = current_filter
        else:
            query_filter |= current_filter

    return query_filter

And finally, in our model:

class ProblemSolutionMapping(models.Model):
    ...
    content_type = models.ForeignKey(ContentType, limit_choices_to=get_content_choices())
    ...
Edward Z.
  • 13
  • 3