Merge branch 'rafiqhilali-fix-cached-image-sizes'

This commit is contained in:
Johannes Wilm 2022-08-15 11:34:59 +02:00
commit 827244ca6c
4 changed files with 46 additions and 14 deletions

View file

@ -4,6 +4,7 @@ Changelog
* Unreleased
* Allowed for rectangular avatars. Custom avatar tag templates now require the specification of both a ``width`` and ``height`` attribute instead of ``size``.
* Made ``True`` the default value of ``AVATAR_CLEANUP_DELETED``. (Set to ``False`` to obtain previous behavior).
* Fix invalidate_cache for on-the-fly created thumbnails
* 6.0.1 (August 12, 2022)
* Exclude tests folder from distribution.

View file

@ -188,6 +188,7 @@ class Avatar(models.Model):
thumb = self.avatar.storage.save(
self.avatar_name(width, height), thumb_file
)
invalidate_cache(self.user, width, height)
def avatar_url(self, width, height=None):
return self.avatar.storage.url(self.avatar_name(width, height))

View file

@ -28,13 +28,17 @@ def get_user(userdescriptor):
return User.objects.get_by_natural_key(userdescriptor)
def get_cache_key(user_or_username, width, height, prefix):
def get_cache_key(user_or_username, prefix, width=None, height=None):
"""
Returns a cache key consisten of a username and image size.
"""
if isinstance(user_or_username, get_user_model()):
user_or_username = get_username(user_or_username)
key = "%s_%s_%s_%s" % (prefix, user_or_username, width, height or width)
key = f"{prefix}_{user_or_username}"
if width:
key += f"_{width}"
if height or width:
key += f"x{height or width}"
return "%s_%s" % (
slugify(key)[:100],
hashlib.md5(force_bytes(key)).hexdigest(),
@ -62,11 +66,16 @@ def cache_result(default_size=settings.AVATAR_DEFAULT_SIZE):
def cached_func(user, width=None, height=None, **kwargs):
prefix = func.__name__
cached_funcs.add(prefix)
key = get_cache_key(user, width or default_size, height, prefix=prefix)
key = get_cache_key(user, prefix, width or default_size, height)
result = cache.get(key)
if result is None:
result = func(user, width or default_size, height, **kwargs)
cache_set(key, result)
# add image size to set of cached sizes so we can invalidate them later
sizes_key = get_cache_key(user, "cached_sizes")
sizes = cache.get(sizes_key, set())
sizes.add((width or default_size, height or width or default_size))
cache_set(sizes_key, sizes)
return result
return cached_func
@ -78,16 +87,18 @@ def invalidate_cache(user, width=None, height=None):
"""
Function to be called when saving or changing a user's avatars.
"""
sizes = set(settings.AVATAR_AUTO_GENERATE_SIZES)
sizes_key = get_cache_key(user, "cached_sizes")
sizes = cache.get(sizes_key, set())
if width is not None:
sizes.add((width, height or width))
for prefix in cached_funcs:
for size in sizes:
if isinstance(size, int):
cache.delete(get_cache_key(user, size, size, prefix))
cache.delete(get_cache_key(user, prefix, size))
else:
# Size is specified with height and width.
cache.delete(get_cache_key(user, size[0], size[1], prefix))
cache.delete(get_cache_key(user, prefix, size[0], size[1]))
cache.set(sizes_key, set())
def get_default_avatar_url():

View file

@ -5,6 +5,7 @@ from shutil import rmtree
from django.contrib.admin.sites import AdminSite
from django.core import management
from django.core.cache import cache
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse
@ -15,7 +16,12 @@ from avatar.conf import settings
from avatar.models import Avatar
from avatar.signals import avatar_deleted
from avatar.templatetags import avatar_tags
from avatar.utils import get_primary_avatar, get_user_model
from avatar.utils import (
get_cache_key,
get_primary_avatar,
get_user_model,
invalidate_cache,
)
class AssertSignal:
@ -465,10 +471,23 @@ class AvatarTests(TestCase):
self.assertMediaFileExists(avatar_80_url)
self.assertNotEqual(avatar_80_mtime, self.get_media_file_mtime(avatar_80_url))
# def testAvatarOrder
# def testReplaceAvatarWhenMaxIsOne
# def testHashFileName
# def testHashUserName
# def testChangePrimaryAvatar
# def testDeleteThumbnailAndRecreation
# def testAutomaticThumbnailCreation
def test_invalidate_cache(self):
upload_helper(self, "test.png")
sizes_key = get_cache_key(self.user, "cached_sizes")
sizes = cache.get(sizes_key, set())
# Only default 80x80 thumbnail is cached
self.assertEqual(len(sizes), 1)
# Invalidate cache
invalidate_cache(self.user)
sizes = cache.get(sizes_key, set())
# No thumbnail is cached.
self.assertEqual(len(sizes), 0)
# Create a custom 25x25 thumbnail and check that it is cached
avatar_tags.avatar(self.user, 25)
sizes = cache.get(sizes_key, set())
self.assertEqual(len(sizes), 1)
# Invalidate cache again.
invalidate_cache(self.user)
sizes = cache.get(sizes_key, set())
# It should now be empty again
self.assertEqual(len(sizes), 0)