django - How to copy actual image file from one model to another? - django

I want to copy images from one model to another within the project. Suppose these are my models:
class BackgroundImage(models.Model):
user = models.ForeignKey(User)
image = models.ImageField(upload_to=get_upload_file_name)
caption = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
class ProfilePicture(models.Model):
user = models.ForeignKey(User)
image = models.ImageField(upload_to=get_upload_file_name)
caption = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
#classmethod
def create_from_bg(cls, bg_img):
img = cls(user=bg_img.user, image=bg_img.image, caption=bg_img.caption+'_copy', pub_date=bg_img.pub_date)
img.save()
return img
For now, I can do these:
To get the user
>>>m = User.objects.get(username='m')
To get the user's profile picture set
>>>m_pro_set = m.profilepicture_set.all()
>>>m_pro_set
[<ProfilePicture: pro_mik>]
Get an image object from Background image of the user
>>>m_back_1 = m.backgroundimage_set.get(id=2)
>>>m_back_1
<BackgroundImage: bg_mik>
And then:
>>>profile_pic = ProfilePicture.create_from_bg(m_back_1)
Now when I check it, it does create a new instance.
>>>m_pro_set
[<ProfilePicture: pro_mik>,<ProfilePicture: bg_mik>]
But, if I check on the path, and even on the media folder, its the same image and not an actual copy of the image file.
>>>profile_pic.image
<ImageFileField: uploaded_files/1389904144_ken.jpg>
>>>m_back_1.image
<ImageFileField: uploaded_files/1389904144_ken.jpg>
How do I go about, to actually copy the original image file within the models? Any help will be much appreciated! Thank you.

so I know this question is pretty old, but hopefully this answer help someone...
My approach for doing this, uploading the photo to the proper path for the suggested model, is:
from django.core.files.base import ContentFile
picture_copy = ContentFile(original_instance.image.read())
new_picture_name = original_instance.image.name.split("/")[-1]
new_instance.image.save(new_picture_name, picture_copy)
Please check that in my case, the new name is just the same file name, but to be updated in the new model image field's path. In your case, depending on what you have inside "get_upload_file_name" it could leads to the same path again (since is used in both classes). Also you can create a new, random name.
Hope this helps someone =)

Best & Short solution is that
existing_instance = YourModel.objects.get(pk=1)
new_instance.image = existing_instance.image
It's work fine for me.

Related

Django ForeignKey on_delete = SET_NULL not working

I'm writing tests for several object models and the following test isn't giving the expected result:
def test_image_on_delete(self):
image = Image(id=self.TEST_IMAGE_ID, name=self.TEST_IMAGE_NAME, src=self.TEST_IMAGE_SRC)
image.save()
category = Category.objects.get(id=self.TEST_ID)
category.image = Image.objects.get(id=self.TEST_IMAGE_ID)
Image.objects.get(id=self.TEST_IMAGE_ID).delete()
self.assertIsNone(category.image)
Here is the image field in Category:
image = ForeignKey(Image, on_delete=SET_NULL, null=True)
I've confirmed that the Image is being deleted, but the assertIsNone fails because the Category's image field doesn't get cleared.
Is there something wrong with the way I've written this test or the ForeignKey field?
Note: I'm using TestCase from django.test
You can refresh your object from the database:
category.image.delete()
# Reload the object from the database.
category.refresh_from_db()
self.assertIsNone(category.image)
Documentation:
https://docs.djangoproject.com/en/3.2/ref/models/instances/#refreshing-objects-from-database
I figured out the issue. on_delete modifies the SAVED version of my Category object, so the copy of the variable I have locally isn't changed.
Here's my modified test that passes:
def test_image_on_delete(self):
image = Image(id=self.TEST_IMAGE_ID, name=self.TEST_IMAGE_NAME, src=self.TEST_IMAGE_SRC)
image.save()
category = Category.objects.get(id=self.TEST_ID)
category.image = Image.objects.get(id=self.TEST_IMAGE_ID)
self.assertIsNotNone(category.image)
category.save()
Image.objects.get(id=self.TEST_IMAGE_ID).delete()
category = Category.objects.get(id=self.TEST_ID)
self.assertIsNone(category.image)

How to delete individual files - Django 1.8

How does one delete individual files in Django?
Say we have a model ('Car') and some related images ('Photos')
If multiple images are uploaded for a car, how does one delete a particular image?
Class Car(models.Model):
make = models.CharFIeld(max_length=10)
thumbnail = models.ImageField(upload_to='thumbnails/')
Class Photos(models.Model):
car = models.ForeignKey(Car, default=None)
image = models.ImageField(upload_to='images/')
We could get all of the images for the car with a query like this:
(queryset=Photos.objects.filter(car_id = pk))
but what if we only want to delete a single image - how do we get the image?
Iterate over a forloop.
queryset=Photos.objects.filter(car_id = pk)
for img in queryset:
img.delete()
or if you want to delete first image, then;
queryset[0].delete()
It is possible to write a custom function allows you to delete individual images. This may not be the best option but it works for me.

Hacking django-stdimage2 to support multiple images

I am working on a Django project in which users should be allowed to upload multiple images at once. That portion of the project will likely be handled by SWFUpload, unless you have a better suggestion.
The image renaming, resizing, and thumbnail creation will be handled by django-stdimage2.
The Problem
django-stdimage2 renames each image using the field name and object primary key.
If five images exist for gear row with primary key 1, all five images will be renamed "image_1.jpeg".
Before I introduce a possible solution, here are my models.
Basically, one gear row can have many gear_image rows.
class gear(models.Model):
id = models.AutoField(primary_key=True)
model = models.CharField(max_length=100)
class gear_images(models.Model):
id = models.AutoField(primary_key=True)
gear_id = models.ForeignKey(gear)
image = StdImageField(upload_to='images/gear', blank=True, size=(640, 480, True), thumbnail_size=(100, 100, True))
A Solution
I was thinking of adding a timestamp, in milliseconds and rounded, to the filename.
I'm neither a Python or Django pro but I poked around in django-stdimage2's fields.py file and I think I located the code I need to edit to make this work.
The two lines of code that are commented out are my proposed solutions:
def _rename_resize_image(self, instance=None, **kwargs):
'''
Renames the image, and calls methods to resize and create the thumbnail
'''
if not kwargs.get('raw', None):
if getattr(instance, self.name):
filename = getattr(instance, self.name).path
ext = os.path.splitext(filename)[1].lower().replace('jpg', 'jpeg')
# time_stamp = int(round(time.time() * 1000))
# dst = self.generate_filename(instance, '%s_%s_%s%s' % (self.name, instance._get_pk_val(), time_stamp, ext))
dst = self.generate_filename(instance, '%s_%s%s' % (self.name, instance._get_pk_val(), ext))
dst_fullpath = os.path.join(settings.MEDIA_ROOT, dst)
if os.path.normcase(os.path.abspath(filename)) != os.path.normcase(os.path.abspath(dst_fullpath)):
os.rename(filename, dst_fullpath)
if self.size:
self._resize_image(dst_fullpath, self.size)
if self.thumbnail_size:
thumbnail_filename = self._get_thumbnail_filename(dst_fullpath)
shutil.copyfile(dst_fullpath, thumbnail_filename)
self._resize_image(thumbnail_filename, self.thumbnail_size)
setattr(instance, self.attname, dst)
instance.save()
Each image name would look something like: image_1_159753456.jpeg
Do you think this is a good work-around? I am open to other ideas also.
Thank you :)
I do not think you have a problem here at all.
django-stdimage{2} will rename to the object id of your gear_images model, not its parent gear model. So one gear can have many images, each will have the gear_images pk appended to the filename.
So really, you only have a problem if it's important to you to use the gear model's pk in the filename instead of the gear_images pk. If you're ok with the latter, then you don't need to do anything, it should just work.

Can't set ImageField url attribute

I want to change my ImageField's attribute, however I'm constantly getting the Can't set attribute error.
My model is
class Society(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
summary = models.TextField(blank=True,null=True)
members = models.ManyToManyField(User,null=True,blank=True)
gallery = models.ForeignKey(Gallery,null=True,blank=True)
avatar = models.ImageField(upload_to=get_society_path)
def save(self,*args,**kwargs):
super(Society, self).save(*args,**kwargs)
fix_avatar_path(self)
def clean(self):
if self.id:
self.avatar.path = get_society_path(self,self.avatar.path)
save_thumb(self.avatar.path)
And my helper functions are :
def get_society_path(instance,filename):
seperator_val = instance.id
if seperator_val is None:
seperator_val = get_time()
return '%s/society_%s/%s' % (settings.UPLOAD_ROOT,seperator_val,time_to_name(filename))
def fix_avatar_path(instance):
org_society_path = get_society_path(instance,instance.avatar.name)
make_upload_dir(org_society_path)
move(instance.avatar.path,org_society_path)
os.rmdir(os.path.dirname(instance.avatar.path))
instance.clean()
The problem is :
I want to save my society directories as society_society_id. But normally, I can't assign any id before the model is saved. So i'm creating a tmp file whose name is a time value.Then to reach societies folder, I want to rename this file. So, my fix_avatar simply moves the tmp file's content to society_(society_id) folder after the society is saved. So far so good everything works well. However, my society's ImageField still holds the previously created folder. In order to change it's value, I found that i can use clean method.(from this SO question) But still i'm getting the same result, the path doesn't change, and gives the "can't set attribute" response.
Any idea ??
Not sure, if this was ever changed in Django since this question was asked. A ticket about this not being possible still exists: https://code.djangoproject.com/ticket/15590
However, you actually can change the path by doing it like this:
self.avatar = 'uploads/example/path/'
What also does the job:
self.avatar.name = 'uploads/example/path/'
It has worked for us in several occasions.
The problem is here:
self.avatar.path = get_society_path(self,self.avatar.path)
You cannot change the value of the path attribute in FileField/ImageField instances, it's readonly. There is a proposal to change this in Django 1.4

Image depending on the text in Python/Django

How can I find an image, depending on the text?
I have image model with keywords:
class Post(models.Model):
image = ImageField(_('Image'), blank=True, upload_to='folder')
keywords = models.CharField(_('Keywords'), max_length=80)
And model which will serve as the search for a suitable image
class TextSearch(models.Model):
body = models.TextField(_('Text'))
Are you looking for something like this?
# Set to search for posts with keyword 'cute'
search = TextSearch(body="cute")
# Go run the search
results = Post.objects.filter(keywords__contains=search.body)
This will give you every Post with the TextSearch's body in the keywords. If that's not what you want, you might want to check here for a full list of Django's QuerySet methods.