request view resolution is broken when using an ASGI servers with subfolder tenants
See original GitHub issueThis initially came up while trying to setup django-tenants in SubFolder mode with an existing project that also uses django channels.
Accessing a tenant url would lead to the following error while processing any request under a tenant subpath :
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/asgiref/sync.py", line 472, in thread_handler
raise exc_info[1]
File "/usr/local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 42, in inner
response = await get_response(request)
File "/usr/local/lib/python3.9/site-packages/django/core/handlers/base.py", line 235, in _get_response_async
callback, callback_args, callback_kwargs = self.resolve_request(request)
File "/usr/local/lib/python3.9/site-packages/django/core/handlers/base.py", line 313, in resolve_request
resolver_match = resolver.resolve(request.path_info)
File "/usr/local/lib/python3.9/site-packages/django/urls/resolvers.py", line 651, in resolve
sub_match = pattern.resolve(new_path)
File "/usr/local/lib/python3.9/site-packages/django/urls/resolvers.py", line 646, in resolve
match = self.pattern.match(path)
File "/code/examples/tenant_subfolder_tutorial/../../django_tenants/urlresolvers.py", line 50, in match
tenant_prefix = self.tenant_prefix
File "/code/examples/tenant_subfolder_tutorial/../../django_tenants/urlresolvers.py", line 33, in tenant_prefix
domain=connection.tenant.domain_subfolder,
Exception Type: AttributeError at /clients/test/
Exception Value: 'FakeTenant' object has no attribute 'domain_subfolder'
I was able to track this down and reproduce the issue with the example tenant_subfolder_tutorial
. As soon as the project is served behind an ASGI server, the previous error occurs when accessing a subfolder tenant.
Steps to reproduce :
- Setup the example project with a tenant
- Setup the tenant_subfolder_tutorial example project and run it
- Create one public tenant, then one
test
tenant, with domain =test
- Head over to http://localhost:8088/clients/test/ and verify the project is properly operating
- Enable ASGI and demonstrate the issue
- Add uvicorn in requirements and create an asgi.py file (or apply https://github.com/ey3ball/django-tenants/commit/e5cc6fa57ed9c35cd5d3cad104d0c6dc90275031)
- Start project through asgi server with
gunicorn tenant_subfolder_tutorial.asgi:application --bind 0.0.0.0:8088 -k uvicorn.workers.UvicornWorker
(or apply https://github.com/django-tenants/django-tenants/commit/31ed2eb81590b2d0c4327afa53e0cbfc94ccda45 and run it with compose) - Attempt to access http://localhost:8088/clients/test/
- Request fails with FakeTenant backtrace above
Preliminary analysis :
- in django_tenants/middleware/subfolder.py: the connection object is used to set a tenant
- It is later accessed by https://github.com/ey3ball/django-tenants/blob/master/django_tenants/urlresolvers.py#L33
- However at that point instead of getting the connection information we though we saved in the first step, we get a default FakeTenant object that defaults to the public tenant (https://github.com/ey3ball/django-tenants/blob/master/django_tenants/postgresql_backend/base.py#L187)
- Because domain_subfolder is not set on this object a backtrace ensues
At this moment I think this boils down to the fact that we can’t rely on the connection object staying the same during the whole processing of the request. In WSGI mode I guess we can rely on 1 request = 1 connection because there will be one thread dedicated to servicing each request. In ASGI mode it seems this does not hold anymore.
Although this came up with the SubFolder Middleware, this might be a more general issue of pinning data on the Connection object in django_tenants middlewares
Issue Analytics
- State:
- Created a year ago
- Reactions:1
- Comments:10 (4 by maintainers)
Per the comment above I don’t think importing connection into tenant_prefix would work because of the Local(thread_critical=True), relying directly on asgiref,local seems cleaner since we have control over the sharing flags instead of relying on whatever magic the connection class performs
I’ve tried the approach above (WIP patch here : https://github.com/ey3ball/django-tenants/commit/b995f77f1b4f0a4d77f945a913aecbe1a58a6de3), this seem to confirm what I’ve discovered so far, with this patch I can successfully acces the homepage of my tenant in subfolder mode with an asgi frontend.
Some more test required, I guess my approach might not support reverse() at the moment since I’m not actually registering the resulting urlconf module anywhere