Skip to content
Snippets Groups Projects
Commit 01909c14 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch '2-optimise-query-count' into 'master'

Resolve "Optimise query count"

Closes #2

See merge request !1
parents cdcfc7bb 1134f719
No related branches found
No related tags found
1 merge request!1Resolve "Optimise query count"
# Generated by Django 3.2 on 2021-05-15 20:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('favicon', '0003_site_manager'),
]
operations = [
migrations.AddConstraint(
model_name='faviconimg',
constraint=models.UniqueConstraint(fields=('faviconFK', 'size', 'rel'), name='favicon_size_rel_unique'),
),
]
from io import BytesIO
import hashlib
import json
import sys
from PIL import Image
from django.conf import settings
from django.core.cache import cache
from django.core.files.storage import default_storage as storage
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db import models
from django.db.models import signals
from django.db.models import Q, signals
use_sites = hasattr(settings, "SITE_ID")
......@@ -23,6 +26,10 @@ config = {
}
config = getattr(settings, 'FAVICON_CONFIG', config)
if "shortcut icon" not in config or 32 not in config["shortcut icon"]:
config.setdefault("shortcut_icon", []).append(32)
config_cache_key = "favicons_" + hashlib.sha1(json.dumps(config, sort_keys=True).encode()).hexdigest()
image_path = getattr(settings, "FAVICON_PATH", "favicon")
......@@ -53,12 +60,45 @@ class Favicon(models.Model):
verbose_name = 'Favicon'
verbose_name_plural = 'Favicons'
def get_favicons(self):
favicons = []
def get_favicons(self, update=False, config_override=None):
"""
Get all combinations of favicons as configured, creating mising ones.
Pass update=True to force re-generation of existing icons.
"""
# Use default config by default
if config_override is None:
config_override = config
# Get all combinations of favicon rels and sizes from config
query = Q()
for rel in config_override:
for size in config_override[rel]:
query |= Q(rel=rel, size=size)
# Get all existing favicons
favicons = FaviconImg.objects.filter(faviconFK=self).filter(query)
# Delete all favicon images to update all
if update:
favicons.delete()
favicons = FaviconImg.objects.none()
found = []
else:
favicons = favicons.all()
found = [(f.rel, f.size) for f in favicons]
# Check whether favicons are missing
new_favicons = []
for rel in config:
for size in config[rel]:
favicons.append(self.get_favicon(size, rel))
return favicons
if not (rel, size) in found:
fav = FaviconImg(faviconFK=self, size=size, rel=rel)
fav.generate_image()
new_favicons.append(fav)
if new_favicons:
FaviconImg.objects.bulk_create(new_favicons, ignore_conflicts=True)
return list(favicons) + new_favicons
def __str__(self):
return self.faviconImage.name
......@@ -69,45 +109,24 @@ class Favicon(models.Model):
def del_image(self):
self.faviconImage.delete()
def get_favicon(self, size, rel, update=False):
"""
get or create a favicon for size, rel(attr) and uploaded favicon
optional:
update=True
"""
fav, _ = FaviconImg.objects.get_or_create(
faviconFK=self, size=size, rel=rel)
if update and fav.faviconImage:
fav.del_image()
if self.faviconImage and not fav.faviconImage:
tmp = Image.open(storage.open(self.faviconImage.name))
tmp.thumbnail((size, size), Image.ANTIALIAS)
tmp_io = BytesIO()
tmp.save(tmp_io, format='PNG')
tmp_file = InMemoryUploadedFile(tmp_io, None, f"fav-{size}s.png", 'image/png', sys.getsizeof(tmp_io), None)
fav.faviconImage = tmp_file
fav.save()
return fav
def as_html(self, update=False):
"""Return <link> html tags for this favicon set."""
html = ''
for favicon in self.get_favicons(update=update):
html += favicon.as_html()
return html
def save(self, *args, **kwargs):
update = False
if self.isFavicon:
for n in Favicon.on_site.exclude(pk=self.pk):
n.isFavicon = False
n.save()
Favicon.on_site.exclude(pk=self.pk).update(isFavicon=False)
super(Favicon, self).save(*args, **kwargs)
if self.faviconImage:
for rel in config:
for size in config[rel]:
self.get_favicon(size=size, rel=rel, update=update)
cache.delete(config_cache_key)
# make sure default favicon is set
self.get_favicon(size=32, rel='shortcut icon')
if self.faviconImage:
html = self.as_html(update=True)
cache.set(config_cache_key, html, timeout=None)
class FaviconImg(models.Model):
......@@ -116,9 +135,26 @@ class FaviconImg(models.Model):
rel = models.CharField(max_length=250, null=True)
faviconImage = models.ImageField(upload_to=image_path)
def as_html(self):
"""Return a <link> tag forthis favicon image."""
return f'<link rel="{self.rel}" sizes="{self.size}x{self.size}" href="{self.faviconImage.url}"/>'
def generate_image(self):
tmp = Image.open(storage.open(self.faviconFK.faviconImage.name))
tmp.thumbnail((self.size, self.size), Image.ANTIALIAS)
tmp_io = BytesIO()
tmp.save(tmp_io, format='PNG')
tmp_file = InMemoryUploadedFile(tmp_io, None, f"fav-{self.size}s.png", 'image/png', sys.getsizeof(tmp_io), None)
self.faviconImage = tmp_file
def del_image(self):
self.faviconImage.delete()
class Meta:
constraints = [models.UniqueConstraint(fields=["faviconFK", "size", "rel"], name="favicon_size_rel_unique")]
signals.pre_delete.connect(pre_delete_image, sender=Favicon)
signals.pre_delete.connect(pre_delete_image, sender=FaviconImg)
from django import template
from django.core.cache import cache
from django.utils.safestring import mark_safe
from favicon.models import Favicon, config
from favicon.models import Favicon, config_cache_key
register = template.Library()
......@@ -16,17 +17,15 @@ def place_favicon(context):
{% place_favicon %}
"""
html = cache.get(config_cache_key)
if html:
return mark_safe(html)
fav = Favicon.on_site.filter(isFavicon=True).first()
if not fav:
return mark_safe('<!-- no favicon -->')
html = ''
for rel in config:
for size in sorted(config[rel], reverse=True):
n = fav.get_favicon(size=size, rel=rel)
html += f'<link rel="{n.rel}" sizes="{n.size}x{n.size}" href="{n.faviconImage.url}"/>'
default_fav = fav.get_favicon(size=32, rel='shortcut icon')
html += f'<link rel="{default_fav.rel}" sizes="{default_fav.size}x{default_fav.size}"\
href="{default_fav.faviconImage.url}"/>'
html = fav.as_html()
cache.set(config_cache_key, html, timeout=None)
return mark_safe(html)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment