django, creating qrcode.png file for a new instance in signals.py - django

I will like to create a qrcode.png file and store it in my S3 bucket once a instance gets created in models.Model (using signals.py). With my code below I managed to create the qrcode__png file in my local base directory but it does not upload to my external S3 bucket or save in my models.Model class. HELP! Thanks.
Signals.py
#receiver(post_save,sender=inventory)
def create__url_qr(sender, instance, created, **kwargs):
if created == True:
qr = qrcode.QRCode(
version=1,
box_size=10,
border=5)
qr.add_data('/MyProperty/item/'+str(instance.id))
qr.make(fit=True)
im=qr.make_image(fill_color="black", back_color="white")
im.save('qrcode___'+str(instance.id)+'.png', format="png")
print('XXXX')
print(im)
print('XXXX')
inst1 = inventory.objects.get(pk=instance.pk)
inst1.qr_code = im
inst1.save()

You can read the file you wrote into a django.core.files.File object and then save that object to your model
import qrcode
from django.core.files import File
qr = qrcode.QRCode(
version=1,
box_size=10,
border=5,
)
qr.add_data('/MyProperty/item/'+str(instance.id))
qr.make(fit=True)
im = qr.make_image(fill_color="black", back_color="white")
im.save('qrcode___'+str(instance.id)+'.png', format="png")
qr_file = File(open('qrcode___'+str(instance.id)+'.png'))
inst1 = inventory.objects.get(pk=instance.pk)
inst1.qr_code.save('qrcode___'+str(instance.id)+'.png', qr_file)
But you don't have to save it locally first, you could just write it to BytesIO and then save it
import qrcode
from io import BytesIO
from django.core.files import File
qr = qrcode.QRCode(
version=1,
box_size=10,
border=5,
)
qr.add_data('/MyProperty/item/'+str(instance.id))
qr.make(fit=True)
im = qr.make_image(fill_color="black", back_color="white")
stream = BytesIO()
im.save(stream, format="png")
qr_file = File(stream)
inst1 = inventory.objects.get(pk=instance.pk)
inst1.qr_code.save('qrcode___'+str(instance.id)+'.png', qr_file)

Related

How to save or upload an image from LOCAL directory to ImageField of database object in DJANGO

I was trying to create some products in ecommerce project in django and i had the data file ready and just wanted to loop throw the data and save to the database with Product.objects.create(image='', ...) but i couldnt upload the images from local directory to database!
I tried these ways:
1
with open('IMAGE_PATH', 'rb') as f:
image = f.read()
Product.objects.create(image=image)
2
image = open('IMAGE_PATH', 'rb')
Product.objects.create(image=image)
3
module_dir = dir_path = os.path.dirname(os.path.realpath(__file__))
for p in products:
file_path = os.path.join(module_dir, p['image'])
Product.objects.create()
product.image.save(
file_path,
File(open(file_path, 'rb'))
)
product.save()
none worked for me.
After some searching, I got the answer.
the code to use would be like this:
from django.core.files import File
for p in products:
product = Product.objects.create()
FILE_PATH = p['image']
local_file = open(f'./APP_NAME/{FILE_PATH}', "rb")
djangofile = File(local_file)
product.image.save('FILE_NAME.jpg', djangofile)
local_file.close()
from django.core.files import File
import urllib
result = urllib.urlretrieve(image_url) # image_url is a URL to an image
model_instance.photo.save(
os.path.basename(self.url),
File(open(result[0], 'rb'))
)
self.save()
Got the answer from here

How to save pillow processed image in already existing Django object

