add support for svg images in library
This commit is contained in:
parent
2c217939b0
commit
ff3a09d30c
7 changed files with 257 additions and 30 deletions
|
@ -2,6 +2,8 @@ from django.db import models
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.utils.text import slugify
|
||||
from django_prose_editor.fields import ProseEditorField
|
||||
import mimetypes
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def validate_image_size(value, mb=1):
|
||||
|
@ -10,6 +12,49 @@ def validate_image_size(value, mb=1):
|
|||
raise ValidationError(f"Maximum file size is {mb} MB")
|
||||
|
||||
|
||||
def validate_image_or_svg(value):
|
||||
"""
|
||||
Validate that the uploaded file is either a valid image or SVG file.
|
||||
"""
|
||||
# Check file size first
|
||||
validate_image_size(value)
|
||||
|
||||
# Get the file extension and MIME type
|
||||
filename = value.name.lower()
|
||||
mime_type, _ = mimetypes.guess_type(filename)
|
||||
|
||||
# List of allowed image formats
|
||||
allowed_image_types = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
"image/bmp",
|
||||
"image/tiff",
|
||||
"image/svg+xml",
|
||||
]
|
||||
|
||||
# Check if it's an SVG file
|
||||
if filename.endswith(".svg") or mime_type == "image/svg+xml":
|
||||
try:
|
||||
# Reset file pointer and read content
|
||||
value.seek(0)
|
||||
content = value.read()
|
||||
value.seek(0) # Reset for later use
|
||||
|
||||
# Try to parse as XML to ensure it's valid SVG
|
||||
ET.fromstring(content)
|
||||
return # Valid SVG
|
||||
except ET.ParseError:
|
||||
raise ValidationError("Invalid SVG file format")
|
||||
|
||||
# For non-SVG files, check MIME type
|
||||
if mime_type not in allowed_image_types:
|
||||
raise ValidationError(
|
||||
f"Unsupported file type. Allowed types: JPEG, PNG, GIF, WebP, BMP, TIFF, SVG"
|
||||
)
|
||||
|
||||
|
||||
class Currency(models.TextChoices):
|
||||
CHF = "CHF", "Swiss Franc"
|
||||
EUR = "EUR", "Euro"
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import os
|
||||
|
||||
import mimetypes
|
||||
import xml.etree.ElementTree as ET
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.text import slugify
|
||||
from PIL import Image as PILImage
|
||||
from .base import validate_image_size
|
||||
from .base import validate_image_or_svg
|
||||
|
||||
|
||||
def get_image_upload_path(instance, filename):
|
||||
|
@ -35,10 +36,10 @@ class ImageLibrary(models.Model):
|
|||
)
|
||||
|
||||
# Image file
|
||||
image = models.ImageField(
|
||||
image = models.FileField(
|
||||
upload_to=get_image_upload_path,
|
||||
validators=[validate_image_size],
|
||||
help_text="Upload image file (max 1MB)",
|
||||
validators=[validate_image_or_svg],
|
||||
help_text="Upload image file (max 1MB) - supports JPEG, PNG, GIF, WebP, BMP, TIFF, and SVG",
|
||||
)
|
||||
|
||||
# Image properties (automatically populated)
|
||||
|
@ -122,14 +123,86 @@ class ImageLibrary(models.Model):
|
|||
Update image properties like width, height, and file size.
|
||||
"""
|
||||
try:
|
||||
# Get image dimensions
|
||||
with PILImage.open(self.image.path) as img:
|
||||
self.width = img.width
|
||||
self.height = img.height
|
||||
|
||||
# Get file size
|
||||
self.file_size = self.image.size
|
||||
|
||||
# Check if it's an SVG file
|
||||
filename = self.image.name.lower()
|
||||
mime_type, _ = mimetypes.guess_type(filename)
|
||||
|
||||
if filename.endswith(".svg") or mime_type == "image/svg+xml":
|
||||
# For SVG files, try to extract dimensions from the SVG content
|
||||
try:
|
||||
with open(self.image.path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Parse the SVG to extract width and height
|
||||
root = ET.fromstring(content)
|
||||
|
||||
# Get width and height attributes
|
||||
width = root.get("width")
|
||||
height = root.get("height")
|
||||
|
||||
# Extract numeric values if they exist
|
||||
if width and height:
|
||||
# Remove units like 'px', 'em', etc. and convert to int
|
||||
try:
|
||||
width_val = int(
|
||||
float(
|
||||
width.replace("px", "")
|
||||
.replace("em", "")
|
||||
.replace("pt", "")
|
||||
)
|
||||
)
|
||||
height_val = int(
|
||||
float(
|
||||
height.replace("px", "")
|
||||
.replace("em", "")
|
||||
.replace("pt", "")
|
||||
)
|
||||
)
|
||||
self.width = width_val
|
||||
self.height = height_val
|
||||
except (ValueError, TypeError):
|
||||
# If we can't parse dimensions, try viewBox
|
||||
viewbox = root.get("viewBox")
|
||||
if viewbox:
|
||||
try:
|
||||
viewbox_parts = viewbox.split()
|
||||
if len(viewbox_parts) >= 4:
|
||||
self.width = int(float(viewbox_parts[2]))
|
||||
self.height = int(float(viewbox_parts[3]))
|
||||
except (ValueError, TypeError):
|
||||
# Default SVG dimensions if we can't parse
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
else:
|
||||
# Check for viewBox if width/height attributes don't exist
|
||||
viewbox = root.get("viewBox")
|
||||
if viewbox:
|
||||
try:
|
||||
viewbox_parts = viewbox.split()
|
||||
if len(viewbox_parts) >= 4:
|
||||
self.width = int(float(viewbox_parts[2]))
|
||||
self.height = int(float(viewbox_parts[3]))
|
||||
except (ValueError, TypeError):
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
else:
|
||||
# Default SVG dimensions
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
|
||||
except (ET.ParseError, FileNotFoundError, UnicodeDecodeError):
|
||||
# If SVG parsing fails, set default dimensions
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
else:
|
||||
# For raster images, use PIL
|
||||
with PILImage.open(self.image.path) as img:
|
||||
self.width = img.width
|
||||
self.height = img.height
|
||||
|
||||
# Save without calling the full save method to avoid recursion
|
||||
ImageLibrary.objects.filter(pk=self.pk).update(
|
||||
width=self.width, height=self.height, file_size=self.file_size
|
||||
|
@ -175,6 +248,28 @@ class ImageLibrary(models.Model):
|
|||
self.usage_count -= 1
|
||||
self.save(update_fields=["usage_count"])
|
||||
|
||||
def is_svg(self):
|
||||
"""
|
||||
Check if the uploaded file is an SVG.
|
||||
"""
|
||||
if not self.image:
|
||||
return False
|
||||
|
||||
filename = self.image.name.lower()
|
||||
mime_type, _ = mimetypes.guess_type(filename)
|
||||
|
||||
return filename.endswith(".svg") or mime_type == "image/svg+xml"
|
||||
|
||||
def get_mime_type(self):
|
||||
"""
|
||||
Return the MIME type of the image file.
|
||||
"""
|
||||
if not self.image:
|
||||
return None
|
||||
|
||||
mime_type, _ = mimetypes.guess_type(self.image.name)
|
||||
return mime_type
|
||||
|
||||
|
||||
class ImageReference(models.Model):
|
||||
"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue