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): """ 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
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, )
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)
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_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)