Skip to content

Commit

Permalink
Fixes #164 ManyToMany Non tenant model (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
gurkanindibay authored Mar 15, 2023
1 parent 0a6af0c commit 2045f3e
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 21 deletions.
7 changes: 4 additions & 3 deletions django_multitenant/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ def wrap_many_related_manager_add(many_related_manager_add):
"""

def add(self, *objs, through_defaults=None):
if get_current_tenant():

if hasattr(self.through, "tenant_field") and get_current_tenant():
through_defaults[
get_tenant_column(self.through)
] = get_current_tenant_value()
Expand Down Expand Up @@ -201,13 +202,13 @@ def tenant_field(self):
return self.TenantMeta.tenant_id
if hasattr(self, "tenant"):
raise AttributeError(
"Tenant field exists which may cause collision with tenant_id field. Please rename the tenant field. "
f"Tenant field exists which may cause collision with tenant_id field. Please rename the tenant field in {self.__class__.__name__} "
)
if hasattr(self, "tenant_id"):
return self.tenant_id

raise AttributeError(
"tenant_id field not found. Please add tenant_id field to the model."
f"tenant_id field not found. Please add tenant_id field to the model {self.__class__.__name__}"
)

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ class Migration(migrations.Migration):
],
bases=(django_multitenant.mixins.TenantModelMixin, models.Model),
),
migrations.CreateModel(
name="Staff",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name="Store",
fields=[
Expand All @@ -66,6 +81,42 @@ class Migration(migrations.Migration):
},
bases=(django_multitenant.mixins.TenantModelMixin, models.Model),
),
migrations.CreateModel(
name="StoreStaff",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"staff",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="tests.staff"
),
),
(
"store",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="tests.store"
),
),
],
options={
"abstract": False,
},
),
migrations.AddField(
model_name="store",
name="store_staffs",
field=models.ManyToManyField(
blank=True, through="tests.StoreStaff", to="tests.Staff"
),
),
migrations.CreateModel(
name="Transaction",
fields=[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Migration(migrations.Migration):
]

operations += [
tenant_migrations.Distribute("Staff", reference=True),
tenant_migrations.Distribute("Store"),
tenant_migrations.Distribute("Product"),
tenant_migrations.Distribute("Purchase"),
Expand Down
25 changes: 16 additions & 9 deletions django_multitenant/tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ class Account(TenantModel):
# TODO change to Meta
tenant_id = "id"

def __str__(self):
return f"{self.name}"


class Employee(models.Model):
# Reference table
Expand Down Expand Up @@ -91,9 +88,6 @@ class Project(TenantModel):
)
tenant_id = "account_id"

def __str__(self):
return f"{self.name} ({self.account})"


class ProjectManager(TenantModel):
project = TenantForeignKey(
Expand Down Expand Up @@ -137,9 +131,6 @@ class Task(TenantModelMixin, models.Model):

tenant_id = "account_id"

def __str__(self):
return f"{self.name} ({self.project})"


class SubTask(TenantModel):
name = models.CharField(max_length=255)
Expand Down Expand Up @@ -246,12 +237,28 @@ class TenantMeta:
tenant_field_name = "tenant_id"


# Non-Tenant Model which should be Reference Table
# to be referenced by Store which is a Tenant Model
# in Citus 10.
class Staff(models.Model):
name = models.CharField(max_length=50)


class Store(TenantModel):
tenant_id = "id"
name = models.CharField(max_length=50)
address = models.CharField(max_length=255)
email = models.CharField(max_length=50)

store_staffs = models.ManyToManyField(
Staff, through="StoreStaff", through_fields=("store", "staff"), blank=True
)


class StoreStaff(models.Model):
store = models.ForeignKey(Store, on_delete=models.CASCADE)
staff = models.ForeignKey(Staff, on_delete=models.CASCADE)


class Product(TenantModel):
store = models.ForeignKey(Store, on_delete=models.CASCADE)
Expand Down
2 changes: 0 additions & 2 deletions django_multitenant/tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@

SECRET_KEY = "blabla"

ROOT_URLCONF = "django_multitenant.tests.urls"


TEMPLATES = [
{
Expand Down
21 changes: 14 additions & 7 deletions django_multitenant/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.conf import settings
from django.db.models import Count
from django.db.utils import NotSupportedError, DataError
from .models import Store, Product, Purchase
from .models import Store, Product, Purchase, Staff, StoreStaff


from django_multitenant.utils import (
Expand Down Expand Up @@ -412,27 +412,21 @@ def test_task_manager(self):
unset_current_tenant()

def test_str_model_tenant_set(self):
from .models import Task

projects = self.projects
account = self.account_fr
tasks = self.tasks

set_current_tenant(account)

print(Task.objects.first())

unset_current_tenant()

def test_str_model_tenant_not_set(self):
from .models import Task

projects = self.projects
account = self.account_fr
tasks = self.tasks

print(Task.objects.first())

def test_exclude_tenant_set(self):
from .models import Task

Expand Down Expand Up @@ -817,6 +811,19 @@ def test_many_to_many_through_saves(self):
purchase.save()
purchase.product_purchased.add(product, through_defaults={"date": date.today()})

def test_many_to_many_through_saves_to_nontenant(self):

store = Store.objects.create(name="store1")
store.save()

set_current_tenant(tenant=store)

staff = Staff.objects.create(name="staff1")

store.store_staffs.add(staff)

self.assertEqual(StoreStaff.objects.get(store=store, staff=staff).store, store)

def test_tenant_id_columns(self):
from .models import Template, Tenant, Business

Expand Down
5 changes: 5 additions & 0 deletions docs/source/migration_mt_django.rst
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,11 @@ In the second section of this article, we introduced the fact that with citus, `
After installing the library, changing the engine, and updating the models, run
:code:`python manage.py makemigrations`. This will produce a migration to make the foreign keys composite when necessary.
.. warning::
If you use Citus 10, you will need to distribute the model inherited from "models.Model" as reference if you have a "ManyToMany" relationship
between a class derived from "TenantModel" and a class derived from "models.Model"
as Citus 10 does not support such relationships between distributed and local tables.
4. Distribute data in Citus
----------------------------
Expand Down

0 comments on commit 2045f3e

Please sign in to comment.