Redimensionamento de imagem do Django e conversão antes do upload

Eu procurei muito sobre este assunto, mas não consegui realmente encontrar o que eu preciso. Eu vou explicar meu problema:

No meu site, o usuário pode fazer upload de uma imagem. Preciso resize essa imagem e convertê-la em JPEG antes de enviá-la.

Eu vi algumas soluções usando um método no Views.py, mas gostaria de fazer isso substituindo o método save () em meu Models.py. O problema é que todas as soluções que encontrei para resize a imagem no método save fazem-no depois de salvá-lo pela primeira vez (chamando super fonction), o que significa usar largura de banda (se eu usar um CDN) por nada (estou certo neste ponto?).

obrigado pela ajuda

Primeiro, é melhor estabelecer o idioma correto. Django e Python existem apenas no lado do servidor. Portanto, qualquer coisa que eles manipulem, salvem ou utilizem de outra maneira, deve ser enviada primeiro ao servidor. Se Django ou Python for gerenciar a foto, o usuário DEVE fazer o upload desta foto para o servidor primeiro. Uma vez que a foto é carregada, o Django está livre para fazer alterações antes de armazenar o arquivo.

Se a sua preocupação for com a largura de banda de upload, e você não quiser que arquivos grandes sejam enviados, você terá que resize e reformatar a foto no lado do cliente. Se este for um aplicativo da web, isso pode ser feito usando Javascript, mas não pode ser feito com Python, pois o Python não opera no lado do cliente para um aplicativo como o seu.

Se a sua preocupação não for com a largura de banda, então você está livre para que o usuário “carregue” o arquivo, mas então faça o Django resize e reformatar antes de salvar.

Você está certo de que deseja replace sua function de salvamento para o object de foto. Eu recomendaria usar uma biblioteca para lidar com o redimensionamento e reformatação, como o 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) 

O Sorl é apenas uma biblioteca na qual estou confiante e familiarizado, mas é necessário algum ajuste e configuração. Você pode conferir Pillow ou algo em vez disso, e apenas replace a linha substituindo self.image .

Eu também encontrei uma questão semelhante aqui .

Editar: viu a atualização para sua resposta de comentário acima. Observe também que, se o seu servidor da Web estiver manipulando o Django, e seus arquivos estiverem sendo salvos em um database em algum CDN, esse método funcionará. A imagem será redimensionada no servidor web antes de ser enviada para o seu CDN (supondo que sua configuração é como eu estou supondo).

Espero que isto ajude!

Aqui está um aplicativo que pode cuidar disso: django-smartfields . Também removerá uma imagem antiga sempre que uma nova for carregada.

 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: código de trabalho completo pode ser encontrado aqui (o código abaixo ainda tem alguns erros)

Ainda lutando, mas há progresso ^^: estou tentando fazer o redimensionamento e conversão em memory com PIL (e vendo a quantidade de perguntas sobre o assunto, parece que não sou a única ^^). Meu código até agora (em 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) 

Eu tenho um erro em “foto = arquivo (saída, photo.name ())”

obrigado

EDIT: Desculpe, foi um erro simples (esqueceu o auto.) Corrigido no código. Agora eu tenho o erro “TypeError, ‘unicode’ object não é chamado”, na mesma linha.

EDIT2: Vi algo sobre “output.getvalue ()” aqui, mas realmente não sei se poderia ser de alguma ajuda.

EDIT3: Resolvido !! Código abaixo.

 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) 

Para o PNG parecer estranho, veja o segundo post neste tópico

Apenas juntei isso para meu próprio projeto, demorei um pouco até perceber que os bytes e a imagem são atributos separados no Django ImageField, essa solução funcionou para mim com o 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") 

Isso funcionou para mim experimentá-lo. Eu estou usando o Django 2.0.6 e o ​​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 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 

Este exemplo mostra como fazer upload de imagem após o redimensionamento da imagem. Altere o pixel de new_img, o que você quiser.