I have created a object model as below
from django.db import models
# Create your models here.
class ImageModel(models.Model):
image = models.ImageField(upload_to='images/')
editedImg = models.ImageField(upload_to='images/')
def delete(self, *args, **kwargs):
self.image.delete()
self.editedImg.delete()
super().delete(*args, **kwargs)
And here is what i am trying to do in a function
from django.shortcuts import render
from EditorApp.forms import ImageForm
from EditorApp.models import ImageModel
from django.http import HttpResponseRedirect
from PIL import Image
def edit_column(request):
codArr = request.POST.getlist('codArr[]')
imgs = ImageModel.objects.first()
orgImage = ImageModel.objects.first().image
orgImage = Image.open(orgImage)
croppedImg = orgImage.crop((int(codArr[0]), int(codArr[1]), int(codArr[2]), int(codArr[3])))
# croppedImg.show()
# imgs.editedImg = croppedImg
# imgs.save()
return HttpResponseRedirect("/editing/")
What i am trying to do is the codArr consists of coordinates of top(x, y) and bottom(x, y) in the array form(Which is not an issue and is tested(croppedImg.show() showed the desired cropped image) and handled and used to crop the image). Image crop is working fine. But what i am trying to do is to save the cropped image in editedImg of the model used above. The above commented one is what i tried but throw a error AttributeError: _committed
As i have not used any name for image in model as its not required.
Kindly help please, Would be very thankfull.
you should do it like this:
from io import BytesIO
from api.models import ProductPicture
from django.core import files
codArr = request.POST.getlist('codArr[]')
img_obj = ImageModel.objects.first()
orgImage = img_obj.image
orgImage = Image.open(orgImage)
croppedImg = orgImage.crop((int(codArr[0]), int(codArr[1]), int(codArr[2]), int(codArr[3])))
thumb_io = BytesIO() # create a BytesIO object
croppedImg.save(thumb_io, 'png')
editedImg = files.File(thumb_io, name=file_name)
img_obj.editedImg = editedImg
img_obj.save()
You can use Python's context manager to open the image and save it to the desired storage in that case I'm using the images dir.
Pillow will crop the image and image.save() will save it to the filesystem and after that, you can add it to Django's ImageField and save it into the DB.
The context manager takes care of the file opening and closing, Pillow
takes care of the image, and Django takes care of the DB.
from PIL import Image
with Image.open(orgImage) as image:
file_name = image.filename # Can be replaced by orgImage filename
cropped_path = f"images/croped-{file_name}"
# The crop method from the Image module takes four coordinates as input.
# The right can also be represented as (left+width)
# and lower can be represented as (upper+height).
(left, upper, right, lower) = (20, 20, 100, 100)
# Here the image "image" is cropped and assigned to new variable im_crop
im_crop = image.crop((left, upper, right, lower))
im_crop.save(cropped_path)
imgs.editedImg = cropped_path
imgs.save()
Pillow's reference

how to unit test file upload in django

