Skip to main content

SQL Views for Postgres

Circle CI

Adds first-class support for PostgreSQL Views in the Django ORM

Installation

Install via pip:

pip install django-postgres-views

Add to installed applications in settings.py:

INSTALLED_APPS = (
  # ...
  'django_postgres_views',
)

Examples

from django.db import models

from django_postgres_views 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:

CREATE VIEW myapp_preferredcustomer AS
SELECT * FROM myapp_customer WHERE is_preferred = TRUE;

To create all your views, run python manage.py sync_postgres_views

You can also specify field names, which will map onto fields in your View:

from django_postgres_views 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:

from django_postgres_views 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-postgres-views can take a projection to figure out what fields it needs to map onto for a view. To use this, set the projection attribute:

from django_postgres_views 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_postgres_views --force

This will forcibly update any views that conflict with your new SQL.

Migrations

When running migrations, we will automatically sync your new views using a postmigration signal, if at any moment you want to override this functionality you can supplement your own by adding the full path to PGVIEW_SYNC_VIEW_PATH with your own migration command.

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_postgres_views --force.

Note: Views are synced after the Django application has migrated and adding models to the dependency list will cause syncing to fail.

Example:

from django_postgres_views 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 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 and call the refresh function.

Example:

from django_postgres_views 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()

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:

from django_postgres_views 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)

Custom Schema

You can define any table name you wish for your views. They can even live inside your own custom PostgreSQL schema.

from django_postgres_views 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

Sync Listeners

django-postgres-views 0.0.1 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

Release files for django-postgres-views 0.0.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-postgres-views 0.0.4
File Size Uploaded
django-postgres-views-0.0.4.tar.gz 13.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-postgres-views 0.0.4
File Interpreter ABI Platform
django_postgres_views-0.0.4-py3-none-any.whl Python 3 none any Details

Total release size:26.7 kB

Release files / django-postgres-views-0.0.4.tar.gz

Download URL django-postgres-views-0.0.4.tar.gz
Size 13.0 kB
Tags Source
SHA-256 checksum
How to use checksums
80adc820d43d02c540b096c9a262f17e0a8a463c62cff7f44697d86389bb6679
BLAKE2b-256 checksum
How to use checksums
43dffca92ebc498bfce03ca2c2c607c3cebba4193506eca99aad3a0461d64d34
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.2 CPython/3.9.5

Release files / django_postgres_views-0.0.4-py3-none-any.whl

Download URL django_postgres_views-0.0.4-py3-none-any.whl
Size 13.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
788ee8d5f1bb28266374c0d5ec9d917ba007b4393f090c8dc8bc443b2f7aa4bc
BLAKE2b-256 checksum
How to use checksums
1435650b85ea8ba4e706beac54cf7c4c6981f1500f406ec4c6bcfc0a3d1f0024
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.2 CPython/3.9.5

Release history Release notifications | RSS feed

This release

0.0.4 This release

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page