Skip to content

Commit 353057c

Browse files
committed
Initial commit.
0 parents  commit 353057c

21 files changed

+783
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

README.rst

Whitespace-only changes.

django_filer_image/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = '0.0.1'

django_filer_image/admin.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.contrib import admin
2+
3+
# Register your models here.

django_filer_image/apps.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class DjangoFilerImageConfig(AppConfig):
5+
name = 'django_filer_image'

django_filer_image/convertor.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from __future__ import annotations
2+
from typing import (
3+
Type,
4+
TYPE_CHECKING,
5+
)
6+
from os.path import splitext
7+
from io import BytesIO
8+
from math import ceil
9+
from PIL import Image
10+
from funcy import first
11+
12+
from .settings import (
13+
IMAGE_QUALITY,
14+
)
15+
16+
17+
def get_image(file):
18+
image = Image.open(file)
19+
image = image.convert('RGB') if not image.mode == 'RGB' else image
20+
21+
return image
22+
23+
24+
def get_preview_image(image, preview_size, quailty=100):
25+
size = get_preview_size(image, preview_size)
26+
resized_image = image.resize(size)
27+
28+
preview_progressive_jpeg = get_jpeg(resized_image, progressive=False, quality=quailty)
29+
30+
return preview_progressive_jpeg
31+
32+
33+
def get_preview_size(image, preview_size):
34+
preview_width, preview_height = preview_size
35+
36+
if not preview_width and not preview_height:
37+
raise ValueError('Resize size is required.')
38+
39+
original_width, original_height = image.size
40+
41+
if preview_width and preview_height:
42+
resize_width = preview_width
43+
resize_height = preview_height
44+
elif preview_width:
45+
resize_width = preview_width
46+
resize_rate = preview_width / original_width
47+
resize_height = ceil(original_height * resize_rate)
48+
else:
49+
resize_height = preview_height
50+
resize_rate = preview_height / original_height
51+
resize_width = ceil(original_width * resize_rate)
52+
53+
return resize_width, resize_height
54+
55+
56+
def get_jpeg(image, progressive=True, quality=100):
57+
return convert_image(
58+
image,
59+
'JPEG',
60+
quality=quality,
61+
optimize=True,
62+
progressive=progressive,
63+
)
64+
65+
66+
def get_webp(image, quality=1000):
67+
return convert_image(
68+
image,
69+
'WEBP',
70+
quality=quality,
71+
optimize=True,
72+
)
73+
74+
75+
def convert_image(image, *args, **kwargs):
76+
blob_image = BytesIO()
77+
image.save(
78+
blob_image,
79+
*args,
80+
**kwargs
81+
)
82+
83+
return blob_image
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<picture>
2+
{% for min_width, width in breakpoints %}
3+
{% if image.webp %}
4+
<source
5+
{% if min_width %}media="(min-width: {{ min_width }}px)"{% endif %}
6+
{{ srcset_attribute}}="{{ get_srcset(image.webp.url, rates, width=width) }}"
7+
type="image/webp"
8+
/>
9+
{% endif %}
10+
{% if image.progressive_jpeg %}
11+
<source
12+
{% if min_width %}media="(min-width: {{ min_width }}px)"{% endif %}
13+
data-srcset="{{ get_srcset(image.progressive_jpeg.url, rates, width=width) }}"
14+
type="image/jpeg"
15+
/>
16+
{% else %}
17+
<source
18+
{% if min_width %}media="(min-width: {{ min_width }}px)"{% endif %}
19+
data-srcset="{{ get_srcset(image.file.url, rates, width=width) }}"
20+
/>
21+
{% endif %}
22+
{% endfor %}
23+
24+
{% set first_breakpoint = first(breakpoints) %}
25+
<img {% if alt %}{{ alt }}{% endif %} class="{{ constant_class }} {{ class }}" src="{{ image.preview.url }}" {% if first_breakpoint %}{{ srcset_attribute }}="{{ get_srcset(image.file.url, rates, width=first_breakpoint[1]) }}"{% endif %}>
26+
</picture>

django_filer_image/migrations/__init__.py

Whitespace-only changes.

