I wanted to upload files with limited extension on imagefield. So, how can I validate my image field for jpg,bmp and gif only.
Also, which extension does image field take in by default?
This is how I do it:
from django.utils.image import Image
# settings.py
# ALLOWED_UPLOAD_IMAGES = ('gif', 'bmp', 'jpeg')
class ImageForm(forms.Form):
image = forms.ImageField()
def clean_image(self):
image = self.cleaned_data["image"]
# This won't raise an exception since it was validated by ImageField.
im = Image.open(image)
if im.format.lower() not in settings.ALLOWED_UPLOAD_IMAGES:
raise forms.ValidationError(_("Unsupported file format. Supported formats are %s."
% ", ".join(settings.ALLOWED_UPLOAD_IMAGES)))
image.seek(0)
return image
Works for ModelForm as well.
Unit test:
from StringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test.utils import override_settings
from django.test import TestCase
class ImageFormTest(TestCase):
def test_image_upload(self):
"""
Image upload
"""
content = 'GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00' \
'\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;'
image = StringIO(content)
image.name = 'image.gif'
image.content_type = 'image/gif'
files = {'image': SimpleUploadedFile(image.name, image.read()), }
form = ImageForm(data={}, files=files)
self.assertTrue(form.is_valid())
#override_settings(ALLOWED_UPLOAD_IMAGES=['png', ])
def test_image_upload_not_allowed_format(self):
image = StringIO('GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00'
'\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;')
image.name = 'image'
files = {'image': SimpleUploadedFile(image.name, image.read()), }
form = ImageForm(data={}, files=files)
self.assertFalse(form.is_valid())
Pillow will allow a bunch of image formats
Related
I want to upload a CSV file in the admin that adds information to a model. In case you can live with a normal form and not a extension of change_form.html and not overwriting response_change (I tried that fist) this is how it can be done:
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
class StoreAdminForm(forms.ModelForm):
## add an extra field:
upfile = forms.FileField()
class Meta:
model = Store
fields = "__all__"
def clean(self):
cleaned_data = super(StoreAdminForm, self).clean()
if "upfile" in self.changed_data:
### file validation on file type etc here ..
## file is valid:
## next lines deal with the InMemoryUploadedFile Type
path = settings.MEDIA_ROOT.joinpath("___tmp___")
tmp = default_storage.save(path, ContentFile(cleaned_data["upfile"].read()))
## open file
with open(tmp_file, encoding = "utf8") as f:
data = f.readlines()
## ...
I hope this helps everyone, I lost some time with not knowing how to deal with the InMemoryUploadedFile types.
Goodmorning,
I'm using Pillow to resize and save an image within a Django model called Post. The image is retrieved from the imagefield, get's a check to see if it is RGB or not, if not the image is converted to RGB.
Finally, I'm creating a thumbnail from the original image and try to save this in the MEDIA_ROOT.
Even though the image get's uploaded, it doesn't seem to convert the image to jpeg.
I followed the tutorial here Django 2+ edit images with Pillow and I'm trying to fit it to my needs.
What am I missing here?
models.py
import os
from django.core.validators import RegexValidator
from django.db import models
from django.utils import timezone
from PIL import Image
from django.conf import settings
from django.db.models.signals import post_save
class Post(models.Model):
# Custom validators
title_validator_specialchar = RegexValidator(regex=r'^[\s*\d*a-zA-Z]{5,60}$', message="The title can't contain any special characters")
category = models.ForeignKey('Category',default=1, on_delete=models.SET_NULL, null=True)
type = models.CharField(max_length=20)
title = models.CharField(max_length=200, validators=[title_validator_specialchar])
content = models.TextField(max_length=2000)
image = models.ImageField(upload_to='%Y/%m/%d/', blank=True)
created_at = models.DateTimeField(editable=False)
updated_at = models.DateTimeField(default=timezone.now)
def save(self, *args, **kwargs):
#On save, update timestamp date created
if not self.id:
self.created_at = timezone.now()
self.updated_at = timezone.now()
return super(Post, self).save(*args, **kwargs)
def __str__(self):
return self.title
def resize_image(instance, **kwargs):
if instance.image:
# we are opening image with Pillow
img = Image.open(instance.image)
# convert image to RGB
if img.mode not in ('L', 'RGB'):
img = img.convert('RGB')
# img.size is tuple with values (width, height)
if img.size[0] > 320 or img.size[1] > 640:
# Using thumbnail to resize image but keep aspect ratio
img.thumbnail((320, 640), Image.ANTIALIAS)
# saving to original place
# instance.image.name is in %Y/%m/%d/<name> format
output = os.path.join(settings.MEDIA_ROOT, instance.image.name)
img.save(output, "JPEG")
# Connect the signal with our model
post_save.connect(resize_image, Post)
A signal handler receives the sender as the first argument, which in case of the post_save signal is the model class, not the model instance.
So the argument instance of resize_image() should be named sender and does not contains what you want. Here is how you get the actual instance:
def resize_image(sender, **kwargs):
instance = kwargs.get('instance')
if instance and instance.image:
...
Since I couldn't find what I needed to get this to work, I decided to use django-imagekit.
I'm using the ProcessedImageField and the ResizeToFill processor on my model;
models.py
image = ProcessedImageField(upload_to='%Y/%m/%d/', processors=[ResizeToFill(384, 216)], format='JPEG', options={'quality': 60}, blank=True)
This happens beacuse You explicitly call img to save in the mediaroot but instance.image stays still. So django will save that image also. So I think You have to change the instance.image attribute rather than calling img to save. For that you have to use django InMemorUploadedFile
import io
from django.core.files.uploadedfile import InMemoryUploadedFile
import sys
if instance.image:
# we are opening image with Pillow
img = Image.open(instance.image)
# convert image to RGB
if img.mode not in ('L', 'RGB'):
img = img.convert('RGB')
# img.size is tuple with values (width, height)
if img.size[0] > 320 or img.size[1] > 640:
# Using thumbnail to resize image but keep aspect ratio
img.thumbnail((320, 640), Image.ANTIALIAS)
# saving to original place by changing instance.image. django will save it
#automatically in mediaroot
img_io = io.BytesIO()
img.save(img_io, "JPEG")
instance.image = InMemoryUploadedFile(img_io, 'ImageField', 'image.jpeg',
'image/jpeg',sys.getsizeof(img_io), None )
we can't pass the Image object directly to inastance.image because it raises an error.
so we have to convert img to InMemoryUploadedFile object.
I'm trying to save images which have been passed to me as Base64 encoded text into a Django Imagefield.
But it seems to not be saving correctly. The database reports all my images are stored as "" when it should report them as a filename for example:
"template_images/template_folders/myImage.png"
The code that's trying to save my images is as follows:
elif model_field.get_internal_type() == "ImageField" or model_field.get_internal_type() == "FileField": # Convert files from base64 back to a file.
if field_elt.text is not None:
setattr(instance, model_field.name, File(b64decode(field_elt.text)))
After reading this answer, I got this to work:
from base64 import b64decode
from django.core.files.base import ContentFile
image_data = b64decode(b64_text)
my_model_instance.cool_image_field = ContentFile(image_data, 'whatup.png')
my_model_instance.save()
Therefore, I suggest you change your code to:
from django.core.files.base import ContentFile
# Your other code...
elif model_field.get_internal_type() == "ImageField" or model_field.get_internal_type() == "FileField": # Convert files from base64 back to a file.
if field_elt.text is not None:
image_data = b64decode(field_elt.text)
setattr(instance, model_field.name, ContentFile(image_data, 'myImage.png'))
Then, assuming your ImageField is defined with the upload_to argument set to template_images/template_folders/, you should see the file save down to YOUR_MEDIA_URL/template_images/template_folders/myImage.png
Another good approach based on this SO answer: https://stackoverflow.com/a/28036805/6143656 tried it and tested in django 1.10
I made a function for decoded base64 file.
def decode_base64_file(data):
def get_file_extension(file_name, decoded_file):
import imghdr
extension = imghdr.what(file_name, decoded_file)
extension = "jpg" if extension == "jpeg" else extension
return extension
from django.core.files.base import ContentFile
import base64
import six
import uuid
# Check if this is a base64 string
if isinstance(data, six.string_types):
# Check if the base64 string is in the "data:" format
if 'data:' in data and ';base64,' in data:
# Break out the header from the base64 content
header, data = data.split(';base64,')
# Try to decode the file. Return validation error if it fails.
try:
decoded_file = base64.b64decode(data)
except TypeError:
TypeError('invalid_image')
# Generate file name:
file_name = str(uuid.uuid4())[:12] # 12 characters are more than enough.
# Get the file name extension:
file_extension = get_file_extension(file_name, decoded_file)
complete_file_name = "%s.%s" % (file_name, file_extension, )
return ContentFile(decoded_file, name=complete_file_name)
Then you can call the function
import decode_base64_file
p = Post(content='My Picture', image=decode_based64_file(your_base64_file))
p.save()
I guess this is the cleanest and shortest way to do this.
Here is how you can handle a Base64 encoded image file in a post request at the Django-based (drf also) API end which saves it as an ImageField.
Let say you have a Model as follows:
Class MyImageModel(models.Model):
image = models.ImageField(upload_to = 'geo_entity_pic')
data=model.CharField()
So the Corresponding Serializer would be as follows:
from drf_extra_fields.fields import Base64ImageField
Class MyImageModelSerializer(serializers.ModelSerializers):
image=Base64ImageField()
class meta:
model=MyImageModel
fields= ('data','image')
def create(self, validated_data):
image=validated_data.pop('image')
data=validated_data.pop('data')
return MyImageModel.objects.create(data=data,image=image)
The corresponding View can be as follows:
elif request.method == 'POST':
serializer = MyImageModelSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)
Notice In the Serializer I have used Implementation of Base64ImageField provided in the module django-extra-field
To install this module run the command
pip install pip install django-extra-fields
Import the same and Done!
Send (via post method) your image as an Base64 encoded String in JSON object along with any other data you have.
Summary: The problem is that I cannot access the thumbnail image from a nested class in the serialized model and I don't know how to expose it in the JSON response.
Description: I am trying to serialize my Thing model to JSON and it works, in a fashion. I get the following in my JSON response:
// JSON response
[
{
pk: 1
model: "base.thing"
fields: {
active: true
created_at: "2011-04-13 07:18:05"
num_views: 1
file: "things/5216868239_b53b8d5e80_b.jpg"
title: "ding ding ding"
}
}
]
I just started using django-imagekit to manipulate the Thing images to thumbnail sizes and it works in normal usage, i.e. running 'thing.thumbnail_image.url' in a template returns the correct url to the thumbnail image.
The sandbox code I'm playing around with:
# base/models.py
from django.db import models
from imagekit.models import ImageModel
class Thing(ImageModel):
title = models.CharField(max_length=30)
file = models.ImageField(upload_to='things')
created_at = models.DateTimeField(auto_now_add=True)
active = models.BooleanField()
num_views = models.PositiveIntegerField(editable=False, default=0)
def __unicode__(self):
return self.title
class IKOptions:
spec_module = 'base.specs'
image_field = 'file'
save_count_as = 'num_views'
# base/views.py
from django.views.generic.base import View
from django.views.generic.list import MultipleObjectTemplateResponseMixin, ListView
from base.models import Thing
from django import http
from django.utils import simplejson as json
from utils import JsonResponse
class JSONResponseMixin(object):
def render_to_response(self, context):
"Returns a JSON response containing 'context' as payload"
return self.get_json_response(context)
def get_json_response(self, content, **httpresponse_kwargs):
"Construct an `HttpResponse` object."
return JsonResponse(content['thing_list']) # I can't serialize the content object by itself
class ThingsView(JSONResponseMixin, ListView):
model = Thing
context_object_name = "thing_list"
template_name = "base/thing_list.html"
def render_to_response(self, context):
if self.request.GET.get('format', 'html') == 'json':
return JSONResponseMixin.render_to_response(self, context)
else:
return ListView.render_to_response(self, context)
# base/specs.py
from imagekit.specs import ImageSpec
from imagekit import processors
class ResizeThumb(processors.Resize):
width = 100
height = 75
crop = True
class ResizeDisplay(processors.Resize):
width = 600
class EnchanceThumb(processors.Adjustment):
contrast = 1.2
sharpness = 1.1
class Thumbnail(ImageSpec):
access_as = 'thumbnail_image'
pre_cache = True
processors = [ResizeThumb, EnchanceThumb]
class Display(ImageSpec):
increment_count = True
processors = [ResizeDisplay]
# utils.py
from django.core.serializers import json, serialize
from django.db.models.query import QuerySet
from django.http import HttpResponse
from django.utils import simplejson
class JsonResponse(HttpResponse):
def __init__(self, object):
if isinstance(object, QuerySet):
content = serialize('json', object)
else:
content = simplejson.dumps(
object, indent=2, cls=json.DjangoJSONEncoder,
ensure_ascii=False)
super(JsonResponse, self).__init__(
content, content_type='application/json')
I appreciate any help with this, it's been blocking me for a day.
Best regards-
Using versions:
Django 1.3
PIL 1.1.7
django-imagekit 0.3.6
simplejson 2.1.3
I couldn't figure out how to expose the inner class over JSON so I chose an alternative way of doing it and dropping django-imagekit and manually resizing the image to a thumbnail and saving it in the model's save() function.
im = ImageOps.fit(im, (sizes['thumbnail']['width'], sizes['thumbnail']['height'],), method=Image.ANTIALIAS)
thumbname = filename + "_" + str(sizes['thumbnail']['width']) + "x" + str(sizes['thumbnail']['height']) + ".jpg"
im.save(fullpath + '/' + thumbname)
It's a less than clean approach but it works for now.
I think that you are looking for a result like <img src="{{ product.photo.url }}"/> which is loading the full path to your images like "/media/images/2013/photo_01.jpg". With JSON you have only the path which is stored in you database model.
What you can do is add some prefix in the template like <img src="media/{{ product.photo.url }}"/>.
How can I easily resize an image after it has been uploaded in Django? I am using Django 1.0.2 and I've installed PIL.
I was thinking about overriding the save() method of the Model to resize it, but I don't really know how to start out and override it.
Can someone point me in the right direction? Thanks :-)
#Guðmundur H:
This won't work because the django-stdimage package does not work on Windows :-(
I recommend using StdImageField from django-stdimage, it should handle all the dirty work for you. It's easy to use, you just specify the dimensions of the resized image in the field definition:
class MyModel(models.Model):
image = StdImageField(upload_to='path/to/img', size=(640, 480))
Check out the docs — it can do thumbnails also.
You should use a method to handle the uploaded file, as demonstrated in the Django documentation.
In this method, you could concatenate the chunks in a variable (rather than writing them to disk directly), create a PIL Image from that variable, resize the image and save it to disk.
In PIL, you should look at Image.fromstring and Image.resize.
I use this code to handle uploaded images, resize them on memory(whithout saving them permanently on disk), and then saving the thumb on a Django ImageField.
Hope can help.
def handle_uploaded_image(i):
import StringIO
from PIL import Image, ImageOps
import os
from django.core.files import File
# read image from InMemoryUploadedFile
image_str = “”
for c in i.chunks():
image_str += c
# create PIL Image instance
imagefile = StringIO.StringIO(image_str)
image = Image.open(imagefile)
# if not RGB, convert
if image.mode not in (“L”, “RGB”):
image = image.convert(“RGB”)
#define file output dimensions (ex 60x60)
x = 130
y = 130
#get orginal image ratio
img_ratio = float(image.size[0]) / image.size[1]
# resize but constrain proportions?
if x==0.0:
x = y * img_ratio
elif y==0.0:
y = x / img_ratio
# output file ratio
resize_ratio = float(x) / y
x = int(x); y = int(y)
# get output with and height to do the first crop
if(img_ratio > resize_ratio):
output_width = x * image.size[1] / y
output_height = image.size[1]
originX = image.size[0] / 2 - output_width / 2
originY = 0
else:
output_width = image.size[0]
output_height = y * image.size[0] / x
originX = 0
originY = image.size[1] / 2 - output_height / 2
#crop
cropBox = (originX, originY, originX + output_width, originY + output_height)
image = image.crop(cropBox)
# resize (doing a thumb)
image.thumbnail([x, y], Image.ANTIALIAS)
# re-initialize imageFile and set a hash (unique filename)
imagefile = StringIO.StringIO()
filename = hashlib.md5(imagefile.getvalue()).hexdigest()+’.jpg’
#save to disk
imagefile = open(os.path.join(‘/tmp’,filename), ‘w’)
image.save(imagefile,’JPEG’, quality=90)
imagefile = open(os.path.join(‘/tmp’,filename), ‘r’)
content = File(imagefile)
return (filename, content)
#views.py
form = YourModelForm(request.POST, request.FILES, instance=profile)
if form.is_valid():
ob = form.save(commit=False)
try:
t = handle_uploaded_image(request.FILES[‘icon’])
ob.image.save(t[0],t[1])
except KeyError:
ob.save()
I highly recommend the sorl-thumbnail app for handling image resizing easily and transparently. It goes in every single Django project I start.
Here is a complete solution for ya using a form. I used admin views for this:
class MyInventoryItemForm(forms.ModelForm):
class Meta:
model = InventoryItem
exclude = ['thumbnail', 'price', 'active']
def clean_photo(self):
import StringIO
image_field = self.cleaned_data['photo']
photo_new = StringIO.StringIO(image_field.read())
try:
from PIL import Image, ImageOps
except ImportError:
import Image
import ImageOps
image = Image.open(photo_new)
# ImageOps compatible mode
if image.mode not in ("L", "RGB"):
image = image.convert("RGB")
image.thumbnail((200, 200), Image.ANTIALIAS)
image_file = StringIO.StringIO()
image.save(image_file, 'png')
image_field.file = image_file
return image_field
My inventory model looks like this:
class InventoryItem(models.Model):
class Meta:
ordering = ['name']
verbose_name_plural = "Items"
def get_absolute_url(self):
return "/products/{0}/".format(self.slug)
def get_file_path(instance, filename):
if InventoryItem.objects.filter(pk=instance.pk):
cur_inventory = InventoryItem.objects.get(pk=instance.pk)
if cur_inventory.photo:
old_filename = str(cur_inventory.photo)
os.remove(os.path.join(MEDIA_ROOT, old_filename))
ext = filename.split('.')[-1]
filename = "{0}.{1}".format(uuid.uuid4(), ext)
return os.path.join('inventory', filename)
#return os.path.join(filename)
def admin_image(self):
return '<img height="50px" src="{0}/{1}"/>'.format(MEDIA_URL, self.photo)
admin_image.allow_tags = True
photo = models.ImageField(_('Image'), upload_to=get_file_path, storage=fs, blank=False, null=False)
thumbnail = models.ImageField(_('Thumbnail'), upload_to="thumbnails/", storage=fs, blank=True, null=True)
....
I ended overwriting the save function of the model instead to save the photo and a thumb instead of just resizing the photo:
def save(self):
# Save this photo instance first
super(InventoryItem, self).save()
from PIL import Image
from cStringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
# Set our max thumbnail size in a tuple (max width, max height)
THUMBNAIL_SIZE = (200, 200)
# Open original photo which we want to thumbnail using PIL's Image object
image = Image.open(os.path.join(MEDIA_ROOT, self.photo.name))
if image.mode not in ('L', 'RGB'):
image = image.convert('RGB')
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
# Save the thumbnail
temp_handle = StringIO()
image.save(temp_handle, 'png') # image stored to stringIO
temp_handle.seek(0) # sets position of file to 0
# Save to the thumbnail field
suf = SimpleUploadedFile(os.path.split(self.photo.name)[-1],
temp_handle.read(), content_type='image/png') # reads in the file to save it
self.thumbnail.save(suf.name+'.png', suf, save=False)
#Save this photo instance again to save the thumbnail
super(InventoryItem, self).save()
Both work great though depending on what you want to do :)
I know this is old, but for anybody stumbling upon it, there is a package, django-thumbs at Django-thumbs - Easy powerful thumbnails for Django integrated with StorageBackend, which automatically generates thumbnails in sizes you specify, or none if you don't. You then call the thumbnail you want with the dimensions you want.
For instance, if you want an image to have thumbnails of 64x64 and 128x128, you simply import thumbs.models.ImageWithThumbsField, and use it in place of ImageField. Add a parameter sizes=((64,64),(128,128)) to the field definition, then from your template you can call:
{{ ClassName.field_name.url_64x64 }}
and
{{ ClassName.field_name.url_128x128 }}
to display the thumbnails. Voila! All the work is done in this package for you.
If you are using Django Rest Framework, this might of use:
First define function to compress and resize image
def compress_image(photo):
# start compressing image
image_temporary = Image.open(photo)
output_io_stream = BytesIO()
image_temporary.thumbnail((1250, 1250), Image.ANTIALIAS)
# change orientation if necessary
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
exif = dict(image_temporary._getexif().items())
# noinspection PyUnboundLocalVariable
if exif.get(orientation) == 3:
image_temporary = image_temporary.rotate(180, expand=True)
elif exif.get(orientation) == 6:
image_temporary = image_temporary.rotate(270, expand=True)
elif exif.get(orientation) == 8:
image_temporary = image_temporary.rotate(90, expand=True)
# saving output
image_temporary.save(output_io_stream, format='JPEG', quality=75, optimize=True, progressive=True)
output_io_stream.seek(0)
photo = InMemoryUploadedFile(output_io_stream, 'ImageField', "%s.jpg" % photo.name.split('.')[0],
'image/jpeg', getsizeof(output_io_stream), None)
return photo
Second, now you can use the function in Serializers:
class SomeSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
# сжимаем рисунок
if 'photo' in validated_data:
validated_data.update({'photo': compress_image(validated_data['photo'])})
return super(SomeSerializer, self).update(instance, validated_data)
def create(self, validated_data):
# сжимаем рисунок
if 'photo' in validated_data:
validated_data.update({'photo': compress_image(validated_data['photo'])})
return super(SomeSerializer, self).create(validated_data)