matthewwithanm / django-imagekit

Automated image processing for Django. Currently v4.0
http://django-imagekit.rtfd.org/
BSD 3-Clause "New" or "Revised" License
2.26k stars 276 forks source link

Can't specify anchor in Thumbnail template tag #494

Closed tim-schilling closed 1 year ago

tim-schilling commented 4 years ago

I've only tested this with Django 2.2 and python 3.7.5.

When attempting to use the thumbnail templatetag, specifying anchor='c' causes the template to render and cause an infinite recursion error. I've traced this down to the pickling logic to come up with the hash for the cache filename. The pickler includes the anchor value which is a SafeString instance which apparently has fits when pickled. It has to do with SafeString being a subclass of str, but I don't know enough to know why.

One fix would be to check if the kwarg to the templatetag includes the anchor, if it does, convert it to a regular string. However, that looks to be a really ugly hack.

The core problem of SafeString not being able to be pickled properly can be verified with:

from copy import copy
from hashlib import md5
from pickle import Pickler
from io import BytesIO

def pickle(obj):
    file = BytesIO()
    Pickler(file, 0).dump(obj)
    return md5(file.getvalue()).hexdigest()

# Copied from Django's source
class SafeData:
    def __html__(self):
        """
        Return the html representation of a string for interoperability.

        This allows other template engines to understand Django's SafeData.
        """
        return self

class SafeText(str, SafeData):
    """
    A str subclass that has been specifically marked as "safe" for HTML output
    purposes.
    """
    def __add__(self, rhs):
        """
        Concatenating a safe string with another safe bytestring or
        safe string is safe. Otherwise, the result is no longer safe.
        """
        t = super().__add__(rhs)
        if isinstance(rhs, SafeData):
            return SafeText(t)
        return t

    def __str__(self):
        return self
SafeString = SafeText

x = 'hello'
safe = SafeString(x)

pickle(x)
pickle(safe)

Changing the protocol on the pickler to 3 (the default for python3) does seem to resolve the issue.