Skip to content

Commit 1441e2a

Browse files
committed
Added different sizes and thumbnail for image.Fix #100
1 parent ac3ed33 commit 1441e2a

File tree

6 files changed

+58
-40
lines changed

6 files changed

+58
-40
lines changed

mosquito_alert/annotations/admin.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
from django import forms
2-
from django.contrib import admin
32
from django.contrib.postgres.forms import SimpleArrayField
4-
from django.utils.html import format_html
53
from django.utils.translation import gettext_lazy as _
4+
from imagekit.admin import AdminThumbnail
65

76
from mosquito_alert.utils.admin import TimeStampedModelAdminMixin
87

@@ -31,17 +30,13 @@ class BasePhotoAnnotationTaskAdminMixin(BaseAnnotationTaskAdminMixin):
3130
_photo_annotation_task_fields = ("photo",)
3231

3332
fields = BaseAnnotationTaskAdminMixin.fields + _photo_annotation_task_fields
34-
readonly_fields = ("preview",) + BaseAnnotationTaskAdminMixin.readonly_fields
33+
readonly_fields = ("image_thumbnail",) + BaseAnnotationTaskAdminMixin.readonly_fields
3534

3635
search_fields = ("photo__image",)
3736
list_display = _photo_annotation_task_fields + TimeStampedModelAdminMixin.list_display
3837
list_filter = TimeStampedModelAdminMixin.list_filter
3938

40-
@admin.display(description=_("Photo preview"))
41-
def preview(self, obj):
42-
if not obj._state.adding:
43-
return format_html(f"<img src='{obj.photo.image.url}' height='150' />")
44-
return "Upload image for preview"
39+
image_thumbnail = AdminThumbnail(image_field=lambda obj: obj.photo.thumbnail)
4540

4641

4742
class BaseAnnotationAdminMixin:

