Comparing many-to-many fields using the "through" keyword breaks the compare view if the intermediate model is not registered with django-reversion
Closed this issue · 2 comments
Using version 0.14.1
This might be a very niche problem but I'm encountering it. Here's the situation:
I have two models, Game and GameRecommendation.
The field Game.recommendation is a m2m relation to Game itself, using GameRecommendation as the intermediate m2m table:
class Game(models.Model):
...
recommendations = models.ManyToManyField('self', through=GameRecommendation, blank=True,
related_name='recommendations+')
Game is versioned/registered, but GameRecommendation is not. When trying to compare versions of Game, I get an exception:
<class 'apps.gaming.models.recommendation_game.GameRecommendation'> has not been registered with django-reversion
I haven't looked much at it, but a quick fix is to wrap line 127 of reversion_compare/mixins.py on a try/except block catching reversion.errors.RegistrationError:
from reversion.errors import RegistrationError
try:
if not obj_compare.changed():
Skip all fields that aren't changed
continue
except RegistrationError:
continue
There might be a better solution, but that would be enough to solve the problem I'm having.
Here's the full stack trace:
[2021-08-14 04:34:02]{ERROR} <class 'apps.gaming.models.recommendation_game.GameRecommendation'> has not been registered with django-reversion
Traceback (most recent call last):
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\utils\decorators.py", line 130, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\views\decorators\cache.py", line 44, in _wrapped_view_func
response = view_func(request, *args, **kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\contrib\admin\sites.py", line 232, in inner
return view(request, *args, **kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\admin.py", line 169, in compare_view
compare_data, has_unfollowed_fields = self.compare(obj, version1, version2)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\mixins.py", line 127, in compare
if not obj_compare.changed():
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 289, in changed
info = self.get_m2o_change_info()
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 322, in get_m2o_change_info
m2o_data1, m2o_data2 = self.get_reverse_foreign_key()
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 316, in get_reverse_foreign_key
return self.compare_obj1.get_reverse_foreign_key(), self.compare_obj2.get_reverse_foreign_key()
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 145, in get_reverse_foreign_key
return self.get_many_to_something(ids, related_model, is_reverse=True)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 198, in get_many_to_something
missing_objects_dict = {
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 201, in <dictcomp>
for ver in Version.objects.get_for_object(o)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\models.py", line 131, in get_for_object
return self.get_for_object_reference(obj.__class__, obj.pk, model_db=model_db)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\models.py", line 126, in get_for_object_reference
return self.get_for_model(model, model_db=model_db).filter(
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\models.py", line 119, in get_for_model
content_type = _get_content_type(model, self.db)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\revisions.py", line 431, in _get_content_type
version_options = _get_options(model)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\revisions.py", line 417, in _get_options
_assert_registered(model)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\revisions.py", line 411, in _assert_registered
raise RegistrationError("{model} has not been registered with django-reversion".format(
reversion.errors.RegistrationError: <class 'apps.gaming.models.recommendation_game.GameRecommendation'> has not been registered with django-reversion
[2021-08-14 04:34:02]{ERROR} Internal Server Error: /admin/gaming/game/1/history/compare/
Traceback (most recent call last):
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\utils\decorators.py", line 130, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\views\decorators\cache.py", line 44, in _wrapped_view_func
response = view_func(request, *args, **kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\contrib\admin\sites.py", line 232, in inner
return view(request, *args, **kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\admin.py", line 169, in compare_view
compare_data, has_unfollowed_fields = self.compare(obj, version1, version2)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\mixins.py", line 127, in compare
if not obj_compare.changed():
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 289, in changed
info = self.get_m2o_change_info()
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 322, in get_m2o_change_info
m2o_data1, m2o_data2 = self.get_reverse_foreign_key()
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 316, in get_reverse_foreign_key
return self.compare_obj1.get_reverse_foreign_key(), self.compare_obj2.get_reverse_foreign_key()
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 145, in get_reverse_foreign_key
return self.get_many_to_something(ids, related_model, is_reverse=True)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 198, in get_many_to_something
missing_objects_dict = {
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion_compare\compare.py", line 201, in <dictcomp>
for ver in Version.objects.get_for_object(o)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\models.py", line 131, in get_for_object
return self.get_for_object_reference(obj.__class__, obj.pk, model_db=model_db)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\models.py", line 126, in get_for_object_reference
return self.get_for_model(model, model_db=model_db).filter(
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\models.py", line 119, in get_for_model
content_type = _get_content_type(model, self.db)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\revisions.py", line 431, in _get_content_type
version_options = _get_options(model)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\revisions.py", line 417, in _get_options
_assert_registered(model)
File "X:\Workdrive\Workspace\virtualenvs\vgjweb\lib\site-packages\reversion\revisions.py", line 411, in _assert_registered
raise RegistrationError("{model} has not been registered with django-reversion".format(
reversion.errors.RegistrationError: <class 'apps.gaming.models.recommendation_game.GameRecommendation'> has not been registered with django-reversion
While we're at it, adding a exclude_compare
field or similar to CompareVersionAdmin would be nice for a performance boost, there are some other m2m fields I don't care about in the Game model that are being compared anyway.
Good point. Can you provide a PR to fix this?
But i think it would be good to inform the user about this and not just ignore the RegistrationError
...
After further inspection, I found out that setting the CompareVersionAdmin.compare_exclude
attribute to the problematic fields solves the problem.