def _get_wrapper_attrs(self, layout): layout_data_key = get_layout_data_key(self.placeholder_name, layout, self.context) attrs = { "class": [ "xt-ph", "xt-ph-edit" if self.edit else None, "xt-global-ph" if self.global_type else None ], "id": "xt-ph-%s" % layout_data_key } if self.edit: # Pass layout editor to editor so we can fetch # correct layout for editing. attrs["data-xt-layout-identifier"] = layout.identifier # We need to pass layout data key here since this is the last # place whe have the context available before editor. attrs["data-xt-layout-data-key"] = layout_data_key attrs["data-xt-placeholder-name"] = self.placeholder_name attrs[ "data-xt-global-type"] = "global" if self.global_type else None attrs["title"] = _("Click to edit placeholder: %s" ) % self.placeholder_name.title() return attrs
def test_page_layout(): if "shuup.xtheme" not in settings.INSTALLED_APPS: pytest.skip("Need shuup.xtheme in INSTALLED_APPS") shop = factories.get_default_shop() theme = get_current_theme(shop) view_config = ViewConfig(theme=theme, shop=shop, view_name="PageView", draft=True) page1_content = printable_gibberish() page1 = create_page(available_from=datetime.date(1917, 12, 6), content=page1_content, shop=shop, url="test1") page2_content = printable_gibberish() page2 = create_page(available_from=datetime.date(1917, 12, 6), content=page2_content, shop=shop, url="test2") placeholder_name = "cms_page" context = {"page": page1} layout = view_config.get_placeholder_layout(PageLayout, placeholder_name, context=context) assert isinstance(layout, PageLayout) assert layout.get_help_text({}) == "" # Invalid context for help text assert page1.title in layout.get_help_text(context) # Make sure layout is empty serialized = layout.serialize() assert len(serialized["rows"]) == 0 assert serialized["name"] == placeholder_name # Add custom plugin to page layout.begin_column({"md": 8}) plugin_text = printable_gibberish() layout.add_plugin("text", {"text": plugin_text}) view_config.save_placeholder_layout( get_layout_data_key(placeholder_name, layout, context), layout) view_config.publish() c = SmartClient() soup = c.soup(reverse("shuup:cms_page", kwargs={"url": page1.url})) page_content = soup.find("div", {"class": "page-content"}) assert page1_content in page_content.text assert plugin_text in page_content.text c = SmartClient() soup = c.soup(reverse("shuup:cms_page", kwargs={"url": page2.url})) page_content = soup.find("div", {"class": "page-content"}) assert page2_content in page_content.text assert plugin_text not in page_content.text
def _add_plugin_and_test_save(view_config, layout, placeholder_name, context, plugin_text=None): if not plugin_text: plugin_text = printable_gibberish() _add_basic_plugin(layout, plugin_text) view_config.save_placeholder_layout(get_layout_data_key(placeholder_name, layout, context), layout) view_config.publish() # Now refetching the layout we should get the plugin layout = view_config.get_placeholder_layout(layout.__class__, placeholder_name, context=context) assert isinstance(layout, layout.__class__) _assert_layout_content_for_basic_plugin(layout, placeholder_name, plugin_text)
def render(self): """ Get this placeholder's rendered contents. :return: Rendered markup. :rtype: markupsafe.Markup """ language = get_language() if not may_inject(self.context): return "" full_content = "" saved_view_config = self.view_config.saved_view_config for layout in self.layouts: cache_key = slugify( "shuup_xtheme_placeholders:placeholder_%s_%s_%s_%s_%s_%s" % ( (saved_view_config.pk if saved_view_config else ""), (saved_view_config.status if saved_view_config else ""), ( saved_view_config.modified_on.isoformat() if saved_view_config and saved_view_config.modified_on else "" ), language, self.placeholder_name, get_layout_data_key(self.placeholder_name, layout, self.context) ) ) layout_content = cache.get(cache_key) if ( settings.SHUUP_XTHEME_USE_PLACEHOLDER_CACHE and saved_view_config and saved_view_config.status == SavedViewConfigStatus.PUBLIC and layout_content ): full_content += layout_content else: wrapper_start = "<div%s>" % get_html_attrs(self._get_wrapper_attrs(layout)) buffer = [] write = buffer.append self._render_layout(write, layout) content = "".join(buffer) layout_content = ( "%(wrapper_start)s%(content)s%(wrapper_end)s" % { "wrapper_start": wrapper_start, "content": content, "wrapper_end": "</div>", }) cache.set(cache_key, layout_content) full_content += layout_content return Markup('<div class="placeholder-edit-wrap">%s</div>' % full_content)
def save_default_placeholder_layout(self, placeholder_name, layout): """ Save a default placeholder layout (only if no data for the PH already exists). :param placeholder_name: Placeholder name :type placeholder_name: str :param layout: Layout or layout data :type layout: Layout|dict :return: True if saved :rtype: bool """ if not self.draft: return False if self.saved_view_config and self.saved_view_config.get_layout_data(placeholder_name) is None: self.save_placeholder_layout(get_layout_data_key(placeholder_name, layout, {}), layout) return True return False
def _get_wrapper_attrs(self, layout): layout_data_key = get_layout_data_key(self.placeholder_name, layout, self.context) attrs = { "class": ["xt-ph", "xt-ph-edit" if self.edit else None, "xt-global-ph" if self.global_type else None], "id": "xt-ph-%s" % layout_data_key } if self.edit: # Pass layout editor to editor so we can fetch # correct layout for editing. attrs["data-xt-layout-identifier"] = layout.identifier # We need to pass layout data key here since this is the last # place whe have the context available before editor. attrs["data-xt-layout-data-key"] = layout_data_key attrs["data-xt-placeholder-name"] = self.placeholder_name attrs["data-xt-global-type"] = "global" if self.global_type else None attrs["title"] = _("Click to edit placeholder: %s") % self.placeholder_name.title() return attrs
def _add_plugin_and_test_save(view_config, layout, placeholder_name, context, plugin_text=None): if not plugin_text: plugin_text = printable_gibberish() _add_basic_plugin(layout, plugin_text) view_config.save_placeholder_layout( get_layout_data_key(placeholder_name, layout, context), layout) view_config.publish() # Now refetching the layout we should get the plugin layout = view_config.get_placeholder_layout(layout.__class__, placeholder_name, context=context) assert isinstance(layout, layout.__class__) _assert_layout_content_for_basic_plugin(layout, placeholder_name, plugin_text)
def get_placeholder_layout(self, layout_cls, placeholder_name, default_layout={}, context=None, layout_data_key=None): """ Get a layout object for the given placeholder. :param layout_cls: :type layout_cls: :param placeholder_name: The name of the placeholder to load. :type placeholder_name: str :param default_layout: Default layout configuration (either a dict or an actual Layout). :type default_layout: dict|Layout :param context: Rendering context. :type context: jinja2.runtime.Context :param layout_data_key: layout data key used for saving the layout data. :type layout_data_key: str :return: Layout. :rtype: Layout """ svc = self.saved_view_config layout = layout_cls(self.theme, placeholder_name=placeholder_name) if not layout_data_key: if not layout.is_valid_context(context or {}): return layout_data_key = get_layout_data_key(placeholder_name, layout, context) if svc: placeholder_data = svc.get_layout_data(layout_data_key) if placeholder_data: return layout.unserialize(self.theme, placeholder_data, placeholder_name=placeholder_name) if default_layout: if isinstance(default_layout, Layout): return default_layout return layout.unserialize(self.theme, default_layout) return layout
def test_page_layout(): if "shuup.xtheme" not in settings.INSTALLED_APPS: pytest.skip("Need shuup.xtheme in INSTALLED_APPS") shop = factories.get_default_shop() theme = get_current_theme(shop) view_config = ViewConfig(theme=theme, shop=shop, view_name="PageView", draft=True) page1_content = printable_gibberish() page1 = create_page(available_from=datetime.date(1917, 12, 6), content=page1_content, shop=shop, url="test1") page2_content = printable_gibberish() page2 = create_page(available_from=datetime.date(1917, 12, 6), content=page2_content, shop=shop, url="test2") placeholder_name = "cms_page" context = {"page": page1} layout = view_config.get_placeholder_layout(PageLayout, placeholder_name, context=context) assert isinstance(layout, PageLayout) assert layout.get_help_text({}) == "" # Invalid context for help text assert page1.title in layout.get_help_text(context) # Make sure layout is empty serialized = layout.serialize() assert len(serialized["rows"]) == 0 assert serialized["name"] == placeholder_name # Add custom plugin to page layout.begin_column({"md": 8}) plugin_text = printable_gibberish() layout.add_plugin("text", {"text": plugin_text}) view_config.save_placeholder_layout(get_layout_data_key(placeholder_name, layout, context), layout) view_config.publish() c = SmartClient() soup = c.soup(reverse("shuup:cms_page", kwargs={"url": page1.url})) page_content = soup.find("div", {"class": "page-content"}) assert page1_content in page_content.text assert plugin_text in page_content.text c = SmartClient() soup = c.soup(reverse("shuup:cms_page", kwargs={"url": page2.url})) page_content = soup.find("div", {"class": "page-content"}) assert page2_content in page_content.text assert plugin_text not in page_content.text
def _render_row(self, write, layout, y, row): """ Render a layout row into HTML. :param write: Writer function :type write: callable :param y: Row Y coordinate :type y: int :param row: Row object :type row: shuup.xtheme.view_config.LayoutRow """ row_attrs = {"class": [layout.row_class, "xt-ph-row"]} if self.edit: row_attrs["data-xt-row"] = str(y) write("<div%s>" % get_html_attrs(row_attrs)) language = get_language() saved_view_config = self.view_config.saved_view_config for x, cell in enumerate(row): cache_key_prefix = slugify( "{x}_{y}_{pk}_{status}_{modified_on}_{lang}_{placeholder}_{data_key}" .format( x=x, y=y, pk=(saved_view_config.pk if saved_view_config else ""), status=(saved_view_config.status if saved_view_config else ""), modified_on=(saved_view_config.modified_on.isoformat() if saved_view_config and saved_view_config.modified_on else ""), lang=language, placeholder=self.placeholder_name, data_key=get_layout_data_key(self.placeholder_name, layout, self.context), )) self._render_cell(write, layout, x, cell, cache_key_prefix) write("</div>\n")
def get_placeholder_layout( self, layout_cls, placeholder_name, default_layout={}, context=None, layout_data_key=None): """ Get a layout object for the given placeholder. :param layout_cls: :type layout_cls: :param placeholder_name: The name of the placeholder to load. :type placeholder_name: str :param default_layout: Default layout configuration (either a dict or an actual Layout) :type default_layout: dict|Layout :param context: Rendering context :type context: jinja2.runtime.Context :param layout_data_key: layout data key used for saving the layout data. :type layout_data_key: str :return: Layout :rtype: Layout """ svc = self.saved_view_config layout = layout_cls(self.theme, placeholder_name=placeholder_name) if not layout_data_key: if not layout.is_valid_context(context or {}): return layout_data_key = get_layout_data_key(placeholder_name, layout, context) if svc: placeholder_data = svc.get_layout_data(layout_data_key) if placeholder_data: return layout.unserialize(self.theme, placeholder_data, placeholder_name=placeholder_name) if default_layout: if isinstance(default_layout, Layout): return default_layout return layout.unserialize(self.theme, default_layout) return layout
def test_plugin(): shop = factories.get_default_shop() set_current_theme(ClassicGrayTheme.identifier, shop) blog_one_page = Page.objects.create( shop=shop, title="Blog One", url="blog_one", available_from=(now() - timedelta(days=10)), available_to=(now() + timedelta(days=10)), content="") blog_two_page = Page.objects.create( shop=shop, title="Blog Two", url="blog_two", available_from=(now() - timedelta(days=10)), available_to=(now() + timedelta(days=10)), content="") # create 10 blog pages for page in [blog_one_page, blog_two_page]: for i in range(10): article = Page.objects.create( shop=shop, title="Article %d %s" % (i, page.title), url="blog-%d-%s" % (i, page.url), available_from=(now() - timedelta(days=10)), available_to=(now() + timedelta(days=10)), content="Content %d" % i, template_name="shuup_cms_blog/blog_page.jinja", parent=page) BlogArticle.objects.create( page=article, is_blog_article=True, image=factories.get_random_filer_image(), small_description="description %d" % i) # create 3 non blog post pages for i in range(3): article = Page.objects.create( shop=shop, title="Nothing %d" % i, url="non-%d" % i, available_from=(now() - timedelta(days=10)), available_to=(now() + timedelta(days=10)), content="content %i" % i) theme = get_current_theme(shop) view_config = ViewConfig(theme=theme, shop=shop, view_name="PageView", draft=True) placeholder_name = "cms_page" context_one = {"page": blog_one_page} context_two = {"page": blog_two_page} layout_one = view_config.get_placeholder_layout(PageLayout, placeholder_name, context=context_one) layout_two = view_config.get_placeholder_layout(PageLayout, placeholder_name, context=context_two) assert isinstance(layout_one, PageLayout) assert isinstance(layout_two, PageLayout) assert layout_one.get_help_text({}) == "" assert layout_two.get_help_text({}) == "" assert blog_one_page.title in layout_two.get_help_text(context_one) assert blog_two_page.title in layout_two.get_help_text(context_two) serialized_one = layout_one.serialize() serialized_two = layout_two.serialize() assert len(serialized_one["rows"]) == 0 assert len(serialized_two["rows"]) == 0 assert serialized_one["name"] == placeholder_name assert serialized_two["name"] == placeholder_name layout_one.begin_column({"sm": 12}) layout_two.begin_column({"sm": 12}) layout_one.add_plugin(ShuupCMSBlogArticleListPlugin.identifier, {"blog_page": blog_one_page.pk}) layout_two.add_plugin(ShuupCMSBlogArticleListPlugin.identifier, {"blog_page": blog_two_page.pk}) view_config.save_placeholder_layout( get_layout_data_key(placeholder_name, layout_one, context_one), layout_one) view_config.save_placeholder_layout( get_layout_data_key(placeholder_name, layout_two, context_two), layout_two) view_config.publish() client = SmartClient() response, soup = client.response_and_soup( reverse("shuup:cms_page", kwargs={"url": blog_one_page.url})) assert response.status_code == 200 assert len(soup.find_all("a", {"class": "article-card"})) == 10 response, soup = client.response_and_soup( reverse("shuup:cms_page", kwargs={"url": blog_two_page.url})) assert response.status_code == 200 assert len(soup.find_all("a", {"class": "article-card"})) == 10 article_one = Page.objects.create( shop=shop, title="Article test", url="blog-test", available_from=(now() - timedelta(days=10)), available_to=(now() + timedelta(days=10)), content="Content test", template_name= "shuup_cms_blog/blog_page.jinja", # Add an article without a parent ) BlogArticle.objects.create(page=article_one, is_blog_article=True, image=factories.get_random_filer_image(), small_description="description test") article_two = Page.objects.create( shop=shop, title="Article test 2", url="blog-test-2", available_from=(now() - timedelta(days=10)), available_to=(now() + timedelta(days=10)), content="Content test 2", template_name= "shuup_cms_blog/blog_page.jinja", # Add an article without a parent ) BlogArticle.objects.create(page=article_two, is_blog_article=True, image=factories.get_random_filer_image(), small_description="description test 2") view_config = ViewConfig(theme=theme, shop=shop, view_name="PageView", draft=True) # No blog page set means that only articles with no parent will be shown layout_two.add_plugin(ShuupCMSBlogArticleListPlugin.identifier, {}) view_config.save_placeholder_layout( get_layout_data_key(placeholder_name, layout_two, context_two), layout_two) view_config.publish() response, soup = client.response_and_soup( reverse("shuup:cms_page", kwargs={"url": blog_two_page.url})) assert response.status_code == 200 assert len(soup.find_all("a", {"class": "article-card"})) == 2 response, soup = client.response_and_soup( reverse("shuup:cms_page", kwargs={"url": article_one.url})) assert response.status_code == 200 assert len(soup.find_all("a", {"class": "article-card"})) == 1 assert soup.find("div", { "class": "article-title" }).text == article_two.title article_one.soft_delete() # Delete the article that has no parent article_two.soft_delete() # Delete the article that has no parent response, soup = client.response_and_soup( reverse("shuup:cms_page", kwargs={"url": blog_two_page.url})) assert response.status_code == 200 assert len(soup.find_all("a", {"class": "article-card"})) == 0 article_with_parent = Page.objects.filter(parent=blog_one_page).first() response, soup = client.response_and_soup( reverse("shuup:cms_page", kwargs={"url": article_with_parent.url})) assert response.status_code == 200 assert len(soup.find_all( "a", {"class": "article-card"})) == 9 # Assert there are 9 articles shown article_card_titles = soup.find_all("div", {"class": "article-title"}) article_card_titles = [title.text for title in article_card_titles] # Assert that the current visited article (article_with_parent) is not visible in Related articles assert article_with_parent.title not in article_card_titles
def test_page_anonymous_layout(): if "shuup.simple_cms" not in settings.INSTALLED_APPS: pytest.skip("Need shuup.simple_cms in INSTALLED_APPS") if "shuup.xtheme" not in settings.INSTALLED_APPS: pytest.skip("Need shuup.xtheme in INSTALLED_APPS") shop = factories.get_default_shop() theme = set_current_theme("shuup.themes.classic_gray", shop) view_config = ViewConfig(theme=theme, shop=shop, view_name="PageView", draft=True) page1_content = printable_gibberish() page1 = create_page(available_from=datetime.date(1917, 12, 6), content=page1_content, shop=shop, url="test1") page2_content = printable_gibberish() page2 = create_page(available_from=datetime.date(1917, 12, 6), content=page2_content, shop=shop, url="test2") person = factories.create_random_person(shop=shop) person.user = factories.create_random_user() password = "******" person.user.set_password(password) person.user.save() person.save() placeholder_name = "cms_page" request = get_request() context = {"page": page1, "request": request} layout = view_config.get_placeholder_layout(PageAnonymousLayout, placeholder_name, context=context) assert isinstance(layout, PageAnonymousLayout) assert layout.get_help_text({}) == "" # Invalid context for help text assert page1.title in layout.get_help_text(context) # Make sure layout is empty serialized = layout.serialize() assert len(serialized["rows"]) == 0 assert serialized["name"] == placeholder_name # Add custom plugin to page layout.begin_column({"md": 8}) plugin_text = printable_gibberish() layout.add_plugin("text", {"text": plugin_text}) view_config.save_placeholder_layout( get_layout_data_key(placeholder_name, layout, context), layout) view_config.publish() c = SmartClient() soup = c.soup(reverse("shuup:cms_page", kwargs={"url": page1.url})) page_content = soup.find("div", {"class": "page-content"}) assert page1_content in page_content.text assert plugin_text in page_content.text # Make sure content not available for page2 c = SmartClient() soup = c.soup(reverse("shuup:cms_page", kwargs={"url": page2.url})) page_content = soup.find("div", {"class": "page-content"}) assert page2_content in page_content.text assert plugin_text not in page_content.text # Make sure content not available for logged in users c = SmartClient() c.login(username=person.user.username, password=password) soup = c.soup(reverse("shuup:cms_page", kwargs={"url": page1.url})) page_content = soup.find("div", {"class": "page-content"}) assert page1_content in page_content.text assert plugin_text not in page_content.text
def test_plugin(): shop = factories.get_default_shop() set_current_theme(ClassicGrayTheme.identifier, shop) blog_page = Page.objects.create(shop=shop, title="Blog", url="blog", available_from=(now() - timedelta(days=10)), available_to=(now() + timedelta(days=10)), content="") # create 10 blog pages for i in range(10): article = Page.objects.create( shop=shop, title="Article %d" % i, url="blog-%d" % i, available_from=(now() - timedelta(days=10)), available_to=(now() + timedelta(days=10)), content="Content %d" % i, template_name="shuup_cms_blog/blog_page.jinja") BlogArticle.objects.create(page=article, is_blog_article=True, image=factories.get_random_filer_image(), small_description="description %d" % i) # create 3 non blog post pages for i in range(3): article = Page.objects.create( shop=shop, title="Nothing %d" % i, url="non-%d" % i, available_from=(now() - timedelta(days=10)), available_to=(now() + timedelta(days=10)), content="content %i" % i) theme = get_current_theme(shop) view_config = ViewConfig(theme=theme, shop=shop, view_name="PageView", draft=True) placeholder_name = "cms_page" context = {"page": blog_page} layout = view_config.get_placeholder_layout(PageLayout, placeholder_name, context=context) assert isinstance(layout, PageLayout) assert layout.get_help_text({}) == "" assert blog_page.title in layout.get_help_text(context) serialized = layout.serialize() assert len(serialized["rows"]) == 0 assert serialized["name"] == placeholder_name layout.begin_column({"sm": 12}) layout.add_plugin(ShuupCMSBlogArticleListPlugin.identifier, {}) view_config.save_placeholder_layout( get_layout_data_key(placeholder_name, layout, context), layout) view_config.publish() client = SmartClient() response, soup = client.response_and_soup( reverse("shuup:cms_page", kwargs={"url": blog_page.url})) assert response.status_code == 200 assert len(soup.find_all("a", {"class": "article-card"})) == 10