Exemple #1
0
def render_content_items(request, items, template_name=None, cachable=None):
    """
    Render a list of :class:`~fluent_contents.models.ContentItem` objects as HTML string.
    This is a variation of the :func:`render_placeholder` function.

    Note that the items are not filtered in any way by parent or language.
    The items are rendered as-is.

    :param request: The current request object.
    :type request: :class:`~django.http.HttpRequest`
    :param items: The list or queryset of objects to render. Passing a queryset is preferred.
    :type items: list or queryset of :class:`~fluent_contents.models.ContentItem`.
    :param template_name: Optional template name used to concatenate the placeholder output.
    :type template_name: str
    :param cachable: Whether the output is cachable, otherwise the full output will not be cached.
                     Default: False when using a template, True otherwise.
    :type cachable: bool | None

    :rtype: :class:`~fluent_contents.models.ContentItemOutput`
    """
    if not items:
        output = ContentItemOutput(mark_safe(u"<!-- no items to render -->"))
    else:
        output = RenderingPipe(request).render_items(
            placeholder=None,
            items=items,
            parent_object=None,
            template_name=template_name,
            cachable=cachable)

    # Wrap the result after it's stored in the cache.
    if markers.is_edit_mode(request):
        output.html = markers.wrap_anonymous_output(output.html)

    return output
Exemple #2
0
def render_content_items(request, items, template_name=None):
    """
    Render a list of :class:`~fluent_contents.models.ContentItem` objects as HTML string.
    This is a variation of the :func:`render_placeholder` function.

    Note that the items are not filtered in any way by parent or language.
    The items are rendered as-is.

    :param request: The current request object.
    :type request: :class:`~django.http.HttpRequest`
    :param items: The list or queryset of objects to render. Passing a queryset is preferred.
    :type items: list or queryset of :class:`~fluent_contents.models.ContentItem`.
    :param template_name: Optional template name used to concatenate the placeholder output.
    :type template_name: str
    :rtype: :class:`~fluent_contents.models.ContentItemOutput`
    """
    if not items:
        output = ContentItemOutput(mark_safe(u"<!-- no items to render -->"))
    else:
        output = _render_items(request,
                               None,
                               items,
                               template_name=template_name)

    if is_edit_mode(request):
        output.html = _wrap_anonymous_output(output.html)

    return output
def render_content_items(request, items, template_name=None, cachable=None):
    """
    Render a list of :class:`~fluent_contents.models.ContentItem` objects as HTML string.
    This is a variation of the :func:`render_placeholder` function.

    Note that the items are not filtered in any way by parent or language.
    The items are rendered as-is.

    :param request: The current request object.
    :type request: :class:`~django.http.HttpRequest`
    :param items: The list or queryset of objects to render. Passing a queryset is preferred.
    :type items: list or queryset of :class:`~fluent_contents.models.ContentItem`.
    :param template_name: Optional template name used to concatenate the placeholder output.
    :type template_name: str
    :param cachable: Whether the output is cachable, otherwise the full output will not be cached.
                     Default: False when using a template, True otherwise.
    :type cachable: bool | None

    :rtype: :class:`~fluent_contents.models.ContentItemOutput`
    """
    if not items:
        output = ContentItemOutput(mark_safe(u"<!-- no items to render -->"))
    else:
        output = _render_items(request, None, items, parent_object=None, template_name=template_name, cachable=cachable)

    if is_edit_mode(request):
        output.html = _wrap_anonymous_output(output.html)

    return output
