Projet

Général

Profil

0001-general-keep-cache-of-cell-types-used-in-a-page-2423.patch

Frédéric Péters, 02 juillet 2018 08:30

Télécharger (14,2 ko)

Voir les différences:

Subject: [PATCH 1/2] general: keep cache of cell types used in a page (#24239)

 .../migrations/0034_page_related_cells.py     | 21 +++++
 combo/data/models.py                          | 93 ++++++++++++++++---
 combo/public/views.py                         |  4 +-
 tests/test_cells.py                           | 44 +++++++++
 tests/test_fargo.py                           |  6 +-
 tests/test_wcs.py                             |  7 +-
 6 files changed, 153 insertions(+), 22 deletions(-)
 create mode 100644 combo/data/migrations/0034_page_related_cells.py
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
-