bernardopires / django-tenant-schemas

Tenant support for Django using PostgreSQL schemas.
https://django-tenant-schemas.readthedocs.org/en/latest/
MIT License
1.46k stars 424 forks source link

Any way to update a tenant schema once it has been created? #455

Closed rajeshyogeshwar closed 7 years ago

rajeshyogeshwar commented 7 years ago

So I successfully create one tenant in public schema and one another tenant with its own schema. A very real situation is that we might need to add models to apps in TENANT_APPS. How does one migrate them to tenant schemas? Looking at the following excerpt from save method in TenantMixin it seems that schema is only created on first save.

        if is_new and self.auto_create_schema:
            try:
                self.create_schema(check_if_exists=True, verbosity=verbosity)
            except:
                # We failed creating the tenant, delete what we created and
                # re-raise the exception
                self.delete(force_drop=True)
                raise
            else:
                post_schema_sync.send(sender=TenantMixin, tenant=self)

In this case what does one do in order to migrate new models in tenant apps? What I can think of is, subclassing the TenantMixin class and overriding the save method where in I can put my own logic for case where schema needs to be updated.

rajeshyogeshwar commented 7 years ago

So I decided to subclass TenantMixin and then create my tenant model by extending it. I overrode save method and added update_schema method to take care of my case. The update_schema method is nothing new just a stripped version of create_schema without one statement which creates tenant schema. Below is my code snippet.

class CustomTenantMixin(TenantMixin):

    def save(self, verbosity=1, *args, **kwargs):
        is_new = self.pk is None

        if is_new and connection.schema_name != get_public_schema_name():
            raise Exception('Can\'t create tenant outside the public schema. Current schema is {0}.'.format(connection.schema_name))
        elif not is_new and connection.schema_name not in (self.schema_name, get_public_schema_name()):
            raise Exception('Can\'t update tenant outside it\'s own schema or the public schema. Current schema is {0}.'.format(connection.schema_name))

        super(TenantMixin, self).save(*args, **kwargs)

        if self.auto_create_schema:
            if is_new:
                try:
                    self.create_schema(check_if_exists=True, verbosity=verbosity)
                except:
                    self.delete(force_drop=True)
                    raise
                else:
                    post_schema_sync.send(sender=TenantMixin, tenant=self)

            else:
                try:
                    self.update_schema(verbosity=verbosity)
                except:
                    raise
                else:
                    post_schema_sync.send(sender=TenantMixin, tenant=self)

    def update_schema(self, check_if_exists=False, sync_schema=True,
                      verbosity=1):
        """
        Updates schema with new migrations. Optionally checks if schema already exists. If it does not it returns False, otherwise runs migrations against that particular schema.
        """

        cursor = connection.cursor()

        if not schema_exists(self.schema_name):
            return False

        # create the schema
        #cursor.execute('CREATE SCHEMA %s' % self.schema_name)

        if sync_schema:
            call_command('migrate_schemas',
                         schema_name=self.schema_name,
                         interactive=False,
                         verbosity=verbosity)

        connection.set_schema_to_public()