citusdata / django-multitenant

Python/Django support for distributed multi-tenant databases like Postgres+Citus
MIT License
707 stars 116 forks source link

Tenant matching query does not exist. #201

Open Soykertje opened 7 months ago

Soykertje commented 7 months ago

Requirements:

Django==4.2
django-multitenant==4.0.0

Python version: 3.9

My models look like this:

# Django
from django.db import models

# Utils
from django_multitenant.fields import TenantForeignKey
from django_multitenant.models import TenantModel

class BaseModel(TenantModel):
    """ Base model

    Act as an abstract model and all the models in the project should inherit from it.
    """

    created = models.DateTimeField(
        'created at',
        auto_now_add=True,
        help_text='Date time on which the object was created.'
    )

    modified = models.DateTimeField(
        'modified at',
        auto_now=True,
        help_text='Date time on which the object was modified.'
    )

    deleted = models.BooleanField(
        default=False,
        help_text='Set to True when an element is deleted'
    )

    tenant = TenantForeignKey(Tenant,
                              on_delete=models.CASCADE,
                              related_name='%(app_label)s_%(class)s_related',
                              null=True)

    class Meta:
        abstract = True
        get_latest_by = 'created'
        ordering = ['-created', '-modified']

    class TenantMeta:
        tenant_field_name = "tenant_id"

class User(AbstractUser, BaseModel):
    pass

class Tenant(TenantModel):
    """
    Tenant model.
    """
    unique_id = models.UUIDField(unique=True, null=False, blank=False)
    name = models.CharField(max_length=500, null=False, blank=False)

    class TenantMeta:
        tenant_field_name = "id"

And my middleware looks like this:

class MultiTenantMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if request.user and not request.user.is_anonymous:
            set_current_tenant(request.user.tenant)

        return self.get_response(request)

When I'm logged at the same time with two different users that belong to a different tenant and interact within the Django admin with both users almost at the same time, one of the sessions starts to throw this exception:

Internal Server Error: /secure/admin/api/imagepool/
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/django/db/models/fields/related_descriptors.py", line 218, in __get__
    rel_obj = self.field.get_cached_value(instance)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/fields/mixins.py", line 15, in get_cached_value
    return instance._state.fields_cache[cache_name]
KeyError: 'tenant'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/app/api/middleware/tenant_middleware.py", line 55, in __call__
    tenant = get_user_tenant(request)
  File "/app/api/middleware/tenant_middleware.py", line 16, in get_user_tenant
    return request.user.tenant
  File "/usr/local/lib/python3.9/site-packages/django/utils/functional.py", line 268, in inner
    return func(_wrapped, *args)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/fields/related_descriptors.py", line 236, in __get__
    rel_obj = self.get_object(instance)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/fields/related_descriptors.py", line 199, in get_object
    return qs.get(self.field.get_reverse_related_filter(instance))
  File "/usr/local/lib/python3.9/site-packages/django/db/models/query.py", line 637, in get
    raise self.model.DoesNotExist(
tenant.models.tenant.Tenant.DoesNotExist: Tenant matching query does not exist.

And that even happens with just one user at the time if I start doing too many requests to the server in a short period of time... And even with normal usage and just one user at the time sometimes I get that error.