def get_output_cache_base_key(self, placeholder_name, instance): """ .. versionadded:: 1.0 Return the default cache key, both :func:`get_output_cache_key` and :func:`get_output_cache_keys` rely on this. By default, this function generates the cache key using :func:`~fluent_contents.cache.get_rendering_cache_key`. """ return get_rendering_cache_key(placeholder_name, instance)
def get_output_cache_keys(self, placeholder_name, instance): """ .. versionadded:: 0.9 Return the possible cache keys for a rendered item. This method should be overwritten when implementing a function :func:`set_cached_output` method or when implementing a :func:`get_output_cache_key` function. By default, this function generates the cache key using :func:`get_output_cache_base_key`. """ base_key = self.get_output_cache_base_key(placeholder_name, instance) cachekeys = [ base_key ] if self.cache_output_per_site: site_ids = list(Site.objects.values_list('pk', flat=True)) if settings.SITE_ID not in site_ids: site_ids.append(settings.SITE_ID) base_key = get_rendering_cache_key(placeholder_name, instance) cachekeys = ["{0}-s{1}".format(base_key, site_id) for site_id in site_ids] if self.cache_output_per_language: # Append language code to all keys, # have to invalidate a lot more items in memcache total_list = [] for user_language in list(self.cache_supported_language_codes) + ['unsupported']: total_list.extend("{0}.{1}".format(base, user_language) for base in cachekeys) cachekeys = total_list return cachekeys
def get_output_cache_keys(self, placeholder_name, instance): """ .. versionadded:: 0.9 Return the possible cache keys for a rendered item. This method should be overwritten when implementing a function :func:`set_cached_output` method or when implementing a :func:`get_output_cache_key` function. By default, this function generates the cache key using :func:`get_output_cache_base_key`. """ base_key = self.get_output_cache_base_key(placeholder_name, instance) cachekeys = [base_key] if self.cache_output_per_site: site_ids = list(Site.objects.values_list('pk', flat=True)) if settings.SITE_ID not in site_ids: site_ids.append(settings.SITE_ID) base_key = get_rendering_cache_key(placeholder_name, instance) cachekeys = [ "{0}-s{1}".format(base_key, site_id) for site_id in site_ids ] if self.cache_output_per_language: # Append language code to all keys, # have to invalidate a lot more items in memcache total_list = [] for user_language in list( self.cache_supported_language_codes) + ['unsupported']: total_list.extend("{0}.{1}".format(base, user_language) for base in cachekeys) cachekeys = total_list return cachekeys
def get_cache_keys(self): """ Get a list of all cache keys associated with this model. """ if not self.placeholder_id: # TODO: prune old placeholder slot name? return [] placeholder_name = self.placeholder.slot return [get_rendering_cache_key(placeholder_name, self)]
def get_output_cache_key(self, placeholder_name, instance): """ .. versionadded:: 0.9 Return the default cache key which is used to store a rendered item. By default, this function generates the cache key using :func:`~fluent_contents.cache.get_rendering_cache_key`. """ cachekey = get_rendering_cache_key(placeholder_name, instance) if self.cache_output_per_site: cachekey = "{0}-s{1}".format(cachekey, settings.SITE_ID) return cachekey
def _fetch_cached_output(self, items, result): """ First try to fetch all items from the cache. The items are 'non-polymorphic', so only point to their base class. If these are found, there is no need to query the derived data from the database. """ if not appsettings.FLUENT_CONTENTS_CACHE_OUTPUT or not self.use_cached_output: result.add_remaining_list(items) return for contentitem in items: result.add_ordering(contentitem) output = None try: plugin = contentitem.plugin except PluginNotFound as ex: result.store_exception(contentitem, ex) # Will deal with that later. logger.debug("- item #%s has no matching plugin: %s", contentitem.pk, str(ex)) continue # Respect the cache output setting of the plugin if self.can_use_cached_output(contentitem): result.add_plugin_timeout(plugin) output = plugin.get_cached_output(result.placeholder_name, contentitem) # Support transition to new output format. if output is not None and not isinstance( output, ContentItemOutput): output = None logger.debug( "Flushed cached output of {}#{} to store new ContentItemOutput format (key: {})" .format( plugin.type_name, contentitem.pk, get_placeholder_name(contentitem.placeholder), )) # For debugging, ignore cached values when the template is updated. if output and settings.DEBUG: cachekey = get_rendering_cache_key(result.placeholder_name, contentitem) if is_template_updated(self.request, contentitem, cachekey): output = None if output: result.store_output(contentitem, output) else: result.add_remaining(contentitem)
def get_output_cache_keys(self, placeholder_name, instance): """ .. versionadded:: 0.9 Return the possible cache keys for a rendered item. This method should be overwritten when implementing a function :func:`set_cached_output` method or when implementing a :func:`get_output_cache_key` function. By default, this function generates the cache key using :func:`get_output_cache_base_key`. """ base_key = self.get_output_cache_base_key(placeholder_name, instance) cachekeys = [ base_key, ] if self.cache_output_per_site: site_ids = list(Site.objects.values_list('pk', flat=True)) if settings.SITE_ID not in site_ids: site_ids.append(settings.SITE_ID) base_key = get_rendering_cache_key(placeholder_name, instance) cachekeys = [ "{0}-s{1}".format(base_key, site_id) for site_id in site_ids ] if self.cache_output_per_language or self.render_ignore_item_language: # Append language code to all keys, # have to invalidate a lot more items in memcache. # Also added "None" suffix, since get_parent_language_code() may return that. # TODO: ideally for render_ignore_item_language, only invalidate all when the fallback language changed. total_list = [] cache_languages = list( self.cache_supported_language_codes) + ['unsupported', 'None'] # All variants of the Placeholder (for full page caching) placeholder = instance.placeholder total_list.extend( get_placeholder_cache_key(placeholder, lc) for lc in cache_languages) # All variants of the ContentItem in different languages for user_language in cache_languages: total_list.extend("{0}.{1}".format(base, user_language) for base in cachekeys) cachekeys = total_list return cachekeys
def _fetch_cached_output(self, items, result): """ First try to fetch all items from the cache. The items are 'non-polymorphic', so only point to their base class. If these are found, there is no need to query the derived data from the database. """ if not appsettings.FLUENT_CONTENTS_CACHE_OUTPUT or not self.use_cached_output: result.add_remaining_list(items) return for contentitem in items: result.add_ordering(contentitem) output = None try: plugin = contentitem.plugin except PluginNotFound as ex: result.store_exception(contentitem, ex) # Will deal with that later. logger.debug("- item #%s has no matching plugin: %s", contentitem.pk, str(ex)) continue # Respect the cache output setting of the plugin if self.can_use_cached_output(contentitem): result.add_plugin_timeout(plugin) output = plugin.get_cached_output(result.placeholder_name, contentitem) # 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, get_placeholder_name(contentitem.placeholder) )) # For debugging, ignore cached values when the template is updated. if output and settings.DEBUG: cachekey = get_rendering_cache_key(result.placeholder_name, contentitem) if is_template_updated(self.request, contentitem, cachekey): output = None if output: result.store_output(contentitem, output) else: result.add_remaining(contentitem)
def get_output_cache_keys(self, placeholder_name, instance): """ .. versionadded:: 0.9 Return the possible cache keys for a rendered item. This method should be overwritten when implementing a function :func:`set_cached_output` method or implementing a :func:`get_output_cache_key` function which can return multiple results. By default, this function generates the cache key using :func:`get_output_cache_key`. """ if self.cache_output_per_site: site_ids = list(Site.objects.values_list('pk', flat=True)) if settings.SITE_ID not in site_ids: site_ids.append(settings.SITE_ID) base_key = get_rendering_cache_key(placeholder_name, instance) return ["{0}-s{1}".format(base_key, site_id) for site_id in site_ids] else: return [ self.get_output_cache_key(placeholder_name, instance) ]
def get_output_cache_keys(self, placeholder_name, instance): """ .. versionadded:: 0.9 Return the possible cache keys for a rendered item. This method should be overwritten when implementing a function :func:`set_cached_output` method or when implementing a :func:`get_output_cache_key` function. By default, this function generates the cache key using :func:`get_output_cache_base_key`. """ base_key = self.get_output_cache_base_key(placeholder_name, instance) cachekeys = [ base_key, ] if self.cache_output_per_site: site_ids = list(Site.objects.values_list('pk', flat=True)) if settings.SITE_ID not in site_ids: site_ids.append(settings.SITE_ID) base_key = get_rendering_cache_key(placeholder_name, instance) cachekeys = ["{0}-s{1}".format(base_key, site_id) for site_id in site_ids] if self.cache_output_per_language or self.render_ignore_item_language: # Append language code to all keys, # have to invalidate a lot more items in memcache. # Also added "None" suffix, since get_parent_language_code() may return that. # TODO: ideally for render_ignore_item_language, only invalidate all when the fallback language changed. total_list = [] cache_languages = list(self.cache_supported_language_codes) + ['unsupported', 'None'] # All variants of the Placeholder (for full page caching) placeholder = instance.placeholder total_list.extend(get_placeholder_cache_key(placeholder, lc) for lc in cache_languages) # All variants of the ContentItem in different languages for user_language in cache_languages: total_list.extend("{0}.{1}".format(base, user_language) for base in cachekeys) cachekeys = total_list return cachekeys
def get_output_cache_keys(self, placeholder_name, instance): """ .. versionadded:: 0.9 Return the possible cache keys for a rendered item. This method should be overwritten when implementing a function :func:`set_cached_output` method or when implementing a :func:`get_output_cache_key` function. By default, this function generates the cache key using :func:`get_output_cache_base_key`. """ base_key = self.get_output_cache_base_key(placeholder_name, instance) if self.cache_output_per_site: site_ids = list(Site.objects.values_list('pk', flat=True)) if settings.SITE_ID not in site_ids: site_ids.append(settings.SITE_ID) base_key = get_rendering_cache_key(placeholder_name, instance) return [ "{0}-s{1}".format(base_key, site_id) for site_id in site_ids ] else: return [base_key]
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) 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) html = None try: # Respect the cache output setting of the plugin if appsettings.FLUENT_CONTENTS_CACHE_OUTPUT and contentitem.plugin.cache_output and contentitem.pk: html = contentitem.plugin.get_cached_output(placeholder_cache_name, contentitem) except PluginNotFound: pass # For debugging, ignore cached values when the template is updated. if html and settings.DEBUG: cachekey = get_rendering_cache_key(placeholder_cache_name, contentitem) if _is_template_updated(request, contentitem, cachekey): html = None if html: output[contentitem.pk] = html 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 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: html = '<!-- 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(). html = conditional_escape(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, html) if edit_mode: html = _wrap_contentitem_output(html, contentitem) output[contentitem.pk or id(contentitem)] = html # Order all rendered items in the correct sequence. The derived tables should be truncated/reset, # so the base class model indexes don't necessary match with the derived indexes. output_ordered = [] for pk in output_ordering: try: output_ordered.append(output[pk]) 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("<!-- 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 seperators or nice start/end code. if not template_name: return mark_safe(''.join(output_ordered)) else: context = { 'contentitems': zip(items, output_ordered), 'edit_mode': edit_mode, } return render_to_string(template_name, context, context_instance=RequestContext(request))
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_name, items, template_name=None): edit_mode = is_edit_mode(request) output = {} output_ordering = [] if not hasattr(items, "non_polymorphic"): # Can't deal with caching, pass the whole queryset as (i, item) list. remaining_items = items output_ordering = (item.pk for item in items) else: items = items.non_polymorphic() # First try to fetch all items non-polymorphic from memcache remaining_items = [] for i, contentitem in enumerate(items): output_ordering.append(contentitem.pk) html = None try: # Respect the cache output setting of the plugin if contentitem.plugin.cache_output and appsettings.FLUENT_CONTENTS_CACHE_OUTPUT: cachekey = get_rendering_cache_key(placeholder_name, contentitem) html = cache.get(cachekey) except PluginNotFound: pass if html: output[contentitem.pk] = html else: remaining_items.append(contentitem) 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 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: html = '<!-- 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 Input.render() and unlike Node.render(). html = conditional_escape(plugin._render_contentitem(request, contentitem)) if plugin.cache_output and appsettings.FLUENT_CONTENTS_CACHE_OUTPUT: cachekey = get_rendering_cache_key(placeholder_name, contentitem) cache.set(cachekey, html) if edit_mode: html = _wrap_contentitem_output(html, contentitem) output[contentitem.pk] = html # Order all rendered items in the correct sequence. The derived tables should be truncated/reset, # so the base class model indexes don't necessary match with the derived indexes. output_ordered = [] for pk in output_ordering: try: output_ordered.append(output[pk]) except KeyError: # NOTE: if a table is truncated/reset, the base class still exists and causes a query to happen every time. item = [item for item in items if item.pk == pk][0] logger.warning("Missing derived model for ContentItem #{id}: {cls}.".format(id=pk, cls=item.plugin.type_name)) pass # Combine all rendered items. Allow rendering the items with a template, # to inserting seperators or nice start/end code. if not template_name: return mark_safe(''.join(output_ordered)) else: context = { 'contentitems': zip(items, output_ordered), 'edit_mode': edit_mode, } return render_to_string(template_name, context, context_instance=RequestContext(request))
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)
def _render_items(request, placeholder_name, items, template_name=None): edit_mode = is_edit_mode(request) output = {} output_ordering = [] 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) html = None try: # Respect the cache output setting of the plugin if contentitem.plugin.cache_output and contentitem.pk and appsettings.FLUENT_CONTENTS_CACHE_OUTPUT: cachekey = get_rendering_cache_key(placeholder_name, contentitem) html = cache.get(cachekey) except PluginNotFound: pass if html: output[contentitem.pk] = html 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 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: html = '<!-- 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(). html = conditional_escape( plugin._render_contentitem(request, contentitem)) if plugin.cache_output and contentitem.pk and appsettings.FLUENT_CONTENTS_CACHE_OUTPUT: cachekey = get_rendering_cache_key(placeholder_name, contentitem) cache.set(cachekey, html) if edit_mode: html = _wrap_contentitem_output(html, contentitem) output[contentitem.pk or id(contentitem)] = html # Order all rendered items in the correct sequence. The derived tables should be truncated/reset, # so the base class model indexes don't necessary match with the derived indexes. output_ordered = [] for pk in output_ordering: try: output_ordered.append(output[pk]) except KeyError: # NOTE: if a table is truncated/reset, the base class still exists and causes a query to happen every time. item = [item for item in items if item.pk == pk][0] logger.warning( "Missing derived model for ContentItem #{id}: {cls}.".format( id=pk, cls=item.plugin.type_name)) pass # Combine all rendered items. Allow rendering the items with a template, # to inserting seperators or nice start/end code. if not template_name: return mark_safe(''.join(output_ordered)) else: context = { 'contentitems': zip(items, output_ordered), 'edit_mode': edit_mode, } return render_to_string(template_name, context, context_instance=RequestContext(request))
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)