Reputation: 981
I would like to write a filter which replaces some $variables$ in my streamfield text. What is the best way to do this in my "Page" model? I tried the following but it is sometimes not working if I save my model as draft and publish it afterwards. Does anyone know a better way doing this?
class CityPage(Page, CityVariables):
cityobject = models.ForeignKey(CityTranslated, on_delete=models.SET_NULL, null=True, blank=True)
streamfield = StreamField(BasicStreamBlock, null=True, blank=True)
content_panels = Page.content_panels + [
FieldPanel('cityobject', classname="full"),
StreamFieldPanel('streamfield'),
]
def get_streamfield(self):
for block in self.streamfield:
if type(block.value) == unicode:
block.value = self.replace_veriables(block.value)
elif type(block.value) == RichText:
block.value.source = self.replace_veriables(block.value.source)
else:
print "notimplemented"
return self.streamfield
And this is just the class which replaces $variables$ with values from my database.
class CityVariables():
def replace_veriables(self, repstr):
reprules = self.get_city_context()
for key, value in reprules.iteritems():
repstr = repstr.replace(key, value)
return repstr
def get_city_context(self):
context = {}
if self.cityobject.population:
context['$population$'] = unicode(self.cityobject.population)
if self.cityobject.transregion:
context['$region$'] = unicode(self.cityobject.transregion)
return context
class BasicStreamBlock(blocks.StreamBlock):
h2 = blocks.CharBlock(icon="title", classname="title")
h3 = blocks.CharBlock(icon="title", classname="title")
h4 = blocks.CharBlock(icon="title", classname="title")
h5 = blocks.CharBlock(icon="title", classname="title")
paragraph = blocks.RichTextBlock(icon="pilcrow")
image = ImageChooserBlock(label="Image", icon="image")
aligned_html = blocks.RawHTMLBlock(icon="code", label='Raw HTML')
Upvotes: 0
Views: 1362
Reputation: 5176
Here is a way to simply make the templated (converted) html output from streamfield from within your CityPage model.
Overview:
$variablename
not $variablename$
but works well and can be configured if really needed.str(self.streamfield)
which will force it to render into nice HTML.class Meta: template = ...
see docs.string.Template
class to create our output text by providing a dict of the template names and what to replace them with. Template variable names should not have the $
symbol in them (variablename
not $variablename
), the library takes care of that for you, it also takes care of basic string conversion.model_to_dict
util from Django to make the CityObject
into a dict to pass directly to the template (think of this as your context for Django templates).$region
would not work, it would need to match the fieldname eg. $transregion
- or just change the fieldnames. It makes it easier for reading the code later if all the variables/fieldnames match anyway.city_page.html
template, we will need to mark it as safe for Django to render directly. Important: please be really careful about this as it means someone could save javascript code to the CityObject an it would run in the frontend, you may want another layer after model_to_dict
to clear any potential js code.Example: myapp/models.py
from django.forms.models import model_to_dict
from django.utils.safestring import mark_safe
from string import Template
# other imports... Page, etc
class CityPage(Page):
cityobject = models.ForeignKey(
CityTranslated, on_delete=models.SET_NULL, null=True, blank=True)
streamfield = StreamField(BasicStreamBlock, null=True, blank=True)
content_panels = Page.content_panels + [
FieldPanel('cityobject', classname="full"),
StreamFieldPanel('streamfield'),
]
def get_templated_streamfield(self):
# using str is a quick way to force the content to be rendered
rendered_streamfield = str(self.streamfield)
# will generate a dict eg. {'population': 23000, 'transregion': 'EU'}
# will not convert to values string/unicode - but this is handled by Template
template_variables = model_to_dict(self.cityobject)
template = Template(rendered_streamfield)
# using safe_substitute will **not** throw an error if a variable exists without a value
converted = template.safe_substitute(template_variables)
# as we have html markup we must mark it as safe
return mark_safe(converted)
Example: myapp/template/city_page.html
{% extends "base.html" %}
{% load wagtailimages_tags %}
{% block content %}
{% include "base/include/header.html" %}
<div class="container">
<div class="row">
<div class="col-md-6">
<em>Streamfield Original (without templating)</em>
{{ page.streamfield }}
</div>
<div class="col-md-2">
<em>Streamfield with templating</em>
{{ page.get_templated_streamfield }}
</div>
</div>
</div>
{% endblock content %}
Upvotes: 2