Really? I forgot to push that?
This commit is contained in:
4
.idea/misc.xml
generated
4
.idea/misc.xml
generated
@@ -3,7 +3,7 @@
|
||||
<component name="Black">
|
||||
<option name="enabledOnReformat" value="true" />
|
||||
<option name="enabledOnSave" value="true" />
|
||||
<option name="sdkName" value="Python 3.12 (kakigoori)" />
|
||||
<option name="sdkName" value="Python 3.13 (kakigoori)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (kakigoori)" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (kakigoori)" project-jdk-type="Python SDK" />
|
||||
</project>
|
@@ -0,0 +1,76 @@
|
||||
# Generated by Django 5.1.1 on 2024-11-10 15:11
|
||||
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def fill_mymodel_uuid(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
ImageVariant = apps.get_model("images", "imagevariant")
|
||||
for obj in ImageVariant.objects.using(db_alias).all():
|
||||
obj.uuid = uuid.uuid4()
|
||||
obj.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("images", "0006_imagevarianttask"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="image",
|
||||
name="version",
|
||||
field=models.IntegerField(default=2),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="imagevariant",
|
||||
name="available",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="imagevariant",
|
||||
name="brightness",
|
||||
field=models.FloatField(default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="imagevariant",
|
||||
name="gaussian_blur",
|
||||
field=models.FloatField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="imagevarianttask",
|
||||
name="brightness",
|
||||
field=models.FloatField(default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="imagevarianttask",
|
||||
name="gaussian_blur",
|
||||
field=models.FloatField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="imagevariant",
|
||||
name="uuid",
|
||||
field=models.UUIDField(null=True),
|
||||
),
|
||||
migrations.RunPython(fill_mymodel_uuid, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name="imagevariant",
|
||||
name="uuid",
|
||||
field=models.UUIDField(
|
||||
default=uuid.uuid4, serialize=False, editable=False, unique=True
|
||||
),
|
||||
),
|
||||
migrations.RemoveField("imagevariant", "id"),
|
||||
migrations.RenameField(
|
||||
model_name="imagevariant", old_name="uuid", new_name="id"
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="imagevariant",
|
||||
name="id",
|
||||
field=models.UUIDField(
|
||||
primary_key=True, default=uuid.uuid4, serialize=False, editable=False
|
||||
),
|
||||
),
|
||||
]
|
107
images/models.py
107
images/models.py
@@ -1,11 +1,12 @@
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
|
||||
from botocore.compat import file_type
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from images.utils import get_b2_resource
|
||||
from PIL import Image as PILImage, ImageOps
|
||||
from PIL import Image as PILImage, ImageOps, ImageEnhance, ImageFilter
|
||||
|
||||
|
||||
class Image(models.Model):
|
||||
@@ -21,6 +22,7 @@ class Image(models.Model):
|
||||
model_version = models.IntegerField(default=1)
|
||||
height = models.IntegerField(default=0)
|
||||
width = models.IntegerField(default=0)
|
||||
version = models.IntegerField(default=2)
|
||||
|
||||
@property
|
||||
def thumbnail_size(self):
|
||||
@@ -33,56 +35,81 @@ class Image(models.Model):
|
||||
def backblaze_filepath(self):
|
||||
return f"{self.id.hex[:2]}/{self.id.hex[2:4]}/{self.id.hex}"
|
||||
|
||||
def create_variant_tasks(self, width, height, original_file_type):
|
||||
ImageVariantTask(
|
||||
image=self,
|
||||
height=height,
|
||||
width=width,
|
||||
original_file_type=original_file_type,
|
||||
def create_variant_tasks(self, variant):
|
||||
ImageVariant(
|
||||
image=variant.image,
|
||||
height=variant.height,
|
||||
width=variant.width,
|
||||
is_full_size=variant.is_full_size,
|
||||
file_type="avif",
|
||||
gaussian_blur=variant.gaussian_blur,
|
||||
brightness=variant.brightness,
|
||||
available=False,
|
||||
).save()
|
||||
|
||||
ImageVariantTask(
|
||||
image=self,
|
||||
height=height,
|
||||
width=width,
|
||||
original_file_type=original_file_type,
|
||||
ImageVariant(
|
||||
image=variant.image,
|
||||
height=variant.height,
|
||||
width=variant.width,
|
||||
is_full_size=variant.is_full_size,
|
||||
file_type="webp",
|
||||
gaussian_blur=variant.gaussian_blur,
|
||||
brightness=variant.brightness,
|
||||
available=False,
|
||||
).save()
|
||||
|
||||
ImageVariantTask(
|
||||
image=self,
|
||||
height=height,
|
||||
width=width,
|
||||
original_file_type=original_file_type,
|
||||
ImageVariant(
|
||||
image=variant.image,
|
||||
height=variant.height,
|
||||
width=variant.width,
|
||||
is_full_size=variant.is_full_size,
|
||||
file_type="jpegli",
|
||||
gaussian_blur=variant.gaussian_blur,
|
||||
brightness=variant.brightness,
|
||||
available=False,
|
||||
).save()
|
||||
|
||||
def create_variant(self, width, height):
|
||||
def create_variant(self, width, height, gaussian_blur, brightness):
|
||||
bucket = get_b2_resource()
|
||||
|
||||
original_image = BytesIO()
|
||||
original_variant = self.imagevariant_set.filter(
|
||||
is_full_size=True, file_type__in=["jpg", "png"]
|
||||
is_full_size=True,
|
||||
file_type__in=["jpg", "png"],
|
||||
gaussian_blur=0,
|
||||
brightness=1,
|
||||
).first()
|
||||
|
||||
bucket.download_fileobj(
|
||||
f"{self.backblaze_filepath}/{original_variant.width}-{original_variant.height}/image.{original_variant.file_type}",
|
||||
original_variant.backblaze_filepath,
|
||||
original_image,
|
||||
)
|
||||
original_image.seek(0)
|
||||
|
||||
resized_image = BytesIO()
|
||||
|
||||
file_extension = "jpg"
|
||||
image_variant = ImageVariant(
|
||||
image=self,
|
||||
height=height,
|
||||
width=width,
|
||||
is_full_size=False,
|
||||
file_type="jpg",
|
||||
gaussian_blur=gaussian_blur,
|
||||
brightness=brightness,
|
||||
available=True,
|
||||
)
|
||||
|
||||
with PILImage.open(original_image) as im:
|
||||
ImageOps.exif_transpose(im, in_place=True)
|
||||
im = im.filter(ImageFilter.GaussianBlur(gaussian_blur))
|
||||
enhancer = ImageEnhance.Brightness(im)
|
||||
im = enhancer.enhance(brightness)
|
||||
im.thumbnail((width, height))
|
||||
|
||||
if im.has_transparency_data:
|
||||
try:
|
||||
im.save(resized_image, "PNG")
|
||||
file_extension = "png"
|
||||
image_variant.file_extension = "png"
|
||||
except OSError:
|
||||
im.convert("RGB").save(resized_image, "JPEG")
|
||||
else:
|
||||
@@ -93,32 +120,40 @@ class Image(models.Model):
|
||||
|
||||
resized_image.seek(0)
|
||||
|
||||
bucket.upload_fileobj(
|
||||
resized_image,
|
||||
f"{self.backblaze_filepath}/{width}-{height}/image.{file_extension}",
|
||||
)
|
||||
|
||||
image_variant = ImageVariant(
|
||||
image=self,
|
||||
height=height,
|
||||
width=width,
|
||||
is_full_size=False,
|
||||
file_type=file_extension,
|
||||
)
|
||||
bucket.upload_fileobj(resized_image, image_variant.backblaze_filepath)
|
||||
|
||||
image_variant.save()
|
||||
|
||||
self.create_variant_tasks(width, height, file_extension)
|
||||
self.create_variant_tasks(image_variant)
|
||||
|
||||
return image_variant
|
||||
|
||||
|
||||
class ImageVariant(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
image = models.ForeignKey(Image, on_delete=models.CASCADE)
|
||||
height = models.IntegerField()
|
||||
width = models.IntegerField()
|
||||
gaussian_blur = models.FloatField(default=0)
|
||||
brightness = models.FloatField(default=1)
|
||||
is_full_size = models.BooleanField(default=False)
|
||||
file_type = models.CharField(max_length=10)
|
||||
available = models.BooleanField(default=False)
|
||||
|
||||
@property
|
||||
def backblaze_filepath(self):
|
||||
return f"{self.id.hex[:2]}/{self.id.hex[2:4]}/{self.id.hex}.{self.file_type}"
|
||||
|
||||
@property
|
||||
def parent_variant_for_optimized_versions(self):
|
||||
return ImageVariant.objects.filter(
|
||||
image_id=self.image_id,
|
||||
height=self.height,
|
||||
width=self.width,
|
||||
gaussian_blur=self.gaussian_blur,
|
||||
brightness=self.brightness,
|
||||
file_type__in=["jpg", "png"],
|
||||
).first()
|
||||
|
||||
|
||||
class ImageVariantTask(models.Model):
|
||||
@@ -127,6 +162,8 @@ class ImageVariantTask(models.Model):
|
||||
image = models.ForeignKey(Image, on_delete=models.CASCADE)
|
||||
height = models.IntegerField()
|
||||
width = models.IntegerField()
|
||||
gaussian_blur = models.FloatField(default=0)
|
||||
brightness = models.FloatField(default=1)
|
||||
original_file_type = models.CharField(max_length=10)
|
||||
file_type = models.CharField(max_length=10)
|
||||
|
||||
|
105
images/views.py
105
images/views.py
@@ -9,6 +9,7 @@ from django.http import (
|
||||
HttpResponseNotFound,
|
||||
)
|
||||
from django.shortcuts import redirect, render
|
||||
from django.template.defaultfilters import first
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from images.decorators import (
|
||||
@@ -16,7 +17,7 @@ from images.decorators import (
|
||||
can_upload_variant,
|
||||
can_upload_image,
|
||||
)
|
||||
from images.models import Image, ImageVariant, ImageVariantTask
|
||||
from images.models import Image, ImageVariant
|
||||
from images.utils import get_b2_resource
|
||||
|
||||
JpegImagePlugin._getmp = lambda x: None
|
||||
@@ -44,13 +45,16 @@ def upload(request):
|
||||
elif im.format == "PNG":
|
||||
file_extension = "png"
|
||||
else:
|
||||
return {"created": False, "error": "Uploaded file should be JPEG or PNG"}
|
||||
return JsonResponse(
|
||||
{"created": False, "error": "Uploaded file should be JPEG or PNG"},
|
||||
status=400,
|
||||
)
|
||||
|
||||
file.seek(0)
|
||||
|
||||
same_md5_image = Image.objects.filter(original_md5=file_md5_hash).first()
|
||||
if same_md5_image:
|
||||
return {"created": False, "id": same_md5_image.id}
|
||||
return JsonResponse({"created": False, "id": same_md5_image.id})
|
||||
|
||||
image = Image(
|
||||
original_name=file.name,
|
||||
@@ -59,44 +63,46 @@ def upload(request):
|
||||
height=height,
|
||||
width=width,
|
||||
model_version=2,
|
||||
version=3,
|
||||
)
|
||||
|
||||
image.save()
|
||||
|
||||
bucket.upload_fileobj(
|
||||
file, f"{image.backblaze_filepath}/{width}-{height}/image.{file_extension}"
|
||||
)
|
||||
|
||||
ImageVariant.objects.get_or_create(
|
||||
variant, _ = ImageVariant.objects.get_or_create(
|
||||
image=image,
|
||||
height=height,
|
||||
width=width,
|
||||
file_type=file_extension,
|
||||
is_full_size=True,
|
||||
available=True,
|
||||
)
|
||||
|
||||
image.create_variant_tasks(image.width, image.height, file_extension)
|
||||
bucket.upload_fileobj(file, variant.backblaze_filepath)
|
||||
|
||||
image.create_variant_tasks(variant)
|
||||
image.uploaded = True
|
||||
image.save()
|
||||
|
||||
return JsonResponse({"created": True, "id": image.id})
|
||||
return JsonResponse({"created": True, "id": image.id}, status=201)
|
||||
|
||||
|
||||
@can_upload_variant
|
||||
def image_type_optimization_needed(request, image_type):
|
||||
tasks = ImageVariantTask.objects.filter(file_type=image_type).all()
|
||||
variants = ImageVariant.objects.filter(
|
||||
image__version=3, file_type=image_type, available=False
|
||||
).all()[:100]
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"variants": [
|
||||
{
|
||||
"image_id": task.image.id,
|
||||
"task_id": task.id,
|
||||
"height": task.height,
|
||||
"width": task.width,
|
||||
"file_type": task.original_file_type,
|
||||
"original_variant_id": variant.parent_variant_for_optimized_versions.id,
|
||||
"original_variant_file_type": variant.parent_variant_for_optimized_versions.file_type,
|
||||
"variant_id": variant.id,
|
||||
"height": variant.height,
|
||||
"width": variant.width,
|
||||
}
|
||||
for task in tasks
|
||||
for variant in variants
|
||||
]
|
||||
}
|
||||
)
|
||||
@@ -105,47 +111,36 @@ def image_type_optimization_needed(request, image_type):
|
||||
@csrf_exempt
|
||||
@can_upload_variant
|
||||
def upload_variant(request):
|
||||
if "task_id" not in request.POST:
|
||||
if "variant_id" not in request.POST:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
task = ImageVariantTask.objects.filter(id=request.POST["task_id"]).first()
|
||||
variant = ImageVariant.objects.filter(id=request.POST["variant_id"]).first()
|
||||
|
||||
if not task:
|
||||
if not variant:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
image = task.image
|
||||
height = task.height
|
||||
width = task.width
|
||||
file_type = task.file_type
|
||||
file = request.FILES["file"]
|
||||
|
||||
if file_type == "jpegli":
|
||||
file_name = "jpegli.jpg"
|
||||
else:
|
||||
file_name = "image." + file_type
|
||||
|
||||
upload_path = f"{image.backblaze_filepath}/{width}-{height}/{file_name}"
|
||||
|
||||
bucket = get_b2_resource()
|
||||
|
||||
bucket.upload_fileobj(file, upload_path)
|
||||
bucket.upload_fileobj(file, variant.backblaze_filepath)
|
||||
|
||||
ImageVariant.objects.get_or_create(
|
||||
image=image,
|
||||
height=height,
|
||||
width=width,
|
||||
file_type=file_type,
|
||||
is_full_size=(height == image.height and width == image.width),
|
||||
)
|
||||
|
||||
task.delete()
|
||||
variant.available = True
|
||||
variant.save()
|
||||
|
||||
return JsonResponse({"status": "ok"})
|
||||
|
||||
|
||||
def image_with_size(request, image, width, height, image_type):
|
||||
gaussian_blur = float(request.GET.get("gaussian_blur", 0))
|
||||
brightness = float(request.GET.get("brightness", 1))
|
||||
|
||||
image_variants = ImageVariant.objects.filter(
|
||||
image=image, height=height, width=width
|
||||
image=image,
|
||||
height=height,
|
||||
width=width,
|
||||
gaussian_blur=gaussian_blur,
|
||||
brightness=brightness,
|
||||
)
|
||||
if image_type != "auto":
|
||||
if image_type == "original":
|
||||
@@ -153,16 +148,21 @@ def image_with_size(request, image, width, height, image_type):
|
||||
else:
|
||||
image_variants = image_variants.filter(file_type=image_type)
|
||||
|
||||
if image.version == 3:
|
||||
image_variants = image_variants.filter(available=True)
|
||||
|
||||
variants = image_variants.all()
|
||||
|
||||
if not variants:
|
||||
if image_type != "auto" and image_type != "original":
|
||||
return JsonResponse({"error": "Image version not available"}, status=404)
|
||||
else:
|
||||
image_variant = image.create_variant(width, height)
|
||||
image_variant = image.create_variant(
|
||||
width, height, gaussian_blur, brightness
|
||||
)
|
||||
|
||||
return redirect(
|
||||
f"{settings.S3_PUBLIC_BASE_PATH}/{image.backblaze_filepath}/{width}-{height}/image.{image_variant.file_type}"
|
||||
f"{settings.S3_PUBLIC_BASE_PATH}/{image_variant.backblaze_filepath}"
|
||||
)
|
||||
|
||||
if image_type == "auto":
|
||||
@@ -195,14 +195,17 @@ def image_with_size(request, image, width, height, image_type):
|
||||
|
||||
variant = variant[0]
|
||||
|
||||
if file_type == "jpegli":
|
||||
file_name = "jpegli.jpg"
|
||||
else:
|
||||
file_name = "image." + file_type
|
||||
if image.version == 2:
|
||||
if file_type == "jpegli":
|
||||
file_name = "jpegli.jpg"
|
||||
else:
|
||||
file_name = "image." + file_type
|
||||
|
||||
return redirect(
|
||||
f"{settings.S3_PUBLIC_BASE_PATH}/{image.backblaze_filepath}/{variant.width}-{variant.height}/{file_name}"
|
||||
)
|
||||
return redirect(
|
||||
f"{settings.S3_PUBLIC_BASE_PATH}/{image.backblaze_filepath}/{variant.width}-{variant.height}/{file_name}"
|
||||
)
|
||||
|
||||
return redirect(f"{settings.S3_PUBLIC_BASE_PATH}/{variant.backblaze_filepath}")
|
||||
|
||||
return HttpResponseNotFound()
|
||||
|
||||
|
Reference in New Issue
Block a user