mosquito_alert/identifications/admin.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ class PhotoIdentificationTaskAdmin(BaseTaskWithResultsAdminMixin, BasePhotoAnnot
328328
{
329329
"fields": (
330330
"task",
331-
("photo", "preview"),
331+
("photo", "image_thumbnail"),
332332
("created_at", "updated_at"),
333333
"is_completed",
334334
)
@@ -380,12 +380,6 @@ class PhotoIdentificationTaskAdmin(BaseTaskWithResultsAdminMixin, BasePhotoAnnot
380380
ExternalIdentificationAdminInline,
381381
]
382382

383-
@admin.display(description=_("Photo preview"))
384-
def preview(self, obj):
385-
if not obj._state.adding:
386-
return format_html(f"<img src='{obj.photo.image.url}' height='150' />")
387-
return "Upload image for preview"
388-
389383
class Media(NumericFilterModelAdmin.Media):
390384
pass
391385

mosquito_alert/images/admin.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.contrib import admin
44
from django.utils.html import format_html
55
from django.utils.safestring import mark_safe
6+
from imagekit.admin import AdminThumbnail
67
from nested_admin.nested import NestedModelAdmin
78
from pygments import highlight
89
from pygments.formatters import HtmlFormatter
@@ -19,32 +20,26 @@ class m2mPhotoAdminInlineMixin:
1920
can_delete = False
2021
fields = [
2122
"photo",
22-
"preview",
23+
"image_thumbnail",
2324
]
2425
readonly_fields = [
25-
"preview",
26+
"image_thumbnail",
2627
]
2728

28-
def preview(self, obj):
29-
if not obj._state.adding:
30-
return format_html(f"<img src='{obj.photo.image.url}' height='150' />")
31-
return "Upload image for preview"
29+
image_thumbnail = AdminThumbnail(image_field=lambda obj: obj.photo.thumbnail)
3230

3331

3432
@admin.register(Photo)
3533
class PhotoAdmin(NestedModelAdmin):
36-
list_display = ("__str__", "user", "created_at", "preview")
34+
list_display = ("__str__", "user", "created_at", "image_thumbnail")
3735
list_filter = ("user", "created_at")
38-
fields = ("created_at", ("image", "preview"))
39-
readonly_fields = ("preview", "created_at")
36+
fields = ("created_at", ("image", "image_thumbnail"))
37+
readonly_fields = ("image_thumbnail", "created_at")
4038
list_per_page = 10
4139

42-
inlines = [FlaggedContentNestedInlineAdmin, PhotoIdentificationTaskAdminInline]
40+
image_thumbnail = AdminThumbnail(image_field="thumbnail")
4341

44-
def preview(self, obj):
45-
if not obj._state.adding:
46-
return format_html(f"<img src='{obj.image.url}' height='150' />")
47-
return "Upload image for preview"
42+
inlines = [FlaggedContentNestedInlineAdmin, PhotoIdentificationTaskAdminInline]
4843

4944
@admin.display(description="EXIF")
5045
def exif_dict(self, obj):

mosquito_alert/images/models.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
from django.db import models
66
from django.utils.functional import cached_property
77
from django.utils.translation import gettext_lazy as _
8-
from imagekit.processors import ResizeToFit
8+
from imagekit.models import ImageSpecField
9+
from imagekit.processors import ResizeToFit, Thumbnail
910
from PIL import ExifTags, Image
1011

1112
from mosquito_alert.moderation.models import FlagModeratedModel
1213
from mosquito_alert.utils.models import TimeStampedModel
1314

14-
from .fields import ProcessedImageField
15+
from .fields import ProcessedImageField as ProcessedImageWithExifField
1516

1617

1718
def image_upload_to(instance, filename):
@@ -39,12 +40,24 @@ class Photo(FlagModeratedModel, TimeStampedModel):
3940
# NOTE: If a processor to autorotate is used, check that
4041
# EXIF orientation is modified accordingly.
4142
# Check PIL.ImageOps.exif_transpose()
42-
image = ProcessedImageField(
43+
image = ProcessedImageWithExifField(
4344
upload_to=image_upload_to,
4445
processors=[ResizeToFit(height=1080, upscale=False)],
4546
format="JPEG",
4647
options={"quality": 95},
4748
)
49+
image_large = ImageSpecField(
50+
source="image", processors=[ResizeToFit(height=720, upscale=False)], format="WEBP", options={"quality": 80}
51+
)
52+
image_medium = ImageSpecField(
53+
source="image", processors=[ResizeToFit(height=360, upscale=False)], format="WEBP", options={"quality": 70}
54+
)
55+
thumbnail = ImageSpecField(
56+
source="image",
57+
processors=[Thumbnail(height=150, width=150, crop=True)],
58+
format="WEBP",
59+
options={"quality": 40},
60+
)
4861
# TODO: license + attributions
4962

5063
# Attributes - Optional

mosquito_alert/images/tests/test_admin.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.core.files import File
55
from django.core.files.uploadedfile import SimpleUploadedFile
66
from django.urls import reverse
7+
from imagekit.admin import AdminThumbnail
78

89
from mosquito_alert.users.tests.factories import UserFactory
910

@@ -81,15 +82,11 @@ def test_update_does_not_modify_user(self, client):
8182
def test_created_at_field_is_readonly(self):
8283
assert "created_at" in PhotoAdmin.readonly_fields
8384

84-
def test_preview_is_readonly(self):
85-
assert "preview" in PhotoAdmin.readonly_fields
85+
def test_image_thumbnail_is_readonly(self):
86+
assert "image_thumbnail" in PhotoAdmin.readonly_fields
8687

87-
def test_preview_html(self):
88-
p = PhotoFactory()
89-
90-
photo_admin = PhotoAdmin(model=Photo, admin_site=AdminSite())
91-
92-
assert photo_admin.preview(p) == f"<img src='{p.image.url}' height='150' />"
88+
def test_image_thumbnail_is_AdminThumbnail(self):
89+
assert isinstance(PhotoAdmin.image_thumbnail, AdminThumbnail)
9390

9491
def test_view(self, admin_client):
9592
p = PhotoFactory()

mosquito_alert/images/tests/test_models.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ def test_user_on_delete_set_null(self):
3232
def test_user_related_name(self):
3333
assert self.model._meta.get_field("user").remote_field.related_name == "photos"
3434

35+
def test_image_can_not_be_null(self):
36+
assert not self.model._meta.get_field("image").null
37+
3538
def test_image_is_uploaded_in_images_path(self):
3639
p = self.factory_cls()
3740
assert os.path.dirname(p.image.name) == "images"
@@ -69,6 +72,27 @@ def test_image_is_not_resized_if_smaller_than_1080p(self):
6972
assert p.image.width == 1080
7073
assert p.image.height == 600
7174

75+
def test_image_large(self):
76+
p = self.factory_cls(image__width=2160, image__height=1080)
77+
78+
assert p.image_large is not None
79+
assert p.image_large.width == 1440
80+
assert p.image_large.height == 720
81+
82+
def test_image_medium(self):
83+
p = self.factory_cls(image__width=2160, image__height=1080)
84+
85+
assert p.image_medium is not None
86+
assert p.image_medium.width == 720
87+
assert p.image_medium.height == 360
88+
89+
def test_thumbnail(self):
90+
p = self.factory_cls(image__width=2160, image__height=1080)
91+
92+
assert p.thumbnail is not None
93+
assert p.thumbnail.width == 150
94+
assert p.thumbnail.height == 150
95+
7296
def test_exif_is_preserved_from_original_image(self):
7397
p = self.factory_cls(image__from_path=testdata.TESTEXIFIMAGE_PATH)
7498

0 commit comments

Comments
 (0)