NilCoalescing / djangochannelsrestframework

A Rest-framework for websockets using Django channels-v4
https://djangochannelsrestframework.readthedocs.io/en/latest/
MIT License
613 stars 86 forks source link

[BUG] Can't override get_queryset method when using ObserverModelInstanceMixin and GenericAsyncAPIConsumer #166

Open erdos4d opened 1 year ago

erdos4d commented 1 year ago

I'm following the example in the docs here using ObserverModelInstanceMixin and GenericAsyncAPIConsumer to set up a subscriptions view on certain model instances that clients have interest in. I have a "real world" permission situation and I need to override the get_queryset method to return a different queryset depending on who is requesting what. When I do this, I get the stack trace I posted below in the LOG section. This happens even if I return the stock SomeModel.objects.all() from the method. It looks like it wants a ModelObserver object to call subscribe on, but gets a _GenericModelObserver instead. This works fine when I just define the queryset attribute like in the example, so I am guessing there is some sort of dynamic class building going on somewhere and overriding the method throws that off.

To Reproduce Follow the example given in the docs, delete the queryset attribute and override the get_queryset method to return the same queryset.

Expected behavior It works and you get the new queryset behavior you coded up.

LOG File "/.venv/lib/python3.10/site-packages/django/contrib/staticfiles/handlers.py", line 101, in call return await self.application(scope, receive, send) File "/.venv/lib/python3.10/site-packages/channels/routing.py", line 62, in call return await application(scope, receive, send) File "/.venv/lib/python3.10/site-packages/channels/security/websocket.py", line 37, in call return await self.application(scope, receive, send) File "/scheduler/middleware.py", line 29, in call return await self.app(scope, receive, send) File "/.venv/lib/python3.10/site-packages/channels/routing.py", line 116, in call return await application( File "/.venv/lib/python3.10/site-packages/channels/consumer.py", line 94, in app return await consumer(scope, receive, send) File "/.venv/lib/python3.10/site-packages/channels/consumer.py", line 58, in call await await_many_dispatch( File "/.venv/lib/python3.10/site-packages/channels/utils.py", line 50, in await_many_dispatch await dispatch(result) File "/.venv/lib/python3.10/site-packages/channels/consumer.py", line 73, in dispatch await handler(message) File "/.venv/lib/python3.10/site-packages/channels/generic/websocket.py", line 194, in websocket_receive await self.receive(text_data=message["text"]) File "/.venv/lib/python3.10/site-packages/channels/generic/websocket.py", line 257, in receive await self.receive_json(await self.decode_json(text_data), kwargs) File "/.venv/lib/python3.10/site-packages/djangochannelsrestframework/consumers.py", line 197, in receive_json await self.handle_action(action, request_id=request_id, content) File "/.venv/lib/python3.10/site-packages/djangochannelsrestframework/consumers.py", line 192, in handle_action await self.handle_exception(exc, action=action, request_id=request_id) File "/.venv/lib/python3.10/site-packages/djangochannelsrestframework/consumers.py", line 152, in handle_exception raise exc File "/.venv/lib/python3.10/site-packages/djangochannelsrestframework/consumers.py", line 185, in handle_action response = await method(request_id=request_id, action=action, **kwargs) File "/.venv/lib/python3.10/site-packages/djangochannelsrestframework/observer/generics.py", line 98, in subscribe_instance groups = set(await self.handle_instance_change.subscribe(instance=instance)) AttributeError: '_GenericModelObserver' object has no attribute 'subscribe'

I'm running ubuntu 22.04.

Additional context I'm willing to put a PR in but I honestly find the code a bit confusing. Many things seem named the same and use decorators for inheritance, which I am honestly not used to. If someone has an idea what is wrong I will try though.

hishnash commented 1 year ago

Hi @erdos4d thank you for reporting this you should absolutly be able to override the get_queryset method... through in my examples I tend to use fitler_queryset (that should work)

just to be clear the intention here is to filter what a user can subscribe to ? or is the intention to filter the results from REST like actions: list retrieve create and update etc?

hishnash commented 1 year ago

So I have done a little more looking into this and the source of the issue is https://github.com/NilCoalescing/djangochannelsrestframework/blob/d6fb7a0e5a77f0fb0f52c410d4caec979e520477/djangochannelsrestframework/observer/generics.py#L41

The reason this is here is we need to register a django even hook on the database record. (this needs to happen before any insurance of the consumer is created but rather when django starts up) so we cant use get_queryset as this is an instance method and we need to be able to know the DB record type at django startup to register change handlers on all running threads/instrances of django.

you can use filter_queryset method (as used here https://nilcoalescing.com/blog/BuildingARealtimeSocialNetworkUsingDjangoChannels/#websocket-consumer ) to filter based on the user (however as that post describes this has limited use for subscription in that case you need to follow the suggestions in the post by defining the groups that are used to notify of changes and limiting what groups a user can subscribe to).

hishnash commented 1 year ago

this mixin should realy check if get_queryset has been defined and if it has it should raise some warning.