150 lines
5.2 KiB
Python
150 lines
5.2 KiB
Python
|
from django import forms
|
||
|
from django.utils.safestring import mark_safe
|
||
|
from django.utils.html import format_html
|
||
|
from django.urls import reverse
|
||
|
from ..models.images import ImageLibrary
|
||
|
|
||
|
|
||
|
class ImageLibraryWidget(forms.Select):
|
||
|
"""
|
||
|
Custom widget for selecting images from the library with thumbnails.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, attrs=None, choices=(), show_thumbnails=True):
|
||
|
self.show_thumbnails = show_thumbnails
|
||
|
super().__init__(attrs, choices)
|
||
|
|
||
|
def format_value(self, value):
|
||
|
"""
|
||
|
Format the selected value for display.
|
||
|
"""
|
||
|
if value is None:
|
||
|
return ""
|
||
|
return str(value)
|
||
|
|
||
|
def render(self, name, value, attrs=None, renderer=None):
|
||
|
"""
|
||
|
Render the widget with thumbnails.
|
||
|
"""
|
||
|
if attrs is None:
|
||
|
attrs = {}
|
||
|
|
||
|
# Add CSS class for styling
|
||
|
attrs["class"] = attrs.get("class", "") + " image-library-select"
|
||
|
|
||
|
# Get all images for the select options
|
||
|
images = ImageLibrary.objects.all().order_by("name")
|
||
|
|
||
|
# Build choices with thumbnails
|
||
|
choices = [("", "--- Select an image ---")]
|
||
|
for image in images:
|
||
|
thumbnail_html = ""
|
||
|
if self.show_thumbnails and image.image:
|
||
|
thumbnail_html = format_html(
|
||
|
' <img src="{}" style="width: 20px; height: 20px; object-fit: cover; margin-left: 5px; vertical-align: middle;" />',
|
||
|
image.image.url,
|
||
|
)
|
||
|
|
||
|
choice_text = (
|
||
|
f"{image.name} ({image.get_category_display()}){thumbnail_html}"
|
||
|
)
|
||
|
choices.append((image.pk, choice_text))
|
||
|
|
||
|
# Build the select element
|
||
|
select_html = format_html(
|
||
|
'<select name="{}" id="{}"{}>{}</select>',
|
||
|
name,
|
||
|
attrs.get("id", ""),
|
||
|
self._build_attrs_string(attrs),
|
||
|
self._build_options(choices, value),
|
||
|
)
|
||
|
|
||
|
# Add preview area
|
||
|
preview_html = ""
|
||
|
if value:
|
||
|
try:
|
||
|
image = ImageLibrary.objects.get(pk=value)
|
||
|
preview_html = format_html(
|
||
|
'<div class="image-preview" style="margin-top: 10px;">'
|
||
|
'<img src="{}" style="max-width: 200px; max-height: 200px; border: 1px solid #ddd; border-radius: 4px;" />'
|
||
|
'<p style="margin-top: 5px; font-size: 12px; color: #666;">{} - {}x{} - {}</p>'
|
||
|
"</div>",
|
||
|
image.image.url,
|
||
|
image.name,
|
||
|
image.width or "?",
|
||
|
image.height or "?",
|
||
|
image.get_file_size_display(),
|
||
|
)
|
||
|
except ImageLibrary.DoesNotExist:
|
||
|
pass
|
||
|
|
||
|
# Add JavaScript for preview updates
|
||
|
js_html = format_html(
|
||
|
"<script>"
|
||
|
'document.addEventListener("DOMContentLoaded", function() {{'
|
||
|
' const select = document.getElementById("{}");\n'
|
||
|
' const previewDiv = select.parentNode.querySelector(".image-preview");\n'
|
||
|
' select.addEventListener("change", function() {{'
|
||
|
" const imageId = this.value;\n"
|
||
|
" if (imageId) {{"
|
||
|
' fetch("/admin/services/imagelibrary/" + imageId + "/preview/")'
|
||
|
" .then(response => response.json())"
|
||
|
" .then(data => {{"
|
||
|
" if (previewDiv) {{"
|
||
|
" previewDiv.innerHTML = data.html;\n"
|
||
|
" }}"
|
||
|
" }});\n"
|
||
|
" }} else {{"
|
||
|
" if (previewDiv) {{"
|
||
|
' previewDiv.innerHTML = "";\n'
|
||
|
" }}"
|
||
|
" }}"
|
||
|
" }});\n"
|
||
|
"}});\n"
|
||
|
"</script>",
|
||
|
attrs.get("id", ""),
|
||
|
)
|
||
|
|
||
|
return mark_safe(select_html + preview_html + js_html)
|
||
|
|
||
|
def _build_attrs_string(self, attrs):
|
||
|
"""
|
||
|
Build HTML attributes string.
|
||
|
"""
|
||
|
attr_parts = []
|
||
|
for key, value in attrs.items():
|
||
|
if key != "id": # id is handled separately
|
||
|
attr_parts.append(f'{key}="{value}"')
|
||
|
return " " + " ".join(attr_parts) if attr_parts else ""
|
||
|
|
||
|
def _build_options(self, choices, selected_value):
|
||
|
"""
|
||
|
Build option elements for the select.
|
||
|
"""
|
||
|
options = []
|
||
|
for value, text in choices:
|
||
|
selected = "selected" if str(value) == str(selected_value) else ""
|
||
|
options.append(f'<option value="{value}" {selected}>{text}</option>')
|
||
|
return "".join(options)
|
||
|
|
||
|
|
||
|
class ImageLibraryField(forms.ModelChoiceField):
|
||
|
"""
|
||
|
Custom form field for selecting images from the library.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, queryset=None, widget=None, show_thumbnails=True, **kwargs):
|
||
|
if queryset is None:
|
||
|
queryset = ImageLibrary.objects.all()
|
||
|
|
||
|
if widget is None:
|
||
|
widget = ImageLibraryWidget(show_thumbnails=show_thumbnails)
|
||
|
|
||
|
super().__init__(queryset=queryset, widget=widget, **kwargs)
|
||
|
|
||
|
def label_from_instance(self, obj):
|
||
|
"""
|
||
|
Return the label for an image instance.
|
||
|
"""
|
||
|
return f"{obj.name} ({obj.get_category_display()})"
|