diff --git a/README.md b/README.md index 95cd4f1..b5f6ce5 100644 --- a/README.md +++ b/README.md @@ -6,23 +6,12 @@ So far only MySQL is supported as backend, but more could be added if necessary. ## Quick start -1. Add "dbview" to your INSTALLED_APPS settings like this: - - ```python - INSTALLED_APPS = ( - ... - 'dbview', - ) - ``` - -2. In your models.py create classes which extend dbview.DbView -like this: - +1. In your models.py create classes which extend dbview.DbView like this: ```python from django.db import models - from dbview import DbView + from dbview.models import DbView class ModelA(models.Model): fielda = models.CharField() @@ -34,7 +23,7 @@ like this: fieldB = models.IntegerField(blank=True, null=True, db_column='fieldb') @classmethod - def view(klass): + def view(cls): ''' This method returns the SQL string that creates the view, in this example fieldB is the result of annotating another column @@ -47,10 +36,25 @@ like this: return str(qs.query) ``` +Alternatively `get_view_str` method could be used to write a custom SQL: -3. Then create a migration point for your view generation, edit that migration -and modify it, add: `from dbview import CreateView` and replace the line -the call to `migrations.CreateModel` with `CreateView`. + ```python + class MyView(DbView): -4. Migrate your database and start using your database views. + # ... + + @classmethod + def get_view_str(cls): + return """ + CREATE VIEW my_view AS ( + SELECT ... + ) + """ + ``` + +2. Then create a migration point for your view generation, edit that migration +and modify it, add: `from dbview.helpers import CreateView` and replace the +line the call to `migrations.CreateModel` with `CreateView`. + +3. Migrate your database and start using your database views. diff --git a/dbview/__init__.py b/dbview/__init__.py index 59292cf..e69de29 100644 --- a/dbview/__init__.py +++ b/dbview/__init__.py @@ -1,3 +0,0 @@ -# placeholder for git -from dbview.helpers import CreateView -from dbview.models import DbView diff --git a/dbview/helpers.py b/dbview/helpers.py index ba3501c..5e82b73 100644 --- a/dbview/helpers.py +++ b/dbview/helpers.py @@ -1,42 +1,64 @@ +import logging + from django.db import migrations from django.apps import apps class CreateView(migrations.CreateModel): + def database_forwards(self, app_label, schema_editor, from_state, to_state): - model = to_state.apps.get_model(app_label, self.name) + fake_model = to_state.apps.get_model(app_label, self.name) - if not self.allow_migrate_model(schema_editor.connection.alias, model): + if not self.allow_migrate_model( + schema_editor.connection.alias, fake_model): raise - models = apps.get_app_config(app_label).models_module - model = getattr(models, self.name) - sql = 'DROP VIEW IF EXISTS %(table)s;' + model = self._get_model(fake_model, app_label, to_state) - args = { - 'table' : schema_editor.quote_name(model._meta.db_table), - } - - sql = sql % args - - schema_editor.execute(sql, None) - - sql = 'CREATE VIEW %(table)s AS %(definition)s' + self._drop_view(fake_model, schema_editor) if hasattr(model, 'view'): - qs = str(model.view()) + self._create_standard_view(model, schema_editor) + elif hasattr(model, 'get_view_str'): + self._create_view_from_raw_sql(model.get_view_str(), schema_editor) else: - raise Exception('Your view needs to define either view or ' + - 'get_view_str') - - args['definition'] = qs - - sql = sql % args - - schema_editor.execute(sql, None) + raise Exception(f"{model} has neither view nor get_view_str") def database_backwards(self, app_label, schema_editor, from_state, to): - model = from_state.apps.get_model(app_label, self.name) - sql = 'DROP VIEW IF EXISTS %s' % \ - schema_editor.quote_name(model._meta.db_table) + fake_model = from_state.apps.get_model(app_label, self.name) + self._drop_view(fake_model, schema_editor) + + def _get_model(self, state, app_label, fake_model): + models = apps.get_app_config(app_label).models_module + + if hasattr(models, self.name): + return getattr(models, self.name) + + # TODO: recursive search + for submodule in models.__dict__.values(): + if hasattr(submodule, self.name): + return getattr(submodule, self.name) + + logging.warning('Using fake model, this may fail with inherited views') + return fake_model + + def _drop_view(self, model, schema_editor): + sql_template = 'DROP VIEW IF EXISTS %(table)s CASCADE' + args = { + 'table': schema_editor.quote_name(model._meta.db_table), + } + sql = sql_template % args + schema_editor.execute(sql, None) + + def _create_standard_view(self, model, schema_editor): + sql_template = 'CREATE VIEW %(table)s AS %(definition)s' + qs = str(model.view()) + args = { + 'table': schema_editor.quote_name(model._meta.db_table), + 'definition': qs, + } + sql = sql_template % args + self._create_view_from_raw_sql(sql, schema_editor) + + def _create_view_from_raw_sql(self, sql, schema_editor): schema_editor.execute(sql, None)