Reputation: 360
I have a stacked inline display. The model for the Inline class has a child in a ManyToMany relationship. I want to display the images but I cannot see how to stop django from escaping the html. It seems like I need a function similar to "display_as", but how do I get django to gather up all the available images and display them in "checkboxSelectMultiple".
FYI: I want to add some sorting to the images too after I get them to display.
Models.py
class BlogWidgetCarousel(models.Model):
entry = models.TextField()
blog = models.ForeignKey(Blog, blank=True, null=True)
position = models.PositiveSmallIntegerField("Position")
images = models.ManyToManyField("Image")
class Meta:
ordering = ('position', )
def __str__(self):
return str(self.position)
def save(self, *args, **kwargs):
self.entry = "<b><i>TODO: create image slider</i></b>"
super(BlogWidgetCarousel, self).save(*args, **kwargs)
def display(self):
return self.entry
class Image(models.Model):
title = models.CharField(max_length=60, blank=False, null=False)
image = models.ImageField(upload_to="images/")
def thumb(self):
return '<a href="{0}"><img src="{0}"></a>'.\
format(MEDIA_URL + str(self.image))
def __str__(self):
#return self.title
#return '<img src="{0}">'.format(MEDIA_URL + str(self.image))
return mark_safe("<b>BOLD</b>") #Added just to test escaping... bold tags still appear on page.
__str__.allow_tags = True #does not appear to work
admin.py
class BlogWidgetCarouselInline(admin.StackedInline):
formfield_overrides = {
models.ManyToManyField: {'widget': CheckboxSelectMultiple},
}
model = BlogWidgetCarousel
extra = 0
#django knows images is ManyToMany
fieldsets = (
("Create Carousel:", {
'fields': (("position"), 'images',)
}),
("Result:", {
'fields': ('thumb', 'display_as',)
}),
)
readonly_fields = ('display_as', 'thumb',)
def display_as(self, instance):
return instance.display()
display_as.allow_tags = True
def thumb(self, instance):
x = ""
for i in instance.images.all():
x += i.thumb()
return x
thumb.allow_tags = True
Update: What I found is that widget I am using has a render function with the following line:
return format_html( '<label{}>{} {}</label>',
label_for, self.tag(attrs), self.choice_label)
This means that the value the template uses is already escaped. Altering as so fixes the problem:
return format_html(
'<label{}>{} {}</label>', label_for, self.tag(attrs), mark_safe(self.choice_label)
)
Now I am not sure if I am implementing something in the "incorrect" way or if it is normal to need to write a custom widget and overwrite the render function.
Upvotes: 0
Views: 853
Reputation: 360
I found that the way to solve this issue for me was to use a raw_id_field.
Upvotes: 0
Reputation: 47856
You can use format_html()
for it. The django.utils.html
module provides some low level utilities for escaping HTML.
This function is to be preferred over string interpolation using %
or str.format
directly, because it applies escaping to all arguments - just like the Template system applies escaping by default.
You could have used mark_safe()
to escape HTML like below:
mark_safe(u"%s <b>%s</b> %s" % (some_html,
escape(some_text),
escape(some_other_text),
))
But by using the below code,
format_html(u"{0} <b>{1}</b> {2}", mark_safe(some_html), some_text, some_other_text)
you don’t need to apply escape() to each argument and risk a bug and an XSS vulnerability if you forget one.
You can use the autoescape
built-in template tag in your template.
This tag takes either on
or off
as an argument and that determines whether auto-escaping is in effect inside the block. The block is closed with an endautoescape
ending tag.
When auto-escaping is in effect, all variable content has HTML escaping applied to it before placing the result into the output (but after any filters have been applied). This is equivalent to manually applying the escape filter to each variable.
{% autoescape on %}
{{ image_object }}
{% endautoescape %}
This should solve your problem.
Upvotes: 1