0001-general-keep-cache-of-cell-types-used-in-a-page-2423.patch
combo/data/migrations/0034_page_related_cells.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.12 on 2018-06-02 11:03 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations |
|
6 |
import jsonfield.fields |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('data', '0033_auto_20180401_1300'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.AddField( |
|
17 |
model_name='page', |
|
18 |
name='related_cells', |
|
19 |
field=jsonfield.fields.JSONField(blank=True, default=dict), |
|
20 |
), |
|
21 |
] |
combo/data/models.py | ||
---|---|---|
35 | 35 |
from django.db import models, transaction |
36 | 36 |
from django.db.models.base import ModelBase |
37 | 37 |
from django.db.models import Max |
38 |
from django.db.models.signals import post_save, pre_delete |
|
39 |
from django.dispatch import receiver |
|
38 | 40 |
from django.forms import models as model_forms |
39 | 41 |
from django import forms |
40 | 42 |
from django import template |
... | ... | |
139 | 141 |
snapshot = models.ForeignKey('PageSnapshot', on_delete=models.CASCADE, null=True, |
140 | 142 |
related_name='temporary_page') |
141 | 143 | |
144 |
# keep a cached list of cell types that are used in the pages. |
|
145 |
related_cells = JSONField(blank=True) |
|
146 | ||
142 | 147 |
_level = None |
143 | 148 |
_children = None |
144 | 149 | |
... | ... | |
152 | 157 |
return (self.get_online_url().strip('/'), ) |
153 | 158 | |
154 | 159 |
def save(self, *args, **kwargs): |
160 |
if not self.id: |
|
161 |
self.related_cells = {'cell_types': []} |
|
155 | 162 |
if not self.order: |
156 | 163 |
max_order = Page.objects.all().aggregate(Max('order')).get('order__max') or 0 |
157 | 164 |
self.order = max_order + 1 |
... | ... | |
293 | 300 |
return element_is_visible(self, user=user) |
294 | 301 | |
295 | 302 |
def get_cells(self): |
296 |
return CellBase.get_cells(page_id=self.id) |
|
303 |
cells = CellBase.get_cells(page=self) |
|
304 |
cell_types = set() |
|
305 |
for cell in cells: |
|
306 |
cell_types.add(cell.get_cell_type_str()) |
|
307 |
if cell_types != set(self.related_cells.get('cell_types', [])): |
|
308 |
self.related_cells['cell_types'] = list(cell_types) |
|
309 |
self.save() |
|
310 |
return cells |
|
297 | 311 | |
298 | 312 |
def get_serialized_page(self): |
299 | 313 |
cells = [x for x in self.get_cells() if x.placeholder and not x.placeholder.startswith('_')] |
... | ... | |
302 | 316 |
del serialized_page['model'] |
303 | 317 |
if 'snapshot' in serialized_page: |
304 | 318 |
del serialized_page['snapshot'] |
319 |
if 'related_cells' in serialized_page['fields']: |
|
320 |
del serialized_page['fields']['related_cells'] |
|
305 | 321 |
serialized_page['cells'] = json.loads(serializers.serialize('json', |
306 | 322 |
cells, use_natural_foreign_keys=True, use_natural_primary_keys=True)) |
307 | 323 |
serialized_page['fields']['groups'] = [x[0] for x in serialized_page['fields']['groups']] |
... | ... | |
491 | 507 |
def get_cells(cls, cell_filter=None, **kwargs): |
492 | 508 |
"""Returns the list of cells of various classes matching **kwargs""" |
493 | 509 |
cells = [] |
494 |
for klass in get_cell_classes(): |
|
510 |
pages = [] |
|
511 |
if 'page' in kwargs: |
|
512 |
pages = [kwargs['page']] |
|
513 |
elif 'page__in' in kwargs: |
|
514 |
pages = kwargs['page__in'] |
|
515 |
cell_classes = get_cell_classes() |
|
516 |
if pages: |
|
517 |
# if there's a request for some specific pages, limit cell types |
|
518 |
# to those that are actually in use in those pages. |
|
519 |
cell_types = set() |
|
520 |
for page in pages: |
|
521 |
if page.related_cells and 'cell_types' in page.related_cells: |
|
522 |
cell_types |= set(page.related_cells['cell_types']) |
|
523 |
else: |
|
524 |
break |
|
525 |
else: |
|
526 |
cell_classes = [get_cell_class(x) for x in cell_types] |
|
527 |
for klass in cell_classes: |
|
495 | 528 |
if cell_filter and not cell_filter(klass): |
496 | 529 |
continue |
497 | 530 |
cells.extend(klass.objects.filter(**kwargs)) |
... | ... | |
924 | 957 | |
925 | 958 |
def get_parents_cells(self, hierarchy): |
926 | 959 |
try: |
927 |
page_ids = [Page.objects.get(slug='index', parent=None).id]
|
|
960 |
pages = [Page.objects.get(slug='index', parent=None)]
|
|
928 | 961 |
except Page.DoesNotExist: |
929 |
page_ids = []
|
|
930 |
page_ids.extend([x.id for x in hierarchy])
|
|
931 |
if not page_ids:
|
|
962 |
pages = [] |
|
963 |
pages.extend(hierarchy)
|
|
964 |
if not pages: |
|
932 | 965 |
return [] |
933 |
if len(page_ids) > 1 and page_ids[0] == page_ids[1]:
|
|
966 |
if len(pages) > 1 and pages[0].id == pages[1].id:
|
|
934 | 967 |
# don't duplicate index cells for real children of the index page. |
935 |
page_ids = page_ids[1:]
|
|
968 |
pages = pages[1:]
|
|
936 | 969 |
cells_by_page = {} |
937 |
for page_id in page_ids:
|
|
938 |
cells_by_page[page_id] = []
|
|
939 |
for cell in list(CellBase.get_cells(placeholder=self.placeholder, page__in=page_ids)):
|
|
970 |
for page in pages:
|
|
971 |
cells_by_page[page.id] = []
|
|
972 |
for cell in list(CellBase.get_cells(placeholder=self.placeholder, page__in=pages)): |
|
940 | 973 |
cells_by_page[cell.page_id].append(cell) |
941 | 974 | |
942 |
cells = cells_by_page[page_ids[-1]]
|
|
943 |
for page_id in reversed(page_ids[:-1]):
|
|
975 |
cells = cells_by_page[pages[-1].id]
|
|
976 |
for page in reversed(pages[:-1]):
|
|
944 | 977 |
for i, cell in enumerate(cells): |
945 | 978 |
if isinstance(cell, ParentContentCell): |
946 |
cells[i:i+1] = cells_by_page[page_id]
|
|
979 |
cells[i:i+1] = cells_by_page[page.id]
|
|
947 | 980 |
break |
948 | 981 |
else: |
949 | 982 |
# no more ParentContentCell, stop folloing the parent page chain |
... | ... | |
1271 | 1304 |
text = models.TextField(blank=True) |
1272 | 1305 |
url = models.CharField(_('URL'), max_length=200, blank=True) |
1273 | 1306 |
last_update_timestamp = models.DateTimeField(auto_now=True) |
1307 | ||
1308 | ||
1309 | ||
1310 |
@receiver(post_save) |
|
1311 |
def cell_post_save(sender, instance=None, **kwargs): |
|
1312 |
if not issubclass(sender, CellBase): |
|
1313 |
return |
|
1314 |
if not instance.page_id: |
|
1315 |
return |
|
1316 |
page = instance.page |
|
1317 |
if not 'cell_types' in instance.page.related_cells: |
|
1318 |
page.get_cells() # will rebuild related_cells cache |
|
1319 |
if instance.get_cell_type_str() not in page.related_cells['cell_types']: |
|
1320 |
page.related_cells['cell_types'].append(sender.get_cell_type_str()) |
|
1321 |
page.save() |
|
1322 | ||
1323 | ||
1324 |
@receiver(pre_delete) |
|
1325 |
def cell_pre_delete(sender, instance=None, **kwargs): |
|
1326 |
if not issubclass(sender, CellBase): |
|
1327 |
return |
|
1328 |
if not instance.page_id: |
|
1329 |
return |
|
1330 |
page = instance.page |
|
1331 |
if not 'cell_types' in page.related_cells: |
|
1332 |
return |
|
1333 |
if not instance.get_cell_type_str() in page.related_cells['cell_types']: |
|
1334 |
return |
|
1335 |
if sender.objects.filter(page_id=instance.page_id).count() > 1: |
|
1336 |
return |
|
1337 |
page.related_cells['cell_types'].remove(sender.get_cell_type_str()) |
|
1338 |
page.save() |
combo/public/views.py | ||
---|---|---|
238 | 238 |
if placeholder.acquired: |
239 | 239 |
cells.append(ParentContentCell(page=selected_page, placeholder=placeholder.key, order=0)) |
240 | 240 |
else: |
241 |
cells = CellBase.get_cells(page_id=selected_page.id)
|
|
241 |
cells = CellBase.get_cells(page=selected_page)
|
|
242 | 242 | |
243 | 243 |
pages = selected_page.get_parents_and_self() |
244 | 244 |
combo_template = settings.COMBO_PUBLIC_TEMPLATES[selected_page.template_name] |
... | ... | |
379 | 379 |
if page.redirect_url: |
380 | 380 |
return HttpResponseRedirect(page.get_redirect_url()) |
381 | 381 | |
382 |
cells = CellBase.get_cells(page_id=page.id)
|
|
382 |
cells = CellBase.get_cells(page=page)
|
|
383 | 383 |
extend_with_parent_cells(cells, hierarchy=pages) |
384 | 384 |
cells = [x for x in cells if x.is_visible(user=request.user)] |
385 | 385 |
tests/test_cells.py | ||
---|---|---|
6 | 6 | |
7 | 7 |
from combo.data.models import Page, CellBase, TextCell, LinkCell, JsonCellBase, JsonCell, ConfigJsonCell |
8 | 8 |
from django.conf import settings |
9 |
from django.db import connection |
|
9 | 10 |
from django.forms.widgets import Media |
10 | 11 |
from django.template import Context |
11 | 12 |
from django.test import override_settings |
12 | 13 |
from django.test.client import RequestFactory |
14 |
from django.test.utils import CaptureQueriesContext |
|
13 | 15 |
from django.contrib.auth.models import User |
14 | 16 |
from django.core.urlresolvers import reverse |
15 | 17 | |
18 |
from combo.data.library import get_cell_classes |
|
16 | 19 |
from combo.utils import NothingInCacheException |
17 | 20 | |
18 | 21 |
from test_manager import admin_user, login |
... | ... | |
627 | 630 | |
628 | 631 |
assert "<p>Public text</p>" not in resp.content |
629 | 632 |
assert "<p>Private text</p>" in resp.content |
633 | ||
634 |
def test_related_cell_types_tracking(): |
|
635 |
page = Page(title='example page', slug='example-page') |
|
636 |
page.save() |
|
637 |
assert page.related_cells['cell_types'] == [] |
|
638 | ||
639 |
TextCell(page=page, placeholder='content', order=0, text='hello').save() |
|
640 |
assert Page.objects.get(id=page.id).related_cells['cell_types'] == ['data_textcell'] |
|
641 | ||
642 |
TextCell(page=page, placeholder='content', order=1, text='hello').save() |
|
643 |
assert Page.objects.get(id=page.id).related_cells['cell_types'] == ['data_textcell'] |
|
644 | ||
645 |
LinkCell(page=page, placeholder='content', order=0, title='Test', url='http://example.net').save() |
|
646 |
assert set(Page.objects.get(id=page.id).related_cells['cell_types']) == set(['data_textcell', 'data_linkcell']) |
|
647 | ||
648 |
with CaptureQueriesContext(connection) as ctx: |
|
649 |
assert len(CellBase.get_cells(page=Page.objects.get(id=page.id))) == 3 |
|
650 |
assert len(ctx.captured_queries) == 1 + 2 |
|
651 | ||
652 |
TextCell.objects.get(order=1).delete() |
|
653 |
assert set(Page.objects.get(id=page.id).related_cells['cell_types']) == set(['data_textcell', 'data_linkcell']) |
|
654 | ||
655 |
TextCell.objects.get(order=0).delete() |
|
656 |
assert set(Page.objects.get(id=page.id).related_cells['cell_types']) == set(['data_linkcell']) |
|
657 | ||
658 |
with CaptureQueriesContext(connection) as ctx: |
|
659 |
assert len(CellBase.get_cells(page=Page.objects.get(id=page.id))) == 1 |
|
660 |
assert len(ctx.captured_queries) == 1 + 1 |
|
661 | ||
662 |
# remove tracker, check it is rebuilt correctly |
|
663 |
page.related_cells = {} |
|
664 |
page.save() |
|
665 | ||
666 |
with CaptureQueriesContext(connection) as ctx: |
|
667 |
assert len(CellBase.get_cells(page=Page.objects.get(id=page.id))) == 1 |
|
668 |
assert len(ctx.captured_queries) == len(get_cell_classes()) |
|
669 | ||
670 |
Page.objects.get(id=page.id).get_cells() |
|
671 | ||
672 |
TextCell(page=page, placeholder='content', order=0, text='hello').save() |
|
673 |
assert set(Page.objects.get(id=page.id).related_cells['cell_types']) == set(['data_textcell', 'data_linkcell']) |
tests/test_fargo.py | ||
---|---|---|
28 | 28 |
resp = app.get(resp.html.find('option', |
29 | 29 |
**{'data-add-url': re.compile('recentdoc')})['data-add-url']) |
30 | 30 | |
31 |
cells = page.get_cells()
|
|
31 |
cells = Page.objects.get(id=page.id).get_cells()
|
|
32 | 32 |
assert len(cells) == 1 |
33 | 33 |
assert isinstance(cells[0], RecentDocumentsCell) |
34 | 34 |
resp = app.get('/manage/pages/%s/' % page.id) |
... | ... | |
42 | 42 |
'backoffice-menu-url': 'http://example.org/manage/',}, |
43 | 43 |
}}): |
44 | 44 |
resp = app.get('/manage/pages/%s/' % page.id) |
45 |
cells = page.get_cells()
|
|
45 |
cells = Page.objects.get(id=page.id).get_cells()
|
|
46 | 46 |
resp.forms[0]['c%s-fargo_site' % cells[0].get_reference()].value = 'second' |
47 | 47 |
resp = resp.forms[0].submit() |
48 |
cells = page.get_cells()
|
|
48 |
cells = Page.objects.get(id=page.id).get_cells()
|
|
49 | 49 |
assert cells[0].fargo_site == 'second' |
50 | 50 | |
51 | 51 |
tests/test_wcs.py | ||
---|---|---|
271 | 271 |
cell.save() |
272 | 272 |
site_export = [page.get_serialized_page()] |
273 | 273 |
cell.delete() |
274 |
assert not page.get_cells()
|
|
274 |
assert not Page.objects.get(id=page.id).get_cells()
|
|
275 | 275 |
Page.load_serialized_pages(site_export) |
276 |
page = Page.objects.get(slug='test_form_cell_save_cache') |
|
276 | 277 |
cells = page.get_cells() |
277 | 278 |
assert len(cells) == 1 |
278 | 279 |
cell = cells[0] |
... | ... | |
518 | 519 |
resp = app.get(resp.html.find('option', |
519 | 520 |
**{'data-add-url': re.compile('wcsformsofcategorycell')})['data-add-url']) |
520 | 521 | |
521 |
cells = page.get_cells()
|
|
522 |
cells = Page.objects.get(id=page.id).get_cells()
|
|
522 | 523 |
assert len(cells) == 1 |
523 | 524 |
assert isinstance(cells[0], WcsFormsOfCategoryCell) |
524 | 525 | |
... | ... | |
538 | 539 |
resp = app.get(resp.html.find('option', |
539 | 540 |
**{'data-add-url': re.compile('wcscurrentformscell')})['data-add-url']) |
540 | 541 | |
541 |
cells = page.get_cells()
|
|
542 |
cells = Page.objects.get(id=page.id).get_cells()
|
|
542 | 543 |
assert len(cells) == 1 |
543 | 544 |
assert isinstance(cells[0], WcsCurrentFormsCell) |
544 | 545 | |
545 |
- |