From 0123af1c2b16b7827bd2bb0d1ea7751e5bc07eb7 Mon Sep 17 00:00:00 2001 From: Lucio Delelis Date: Sat, 30 Jan 2021 23:13:49 -0300 Subject: [PATCH 1/5] implements feature flag mechanism, correct org relations --- mockserver/tenants/models.py | 35 ++++++++++++++++++++++--- mockserver/tenants/tests/test_models.py | 30 ++++++++++++++++----- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/mockserver/tenants/models.py b/mockserver/tenants/models.py index 1009284..c1b917d 100644 --- a/mockserver/tenants/models.py +++ b/mockserver/tenants/models.py @@ -62,6 +62,13 @@ class Meta: class OrganizationProfile(models.Model): + organization = models.OneToOneField( + 'tenants.Organization', + related_name="public_profile", + on_delete=models.CASCADE, + null=True + ) + public_name = models.CharField(max_length=255) description = models.TextField(null=True) technologies = models.ManyToManyField( @@ -96,18 +103,29 @@ class Organization(DateAwareModel): ) profile = models.OneToOneField( 'tenants.OrganizationProfile', - related_name='organization', on_delete=models.CASCADE, + related_name="deprecated_org", + null=True + ) + feature_flags = models.OneToOneField( + 'tenants.FeatureFlag', + on_delete=models.CASCADE, + related_name="deprecated_org", null=True ) def save(self, **kwargs): - if not self.pk: - self.profile = OrganizationProfile.objects.create( + is_new = not self.pk + super(Organization, self).save(**kwargs) + + if is_new: + OrganizationProfile.objects.create( organization=self, public_name=self.name ) - return super(Organization, self).save(**kwargs) + FeatureFlag.objects.create( + organization=self, + ) @property def member_count(self): @@ -121,6 +139,15 @@ def __str__(self): return f"{self.name} ({self.uuid})" +class FeatureFlag(DateAwareModel): + organization = models.OneToOneField( + 'tenants.Organization', + related_name="featureflag", + on_delete=models.CASCADE, + null=True + ) + + class Tenant(DateAwareModel, User): pass diff --git a/mockserver/tenants/tests/test_models.py b/mockserver/tenants/tests/test_models.py index a52e8f8..93b7012 100644 --- a/mockserver/tenants/tests/test_models.py +++ b/mockserver/tenants/tests/test_models.py @@ -5,18 +5,36 @@ from common.tests.mixins import MockTestMixin from tenants.models import ( OrganizationInvite, - OrganizationProfile + OrganizationProfile, + FeatureFlag ) class OrganizationModelTestCase(MockTestMixin, TestCase): - def test_new_organization_creates_profile(self): + def test_organization_handles_profile(self): organization = self.create_bare_minimum_organization() - self.assertIsNotNone(organization.profile) - self.assertEqual(OrganizationProfile.objects.count(), 1) - self.assertEqual(organization.profile.public_name, organization.name) - self.assertEqual(organization.profile.organization.pk, organization.pk) + with self.subTest("automatically creates profile"): + self.assertIsNotNone(organization.public_profile) + self.assertEqual(OrganizationProfile.objects.count(), 1) + self.assertEqual(organization.public_profile.public_name, organization.name) + self.assertEqual(organization.public_profile.organization.pk, organization.pk) + with self.subTest("automatically deletes profile"): + organization.delete() + + self.assertEqual(OrganizationProfile.objects.count(), 0) + + + def test_organization_handles_feature_flags(self): + organization = self.create_bare_minimum_organization() + + with self.subTest("automatically creates feature flag"): + self.assertIsNotNone(organization.featureflag) + self.assertEqual(FeatureFlag.objects.count(), 1) + with self.subTest("automatically deletes feature flag"): + organization.delete() + + self.assertEqual(FeatureFlag.objects.count(), 0) class OrganizationInviteModelTestCase(MockTestMixin, TestCase): From e3ff4484e8b3a81cac91a6ae51115cf237ebed34 Mon Sep 17 00:00:00 2001 From: Lucio Delelis Date: Sat, 30 Jan 2021 23:16:55 -0300 Subject: [PATCH 2/5] adds migrations --- .../0012_organization_feature_flags.py | 30 ++++++++++++++++ .../0013_organization_profile_fix.py | 34 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 mockserver/tenants/migrations/0012_organization_feature_flags.py create mode 100644 mockserver/tenants/migrations/0013_organization_profile_fix.py diff --git a/mockserver/tenants/migrations/0012_organization_feature_flags.py b/mockserver/tenants/migrations/0012_organization_feature_flags.py new file mode 100644 index 0000000..871da8b --- /dev/null +++ b/mockserver/tenants/migrations/0012_organization_feature_flags.py @@ -0,0 +1,30 @@ +# Generated by Django 3.0.9 on 2021-01-31 00:19 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenants', '0011_project_slug_name'), + ] + + operations = [ + migrations.CreateModel( + name='FeatureFlag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='organization', + name='feature_flags', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='organization', to='tenants.FeatureFlag'), + ), + ] diff --git a/mockserver/tenants/migrations/0013_organization_profile_fix.py b/mockserver/tenants/migrations/0013_organization_profile_fix.py new file mode 100644 index 0000000..2adcd31 --- /dev/null +++ b/mockserver/tenants/migrations/0013_organization_profile_fix.py @@ -0,0 +1,34 @@ +# Generated by Django 3.0.9 on 2021-01-31 01:50 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenants', '0012_organization_feature_flags'), + ] + + operations = [ + migrations.AddField( + model_name='featureflag', + name='organization', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='featureflag', to='tenants.Organization'), + ), + migrations.AddField( + model_name='organizationprofile', + name='organization', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='public_profile', to='tenants.Organization'), + ), + migrations.AlterField( + model_name='organization', + name='feature_flags', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='deprecated_org', to='tenants.FeatureFlag'), + ), + migrations.AlterField( + model_name='organization', + name='profile', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='deprecated_org', to='tenants.OrganizationProfile'), + ), + ] From 4ae317f7abe71b0ed3bc268ed807443c2b34f64d Mon Sep 17 00:00:00 2001 From: Lucio Delelis Date: Sat, 30 Jan 2021 23:19:57 -0300 Subject: [PATCH 3/5] flake8 --- mockserver/tenants/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mockserver/tenants/models.py b/mockserver/tenants/models.py index c1b917d..c33869b 100644 --- a/mockserver/tenants/models.py +++ b/mockserver/tenants/models.py @@ -115,7 +115,7 @@ class Organization(DateAwareModel): ) def save(self, **kwargs): - is_new = not self.pk + is_new = not self.pk super(Organization, self).save(**kwargs) if is_new: From d7dfc45c35682e8d0218837fa46376823c4e0080 Mon Sep 17 00:00:00 2001 From: Lucio Delelis Date: Sun, 31 Jan 2021 01:57:59 -0300 Subject: [PATCH 4/5] correctly handles migration and deprecation of organization fields --- .../0014_organization_data_migration.py | 31 +++++++++++++++++++ .../0015_organization_fields_remove.py | 21 +++++++++++++ mockserver/tenants/models.py | 16 ++-------- mockserver/tenants/tests/test_models.py | 8 ++--- 4 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 mockserver/tenants/migrations/0014_organization_data_migration.py create mode 100644 mockserver/tenants/migrations/0015_organization_fields_remove.py diff --git a/mockserver/tenants/migrations/0014_organization_data_migration.py b/mockserver/tenants/migrations/0014_organization_data_migration.py new file mode 100644 index 0000000..16799aa --- /dev/null +++ b/mockserver/tenants/migrations/0014_organization_data_migration.py @@ -0,0 +1,31 @@ +# Generated by Django 3.0.9 on 2021-01-31 04:36 + +from django.db import migrations + + +def forwards(apps, schema_editor): + Organization = apps.get_model('tenants', 'Organization') + + for org in Organization.objects.all(): + if org.profile is not None: + org.profile.organization = org + org.profile.save() + + if org.feature_flags is not None: + org.feature_flags.organization = org + org.feature_flags.save() + + +def backwards(apps, schema_editor): + ... + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenants', '0013_organization_profile_fix'), + ] + + operations = [ + migrations.RunPython(forwards, backwards) + ] diff --git a/mockserver/tenants/migrations/0015_organization_fields_remove.py b/mockserver/tenants/migrations/0015_organization_fields_remove.py new file mode 100644 index 0000000..7daaa47 --- /dev/null +++ b/mockserver/tenants/migrations/0015_organization_fields_remove.py @@ -0,0 +1,21 @@ +# Generated by Django 3.0.9 on 2021-01-31 04:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenants', '0014_organization_data_migration'), + ] + + operations = [ + migrations.RemoveField( + model_name='organization', + name='feature_flags', + ), + migrations.RemoveField( + model_name='organization', + name='profile', + ), + ] diff --git a/mockserver/tenants/models.py b/mockserver/tenants/models.py index c33869b..dfa47d1 100644 --- a/mockserver/tenants/models.py +++ b/mockserver/tenants/models.py @@ -64,7 +64,7 @@ class Meta: class OrganizationProfile(models.Model): organization = models.OneToOneField( 'tenants.Organization', - related_name="public_profile", + related_name="profile", on_delete=models.CASCADE, null=True ) @@ -101,18 +101,6 @@ class Organization(DateAwareModel): through=OrganizationMembership, blank=True ) - profile = models.OneToOneField( - 'tenants.OrganizationProfile', - on_delete=models.CASCADE, - related_name="deprecated_org", - null=True - ) - feature_flags = models.OneToOneField( - 'tenants.FeatureFlag', - on_delete=models.CASCADE, - related_name="deprecated_org", - null=True - ) def save(self, **kwargs): is_new = not self.pk @@ -142,7 +130,7 @@ def __str__(self): class FeatureFlag(DateAwareModel): organization = models.OneToOneField( 'tenants.Organization', - related_name="featureflag", + related_name="feature_flags", on_delete=models.CASCADE, null=True ) diff --git a/mockserver/tenants/tests/test_models.py b/mockserver/tenants/tests/test_models.py index 93b7012..ba6525d 100644 --- a/mockserver/tenants/tests/test_models.py +++ b/mockserver/tenants/tests/test_models.py @@ -15,10 +15,10 @@ def test_organization_handles_profile(self): organization = self.create_bare_minimum_organization() with self.subTest("automatically creates profile"): - self.assertIsNotNone(organization.public_profile) + self.assertIsNotNone(organization.profile) self.assertEqual(OrganizationProfile.objects.count(), 1) - self.assertEqual(organization.public_profile.public_name, organization.name) - self.assertEqual(organization.public_profile.organization.pk, organization.pk) + self.assertEqual(organization.profile.public_name, organization.name) + self.assertEqual(organization.profile.organization.pk, organization.pk) with self.subTest("automatically deletes profile"): organization.delete() @@ -29,7 +29,7 @@ def test_organization_handles_feature_flags(self): organization = self.create_bare_minimum_organization() with self.subTest("automatically creates feature flag"): - self.assertIsNotNone(organization.featureflag) + self.assertIsNotNone(organization.feature_flags) self.assertEqual(FeatureFlag.objects.count(), 1) with self.subTest("automatically deletes feature flag"): organization.delete() From 195c96d4f50ac6a8e27b22a6a23d31587ef9b28c Mon Sep 17 00:00:00 2001 From: Lucio Delelis Date: Sun, 31 Jan 2021 02:00:30 -0300 Subject: [PATCH 5/5] adds missing migration --- .../0016_organization_rename_reverse.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 mockserver/tenants/migrations/0016_organization_rename_reverse.py diff --git a/mockserver/tenants/migrations/0016_organization_rename_reverse.py b/mockserver/tenants/migrations/0016_organization_rename_reverse.py new file mode 100644 index 0000000..41266b9 --- /dev/null +++ b/mockserver/tenants/migrations/0016_organization_rename_reverse.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.9 on 2021-01-31 05:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tenants', '0015_organization_fields_remove'), + ] + + operations = [ + migrations.AlterField( + model_name='featureflag', + name='organization', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='feature_flags', to='tenants.Organization'), + ), + migrations.AlterField( + model_name='organizationprofile', + name='organization', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='profile', to='tenants.Organization'), + ), + ]