Exemple #4
0
    def _render_contentitem(self, request, instance):
        # Internal wrapper for render(), to allow updating the method signature easily.
        # It also happens to really simplify code navigation.
        result = self.render(request=request, instance=instance)

        if isinstance(result, ContentItemOutput):
            # Return in new 1.0 format

            # Also include the statically declared FrontendMedia, inserted before any extra added files.
            # These could be included already in the ContentItemOutput object, but duplicates are removed.
            media = self.get_frontend_media(instance)
            if media is not ImmutableMedia.empty_instance:
                result._insert_media(media)

            return result
        elif isinstance(result,
                        (HttpResponseRedirect, HttpResponsePermanentRedirect)):
            # Can't return a HTTP response from a plugin that is rendered as a string in a template.
            # However, this response can be translated into our custom exception-based redirect mechanism.
            return self.redirect(result['Location'], result.status_code)
        else:
            # Old 0.9 syntax, wrap it.
            # The 'cacheable' is implied in the rendering already, but this is just for completeness.
            media = self.get_frontend_media(instance)
            return ContentItemOutput(result,
                                     media,
                                     cacheable=self.cache_output,
                                     cache_timeout=self.cache_timeout)
    def merge_output(self, result, items, template_name):
        """
        Combine all rendered items. Allow rendering the items with a template,
        to inserting separators or nice start/end code.
        """
        html_output, media = self.get_html_output(result, items)

        if not template_name:
            merged_html = mark_safe("".join(html_output))
        else:
            context = {
                "contentitems": list(zip(items, html_output)),
                "parent_object": result.parent_object,  # Can be None
                "edit_mode": self.edit_mode,
            }

            context = PluginContext(self.request, context)
            merged_html = render_to_string(template_name, context.flatten())

        return ContentItemOutput(
            merged_html,
            media,
            cacheable=result.all_cacheable,
            cache_timeout=result.all_timeout,
        )
Exemple #6
0
    def merge_output(self, result, items, template_name):
        """
        Combine all rendered items. Allow rendering the items with a template,
        to inserting separators or nice start/end code.
        """
        html_output, media = self.get_html_output(result, items)

        if not template_name:
            merged_html = mark_safe(u''.join(html_output))
        else:
            context = {
                'contentitems': list(zip(items, html_output)),
                'parent_object': result.parent_object,  # Can be None
                'edit_mode': self.edit_mode,
            }

            if django.VERSION >= (1, 8):
                # Avoid RemovedInDjango110Warning
                context = PluginContext(self.request, context)
                merged_html = render_to_string(template_name, context)
            else:
                merged_html = render_to_string(template_name,
                                               context,
                                               context_instance=PluginContext(
                                                   self.request))

        return ContentItemOutput(merged_html,
                                 media,
                                 cacheable=result.all_cacheable,
                                 cache_timeout=result.all_timeout)
def render_content_items(request, items, template_name=None):
    """
    Render a list of :class:`~fluent_contents.models.ContentItem` objects as HTML string.
    This is a variation of the :func:`render_placeholder` function.

    Note that the items are not filtered in any way by parent or language.
    The items are rendered as-is.
    """
    if not items:
        output = ContentItemOutput(mark_safe(u"<!-- no items to render -->"))
    else:
        output = _render_items(request, None, items, template_name=template_name)

    if is_edit_mode(request):
        output.html = _wrap_anonymous_output(output.html)

    return output
    def render_items(self,
                     placeholder,
                     items,
                     parent_object=None,
                     template_name=None,
                     cachable=None):
        """
        The main rendering sequence.
        """
        # Unless it was done before, disable polymorphic effects.
        is_queryset = False
        if hasattr(items, "non_polymorphic"):
            is_queryset = True
            if not items.polymorphic_disabled and items._result_cache is None:
                items = items.non_polymorphic()

        # See if the queryset contained anything.
        # This test is moved here, to prevent earlier query execution.
        if not items:
            logger.debug(
                "- no items in placeholder '%s'",
                get_placeholder_debug_name(placeholder),
            )
            return ContentItemOutput(
                mark_safe("<!-- no items in placeholder '{}' -->".format(
                    escape(get_placeholder_name(placeholder)))),
                cacheable=True,
            )

        # Tracked data during rendering:
        result = self.result_class(
            request=self.request,
            parent_object=parent_object,
            placeholder=placeholder,
            items=items,
            all_cacheable=self._can_cache_merged_output(
                template_name, cachable),
        )
        if self.edit_mode:
            result.set_uncachable()

        if is_queryset:
            # Phase 1: get cached output
            self._fetch_cached_output(items, result=result)
            result.fetch_remaining_instances()
        else:
            # The items is either a list of manually created items, or it's a QuerySet.
            # Can't prevent reading the subclasses only, so don't bother with caching here.
            result.add_remaining_list(items)

        # Start the actual rendering of remaining items.
        if result.remaining_items:
            # Phase 2: render remaining items
            self._render_uncached_items(result.remaining_items, result=result)

        # And merge all items together.
        return self.merge_output(result, items, template_name)
    def merge_output(self, result, items, template_name):
        # Collect all individual rendered items.
        html_output = []
        for contentitem, output in result.get_output():
            html_output.append(output.html)

        # since media is not included, cachable is false
        merged_html = mark_safe(u"".join(html_output))
        return ContentItemOutput(merged_html, cacheable=False)
