matthewwithanm / django-imagekit

Automated image processing for Django. Currently v4.0
http://django-imagekit.rtfd.org/
BSD 3-Clause "New" or "Revised" License
2.26k stars 276 forks source link

How to preview image in Django admin before save model? #444

Closed koddr closed 6 years ago

koddr commented 6 years ago

Hello again!

I search for best practice for preview image in Django admin before save model. I want to display image quickly for my site moderators (with included filter from ImageKit) when they're adding image to the ImageField in admin. This's important, because this images posted to our Instagram account after save model (automatically).

I know about https://github.com/matthewwithanm/django-imagekit#admin but it's way for preview image AFTER save model. I need to BEFORE save...

Can somebody help me understand what to do or/and share their recipes? 👍 Thanks.

koddr commented 6 years ago

In my case, I do it with replace default template and add some JavaScript code.

For example, my models.py:

# ./example/models.py

class Example(models.Model):
    """ Example model """

    image = models.ImageField('Picture', default=None)

    image_790x160 = ImageSpecField(
        source='image',
        processors=[ResizeToFill(790, 160)],
        format='JPEG',
        options={'quality': 85}
    )
    ...

...and admin.py:

# ./example/admin.py

@admin.register(Example)  # Register `Example` as Admin model
class ExampleAdmin(admin.ModelAdmin):
    list_display = ('id',)
    readonly_fields = ('admin_thumbnail',)
    admin_thumbnail = AdminThumbnail(image_field='image_790x160', template='admin/thumbnail.html')
    ...

...and custom thumbnail template:

<!-- ./templates/admin/thumbnail.html -->

{% if thumbnail %}

  <img src="{{ thumbnail.url }}" alt="thumbnail">

{% else %}

  <div id="preview_image" style="display: none; width: 790px; height: 160px;"></div>

  <script>
      function handleFileSelect(event) {
          var files = event.target.files; // FileList object
          // Loop through the FileList and render image files as thumbnails
          for (var i = 0, f; f = files[i]; i++) {
              // Only process image files
              if (!f.type.match('image.*')) continue;
              // Init FileReader()
              // See: https://developer.mozilla.org/en-US/docs/Web/API/FileReader
              var reader = new FileReader();
              // Closure to capture the file information
              reader.onload = (function () {
                  return function (e) {
                      // Render background image
                      document.getElementById('preview_image').style.background = 'url(' + e.target.result + ') no-repeat center center';
                      document.getElementById('preview_image').style.backgroundSize = 'cover';
                      // Set `display: block` to preview image container
                      document.getElementById('preview_image').style.display = 'block';
                  };
              })(f);
              // Read in the image file as a data URL
              reader.readAsDataURL(f);
          }
      }

      // Change background after change file input
      // id_image — is default ID for ImageField input named `image` (in Django Admin)
      document.getElementById('id_image').addEventListener('change', handleFileSelect, false);
  </script>

{% endif %}

P.S. would be nice to add something like my case to official docs of Django ImageKit for future people who search solution for similar things 👍

vstoykov commented 6 years ago

I think that this is outside of the scope of ImageKit. ImageKit can't work with files which are not saved (not uploaded at all to the server). You have two options:

  1. To use JavaScript to mimic the behavior of selected processors and work with source files before upload
  2. To create some kind of clipboard which can use the same processors and upload the files (with javascript) to this kind of "clipboard". You can use for inspiration https://github.com/IndustriaTech/django-user-clipboard. This app is not production ready for general purpose (for that reason it's not in PyPi) but you can look at the code and tests to see the idea. If you have any suggestions for it I'll be glad to discuss them.

In the end It all depends on your use case, and general solutions that can be included in ImageKit can be very hard or unneeded.

qylove516 commented 5 years ago

Hi,Koddr ,I have no idea that where is AdminThumbnail from?Would you please tell me?

lucasvazq commented 2 years ago

Guys, I drop a solution right here anyway, Ha!

Steps

1) Define a custom field in the model (models.py):

from django.contrib.admin.decorators import display
from django.db.models import Model
from django.db.models.fields.files import ImageField
from django.template.loader import get_template

class MyModel(Model):
    my_image_field = ImageField()

    # This is our new field. It renders a preview of the image before and post save.
    @display(description='Preview')
    def my_image_thumbnail(self):
        return get_template('my_image_thumbnail_template.html').render({
            'field_name': 'my_image_field',
            'src': self.my_image_field.url if self.my_image_field else None,
        })

2) Make it available in the admin config of the app (admin.py):

from django.contrib import admin

from .models import MyModel

admin.site.register(MyModel, fields=('my_image_thumbnail', 'image'), readonly_fields=('my_image_thumbnail',))

3) Define a template for that new field (my_image_thumbnail_template.html):

<img
  <!--
    If there's no image, for example, when we are adding a new instance,
    we can fill the empty image preview with a placeholder image.
    If want to get this placeholder image URL from your static files,
    must load their URL in the `MyModel.my_image_thumbnail` method.
    Here is a thread that shows how to do it: https://stackoverflow.com/questions/11721818
  -->
  src="{% if src %}{{ src }}{% else %}http://atrilco.com/wp-content/uploads/2017/11/ef3-placeholder-image.jpg{% endif %}"
  id="image-thumbnail-{{ field_name }}"
>

<script>
  // Vainilla javascript 🚀 (Babel defaults, not ie 11, not ie_mob 11)

  // This function adds an event listener over the input image field (`my_field_image`).
  // If the user edits that field, the preview image will be updated.
  function changeThumbnail() {
    const fieldName = "{{ field_name }}";
    document
      .getElementById(`id_${fieldName}`)
      .addEventListener("change", function (e) {
        const reader = new FileReader();
        reader.onload = function (e) {
          document.getElementById(`image-thumbnail-${fieldName}`).src = e.target.result;
        };
        reader.readAsDataURL(this.files[0]);
      });
  }

  // This function executes a callback when the document is ready.
  // https://stackoverflow.com/questions/9899372
  //
  // Is this required?
  // No, but, in most cases, the image preview is rendered above the input field,
  // as we do in the `admin.py` module.
  // In that case, need to wait for the document to be ready before adding the
  // event listener defined in `changeThumbnail`.
  function docReady(fn) {
    if (document.readyState === "complete" || document.readyState === "interactive") {
      setTimeout(fn, 1);
    } else {
      document.addEventListener("DOMContentLoaded", fn);
    }
  }
   docReady(changeThumbnail);
</script>

Result

New instance without image

admin

New instance with image

admin2

koddr commented 2 years ago

@lucasvazq thanks! It's better late than never 😉

GaniyevUz commented 4 days ago

@lucasvazq i was searching for a long time thanks anyway.