跳转到主要内容

在Django中创建和管理Postgres SQL视图

项目描述

Postgres SQL视图

在Django ORM中为PostgreSQL视图添加了一级支持。由mypebble创建的原始django-pgviews的分支,支持Django 3.2+。

安装

通过pip安装

pip install django-pgviews-redux

将应用程序添加到settings.py中的已安装应用程序

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

示例

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

注意 在Meta中包含managed = False非常重要,这样Django 1.7迁移就不会尝试为该视图创建数据库表。

此SQL生成的可能如下所示

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

要创建所有视图,运行python manage.py sync_pgviews

您还可以指定字段名称,它们将映射到视图中的字段

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

用法

要将映射到视图,只需扩展 pg_views.view.View,将 SQL 赋值给 sql 参数并定义一个 db_table。您必须在 Meta 类中始终设置 managed = False

视图可以通过多种方式创建

  1. 定义映射到视图输出的字段
  2. 定义一个描述视图字段的投影

定义字段

就像使用任何 Django 模型一样定义字段

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'

定义投影

django-pgviews 可以接受一个投影来确定它需要映射到视图的字段。要使用此功能,设置 projection 属性

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

这会将 myapp.Customer 上的所有字段应用到 PreferredCustomer

特性

更新视图

有时您的模型发生变化,您需要数据库视图反映新的数据。更新视图逻辑就像修改底层 SQL 并运行

python manage.py sync_pgviews --force

这将强制更新与您的新 SQL 冲突的任何视图

依赖项

您可以指定您依赖的其他视图。这确保了其他视图在之前安装。使用依赖项还确保在使用 sync_pgviews --force 时正确刷新您的视图。

注意:视图在 Django 应用迁移后同步,将模型添加到依赖列表会导致同步失败。

示例

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

物化视图

Postgres 9.3 及更高版本支持 物化视图,允许您缓存视图的结果,从而可能使它们加载更快。

但是,您需要手动刷新视图。为此,您可以附加 信号 并调用刷新函数。

示例

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

并发刷新

Postgres 9.4 及更高版本允许在存在唯一索引的情况下并发刷新物化视图,而不会阻塞读取。要启用并发刷新,指定可以用于物化视图唯一索引的列名称。唯一索引可以定义在物化视图的多个列上。一旦启用,将 concurrently=True 传递给模型的刷新方法将导致 postgres 并发执行刷新。(请注意,刷新方法本身会阻塞,直到刷新完成;并发刷新在物化视图在另一个进程或线程中更新时最有用。)

示例

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)

索引

由于物化视图不是通过通常的 Django 模型字段定义的,因此在那里定义的任何索引都不会创建在物化视图中。幸运的是,Django 提供了一个名为 indexes 的 Meta 选项,可以用于向模型添加自定义索引。pg_views 支持使用此选项在物化视图中定义索引。

在以下示例中,将创建一个索引,位于 name 列上。post_code 字段定义上的 db_index=True 将被忽略。

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

物化视图可以创建为有数据或无数据。默认情况下,它们是有数据的,但是 pg_views 支持通过为 pg.MaterializedView 类定义 with_data = False 来创建无数据的物化视图。此类视图在第一次刷新之前不支持查询(引发 django.db.utils.OperationalError)。

示例

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)

条件性物化视图重建

由于所有物化视图在运行 migrate 时都会被重新创建,即使视图的定义没有发生变化,这也可能导致过时的重新创建。为了避免这种情况,版本 0.7.0 及更高版本包含一个功能,该功能会检查数据库中现有的物化视图定义(如果存在的话),并将定义与您当前在 pg.MaterializedView 子类中定义的进行比较。如果定义完全匹配,则会跳过物化视图的重新创建。

通过将 Django 设置中的 MATERIALIZED_VIEWS_CHECK_SQL_CHANGED 设置为 True,可以启用此功能,使其在运行 migrate 时生效。命令 sync_pgviews 也使用此设置,但同时还具有开关 --enable-materialized-views-check-sql-changed--disable-materialized-views-check-sql-changed,这些开关将覆盖此命令的设置。

此功能还考虑了索引。当认为视图不需要重新创建时,过程仍会检查表上的索引,并删除任何额外的索引并创建任何缺失的索引。这种协调是通过索引名称完成的,所以如果您为索引使用了自定义名称,那么在内容变化而名称不变的情况下,它可能不会得到更新。

模式

默认情况下,视图将在数据库的模式中创建,这通常是 public。该包支持通过使用选项("OPTIONS": {"options": "-c search_path=custom_schema"})在设置中定义数据库的模式。

如果使用,该包还支持 django-tenants

如果与数据库的默认模式不同,可以为视图显式定义模式,如下所示

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

动态视图 SQL

如果您需要动态视图 SQL(例如,如果它需要从中获取设置的值),您可以通过覆盖视图上的 run_sql 类方法来返回 SQL。该方法应返回一个 namedtuple ViewSQL,其中包含查询和可能传递给 cursor.execute 调用的参数。参数可以是 None 或查询参数的列表。

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

同步监听器

django-pgviews 0.5.0 添加了监听 post_sync 事件的能力。

view_synced

每次视图与数据库同步时都会触发。

提供参数

  • sender - 视图类
  • update - 是否需要更新的视图
  • force - 是否传递了 force
  • status - 创建视图的结果,例如 EXISTSFORCE_REQUIRED
  • has_changed - 视图是否需要更改

all_views_synced

在所有 Postgres 视图同步后发送。

提供参数

  • sender - 总是 None

多个数据库

django-pgviews 可以使用多个数据库。类似于 Django 的 migrate 管理命令,我们的命令(clear_pgviewsrefresh_pgviewssync_pgviews)一次只操作一个数据库。您可以通过提供 --database 选项来指定要同步的数据库。例如

python manage.py sync_pgviews  # uses default db
python manage.py sync_pgviews --database=myotherdb

除非使用自定义路由器,否则 django-pgviews 将将所有视图同步到指定的数据库。如果您想自动与多个数据库交互,则需要采取一些额外的步骤。请参阅 Django 的 自动数据库路由 以将视图固定到特定数据库。

Django 兼容性

Django 版本 Django-PGView 版本
1.4 及以下 不受支持
1.5 0.0.1
1.6 0.0.3
1.7 0.0.4
1.9 0.1.0
1.10 0.2.0
2.2 0.6.0
3.0 0.6.0
3.1 0.6.1
3.2 0.7.1
4.0 0.8.1
4.1 0.8.4
4.2 0.9.2
5.0 0.9.4

Python 3 支持

Django PGViews Redux 仅官方支持 Python 3.7+,它可能在 3.6 上工作,但没有保证。

项目详细信息


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源代码分发

django_pgviews_redux-0.10.1.tar.gz (18.2 kB 查看哈希值)

上传时间 源代码

构建分发

django_pgviews_redux-0.10.1-py3-none-any.whl (18.7 kB 查看哈希值)

上传时间 Python 3

由支持