Exemple #10
0
    def render_item(self, contentitem):
        """
        Render the item - but render as search text instead.
        """
        plugin = contentitem.plugin
        if not plugin.search_output and not plugin.search_fields:
            # Only render items when the item was output will be indexed.
            raise SkipItem

        if not plugin.search_output:
            output = ContentItemOutput('', cacheable=False)
        else:
            output = super(SearchRenderingPipe, self).render_item(contentitem)

        if plugin.search_fields:
            # Just add the results into the output, but avoid caching that somewhere.
            output.html += plugin.get_search_text(contentitem)
            output.cacheable = False

        return output
    def render_item(self, contentitem):
        """
        Render the item - but render as search text instead.
        """
        plugin = contentitem.plugin
        if not plugin.search_output and not plugin.search_fields:
            # Only render items when the item was output will be indexed.
            raise SkipItem

        if not plugin.search_output:
            output = ContentItemOutput('', cacheable=False)
        else:
            output = super(SearchRenderingPipe, self).render_item(contentitem)

        if plugin.search_fields:
            # Just add the results into the output, but avoid caching that somewhere.
            output.html += plugin.get_search_text(contentitem)
            output.cacheable = False

        return output
def render_content_items(request, items, template_name=None):
    """
    Render a list of :class:`~fluent_contents.models.ContentItem` objects as HTML string.
    This is a variation of the :func:`render_placeholder` function.

    Note that the items are not filtered in any way by parent or language.
    The items are rendered as-is.
    """
    if not items:
        output = ContentItemOutput(mark_safe(u"<!-- no items to render -->"))
    else:
        output = _render_items(request,
                               None,
                               items,
                               template_name=template_name)

    if is_edit_mode(request):
        output.html = _wrap_anonymous_output(output.html)

    return output
 def _render_contentitem(self, request, instance):
     # Internal wrapper for render(), to allow updating the method signature easily.
     # It also happens to really simplify code navigation.
     result = self.render(request=request, instance=instance)
     if isinstance(result, ContentItemOutput):
         # Return in new 1.0 format
         return result
     else:
         # Old 0.9 syntax, correct it.
         html = result
         media = self.get_frontend_media(instance)
         return ContentItemOutput(html, media)
