image library
created using VS Codey Copilot Agent with Claude Sonnet 4
This commit is contained in:
parent
bdf06863d2
commit
52dbe89582
14 changed files with 1366 additions and 3 deletions
149
hub/services/forms/image_library.py
Normal file
149
hub/services/forms/image_library.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
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()})"
|
Loading…
Add table
Add a link
Reference in a new issue