Package: django-pgviews-schema
SQL Views for PostgresAdds first-class support forPostgreSQL Viewsin the Django ORM.
Fork of thedjango-pgviews-reduxbymypebblewith support for Django 3.2+ and schema postgres.InstallationInstall via pip:pip install django-pgviews-schemaAdd to installed applications in settings.py:INSTALLED_APPS=(# ...'django_pgviews',)Examplesfromdjango.dbimportmodelsfromdjango_pgviewsimportviewaspgclassCustomer(models.Model):name=models.CharField(max_length=100)post_code=models.CharField(max_length=20)is_preferred=models.BooleanField(default=False)classMeta:app_label='myapp'classPreferredCustomer(pg.View):projection=['myapp.Customer.*',]dependencies=['myapp.OtherView',]sql="""SELECT * FROM myapp_customer WHERE is_preferred = TRUE;"""classMeta:app_label='myapp'db_table='myapp_preferredcustomer'managed=FalseNOTEIt is important that we include themanaged = Falsein theMetaso
Django 1.7 migrations don't attempt to create DB tables for this view.The SQL produced by this might look like:CREATEVIEWmyapp_preferredcustomerASSELECT*FROMmyapp_customerWHEREis_preferred=TRUE;To create all your views, runpython manage.py sync_pgviewsYou can also specify field names, which will map onto fields in your View:fromdjango_pgviewsimportviewaspgVIEW_SQL="""SELECT name, post_code FROM myapp_customer WHERE is_preferred = TRUE"""classPreferredCustomer(pg.View):name=models.CharField(max_length=100)post_code=models.CharField(max_length=20)sql=VIEW_SQLUsageTo map onto a View, simply extendpg_views.view.View, assign SQL to thesqlargument and define adb_table. You mustalwayssetmanaged = Falseon theMetaclass.Views can be created in a number of ways:Define fields to map onto the VIEW outputDefine a projection that describes the VIEW fieldsDefine FieldsDefine the fields as you would with any Django Model:fromdjango_pgviewsimportviewaspgVIEW_SQL="""SELECT name, post_code FROM myapp_customer WHERE is_preferred = TRUE"""classPreferredCustomer(pg.View):name=models.CharField(max_length=100)post_code=models.CharField(max_length=20)sql=VIEW_SQLclassMeta:managed=Falsedb_table='my_sql_view'Define Projectiondjango-pgviewscan take a projection to figure out what fields it needs to
map onto for a view. To use this, set theprojectionattribute:fromdjango_pgviewsimportviewaspgclassPreferredCustomer(pg.View):projection=['myapp.Customer.*',]sql="""SELECT * FROM myapp_customer WHERE is_preferred = TRUE;"""classMeta:db_table='my_sql_view'managed=FalseThis will take all fields onmyapp.Customerand apply them toPreferredCustomerFeaturesUpdating ViewsSometimes 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 --forceThis will forcibly update any views that conflict with your new SQL.set DEFAULT_SCHEMA in your settings if the currentSchema postgres is different of "public"DependenciesYou 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 usingsync_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:fromdjango_pgviewsimportviewaspgclassPreferredCustomer(pg.View):dependencies=['myapp.OtherView',]sql="""SELECT * FROM myapp_customer WHERE is_preferred = TRUE;"""classMeta:app_label='myapp'db_table='myapp_preferredcustomer'managed=FalseMaterialized ViewsPostgres 9.3 and up supportsmaterialized viewswhich 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 attachsignalsand call the refresh function.Example:fromdjango_pgviewsimportviewaspgVIEW_SQL="""SELECT name, post_code FROM myapp_customer WHERE is_preferred = TRUE"""classCustomer(models.Model):name=models.CharField(max_length=100)post_code=models.CharField(max_length=20)is_preferred=models.BooleanField(default=True)classPreferredCustomer(pg.MaterializedView):name=models.CharField(max_length=100)post_code=models.CharField(max_length=20)sql=VIEW_SQL@receiver(post_save,sender=Customer)defcustomer_saved(sender,action=None,instance=None,**kwargs):PreferredCustomer.refresh()Concurrent refreshPostgres 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, passingconcurrently=Trueto 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:fromdjango_pgviewsimportviewaspgVIEW_SQL="""SELECT id, name, post_code FROM myapp_customer WHERE is_preferred = TRUE"""classPreferredCustomer(pg.MaterializedView):concurrent_index='id, post_code'sql=VIEW_SQLname=models.CharField(max_length=100)post_code=models.CharField(max_length=20)@receiver(post_save,sender=Customer)defcustomer_saved(sender,action=None,instance=None,**kwargs):PreferredCustomer.refresh(concurrently=True)IndexesAs 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 calledindexeswhich can be used to add custom
indexes to models.pg_viewssupports defining indexes on materialized views using this option.In the following example, one index will be created, on thenamecolumn. Thedb_index=Trueon the field definition
forpost_codewill get ignored.fromdjango_pgviewsimportviewaspgVIEW_SQL="""SELECT id, name, post_code FROM myapp_customer WHERE is_preferred = TRUE"""classPreferredCustomer(pg.MaterializedView):sql=VIEW_SQLname=models.CharField(max_length=100)post_code=models.CharField(max_length=20,db_index=True)classMeta:managed=False# don't forget this, otherwise Django will think it's a regular modelindexes=[models.Index(fields=["name"]),]WITH NO DATAMaterialized views can be created either with or without data. By default, they are created with data, howeverpg_viewssupports creating materialized views without data, by definingwith_data = Falsefor thepg.MaterializedViewclass. Such views then do not support querying until the first
refresh (raisingdjango.db.utils.OperationalError).Example:fromdjango_pgviewsimportviewaspgclassPreferredCustomer(pg.MaterializedView):concurrent_index='id, post_code'sql="""SELECT id, name, post_code FROM myapp_customer WHERE is_preferred = TRUE"""with_data=Falsename=models.CharField(max_length=100)post_code=models.CharField(max_length=20)Conditional materialized views recreateSince all materialized views are recreated on runningmigrate, 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 yourpg.MaterializedViewsubclass. If the definition matches
exactly, the re-create of materialized view is skipped.This feature is enabled by setting theMATERIALIZED_VIEWS_CHECK_SQL_CHANGEDin your Django settings toTrue,
which enables the feature when runningmigrate. The commandsync_pgviewsuses this setting as well,
however it also has switches--enable-materialized-views-check-sql-changedand--disable-materialized-views-check-sql-changedwhich 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 SchemaYou can define any table name you wish for your views. They can even live inside your own customPostgreSQL schema.fromdjango_pgviewsimportviewaspgclassPreferredCustomer(pg.View):sql="""SELECT * FROM myapp_customer WHERE is_preferred = TRUE;"""classMeta:db_table='my_custom_schema.preferredcustomer'managed=FalseDynamic View SQLIf you need a dynamic view SQL (for example if it needs a value from settings in it), you can override therun_sqlclassmethod on the view to return the SQL. The method should return a namedtupleViewSQL, which contains the query
and potentially the params tocursor.executecall. Params should be either None or a list of parameters for the query.fromdjango.confimportsettingsfromdjango_pgviewsimportviewaspgclassPreferredCustomer(pg.View):@classmethoddefget_sql(cls):returnpg.ViewSQL("""SELECT * FROM myapp_customer WHERE is_preferred = TRUE and created_at >= %s;""",[settings.MIN_PREFERRED_CUSTOMER_CREATED_AT])classMeta:db_table='preferredcustomer'managed=FalseSync Listenersdjango-pgviews 0.5.0 adds the ability to listen to when apost_syncevent has
occurred.view_syncedFired every time a VIEW is synchronised with the database.Provides args:sender- View Classupdate- Whether the view to be updatedforce- Whetherforcewas passedstatus- The result of creating the view e.g.EXISTS,FORCE_REQUIREDhas_changed- Whether the view had to changeall_views_syncedSent after all Postgres VIEWs are synchronised.Provides args:sender- AlwaysNoneMultiple databasesdjango-pgviews can use multiple databases. Similar to Django'smigratemanagement 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--databaseoption. For example:pythonmanage.pysync_pgviews# uses default dbpythonmanage.pysync_pgviews--database=myotherdbUnless 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'sAutomatic database
routingto pin views to specific databases.Django CompatibilityDjango VersionDjango-PGView Version1.4 and downUnsupported1.50.0.11.60.0.31.70.0.41.90.1.01.100.2.02.20.6.03.00.6.03.10.6.13.20.7.14.00.8.14.10.8.44.20.9.25.00.9.4Python 3 SupportDjango PGViews Redux only officially supports Python 3.7+, it might work on 3.6, but there's no guarantees.