Say I have a model and form that support file uploads:
class Foo(Document):
name = StringField()
file = FileField()
class FooForm(Form):
name = CharField()
file = ImageField()
def save(self):
Foo(name=self.cleaned_data['name'], file=self.cleaned_data['file']).save()
When POSTing from the actual browser form.is_valid() returns True, so we can call save()
When I go to use FooForm to take a PIL Image (specifically a <PIL.Image._ImageCrop image mode=RGB size=656x677 at 0x10F6812D8>), is_valid() is False because form.errors says:
load a valid image. The file you uploaded was either not an image or a corrupted image.
Here's what I'm trying to do to save the form:
img = ... our PIL image ...
post = {'name': name}
file = {'file': img}
form = FooForm(post, file)
if form.is_valid():
form.save()
See what I'm doing wrong that's causing is_valid() to be False?
Edit: I think this issue is more about coercing PIL Image to something BaseForm's files parameter accepts.
This ended up being my solution to get the FooForm to properly validate. I'm sure there's a better method.
img = ... our PIL image ...
buffer = StringIO()
img.save(buffer, 'png')
buffer.seek(0)
image_file = SimpleUploadedFile('foo.png', buffer.read(), content_type="image/png")
buffer.close()
post = {'name': name}
file = {'file': image_file}
form = FooForm(post, file)
if form.is_valid():
form.save()
I would recommend changing the form initialisation to use a simple dictionary like this:
img = ... our PIL image ...
form = FooForm({'name': name, 'file': img})
if form.is_valid():
form.save()
Related
This code return a TypeError as expected str, bytes or os.PathLike object, not InMemoryUploadedFile
I don't know how to pass user data in the form of file and image to my code.py file for making changes to the original.
views.py
def home(request):
new_image = None
file = None
form = ScanForm()
if request.method == 'POST':
form = ScanForm(request.POST, request.FILES)
if form.is_valid():
image = request.FILES['image']
xml_file = request.FILES['xml_file']
new_image = code.create(image, code.search(
xml_file)[0], code.search(xml_file)[1])
form.save()
return render(request, 'app/home.html', {'form': form, 'new_image': new_image})
else:
form = ScanForm()
return render(request, 'app/home.html', {'form': form, 'new_image': new_image})
printing image and xml_file successfully prints out their names
forms.py
class ScanForm(forms.ModelForm):
class Meta:
model = Scan
fields = '__all__'
models.py
class Scan(models.Model):
image = models.ImageField(upload_to='images')
xml_file = models.FileField(upload_to='files')
processed_at = models.DateTimeField(auto_now_add=True)
description = models.CharField(max_length=500, null=True)
class Meta:
ordering = ['-processed_at']
def __str__(self):
return self.description
Here is the code for manipulation of image according to the data in the xml
code.py
def search(path):
new = []
object_names = []
object_values = []
txt = Path(path).read_text()
txt.strip()
names = et.fromstring(txt).findall('object')
for i in names:
object_names.append(i[0].text)
values = et.fromstring(txt).findall('object/bndbox')
for i in values:
for j in i:
object_values.append(int(j.text))
return object_names, object_values
def create(image, object_names, object_values):
img = cv.imread(image)
on = len(object_names)
ov = len(object_values)
for i in list(range(0, ov, on)):
cv.rectangle(img, (object_values[i], object_values[i+1]),
(object_values[i+2], object_values[i+3]), (0, 0, 255), thickness=5)
return img
This code.py works fine if tested by passing data manually using local folder.
Here is the Traceback:
Traceback image
pathlib.Path() handles file paths, not memory objects. request.FILES are the data attached to the POST request. During your handling of a POST request, you can validate this data and decide to save it to the server disk.
If you would like your image processing to read the file from the server disk, you have to save the new model instance first. You can then access the file's path on disk through the name attribute of the model's ImageField, see Using files in models.
If you want to handle the uploaded data before saving it to disk, you can read it as follows:
txt = request.FILES["xml_file"].read()
See UploadedFile.read()
models.py
class FileUpload(models.Model):
File_Name = models.CharField(max_length=255, blank=True)
File_path = models.FileField(upload_to='')
Description = models.CharField(max_length=255, blank=True)
Upload_Date = models.DateTimeField(auto_now_add=True)
forms.py
class FileUploadForm(forms.Form):
class Meta:
model = FileUpload
File_Name = forms.CharField(label="File Name",max_length=255)
Description = forms.CharField(label="Description", max_length=255)
I'm new in Django.I need help. How to upload images in the database and view those images? Thanks in advance!
here paths are stored in database and images are stored in a folder. But I don't need that. I want to save images and path to the database and I need to view that image. Please help!
views.py:
def uploadfile(request):
print('inside upload logic')
if request.method == 'POST':
form = FileUploadForm(request.POST, request.FILES)
if form.is_valid():
# ImageUpload(request.FILES['File_Name'])
myfile = request.FILES['File_Name']
fs = FileSystemStorage()
filename = fs.save(myfile.name, myfile)
uploaded_file_url = fs.url(filename)
newdoc = FileUpload(File_Name=myfile.name, File_path=uploaded_file_url, Description=request.POST['Description'])
newdoc.save()
#return HttpResponse("File uploaded successfuly")
return render(request, 'Login/fileupload.html')
else:
form = FileUploadForm()
return render(request, 'Login/fileupload.html', {
'form': form
})
You normally shouldn't store the image data in your database. If you need to upload and store images, use the ImageField or FileField and follow the instructions from the docs to save the image. The only thing you need to do is:
form = FileUploadForm(request.POST, request.FILES)
if form.is_valid():
uploaded_file = FileUpload(
File_path=request.FILES['File_path'], # or whatever you've called the file input
File_name=form.cleaned_data['File_Name'],
Description=form.cleaned_data['Description'])
uploaded_file.save()
It would be easier to use a ModelForm in your case, then you only need to save the form:
if form.is_valid():
form.save()
This will automatically save the file in the correct way. No need to do the saving manually like you are doing.
To view the image is as simple as this:
file_upload = FileUpload.objects.get(id=34)
file_url = file_upload.File_path.url # url relative to `MEDIA_ROOT`
# or in a template
{% load static %}
{% get_media_prefix %}{{ file_upload.File_path.url }}
If you really need to store the images as binary blobs to your database (but beware that it almost never makes sense to do so), use Django's BinaryField, as described here.
This also means you will have to handle transforming the image back and forth from a binary blob to a proper image file. Consider also storing the content type (image/jpg or image/png or image/webp) since you will need that to properly re-create the file.
I'm using PIL to resize an uploaded photo before saving. Note that I'm using formsets to upload the pictures. I'm using BytesIO to open the file. At the last step, I get the error - '_io.BytesIO' object has no attribute 'name'. Why is this?
def fsbo_create_listing(request):
PhotoFormSet = formset_factory(OwnerListingPhotoForm, extra=15)
if request.method == 'POST':
form = OwnerListingForm(request.POST)
photo_formset = PhotoFormSet(request.POST, request.FILES)
if form.is_valid() and photo_formset.is_valid():
form.instance.user = request.user
form.save()
for i in photo_formset:
if i.instance.pk and i.instance.photo == '':
i.instance.delete()
elif i.cleaned_data:
temp = i.save(commit=False)
temp.listing = form.instance
temp.save() # Where the error happens
def clean_photo(self):
picture = self.cleaned_data.get('photo')
# I had to import ImageFieldFile. If picture is already uploaded, picture would still be retrieved as ImageFieldFile. The following line checks the variable type of `picture` to determine whether the cleaning should proceed.
if type(picture) != ImageFieldFile:
image_field = self.cleaned_data.get('photo')
image_file = BytesIO(image_field.read())
image = Image.open(image_file)
image = ImageOps.fit(image, (512,512,), Image.ANTIALIAS)
image_file = BytesIO()
image.save(image_file, 'JPEG', quality=90)
image_field.file = image_file
#if picture._size > 2*1024*1024:
#raise ValidationError("Image file too large. Max size is 2MB.")
return picture
class OwnerListingPhoto(models.Model):
listing = models.ForeignKey(OwnerListing, on_delete=models.CASCADE, related_name='owner_listing_photo')
photo = models.ImageField(upload_to=owner_listing_folder_name)
The issue is that new versions of Django default to using MemoryFileUploadHandler, which doesn't create a temporary file, and therefore there is no file "name." See related Django ticket.
You'll probably have to modify your code a bit to make this work, but you can at least start getting the name property by setting:
FILE_UPLOAD_HANDLERS = [
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
]
In your settings.py file.
You may find the code I've used to solve almost the exact same issue as helpful.
def clean_logo_file(self):
logo_file_field = self.cleaned_data.get('logo_file')
if logo_file_field:
try:
logo_file = logo_file_field.file
with Image.open(logo_file_field.file.name) as image:
image.thumbnail((512, 512), Image.ANTIALIAS)
image.save(logo_file, format=image.format)
logo_file_field.file = logo_file
return logo_file_field
except IOError:
logger.exception("Error during image resize.")
Additional information on upload handlers.
If file is bigger than 2.5mb (2621440 bytes) - Django will
use TemporaryFileUploadHandler.
Otherwise Django will use MemoryFileUploadHandler.
You can change FILE_UPLOAD_MAX_MEMORY_SIZE (doc) in settings.py
Or change FILE_UPLOAD_HANDLERS (doc) as Nostalg.io mentioned above.
My example with Django Rest Framework serializers:
Broken code:
# models.py
class ImageModel(Model):
image = models.ImageField(upload_to='images/', null=False, blank=False)
# serializers.py
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = ImageModel
fields = ["id", "image"]
read_only_fields = ["id"]
def validate_image(self, user_img):
img = Image.open(user_img)
... # process image here
img_io = io.BytesIO()
img.save(img_io, format='JPEG', quality=100)
filename = "%s.jpg" % user_img.name.split('.')[0]
user_img.name = "%s.jpg" % user_img.name.split('.')[0]
user_img.file = img_io # BAD IDEA!!!
# This overrides django's tempfile._TemporaryFileWrapper() with _io.BytesIO() !!!
...
return user_img # if picture bigger than 2.5mb -> gives an error!
Fixed code:
#settings.py
FILE_UPLOAD_HANDLERS = [
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
]
# serializers.py
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = ImageModel
fields = ["id", "image"]
read_only_fields = ["id"]
def validate_image(self, user_img):
img = Image.open(user_img)
... # process image here
# override old TemporaryFile image with edited image
path_to_tmp = user_img.file.name
new_filename = "%s.jpeg" % user_img.name.split('.')[0]
# set new image name
img.save(path_to_tmp, format='JPEG', quality=100)
user_img.name = new_filename
...
return user_img # no errors more :)
It might be more rational to process image by rewriting save() method in models.py, but I convert images in serializers.py because of handly ValidationError() :)
I am working on a project that will allow the user to upload image. The uploaded image will later on displayed and be passed to another form. To do this, I need to get the image url of the uploaded image. Here is my code:
def inputImage(request):
if request.method == 'POST':
form = ImageDetailsForm(request.POST, request.FILES)
if form.is_valid():
form.save()
message = "The image was successfully uploaded!"
imageName = str(request.FILES['image'].name)
imageURL = settings.MEDIA_URL + "/" + imageName
return render(request,'success.html', {'message': message, 'image': imageURL})
The code is working, however a problem would occur if the user uploads a file with an existing filename at the storage. To avoid conflict, Django automatically renames the file but the line
imageName = str(request.FILES['image'].name)
only returns the original filename of the uploaded image. I have also tried to use
imageName = str(form.cleaned_data['image'].name)
but still no changes. It returns "/media//1.png" instead of "/media//1_0rnKMaT.png"
Any ideas on how to get the URL of the current upload in Django?
Edit:
here is my models.py:
class ImageDetails(models.Model):
image = models.ImageField(null=True)
and my forms.py
class ImageDetailsForm(forms.ModelForm):
class Meta:
model = ImageDetails
fields= ('image')
widgets = {
'status': forms.HiddenInput(),
}
You can access the saved model instance through form.instance, so you should be able to get the name of the saved file with form.instance.image_field_name.name where image_field_name is the name of the image field in your model.
I am writing my first django app that uses the ImageField and I am having
difficulty. The problem is that my images are not uploading. I have looked at
as many examples that I can find. And I'm not sure what's going wrong here.
I am trying to verify that my photos are uploading by looking in the location
of the upload_to directory. When the form is displayed in the web page the
correct upload file button is displayed. When I hit submit, the code below executes,
but no images are ever uploaded.
Based on the upload_to, I am expecting to see images uploaded to see images under either:
myproject/photos or myproject/media/photos correct?
Am I doing anything obvious wrong here? How do I get my images to upload?
--------------settings.py-------------
MEDIA_ROOT = '/home/me/django_projects/myproject/media/'
MEDIA_URL = '/media/'
--------------model.py-------------
class Person(models.Model):
lastName = models.CharField(max_length=20)
firstName = models.CharField(max_length=20)
image = models.ImageField(upload_to='photos', blank=True, null=True)
# save executes but no image is saved.
# Because images are uploaded along with a new entry, I needed a special
# work around to get the self.id
def save(self):
for field in self._meta.fields:
if field.name == 'image':
if self.id is not None:
#save image now
field.upload_to = 'photos/%d' % self.id
else:
#Save image later
saveImageLast = True
super(Customer, self).save() # should create self.id if not already
if saveImageLast == True:
field.upload_to = 'photos/%d' % self.id
super(Customer, self).save()
print "save complete" #prints, but no image ...?
--------------forms.py-------------
class PersonForm(ModelForm):
class Meta:
model = Person
fields = ( 'lastName', 'firstName', 'image' )
from django documentation, i think this can help (in the past this helped me):
Firstly, in order to upload files, you'll need to make sure that your
element correctly defines the enctype as "multipart/form-data"
<form enctype="multipart/form-data" method="post" action="/foo/">
In your view where you create an instance of the form with post data, ensure you have provided request.FILES
form = PersonForm(request.POST, request.FILES)
This is a bit late, but 'upload_to' is not a method. It's an attribute that represents the relative path from your MEDIA_ROOT. If you want to save an image in the folder 'photos' with the filename self.id, you need to create a function at the top of your model class. For instance:
class Person(models.Model):
def file_path(instance):
return '/'.join(['photos', instance.id])
image = models.ImageField(upload_to=file_path)
Then when you are actually saving your image you would call:
person = Person(firstName='hey', lasteName='joe')
person.image.save(ImageObject.name, ImageObject)
More on the image file objects here.
More on upload_to here.