jedie/django-reversion-compare

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

dreiq commented

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.

jedie commented

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...

dreiq commented

After further inspection, I found out that setting the CompareVersionAdmin.compare_exclude attribute to the problematic fields solves the problem.