Resize image according to other model fields - python

How do I use the value of other model fields in some field? I want to resize an image to the width and height specified in their respective fields:
class MyImage(models.Model):
name = models.CharField(max_length=128, blank=False)
width = models.PositiveIntegerField(blank=False) #--->
height = models.PositiveIntegerField(blank=False) #--->
new_image = ProcessedImageField(processors=[ResizeToFill(500, 500)], #<---
format='JPEG',
options={'quality': 60})
I have to to swap first 500 into value from variable width and and second 500 into variable height. The field ProcessedImageField is from a package django-imagekit that I am using.

You can create an exchange function, where through ORM you take all your objects and walk through them exchanging the necessary values
Django ORM query: how to swap value of a attribute?

As described in the django-imagekit documentation on Specs That Change you can write a separate class for the ImageSpec provide the processors attribute as a property to dynamically get the proccessors, register this image spec and provide it's id to your ProcessedImageField to achieve your purpose:
from django.db import models
from imagekit import ImageSpec, register
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill
from imagekit.utils import get_field_info
class YourImage(ImageSpec):
format = 'JPEG'
options = {'quality': 60}
#property
def processors(self):
model, field_name = get_field_info(self.source)
return [ResizeToFill(model.width, model.height)]
register.generator('your_app:your_image', YourImage)
class MyImage(models.Model):
name = models.CharField(max_length=128, blank=False)
width = models.PositiveIntegerField(blank=False)
height = models.PositiveIntegerField(blank=False)
new_image = ProcessedImageField(spec_id='your_app:your_image')

Related

Django : Error: 'duplicate key value violates unique constraint'

I have to save images with s3 when creating a new object for my website.
I have a code to save my images that I use in my model's save method.
When I use the django administration panel, I can easily create my object without any error.
It also works well when I send my data without adding an image.
But when I try to create my object throught my view, I get this error : Error: 'duplicate key value violates unique constraint « store_shop_pkey »' DETAIL: key (id)=(37) already exists
I think that in my serializer's create method, I try to save my object twice : once for the image and once for the rest of my object's keys.
I don't know how to resolve this issue.
It works well with PUT method.
Here is my code :
models.py :
class Shop(models.Model):
name = models.CharField(max_length=255)
category = models.ForeignKey(ShopCategory, on_delete=models.SET_NULL, null=True, blank=True)
description = models.TextField(blank=True, null=True)
path = models.CharField(max_length=255, unique=True, null=True, blank=True) # Set a null and blank = True for serializer
mustBeLogged = models.BooleanField(default=False)
deliveries = models.FloatField(validators=[MinValueValidator(0),], default=7)
message = models.TextField(null=True, blank=True)
banner = models.ImageField(null=True, blank=True)
def save(self, *args, **kwargs):
try:
"""If we want to update"""
this = Shop.objects.get(id=self.id)
if self.banner:
image_resize(self.banner, 300, 300)
if this.banner != self.banner:
this.banner.delete(save=False)
else:
this.banner.delete(save=False)
except:
"""If we want to create a shop"""
if self.banner:
image_resize(self.banner, 300, 300)
super().save(*args, **kwargs)
def delete(self):
self.banner.delete(save=False)
super().delete()
def __str__(self):
return self.name
utils.py :
def image_resize(image, width, height):
# Open the image using Pillow
img = Image.open(image)
# check if either the width or height is greater than the max
if img.width > width or img.height > height:
output_size = (width, height)
# Create a new resized “thumbnail” version of the image with Pillow
img.thumbnail(output_size)
# Find the file name of the image
img_filename = Path(image.file.name).name
# Spilt the filename on “.” to get the file extension only
img_suffix = Path(image.file.name).name.split(".")[-1]
# Use the file extension to determine the file type from the image_types dictionary
img_format = image_types[img_suffix]
# Save the resized image into the buffer, noting the correct file type
buffer = BytesIO()
img.save(buffer, format=img_format)
# Wrap the buffer in File object
file_object = File(buffer)
# Save the new resized file as usual, which will save to S3 using django-storages
image.save(img_filename, file_object)
views.py :
class ShopList(ShopListView):
"""Create shop"""
def post(self, request):
"""For admin to create shop"""
serializer = MakeShopSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
serializers.py :
class MakeShopSerializer(serializers.ModelSerializer):
class Meta:
model = Shop
fields = '__all__'
def create(self, validated_data):
# validated_data.pop('path')
path = validated_data["name"].replace(" ", "-").lower()
path = unidecode.unidecode(path)
unique = False
while unique == False:
if len(Shop.objects.filter(path=path)) == 0:
unique = True
else:
# Generate a random string
char = "abcdefghijklmnopqrstuvwxyz"
path += "-{}".format("".join(random.sample(char, 5)))
shop = Shop.objects.create(**validated_data, path=path)
shop.save()
return shop
def update(self, instance, validated_data):
#You will have path in validated_data
#And you may have to check if the values are null
return super(MakeShopSerializer, self).update(instance, validated_data)
Finally, this is the data that I send :
Thank you by advance for your help
The next line is possible to have a problem with primary key:
shop = Shop.objects.create(**validated_data, path=path)
you may try to put every single attribute one by one, like this:
shop = Shop.objects.create(
name=validated_data['name'],
category=validated_data['category'],
...
path=path)
If it didn't resolve your problem, please post more about your error from terminal
Your model's unique key is path, so inside create function of your serializer,
shop = Shop.objects.create(**validated_data, path=path), tries creating a new model.
Consider the second instance you trying to add has the same path as the previous instance, in that case, you get this error since path should be unique and you are trying to add another model with the same value which DBMS rejects.
One of the things you can try is,
create or update the instance. If you are fine with updating the instance when your second instance has the same path as the previous one, then use
Shop.objects.update_or_create(path=path,
defaults=validated_data)
else
try adding unique constraints if your model cannot stand unique just by using the path field.
django documentation

