diff --git a/README.rst b/README.rst index 92f9dee..fff077a 100644 --- a/README.rst +++ b/README.rst @@ -63,7 +63,11 @@ Quick start 3. 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``. + ``from dbview.helpers import DropView`` and replace the line the + call to ``migrations.DeleteModel`` with ``DropView``. + 4. Migrate your database and start using your database views. diff --git a/dbview/helpers.py b/dbview/helpers.py index 3298932..ed93aab 100644 --- a/dbview/helpers.py +++ b/dbview/helpers.py @@ -1,69 +1,118 @@ +"""HELPERS to create SQL VIEW +https://github.com/manuelnaranjo/django-database-view updated helper +(issue created. fork with solve created: https://github.com/Seriouskosk/django-database-view) +For Create/Drop SQL VIEW you must do: +1) manage.py makemigrations +2) change: +migrations.CreateModel on helpers.CreateView +migrations.DeleteModel on helpers.DropView +3) manage.py migrate""" import logging +import types -from django.db import migrations from django.apps import apps +from django.db import migrations -class CreateView(migrations.CreateModel): - - def database_forwards(self, app_label, schema_editor, from_state, to_state): - fake_model = to_state.apps.get_model(app_label, self.name) - - if not self.allow_migrate_model( - schema_editor.connection.alias, fake_model): - return - - model = self._get_model(fake_model, app_label, to_state) - - self._drop_view(fake_model, model, schema_editor) +class SQLViewsMixin: + """Mixin for work with SQL VIEWS""" - if hasattr(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) + @staticmethod + def _drop_view(model, schema_editor): + """DROP VIEW from DB""" + if hasattr(model, "drop_view_sql"): + sql_template = model.drop_view_sql else: - raise Exception('{} has neither view nor get_view_str'.format( - model)) - - def database_backwards(self, app_label, schema_editor, from_state, to): - fake_model = from_state.apps.get_model(app_label, self.name) - model = self._get_model(fake_model, app_label, to) - self._drop_view(fake_model, model, schema_editor) + sql_template = "DROP VIEW IF EXISTS %(table)s" + args = { + "table": schema_editor.quote_name(model._meta.db_table), + } + sql = sql_template % args + schema_editor.execute(sql, None) - def _get_model(self, state, app_label, fake_model): + def get_model_instance(self, submodules): + """get model recursive""" + attrs = [attr for attr in dir(submodules) if "_" not in attr] + for attr in attrs: + value = getattr(submodules, attr) + if self.name in str(value): + return value + if isinstance(value, types.ModuleType): + return self.get_model_instance(value) + return None + + def _get_model(self, app_label, fake_model): + """ + :fake_model: loaded from code + :return model: loaded from App""" models = apps.get_app_config(app_label).models_module if hasattr(models, self.name): return getattr(models, self.name) - # TODO: identify model more reliably and support more than 1 level - for submodule in models.__dict__.values(): - if hasattr(submodule, self.name): - return getattr(submodule, self.name) + sub_model = self.get_model_instance(models) + if sub_model: + return sub_model - logging.warning('Using fake model, this may fail with inherited views') + logging.warning("Using fake model, this may fail with inherited views") return fake_model - def _drop_view(self, fake_model, model, schema_editor): - if hasattr(model, 'drop_view_sql'): - sql_template = model.drop_view_sql - else: - sql_template = 'DROP VIEW IF EXISTS %(table)s' - args = { - 'table': schema_editor.quote_name(fake_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' + """CREATE VIEW in DB""" + 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, + "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): + @staticmethod + def _create_view_from_raw_sql(sql, schema_editor): + """Execute sql""" schema_editor.execute(sql, None) + + def create_view(self, app_label, schema_editor, state): + """create view method""" + fake_model = state.apps.get_model(app_label, self.name) + model = self._get_model(app_label, fake_model) + + self._drop_view(model, schema_editor) + + if hasattr(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(f"{model} has neither view or get_view_str") + + def drop_view(self, app_label, schema_editor, state): + """Drop view method""" + fake_model = state.apps.get_model(app_label, self.name) + model = self._get_model(app_label, fake_model) + self._drop_view(model, schema_editor) + + +class DropView(migrations.DeleteModel, SQLViewsMixin): + """Drop SQL View migrations""" + + def database_forwards(self, app_label, schema_editor, from_state, to_state): + """Forwards DROP VIEW from DB""" + self.drop_view(app_label, schema_editor, from_state) + + def database_backwards(self, app_label, schema_editor, from_state, to_state): + """Backwards CREATE VIEW from DB""" + self.create_view(app_label, schema_editor, to_state) + + +class CreateView(migrations.CreateModel, SQLViewsMixin): + """Create SQL View migrations""" + + def database_forwards(self, app_label, schema_editor, from_state, to_state): + """Forwards CREATE VIEW from DB""" + self.create_view(app_label, schema_editor, from_state) + + def database_backwards(self, app_label, schema_editor, from_state, to_state): + """Backwards DROP VIEW from DB""" + self.drop_view(app_label, schema_editor, to_state) diff --git a/setup.py b/setup.py index 370b20b..4fa4386 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ def read(fname): setup( name='django-database-view', - version='0.3.0', + version='0.3.1', packages=['dbview'], long_description=read('README.rst'), include_package_data=True,