Replace django-image-cropping with an in-repo Cropper.js admin widget (#1299) - #1305
Merged
Conversation
…1269) Swap the EOL PyPI django-image-cropping (v1.7, Feb 2022; Jcrop+jQuery; Django <=4.0; "save first, then crop") for an in-repo fork whose only material change is a modern Cropper.js admin widget that previews and crops client-side, before the first save. Closes the long-standing #1299 friction and retires a Django-6.1-blocking dependency (#1269 sibling). New top-level image_cropping/ package (project source, like sortedm2m_filter_horizontal_widget): ImageRatioField, crop_corners, ImageCroppingMixin, CropImageWidget, vendored cropper.min.{js,css} v1.6.2 + ml_cropper.{js,css} glue (instant FileReader preview -> Cropper.js -> "x1,y1,x2,y2" box; keyboard-accessible numeric X/Y/W/H inputs; no-JS fallback). Deliberately migration-neutral: ImageRatioField stays a CharField whose deconstruct() returns "image_cropping.fields.ImageRatioField", so the gitignored per-environment migrations that import image_cropping.fields keep working and the DB column is untouched (pinned by a regression test). Keeping the package name is what preserves those imports. - requirements.txt: drop django-image-cropping (django-appconf falls away as a transitive dep); INSTALLED_APPS keeps 'image_cropping' (now the fork). - Banner: convert the 2 cropped_thumbnail calls to the standard {% thumbnail box= %} idiom; remove 19 now-dead {% load cropping %} lines; add {% load thumbnail %} to base.html. - Refresh the obsolete "Save and continue editing" help texts on Banner, News, Person, Photo, Project. - Update CLAUDE.md image-handling docs; add image_cropping/README.md. Tests: 14 new (crop_corners parsing, deconstruct migration contract, widget Media uses Cropper not Jcrop, model crop metadata); full suite 149 passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Editor-facing polish for the admin cropper. The raw "Crop region (pixels)" X/Y/W/H fieldset read as dev internals; coordinates mean nothing to someone cropping a photo. - Add a live WYSIWYG preview (Cropper.js `preview`) showing exactly what the cropped thumbnail will look like, sized to the field's aspect ratio. - Move the numeric inputs into a collapsed "Adjust crop precisely (pixels)" <details> disclosure — still keyboard/screen-reader accessible, just no longer cluttering the default drag-to-crop view. - Fix: keep the instant-preview blob: object URL alive for the crop session. It was revoked on the main image's load, but Cropper builds its preview clone from the same URL afterward, leaving the preview blank (naturalWidth 0). Now the previous URL is revoked only when a new file replaces it. Browser-verified (Playwright + Chrome): preview renders the cropped pixels, disclosure collapsed by default, numeric inputs still drive the crop when expanded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The precise-pixel inputs wrapped (Height fell to a second line) because they're rendered inside Django admin's aligned form, whose label styling (width:160px, min-width:160px, float:left) was applied to each field's <label> wrapper and forced it ~170px wide. Override that styling on the wrappers (min-width:0 !important is the key — min-width floors width) and give the number inputs a compact fixed width, so all four share a single flex row. (The easter-egg image on Person already gets its own cropper + live preview; both crop fields are handled identically by the widget. Verified in-browser.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the EOL PyPI
django-image-cropping(v1.7, Feb 2022; Jcrop + jQuery; classifiers stop at Django 4.0; "upload → Save and continue → scroll up → crop") with a small in-repo fork whose only material change is a modern Cropper.js admin widget that previews and crops the image client-side, before the first save.Closes #1299. Advances #1269 (retires a fragile, unmaintained dependency that was a Django 6.1 upgrade risk). Follow-up enhancement filed as #1304.
Why this approach
The only thing tying us to the unmaintained dependency was the admin widget JS —
ImageRatioFieldand thecrop_cornersprocessor are pure Django/Pillow. So instead of ripping the system out, we vendorimage_cropping/in-repo (treated as project source, exactly likesortedm2m_filter_horizontal_widget) and rewrite just the widget. There's no maintained drop-in that does instant client-side cropping plus a non-destructive box fed to easy_thumbnails (most modern widgets either only preview, or bake a destructive crop) — and our render pipeline (one stored box → rendered at every size on demand) is the valuable part worth keeping.What changed
image_cropping/package:ImageRatioField,crop_corners,ImageCroppingMixin,CropImageWidget, vendored Cropper.js v1.6.2 (MIT, no build step) +ml_cropper.{js,css}glue. Seeimage_cropping/README.md.FileReaderpreview on file select; live WYSIWYG preview of the cropped result; precise keyboard-accessible X/Y/W/H inputs tucked in a collapsed "Adjust crop precisely" disclosure.requirements.txt: dropdjango-image-cropping(django-appconffalls away as a transitive dep).INSTALLED_APPSkeeps'image_cropping'(now the fork).cropped_thumbnailcalls to the standard{% thumbnail … box=banner.cropping %}idiom; remove 19 now-dead{% load cropping %}lines; add{% load thumbnail %}tobase.html.Migration safety
Deliberately migration-neutral:
ImageRatioFieldstays aCharFieldwhosedeconstruct()returnsimage_cropping.fields.ImageRatioField, so the gitignored, per-environment migrations thatimport image_cropping.fieldskeep working and the DB column is untouched (makemigrationsreports no change to anycroppingfield). Keeping the package name is what preserves those imports — important given deploys are push-only with no server shell. A regression test pins thedeconstruct()path.Testing
website/tests/test_image_cropping.py(crop_corners parsing, thedeconstruct()migration contract, widget Media uses Cropper not Jcrop, model crop metadata).--settings=makeabilitylab.settings_test).blob:URLs withcheckOrientation:true, and the live preview going blank because the object URL was revoked too early.Screenshots
Checklist
Screenshot after
🤖 Generated with Claude Code