diff options
| author | CoprDistGit <infra@openeuler.org> | 2023-05-05 15:29:38 +0000 |
|---|---|---|
| committer | CoprDistGit <infra@openeuler.org> | 2023-05-05 15:29:38 +0000 |
| commit | 49b2ddf04e0ff6448d67b19cde1e0372de1dbdfb (patch) | |
| tree | 458f4655bc5538dcaee73d4c6e02d9c60e157603 | |
| parent | fa134fb8f65fc1653e38c97afb5cd29d392482a1 (diff) | |
automatic import of python-django-pgviews-reduxopeneuler20.03
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | python-django-pgviews-redux.spec | 1485 | ||||
| -rw-r--r-- | sources | 1 |
3 files changed, 1487 insertions, 0 deletions
@@ -0,0 +1 @@ +/django-pgviews-redux-0.9.2.tar.gz diff --git a/python-django-pgviews-redux.spec b/python-django-pgviews-redux.spec new file mode 100644 index 0000000..b2c9f89 --- /dev/null +++ b/python-django-pgviews-redux.spec @@ -0,0 +1,1485 @@ +%global _empty_manifest_terminate_build 0 +Name: python-django-pgviews-redux +Version: 0.9.2 +Release: 1 +Summary: Create and manage Postgres SQL Views in Django +License: Public Domain +URL: https://github.com/xelixdev/django-pgviews-redux +Source0: https://mirrors.nju.edu.cn/pypi/web/packages/84/49/f6b41117fec8916040e460075d61c6c6b5837992c5fc25dd1b701811eea9/django-pgviews-redux-0.9.2.tar.gz +BuildArch: noarch + + +%description +# SQL Views for Postgres + +[](https://github.com/psf/black) + +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 + +### 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. + +### Custom Schema + +You can define any table name you wish for your views. They can even live inside your own custom +[PostgreSQL schema](http://www.postgresql.org/docs/current/static/ddl-schemas.html). + +```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> + </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. + + + + +%package -n python3-django-pgviews-redux +Summary: Create and manage Postgres SQL Views in Django +Provides: python-django-pgviews-redux +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: python3-pip +%description -n python3-django-pgviews-redux +# SQL Views for Postgres + +[](https://github.com/psf/black) + +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 + +### 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. + +### Custom Schema + +You can define any table name you wish for your views. They can even live inside your own custom +[PostgreSQL schema](http://www.postgresql.org/docs/current/static/ddl-schemas.html). + +```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> + </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. + + + + +%package help +Summary: Development documents and examples for django-pgviews-redux +Provides: python3-django-pgviews-redux-doc +%description help +# SQL Views for Postgres + +[](https://github.com/psf/black) + +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 + +### 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. + +### Custom Schema + +You can define any table name you wish for your views. They can even live inside your own custom +[PostgreSQL schema](http://www.postgresql.org/docs/current/static/ddl-schemas.html). + +```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> + </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. + + + + +%prep +%autosetup -n django-pgviews-redux-0.9.2 + +%build +%py3_build + +%install +%py3_install +install -d -m755 %{buildroot}/%{_pkgdocdir} +if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi +if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi +if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi +if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi +pushd %{buildroot} +if [ -d usr/lib ]; then + find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/lib64 ]; then + find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/bin ]; then + find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/sbin ]; then + find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst +fi +touch doclist.lst +if [ -d usr/share/man ]; then + find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst +fi +popd +mv %{buildroot}/filelist.lst . +mv %{buildroot}/doclist.lst . + +%files -n python3-django-pgviews-redux -f filelist.lst +%dir %{python3_sitelib}/* + +%files help -f doclist.lst +%{_docdir}/* + +%changelog +* Fri May 05 2023 Python_Bot <Python_Bot@openeuler.org> - 0.9.2-1 +- Package Spec generated @@ -0,0 +1 @@ +01599a4c174b5bbef559d4e202c6671d django-pgviews-redux-0.9.2.tar.gz |