django_filer_image/models.py

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from os.path import splitext
2+
from django.template.loader import get_template
3+
from django.db.models.signals import pre_save
4+
from django.dispatch import receiver
5+
from django.utils.html import mark_safe
6+
from filer.fields.multistorage_file import MultiStorageFileField
7+
from filer.models.abstract import BaseImage
8+
from funcy import first
9+
10+
from .convertor import (
11+
get_image,
12+
get_jpeg,
13+
get_webp,
14+
get_preview_image,
15+
)
16+
from .settings import (
17+
BREAKPOINTS,
18+
RATES,
19+
IMAGE_QUALITY,
20+
IMAGE_CONSTANT_CSS_CLASS,
21+
IMAGE_CSS_CLASS,
22+
IMAGE_TEMPLATE,
23+
IMAGE_SRCSET_ATTRIBUTE,
24+
USES_WEBP,
25+
USES_PROGRESSIVE_JPEG,
26+
USES_PREVIEW,
27+
PREVIEW_SIZE,
28+
)
29+
30+
31+
class Image(BaseImage):
32+
webp = MultiStorageFileField(
33+
verbose_name='WebP image',
34+
null=True,
35+
blank=True,
36+
max_length=255,
37+
)
38+
progressive_jpeg = MultiStorageFileField(
39+
verbose_name='Progressive jpeg image',
40+
null=True,
41+
blank=True,
42+
max_length=255,
43+
)
44+
preview = MultiStorageFileField(
45+
verbose_name='Progressive jpeg preview image',
46+
null=True,
47+
blank=True,
48+
max_length=255,
49+
)
50+
51+
def render_image(
52+
self,
53+
breakpoints=None,
54+
rates=None,
55+
class_=IMAGE_CSS_CLASS,
56+
alt='',
57+
template=IMAGE_TEMPLATE,
58+
srcset_attribute=IMAGE_SRCSET_ATTRIBUTE,
59+
**kwargs,
60+
):
61+
template = get_template(template)
62+
context = {
63+
'image': self,
64+
'breakpoints': breakpoints or BREAKPOINTS,
65+
'rates': rates or RATES,
66+
'constant_class': IMAGE_CONSTANT_CSS_CLASS,
67+
'class': class_,
68+
'alt': alt,
69+
'srcset_attribute': srcset_attribute,
70+
'first': first,
71+
**kwargs,
72+
}
73+
74+
html = template.render(context)
75+
76+
return mark_safe(html)
77+
78+
class Meta(BaseImage.Meta):
79+
app_label = 'images'
80+
81+
82+
@receiver(pre_save, sender=Image)
83+
def generate_images(sender, instance: Image, **kwargs):
84+
image = get_image(instance.file)
85+
filename = first(splitext(instance.file.name))
86+
87+
if USES_PREVIEW:
88+
preview_image = get_preview_image(image, PREVIEW_SIZE)
89+
instance.preview.save(
90+
f'{filename}-preview.jpeg',
91+
preview_image,
92+
save=False,
93+
)
94+
95+
if USES_PROGRESSIVE_JPEG:
96+
progressive_jpeg_image = get_jpeg(
97+
image,
98+
progressive=True,
99+
quality=IMAGE_QUALITY,
100+
)
101+
instance.progressive_jpeg.save(
102+
f'{filename}.jpeg',
103+
progressive_jpeg_image,
104+
save=False,
105+
)
106+
107+
if USES_WEBP:
108+
webp_image = get_webp(
109+
image,
110+
quality=IMAGE_QUALITY,
111+
)
112+
instance.webp.save(
113+
f'{filename}.webp',
114+
webp_image,
115+
save=False,
116+
)