In my django app, I have a view which accomplishes file upload.The core snippet is like this
...
if (request.method == 'POST'):
if request.FILES.has_key('file'):
file = request.FILES['file']
with open(settings.destfolder+'/%s' % file.name, 'wb+') as dest:
for chunk in file.chunks():
dest.write(chunk)
I would like to unit test the view.I am planning to test the happy path as well as the fail path..ie,the case where the request.FILES has no key 'file' , case where request.FILES['file'] has None..
How do I set up the post data for the happy path?Can somebody tell me?
I used to do the same with open('some_file.txt') as fp: but then I needed images, videos and other real files in the repo and also I was testing a part of a Django core component that is well tested, so currently this is what I have been doing:
from django.core.files.uploadedfile import SimpleUploadedFile
def test_upload_video(self):
video = SimpleUploadedFile("file.mp4", "file_content", content_type="video/mp4")
self.client.post(reverse('app:some_view'), {'video': video})
# some important assertions ...
In Python 3.5+ you need to use bytes object instead of str. Change "file_content" to b"file_content"
It's been working fine, SimpleUploadedFile creates an InMemoryFile that behaves like a regular upload and you can pick the name, content and content type.
From Django docs on Client.post:
Submitting files is a special case. To POST a file, you need only
provide the file field name as a key, and a file handle to the file
you wish to upload as a value. For example:
c = Client()
with open('wishlist.doc') as fp:
c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
I recommend you to take a look at Django RequestFactory. It's the best way to mock data provided in the request.
Said that, I found several flaws in your code.
"unit" testing means to test just one "unit" of functionality. So,
if you want to test that view you'd be testing the view, and the file
system, ergo, not really unit test. To make this point more clear. If
you run that test, and the view works fine, but you don't have
permissions to save that file, your test would fail because of that.
Other important thing is test speed. If you're doing something like
TDD the speed of execution of your tests is really important.
Accessing any I/O is not a good idea.
So, I recommend you to refactor your view to use a function like:
def upload_file_to_location(request, location=None): # Can use the default configured
And do some mocking on that. You can use Python Mock.
PS: You could also use Django Test Client But that would mean that you're adding another thing more to test, because that client make use of Sessions, middlewares, etc. Nothing similar to Unit Testing.
I do something like this for my own event related application but you should have more than enough code to get on with your own use case
import tempfile, csv, os
class UploadPaperTest(TestCase):
def generate_file(self):
try:
myfile = open('test.csv', 'wb')
wr = csv.writer(myfile)
wr.writerow(('Paper ID','Paper Title', 'Authors'))
wr.writerow(('1','Title1', 'Author1'))
wr.writerow(('2','Title2', 'Author2'))
wr.writerow(('3','Title3', 'Author3'))
finally:
myfile.close()
return myfile
def setUp(self):
self.user = create_fuser()
self.profile = ProfileFactory(user=self.user)
self.event = EventFactory()
self.client = Client()
self.module = ModuleFactory()
self.event_module = EventModule.objects.get_or_create(event=self.event,
module=self.module)[0]
add_to_admin(self.event, self.user)
def test_paper_upload(self):
response = self.client.login(username=self.user.email, password='foz')
self.assertTrue(response)
myfile = self.generate_file()
file_path = myfile.name
f = open(file_path, "r")
url = reverse('registration_upload_papers', args=[self.event.slug])
# post wrong data type
post_data = {'uploaded_file': i}
response = self.client.post(url, post_data)
self.assertContains(response, 'File type is not supported.')
post_data['uploaded_file'] = f
response = self.client.post(url, post_data)
import_file = SubmissionImportFile.objects.all()[0]
self.assertEqual(SubmissionImportFile.objects.all().count(), 1)
#self.assertEqual(import_file.uploaded_file.name, 'files/registration/{0}'.format(file_path))
os.remove(myfile.name)
file_path = import_file.uploaded_file.path
os.remove(file_path)
I did something like that :
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.core.files import File
from django.utils.six import BytesIO
from .forms import UploadImageForm
from PIL import Image
from io import StringIO
def create_image(storage, filename, size=(100, 100), image_mode='RGB', image_format='PNG'):
"""
Generate a test image, returning the filename that it was saved as.
If ``storage`` is ``None``, the BytesIO containing the image data
will be passed instead.
"""
data = BytesIO()
Image.new(image_mode, size).save(data, image_format)
data.seek(0)
if not storage:
return data
image_file = ContentFile(data.read())
return storage.save(filename, image_file)
class UploadImageTests(TestCase):
def setUp(self):
super(UploadImageTests, self).setUp()
def test_valid_form(self):
'''
valid post data should redirect
The expected behavior is to show the image
'''
url = reverse('image')
avatar = create_image(None, 'avatar.png')
avatar_file = SimpleUploadedFile('front.png', avatar.getvalue())
data = {'image': avatar_file}
response = self.client.post(url, data, follow=True)
image_src = response.context.get('image_src')
self.assertEquals(response.status_code, 200)
self.assertTrue(image_src)
self.assertTemplateUsed('content_upload/result_image.html')
create_image function will create image so you don't need to give static path of image.
Note : You can update code as per you code.
This code for Python 3.6.
from rest_framework.test import force_authenticate
from rest_framework.test import APIRequestFactory
factory = APIRequestFactory()
user = User.objects.get(username='#####')
view = <your_view_name>.as_view()
with open('<file_name>.pdf', 'rb') as fp:
request=factory.post('<url_path>',{'file_name':fp})
force_authenticate(request, user)
response = view(request)
As mentioned in Django's official documentation:
Submitting files is a special case. To POST a file, you need only provide the file field name as a key, and a file handle to the file you wish to upload as a value. For example:
c = Client()
with open('wishlist.doc') as fp:
c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
More Information: How to check if the file is passed as an argument to some function?
While testing, sometimes we want to make sure that the file is passed as an argument to some function.
e.g.
...
class AnyView(CreateView):
...
def post(self, request, *args, **kwargs):
attachment = request.FILES['attachment']
# pass the file as an argument
my_function(attachment)
...
In tests, use Python's mock something like this:
# Mock 'my_function' and then check the following:
response = do_a_post_request()
self.assertEqual(mock_my_function.call_count, 1)
self.assertEqual(
mock_my_function.call_args,
call(response.wsgi_request.FILES['attachment']),
)
if you want to add other data with file upload then follow the below method
file = open('path/to/file.txt', 'r', encoding='utf-8')
data = {
'file_name_to_receive_on_backend': file,
'param1': 1,
'param2': 2,
.
.
}
response = self.client.post("/url/to/view", data, format='multipart')`
The only file_name_to_receive_on_backend will be received as a file other params received normally as post paramas.
In Django 1.7 there's an issue with the TestCase wich can be resolved by using open(filepath, 'rb') but when using the test client we have no control over it. I think it's probably best to ensure file.read() returns always bytes.
source: https://code.djangoproject.com/ticket/23912, by KevinEtienne
Without rb option, a TypeError is raised:
TypeError: sequence item 4: expected bytes, bytearray, or an object with the buffer interface, str found
from django.test import Client
from requests import Response
client = Client()
with open(template_path, 'rb') as f:
file = SimpleUploadedFile('Name of the django file', f.read())
response: Response = client.post(url, format='multipart', data={'file': file})
Hope this helps.
Very handy solution with mock
from django.test import TestCase, override_settings
#use your own client request factory
from my_framework.test import APIClient
from django.core.files import File
import tempfile
from pathlib import Path
import mock
image_mock = mock.MagicMock(spec=File)
image_mock.name = 'image.png' # or smt else
class MyTest(TestCase):
# I assume we want to put this file in storage
# so to avoid putting garbage in our MEDIA_ROOT
# we're using temporary storage for test purposes
#override_settings(MEDIA_ROOT=Path(tempfile.gettempdir()))
def test_send_file(self):
client = APIClient()
client.post(
'/endpoint/'
{'file':image_mock},
format="multipart"
)
I am using Python==3.8.2 , Django==3.0.4, djangorestframework==3.11.0
I tried self.client.post but got a Resolver404 exception.
Following worked for me:
import requests
upload_url='www.some.com/oaisjdoasjd' # your url to upload
with open('/home/xyz/video1.webm', 'rb') as video_file:
# if it was a text file we would perhaps do
# file = video_file.read()
response_upload = requests.put(
upload_url,
data=video_file,
headers={'content-type': 'video/webm'}
)
I am using django rest framework and I had to test the upload of multiple files.
I finally get it by using format="multipart" in my APIClient.post request.
from rest_framework.test import APIClient
...
self.client = APIClient()
with open('./photo.jpg', 'rb') as fp:
resp = self.client.post('/upload/',
{'images': [fp]},
format="multipart")
I am using GraphQL, upload for test:
with open('test.jpg', 'rb') as fp:
response = self.client.execute(query, variables, data={'image': [fp]})
code in class mutation
#classmethod
def mutate(cls, root, info, **kwargs):
if image := info.context.FILES.get("image", None):
kwargs["image"] = image
TestingMainModel.objects.get_or_create(
id=kwargs["id"],
defaults=kwargs
)

Django: Copy FileFields

I'm trying to copy a file using a hardlink, where the file is stored as a Django FileField. I'd like to use a hardlink to save space and copy time (no changes are expected to be made to the original file or copy). However, I'm getting some odd errors when I try to call new_file.save() from the snippet below.
AttributeError: 'file' object has no attribute '_committed'
My thinking is that after making the hardlink, I can just open the linked file and store it to the Django new File instance's FileFile. Am I missing a step here or something?
models.py
class File(models.Model):
stored_file = models.FileField()
elsewhere.py
import os
original_file = File.objects.get(id=1)
original_file_path = original_file.file.path
new_file = File()
new_file_path = '/path/to/new/file'
os.makedirs(os.path.realpath(os.path.dirname(new_file_path)))
os.link(original_file_path, new_file_path)
new_file.stored_file = file(new_file_path)
new_file.save()
There is no need to create hardlink, just duplicate the file holder:
new_file = File(stored_file=original_file.stored_file)
new_file.save()
update
If you want to specify file to FileField or ImageField, you could simply
new_file = File(stored_file=new_file_path)
# or
new_file = File()
new_file.stored_file = new_file_path
# or
from django.core.files.base import File
# from django.core.files.images import ImageFile # for ImageField
new_file.stored_file = File(new_file_path)
the field accepts path in basestring or File() instance, the code in your question uses file() and hence is not accepted.
I think I solved this issue, but not sure why it works. I wrapped the file object in a "DjangoFile" class (I imported as DjangoFile to avoid clashing with my previously defined File model).
from django.core.files.base import File as DjangoFile
...
new_file.stored_file = DjangoFile(file(new_file_path))
new_file.save()
This approached seemed to save the file OK.

copy file from one model to another

I have 2 simple models:
class UploadImage(models.Model):
Image = models.ImageField(upload_to="temp/")
class RealImage(models.Model):
Image = models.ImageField(upload_to="real/")
And one form
class RealImageForm(ModelForm):
class Meta:
model = RealImage
I need to save file from UploadImage into RealImage. How could i do this.
Below code doesn't work
realform.Image=UploadImage.objects.get(id=image_id).Image
realform.save()
Tnx for help.
Inspired by Gerard's solution I came up with the following code:
from django.core.files.base import ContentFile
#...
class Example(models.Model):
file = models.FileField()
def duplicate(self):
"""
Duplicating this object including copying the file
"""
new_example = Example()
new_file = ContentFile(self.file.read())
new_file.name = self.file.name
new_example.file = new_file
new_example.save()
This will actually go as far as renaming the file by adding a "_1" to the filename so that both the original file and this new copy of the file can exist on disk at the same time.
Although this is late, but I would tackle this problem thus,
class UploadImage(models.Model):
Image = models.ImageField(upload_to="temp/")
# i need to delete the temp uploaded file from the file system when i delete this model
# from the database
def delete(self, using=None):
name = self.Image.name
# i ensure that the database record is deleted first before deleting the uploaded
# file from the filesystem.
super(UploadImage, self).delete(using)
self.Image.storage.delete(name)
class RealImage(models.Model):
Image = models.ImageField(upload_to="real/")
# in my view or where ever I want to do the copying i'll do this
import os
from django.core.files import File
uploaded_image = UploadImage.objects.get(id=image_id).Image
real_image = RealImage()
real_image.Image = File(uploaded_image, uploaded_image.name)
real_image.save()
uploaded_image.close()
uploaded_image.delete()
If I were using a model form to handle the process, i'll just do
# django model forms provides a reference to the associated model via the instance property
form.instance.Image = File(uploaded_image, os.path.basename(uploaded_image.path))
form.save()
uploaded_image.close()
uploaded_image.delete()
note that I ensure the uploaded_image file is closed because calling real_image.save() will open the file and read its content. That is handled by what ever storage system is used by the ImageField instance
Try doing that without using a form. Without knowing the exact error that you are getting, I can only speculate that the form's clean() method is raising an error because of a mismatch in the upload_to parameter.
Which brings me to my next point, if you are trying to copy the image from 'temp/' to 'real/', you will have to do a some file handling to move the file yourself (easier if you have PIL):
import Image
from django.conf import settings
u = UploadImage.objects.get(id=image_id)
im = Image.open(settings.MEDIA_ROOT + str(u.Image))
newpath = 'real/' + str(u.Image).split('/', 1)[1]
im.save(settings.MEDIA_ROOT + newpath)
r = RealImage.objects.create(Image=newpath)
Hope that helped...
I had the same problem and solved it like this, hope it helps anybody:
# models.py
class A(models.Model):
# other fields...
attachment = FileField(upload_to='a')
class B(models.Model):
# other fields...
attachment = FileField(upload_to='b')
# views.py or any file you need the code in
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from django.core.files.base import ContentFile
from main.models import A, B
obj1 = A.objects.get(pk=1)
# You and either copy the file to an existent object
obj2 = B.objects.get(pk=2)
# or create a new instance
obj2 = B(**some_params)
tmp_file = StringIO(obj1.attachment.read())
tmp_file = ContentFile(tmp_file.getvalue())
url = obj1.attachment.url.split('.')
ext = url.pop(-1)
name = url.pop(-1).split('/')[-1] # I have my files in a remote Storage, you can omit the split if it doesn't help you
tmp_file.name = '.'.join([name, ext])
obj2.attachment = tmp_file
# Remember to save you instance
obj2.save()
Update Gerard's Solution to handle it in a generic way:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from django.core.files.base import ContentFile
init_str = "src_obj." + src_field_name + ".read()"
file_name_str = "src_obj." + src_field_name + ".name"
try:
tmp_file = StringIO(eval(str(init_str)))
tmp_file = ContentFile(tmp_file.getvalue())
tmp_file.name = os.path.basename(eval(file_name_str))
except AttributeError:
tmp_file = None
if tmp_file:
try:
dest_obj.__dict__[dest_field_name] = tmp_file
dest_obj.save()
except KeyError:
pass
Variable's Used:
src_obj = source attachment object.
src_field_name = source attachment object's FileField Name.
dest_obj = destination attachment object.
dest_field_name = destination attachment object's FileField Name.