How to resize an ImageField image before saving it in python Django model

I am trying to implement a Django ImageField class function for resizing images however I am not certain where this function accepts my new image dimensions
Running on Linux and Python 3.7
I've had a look at this documentation, but can't quite make sense of it:
https://docs.djangoproject.com/en/1.11/_modules/django/db/models/fields/files/#ImageField
I'd really appreciate it if someone can show me an example of how to use this function.
EDIT
I haven't successfully managed to resize my image dimensions yet, which is what i am trying to achieve. How may I resize this image before I save it given it is being fetched by ImageField (I found the update_dimensions_fields class function for ImageField however i cant figure out how to use it)
class Posts(models.Model):
title = models.CharField(max_length=200, blank=True)
body = models.TextField(blank=True)
created_at = models.DateTimeField(default=datetime.datetime.now)
post_image = models.ImageField(upload_to=get_image_path, blank=True, null=True)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
# I would like to use the function beneath to resize my images before I save them to my database
self.post_image.update_dimension_fields(self, instance, force=False, *args, **kwargs)
super().save(*args, **kwargs) # Call the "real" save() method.
class Meta:
verbose_name_plural = "Posts"
You could use the django-resized library. It resizes images when uploaded and stores them for you.
Usage
from django_resized import ResizedImageField
class Posts(models.Model):
title = models.CharField(max_length=200, blank=True)
body = models.TextField(blank=True)
created_at = models.DateTimeField(default=datetime.datetime.now)
post_image = ResizedImageField(size=[500, 300], upload_to=get_image_path, blank=True, null=True)
def __str__(self):
return self.title
Options
size - max width and height, for example [640, 480]
crop - resize and crop. ['top', 'left'] - top left corner, ['middle', -
'center'] is center cropping, ['bottom', 'right'] - crop right bottom corner.
quality - quality of resized image 1..100
keep_meta - keep EXIF and other meta data, default True
force_format - force the format of the resized image, available formats are the one supported by pillow, default to None
**
This will work
**First of all install "PIL Fork" using 'pip install pillow
from PIL import Image
def __str__(self):
return self.title
def save(self, *args, **kwargs):
super(Posts, self).save(*args, **kwargs)
imag = Image.open(self.post_image.path)
if imag.width > 400 or imag.height> 300:
output_size = (400, 300)
imag.thumbnail(output_size)
imag.save(self.post_image.path)
class Meta:
verbose_name_plural = "Posts"
You can use this method to resize the image before saving it: (you need pip install pillow)
import os
from io import BytesIO
from PIL import Image as PilImage
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
def resize_uploaded_image(image, max_width, max_height):
size = (max_width, max_height)
# Uploaded file is in memory
if isinstance(image, InMemoryUploadedFile):
memory_image = BytesIO(image.read())
pil_image = PilImage.open(memory_image)
img_format = os.path.splitext(image.name)[1][1:].upper()
img_format = 'JPEG' if img_format == 'JPG' else img_format
if pil_image.width > max_width or pil_image.height > max_height:
pil_image.thumbnail(size)
new_image = BytesIO()
pil_image.save(new_image, format=img_format)
new_image = ContentFile(new_image.getvalue())
return InMemoryUploadedFile(new_image, None, image.name, image.content_type, None, None)
# Uploaded file is in disk
elif isinstance(image, TemporaryUploadedFile):
path = image.temporary_file_path()
pil_image = PilImage.open(path)
if pil_image.width > max_width or pil_image.height > max_height:
pil_image.thumbnail(size)
pil_image.save(path)
image.size = os.stat(path).st_size
return image
Then use it in the clean method of the image field in your form:
class ImageForm(forms.Form):
IMAGE_WIDTH = 450
IMAGE_HEIGHT = 450
image = forms.ImageField()
def clean_image(self):
image = self.cleaned_data.get('image')
image = resize_uploaded_image(image, self.IMAGE_WIDTH, self.IMAGE_HEIGHT)
return image
To understand how the resize_uploaded_image method works, you may read about how Django handles uploaded files in the docs, here and here.
You can use the django-imagekit library.
Installation
Install Pillow. (If you're using an ImageField in Django,
you should have already done this.)
pip install django-imagekit
Add 'imagekit' to your INSTALLED_APPS list in your project's settings.py
Models.py
from django.db import models
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
Pay attention to the source attribute of ImageSpecField which directs to the actual image field (avatar in this case) as ImageSpecField are virtual in nature, you can read more about it here
Using in your template
<img src="{{ profile.avatar_thumbnail.url }}" />

