简体   繁体   English

Django 图像在上传前调整大小和转换

[英]Django image resizing and convert before upload

I searched a lot on this subject but couldn't really find what I need.我在这个主题上搜索了很多,但找不到我需要的东西。 I'll explain my problem :我会解释我的问题:

On my website, the user can upload an image.在我的网站上,用户可以上传图片。 I need to resize this image and convert it to JPEG before uploading it.上传之前,我需要调整此图像的大小并将其转换为 JPEG。

I saw some solutions using a method in the Views.py, but I'd like to do it by overriding the save() method in my Models.py.我在 Views.py 中看到了一些使用方法的解决方案,但我想通过覆盖 Models.py 中的 save() 方法来实现。 The problem is, all solutions I found for resizing image in save method do it after saving it a first time (calling super fonction), which means it'd use bandwidth (if I use a CDN) for nothing (am I right on this point?).问题是,我在 save 方法中找到的用于调整图像大小的所有解决方案都是在第一次保存(调用超级函数)后执行的,这意味着它会无用地使用带宽(如果我使用 CDN)(我对此是否正确)观点?)。

Thank you for your help感谢您的帮助

First, it's best to establish the correct language.首先,最好建立正确的语言。 Django and Python exist only on the server side. Django 和 Python 只存在于服务器端。 Therefore, anything they manipulate, save, or otherwise use, has to be first sent to the server.因此,他们操作、保存或以其他方式使用的任何内容都必须首先发送到服务器。 If Django or Python is to manage the photo, the user MUST upload this photo to the server first.如果要使用 Django 或 Python 来管理照片,则用户必须先将此照片上传到服务器。 Once the photo is uploaded, Django is free to make changes before storing the file.上传照片后,Django 可以在存储文件之前自由进行更改。

If your concern is with upload bandwidth, and you don't want large files being uploaded, you will have to resize and reformat the photo on the client side.如果您担心上传带宽,并且不希望上传大文件,则必须在客户端调整照片大小并重新格式化。 If this is a web application, this can be done using Javascript, but can not be done with Python, since Python does not operate on the client side for an application like yours.如果这是一个 Web 应用程序,这可以使用 Javascript 完成,但不能使用 Python 完成,因为 Python 不会在客户端运行像您这样的应用程序。

If your concern is not with bandwidth, then you're free to have the user "upload" the file, but then have Django resize and reformat it before saving.如果您不关心带宽,那么您可以自由地让用户“上传”文件,但在保存之前让 Django 调整大小并重新格式化。

You are correct that you will want to override your save function for the photo object.您想覆盖照片对象的保存功能是正确的。 I would recommend using a library to handle the resizing and reformatting, such as sorl .我建议使用库来处理调整大小和重新格式化,例如sorl

from sorl.thumbnail import ImageField, get_thumbnail

class MyPhoto(models.Model):
    image = ImageField()

    def save(self, *args, **kwargs):
        if self.image:
            self.image = get_thumbnail(self.image, '500x600', quality=99, format='JPEG')
        super(MyPhoto, self).save(*args, **kwargs)

Sorl is just a library I am confident and familiar with, but it takes some tuning and configuration. Sorl 只是我自信和熟悉的一个库,但它需要一些调整和配置。 You can check out Pillow or something instead, and just replace the line overriding self.image .您可以查看 Pillow 或其他东西,只需替换覆盖self.image的行。

I also found a similar question here .我也在这里找到了一个类似的问题。