def _render_items(request, placeholder, items, parent_object=None, template_name=None, cachable=False):
    edit_mode = is_edit_mode(request)
    item_output = {}
    output_ordering = []
    all_cacheable = True
    all_timeout = DEFAULT_TIMEOUT

    placeholder_cache_name = "@global@" if placeholder is None else placeholder.slot

    if not hasattr(items, "non_polymorphic"):
        # The items is either a list of manually created items, or it's a QuerySet.
        # Can't prevent reading the subclasses only, so don't bother with caching here.
        remaining_items = items
        output_ordering = [item.pk or id(item) for item in items]
    else:
        # Unless it was done before, disable polymorphic effects.
        if not items.polymorphic_disabled:
            items = items.non_polymorphic()

        # First try to fetch all items non-polymorphic from memcache
        # If these are found, there is no need to query the derived data from the database.
        remaining_items = []
        for i, contentitem in enumerate(items):
            output_ordering.append(contentitem.pk)
            output = None
            # Respect the cache output setting of the plugin
            if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT:
                try:
                    plugin = contentitem.plugin
                except PluginNotFound:
                    pass
                else:
                    if plugin.cache_output and contentitem.pk:
                        output = plugin.get_cached_output(placeholder_cache_name, contentitem)
                        all_timeout = _min_timeout(all_timeout, plugin.cache_timeout)

                        # Support transition to new output format.
                        if output is not None and not isinstance(output, ContentItemOutput):
                            output = None
                            logger.debug(
                                "Flushed cached output of {0}#{1} to store new ContentItemOutput format (key: {2})".format(
                                    plugin.type_name, contentitem.pk, placeholder_cache_name
                                )
                            )

            # For debugging, ignore cached values when the template is updated.
            if output and settings.DEBUG:
                cachekey = get_rendering_cache_key(placeholder_cache_name, contentitem)
                if _is_template_updated(request, contentitem, cachekey):
                    output = None

            if output:
                item_output[contentitem.pk] = output
            else:
                remaining_items.append(contentitem)

        # Fetch derived table data for all objects not found in memcached
        if remaining_items:
            remaining_items = items.get_real_instances(remaining_items)

    # See if the queryset contained anything.
    # This test is moved here, to prevent earlier query execution.
    if not items:
        placeholder_name = _get_placeholder_name(placeholder)
        logger.debug("- no items in placeholder '%s'", placeholder_name)
        return ContentItemOutput(mark_safe(u"<!-- no items in placeholder '{0}' -->".format(escape(placeholder_name))))
    elif remaining_items:
        # Render remaining items
        for contentitem in remaining_items:
            try:
                plugin = contentitem.plugin
            except PluginNotFound as e:
                output = ContentItemOutput(mark_safe(u"<!-- error: {0} -->\n".format(str(e))))
                logger.debug("- item #%s has no matching plugin: %s", contentitem.pk, str(e))
            else:
                if plugin.render_ignore_item_language or (plugin.cache_output and plugin.cache_output_per_language):
                    # Render the template in the current language.
                    # The cache also stores the output under the current language code.
                    #
                    # It would make sense to apply this for fallback content too,
                    # but that would be ambiguous however because the parent_object could also be a fallback,
                    # and that case can't be detected here. Hence, better be explicit when desiring multi-lingual content.
                    render_language = get_language()  # Avoid switching the content,
                else:
                    # Render the template in the ContentItem language.
                    # This makes sure that {% trans %} tag output matches the language of the model field data.
                    render_language = contentitem.language_code

                with smart_override(render_language):
                    # Plugin output is likely HTML, but it should be placed in mark_safe() to raise awareness about escaping.
                    # This is just like Django's Input.render() and unlike Node.render().
                    output = plugin._render_contentitem(request, contentitem)

                if (
                    appsettings.FLUENT_CONTENTS_CACHE_OUTPUT
                    and plugin.cache_output
                    and output.cacheable
                    and contentitem.pk
                ):
                    # Cache the output
                    contentitem.plugin.set_cached_output(placeholder_cache_name, contentitem, output)
                else:
                    # Item blocks caching the complete placeholder.
                    all_cacheable = False

                    if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT:
                        logger.debug("- item #%s is NOT cachable! Prevented by %r", contentitem.pk, plugin)

                if edit_mode:
                    output.html = _wrap_contentitem_output(output.html, contentitem)

            item_id = contentitem.pk or id(contentitem)
            item_output[item_id] = output

    # Order all rendered items in the correct sequence.  The derived tables could be truncated/reset,
    # so the base class model indexes don't necessary match with the derived indexes. Hence the dict + KeyError handling.
    #
    # The media is also collected in the same ordering, in case it's handled by django-compressor for example.
    output_ordered = []
    merged_media = Media()
    for pk in output_ordering:
        try:
            output = item_output[pk]
            output_ordered.append(output.html)
            _add_media(merged_media, output.media)
        except KeyError:
            # The get_real_instances() didn't return an item for the derived table. This happens when either:
            # - that table is truncated/reset, while there is still an entry in the base ContentItem table.
            #   A query at the derived table happens every time the page is being rendered.
            # - the model was completely removed which means there is also a stale ContentType object.
            item = next(item for item in items if item.pk == pk)
            try:
                class_name = item.plugin.type_name
            except PluginNotFound:
                # Derived table isn't there because the model has been removed.
                # There is a stale ContentType object, no plugin associated or loaded.
                class_name = "content type is stale"

            output_ordered.append(
                u"<!-- Missing derived model for ContentItem #{id}: {cls}. -->\n".format(id=pk, cls=class_name)
            )
            logger.warning("Missing derived model for ContentItem #{id}: {cls}.".format(id=pk, cls=class_name))
            pass

    # Combine all rendered items. Allow rendering the items with a template,
    # to inserting separators or nice start/end code.
    if not template_name:
        merged_output = mark_safe("".join(output_ordered))
    else:
        context = {
            "contentitems": list(zip(items, output_ordered)),
            "parent_object": parent_object,  # Can be None
            "edit_mode": edit_mode,
        }
        merged_output = render_to_string(template_name, context, context_instance=RequestContext(request))

        # By default, cachable is False for templates.
        # Template name is ambiguous, can't reliable expire.
        # Nor can be determined whether the template is consistent or not cacheable.

    if not cachable:
        all_cacheable = False

    return ContentItemOutput(merged_output, merged_media, cacheable=all_cacheable, cache_timeout=all_timeout)
