Example #1
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
Example #2
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
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
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_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(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)