Mastering Django CI/CD using GitHub Actions
Testing and delivery is a common repetitive cycle in application development. This cycle consumes more time and resources as we fix or add more features to our application. Automating this repetitive process will reduce the time spent in application ...
Read: https://thecodeway.hashnode.dev/mastering-django-cicd-using-github-actions
Testing and delivery is a common repetitive cycle in application development. This cycle consumes more time and resources as we fix or add more features to our application. Automating this repetitive process will reduce the time spent in application ...
Read: https://thecodeway.hashnode.dev/mastering-django-cicd-using-github-actions
Django Unleashed Framework
Photo
Интеграция Django с материализованными представлениями PostgreSQL
Django — один из самых популярных веб-фреймворков, написанных на Python. Он следует принципу “Не повторяйся” (DRY) и быстро решает задачи веб-разработки с помощью набора инструментов, библиотек и соглашений.
Действительно продвинутым Django делает встроенная поддержка ORM (механизма объектно-реляционного отображения), который разрабатывает команда Django. Модель данных в Django представлена в виде классов Python, в которых можно создавать и запрашивать таблицы базы данных без использования необработанных SQL-запросов.
В этой статье мы рассмотрим, как интегрировать Django с материализованными представлениями PostgreSQL. Настроим ORM Django на полную поддержку материализованных представлений, определив модели материализованных представлений в проекте Django таким образом, чтобы изменения модели могли быть обнаружены системой миграции Django.
Что такое материализованные представления?
Представления в движках баз данных — это виртуальные таблицы, предоставляющие возможность выполнять запросы и получать данные из существующих таблиц. Они позволяют упростить процесс выполнения сложных запросов. Однако использование представлений, динамически выполняющих первичный запрос (primary query) при каждом обращении, может идти в ущерб производительности.
Именно здесь в игру вступают материализованные представления. В отличие от обычных, материализованные представления хранят результаты базового запроса в физической таблице. Другими словами, материализованные представления компилируют результаты запроса и сохраняют их как отдельную сущность в базе данных. Поэтому получить доступ к материализованным представлениям можно намного быстрее, чем к обычным представлениям, предполагающим повторное выполнение запроса при каждом обращении.
При этом необходимо учитывать некоторые недостатки материализованных представлений.
1. Данные в материализованных представлениях могут устаревать, если не обновлять их регулярно, что может привести к несоответствиям.
2. Необходимо учитывать требования к хранению, поскольку материализованные представления потребляют дисковое пространство для хранения данных.
3. Материализованные представления добавляют сложности системе, так как требуют обслуживания при обновлении базовых таблиц.
Создание материализованного представления в PostgreSQL
Материализованное представление можно создать с помощью следующей команды SQL:
CREATE MATERIALIZED VIEW popular_posts AS
SELECT * FROM posts WHERE rating > 200;
Этот запрос создаст новое материализованное представление из базового запроса с кэшированным результатом. При этом обновленные данные в исходной таблице не будут автоматически доступны в материализованном представлении, так как их нужно будет обновить вручную с помощью следующей команды:
REFRESH MATERIALIZED VIEW popular_posts;
Эта команда обновит материализованное представление и заполнит свежими данными базовую таблицу, а также перекомпилирует определение базового запроса материализованного представления.
Проблема
Поскольку в настоящее время ORM Django поддерживает только таблицы баз данных, мне стало интересно, можно ли настроить ORM Django для поддержки материализованных представлений. Проведя исследование, я понял, что можно добиться этого несколькими способами, но не был удовлетворен ни одним решением, так как ни одна из настроек не могла полностью поддерживать материализованные представления для использования в качестве моделей Django.
Одно из решений предполагало создание пустого файла миграции вручную и указание SQL-запроса для создания материализованного представления с помощью команды
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("api", "0001_initial"),
]
operations = [
migrations.RunSQL(
sql="CREATE MATERIALIZED VIEW popular_posts AS SELECT * FROM posts WHERE rating > 200",
reverse_sql="DROP MATERIALIZED VIEW popular_posts"
)
]
Кроме того, нео[...]
Django — один из самых популярных веб-фреймворков, написанных на Python. Он следует принципу “Не повторяйся” (DRY) и быстро решает задачи веб-разработки с помощью набора инструментов, библиотек и соглашений.
Действительно продвинутым Django делает встроенная поддержка ORM (механизма объектно-реляционного отображения), который разрабатывает команда Django. Модель данных в Django представлена в виде классов Python, в которых можно создавать и запрашивать таблицы базы данных без использования необработанных SQL-запросов.
В этой статье мы рассмотрим, как интегрировать Django с материализованными представлениями PostgreSQL. Настроим ORM Django на полную поддержку материализованных представлений, определив модели материализованных представлений в проекте Django таким образом, чтобы изменения модели могли быть обнаружены системой миграции Django.
Что такое материализованные представления?
Представления в движках баз данных — это виртуальные таблицы, предоставляющие возможность выполнять запросы и получать данные из существующих таблиц. Они позволяют упростить процесс выполнения сложных запросов. Однако использование представлений, динамически выполняющих первичный запрос (primary query) при каждом обращении, может идти в ущерб производительности.
Именно здесь в игру вступают материализованные представления. В отличие от обычных, материализованные представления хранят результаты базового запроса в физической таблице. Другими словами, материализованные представления компилируют результаты запроса и сохраняют их как отдельную сущность в базе данных. Поэтому получить доступ к материализованным представлениям можно намного быстрее, чем к обычным представлениям, предполагающим повторное выполнение запроса при каждом обращении.
При этом необходимо учитывать некоторые недостатки материализованных представлений.
1. Данные в материализованных представлениях могут устаревать, если не обновлять их регулярно, что может привести к несоответствиям.
2. Необходимо учитывать требования к хранению, поскольку материализованные представления потребляют дисковое пространство для хранения данных.
3. Материализованные представления добавляют сложности системе, так как требуют обслуживания при обновлении базовых таблиц.
Создание материализованного представления в PostgreSQL
Материализованное представление можно создать с помощью следующей команды SQL:
CREATE MATERIALIZED VIEW popular_posts AS
SELECT * FROM posts WHERE rating > 200;
Этот запрос создаст новое материализованное представление из базового запроса с кэшированным результатом. При этом обновленные данные в исходной таблице не будут автоматически доступны в материализованном представлении, так как их нужно будет обновить вручную с помощью следующей команды:
REFRESH MATERIALIZED VIEW popular_posts;
Эта команда обновит материализованное представление и заполнит свежими данными базовую таблицу, а также перекомпилирует определение базового запроса материализованного представления.
Проблема
Поскольку в настоящее время ORM Django поддерживает только таблицы баз данных, мне стало интересно, можно ли настроить ORM Django для поддержки материализованных представлений. Проведя исследование, я понял, что можно добиться этого несколькими способами, но не был удовлетворен ни одним решением, так как ни одна из настроек не могла полностью поддерживать материализованные представления для использования в качестве моделей Django.
Одно из решений предполагало создание пустого файла миграции вручную и указание SQL-запроса для создания материализованного представления с помощью команды
RunSQL
.from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("api", "0001_initial"),
]
operations = [
migrations.RunSQL(
sql="CREATE MATERIALIZED VIEW popular_posts AS SELECT * FROM posts WHERE rating > 200",
reverse_sql="DROP MATERIALIZED VIEW popular_posts"
)
]
Кроме того, нео[...]
❤1
Django Unleashed Framework
Интеграция Django с материализованными представлениями PostgreSQL Django — один из самых популярных веб-фреймворков, написанных на Python. Он следует принципу “Не повторяйся” (DRY) и быстро решает задачи веб-разработки с помощью набора инструментов, библиотек…
бходимо создать класс модели, определив поля точно так же, как и в запросе материализованного представления. Класс модели должен иметь:
* опцию
* опцию
class PopularPosts(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
tag_names = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
rating = models.PositiveIntegerField()
class Meta:
managed = False
db_table = 'popular_posts'
На мой взгляд, данный подход не является оптимальным решением, эффективным в долгосрочной перспективе, особенно при частых обновлениях или изменениях атрибутов класса модели. Когда потребуется изменение атрибутов класса модели, нужно переписывать новый необработанный запрос в новом файле миграции, что может занять много времени и чревато ошибками. Более того, это может стать громоздкой задачей, особенно по мере роста и развития приложения с течением времени.
Как интегрировать Django с материализованными представлениями
Чтобы интегрировать Django с материализованными представлениями, в нашем примере будет применено несколько настроек.
* Создание пользовательского класса модели.
* Создание пользовательского класса поля модели.
* Создание пользовательского движка базы данных для PostgreSQL.
* Соединение всего созданного в проекте.
Шаг 1. Создание пользовательского класса модели
Первым шагом к интеграции Django с материализованными представлениями в PostgreSQL является создание пользовательского класса модели с целью сообщить Django, что модель предназначена для материализованного представления.
Существует несколько способов определить, является ли класс модели материализованным представлением. Я создал класс модели таким образом, чтобы он принимал два пользовательских атрибута класса Meta внутри класса модели.
*
*
class MaterializedViewModel(models.Model):
class Meta:
materialized_view = True
view_parent_model = 'app_label.Model'
Обычно Django не позволяет определять пользовательские атрибуты своего класса Meta внутри классов модели, выдавая исключение TypeError:
raise TypeError(
TypeError: 'class Meta' got invalid attribute(s): materialized_view
Проведя исследование, я обнаружил, что в Django можно использовать только атрибуты Meta, которые статически определены в кортеже
# djangoProject/__init__.py
import django.db.models.options as options
options.DEFAULT_NAMES += ('materialized_view', 'view_parent_model',)
Шаг 2. Создание пользовательского поля модели
Следующий шаг — создание пользовательского поля модели, специально предназначенного для класса модели материализованного представления, которое будет иметь два пользовательских атрибута. Каждое из пользовательских полей, указанных в классе модели, впоследствии будет преобразовано в необработанные SQL-запросы для генерации SQL, что необходимо для создания материализованного представления.
from[...]
* опцию
managed
, которую нужно установить в false в определении мета-класса для указания того, что схема базы данных не должна управляться Django, чтобы предотвратить создание новой таблицы системой миграции;* опцию
db_table
, которая должна быть явно установлена в соответствии с именем материализованного представления.class PopularPosts(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
tag_names = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
rating = models.PositiveIntegerField()
class Meta:
managed = False
db_table = 'popular_posts'
На мой взгляд, данный подход не является оптимальным решением, эффективным в долгосрочной перспективе, особенно при частых обновлениях или изменениях атрибутов класса модели. Когда потребуется изменение атрибутов класса модели, нужно переписывать новый необработанный запрос в новом файле миграции, что может занять много времени и чревато ошибками. Более того, это может стать громоздкой задачей, особенно по мере роста и развития приложения с течением времени.
Как интегрировать Django с материализованными представлениями
Чтобы интегрировать Django с материализованными представлениями, в нашем примере будет применено несколько настроек.
* Создание пользовательского класса модели.
* Создание пользовательского класса поля модели.
* Создание пользовательского движка базы данных для PostgreSQL.
* Соединение всего созданного в проекте.
Шаг 1. Создание пользовательского класса модели
Первым шагом к интеграции Django с материализованными представлениями в PostgreSQL является создание пользовательского класса модели с целью сообщить Django, что модель предназначена для материализованного представления.
Существует несколько способов определить, является ли класс модели материализованным представлением. Я создал класс модели таким образом, чтобы он принимал два пользовательских атрибута класса Meta внутри класса модели.
*
materialized_view
указывает, что класс модели является материализованным представлением, а не таблицей.*
view_parent_model
указывает классу модели, какой реальный класс модели должен использоваться, чтобы создать базовый запрос для материализованного представления.class MaterializedViewModel(models.Model):
class Meta:
materialized_view = True
view_parent_model = 'app_label.Model'
Обычно Django не позволяет определять пользовательские атрибуты своего класса Meta внутри классов модели, выдавая исключение TypeError:
raise TypeError(
TypeError: 'class Meta' got invalid attribute(s): materialized_view
Проведя исследование, я обнаружил, что в Django можно использовать только атрибуты Meta, которые статически определены в кортеже
DEFAULT_NAMES
, который находится в модуле django.db.models.options
. Чтобы устранить это ограничение, я применил обходной путь, импортировав модуль options и переопределив переменную DEFAULT_NAMES
в файле __init__.py
модуля проекта перед наполнением приложений Django. Эта модификация обеспечивает поддержку пользовательских атрибутов в классе Meta.# djangoProject/__init__.py
import django.db.models.options as options
options.DEFAULT_NAMES += ('materialized_view', 'view_parent_model',)
Шаг 2. Создание пользовательского поля модели
Следующий шаг — создание пользовательского поля модели, специально предназначенного для класса модели материализованного представления, которое будет иметь два пользовательских атрибута. Каждое из пользовательских полей, указанных в классе модели, впоследствии будет преобразовано в необработанные SQL-запросы для генерации SQL, что необходимо для создания материализованного представления.
from[...]
Django Unleashed Framework
бходимо создать класс модели, определив поля точно так же, как и в запросе материализованного представления. Класс модели должен иметь: * опцию managed, которую нужно установить в false в определении мета-класса для указания того, что схема базы данных не…
django.db.models import fields
from django.db.models.expressions import Combinable, ExpressionWrapper, F
class MaterializedViewField(fields.Field):
def __init__(self, child, source=None, **kwargs):
super().__init__(**kwargs)
self.child = child
if isinstance(source, Combinable) or source is None:
self.source = source
elif isinstance(source, str):
self.source = ExpressionWrapper(F(source), output_field=child)
else:
self.source = None
def deconstruct(self):
"""
Переопределение метода deconstruct для включения атрибутов пользовательских полей в файлы миграции
при выполнении команды `makemigrations` в отншении модели материализованного представления.
"""
name, path, args, keywords = super().deconstruct()
keywords.update(
source=self.source, child=self.child
)
return name, path, args, keywords
Шаг 3. Создание пользовательского движка базы данных для PostgreSQL
Следующий шаг — создание пользовательского движка базы данных с пользовательским классом редактора. Этот движок отвечает за реализацию необходимой настройки схемы.
Чтобы настроить редактор схем баз данных Django, нужно определить новый движок базы данных в проекте и сослаться на него в атрибуте
Обратите внимание: пользовательский движок базы данных нужно поместить в каталог с файлом
За дополнительной информацией обратитесь к документации Django.
Ниже приведен пример структуры папок, где движок базы данных помещен в приложение Django под названием core и в подпапку backends.
project_root/
...
django_project/
__init__.py
settings.py
core/
__init__.py
backends/
__init__.py
db/
__init__.py
base.py
Следующий код является примером пользовательского движка базы данных (он расположен в файле
from django.apps import apps
from django.db.backends.postgresql import base
from django.db.models import QuerySet, options, Model
from api.fields import MaterializedViewField
class DatabaseSchemaEditor(base.DatabaseSchemaEditor):
sql_create_materialized_view = "CREATE MATERIALIZED VIEW %(table)s AS %(definition)s"
sql_delete_materialized_view = "DROP MATERIALIZED VIEW %(table)s"
sql_refresh_materialized_View = "REFRESH MATERIALIZED VIEW %(concurrently)s %(view)s"
@staticmethod
def model_meta(model: type[Model]) -> options.Options:
return model._meta
def _get_parent_model(self, model: type[Model]):
"""
Возвращает базовую модель представления, которая будет использоваться для генерации SQL материализованного представления.
"""
parent_model = getattr(self.model_meta(model), 'view_parent_model', None)
if parent_model:
return apps.get_model(*parent_model.split('.'))
def model_is_materialized_view(self, model: type[Model]) -> bool:
"""Проверяет, является ли класс модели моделью материализованного представления или обычной моделью django."""
return getattr(self.model_meta(model), 'materialized_view', False)
def get_queryset(self, model: Model, extra_field=None):
"""Генерирует набор запросов из предоставленной родительской модели и предоставленных полей."""
def append_field(_model_field):
if _model_field.source is None:
concrete_fields.append(_model_field.name)
else:
annotation_fields.update({_model_field.attname: _model_field.source})
concrete_fields = []
annotation_fields = dict()
for field_name, field in model.__dict__.items():
if hasattr(field, 'field'):
model_field: MaterializedViewField = field.field
if isinstance(model_field, MaterializedViewField):
append_field(model_field)
if extra_field:
append_field(extra_field)
return QuerySet(
model=self._get_parent_model(model)
).only(*concrete_fields).annotate(**annotation_fields).query
def create_model(self, model, extra_field=None):
if self.model_is_materialized_view(model):
sql = self.sql[...]
from django.db.models.expressions import Combinable, ExpressionWrapper, F
class MaterializedViewField(fields.Field):
def __init__(self, child, source=None, **kwargs):
super().__init__(**kwargs)
self.child = child
if isinstance(source, Combinable) or source is None:
self.source = source
elif isinstance(source, str):
self.source = ExpressionWrapper(F(source), output_field=child)
else:
self.source = None
def deconstruct(self):
"""
Переопределение метода deconstruct для включения атрибутов пользовательских полей в файлы миграции
при выполнении команды `makemigrations` в отншении модели материализованного представления.
"""
name, path, args, keywords = super().deconstruct()
keywords.update(
source=self.source, child=self.child
)
return name, path, args, keywords
Шаг 3. Создание пользовательского движка базы данных для PostgreSQL
Следующий шаг — создание пользовательского движка базы данных с пользовательским классом редактора. Этот движок отвечает за реализацию необходимой настройки схемы.
Чтобы настроить редактор схем баз данных Django, нужно определить новый движок базы данных в проекте и сослаться на него в атрибуте
ENGINE
записи DATABASES
в файле настроек проекта.Обратите внимание: пользовательский движок базы данных нужно поместить в каталог с файлом
base.py
и классом DatabaseWrapper
.За дополнительной информацией обратитесь к документации Django.
Ниже приведен пример структуры папок, где движок базы данных помещен в приложение Django под названием core и в подпапку backends.
project_root/
...
django_project/
__init__.py
settings.py
core/
__init__.py
backends/
__init__.py
db/
__init__.py
base.py
Следующий код является примером пользовательского движка базы данных (он расположен в файле
base.py
).from django.apps import apps
from django.db.backends.postgresql import base
from django.db.models import QuerySet, options, Model
from api.fields import MaterializedViewField
class DatabaseSchemaEditor(base.DatabaseSchemaEditor):
sql_create_materialized_view = "CREATE MATERIALIZED VIEW %(table)s AS %(definition)s"
sql_delete_materialized_view = "DROP MATERIALIZED VIEW %(table)s"
sql_refresh_materialized_View = "REFRESH MATERIALIZED VIEW %(concurrently)s %(view)s"
@staticmethod
def model_meta(model: type[Model]) -> options.Options:
return model._meta
def _get_parent_model(self, model: type[Model]):
"""
Возвращает базовую модель представления, которая будет использоваться для генерации SQL материализованного представления.
"""
parent_model = getattr(self.model_meta(model), 'view_parent_model', None)
if parent_model:
return apps.get_model(*parent_model.split('.'))
def model_is_materialized_view(self, model: type[Model]) -> bool:
"""Проверяет, является ли класс модели моделью материализованного представления или обычной моделью django."""
return getattr(self.model_meta(model), 'materialized_view', False)
def get_queryset(self, model: Model, extra_field=None):
"""Генерирует набор запросов из предоставленной родительской модели и предоставленных полей."""
def append_field(_model_field):
if _model_field.source is None:
concrete_fields.append(_model_field.name)
else:
annotation_fields.update({_model_field.attname: _model_field.source})
concrete_fields = []
annotation_fields = dict()
for field_name, field in model.__dict__.items():
if hasattr(field, 'field'):
model_field: MaterializedViewField = field.field
if isinstance(model_field, MaterializedViewField):
append_field(model_field)
if extra_field:
append_field(extra_field)
return QuerySet(
model=self._get_parent_model(model)
).only(*concrete_fields).annotate(**annotation_fields).query
def create_model(self, model, extra_field=None):
if self.model_is_materialized_view(model):
sql = self.sql[...]
Django Unleashed Framework
django.db.models import fields from django.db.models.expressions import Combinable, ExpressionWrapper, F class MaterializedViewField(fields.Field): def __init__(self, child, source=None, **kwargs): super().__init__(**kwargs) self.child = child if isinstance(source…
_create_materialized_view % {
'table': self.quote_name(self.model_meta(model).db_table),
'definition': self.get_queryset(model, extra_field=extra_field)
}
self.execute(sql)
else:
super().create_model(model)
def add_field(self, model: Model, field):
if self.model_is_materialized_view(model):
setattr(model, field.attname, field)
self.delete_model(model)
self.create_model(model, extra_field=field)
else:
super().add_field(model, field)
def remove_field(self, model, field):
if self.model_is_materialized_view(model):
delattr(model, field.attname)
self.delete_model(model)
self.create_model(model)
else:
super().remove_field(model, field)
def alter_field(self, model, old_field, new_field, strict=False):
if self.model_is_materialized_view(model):
delattr(model, old_field.attname)
self.delete_model(model)
self.create_model(model, extra_field=new_field)
else:
super().alter_field(model, old_field, new_field, strict)
def delete_model(self, model):
if self.model_is_materialized_view(model):
self.execute(
self.sql_delete_materialized_view % {
"table": self.model_meta(model).db_table
}
)
else:
super().delete_model(model)
def refresh_materialized_view(self, model: type[Model], concurrent=False):
"""
Выполняет запрос на обновление материализованного представления,
если было желательно заполнять данные представления по требованию.
"""
self.execute(self.sql_refresh_materialized_View % {
'view': model._meta.db_table,
'concurrently': 'CONCURRENTLY' if concurrent else ''
})
class DatabaseWrapper(base.DatabaseWrapper):
SchemaEditorClass = DatabaseSchemaEditor
Приведенный выше код переопределяет встроенный в Django класс схемы базы данных postgres для поддержки выполнения запросов, необходимых для создания материализованных представлений после выполнения команды migrate.
Короче говоря, все методы работы с базой данных были модифицированы, чтобы проверить, является ли модель моделью материализованного представления или моделью обычного класса. Если это модель материализованного представления, будет вызван метод
Наконец, добавим пользовательский движок базы данных в настройки базы данных проекта следующим образом:
DATABASES = {
'default': {
'ENGINE': 'core.backends.db',
'HOST': '<db-host',
'NAME': '<db-name',
'USER': '<user',
'PASSWORD': '<passwrod',
'PORT': 5432,
'ATOMIC_REQUESTS': True,
}
}
Шаг 4. Собираем все вместе
Теперь продемонстрирую использование материализованного представления на нескольких примерах моделей. Определение модели в основном представляет собой простую модель Post с отношением “многие ко многим” с моделями Comment и Tag, как показано в примере ниже:
class Tag(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'Comment by {self.author.username} on {self.post.title}'
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
Затем необходимо провести миграцию моделей, последовательно используя команды
$ python manage.py makemigraitons
$ python manage.py migrate
После того как все модели перенесены в базу данных, нужно создать определение модели материализованного представлен[...]
'table': self.quote_name(self.model_meta(model).db_table),
'definition': self.get_queryset(model, extra_field=extra_field)
}
self.execute(sql)
else:
super().create_model(model)
def add_field(self, model: Model, field):
if self.model_is_materialized_view(model):
setattr(model, field.attname, field)
self.delete_model(model)
self.create_model(model, extra_field=field)
else:
super().add_field(model, field)
def remove_field(self, model, field):
if self.model_is_materialized_view(model):
delattr(model, field.attname)
self.delete_model(model)
self.create_model(model)
else:
super().remove_field(model, field)
def alter_field(self, model, old_field, new_field, strict=False):
if self.model_is_materialized_view(model):
delattr(model, old_field.attname)
self.delete_model(model)
self.create_model(model, extra_field=new_field)
else:
super().alter_field(model, old_field, new_field, strict)
def delete_model(self, model):
if self.model_is_materialized_view(model):
self.execute(
self.sql_delete_materialized_view % {
"table": self.model_meta(model).db_table
}
)
else:
super().delete_model(model)
def refresh_materialized_view(self, model: type[Model], concurrent=False):
"""
Выполняет запрос на обновление материализованного представления,
если было желательно заполнять данные представления по требованию.
"""
self.execute(self.sql_refresh_materialized_View % {
'view': model._meta.db_table,
'concurrently': 'CONCURRENTLY' if concurrent else ''
})
class DatabaseWrapper(base.DatabaseWrapper):
SchemaEditorClass = DatabaseSchemaEditor
Приведенный выше код переопределяет встроенный в Django класс схемы базы данных postgres для поддержки выполнения запросов, необходимых для создания материализованных представлений после выполнения команды migrate.
Короче говоря, все методы работы с базой данных были модифицированы, чтобы проверить, является ли модель моделью материализованного представления или моделью обычного класса. Если это модель материализованного представления, будет вызван метод
get_queryset
, чтобы сгенерировать необработанный SQL-запрос из родительской модели для создания или обновления материализованного представления. В противном случае родительский метод будет вызван как обычно.Наконец, добавим пользовательский движок базы данных в настройки базы данных проекта следующим образом:
DATABASES = {
'default': {
'ENGINE': 'core.backends.db',
'HOST': '<db-host',
'NAME': '<db-name',
'USER': '<user',
'PASSWORD': '<passwrod',
'PORT': 5432,
'ATOMIC_REQUESTS': True,
}
}
Шаг 4. Собираем все вместе
Теперь продемонстрирую использование материализованного представления на нескольких примерах моделей. Определение модели в основном представляет собой простую модель Post с отношением “многие ко многим” с моделями Comment и Tag, как показано в примере ниже:
class Tag(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'Comment by {self.author.username} on {self.post.title}'
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
Затем необходимо провести миграцию моделей, последовательно используя команды
makemigrations
и migrate
.$ python manage.py makemigraitons
$ python manage.py migrate
После того как все модели перенесены в базу данных, нужно создать определение модели материализованного представлен[...]
Django Unleashed Framework
_create_materialized_view % { 'table': self.quote_name(self.model_meta(model).db_table), 'definition': self.get_queryset(model, extra_field=extra_field) } self.execute(sql) else: super().create_model(model) def add_field(self, model: Model, field): if s…
ия для модели Post.
class MaterializedViewBaseModel(models.Model):
class Meta:
abstract = True
@classmethod
def check(cls, **kwargs):
errors = super().check(**kwargs)
if not hasattr(cls._meta, 'materialized_view'):
errors.append(
checks.Error(
'The `view` attribute is required in materialized view meta class.',
obj=cls,
id='models.E100',
)
)
view_parent_model = getattr(cls._meta, 'view_parent_model', None)
if view_parent_model:
try:
apps.get_model(*getattr(cls._meta, 'view_parent_model').split('.'))
except (LookupError, ValueError) as e:
errors.append(
checks.Error(
f"Invalid `view_parent_model` format, {e}", obj=cls, id='models.E101'
)
)
else:
errors.append(
checks.Error(
'The `view_parent_model` attribute is required in materialized view meta class.',
obj=cls,
id='models.E101'
)
)
return errors
@classmethod
def refresh(cls, concurrent=False):
with connection.cursor() as cursor:
editor = cursor.db.schema_editor()
editor.refresh_materialized_view(cls, concurrent=concurrent)
class PostMaterializedView(MaterializedViewBaseModel):
post_id = MaterializedViewField(source='pk', child=models.IntegerField())
title = MaterializedViewField(child=models.CharField())
content = MaterializedViewField(child=models.CharField())
tag_names = MaterializedViewField(
source=aggregates.StringAgg('tags__name', delimiter="'; '", distinct=True),
child=models.CharField()
)
comments_count = MaterializedViewField(
source=functions.Coalesce(models.Count('comments', distinct=True), 0),
child=models.IntegerField()
)
comment_authors = MaterializedViewField(
source=aggregates.StringAgg(
'comments__author__first_name', delimiter="', '", distinct=True
),
child=models.CharField()
)
class Meta:
view_parent_model = 'api.Post'
materialized_view = True
constraints = [
models.UniqueConstraint(
models.F('post_id'), name='unique_post_id',
)
]
В приведенном выше примере пользовательские
Поле модели
Материализованный абстрактный класс в основном выполняет проверки пользовательских атрибутов класса
Наконец, последовательное выполнение команд
# Сгенерировано Django 4.2.1 2023.05.22 в 22:10
import api.fields
import django.contrib.postgres.aggregates.general
from django.db import migrations, models
import django.db.models.aggregates
import django.db.models.functions.comparison
class Migration(migrations.Migration):
dependencies = [
("api", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="PostMaterializedView",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"post_id",
api.fields.MaterializedViewField(
child=models.IntegerField(),
source=models.ExpressionWrapper(
models.F("pk"), output_field=models.IntegerField()
),
),
),
(
"title",
api.fields.MaterializedViewField(
child=models.CharField(), source=None
),
),
(
"content",
api.fields.MaterializedViewField(
child=models.CharField(), source=Non[...]
class MaterializedViewBaseModel(models.Model):
class Meta:
abstract = True
@classmethod
def check(cls, **kwargs):
errors = super().check(**kwargs)
if not hasattr(cls._meta, 'materialized_view'):
errors.append(
checks.Error(
'The `view` attribute is required in materialized view meta class.',
obj=cls,
id='models.E100',
)
)
view_parent_model = getattr(cls._meta, 'view_parent_model', None)
if view_parent_model:
try:
apps.get_model(*getattr(cls._meta, 'view_parent_model').split('.'))
except (LookupError, ValueError) as e:
errors.append(
checks.Error(
f"Invalid `view_parent_model` format, {e}", obj=cls, id='models.E101'
)
)
else:
errors.append(
checks.Error(
'The `view_parent_model` attribute is required in materialized view meta class.',
obj=cls,
id='models.E101'
)
)
return errors
@classmethod
def refresh(cls, concurrent=False):
with connection.cursor() as cursor:
editor = cursor.db.schema_editor()
editor.refresh_materialized_view(cls, concurrent=concurrent)
class PostMaterializedView(MaterializedViewBaseModel):
post_id = MaterializedViewField(source='pk', child=models.IntegerField())
title = MaterializedViewField(child=models.CharField())
content = MaterializedViewField(child=models.CharField())
tag_names = MaterializedViewField(
source=aggregates.StringAgg('tags__name', delimiter="'; '", distinct=True),
child=models.CharField()
)
comments_count = MaterializedViewField(
source=functions.Coalesce(models.Count('comments', distinct=True), 0),
child=models.IntegerField()
)
comment_authors = MaterializedViewField(
source=aggregates.StringAgg(
'comments__author__first_name', delimiter="', '", distinct=True
),
child=models.CharField()
)
class Meta:
view_parent_model = 'api.Post'
materialized_view = True
constraints = [
models.UniqueConstraint(
models.F('post_id'), name='unique_post_id',
)
]
В приведенном выше примере пользовательские
Options
класса Meta
были определены для информирования редактора схемы базы данных о том, что модель является материализованным представлением, и генерации SQL-запроса из модели Post
.Поле модели
MaterializedViewField
было использовано для определения полей модели, предоставляющих поле дочерней модели и атрибут источника. Важно отметить, что поле модели было специально разработано для поддержки различных выражений запросов базы данных, упомянутых в документации Django, включая F
, StringAggr
, Count
, Avg
, Sum
, Case
и так далее.Материализованный абстрактный класс в основном выполняет проверки пользовательских атрибутов класса
Meta
. Например, проверяет существование атрибутов materialized_view
и view_parent_model
и уточняет был ли view_parent_model
определен в правильном формате.Наконец, последовательное выполнение команд
makemigrations
и migrate
должно создать новое материализованное представление, легко используемое в Django для выполнения запросов и операций фильтрации.# Сгенерировано Django 4.2.1 2023.05.22 в 22:10
import api.fields
import django.contrib.postgres.aggregates.general
from django.db import migrations, models
import django.db.models.aggregates
import django.db.models.functions.comparison
class Migration(migrations.Migration):
dependencies = [
("api", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="PostMaterializedView",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"post_id",
api.fields.MaterializedViewField(
child=models.IntegerField(),
source=models.ExpressionWrapper(
models.F("pk"), output_field=models.IntegerField()
),
),
),
(
"title",
api.fields.MaterializedViewField(
child=models.CharField(), source=None
),
),
(
"content",
api.fields.MaterializedViewField(
child=models.CharField(), source=Non[...]
Django Unleashed Framework
ия для модели Post. class MaterializedViewBaseModel(models.Model): class Meta: abstract = True @classmethod def check(cls, **kwargs): errors = super().check(**kwargs) if not hasattr(cls._meta, 'materialized_view'): errors.append( checks.Error( 'The `view`…
e
),
),
(
"tag_names",
api.fields.MaterializedViewField(
child=models.CharField(),
source=django.contrib.postgres.aggregates.general.StringAgg(
"tags__name", delimiter="'; '", distinct=True
),
),
),
(
"comments_count",
api.fields.MaterializedViewField(
child=models.IntegerField(),
source=django.db.models.functions.comparison.Coalesce(
django.db.models.aggregates.Count(
"comments", distinct=True
),
0,
),
),
),
(
"comment_authors",
api.fields.MaterializedViewField(
child=models.CharField(),
source=django.contrib.postgres.aggregates.general.StringAgg(
"comments__author__first_name",
delimiter="', '",
distinct=True,
),
),
),
],
options={
"materialized_view": True,
"view_parent_model": "api.Post",
},
),
migrations.AddConstraint(
model_name="postmaterializedview",
constraint=models.UniqueConstraint(
models.F("post_id"), name="unique_post_id"
),
),
]
Обратите внимание: атрибуты
Теперь Django успешно интегрирован с материализованными представлениями PostgreSQL.
Следующие шаги
Хотя нам удалось внедрить в Django новую функцию для использования материализованных представлений PostgreSQL, есть несколько улучшений, достойных обсуждения.
Поскольку теперь ясно, что материализованные представления нуждаются в регулярных обновлениях в случае, если базовая таблица представления была обновлена, необходимо разработать механизм для поддержания данных материализованного представления в актуальном состоянии.
На предложенном решении можно протестировать более сложные примеры, чтобы проверить его динамичность для различных типов запросов и случаев использования.
Заключение
Надеюсь, это руководство позволит вам легко интегрировать материализованные представления в приложения Django. Для этого потребуется запустить такие механизмы, как переопределение класса схемы базы данных, разработка пользовательских классов модели и разработка пользовательских полей модели.
Благодаря возможности создавать и обновлять материализованные представления с помощью системы миграции Django, вы сможете поддерживать синхронизацию между схемой базы данных и определениями моделей. Разделение обычных моделей и моделей материализованных представлений улучшает организацию и удобство сопровождения кода.
Читайте также:
* Как загружать файлы и изображения в приложении Django
* Как заказывали: админ-панель от Django Jet
* Простой прием для молниеносных запросов LIKE и ILIKE
Читайте нас в Telegram, VK и Дзен
Перевод статьи Abdulla Hashim: Integrating Django with PostgreSQL Materialized Views
Читать: Интеграция Django с материализованными представлениями PostgreSQL .
),
),
(
"tag_names",
api.fields.MaterializedViewField(
child=models.CharField(),
source=django.contrib.postgres.aggregates.general.StringAgg(
"tags__name", delimiter="'; '", distinct=True
),
),
),
(
"comments_count",
api.fields.MaterializedViewField(
child=models.IntegerField(),
source=django.db.models.functions.comparison.Coalesce(
django.db.models.aggregates.Count(
"comments", distinct=True
),
0,
),
),
),
(
"comment_authors",
api.fields.MaterializedViewField(
child=models.CharField(),
source=django.contrib.postgres.aggregates.general.StringAgg(
"comments__author__first_name",
delimiter="', '",
distinct=True,
),
),
),
],
options={
"materialized_view": True,
"view_parent_model": "api.Post",
},
),
migrations.AddConstraint(
model_name="postmaterializedview",
constraint=models.UniqueConstraint(
models.F("post_id"), name="unique_post_id"
),
),
]
Обратите внимание: атрибуты
source
и child
поля MaterializedViewField
были включены в файл миграции, так как мы определили их в методе deconstruct
поля.Теперь Django успешно интегрирован с материализованными представлениями PostgreSQL.
Следующие шаги
Хотя нам удалось внедрить в Django новую функцию для использования материализованных представлений PostgreSQL, есть несколько улучшений, достойных обсуждения.
Поскольку теперь ясно, что материализованные представления нуждаются в регулярных обновлениях в случае, если базовая таблица представления была обновлена, необходимо разработать механизм для поддержания данных материализованного представления в актуальном состоянии.
На предложенном решении можно протестировать более сложные примеры, чтобы проверить его динамичность для различных типов запросов и случаев использования.
Заключение
Надеюсь, это руководство позволит вам легко интегрировать материализованные представления в приложения Django. Для этого потребуется запустить такие механизмы, как переопределение класса схемы базы данных, разработка пользовательских классов модели и разработка пользовательских полей модели.
Благодаря возможности создавать и обновлять материализованные представления с помощью системы миграции Django, вы сможете поддерживать синхронизацию между схемой базы данных и определениями моделей. Разделение обычных моделей и моделей материализованных представлений улучшает организацию и удобство сопровождения кода.
Читайте также:
* Как загружать файлы и изображения в приложении Django
* Как заказывали: админ-панель от Django Jet
* Простой прием для молниеносных запросов LIKE и ILIKE
Читайте нас в Telegram, VK и Дзен
Перевод статьи Abdulla Hashim: Integrating Django with PostgreSQL Materialized Views
Читать: Интеграция Django с материализованными представлениями PostgreSQL .
😱3🤔1
5 Django Commands Every Developer Should Know
django-admin startproject
This command is used to create a new Django project. It sets up the basic directory structure and configuration files for a Django project. For example, running django-admin startproject mylibrary will create a new project ...
Read: https://codepriest.hashnode.dev/5-django-commands-every-developer-should-know
django-admin startproject
This command is used to create a new Django project. It sets up the basic directory structure and configuration files for a Django project. For example, running django-admin startproject mylibrary will create a new project ...
Read: https://codepriest.hashnode.dev/5-django-commands-every-developer-should-know
Free Services to Support Development of a Django App I
Introduction
When we are hosting a website using cloud services, we know that the process can be quite expensive. So it makes sense for us to stick to as many free services as we can during the development phase.
Here are 5 different services that we...
Read: https://diptonil.hashnode.dev/free-services-to-support-development-of-a-django-app-i
Introduction
When we are hosting a website using cloud services, we know that the process can be quite expensive. So it makes sense for us to stick to as many free services as we can during the development phase.
Here are 5 different services that we...
Read: https://diptonil.hashnode.dev/free-services-to-support-development-of-a-django-app-i
Structuring Your Django Template The Right Way
Introduction
Templates play a vital role in Django's MVT (Model-View-Template) structure. In simple terms, a Django template is like a particular HTML file written in languages like HTML, CSS, and Javascript. These templates create the visual appeara...
Read: https://faithbolanle.hashnode.dev/structuring-your-django-template-the-right-way
Introduction
Templates play a vital role in Django's MVT (Model-View-Template) structure. In simple terms, a Django template is like a particular HTML file written in languages like HTML, CSS, and Javascript. These templates create the visual appeara...
Read: https://faithbolanle.hashnode.dev/structuring-your-django-template-the-right-way
Choosing the Right Framework: Laravel, Django, or React?
In the world of web development, selecting the right framework is crucial to ensure efficient and successful project outcomes. Laravel, Django, and React are three popular choices that developers often consider.
Before hardcore developer comes at me...
Read: https://scofield.hashnode.dev/choosing-the-right-framework-laravel-django-or-react
In the world of web development, selecting the right framework is crucial to ensure efficient and successful project outcomes. Laravel, Django, and React are three popular choices that developers often consider.
Before hardcore developer comes at me...
Read: https://scofield.hashnode.dev/choosing-the-right-framework-laravel-django-or-react
👍1
Python Web Development Explained: An In-Depth Look at Flask and Django
Hey there, fellow coders! Are you ready for an exciting deep-dive into the world of Flask and Django - two Python frameworks that are absolute game-changers in web development? Python, our trusty friend, is known for its simplicity and versatility, p...
Read: https://asksnehasish.hashnode.dev/mastering-web-dev-python-flask-django
Hey there, fellow coders! Are you ready for an exciting deep-dive into the world of Flask and Django - two Python frameworks that are absolute game-changers in web development? Python, our trusty friend, is known for its simplicity and versatility, p...
Read: https://asksnehasish.hashnode.dev/mastering-web-dev-python-flask-django
👍1
Essential Django Commands for Web Development
Django is a powerful web framework that simplifies the process of building web applications. As a Django developer, it's important to be familiar with certain commands that can greatly enhance your productivity. In this blog post, we'll explore some ...
Read: https://kcihemelandu.hashnode.dev/essential-django-commands-for-web-development
Django is a powerful web framework that simplifies the process of building web applications. As a Django developer, it's important to be familiar with certain commands that can greatly enhance your productivity. In this blog post, we'll explore some ...
Read: https://kcihemelandu.hashnode.dev/essential-django-commands-for-web-development
How to Create an Empty Migration File in Django
Migrations are an essential part of Django, as they allow you to manage your database schema and ensure that it is consistent with your models. Most migrations capture actual changes to your models, but there are some scenarios where you may need to create an empty migration file.
In this article, we will show you how to create an empty migration …
Read: https://djangocentral.com/creating-an-empty-migration-file-in-django/
Migrations are an essential part of Django, as they allow you to manage your database schema and ensure that it is consistent with your models. Most migrations capture actual changes to your models, but there are some scenarios where you may need to create an empty migration file.
In this article, we will show you how to create an empty migration …
Read: https://djangocentral.com/creating-an-empty-migration-file-in-django/
Djangocentral
How to Create an Empty Migration File in Django
Migrations are an essential part of Django, as they allow you to manage your database schema and ensure that it is consistent with your models. Most migratio
Notes from a course that helped me integrate django and Vue
Introduction
In one of the projects that I'm working on, there was a need to integrate django and Vue.
There are a lot of articles, blog posts and courses on integrating both. Most of them use the strict frontend and backend approach i.e. Vue takes c...
Read: https://hellosambhavi.com/notes-from-a-course-that-helped-me-integrate-django-and-vue
Introduction
In one of the projects that I'm working on, there was a need to integrate django and Vue.
There are a lot of articles, blog posts and courses on integrating both. Most of them use the strict frontend and backend approach i.e. Vue takes c...
Read: https://hellosambhavi.com/notes-from-a-course-that-helped-me-integrate-django-and-vue
#6 - Change of plan with the frontend approach
In my previous post, I wrote about setting up the User module in Wobu. Backend work started to take good shape. After a while, I was at crossroads 🚦.
When I started developing Wobu, I wanted to focus on the backend first and decided to keep the fron...
Read: https://hellosambhavi.com/6-change-of-plan-with-the-frontend-approach
In my previous post, I wrote about setting up the User module in Wobu. Backend work started to take good shape. After a while, I was at crossroads 🚦.
When I started developing Wobu, I wanted to focus on the backend first and decided to keep the fron...
Read: https://hellosambhavi.com/6-change-of-plan-with-the-frontend-approach
Understanding the 'F' Expression in Django: When and Why to Use It
In Django,
This article aims to explore the 'F' expression in Django QuerySets, explaining its purpose and discussing scenarios where it proves beneficial.
What is the 'F' Expression?
The 'F' expression …
Read: https://djangocentral.com/f-expression-in-django/
In Django,
QuerySets
provide a powerful way to retrieve, manipulate, and filter data from the database. Among the various tools available within QuerySets, the 'F' expression stands out as a valuable feature.This article aims to explore the 'F' expression in Django QuerySets, explaining its purpose and discussing scenarios where it proves beneficial.
What is the 'F' Expression?
The 'F' expression …
Read: https://djangocentral.com/f-expression-in-django/
Djangocentral
Understanding the 'F' Expression in Django: When and Why to Use It
In Django, QuerySets provide a powerful way to retrieve, manipulate, and filter data from the database. Among the various tools available within QuerySets, t
Comparing Backend Frameworks
In the ever-evolving world of web development, choosing the right backend framework is crucial for building robust, scalable and efficient applications. Among the multitude of options available, flask, Django and express.js stand out as powerful tool...
Read: https://softwareninja.hashnode.dev/comparing-backend-frameworks
In the ever-evolving world of web development, choosing the right backend framework is crucial for building robust, scalable and efficient applications. Among the multitude of options available, flask, Django and express.js stand out as powerful tool...
Read: https://softwareninja.hashnode.dev/comparing-backend-frameworks
Django Rest API Security: Broken Object Level Authorization
Introduction
Object-level permission grants or restricts access to individual objects based on the authenticated user's role or defined permissions. This is useful in applications to control data visibility and prevent accidental data leakages.
Suppo...
Read: https://blog.stevenwithph.com/django-rest-api-security-broken-object-level-authorization
Introduction
Object-level permission grants or restricts access to individual objects based on the authenticated user's role or defined permissions. This is useful in applications to control data visibility and prevent accidental data leakages.
Suppo...
Read: https://blog.stevenwithph.com/django-rest-api-security-broken-object-level-authorization