def _render_items(request, placeholder, items, parent_object=None, template_name=None):
    edit_mode = is_edit_mode(request)
    item_output = {}
    output_ordering = []
    placeholder_cache_name = '@global@' if placeholder is None else placeholder.slot

    if not hasattr(items, "non_polymorphic"):
        # The items is either a list of manually created items, or it's a QuerySet.
        # Can't prevent reading the subclasses only, so don't bother with caching here.
        remaining_items = items
        output_ordering = [item.pk or id(item) for item in items]
    else:
        items = items.non_polymorphic()

        # First try to fetch all items non-polymorphic from memcache
        # If these are found, there is no need to query the derived data from the database.
        remaining_items = []
        for i, contentitem in enumerate(items):
            output_ordering.append(contentitem.pk)
            output = None
            try:
                # Respect the cache output setting of the plugin
                if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT and contentitem.plugin.cache_output and contentitem.pk:
                    output = contentitem.plugin.get_cached_output(placeholder_cache_name, contentitem)

                    # Support transition to new output format.
                    if not isinstance(output, ContentItemOutput):
                        output = None
                        logger.debug("Flushed cached output of {0}#{1} to store new format (key: {2}) ".format(contentitem.plugin.type_name, contentitem.pk, placeholder_cache_name))
            except PluginNotFound:
                pass

            # For debugging, ignore cached values when the template is updated.
            if output and settings.DEBUG:
                cachekey = get_rendering_cache_key(placeholder_cache_name, contentitem)
                if _is_template_updated(request, contentitem, cachekey):
                    output = None

            if output:
                item_output[contentitem.pk] = output
            else:
                remaining_items.append(contentitem)

        # Fetch derived table data for all objects not found in memcached
        if remaining_items:
            remaining_items = items.get_real_instances(remaining_items)

    # See if the queryset contained anything.
    # This test is moved here, to prevent earlier query execution.
    if not items:
        return ContentItemOutput(mark_safe(u"<!-- no items in placeholder '{0}' -->".format(escape(_get_placeholder_name(placeholder)))))
    elif remaining_items:
        # Render remaining items
        for contentitem in remaining_items:
            try:
                plugin = contentitem.plugin
            except PluginNotFound as e:
                output = ContentItemOutput(mark_safe(u'<!-- error: {0} -->\n'.format(str(e))))
            else:
                # Always try to render the template in the ContentItem language.
                # This makes sure that {% trans %} tags function properly if the language happens to be different.
                with smart_override(contentitem.language_code):
                    # Plugin output is likely HTML, but it should be placed in mark_safe() to raise awareness about escaping.
                    # This is just like Django's Input.render() and unlike Node.render().
                    output = plugin._render_contentitem(request, contentitem)

                if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT and plugin.cache_output and contentitem.pk:
                    contentitem.plugin.set_cached_output(placeholder_cache_name, contentitem, output)

                if edit_mode:
                    output.html = _wrap_contentitem_output(output.html, contentitem)

            item_id = contentitem.pk or id(contentitem)
            item_output[item_id] = output

    # Order all rendered items in the correct sequence.  The derived tables could be truncated/reset,
    # so the base class model indexes don't necessary match with the derived indexes. Hence the dict + KeyError handling.
    #
    # The media is also collected in the same ordering, in case it's handled by django-compressor for example.
    output_ordered = []
    merged_media = Media()
    for pk in output_ordering:
        try:
            output = item_output[pk]
            output_ordered.append(output.html)
            _add_media(merged_media, output.media)
        except KeyError:
            # The get_real_instances() didn't return an item for the derived table. This happens when either:
            # - that table is truncated/reset, while there is still an entry in the base ContentItem table.
            #   A query at the derived table happens every time the page is being rendered.
            # - the model was completely removed which means there is also a stale ContentType object.
            item = next(item for item in items if item.pk == pk)
            try:
                class_name = item.plugin.type_name
            except PluginNotFound:
                # Derived table isn't there because the model has been removed.
                # There is a stale ContentType object, no plugin associated or loaded.
                class_name = 'content type is stale'

            output_ordered.append(u"<!-- Missing derived model for ContentItem #{id}: {cls}. -->\n".format(id=pk, cls=class_name))
            logger.warning("Missing derived model for ContentItem #{id}: {cls}.".format(id=pk, cls=class_name))
            pass


    # Combine all rendered items. Allow rendering the items with a template,
    # to inserting separators or nice start/end code.
    if not template_name:
        merged_output = mark_safe(''.join(output_ordered))
    else:
        context = {
            'contentitems': list(zip(items, output_ordered)),
            'parent_object': parent_object,  # Can be None
            'edit_mode': edit_mode,
        }
        merged_output = render_to_string(template_name, context, context_instance=RequestContext(request))

    return ContentItemOutput(merged_output, merged_media)
