dimagi / django-prbac

Other
139 stars 38 forks source link

Error when deleting a role from the Admin site #60

Open stgraham2000 opened 4 years ago

stgraham2000 commented 4 years ago

If you try to delete a role from the admin interface on Django 3.0.9 you get:

Request Method:  POST
Request URL: http://localhost:8080/admin/django_prbac/role/1/change/
Django Version: 3.0.9
Exception Type: TypeError
Exception Value: unhashable type: 'UserRole'
Exception Location: /usr/local/lib/python3.7/site-packages/django/contrib/admin/utils.py in collect, line 179
Python Executable: /usr/local/bin/python
Python Version: 3.7.7
...
millerdev commented 4 years ago

I think I need more of a stack trace to troubleshoot.

render() got an unexpected keyword argument 'renderer'

Looks like the same error fixed by https://github.com/dimagi/django-prbac/pull/61 —is this a duplicate?

stgraham2000 commented 4 years ago

I wish it was a duplicate of #61 but it’s not. I’ll dig up better trace for you. My immediate need was to edit the role which I got past with the last fix to #61. Delete would be nice but didn’t hold me back yet. I’ll retry and give you the full trace

stgraham2000 commented 4 years ago

My bad on the issue reporting- I'll edit the original issue because I had a copy/paste error (now wonder why you thought it was a duplicate). Sorry about that.

Here is the actual trace and not the copy from debug=True from the browser:


(0802ab) ERROR [MainProcess-40] log_response:228 django.request Internal Server Error: /admin/django_prbac/role/
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/options.py", line 607, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/sites.py", line 231, in inner
    return view(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/utils/decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/options.py", line 1704, in changelist_view
    response = self.response_action(request, queryset=cl.get_queryset(request))
  File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/options.py", line 1390, in response_action
    response = func(self, request, queryset)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/actions.py", line 28, in delete_selected
    deletable_objects, model_count, perms_needed, protected = modeladmin.get_deleted_objects(queryset, request)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/options.py", line 1826, in get_deleted_objects
    return get_deleted_objects(objs, request, self.admin_site)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/utils.py", line 118, in get_deleted_objects
    collector.collect(objs)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/utils.py", line 181, in collect
    return super().collect(objs, source_attr=source_attr, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/db/models/deletion.py", line 245, in collect
    field.remote_field.on_delete(self, field, sub_objs, self.using)
  File "/usr/local/lib/python3.7/site-packages/django/db/models/deletion.py", line 17, in CASCADE
    source_attr=field.name, nullable=field.null)
  File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/utils.py", line 179, in collect
    self.model_objs[obj._meta.model].add(obj)
TypeError: unhashable type: 'UserRole'```
millerdev commented 4 years ago

Thanks @stgraham2000. Do you know if it is a new requirement with Django 3 that model classes must be hashable to support delete via admin interface? In other words, I wonder if this ever worked?

stgraham2000 commented 4 years ago

@millerdev, I just ran a simple test with 2.2.15 and I can delete roles. So to answer your question, it looks like it is a django 3+ issue. That said, another issue which we should probably raise is you can't add a new role in the admin if you leave the parameters blank. I'll quickly submit that issue as well with trace.

mastnym commented 3 years ago

I've discovered the same.

A class that overrides eq() and does not define hash() will have its hash() implicitly set to None. [source]

The solution would of course be, just to add a __hash__() method on Userrole, but here is a quick monkey patch:

def patch_hash(cls):
    __class__ = cls
    def hash(self):
       return super().__hash__()
    cls.__hash__ = hash
patch_hash(UserRole)
mastnym commented 3 years ago

One more thing that I discovered when using Django built in collector for cascade deletion. There should be a check in __eq__ method if the other model is also a UserRole first. Otherwise it will fail when comparing with other model's role and user attributes And for some weird reason, if you have a Role connected to a UserRole this Role is not found by django collector whe deleting corresponding user. Cascade delete is set properly on UserRole model