pythondjangobinarybase64django-admin

DjangoAdmin render BinaryField as image


I wrote middleware to catch requests and view them on admin page. It successfully views raw text request body, but fails on image render.
admin.py

class RequestLogEntryAdmin(admin.ModelAdmin):
    fieldsets = (
        ('Request', {
            'fields': ('request_headers', 'request_size', 'request_body_'),
        })
    )
    
    def request_body_(self, obj):
        if b'Content-Type: image/png' in base64.b64decode(obj.request_body):
            return obj.image_handler(bytes(obj.request_body))
        return bytes(obj.request_body)

models.py

class RequestLogEntry(models.Model):
    # ...
    request_body = models.BinaryField(null=True)
    # ...
    
    def image_handler(self, binaryfield):
        return mark_safe(f'<img src="data:image/png;base64,{binaryfield}" width="150" height="150" />')

Before saving request.body is being base64 encoded
middleware.py

    # ...
    log_entry.request_body = base64.b64encode(request.body)
    # ...

Current code produces the following:Broken image

Base64 decoded request.body looks like this:Raw request body My guess is that some extra data being mixed with the image bytes (like 'Content-Disposition' and 'Content-Type' headers), but I'm not sure how can I cut it out from the request body.


Solution

  • When you encode the HttpRequest.body like so:

    base64.b64encode(request.body)

    You are actually encoding the raw HTTP request body (which is a bytestring) as a Base64 [wikipedia.org] encoded string.

    HTTP is a text based protocol. The body and the headers are represented as a sequence of characters. Django reserves this feature of HTTP by keeping the raw body in HttpRequest.body for anyone who wants to work at this low level.

    But since the request contains files, you should handle it this way:

    if request.FILES:
        file = request.FILES.get("image")
        if file:
            log_entry.request_body = file.read() #[1]

    Now image_handler() will have to change a little because binaryfield is raw bits of binary data and not Base64 string.

    def image_handler(self, binaryfield):
        return mark_safe(f"<img src='data:image/png;base64,{base64.b64encode(binaryfield)}' width='150' height='150' />")

    [1] Upon calling read() on an UploadedFile, you are essentially reading the entire data from the UploadedFile into memory. Be careful with this method: if the uploaded file is huge it can overwhelm your system.