diff --git a/tenants/access.py b/tenants/access.py index 9b07303..d3ba646 100644 --- a/tenants/access.py +++ b/tenants/access.py @@ -7,7 +7,7 @@ def can_pick_tenant(user): def filter_for_user(model, user): tenant_accessor = get_tenant_accessor(model) - query = {tenant_accessor + '__user': user} + query = {tenant_accessor + '__users': user} return model.objects.filter(**query) diff --git a/tenants/admin.py b/tenants/admin.py index 831f67b..3ca215a 100644 --- a/tenants/admin.py +++ b/tenants/admin.py @@ -4,6 +4,6 @@ from django.contrib.admin import ModelAdmin from .models import Tenant class TenantAdmin(ModelAdmin): - list_display = ('user', 'uuid') + list_display = ('__str__', 'uuid') admin.site.register(Tenant, TenantAdmin) \ No newline at end of file diff --git a/tenants/forms.py b/tenants/forms.py index 5090799..c890c4f 100644 --- a/tenants/forms.py +++ b/tenants/forms.py @@ -14,7 +14,7 @@ class TenantModelForm(ModelForm): def full_clean(self): super().full_clean() if not can_pick_tenant(self.user) and self.instance: - self.instance.tenant = Tenant.objects.get(user=self.user) + self.instance.tenant = Tenant.objects.get(users=self.user) self.validate_unique_for_tenant() def validate_unique_for_tenant(self): diff --git a/tenants/migrations/0002_tenant_users.py b/tenants/migrations/0002_tenant_users.py new file mode 100644 index 0000000..0893ad4 --- /dev/null +++ b/tenants/migrations/0002_tenant_users.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.10 on 2023-08-03 10:45 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('tenants', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='tenant', + name='users', + field=models.ManyToManyField(related_name='users', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/tenants/migrations/0003_migrate_existing_users.py b/tenants/migrations/0003_migrate_existing_users.py new file mode 100644 index 0000000..6ab975a --- /dev/null +++ b/tenants/migrations/0003_migrate_existing_users.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.10 on 2023-08-03 10:45 + +from django.db import migrations + +def migrate_existing_users(apps, _): + Tenant = apps.get_model('tenants', 'Tenant') + for tenant in Tenant.objects.all(): + tenant.users.add(tenant.user) + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenants', '0002_tenant_users'), + ] + + operations = [ + migrations.RunPython(migrate_existing_users) + ] diff --git a/tenants/migrations/0004_remove_tenant_user.py b/tenants/migrations/0004_remove_tenant_user.py new file mode 100644 index 0000000..3cdc3ec --- /dev/null +++ b/tenants/migrations/0004_remove_tenant_user.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.10 on 2023-08-03 10:49 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('tenants', '0003_migrate_existing_users'), + ] + + operations = [ + migrations.RemoveField( + model_name='tenant', + name='user', + ), + migrations.AlterField( + model_name='tenant', + name='users', + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/tenants/models.py b/tenants/models.py index 2c43779..3c19bf9 100644 --- a/tenants/models.py +++ b/tenants/models.py @@ -1,13 +1,20 @@ from uuid import uuid4 from django.contrib.auth.models import User -from django.db.models import Model, ForeignKey, CASCADE, UUIDField +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Model, UUIDField, ManyToManyField class Tenant(Model): - user = ForeignKey(User, on_delete=CASCADE) + users = ManyToManyField(User) uuid = UUIDField(unique=True, default=uuid4, editable=False) def __str__(self): - return self.user.username \ No newline at end of file + # We have to check that `id` is set to avoid a `RecursionError`. + if self.id: + try: + return self.users.earliest('date_joined').username + except ObjectDoesNotExist: + pass + return str(self.uuid) \ No newline at end of file diff --git a/winget/tests/test_api.py b/winget/tests/test_api.py index 74cc56c..c8aafa1 100644 --- a/winget/tests/test_api.py +++ b/winget/tests/test_api.py @@ -1,11 +1,10 @@ from base64 import b64encode -from django.contrib.auth.models import User from django.core.files import File from django.urls import reverse from rest_framework.test import APIClient -from tenants.models import Tenant from winget.models import Package, Version, Installer -from winget.tests.util import TestCaseThatUploadsFiles, MultiUploadInMemoryFile +from winget.tests.util import TestCaseThatUploadsFiles, \ + MultiUploadInMemoryFile, create_tenant import json @@ -15,9 +14,9 @@ class APITest(TestCaseThatUploadsFiles): super().setUp() self.client = APIClient() self.credentials = ('user', 'pw') - self.tenant = self._create_user_tenant(self.credentials) + self.tenant = create_tenant(*self.credentials) self.other_credentials = ('user2', 'pw2') - self._create_user_tenant(self.other_credentials) + create_tenant(*self.other_credentials) def test_create_package(self): response = self._check_unauthorized_and_request( 'post', 'package-list', _PACKAGE_PAYLOAD, self.credentials @@ -128,9 +127,6 @@ class APITest(TestCaseThatUploadsFiles): self.test_create_version(use_correct_credentials=False) def test_cannot_create_installer_for_other_tenants_version(self): self.test_create_installer(use_correct_credentials=False) - def _create_user_tenant(self, credentials): - user = User.objects.create_user(credentials[0], password=credentials[1]) - return Tenant.objects.create(user=user) def _check_unauthorized_and_request( self, method, view_name, data, credentials=None, view_kwargs=None ): diff --git a/winget/tests/test_views.py b/winget/tests/test_views.py index 959025c..0ba2188 100644 --- a/winget/tests/test_views.py +++ b/winget/tests/test_views.py @@ -1,10 +1,9 @@ -from django.contrib.auth.models import User from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase, Client from django.urls import reverse -from tenants.models import Tenant from winget.models import Package, Version, Installer +from winget.tests.util import create_tenant class APITest(TestCase): @@ -12,9 +11,7 @@ class APITest(TestCase): def setUp(self): super().setUp() self.client = Client() - self.tenant = Tenant.objects.create( - user=User.objects.create_user('user@gmail.com') - ) + self.tenant = create_tenant() def test_information_required_parts(self): data = self._get('information')['Data'] diff --git a/winget/tests/util.py b/winget/tests/util.py index 5b4b0f9..f8bdadb 100644 --- a/winget/tests/util.py +++ b/winget/tests/util.py @@ -1,7 +1,10 @@ +from django.contrib.auth.models import User from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase, override_settings from tempfile import TemporaryDirectory +from tenants.models import Tenant + """ When a test case uploads files, they by default get placed into media/. This folder (out of the box) is not cleaned up after the test case is run. @@ -24,4 +27,10 @@ class MultiUploadInMemoryFile(SimpleUploadedFile): """ def read(self): self.seek(0) - return super().read() \ No newline at end of file + return super().read() + +def create_tenant(username='user@gmail.com', password=None): + user = User.objects.create_user(username, password=password) + tenant = Tenant.objects.create() + tenant.users.add(user) + return tenant \ No newline at end of file