Opened 8 years ago
Last modified 10 months ago
#27460 new New feature
Allow declaring a GenericRelation from an abstract model to another abstract model
Reported by: | Wolfgang Grim | Owned by: | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 1.10 |
Severity: | Normal | Keywords: | GenericRelation, Abstract, ContentFramework |
Cc: | Alex Hill, Sage Abdullah | Triage Stage: | Accepted |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Creating the following models does work in Django 1.9, however in Django 1.10 the following error is raised as soon as the class that inherits is created (in the example as soon as class DetailTest is created):
<function make_generic_foreign_order_accessors at 0x7f5444873488>: (models.E022) <function make_generic_foreign_order_accessors at 0x7f5444873488> contains a lazy reference to testac.abstractmaster, but app 'testac' doesn't provide model 'abstractmaster'. testac.DetailTest.relatedMasterModel: (fields.E307) The field testac.DetailTest.relatedMasterModel was declared with a lazy reference to 'testac.abstractmaster', but app 'testac' doesn't provide model 'abstractmaster'.
models.py
from __future__ import unicode_literals from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType class AbstractMaster (models.Model): class Meta: abstract=True detailModel_type = models.ForeignKey(ContentType, blank=True, null=True) detailModel_id = models.PositiveIntegerField(blank=True, null=True) detail_Model = GenericForeignKey('detailModel_type', 'detailModel_id') class AbstractDetail (models.Model): class Meta: abstract=True masterModel_type = models.ForeignKey(ContentType, blank=True, null=True) masterModel_id = models.PositiveIntegerField(blank=True, null=True) master_Model = GenericForeignKey('masterModel_type', 'masterModel_id') relatedMasterModel = GenericRelation(AbstractMaster, object_id_field='detailModel_id', content_type_field='detailModel_id', related_query_name='%(app_label)s_%(class)s_detail_model') class MasterTest (AbstractMaster): pass class DetailTest (AbstractDetail): pass
Change History (12)
comment:1 by , 8 years ago
Cc: | added |
---|---|
Component: | Uncategorized → Core (Other) |
comment:2 by , 8 years ago
I'm pretty sure this is just a more explicit failure of something that didn't work before. You can create the models as described, but see it blow up in 1.9 in the sample below when you actually try to use it.
I might be thinking about this wrong, but what would a GenericRelation to an abstract model mean? Which model would queries on the field yield instances of?
Wolfgang - can you provide an example of how you were successfully using this in 1.9?
>>> from ag.models import MasterTest, DetailTest >>> mt = MasterTest.objects.create() >>> dt = DetailTest.objects.create() >>> mt.detail_Model = dt >>> mt.save() >>> dt.master_Model = mt >>> dt.save() >>> >>> dt.relatedMasterModel.all() Traceback (most recent call last): File "/home/alexh/djexperiments/env/lib/python3.5/site-packages/django/core/management/commands/shell.py", line 69, in handle self.run_shell(shell=options['interface']) File "/home/alexh/djexperiments/env/lib/python3.5/site-packages/django/core/management/commands/shell.py", line 61, in run_shell raise ImportError ImportError During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<console>", line 1, in <module> File "/home/alexh/djexperiments/env/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py", line 468, in __get__ return self.related_manager_cls(instance) File "/home/alexh/djexperiments/env/lib/python3.5/site-packages/django/utils/functional.py", line 33, in __get__ res = instance.__dict__[self.name] = self.func(instance) File "/home/alexh/djexperiments/env/lib/python3.5/site-packages/django/contrib/contenttypes/fields.py", line 448, in related_manager_cls self.rel.model._default_manager.__class__, AttributeError: type object 'AbstractMaster' has no attribute '_default_manager' >>>
comment:3 by , 8 years ago
Hmm I also don't get it to run now - But I can explain what I want to achieve:
models.py
from __future__ import unicode_literals from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType class AbstractMaster (models.Model): class Meta: abstract=True detailModel_type = models.ForeignKey(ContentType, blank=True, null=True) detailModel_id = models.PositiveIntegerField(blank=True, null=True) detail_Model = GenericForeignKey('detailModel_type', 'detailModel_id') class AbstractDetail (models.Model): class Meta: abstract=True name = models.CharField (max_length=255, null=True) masterModel_type = models.ForeignKey(ContentType, blank=True, null=True) masterModel_id = models.PositiveIntegerField(blank=True, null=True) master_Model = GenericForeignKey('masterModel_type', 'masterModel_id') relatedMasterModel = GenericRelation(AbstractMaster, object_id_field='detailModel_id', content_type_field='detailModel_id', related_query_name='%(app_label)s_%(class)s_detail_model') class MasterTest (AbstractMaster): pass class DetailTest (AbstractDetail): pass
python manage.py shell
from testac.models import MasterTest, DetailTest mt = MasterTest.objects.create() dt = DetailTest.objects.create(name="Test") mt.detail_Model = dt mt.save() dt.master_Model = mt dt.save() mt.detail_Model.name # Out[8]: 'Test' (So this is working) MasterTest.objects.filter(detail_Model__name="Test") # Throws: FieldError: Field 'detail_Model' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.
comment:4 by , 8 years ago
If the use case is invalid, perhaps the error message about " app 'testac' doesn't provide model 'abstractmaster'" could be improved with an explanation about abstract models?
comment:5 by , 8 years ago
Hi Tim,
The message when you try to declare a normal relation to an abstract model is this:
ag.DetailTest.something: (fields.E300) Field defines a relation with model 'AbstractSomething', which is either not installed, or is abstract.
I'll look at how we can catch the GenericRelation case in the same branch, because that's much nicer.
Wolfgang: the trend in Django is to alert the user to problems earlier rather than later, and I'm quite sure that's what's happening here. There hasn't actually been any change in functionality since 1.9.
When you do MasterTest.objects.filter(detail_Model__name="Test")
, Django doesn't know which table to look for a matching "name" field in, because the relation is generic and that information is stored in the database. It would technically be possible to implement this, and return a heterogeneous collection of objects that inherit from the abstract model, but the code would be messy, it would be completely new ground for the Django ORM, and for such a corner case I just don't think it would be accepted.
There are third-party libraries that provide more polymorphism in Django, and I'd recommend looking into those to see if they meet your needs. Here are a couple:
https://django-polymorphic.readthedocs.io/en/stable/
https://django-model-utils.readthedocs.io/en/latest/managers.html#inheritancemanager
comment:6 by , 8 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
Tim: should I accept this and change its description to indicate that the error message needs improvement, or close it and create a new ticket?
comment:7 by , 8 years ago
Hello! At least the following should work right?
from __future__ import unicode_literals from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType class AbstractDetail (models.Model): class Meta: abstract=True relatedTrackModel = GenericRelation("TrackDetail", object_id_field='detailModel_id', content_type_field='detailModel_id', related_query_name='%(app_label)s_%(class)s_detail_model') class TrackDetail (models.Model): detailModel_type = models.ForeignKey(ContentType, blank=True, null=True) detailModel_id = models.PositiveIntegerField(blank=True, null=True) detail_Model = GenericForeignKey('detailModel_type', 'detailModel_id') added = models.DateTimeField(null=True) class DetailTest (AbstractDetail): name = models.CharField(max_length=200, null=True) pass
shell:
from testac.models import DetailTest, TrackDetail from django.utils import timezone dt = DetailTest.objects.create(name="name1") track = TrackDetail.objects.create() track.detail_Model=dt track.added = timezone.now() x = TrackDetail.objects.filter(testac_detailtest_detail_model__name="name") FieldError: Cannot resolve keyword 'testac_detailtest_detail_model' into field. Choices are: %(app_label)s_%(class)s_detail_model, added, detailModel_id, detailModel_type, detailModel_type_id, detail_Model, id
comment:9 by , 8 years ago
It doesn't work neither in 1.9 nor in 1.10 I receive
FieldError: Cannot resolve keyword 'testac_detailtest_detail_model' into field. Choices are: %(app_label)s_%(class)s_detail_model, added, detailModel_id, detailModel_type, detailModel_type_id, detail_Model, id
using dt.relatedTrackModel() I get an empty Queryset
comment:10 by , 8 years ago
Component: | Core (Other) → Database layer (models, ORM) |
---|---|
Summary: | GenericRelation doesn't work from AbstractClass to AbstractClass in Django 1.10(.3) → Allow declaring a GenericRelation from an abstract model to another abstract model |
Triage Stage: | Unreviewed → Accepted |
Type: | Bug → New feature |
Tentatively accepting and retitling based on a skim of the issue.
comment:11 by , 21 months ago
Owner: | removed |
---|---|
Status: | assigned → new |
comment:12 by , 10 months ago
Cc: | added |
---|
Alex, was this an oversight in 2ff7ef15b0a1d41e3f121e96cb72a383863046c0?