django_filer_image/settings.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from django.conf import settings
2+
3+
DEFAULT_USES_WEBP = False
4+
DEFAULT_USES_PROGRESSIVE_JPEG = False
5+
DEFAULT_USES_PREVIEW = False
6+
7+
DEFAULT_PREVIEW_SIZE = (10, None)
8+
DEFAULT_IMAGE_QUALITY = 80
9+
10+
DEFAULT_IMAGE_TEMPLATE = 'images/image.html.j2'
11+
DEFAULT_IMAGE_CSS_CLASS = ''
12+
DEFAULT_IMAGE_CONSTANT_CSS_CLASS = ''
13+
DEFAULT_IMAGE_SRCSET_ATTRIBUTE = 'srcset'
14+
DEFAULT_BREAKPOINTS = [
15+
(1280, 1280),
16+
(768, 1080),
17+
(350, 767),
18+
(None, 350),
19+
]
20+
DEFAULT_RATES = [1, 1.5, 2, 3]
21+
22+
BREAKPOINTS = getattr(settings, 'FILER_IMAGE_BREAKPOINTS', DEFAULT_BREAKPOINTS)
23+
RATES = getattr(settings, 'FILER_IMAGE_RATES', DEFAULT_RATES)
24+
PREVIEW_SIZE = getattr(settings, 'FILER_IMAGE_PREVIEW_SIZE', DEFAULT_PREVIEW_SIZE)
25+
IMAGE_QUALITY = getattr(settings, 'FILER_IMAGE_QUALITY', DEFAULT_IMAGE_QUALITY)
26+
USES_WEBP = getattr(settings, 'FILER_IMAGE_USES_WEBP', DEFAULT_USES_WEBP)
27+
USES_PROGRESSIVE_JPEG = getattr(settings, 'FILER_IMAGE_USES_PROGRESSIVE_JPEG', DEFAULT_USES_PROGRESSIVE_JPEG)
28+
USES_PREVIEW = getattr(settings, 'FILER_IMAGE_USES_PREVIEW', DEFAULT_USES_PREVIEW)
29+
30+
IMAGE_TEMPLATE = getattr(settings, 'FILER_IMAGE_DEFAULT_TEMPLATE', DEFAULT_IMAGE_TEMPLATE)
31+
IMAGE_CSS_CLASS = getattr(settings, 'FILER_IMAGE_DEFAULT_CSS_CLASS', DEFAULT_IMAGE_CSS_CLASS)
32+
IMAGE_CONSTANT_CSS_CLASS = getattr(settings, 'FILER_IMAGE_CONSTANT_CSS_CLASS', DEFAULT_IMAGE_CONSTANT_CSS_CLASS)
33+
IMAGE_SRCSET_ATTRIBUTE = getattr(settings, 'FILER_IMAGE_DEFAULT_SRCSET_ATTRIBUTE', DEFAULT_IMAGE_SRCSET_ATTRIBUTE)

django_filer_image/tests.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.test import TestCase
2+
3+
# Create your tests here.

django_filer_image/views.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from __future__ import annotations
2+
from filer.fields.multistorage_file import MultiStorageFileField
3+
from filer.models.abstract import BaseImage
4+
from django.template.loader import get_template
5+
from django.utils.html import mark_safe
6+
from typing import TYPE_CHECKING
7+
8+
from .settings import (
9+
BREAKPOINTS,
10+
RATES,
11+
)
12+
13+
if TYPE_CHECKING:
14+
from django.template import Template
15+
16+
17+
class ProgressiveImage(BaseImage):
18+
webp = MultiStorageFileField(
19+
verbose_name='WebP изображение',
20+
null=True,
21+
blank=True,
22+
max_length=255,
23+
)
24+
progressive_jpeg = MultiStorageFileField(
25+
verbose_name='Progressive jpeg',
26+
null=True,
27+
blank=True,
28+
max_length=255,
29+
)
30+
preview = MultiStorageFileField(
31+
verbose_name='Progressive jpeg превью изображение',
32+
null=True,
33+
blank=True,
34+
max_length=255,
35+
)
36+
37+
def render_image(self, breakpoints=None, rates=None, class_='', alt=''):
38+
template: Template = get_template('images/image.html.j2')
39+
context = {
40+
'image': self,
41+
'breakpoints': breakpoints or BREAKPOINTS,
42+
'rates': rates or RATES,
43+
'class': class_,
44+
'alt': alt,
45+
}
46+
47+
html = template.render(context)
48+
49+
return mark_safe(html)
50+
51+
class Meta(BaseImage.Meta):
52+
app_label = 'images'

example/example/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)