Edit: saw the update to your comment response above.编辑:在上面看到了对您的评论回复的更新。 Also note that if your webserver is handling Django, and your files are being saved to some CDN, this method will work.另请注意,如果您的网络服务器正在处理 Django,并且您的文件正在保存到某些 CDN,则此方法将起作用。 The image will be resized on the webserver before being uploaded to your CDN (assuming your configuration is as I'm assuming).图像将上传到 CDN之前在网络服务器上调整大小(假设您的配置与我假设的一样)。

Hope this helps!希望这可以帮助!

from django.db import models
from django.contrib.auth.models import User
from PIL import Image



class profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.CharField(max_length=300)
    location = models.CharField(max_length=99)
    image = models.ImageField(default='default.jpg', upload_to='profile_pics')

    def save(self):
        super().save()  # saving image first

        img = Image.open(self.image.path) # Open image using self

        if img.height > 300 or img.width > 300:
            new_img = (300, 300)
            img.thumbnail(new_img)
            img.save(self.image.path)  # saving image at the same path

This example shows how to upload image after image re-sizing.此示例显示如何在图像调整大小后上传图像。 Change the pixel of new_img, whatever you want.随意更改 new_img 的像素。

Here is an app that can take care of that: django-smartfields .这是一个可以解决这个问题的应用程序: django-smartfields It will also remove an old image whenever a new one is uploaded.每当上传新图像时,它也会删除旧图像。

from django.db import models

from smartfields import fields
from smartfields.dependencies import FileDependency
from smartfields.processors import ImageProcessor

class ImageModel(models.Model):
    image = fields.ImageField(dependencies=[
        FileDependency(processor=ImageProcessor(
            format='JPEG', scale={'max_width': 300, 'max_height': 300}))
    ])

EDIT4 : full working code can be found here (the code below still has some mistakes) EDIT4:可以在这里找到完整的工作代码(下面的代码仍然有一些错误)

Still fighting, but there is progress ^^ : I am trying to do the resizing and convertion in memory with PIL (and seeing the amount of questions on the subject, it seems that I am not the only one ^^).仍在战斗,但有进步^^:我正在尝试使用 PIL 进行内存中的调整大小和转换(并且看到有关该主题的问题数量,似乎我不是唯一一个 ^^)。 My code so far (in Models.py):到目前为止我的代码(在 Models.py 中):

from PIL import Image as Img
import StringIO
from django.core.files import File

class Mymodel(models.Model):
#blablabla
photo = models.ImageField(uppload_to="...", blank=True)

    def save(self, *args, **kwargs):
        if self.photo:
            image = Img.open(StringIO.StringIO(self.photo.read()))
            image.thumbnail((100,100), Img.ANTIALIAS)
            output = StringIO.StringIO()
            image.save(output, format='JPEG', quality=75)
            output.seek(0)
            self.photo = File(output, self.photo.name())
        super(Mymodel, self).save(*args, **kwargs)

I have an error at "photo = File(output, photo.name())"我在“photo = File(output, photo.name())”有错误

Thanks谢谢

EDIT : Sorry, it was a simple mistake (forgot the self.) corrected in code.编辑:对不起,这是一个简单的错误(忘记了自我。)在代码中更正。 Now I have the error "TypeError, 'unicode' object is not callable", at the same line.现在我在同一行出现错误“TypeError,'unicode' 对象不可调用”。

EDIT2: Saw something about "output.getvalue()" here but don't really know if could be of any help. EDIT2: 在这里看到了一些关于“output.getvalue()”的内容但不知道是否有任何帮助。

EDIT3 : Solved!! EDIT3:解决了!! Code below.代码如下。

from PIL import Image as Img
import StringIO
from django.core.files.uploadedfile import InMemoryUploadedFile

class Mymodel(models.Model):
    photo = models.ImageField(upload_to="...", blank=True)

    def save(self, *args, **kwargs):
        if self.photo:
            image = Img.open(StringIO.StringIO(self.photo.read()))
            image.thumbnail((200,200), Img.ANTIALIAS)
            output = StringIO.StringIO()
            image.save(output, format='JPEG', quality=75)
            output.seek(0)
            self.photo= InMemoryUploadedFile(output,'ImageField', "%s.jpg" %self.photo.name, 'image/jpeg', output.len, None)
        super(Mymodel, self).save(*args, **kwargs)

For PNG looking weird, see second post on this thread 对于 PNG 看起来很奇怪,请参阅此线程上的第二篇文章

Just put together this for my own project, took me a while before i realized that the bytes and the image is separate attributes on the Django ImageField, this solution worked for me with Python 3.6.只是将它放在我自己的项目中,花了我一段时间才意识到字节和图像是 Django ImageField 上的单独属性,这个解决方案适用于 Python 3.6。

    def clean_image(self):
        image_field = self.cleaned_data.get('image')
        if image_field:
            try:
                image_file = BytesIO(image_field.file.read())
                image = Image.open(image_file)
                image.thumbnail((300, 300), Image.ANTIALIAS)
                image_file = BytesIO()
                image.save(image_file, 'PNG')
                image_field.file = image_file
                image_field.image = image

                return image_field
            except IOError:
                logger.exception("Error during resize image")

Here is one more package that works for me with minimal code modification - django-resized .这是另一个对我有用的软件包,只需最少的代码修改 - django-resized

models.py模型.py

from django_resized import ResizedImageField

class Post(models.Model):
    image = ResizedImageField(upload_to='uploads/%Y/%m/%d')

settings.py设置.py

DJANGORESIZED_DEFAULT_SIZE = [1024, 768]
DJANGORESIZED_DEFAULT_QUALITY = 75
DJANGORESIZED_DEFAULT_KEEP_META = True
DJANGORESIZED_DEFAULT_FORCE_FORMAT = 'JPEG'
DJANGORESIZED_DEFAULT_FORMAT_EXTENSIONS = {'JPEG': ".jpg"}
DJANGORESIZED_DEFAULT_NORMALIZE_ROTATION = True

That's it!就是这样!

This Worked for me try it.这对我有用,试试吧。 I am using Django 2.0.6 and Python 3.6.4我正在使用 Django 2.0.6 和 Python 3.6.4

from django.db import models
from PIL import Image
from io import BytesIO
from django.core.files.uploadedfile import InMemoryUploadedFile

class ImageUpload(models.Model):
    name = models.CharField(max_length=100)
    uploadedImage = models.ImageField(upload_to='Uploads/%Y/%m/', db_column="Upload an Image")
    def save(self, *args, **kwargs):
        imageTemproary = Image.open(self.uploadedImage)
        outputIoStream = BytesIO()
        imageTemproaryResized = imageTemproary.resize( (1020,573) ) 
        imageTemproaryResized.save(outputIoStream , format='JPEG', quality=85)
        outputIoStream.seek(0)
        self.uploadedImage = InMemoryUploadedFile(outputIoStream,'ImageField', "%s.jpg" %self.uploadedImage.name.split('.')[0], 'image/jpeg', sys.getsizeof(outputIoStream), None)
        super(ImageUpload, self).save(*args, **kwargs)
from PIL import Image
class Article(TimeStampedModel):
image = models.ImageField(upload_to='article_images/', null=True, blank=True)


def save(self, *args, **kwargs):
    if self.image:
        super().save(*args, **kwargs)
        img = Image.open(self.image.path)
        if img.height > 700 or img.weight > 700:
            output_size = (700,700)
            img.thumbnail(output_size)
            img.save(self.image.path)

The easiest solution:最简单的解决方案:

def compress(image):
    im = Image.open(image)
    # create a BytesIO object
    im_io = BytesIO() 
    # save image to BytesIO object
    im.save(im_io, 'JPEG', quality=70) 
    # create a django-friendly Files object
    new_image = File(im_io, name=image.name)
    return new_image

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM