Generic Foreign Keys break when used with multi-db.
|Reported by:||legutierr||Owned by:||nobody|
|Cc:||django@…, albrecht.andi@…, 4glitch@…, django@…||Triage Stage:||Accepted|
|Has patch:||yes||Needs documentation:||no|
|Needs tests:||yes||Patch needs improvement:||no|
Specifically, retrieving and saving objects that use the GenericForeignKey descriptor fails when the ContentType table is stored in a different database than the table of the instance being retrieved or saved.
There can only be one valid and consistent content-type table in a system. In a two-db system, the content type table will be in either db A or db B. Therefore, a portion of the system's tables will be registered in a content-type table that is stored in a different database.
This is all OK when retrieving objects based on a GenericForeignKey reference, because a user in such a situation will typically rely on the underlying routers to determine which database to use in retrieving content-type records.
However, this method in django/trunk/django/contrib/contenttypes/generic.py does not use routers in determining what database to use to fetch the content-type, but instead uses the source database of the orm object the content-type of which is being queried:
52 def get_content_type(self, obj=None, id=None, using=None): 53 # Convenience function using get_model avoids a circular import when 54 # using this model 55 ContentType = get_model("contenttypes", "contenttype") 56 if obj: 57 return ContentType.objects.db_manager(obj._state.db).get_for_model(obj) 58 elif id: 59 return ContentType.objects.db_manager(using).get_for_id(id) 60 else: 61 # This should never happen. I love comments like this, don't you? 62 raise Exception("Impossible arguments to GFK.get_content_type!")
The above method is what is used at object-creation time to transform a generic object in the __init__ kwargs of a new orm object into a content-type/object-id pair for purposes of saving. The use of this method results in either: (a) an error being generated when the specified table is not to be found in the designated database; or (b) incorrect information being saved when a parallel--but incorrect (according to the router rules)--content type table from the other database is used (i.e. database corruption).
A simple fix would be to not assume that the content-types table is in the same database as the object with the GenericForeignKey field. I don't have a patch right now, but it may be as simple as replacing line 57:
57 return ContentType.objects.db_manager(obj._state.db).get_for_model(obj)
57 return ContentType.objects.db_manager(using).get_for_model(obj)
A similar error seems to exist in the following block of code as well, however:
220 manager = RelatedManager( 221 model = rel_model, 222 instance = instance, 223 symmetrical = (self.field.rel.symmetrical and instance.__class__ == rel_model), 224 join_table = qn(self.field.m2m_db_table()), 225 source_col_name = qn(self.field.m2m_column_name()), 226 target_col_name = qn(self.field.m2m_reverse_name()), 227 content_type = ContentType.objects.db_manager(instance._state.db).get_for_model(instance), 228 content_type_field_name = self.field.content_type_field_name, 229 object_id_field_name = self.field.object_id_field_name 230 )
Requiring that line 227 be replaced, perhaps:
227 content_type = ContentType.objects.get_for_model(instance),
The problem here is that no using argument is specified for the __get__ method, nor can a using argument be specified there, making the usage in that function inconsistent with the rest of the file, should this fix be put in place.
Change History (15)
comment:1 Changed 3 years ago by russellm
- Needs documentation unset
- Needs tests unset
- Patch needs improvement unset
- Triage Stage changed from Unreviewed to Design decision needed
comment:2 Changed 3 years ago by gabrielhurley
- Component changed from Contrib apps to contrib.contenttypes
Changed 3 years ago by natalie.stepina@…
comment:8 Changed 2 years ago by aaugustin
- Triage Stage changed from Design decision needed to Accepted
- UI/UX unset