def _render_items(request,
                  placeholder,
                  items,
                  parent_object=None,
                  template_name=None,
                  cachable=False):
    edit_mode = is_edit_mode(request)
    item_output = {}
    output_ordering = []
    all_cacheable = True
    all_timeout = DEFAULT_TIMEOUT

    placeholder_cache_name = '@global@' if placeholder is None else placeholder.slot

    if not hasattr(items, "non_polymorphic"):
        # The items is either a list of manually created items, or it's a QuerySet.
        # Can't prevent reading the subclasses only, so don't bother with caching here.
        remaining_items = items
        output_ordering = [item.pk or id(item) for item in items]
    else:
        # Unless it was done before, disable polymorphic effects.
        if not items.polymorphic_disabled:
            items = items.non_polymorphic()

        # First try to fetch all items non-polymorphic from memcache
        # If these are found, there is no need to query the derived data from the database.
        remaining_items = []
        for i, contentitem in enumerate(items):
            output_ordering.append(contentitem.pk)
            output = None
            # Respect the cache output setting of the plugin
            if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT:
                try:
                    plugin = contentitem.plugin
                except PluginNotFound:
                    pass
                else:
                    if plugin.cache_output and contentitem.pk:
                        output = plugin.get_cached_output(
                            placeholder_cache_name, contentitem)
                        all_timeout = _min_timeout(all_timeout,
                                                   plugin.cache_timeout)

                        # Support transition to new output format.
                        if output is not None and not isinstance(
                                output, ContentItemOutput):
                            output = None
                            logger.debug(
                                "Flushed cached output of {0}#{1} to store new ContentItemOutput format (key: {2})"
                                .format(plugin.type_name, contentitem.pk,
                                        placeholder_cache_name))

            # For debugging, ignore cached values when the template is updated.
            if output and settings.DEBUG:
                cachekey = get_rendering_cache_key(placeholder_cache_name,
                                                   contentitem)
                if _is_template_updated(request, contentitem, cachekey):
                    output = None

            if output:
                item_output[contentitem.pk] = output
            else:
                remaining_items.append(contentitem)

        # Fetch derived table data for all objects not found in memcached
        if remaining_items:
            remaining_items = items.get_real_instances(remaining_items)

    # See if the queryset contained anything.
    # This test is moved here, to prevent earlier query execution.
    if not items:
        placeholder_name = _get_placeholder_name(placeholder)
        logger.debug("- no items in placeholder '%s'", placeholder_name)
        return ContentItemOutput(
            mark_safe(u"<!-- no items in placeholder '{0}' -->".format(
                escape(placeholder_name))))
    elif remaining_items:
        # Render remaining items
        for contentitem in remaining_items:
            try:
                plugin = contentitem.plugin
            except PluginNotFound as e:
                output = ContentItemOutput(
                    mark_safe(u'<!-- error: {0} -->\n'.format(str(e))))
                logger.debug("- item #%s has no matching plugin: %s",
                             contentitem.pk, str(e))
            else:
                if plugin.render_ignore_item_language \
                or (plugin.cache_output and plugin.cache_output_per_language):
                    # Render the template in the current language.
                    # The cache also stores the output under the current language code.
                    #
                    # It would make sense to apply this for fallback content too,
                    # but that would be ambiguous however because the parent_object could also be a fallback,
                    # and that case can't be detected here. Hence, better be explicit when desiring multi-lingual content.
                    render_language = get_language(
                    )  # Avoid switching the content,
                else:
                    # Render the template in the ContentItem language.
                    # This makes sure that {% trans %} tag output matches the language of the model field data.
                    render_language = contentitem.language_code

                with smart_override(render_language):
                    # Plugin output is likely HTML, but it should be placed in mark_safe() to raise awareness about escaping.
                    # This is just like Django's Input.render() and unlike Node.render().
                    output = plugin._render_contentitem(request, contentitem)

                if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT \
                and plugin.cache_output \
                and output.cacheable \
                and contentitem.pk:
                    # Cache the output
                    contentitem.plugin.set_cached_output(
                        placeholder_cache_name, contentitem, output)
                else:
                    # Item blocks caching the complete placeholder.
                    all_cacheable = False

                    if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT:
                        logger.debug(
                            "- item #%s is NOT cachable! Prevented by %r",
                            contentitem.pk, plugin)

                if edit_mode:
                    output.html = _wrap_contentitem_output(
                        output.html, contentitem)

            item_id = contentitem.pk or id(contentitem)
            item_output[item_id] = output

    # Order all rendered items in the correct sequence.  The derived tables could be truncated/reset,
    # so the base class model indexes don't necessary match with the derived indexes. Hence the dict + KeyError handling.
    #
    # The media is also collected in the same ordering, in case it's handled by django-compressor for example.
    output_ordered = []
    merged_media = Media()
    for pk in output_ordering:
        try:
            output = item_output[pk]
            output_ordered.append(output.html)
            _add_media(merged_media, output.media)
        except KeyError:
            # The get_real_instances() didn't return an item for the derived table. This happens when either:
            # - that table is truncated/reset, while there is still an entry in the base ContentItem table.
            #   A query at the derived table happens every time the page is being rendered.
            # - the model was completely removed which means there is also a stale ContentType object.
            item = next(item for item in items if item.pk == pk)
            try:
                class_name = item.plugin.type_name
            except PluginNotFound:
                # Derived table isn't there because the model has been removed.
                # There is a stale ContentType object, no plugin associated or loaded.
                class_name = 'content type is stale'

            output_ordered.append(
                u"<!-- Missing derived model for ContentItem #{id}: {cls}. -->\n"
                .format(id=pk, cls=class_name))
            logger.warning(
                "Missing derived model for ContentItem #{id}: {cls}.".format(
                    id=pk, cls=class_name))
            pass

    # Combine all rendered items. Allow rendering the items with a template,
    # to inserting separators or nice start/end code.
    if not template_name:
        merged_output = mark_safe(''.join(output_ordered))
    else:
        context = {
            'contentitems': list(zip(items, output_ordered)),
            'parent_object': parent_object,  # Can be None
            'edit_mode': edit_mode,
        }
        merged_output = render_to_string(
            template_name, context, context_instance=RequestContext(request))

        # By default, cachable is False for templates.
        # Template name is ambiguous, can't reliable expire.
        # Nor can be determined whether the template is consistent or not cacheable.

    if not cachable:
        all_cacheable = False

    return ContentItemOutput(merged_output,
                             merged_media,
                             cacheable=all_cacheable,
                             cache_timeout=all_timeout)
