Skip to content

Commit 941d521

Browse files
committed
Add a command to refresh all materialized views
1 parent 6facce4 commit 941d521

File tree

3 files changed

+102
-13
lines changed

3 files changed

+102
-13
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import logging
2+
3+
from django.core.management.base import BaseCommand
4+
5+
from django_pgviews.models import ViewRefresher
6+
7+
log = logging.getLogger("django_pgviews.refresh_pgviews")
8+
9+
10+
class Command(BaseCommand):
11+
help = """Refresh materialized Postgres views for all installed apps."""
12+
13+
def add_arguments(self, parser):
14+
parser.add_argument(
15+
"-C",
16+
"--concurrently",
17+
action="store_true",
18+
dest="concurrently",
19+
help="Refresh concurrently if the materialized view supports it",
20+
)
21+
22+
def handle(self, concurrently, **options):
23+
ViewRefresher().run(concurrently)

django_pgviews/models.py

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
log = logging.getLogger("django_pgviews.sync_pgviews")
1010

1111

12-
class ViewSyncer(object):
13-
def run(self, force, update, materialized_views_check_sql_changed=False, **options):
14-
self.synced = []
12+
class RunBacklog(object):
13+
def __init__(self) -> None:
14+
super().__init__()
15+
self.finished = []
16+
17+
def run(self, **kwargs):
18+
self.finished = []
1519
backlog = []
1620
for view_cls in apps.get_models():
1721
if not (isinstance(view_cls, type) and issubclass(view_cls, View) and hasattr(view_cls, "sql")):
@@ -20,30 +24,44 @@ def run(self, force, update, materialized_views_check_sql_changed=False, **optio
2024
loop = 0
2125
while len(backlog) > 0 and loop < 10:
2226
loop += 1
23-
backlog = self.run_backlog(backlog, force, update, materialized_views_check_sql_changed)
27+
backlog = self.run_backlog(backlog, **kwargs)
2428

2529
if loop >= 10:
2630
log.warning("pgviews dependencies hit limit. Check if your model dependencies are correct")
27-
else:
31+
return False
32+
33+
return True
34+
35+
def run_backlog(self, backlog, **kwargs):
36+
raise NotImplementedError
37+
38+
39+
class ViewSyncer(RunBacklog):
40+
def run(self, force, update, materialized_views_check_sql_changed=False, **options):
41+
if super().run(
42+
force=force, update=update, materialized_views_check_sql_changed=materialized_views_check_sql_changed
43+
):
2844
all_views_synced.send(sender=None)
2945

30-
def run_backlog(self, models, force, update, materialized_views_check_sql_changed):
46+
def run_backlog(self, backlog, *, force, update, materialized_views_check_sql_changed, **kwargs):
3147
"""Installs the list of models given from the previous backlog
3248
3349
If the correct dependent views have not been installed, the view
3450
will be added to the backlog.
3551
3652
Eventually we get to a point where all dependencies are sorted.
3753
"""
38-
backlog = []
39-
for view_cls in models:
54+
new_backlog = []
55+
for view_cls in backlog:
4056
skip = False
4157
name = "{}.{}".format(view_cls._meta.app_label, view_cls.__name__)
4258
for dep in view_cls._dependencies:
43-
if dep not in self.synced:
59+
if dep not in self.finished:
4460
skip = True
61+
break
62+
4563
if skip is True:
46-
backlog.append(view_cls)
64+
new_backlog.append(view_cls)
4765
log.info("Putting pgview at back of queue: %s", name)
4866
continue # Skip
4967

@@ -73,7 +91,7 @@ def run_backlog(self, models, force, update, materialized_views_check_sql_change
7391
status=status,
7492
has_changed=status not in ("EXISTS", "FORCE_REQUIRED"),
7593
)
76-
self.synced.append(name)
94+
self.finished.append(name)
7795
except Exception as exc:
7896
exc.view_cls = view_cls
7997
exc.python_name = name
@@ -93,4 +111,32 @@ def run_backlog(self, models, force, update, materialized_views_check_sql_change
93111
msg = status
94112

95113
log.info("pgview %s %s", name, msg)
96-
return backlog
114+
return new_backlog
115+
116+
117+
class ViewRefresher(RunBacklog):
118+
def run(self, concurrently, **kwargs):
119+
return super().run(concurrently=concurrently, **kwargs)
120+
121+
def run_backlog(self, backlog, *, concurrently, **kwargs):
122+
new_backlog = []
123+
for view_cls in backlog:
124+
skip = False
125+
name = "{}.{}".format(view_cls._meta.app_label, view_cls.__name__)
126+
for dep in view_cls._dependencies:
127+
if dep not in self.finished:
128+
skip = True
129+
break
130+
131+
if skip is True:
132+
new_backlog.append(view_cls)
133+
log.info("Putting pgview at back of queue: %s", name)
134+
continue # Skip
135+
136+
if issubclass(view_cls, MaterializedView):
137+
view_cls.refresh(concurrently=concurrently)
138+
log.info("pgview %s refreshed", name)
139+
140+
self.finished.append(name)
141+
142+
return new_backlog

tests/test_project/test_project/viewtest/tests.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,31 @@ def test_migrate_materialized_views_check_sql_changed_default(self):
218218

219219
models.TestModel.objects.create(name="Test")
220220

221-
print("Run migrate\n")
222221
call_command("migrate")
223222

224223
self.assertEqual(models.MaterializedRelatedView.objects.count(), 1)
225224

225+
def test_refresh_pgviews(self):
226+
models.TestModel.objects.create(name="Test")
227+
228+
call_command("refresh_pgviews")
229+
230+
self.assertEqual(models.MaterializedRelatedView.objects.count(), 1)
231+
self.assertEqual(models.DependantView.objects.count(), 1)
232+
self.assertEqual(models.DependantMaterializedView.objects.count(), 1)
233+
self.assertEqual(models.MaterializedRelatedViewWithIndex.objects.count(), 1)
234+
self.assertEqual(models.MaterializedRelatedViewWithNoData.objects.count(), 1)
235+
236+
models.TestModel.objects.create(name="Test 2")
237+
238+
call_command("refresh_pgviews", concurrently=True)
239+
240+
self.assertEqual(models.MaterializedRelatedView.objects.count(), 2)
241+
self.assertEqual(models.DependantView.objects.count(), 2)
242+
self.assertEqual(models.DependantMaterializedView.objects.count(), 2)
243+
self.assertEqual(models.MaterializedRelatedViewWithIndex.objects.count(), 2)
244+
self.assertEqual(models.MaterializedRelatedViewWithNoData.objects.count(), 2)
245+
226246

227247
class TestMaterializedViewsCheckSQLSettings(TestCase):
228248
def setUp(self):

0 commit comments

Comments
 (0)