500 Server Error when deleting a user from the amin console after upgrading from Nutmeg to Palm

Hi

Tutor version: 16
Open edX version: Palm

I recently upgraded my Open edX platform (installed with Tutor) from Nutmeg to Palm (don’t know if this upgrade is the cause of the following issue though).

When I try to delete users from the admin console (either the LMS admin console or the CMS admin console) I get an 500 server error.

Here are the corresponding logs on the server:

tutor_local-lms-1            | 2023-09-17 21:49:15,887 ERROR 26 [root] [user None] [ip None] signals.py:22 - Uncaught exception from None
tutor_local-lms-1            | Traceback (most recent call last):
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
tutor_local-lms-1            |     response = get_response(request)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
tutor_local-lms-1            |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
tutor_local-lms-1            |   File "/opt/pyenv/versions/3.8.15/lib/python3.8/contextlib.py", line 75, in inner
tutor_local-lms-1            |     return func(*args, **kwds)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 616, in wrapper
tutor_local-lms-1            |     return self.admin_site.admin_view(view)(*args, **kwargs)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
tutor_local-lms-1            |     response = view_func(request, *args, **kwargs)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
tutor_local-lms-1            |     response = view_func(request, *args, **kwargs)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/sites.py", line 232, in inner
tutor_local-lms-1            |     return view(request, *args, **kwargs)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper
tutor_local-lms-1            |     return bound_method(*args, **kwargs)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
tutor_local-lms-1            |     response = view_func(request, *args, **kwargs)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1851, in delete_view
tutor_local-lms-1            |     return self._delete_view(request, object_id, extra_context)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1872, in _delete_view
tutor_local-lms-1            |     deleted_objects, model_count, perms_needed, protected = self.get_deleted_objects([obj], request)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1846, in get_deleted_objects
tutor_local-lms-1            |     return get_deleted_objects(objs, request, self.admin_site)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/utils.py", line 152, in get_deleted_objects
tutor_local-lms-1            |     to_delete = collector.nested(format_callback)
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/utils.py", line 214, in nested
tutor_local-lms-1            |     roots.extend(self._nested(root, seen, format_callback))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/utils.py", line 198, in _nested
tutor_local-lms-1            |     children.extend(self._nested(child, seen, format_callback))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/utils.py", line 198, in _nested
tutor_local-lms-1            |     children.extend(self._nested(child, seen, format_callback))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/utils.py", line 200, in _nested
tutor_local-lms-1            |     ret = [format_callback(obj)]
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/contrib/admin/utils.py", line 127, in format_callback
tutor_local-lms-1            |     no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), obj)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/courseware/models.py", line 291, in __str__
tutor_local-lms-1            |     return str(repr(self))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
tutor_local-lms-1            |     return '<%s: %s>' % (self.__class__.__name__, self)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/courseware/models.py", line 291, in __str__
tutor_local-lms-1            |     return str(repr(self))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
tutor_local-lms-1            |     return '<%s: %s>' % (self.__class__.__name__, self)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/courseware/models.py", line 291, in __str__
tutor_local-lms-1            |     return str(repr(self))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
tutor_local-lms-1            |     return '<%s: %s>' % (self.__class__.__name__, self)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/courseware/models.py", line 291, in __str__
tutor_local-lms-1            |     return str(repr(self))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
tutor_local-lms-1            |     return '<%s: %s>' % (self.__class__.__name__, self)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/courseware/models.py", line 291, in __str__
tutor_local-lms-1            |     return str(repr(self))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
tutor_local-lms-1            |     return '<%s: %s>' % (self.__class__.__name__, self)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/courseware/models.py", line 291, in __str__
tutor_local-lms-1            |     return str(repr(self))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
tutor_local-lms-1            |     return '<%s: %s>' % (self.__class__.__name__, self)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/courseware/models.py", line 291, in __str__
tutor_local-lms-1            |     return str(repr(self))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
tutor_local-lms-1            |     return '<%s: %s>' % (self.__class__.__name__, self)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/courseware/models.py", line 291, in __str__
tutor_local-lms-1            |     return str(repr(self))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
tutor_local-lms-1            |     return '<%s: %s>' % (self.__class__.__name__, self)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/courseware/models.py", line 291, in __str__
tutor_local-lms-1            |     return str(repr(self))
tutor_local-lms-1            |   File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
tutor_local-lms-1            |     return '<%s: %s>' % (self.__class__.__name__, self)
tutor_local-lms-1            |   File "/openedx/edx-platform/./lms/djangoapps/coursewa

...

tutor_local-lms-1            | RecursionError: maximum recursion depth exceeded while getting the str of an object


I managed to delete the user using the command line though tutor local run lms ./manage.py lms manage_user --remove <user_name> <user_email>

Any idea ?

Thanks

This issue is related to the implementation of the lms.djangoapps.courseware.models.StudentModuleHistory.__str__ method. It’s super easy to reproduce:

tutor local run lms ./manage.py shell -c 'from lms.djangoapps.courseware.models import StudentModuleHistory; StudentModuleHistory.objects.all()[1]'

Causes the following infinite loop:

...
  File "/openedx/edx-platform/lms/djangoapps/courseware/models.py", line 291, in __str__
    return str(repr(self))
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 521, in __repr__
    return '<%s: %s>' % (self.__class__.__name__, self)
...

As far as I understand, the __str__ method is completely useless. It was implemented years ago and I don’t know how it could ever have worked. Maybe at some point there was another __repr__ method in theStudentModuleHistory class (or one of its parents) which avoided the infinite recursion. I suggest it is removed. @RonanFR would you like to open a PR? If not I can do it myself.

Hi @regis
Thanks for your quick reply

Happy to help but not sure about the best procedure: should I create an issue on Github ? or open a PR directly ?

If I open a PR, should I just delete the __str__ method + commit + push and then put a link to this discussion ?

Thanks again

Yes, pretty much. Please also include as much information as possible in the commit message, such that people don’t have to read this entire discussion to figure out what is going on.

And of course you should verify that the fix allows you to delete a user from the admin console :slight_smile: