PIL to Django ImageField - python

I try to create an image from an url and save it in my django model. If the first part works fine, I do not know how to associate the generated file to my object.
This is my function to generate the image file:
def get_remote_image(image_url, merchant_product_path):
im = None
name = ''
r = requests.get(image_url, stream=True)
if r.status_code == 200:
name = urlparse(image_url).path.split('/')[-1]
full_path = os.path.join(settings.MEDIA_ROOT, merchant_product_path)
if not os.path.exists(full_path):
os.makedirs(full_path)
im = Image.open(r.raw)
if im.mode != "RGB":
im = im.convert("RGB")
im.thumbnail((500, 500), Image.ANTIALIAS)
im.save(full_path + name, 'JPEG')
return {'im': im, 'name': name}
And now, the part to associate this file to my object:
i = get_remote_image(row['pict'], m.get_products_media_path())
obj, created = ProductLine.objects.update_or_create(
...
...
...
)
if i['im'] is not None:
try:
obj.main_picture.save(
i['name'],
ContentFile(i['im']),
save=True)
except TypeError:
continue
This code works but unfortunately, mu pictures are created in the correct folder, objects are created/update but each one has no picture file :(
Can someone tell me what's wrong ?

I've finally found a solution:
def get_remote_image(image_url):
im = None
name = ''
r = requests.get(image_url, stream=True)
if r.status_code == 200:
name = urlparse(image_url).path.split('/')[-1]
i = Image.open(r.raw)
buffer = BytesIO()
if i.mode != "RGB":
i = i.convert("RGB")
i.thumbnail((500, 500), Image.ANTIALIAS)
i.save(buffer, format='JPEG')
im = InMemoryUploadedFile(
buffer,
None,
name,
'image/jpeg',
buffer.tell(),
None)
return {'im': im, 'name': name}
and then:
obj, created = ProductLine.objects.update_or_create(
...
...
...
)
i = get_remote_image(row['pict'])
obj.main_picture.save(
os.path.join(m.get_products_image_path(), i['name']),
i['im'],
save=True)
Hope this will help some other users in this situation.

With a model like :
class ProductLine(models.Model):
name = models.CharField(max_length=250, null=True)
image = models.ImageField(null=True)
You can directly link the picture on your computer using is path instead of his binary content.
obj, created = ProductLine.objects.update_or_create(...)
obj.image.name = "/path/to/the/file"
obj.save()

Related

Set in memory image in PDF with Flask

I am just trying to draw an image to a pdf. This image is loaded from my mongodb database through an API.
I tried this solution but it raises the following error:
2022-09-14T21:05:56.561767+00:00 app[web.1]: remote_file = ImageReader(urlopen(img_url)).read()
2022-09-14T21:05:56.561767+00:00 app[web.1]: AttributeError: 'ImageReader' object has no attribute 'read'
Also, if you know a better way to set this image in the pdf let me know please.
This is the code I am using:
def get(self, usermail, dog_name):
client = pymongo.MongoClient('mongodb://uri')
filter={'UserMail':usermail,'title':dog_name}
result = client['db']['Todo'].find(
filter=filter
)
json_response = json.dumps(list(result), default=json_util.default)
dog = json.loads(json_response)
df = pd.DataFrame(dog).to_dict()
dog_img = df['DogImg'][0]
img_url = 'https://url' + dog_img
dog_age = df['Age'][0]
dog_desc = df['DogDescription'][0]
dog_name = df['title'][0]
dog_breed = df['Breed'][0]
buf = io.BytesIO()
c = canvas.Canvas(buf, pagesize=letter)
#c.drawImage(logo, 30, 700, width=50, height=50)
c.setFont("Helvetica", 20)
c.drawString(100, 720, dog_name)
buf.seek(0)
remote_file = ImageReader(urlopen(img_url)).read()
memory_file = io.BytesIO(remote_file)
buf.seek(0)
new_pdf = PdfFileReader(buf)
existing_pdf = PdfFileReader(memory_file)
pdf = PdfFileWriter()
page = existing_pdf.getPage(0)
page.mergePage(new_pdf.getPage(0))
pdf.addPage(page)
outputStream = open("destination.pdf", "wb")
pdf.write(outputStream)
outfile = io.BytesIO()
pdf.write(outfile)
outfile.seek(0)
return send_file(outfile, mimetype='application/pdf')
Try using the following
from reportlab.platypus import Image, SimpleDocTemplate
from flask import send_file
pdf_filename = 'original.pdf'
new_filename = "new_filename.pdf"
pdf_template = SimpleDocTemplate(pdf_filename, pagesize=letter,topMargin=0, bottomMargin=0, leftMargin=0, rightMargin=0)
story1 = []
img_url = 'https://url' + dog_img
# or from locally in project
# img_url = os.path.join(cwd, "dog_img")
img1 = Image(img_url, width=600, height=780)
story1.append(img1)
pdf_template.build(story1)
return send_file(pdf_filename, attachment_filename=new_filename, as_attachment=True)

Django HEIC to PNG conversion

I'm trying to convert heic to png, which I successfully achieved in Python with the help of Wand library. I also saved the file locally to see, if the convertion worked, and it did. The problem is that Django's serializer cant take in Wand image, and I have to convert it to InMemoryUploadedFile. Whatever I do, I still can't make the serializator to take in the converted image.
views.py update_post()
#api_view(['PUT'])
#permission_classes([IsAuthenticated])
def update_post(request, id):
image = request.FILES['image']
print(image.size, image.name, image.file,
image.content_type, image.field_name)
if image.content_type == 'image/heif':
img = Image(file=image)
img.format = 'png'
img_io = io.BytesIO()
img.save(file=img_io)
filename = image.name.replace('.heic', '.png')
img.save(filename=filename)
img_file = InMemoryUploadedFile(
img_io,
'image',
filename,
'image/png',
sys.getsizeof(img_io),
None)
print(img_file.size, img_file.name, img_file.file,
img_file.content_type, img_file.field_name)
image = img_file
#request.data['image'] = img_file
#request.FILES['image'] = img_file
# data = request.data
# print(img_file, image)
loggedin_user = request.user.username
post = Post.objects.get(id=id)
post_user = post.user
if (str(post_user) == str(loggedin_user)):
serializer = PostSerializer(
instance=post, data={'caption': request.data['caption'], 'image': image})
if serializer.is_valid():
print(serializer.validated_data)
serializer.save()
else:
print('achjo')
print(serializer.data)
return Response(status=status.HTTP_200_OK)

Python importing Vcard Photo

I'm trying to import a photo into my card using vobject. I have the image stored inside my static folder and the filename inside my database. I would like to import it in my VCard.
So far I have made this:
if request.method == "POST":
rowr = User.query.filter_by(user_id = user_id).first()
rowes = Profile.query.filter_by(user_id = user_id).first()
vcf_file_path = 'static/Contacto.vcf'
with open(vcf_file_path , 'w') as file_vcard:
vcard = vobject.vCard()
o = vcard.add('fn')
o.value = rowes.pname
if rowr.img_url != 'default.png':
o = vcard.add('PHOTO')
o.value = rowr.img_url
if rowes.pemail != None:
o = vcard.add('email')
o.type_param = 'INTERNET'
o.value = rowes.pemail
if rowes.pcellphone != None:
o = vcard.add('TEL')
o.type_param = 'Número Celular'
o.value = str(rowes.pcellphone)
if rowes.webpage != None:
o = vcard.add('url')
o.type_param = "Página Web"
o.value = rowes.webpage
if rowes.textarea != None:
o = vcard.add('note')
o.type_param = "note"
o.value = rowes.textarea
file_vcard.write(vcard.serialize())
But it's clearly no displaying the image in my VCard. I've tried writing the entire path but also didn't work. Thanks in advance
Looks like you're writing the string 'default.jpg' to the photo field, when actually you need to write the base64 encoded image. You could do this with a function like:
import base64
def b64_image(filename):
with open(filename, 'rb') as f:
b64 = base64.b64encode(f.read())
return b64.decode('utf-8')
Then call it like:
o = vcard.add('PHOTO;ENCODING=b;TYPE=image/jpeg')
o.value = b64_image('default.jpg')
Obviously pass that function a valid path to the image file, which in your case may be rowr.img_url.
I based this format on the
vCard Format Specification.
When tested this creates a line in the vcf file which looks like:
PHOTO;ENCODING=B;TYPE=IMAGE/JPEG:iVBORw0KGgoAAA...........
I opened this in Contacts v12.0 on OSx and it renders my sample image:

Thumbnail for ImageField in django model, override save

I am trying to create a thumbnail from ImageField and save it to my model. I am using this as reference: http://www.yilmazhuseyin.com/blog/dev/create-thumbnails-imagefield-django/
Files are created correctly, I get no errors but fields are not saved in model.
I think i could messed things up in save method. What am I doing wrong?
Here is my code:
def get_avatar_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'avatars/{0}/{1}'.format(instance.id, filename)
class User(AbstractUser):
# First Name and Last Name do not cover name patterns
# around the globe.
name = models.CharField(_('Name of User'), blank=True, max_length=255)
avatar = models.ImageField(upload_to=get_avatar_path, blank=True)
small_avatar = models.ImageField(upload_to=get_avatar_path,
blank=True)
def create_thumbnail(self):
# If there is no image associated with this.
# do not create thumbnail
if not self.avatar:
return
# Set our max thumbnail size in a tuple (max width, max height)
THUMBNAIL_SIZE = (200, 200)
DJANGO_TYPE = self.avatar.file.content_type
print DJANGO_TYPE
if DJANGO_TYPE == 'image/jpeg':
PIL_TYPE = 'jpeg'
FILE_EXTENSION = 'jpg'
elif DJANGO_TYPE == 'image/png':
PIL_TYPE = 'png'
FILE_EXTENSION = 'png'
elif DJANGO_TYPE == 'image/gif':
PIL_TYPE = 'gif'
FILE_EXTENSION = 'gif'
# Open original photo which we want to thumbnail using PIL's Image
image = Image.open(StringIO(self.avatar.read()))
# use our PIL Image object to create the thumbnail, which already
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
# Save the thumbnail
temp_handle = StringIO()
image.save(temp_handle, PIL_TYPE)
temp_handle.seek(0)
# Save image to a SimpleUploadedFile which can be saved into ImageField
print os.path.split(self.avatar.name)[-1]
suf = SimpleUploadedFile(os.path.split(self.avatar.name)[-1],
temp_handle.read(), content_type=DJANGO_TYPE)
# Save SimpleUploadedFile into image field
print os.path.splitext(suf.name)[0]
self.small_avatar.save(
'%s_thumbnail.%s' % (os.path.splitext(suf.name)[0], FILE_EXTENSION),
suf, save=False)
def save(self, *args, **kwargs):
self.create_thumbnail()
super(User, self).save()
Thanks for feedback. Sorry for newbie problems, I am a beginner developer.
Problem seems to be resolved by invalidating caches, restarting IDE and services.
In my opinion code above should be persisted as some of solutions on the web are outdated.

Django: changing image size and upload to S3

I have inherited a Django Project and we have moved images to S3
One of the models is a typical user profile
class Profile(UUIDBase):
first_name = models.CharField(_("First Name"), max_length=20)
last_name = models.CharField(_("Last Name"), max_length=20, null=True)
profile_image = models.ImageField(
_("Profile Image"),
upload_to=profile_image_name,
max_length=254,
blank=True,
null=True
)
profile_image_thumb = models.ImageField(
_("Profile Image Thumbnail"),
upload_to=profile_image_name,
max_length=254,
blank=True,
null=True
)
... other fields
Where profile_image_name is a function:
def profile_image_name(instance, filename):
if filename:
target_dir = 'uploads/profile_img/'
_, ext = filename.rsplit('.', 1)
filename = str(instance.uid) + '.' + ext
return '/'.join([target_dir, filename])
I have a bit of code that worked:
#shared_task
def resize_image(image_path, dim_x, append_str='_resized', **kwargs):
'''
resize any image_obj while maintaining aspect ratio
'''
orig = storage.open(image_path, 'r')
im = Image.open(orig, mode='r')
new_y = (float(dim_x) * float(im.height)) / float(im.width)
new_im = im.resize((dim_x, int(new_y)), Image.ANTIALIAS)
img_path, img_name = path.split(image_path)
file_name, img_ext = img_name.rsplit('.', 1)
new_img_path = path.join(img_path, file_name + append_str + '.' + img_ext)
try:
new_f = storage.open(new_img_path, 'w')
except IOError as e:
logger.critical("Caught IOError in {}, {}".format(__file__, e))
ravenclient.captureException()
return None
try:
new_im.save(new_f)
except IOError as e:
logger.critical("Caught IOError in {}, {}".format(__file__, e))
ravenclient.captureException()
return None
except Exception as e:
logger.critical("Caught unhandled exception in {}. {}".format(
__file__, e)
)
ravenclient.captureException()
return None
im.close()
new_im.close()
new_f.close()
return new_img_path
Which is called from a post_save signal handler :
#receiver(post_save, sender=Profile, dispatch_uid='resize_profile_image')
def resize_profile_image(sender, instance=None, created=False, **kwargs):
if created:
if instance.profile_image:
width, height = image_dimensions(instance.profile_image.name)
print(width, height)
if width > MAX_WIDTH:
result = resize_image.delay(instance.profile_image.name, MAX_WIDTH)
instance.profile_image.name = result.get()
if width > THUMB_WIDTH:
result = resize_image.delay(
instance.profile_image.name,
THUMB_WIDTH,
append_str='_thumb'
)
instance.profile_image_thumb.name = result.get()
try:
instance.save()
except Exception as e:
log.critical("Unhandled exception in {}, {}".format(__name__, e))
ravenclient.captureException()
The intent is to take uploaded images and resize them 1) to the max width that a mobile device can display and 2) to a 50 pixel thumbnail for use in the mobile app.
When I look on S3, I do not see my resized images or thumbnails. Yet the unit tests (which are thorough) don't give any errors.
When I get the image dimensions:
def image_dimensions(image_path):
f = storage.open(image_path, 'r')
im = Image.open(f, 'r')
height = im.height
width = im.width
im.close()
f.close()
return (width, height)
There is no problem accessing the object's ImageField. I get no error when I use default_storage to open the instance's profile_image. The PIL method
new_im = im.resize((dim_x, int(new_y)), Image.ANTIALIAS) does return a new instance of class 'PIL.Image.Image'.
In fact (pardon my verbosity)
This does not raise an error:
>>> u = User(email="root#groupon.com", password="sdfbskjfskjfskjdf")
>>> u.save()
>>> p = Profile(user=u, profile_image=create_image_file())
>>> p.save()
>>> from django.core.files.storage import default_storage as storage
>>> orig = storage.open(p.profile_image.name, 'r')
>>> orig
<S3BotoStorageFile: uploads/profile_img/b0fd4f00-cce6-4dd3-b514-4c46a801ab19.jpg>
>>> im = Image.open(orig, mode='r')
>>> im
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=5000x5000 at 0x10B8F1FD0>
>>> im.__class__
<class 'PIL.JpegImagePlugin.JpegImageFile'>
>>> dim_x = 500
>>> new_y = (float(dim_x) * float(im.height)) / float(im.width)
>>> new_im = im.resize((dim_x, int(new_y)), Image.ANTIALIAS)
>>> new_im.__class__
<class 'PIL.Image.Image'>
>>> img_path, img_name = path.split(p.profile_image.name)
>>> file_name, img_ext = img_name.rsplit('.', 1)
>>> append_str='_resized'
>>> new_img_path = path.join(img_path, file_name + append_str + '.' + img_ext)
>>> new_f = storage.open(new_img_path, 'w')
>>> new_f
<S3BotoStorageFile: uploads/profile_img/b0fd4f00-cce6-4dd3-b514-4c46a801ab19_resized.jpg>
>>> new_im.save(new_f) #### This does NOT create an S3 file!!!!
>>> im.close()
>>> new_im.close()
>>> new_f.close()
>>> p.save() uploads the new profile image to S3. I was expecting >>> new_im.save(new_f) to write the Image file to S3. But it does not.
Any insight or help is greatly appreciated and thank you for taking the time to look at this problem.
Edit ...
My settings:
AWS_STORAGE_BUCKET_NAME = 'testthis'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
MEDIAFILES_LOCATION = 'media'
MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, MEDIAFILES_LOCATION)
DEFAULT_FILE_STORAGE = 'custom_storages.MediaStorage'
Where custom_storage.py is
from django.conf import settings
from storages.backends.s3boto import S3BotoStorage
class MediaStorage(S3BotoStorage):
location = settings.MEDIAFILES_LOCATION
bucket_name = settings.AWS_STORAGE_BUCKET_NAME
I think the whole setup is crazy. I'd strongly suggest you look into using a library like django-versatileimagefield. The implementation would look like this:
from versatileimagefield.fields import VersatileImageField
from versatileimagefield.image_warmer import VersatileImageFieldWarmer
class Profile(UUIDBase):
first_name = models.CharField(_("First Name"), max_length=20)
last_name = models.CharField(_("Last Name"), max_length=20, null=True)
image = VersatileImageFied(upload_to='uploads/profile_img/', blank=True, null=True)
#receiver(models.signals.post_save, sender=Profile)
def warm_profile_image(sender, instance, **kwargs):
if instance.image:
VersatileImageFieldWarmer(instance_or_queryset=instance, rendition_key_set='profile_image', image_attr='image', verbose=True).warm()
And in your settings:
VERSATILEIMAGEFIELD_RENDITION_KEY_SETS = {
'profile_image': [
('cropped', 'crop__400x400'),
('thumbnail', 'thumbnail__20x20')
]
}
The Profile warmer creates representations saved to S3 no problem. You can access either the full image as profile.image or the different versions as profile.image.cropped and profile.image.thumbnail. The library even allows you to set up a point of interest so the cropping happens around a specific center point in the image.
Serializers if using DRF:
from versatileimagefield.serializers import VersatileImageFieldSerializer
class ProfileSerializer(serializers.ModelSerializer):
image = VersatileImageFieldSerializer(
sizes=[
('cropped', 'crop__400x400'),
('thumbnail', 'thumbnail__20x20')
],
required=False
)
... other fields and the Meta class
The issue seems to be related to PIL's JPEG library:
>>> u = User(email="root#groupon.com", password="sdfbskjfskjfskjdf")
>>> u.save()
>>> p = Profile(user=u, profile_image=create_image_file())
>>> p.save()
>>> from django.core.files.storage import default_storage as storage
>>> orig = storage.open(p.profile_image.name, 'r')
>>> orig
<S3BotoStorageFile: uploads/profile_img/b0fd4f00-cce6-4dd3-b514-4c46a801ab19.png>
>>> im = Image.open(orig, mode='r')
>>> im.__class__
<class 'PIL.PngImagePlugin.PngImageFile'>
>>> dim_x = 500
>>> new_y = (float(dim_x) * float(im.height)) / float(im.width)
>>> new_im = im.resize((dim_x, int(new_y)), Image.ANTIALIAS)
>>> new_im.__class__
<class 'PIL.Image.Image'>
>>> img_path, img_name = path.split(p.profile_image.name)
>>> file_name, img_ext = img_name.rsplit('.', 1)
>>> append_str='_resized'
>>> new_img_path = path.join(img_path, file_name + append_str + '.' + img_ext)
>>> new_f = storage.open(new_img_path, 'w')
>>> new_f
<S3BotoStorageFile: uploads/profile_img/b0fd4f00-cce6-4dd3-b514-4c46a801ab19_resized.png>
>>> new_im.save(new_f) #### This does create a file on S3!
>>> im.close()
>>> new_im.close()
>>> new_f.close()
The problem is in django-storages. S3BotoStorageFile's _file attribute is a SpooledTemporaryFile which works for png files, but not jpg files.
See: https://github.com/jschneier/django-storages/issues/155

Categories