# SQL Views for Postgres
Adds first-class support for [PostgreSQL Views][pg-views] in the Django ORM.
Fork of the original [django-pgviews][django-pgviews] by [mypebble][mypebble] with support for Django 3.2+.
[pg-views]: http://www.postgresql.org/docs/9.1/static/sql-createview.html
[django-pgviews]: https://github.com/mypebble/django-pgviews
[mypebble]: https://github.com/mypebble
## Installation
Install via pip:
pip install django-pgviews-redux
Add to installed applications in settings.py:
```python
INSTALLED_APPS = (
# ...
'django_pgviews',
)
```
## Examples
```python
from django.db import models
from django_pgviews import view as pg
class Customer(models.Model):
name = models.CharField(max_length=100)
post_code = models.CharField(max_length=20)
is_preferred = models.BooleanField(default=False)
class Meta:
app_label = 'myapp'
class PreferredCustomer(pg.View):
projection = ['myapp.Customer.*',]
dependencies = ['myapp.OtherView',]
sql = """SELECT * FROM myapp_customer WHERE is_preferred = TRUE;"""
class Meta:
app_label = 'myapp'
db_table = 'myapp_preferredcustomer'
managed = False
```
**NOTE** It is important that we include the `managed = False` in the `Meta` so
Django 1.7 migrations don't attempt to create DB tables for this view.
The SQL produced by this might look like:
```postgresql
CREATE VIEW myapp_preferredcustomer AS
SELECT * FROM myapp_customer WHERE is_preferred = TRUE;
```
To create all your views, run ``python manage.py sync_pgviews``
You can also specify field names, which will map onto fields in your View:
```python
from django_pgviews import view as pg
VIEW_SQL = """
SELECT name, post_code FROM myapp_customer WHERE is_preferred = TRUE
"""
class PreferredCustomer(pg.View):
name = models.CharField(max_length=100)
post_code = models.CharField(max_length=20)
sql = VIEW_SQL
```
## Usage
To map onto a View, simply extend `pg_views.view.View`, assign SQL to the
`sql` argument and define a `db_table`. You must _always_ set `managed = False`
on the `Meta` class.
Views can be created in a number of ways:
1. Define fields to map onto the VIEW output
2. Define a projection that describes the VIEW fields
### Define Fields
Define the fields as you would with any Django Model:
```python
from django_pgviews import view as pg
VIEW_SQL = """
SELECT name, post_code FROM myapp_customer WHERE is_preferred = TRUE
"""
class PreferredCustomer(pg.View):
name = models.CharField(max_length=100)
post_code = models.CharField(max_length=20)
sql = VIEW_SQL
class Meta:
managed = False
db_table = 'my_sql_view'
```
### Define Projection
`django-pgviews` can take a projection to figure out what fields it needs to
map onto for a view. To use this, set the `projection` attribute:
```python
from django_pgviews import view as pg
class PreferredCustomer(pg.View):
projection = ['myapp.Customer.*',]
sql = """SELECT * FROM myapp_customer WHERE is_preferred = TRUE;"""
class Meta:
db_table = 'my_sql_view'
managed = False
```
This will take all fields on `myapp.Customer` and apply them to
`PreferredCustomer`
## Features
### Configuration
`MATERIALIZED_VIEWS_DISABLE_SYNC_ON_MIGRATE`
When set to True, it skips running `sync_pgview` during migrations, which can be useful if you want to control the synchronization manually or avoid potential overhead during migrations. (default: False)
```
MATERIALIZED_VIEWS_DISABLE_SYNC_ON_MIGRATE = True
```
### Updating Views
Sometimes your models change and you need your Database Views to reflect the new
data. Updating the View logic is as simple as modifying the underlying SQL and
running:
```
python manage.py sync_pgviews --force
```
This will forcibly update any views that conflict with your new SQL.
### Dependencies
You can specify other views you depend on. This ensures the other views are
installed beforehand. Using dependencies also ensures that your views get
refreshed correctly when using `sync_pgviews --force`.
**Note:** Views are synced after the Django application has migrated and adding
models to the dependency list will cause syncing to fail.
Example:
```python
from django_pgviews import view as pg
class PreferredCustomer(pg.View):
dependencies = ['myapp.OtherView',]
sql = """SELECT * FROM myapp_customer WHERE is_preferred = TRUE;"""
class Meta:
app_label = 'myapp'
db_table = 'myapp_preferredcustomer'
managed = False
```
### Materialized Views
Postgres 9.3 and up supports [materialized views](http://www.postgresql.org/docs/current/static/sql-creatematerializedview.html)
which allow you to cache the results of views, potentially allowing them
to load faster.
However, you do need to manually refresh the view. To do this automatically,
you can attach [signals](https://docs.djangoproject.com/en/1.8/ref/signals/)
and call the refresh function.
Example:
```python
from django_pgviews import view as pg
VIEW_SQL = """
SELECT name, post_code FROM myapp_customer WHERE is_preferred = TRUE
"""
class Customer(models.Model):
name = models.CharField(max_length=100)
post_code = models.CharField(max_length=20)
is_preferred = models.BooleanField(default=True)
class PreferredCustomer(pg.MaterializedView):
name = models.CharField(max_length=100)
post_code = models.CharField(max_length=20)
sql = VIEW_SQL
@receiver(post_save, sender=Customer)
def customer_saved(sender, action=None, instance=None, **kwargs):
PreferredCustomer.refresh()
```
#### Concurrent refresh
Postgres 9.4 and up allow materialized views to be refreshed concurrently, without blocking reads, as long as a
unique index exists on the materialized view. To enable concurrent refresh, specify the name of a column that can be
used as a unique index on the materialized view. Unique index can be defined on more than one column of a materialized
view. Once enabled, passing `concurrently=True` to the model's refresh method will result in postgres performing the
refresh concurrently. (Note that the refresh method itself blocks until the refresh is complete; concurrent refresh is
most useful when materialized views are updated in another process or thread.)
Example:
```python
from django_pgviews import view as pg
VIEW_SQL = """
SELECT id, name, post_code FROM myapp_customer WHERE is_preferred = TRUE
"""
class PreferredCustomer(pg.MaterializedView):
concurrent_index = 'id, post_code'
sql = VIEW_SQL
name = models.CharField(max_length=100)
post_code = models.CharField(max_length=20)
@receiver(post_save, sender=Customer)
def customer_saved(sender, action=None, instance=None, **kwargs):
PreferredCustomer.refresh(concurrently=True)
```
#### Indexes
As the materialized view isn't defined through the usual Django model fields, any indexes defined there won't be
created on the materialized view. Luckily Django provides a Meta option called `indexes` which can be used to add custom
indexes to models. `pg_views` supports defining indexes on materialized views using this option.
In the following example, one index will be created, on the `name` column. The `db_index=True` on the field definition
for `post_code` will get ignored.
```python
from django_pgviews import view as pg
VIEW_SQL = """
SELECT id, name, post_code FROM myapp_customer WHERE is_preferred = TRUE
"""
class PreferredCustomer(pg.MaterializedView):
sql = VIEW_SQL
name = models.CharField(max_length=100)
post_code = models.CharField(max_length=20, db_index=True)
class Meta:
managed = False # don't forget this, otherwise Django will think it's a regular model
indexes = [
models.Index(fields=["name"]),
]
```
#### WITH NO DATA
Materialized views can be created either with or without data. By default, they are created with data, however
`pg_views` supports creating materialized views without data, by defining `with_data = False` for the
`pg.MaterializedView` class. Such views then do not support querying until the first
refresh (raising `django.db.utils.OperationalError`).
Example:
```python
from django_pgviews import view as pg
class PreferredCustomer(pg.MaterializedView):
concurrent_index = 'id, post_code'
sql = """
SELECT id, name, post_code FROM myapp_customer WHERE is_preferred = TRUE
"""
with_data = False
name = models.CharField(max_length=100)
post_code = models.CharField(max_length=20)
```
#### Conditional materialized views recreate
Since all materialized views are recreated on running `migrate`, it can lead to obsolete recreations even if there
were no changes to the definition of the view. To prevent this, version 0.7.0 and higher contain a feature which
checks existing materialized view definition in the database (if the mat. view exists at all) and compares the
definition with the one currently defined in your `pg.MaterializedView` subclass. If the definition matches
exactly, the re-create of materialized view is skipped.
This feature is enabled by setting the `MATERIALIZED_VIEWS_CHECK_SQL_CHANGED` in your Django settings to `True`,
which enables the feature when running `migrate`. The command `sync_pgviews` uses this setting as well,
however it also has switches `--enable-materialized-views-check-sql-changed` and
`--disable-materialized-views-check-sql-changed` which override this setting for that command.
This feature also takes into account indexes. When a view is deemed not needing recreating, the process will still
check the indexes on the table and delete any extra indexes and create any missing indexes. This reconciliation
is done through the index name, so if you use custom names for your indexes, it might happen that it won't get updated
on change of the content but not the name.
### Schemas
By default, the views will get created in the schema of the database, this is usually `public`.
The package supports the database defining the schema in the settings by using
options (`"OPTIONS": {"options": "-c search_path=custom_schema"}`).
The package `django-tenants` is supported as well, if used.
It is possible to define the schema explicitly for a view, if different from the default schema of the database, like
this:
```python
from django_pgviews import view as pg
class PreferredCustomer(pg.View):
sql = """SELECT * FROM myapp_customer WHERE is_preferred = TRUE;"""
class Meta:
db_table = 'my_custom_schema.preferredcustomer'
managed = False
```
### Dynamic View SQL
If you need a dynamic view SQL (for example if it needs a value from settings in it), you can override the `run_sql`
classmethod on the view to return the SQL. The method should return a namedtuple `ViewSQL`, which contains the query
and potentially the params to `cursor.execute` call. Params should be either None or a list of parameters for the query.
```python
from django.conf import settings
from django_pgviews import view as pg
class PreferredCustomer(pg.View):
@classmethod
def get_sql(cls):
return pg.ViewSQL(
"""SELECT * FROM myapp_customer WHERE is_preferred = TRUE and created_at >= %s;""",
[settings.MIN_PREFERRED_CUSTOMER_CREATED_AT]
)
class Meta:
db_table = 'preferredcustomer'
managed = False
```
### Sync Listeners
django-pgviews 0.5.0 adds the ability to listen to when a `post_sync` event has
occurred.
#### `view_synced`
Fired every time a VIEW is synchronised with the database.
Provides args:
* `sender` - View Class
* `update` - Whether the view to be updated
* `force` - Whether `force` was passed
* `status` - The result of creating the view e.g. `EXISTS`, `FORCE_REQUIRED`
* `has_changed` - Whether the view had to change
#### `all_views_synced`
Sent after all Postgres VIEWs are synchronised.
Provides args:
* `sender` - Always `None`
### Multiple databases
django-pgviews can use multiple databases. Similar to Django's `migrate`
management command, our commands (`clear_pgviews`, `refresh_pgviews`,
`sync_pgviews`) operate on one database at a time. You can specify which
database to synchronize by providing the `--database` option. For example:
```shell
python manage.py sync_pgviews # uses default db
python manage.py sync_pgviews --database=myotherdb
```
Unless using custom routers, django-pgviews will sync all views to the specified
database. If you want to interact with multiple databases automatically, you'll
need to take some additional steps. Please refer to Django's [Automatic database
routing](https://docs.djangoproject.com/en/3.2/topics/db/multi-db/#automatic-database-routing)
to pin views to specific databases.
## Django Compatibility
<table>
<thead>
<tr>
<th>Django Version</th>
<th>Django-PGView Version</th>
</tr>
</thead>
<tbody>
<tr>
<td>1.4 and down</td>
<td>Unsupported</td>
</tr>
<tr>
<td>1.5</td>
<td>0.0.1</td>
</tr>
<tr>
<td>1.6</td>
<td>0.0.3</td>
</tr>
<tr>
<td>1.7</td>
<td>0.0.4</td>
</tr>
<tr>
<td>1.9</td>
<td>0.1.0</td>
</tr>
<tr>
<td>1.10</td>
<td>0.2.0</td>
</tr>
<tr>
<td>2.2</td>
<td>0.6.0</td>
</tr>
<tr>
<td>3.0</td>
<td>0.6.0</td>
</tr>
<tr>
<td>3.1</td>
<td>0.6.1</td>
<tr>
<td>3.2</td>
<td>0.7.1</td>
</tr>
<tr>
<td>4.0</td>
<td>0.8.1</td>
</tr>
<tr>
<td>4.1</td>
<td>0.8.4</td>
</tr>
<tr>
<td>4.2</td>
<td>0.9.2</td>
</tr>
<tr>
<td>5.0</td>
<td>0.9.4</td>
</tr>
</tbody>
</table>
## Python 3 Support
Django PGViews Redux only officially supports Python 3.7+, it might work on 3.6, but there's no guarantees.
Raw data
{
"_id": null,
"home_page": "https://github.com/xelixdev/django-pgviews-redux",
"name": "django-pgviews-redux",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": null,
"keywords": "django, views, materialized views, postgres",
"author": "Mikul\u00e1\u0161 Poul",
"author_email": "mikulas.poul@xelix.com",
"download_url": "https://files.pythonhosted.org/packages/7f/b6/6ffd824b89f15d7f67b293aaa6e0bf28319bd708dc2b18959a3467a73509/django_pgviews_redux-0.11.0.tar.gz",
"platform": null,
"description": "# SQL Views for Postgres\n\nAdds first-class support for [PostgreSQL Views][pg-views] in the Django ORM.\nFork of the original [django-pgviews][django-pgviews] by [mypebble][mypebble] with support for Django 3.2+.\n\n[pg-views]: http://www.postgresql.org/docs/9.1/static/sql-createview.html\n[django-pgviews]: https://github.com/mypebble/django-pgviews\n[mypebble]: https://github.com/mypebble\n\n## Installation\n\nInstall via pip:\n\n pip install django-pgviews-redux\n\nAdd to installed applications in settings.py:\n\n```python\nINSTALLED_APPS = (\n # ...\n 'django_pgviews',\n)\n```\n\n## Examples\n\n```python\nfrom django.db import models\n\nfrom django_pgviews import view as pg\n\n\nclass Customer(models.Model):\n name = models.CharField(max_length=100)\n post_code = models.CharField(max_length=20)\n is_preferred = models.BooleanField(default=False)\n\n class Meta:\n app_label = 'myapp'\n\nclass PreferredCustomer(pg.View):\n projection = ['myapp.Customer.*',]\n dependencies = ['myapp.OtherView',]\n sql = \"\"\"SELECT * FROM myapp_customer WHERE is_preferred = TRUE;\"\"\"\n\n class Meta:\n app_label = 'myapp'\n db_table = 'myapp_preferredcustomer'\n managed = False\n```\n\n**NOTE** It is important that we include the `managed = False` in the `Meta` so\nDjango 1.7 migrations don't attempt to create DB tables for this view.\n\nThe SQL produced by this might look like:\n\n```postgresql\nCREATE VIEW myapp_preferredcustomer AS\nSELECT * FROM myapp_customer WHERE is_preferred = TRUE;\n```\n\nTo create all your views, run ``python manage.py sync_pgviews``\n\nYou can also specify field names, which will map onto fields in your View:\n\n```python\nfrom django_pgviews import view as pg\n\n\nVIEW_SQL = \"\"\"\n SELECT name, post_code FROM myapp_customer WHERE is_preferred = TRUE\n\"\"\"\n\n\nclass PreferredCustomer(pg.View):\n name = models.CharField(max_length=100)\n post_code = models.CharField(max_length=20)\n\n sql = VIEW_SQL\n```\n\n## Usage\n\nTo map onto a View, simply extend `pg_views.view.View`, assign SQL to the\n`sql` argument and define a `db_table`. You must _always_ set `managed = False`\non the `Meta` class.\n\nViews can be created in a number of ways:\n\n1. Define fields to map onto the VIEW output\n2. Define a projection that describes the VIEW fields\n\n### Define Fields\n\nDefine the fields as you would with any Django Model:\n\n```python\nfrom django_pgviews import view as pg\n\n\nVIEW_SQL = \"\"\"\n SELECT name, post_code FROM myapp_customer WHERE is_preferred = TRUE\n\"\"\"\n\n\nclass PreferredCustomer(pg.View):\n name = models.CharField(max_length=100)\n post_code = models.CharField(max_length=20)\n\n sql = VIEW_SQL\n\n class Meta:\n managed = False\n db_table = 'my_sql_view'\n```\n\n### Define Projection\n\n`django-pgviews` can take a projection to figure out what fields it needs to\nmap onto for a view. To use this, set the `projection` attribute:\n\n```python\nfrom django_pgviews import view as pg\n\n\nclass PreferredCustomer(pg.View):\n projection = ['myapp.Customer.*',]\n sql = \"\"\"SELECT * FROM myapp_customer WHERE is_preferred = TRUE;\"\"\"\n\n class Meta:\n db_table = 'my_sql_view'\n managed = False\n```\n\nThis will take all fields on `myapp.Customer` and apply them to\n`PreferredCustomer`\n\n## Features\n\n### Configuration\n`MATERIALIZED_VIEWS_DISABLE_SYNC_ON_MIGRATE`\n\nWhen set to True, it skips running `sync_pgview` during migrations, which can be useful if you want to control the synchronization manually or avoid potential overhead during migrations. (default: False)\n```\nMATERIALIZED_VIEWS_DISABLE_SYNC_ON_MIGRATE = True\n```\n\n### Updating Views\n\nSometimes your models change and you need your Database Views to reflect the new\ndata. Updating the View logic is as simple as modifying the underlying SQL and\nrunning:\n\n```\npython manage.py sync_pgviews --force\n```\n\nThis will forcibly update any views that conflict with your new SQL.\n\n### Dependencies\n\nYou can specify other views you depend on. This ensures the other views are\ninstalled beforehand. Using dependencies also ensures that your views get\nrefreshed correctly when using `sync_pgviews --force`.\n\n**Note:** Views are synced after the Django application has migrated and adding\nmodels to the dependency list will cause syncing to fail.\n\nExample:\n\n```python\nfrom django_pgviews import view as pg\n\nclass PreferredCustomer(pg.View):\n dependencies = ['myapp.OtherView',]\n sql = \"\"\"SELECT * FROM myapp_customer WHERE is_preferred = TRUE;\"\"\"\n\n class Meta:\n app_label = 'myapp'\n db_table = 'myapp_preferredcustomer'\n managed = False\n```\n\n### Materialized Views\n\nPostgres 9.3 and up supports [materialized views](http://www.postgresql.org/docs/current/static/sql-creatematerializedview.html)\nwhich allow you to cache the results of views, potentially allowing them\nto load faster.\n\nHowever, you do need to manually refresh the view. To do this automatically,\nyou can attach [signals](https://docs.djangoproject.com/en/1.8/ref/signals/)\nand call the refresh function.\n\nExample:\n\n```python\nfrom django_pgviews import view as pg\n\n\nVIEW_SQL = \"\"\"\n SELECT name, post_code FROM myapp_customer WHERE is_preferred = TRUE\n\"\"\"\n\nclass Customer(models.Model):\n name = models.CharField(max_length=100)\n post_code = models.CharField(max_length=20)\n is_preferred = models.BooleanField(default=True)\n\n\nclass PreferredCustomer(pg.MaterializedView):\n name = models.CharField(max_length=100)\n post_code = models.CharField(max_length=20)\n\n sql = VIEW_SQL\n\n\n@receiver(post_save, sender=Customer)\ndef customer_saved(sender, action=None, instance=None, **kwargs):\n PreferredCustomer.refresh()\n```\n\n#### Concurrent refresh\n\nPostgres 9.4 and up allow materialized views to be refreshed concurrently, without blocking reads, as long as a\nunique index exists on the materialized view. To enable concurrent refresh, specify the name of a column that can be\nused as a unique index on the materialized view. Unique index can be defined on more than one column of a materialized\nview. Once enabled, passing `concurrently=True` to the model's refresh method will result in postgres performing the\nrefresh concurrently. (Note that the refresh method itself blocks until the refresh is complete; concurrent refresh is\nmost useful when materialized views are updated in another process or thread.)\n\nExample:\n\n```python\nfrom django_pgviews import view as pg\n\n\nVIEW_SQL = \"\"\"\n SELECT id, name, post_code FROM myapp_customer WHERE is_preferred = TRUE\n\"\"\"\n\nclass PreferredCustomer(pg.MaterializedView):\n concurrent_index = 'id, post_code'\n sql = VIEW_SQL\n\n name = models.CharField(max_length=100)\n post_code = models.CharField(max_length=20)\n\n\n@receiver(post_save, sender=Customer)\ndef customer_saved(sender, action=None, instance=None, **kwargs):\n PreferredCustomer.refresh(concurrently=True)\n```\n\n#### Indexes\n\nAs the materialized view isn't defined through the usual Django model fields, any indexes defined there won't be\ncreated on the materialized view. Luckily Django provides a Meta option called `indexes` which can be used to add custom\nindexes to models. `pg_views` supports defining indexes on materialized views using this option.\n\nIn the following example, one index will be created, on the `name` column. The `db_index=True` on the field definition\nfor `post_code` will get ignored.\n\n```python\nfrom django_pgviews import view as pg\n\n\nVIEW_SQL = \"\"\"\n SELECT id, name, post_code FROM myapp_customer WHERE is_preferred = TRUE\n\"\"\"\n\nclass PreferredCustomer(pg.MaterializedView):\n sql = VIEW_SQL\n\n name = models.CharField(max_length=100)\n post_code = models.CharField(max_length=20, db_index=True)\n\n class Meta:\n managed = False # don't forget this, otherwise Django will think it's a regular model\n indexes = [\n models.Index(fields=[\"name\"]),\n ]\n```\n\n#### WITH NO DATA\n\nMaterialized views can be created either with or without data. By default, they are created with data, however\n`pg_views` supports creating materialized views without data, by defining `with_data = False` for the\n`pg.MaterializedView` class. Such views then do not support querying until the first\nrefresh (raising `django.db.utils.OperationalError`).\n\nExample:\n\n```python\nfrom django_pgviews import view as pg\n\nclass PreferredCustomer(pg.MaterializedView):\n concurrent_index = 'id, post_code'\n sql = \"\"\"\n SELECT id, name, post_code FROM myapp_customer WHERE is_preferred = TRUE\n \"\"\"\n with_data = False\n\n name = models.CharField(max_length=100)\n post_code = models.CharField(max_length=20)\n```\n\n#### Conditional materialized views recreate\n\nSince all materialized views are recreated on running `migrate`, it can lead to obsolete recreations even if there\nwere no changes to the definition of the view. To prevent this, version 0.7.0 and higher contain a feature which\nchecks existing materialized view definition in the database (if the mat. view exists at all) and compares the\ndefinition with the one currently defined in your `pg.MaterializedView` subclass. If the definition matches\nexactly, the re-create of materialized view is skipped.\n\nThis feature is enabled by setting the `MATERIALIZED_VIEWS_CHECK_SQL_CHANGED` in your Django settings to `True`,\nwhich enables the feature when running `migrate`. The command `sync_pgviews` uses this setting as well,\nhowever it also has switches `--enable-materialized-views-check-sql-changed` and\n`--disable-materialized-views-check-sql-changed` which override this setting for that command.\n\nThis feature also takes into account indexes. When a view is deemed not needing recreating, the process will still\ncheck the indexes on the table and delete any extra indexes and create any missing indexes. This reconciliation\nis done through the index name, so if you use custom names for your indexes, it might happen that it won't get updated\non change of the content but not the name.\n\n### Schemas\n\nBy default, the views will get created in the schema of the database, this is usually `public`.\nThe package supports the database defining the schema in the settings by using\noptions (`\"OPTIONS\": {\"options\": \"-c search_path=custom_schema\"}`).\n\nThe package `django-tenants` is supported as well, if used.\n\nIt is possible to define the schema explicitly for a view, if different from the default schema of the database, like\nthis:\n\n```python\nfrom django_pgviews import view as pg\n\n\nclass PreferredCustomer(pg.View):\n sql = \"\"\"SELECT * FROM myapp_customer WHERE is_preferred = TRUE;\"\"\"\n\n class Meta:\n db_table = 'my_custom_schema.preferredcustomer'\n managed = False\n```\n\n### Dynamic View SQL\n\nIf you need a dynamic view SQL (for example if it needs a value from settings in it), you can override the `run_sql`\nclassmethod on the view to return the SQL. The method should return a namedtuple `ViewSQL`, which contains the query\nand potentially the params to `cursor.execute` call. Params should be either None or a list of parameters for the query.\n\n```python\nfrom django.conf import settings\nfrom django_pgviews import view as pg\n\n\nclass PreferredCustomer(pg.View):\n @classmethod\n def get_sql(cls):\n return pg.ViewSQL(\n \"\"\"SELECT * FROM myapp_customer WHERE is_preferred = TRUE and created_at >= %s;\"\"\",\n [settings.MIN_PREFERRED_CUSTOMER_CREATED_AT]\n )\n\n class Meta:\n db_table = 'preferredcustomer'\n managed = False\n```\n\n### Sync Listeners\n\ndjango-pgviews 0.5.0 adds the ability to listen to when a `post_sync` event has\noccurred.\n\n#### `view_synced`\n\nFired every time a VIEW is synchronised with the database.\n\nProvides args:\n* `sender` - View Class\n* `update` - Whether the view to be updated\n* `force` - Whether `force` was passed\n* `status` - The result of creating the view e.g. `EXISTS`, `FORCE_REQUIRED`\n* `has_changed` - Whether the view had to change\n\n#### `all_views_synced`\n\nSent after all Postgres VIEWs are synchronised.\n\nProvides args:\n* `sender` - Always `None`\n\n\n### Multiple databases\n\ndjango-pgviews can use multiple databases. Similar to Django's `migrate`\nmanagement command, our commands (`clear_pgviews`, `refresh_pgviews`,\n`sync_pgviews`) operate on one database at a time. You can specify which\ndatabase to synchronize by providing the `--database` option. For example:\n\n```shell\npython manage.py sync_pgviews # uses default db\npython manage.py sync_pgviews --database=myotherdb\n```\n\nUnless using custom routers, django-pgviews will sync all views to the specified\ndatabase. If you want to interact with multiple databases automatically, you'll\nneed to take some additional steps. Please refer to Django's [Automatic database\nrouting](https://docs.djangoproject.com/en/3.2/topics/db/multi-db/#automatic-database-routing)\nto pin views to specific databases.\n\n\n## Django Compatibility\n\n<table>\n <thead>\n <tr>\n <th>Django Version</th>\n <th>Django-PGView Version</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>1.4 and down</td>\n <td>Unsupported</td>\n </tr>\n <tr>\n <td>1.5</td>\n <td>0.0.1</td>\n </tr>\n <tr>\n <td>1.6</td>\n <td>0.0.3</td>\n </tr>\n <tr>\n <td>1.7</td>\n <td>0.0.4</td>\n </tr>\n <tr>\n <td>1.9</td>\n <td>0.1.0</td>\n </tr>\n <tr>\n <td>1.10</td>\n <td>0.2.0</td>\n </tr>\n <tr>\n <td>2.2</td>\n <td>0.6.0</td>\n </tr>\n <tr>\n <td>3.0</td>\n <td>0.6.0</td>\n </tr>\n <tr>\n <td>3.1</td>\n <td>0.6.1</td>\n <tr>\n <td>3.2</td>\n <td>0.7.1</td>\n </tr>\n <tr>\n <td>4.0</td>\n <td>0.8.1</td>\n </tr>\n <tr>\n <td>4.1</td>\n <td>0.8.4</td>\n </tr>\n <tr>\n <td>4.2</td>\n <td>0.9.2</td>\n </tr>\n <tr>\n <td>5.0</td>\n <td>0.9.4</td>\n </tr>\n </tbody>\n</table>\n\n## Python 3 Support\n\nDjango PGViews Redux only officially supports Python 3.7+, it might work on 3.6, but there's no guarantees.\n\n",
"bugtrack_url": null,
"license": null,
"summary": "Create and manage Postgres SQL Views in Django",
"version": "0.11.0",
"project_urls": {
"Homepage": "https://github.com/xelixdev/django-pgviews-redux",
"Repository": "https://github.com/xelixdev/django-pgviews-redux"
},
"split_keywords": [
"django",
" views",
" materialized views",
" postgres"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "5fcd349ce6ce7aa8b82be345493002d56ced7725f5f30c177ef7702b070778fa",
"md5": "93d0bbc9a1aba0e9aa6afe8fe88867e4",
"sha256": "5bbc4c429e2a12c8ccaa5f1b2d02a7ba1bce9a3bca493bf8ec1f9d502bb8667e"
},
"downloads": -1,
"filename": "django_pgviews_redux-0.11.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "93d0bbc9a1aba0e9aa6afe8fe88867e4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 18865,
"upload_time": "2024-11-27T09:11:52",
"upload_time_iso_8601": "2024-11-27T09:11:52.584218Z",
"url": "https://files.pythonhosted.org/packages/5f/cd/349ce6ce7aa8b82be345493002d56ced7725f5f30c177ef7702b070778fa/django_pgviews_redux-0.11.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "7fb66ffd824b89f15d7f67b293aaa6e0bf28319bd708dc2b18959a3467a73509",
"md5": "dfc09f1f421d1c70d16e5bd19ed038e2",
"sha256": "2d7f8d3619544a8bedadde5e685ab380b9baa04759e44ab6493b642beeb2dd31"
},
"downloads": -1,
"filename": "django_pgviews_redux-0.11.0.tar.gz",
"has_sig": false,
"md5_digest": "dfc09f1f421d1c70d16e5bd19ed038e2",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 18504,
"upload_time": "2024-11-27T09:11:53",
"upload_time_iso_8601": "2024-11-27T09:11:53.814945Z",
"url": "https://files.pythonhosted.org/packages/7f/b6/6ffd824b89f15d7f67b293aaa6e0bf28319bd708dc2b18959a3467a73509/django_pgviews_redux-0.11.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-27 09:11:53",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "xelixdev",
"github_project": "django-pgviews-redux",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "django-pgviews-redux"
}