def _render_items(request, placeholder, items, template_name=None):
    edit_mode = is_edit_mode(request)
    item_output = {}
    output_ordering = []
    placeholder_cache_name = '@global@' if placeholder is None else placeholder.slot

    if not hasattr(items, "non_polymorphic"):
        # The items is either a list of manually created items, or it's a QuerySet.
        # Can't prevent reading the subclasses only, so don't bother with caching here.
        remaining_items = items
        output_ordering = [item.pk or id(item) for item in items]
    else:
        items = items.non_polymorphic()

        # First try to fetch all items non-polymorphic from memcache
        # If these are found, there is no need to query the derived data from the database.
        remaining_items = []
        for i, contentitem in enumerate(items):
            output_ordering.append(contentitem.pk)
            output = None
            try:
                # Respect the cache output setting of the plugin
                if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT and contentitem.plugin.cache_output and contentitem.pk:
                    output = contentitem.plugin.get_cached_output(
                        placeholder_cache_name, contentitem)

                    # Support transition to new output format.
                    if not isinstance(output, ContentItemOutput):
                        output = None
                        logger.debug(
                            "Flushed cached output of {0}#{1} to store new format (key: {2}) "
                            .format(contentitem.plugin.type_name,
                                    contentitem.pk, placeholder_cache_name))
            except PluginNotFound:
                pass

            # For debugging, ignore cached values when the template is updated.
            if output and settings.DEBUG:
                cachekey = get_rendering_cache_key(placeholder_cache_name,
                                                   contentitem)
                if _is_template_updated(request, contentitem, cachekey):
                    output = None

            if output:
                item_output[contentitem.pk] = output
            else:
                remaining_items.append(contentitem)

        # Fetch derived table data for all objects not found in memcached
        if remaining_items:
            remaining_items = items.get_real_instances(remaining_items)

    # See if the queryset contained anything.
    # This test is moved here, to prevent earlier query execution.
    if not items:
        return ContentItemOutput(
            mark_safe(u"<!-- no items in placeholder '{0}' -->".format(
                escape(_get_placeholder_name(placeholder)))))
    elif remaining_items:
        # Render remaining items
        for contentitem in remaining_items:
            try:
                plugin = contentitem.plugin
            except PluginNotFound as e:
                output = ContentItemOutput(
                    mark_safe(u'<!-- error: {0} -->\n'.format(str(e))))
            else:
                # Plugin output is likely HTML, but it should be placed in mark_safe() to raise awareness about escaping.
                # This is just like Django's Input.render() and unlike Node.render().
                output = plugin._render_contentitem(request, contentitem)

                if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT and plugin.cache_output and contentitem.pk:
                    contentitem.plugin.set_cached_output(
                        placeholder_cache_name, contentitem, output)

                if edit_mode:
                    output.html = _wrap_contentitem_output(
                        output.html, contentitem)

            item_id = contentitem.pk or id(contentitem)
            item_output[item_id] = output

    # Order all rendered items in the correct sequence.  The derived tables could be truncated/reset,
    # so the base class model indexes don't necessary match with the derived indexes. Hence the dict + KeyError handling.
    #
    # The media is also collected in the same ordering, in case it's handled by django-compressor for example.
    output_ordered = []
    merged_media = Media()
    for pk in output_ordering:
        try:
            output = item_output[pk]
            output_ordered.append(output.html)
            _add_media(merged_media, output.media)
        except KeyError:
            # The get_real_instances() didn't return an item for the derived table. This happens when either:
            # - that table is truncated/reset, while there is still an entry in the base ContentItem table.
            #   A query at the derived table happens every time the page is being rendered.
            # - the model was completely removed which means there is also a stale ContentType object.
            item = next(item for item in items if item.pk == pk)
            try:
                class_name = item.plugin.type_name
            except PluginNotFound:
                # Derived table isn't there because the model has been removed.
                # There is a stale ContentType object, no plugin associated or loaded.
                class_name = 'content type is stale'

            output_ordered.append(
                u"<!-- Missing derived model for ContentItem #{id}: {cls}. -->\n"
                .format(id=pk, cls=class_name))
            logger.warning(
                "Missing derived model for ContentItem #{id}: {cls}.".format(
                    id=pk, cls=class_name))
            pass

    # Combine all rendered items. Allow rendering the items with a template,
    # to inserting separators or nice start/end code.
    if not template_name:
        merged_output = mark_safe(''.join(output_ordered))
    else:
        context = {
            'contentitems': zip(items, output_ordered),
            'edit_mode': edit_mode,
        }
        merged_output = render_to_string(
            template_name, context, context_instance=RequestContext(request))

    return ContentItemOutput(merged_output, merged_media)