'Using Django's cache to store a model property

I have an image model with a foreign key back to the parent model.

class MyImage(models.Model):
    parent = models.ForeignKey(ParentModel, related_name='images', on_delete=models.CASCADE)
    image = models.ImageField(upload_to='images')

I am also using django-s3-storage to have these images served from a private S3 bucket.

When I ask for the image url in a template using (this is a class based detail view, I also get the images in a class based list view):

{% for image in object.images.all %}
    <img src="{{image.image.url}}" alt="">
{% endfor %}

the value returned from url is a signed url and that's fine. I'd prefer not to make my bucket public. The problem is that this signed url is generated every time I get the url from the image model, which prevents the image from being cached in the browser as the signature changes on every request. This isn't ideal since these images will rarely change and I'd like them browser cached to reduce S3 costs.

I am currently also using Django's built-in caching (in my case, I'm using database caching for the time being). The built-in caching will let me cache template fragments like so:

{% for image in object.images.all %}
    {% cache 1440 object_image object %}
        <img src="{{image.image.url}}" alt="">
    {% endcache %}
{% endfor %}

This seems to work okay as the fragment gets cached with the signed url; however, I am using the images in several different templates and I also have some additional logic I use for getting images based on other image model fields (default, thumbnail, etc). This means that one image will store several different cached values each with a different signed url. That's not the end of the world, but I'd like the image's signed url to be the same for all templates.

Reading the documentation for caching, there isn't a built-in decorator to use on a model property that would function the same as the template fragment tag.

What I'd like to do is add a model property to my image model that is decorated so that the image's url gets cached so that I can just call it from any template and it will return the same signed url from the cache, such as:

class MyImage(models.Model):
    parent = models.ForeignKey(ParentModel, related_name='images', on_delete=models.CASCADE)
    image = models.ImageField(upload_to='images')

    @property
    @cache('object_image', object)
    def url(self):
        return self.image.url

Since it doesn't seem like there's an existing decorator, is this a case where I should just implement my own decorator and/or access the cache API directly inside my url property? Or am I missing an existing interface to use where I can access built-in features and use those instead?

from django.core.cache import cache

class MyImage(models.Model):
    parent = models.ForeignKey(ParentModel, related_name='images', on_delete=models.CASCADE)
    image = models.ImageField(upload_to='images')

    @property
    def url(self):
        return cache.get_or_set(self, self.image.url, 1440)


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source