Merge branch 'feature/raw_sql_view'
commit
46a32aba7d
40
README.md
40
README.md
|
|
@ -6,23 +6,12 @@ So far only MySQL is supported as backend, but more could be added if necessary.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
1. Add "dbview" to your INSTALLED_APPS settings like this:
|
1. In your models.py create classes which extend dbview.DbView like this:
|
||||||
|
|
||||||
```python
|
|
||||||
INSTALLED_APPS = (
|
|
||||||
...
|
|
||||||
'dbview',
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
2. In your models.py create classes which extend dbview.DbView
|
|
||||||
like this:
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from dbview import DbView
|
from dbview.models import DbView
|
||||||
|
|
||||||
class ModelA(models.Model):
|
class ModelA(models.Model):
|
||||||
fielda = models.CharField()
|
fielda = models.CharField()
|
||||||
|
|
@ -34,7 +23,7 @@ like this:
|
||||||
fieldB = models.IntegerField(blank=True, null=True, db_column='fieldb')
|
fieldB = models.IntegerField(blank=True, null=True, db_column='fieldb')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def view(klass):
|
def view(cls):
|
||||||
'''
|
'''
|
||||||
This method returns the SQL string that creates the view, in this
|
This method returns the SQL string that creates the view, in this
|
||||||
example fieldB is the result of annotating another column
|
example fieldB is the result of annotating another column
|
||||||
|
|
@ -47,10 +36,25 @@ like this:
|
||||||
return str(qs.query)
|
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
|
```python
|
||||||
and modify it, add: `from dbview import CreateView` and replace the line
|
|
||||||
the call to `migrations.CreateModel` with `CreateView`.
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# placeholder for git
|
|
||||||
from dbview.helpers import CreateView
|
|
||||||
from dbview.models import DbView
|
|
||||||
|
|
@ -1,42 +1,64 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
class CreateView(migrations.CreateModel):
|
class CreateView(migrations.CreateModel):
|
||||||
|
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
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
|
raise
|
||||||
|
|
||||||
|
model = self._get_model(fake_model, app_label, to_state)
|
||||||
|
|
||||||
|
self._drop_view(fake_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 nor get_view_str")
|
||||||
|
|
||||||
|
def database_backwards(self, app_label, schema_editor, from_state, to):
|
||||||
|
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
|
models = apps.get_app_config(app_label).models_module
|
||||||
model = getattr(models, self.name)
|
|
||||||
|
|
||||||
sql = 'DROP VIEW IF EXISTS %(table)s;'
|
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 = {
|
args = {
|
||||||
'table': schema_editor.quote_name(model._meta.db_table),
|
'table': schema_editor.quote_name(model._meta.db_table),
|
||||||
}
|
}
|
||||||
|
sql = sql_template % args
|
||||||
sql = sql % args
|
|
||||||
|
|
||||||
schema_editor.execute(sql, None)
|
schema_editor.execute(sql, None)
|
||||||
|
|
||||||
sql = 'CREATE VIEW %(table)s AS %(definition)s'
|
def _create_standard_view(self, model, schema_editor):
|
||||||
|
sql_template = 'CREATE VIEW %(table)s AS %(definition)s'
|
||||||
if hasattr(model, 'view'):
|
|
||||||
qs = str(model.view())
|
qs = str(model.view())
|
||||||
else:
|
args = {
|
||||||
raise Exception('Your view needs to define either view or ' +
|
'table': schema_editor.quote_name(model._meta.db_table),
|
||||||
'get_view_str')
|
'definition': qs,
|
||||||
|
}
|
||||||
args['definition'] = qs
|
sql = sql_template % args
|
||||||
|
self._create_view_from_raw_sql(sql, schema_editor)
|
||||||
sql = sql % args
|
|
||||||
|
|
||||||
schema_editor.execute(sql, None)
|
def _create_view_from_raw_sql(self, sql, schema_editor):
|
||||||
|
|
||||||
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)
|
|
||||||
schema_editor.execute(sql, None)
|
schema_editor.execute(sql, None)
|
||||||
|
|
|
||||||
2
setup.py
2
setup.py
|
|
@ -6,7 +6,7 @@ os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='django-database-view',
|
name='django-database-view',
|
||||||
version='0.1.3',
|
version='0.2.0',
|
||||||
packages=['dbview'],
|
packages=['dbview'],
|
||||||
setup_requires=['setuptools-markdown'],
|
setup_requires=['setuptools-markdown'],
|
||||||
long_description_markdown_filename='README.md',
|
long_description_markdown_filename='README.md',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue