Dennis_M
Dennis_M

Reputation: 360

In the django admin console how do you stop fieldset fields from escaping html?

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

enter image description here

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

Answers (2)

Dennis_M
Dennis_M

Reputation: 360

I found that the way to solve this issue for me was to use a raw_id_field.

Upvotes: 0

Rahul Gupta
Rahul Gupta

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

Related Questions