Sorting images by height/orientation

I made image gallery with grid view, but I don't like the way that rows look like - vertical photos disrupt everything. As I don't want to manually change images order I'm looking for a way to sort them automaticaly by image height or just image orientation, so vertical photos go to the bottom in one row.
Thats how my model in Django looks like:
class Photo(models.Model):
title = models.CharField(max_length=150)
image = models.ImageField()
description = models.TextField(blank=True)
category = models.IntegerField(choices=CATEGORIES)
published = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
Here is my grid_view:
def photos_grid(request):
global cat_list
photos = Photo.objects.order_by('published')
output = {'photos': photos, 'categories': cat_list,}
return render(request, 'photos/photos_grid.html', output)
I tried that (how to find height and width of image for FileField Django) way of getting image dimensions but I got
ValueError: invalid literal for int() with base 10: 'height'
every way I try to put it in my code. Other idea (by getting manually dimensions in views.py) works, but I can't put it together with my photos on the list so it get sorted.
You must include the height and width fields in your model, for example:
class Photo(models.Model):
image = models.ImageField(height_field='image_height', width_field='image_width')
image_height = models.IntegerField()
image_width = models.IntegerField()
...
After migrating your database, you can write the following:
Photo.objects.all().order_by('image_height')
Edit: If you need to access the orientation, add another field, such as:
class Photo(models.Model):
...
aspect_ratio = models.FloatField(blank=True, null=True)
then, override your save method to populate this field using height and width, i.e.:
class Photo(models.Model):
...
def save(self, **kwargs):
self.aspect_ratio = float(self.image_height) / float(self.image_width)
super(Photo, self).save(kwargs)
You can then order by your new field, such as:
Photo.objects.all().order_by('aspect_ratio')

Recursive Relationship with a Description in Django Models

My project involves sorting many images. As part of this sorting, I want to be able to manually (as the user) mark several images as duplicates of each other with a brief description of why each relationship was created. These relationships will not be defined at the time an image is loaded into Django, but at a later time after uploading all the images.
My question: How can I create an unlimited number of duplicates? Aka, how would I define that several images are all related to each other, and include a CharField description of why each relationship exists?
This is a django app and the code is from models.py.
Thank you.
from django.db import models
class tag(models.Model):
tag = models.CharField(max_length=60)
x = models.IntegerField(null=True)
y = models.IntegerField(null=True)
point = [x,y]
def __unicode__(self):
return self.tag
#...
class image(models.Model):
image = models.ImageField(upload_to='directory/')
title = models.CharField(max_length=60, blank=True, help_text="Descriptive image title")
tags = models.ManyToManyField(tag, blank=True, help_text="Searchable Keywords")
#...
##### HELP NEEDED HERE ##################
duplicates = [models.ManyToManyField('self', null=True), models.CharField(max_length=60)]
##########################################
def __unicode__(self):
return self.image.name
You'd have to go with an extra model for grouping those duplicates, because you want a description field with it. Something like
class DupeSeries(Model):
description = CharField(...)
members = ManyToManyField("image", related_name="dupes", ...)
Example usage:
img = image(title="foo!", image="/path/to/image.jpg")
dup_of_img = image(title="foo!dup", image="/path/to/dup/image.jpg")
img.save()
dup_of_img.save()
dupes_of_foo = DupeSeries(description="foo! lookalikes")
dupes_of_foo.members.add(img, dup_of_img)
# Notice how *img.dupes.all()* returns both image instances.
assert(list(img.dupes.all()) == [img, dup_of_img])

How can a django model field relate to multiple model?

suppose I have three model like this:
class video(models.Model):
name=models.CharField(max_length = 100)
class image(models.Model):
name=models.CharField(max_length = 100)
class comments(models.Model):
content=models.CharField(max_length = 100)
now i want to notic the user if their video or image get an comment
this is what i want
the message model:
class message(models.Model):
type=models.CharField(max_length = 100) # 'video' or 'image'
video_or_image=models.ForeignKey(video or image)
#the type is just a string to tell if the comment is about the video or image
#video_or_image need to be video foreignkey or image foreignkey depends on type
is it possible.
I currently work around this by two method
first:
class message(models.Model):
type = models.CharField(max_length = 100) # 'video' or 'image'
video_or_image_id = models.IntegerField(default = 1)
#
second
class message(models.Model):
type=models.CharField(max_length = 100) # 'video' or 'image'
video=models.ForeignKey(video)
image=models.ForeignKey(image)
# if the comment is about video just leave the image empty
if the one field to multiple model can not be done, then which my work around method is better, or help me with a better one!
You are looking for a GenericForeignKey.
This is also the way contrib.comments relates comments to commented items.

Categories

Resources