Problem with django and sorl.thumbnail model field - python

What i want: when user download photo from the admin's panel, I want create a preview(thumbnail) and save it into other field of this model, using sorl.thumbnail.
What i do:
from sorl.thumbnail import ImageField, get_thumbnail
class sitePhotos(models.Model):
photo = ImageField(verbose_name=u'Фотография для галереи',
upload_to=upload_galery_photos, null=True)
preview = ImageField(upload_to=upload_galery_previews, editable=False, null=True)
Migrations are doing.
I was trying overwrite save method from class sitePhotos:
def save(self, *args, **kwargs):
self.preview = get_thumbnail(self.photo, '250x250', crop='center', quality=99)
super(sitePhotos, self).save(*args, **kwargs)
Problems: Documentation sorl.thumbnail: https://sorl-thumbnail.readthedocs.io/en/latest/examples.html#low-level-api-examples
im = get_thumbnail(my_file, '100x100', crop='center', quality=99)
What's type of my_file? It is a url, ImageField or what? When start has error 'ImageField' object has no attribute '_committed'
Google: override save method - 'ImageFile' object has no attribute '_committed'
After that i have magic with urls and other. My finish function:
self.preview = get_thumbnail('../'+self.photo.url, '250x250',
crop='center', quality=99).url
May be somebody has working example of this or where can I read about this? Thank you for your answers!

You can use the save method on Image file:
def save(self, *args, **kwargs):
preview = get_thumbnail(self.photo, '250x250', crop='center', quality=99)
self.preview.save(preview.name, ContentFile(preview.read()), save=False)
super(sitePhotos, self).save(*args, **kwargs)

Related

Django create file programatically on save

The goal with this is for the user to create an instance of the model where they populate a source URLField, and on save, it would fetch the URL and save it as a file.
class ComicImage(UUIDModel):
src = models.URLField('Comic URL', max_length=512, blank=True)
img = models.FileField('Images', null=True, blank=True, upload_to='comics')
def save(self, *args, **kwargs):
img_data = BytesIO(requests.get(self.src).content)
self.img.save(f'{self.hash}-i.webp', content=img_data)
super().save(*args, **kwargs)
But I keep getting an error
ValueError: The 'img' attribute has no file associated with it.
Which appears that it's not assigning a file to the field. Or rather, it doesn't until AFTER the model is saved.
Alternatively:
I also tried
self.img.save(f'{self.hash}-i.webp', content=img_data, save=True)
But it gets stuck in a loop of saving forever. If I set save=False, then it creates the model but not the file.
Is there a way to create/populate/modify the file before it's saved?

How to save a changed value in Django admin on api creation?

In Django Admin I am displaying a url.
This url is created using the id the object that it is attached to.
I'm using python, django and django-rest-framework.
In my views I have logic on the ApiDetail class. Here I override the 'get' method.
I increment the current object in views.py:
currentObject = Api.objects.get(id=pk)
currentObject.currentNumber += 1
currentObject.save()
return self.retrieve(request, *args, **kwargs)
In models.py I set the url field:
class Api(models.Model):
myUrl = models.CharField(max_length=500, blank=True, verbose_name="Url", editable=False)
def save(self, *args, **kwargs):
self.formUrl = "https://custumUrl/"+str(self.id)+"/"
super(Api, self).save(*args, **kwargs)
Here I override the api save method to update the formUrl field.
The problem I have is when a form is first added to Django admin and saved the url says:
https://custumUrl/none/
It should say:
https://custumUrl/1/
Or any number, but definitely the number of the objects id.
I think Daniel is right in their comments and you should follow their advice.
But if you don't want to do that, then you should first save an object, then assign an id value to the url, then save it again:
class Api(models.Model):
myUrl = models.CharField(max_length=500, blank=True, verbose_name="Url", editable=False)
def save(self, *args, **kwargs):
super(Api, self).save(*args, **kwargs)
self.formUrl = "https://custumUrl/"+str(self.id)+"/"
super(Api, self).save(*args, **kwargs)
Is currentNumber defined in he Api class?
Also, in your Api class, you have myUrl defined, but in the save method it's formUrl.
Maybe try something like this:
class Api(models.Model):
formUrl = models.CharField(max_length=500, blank=True, verbose_name="Url", editable=False)
def save(self):
"""If this is the firsts time populate required details, otherwise update it."""
if not self.id:
latest_obj = Api.latest('id')
this_id = latest_obj.id
self.formUrl = "https://custumUrl/"+str(this_id)+"/"
super(Api, self).save()
else:
#Save it as is
super(Api, self).save()

'FavoriteManager' object has no attribute 'get_query_set'

I'm using django-favorites from https://bitbucket.org/last_partizan/django-favorites/overview, and I set everything right and when I run the code it gave me 'FavoriteManager' object has no attribute 'get_query_set'. The error was occuring from the django-favorites models.py from this line
qs = self.get_query_set().filter(content_type=content_type, object_id=obj.pk)
So I thought, I need to specify object. So I'm trying to use fav button for all my Post, so I need to change obj to Post. But even after changing I get same error. What is going on with this?
This is models.py inside favorite app, where error is occurring
def favorites_for_object(self, obj, user=None):
""" Returns Favorites for a specific object """
content_type = ContentType.objects.get_for_model(type(obj))
qs = self.get_query_set().filter(content_type=content_type,
object_id=obj.pk)
if user:
qs = qs.filter(user=user)
return qs
This is where I'm calling fav_item
<div class="actions">{% fav_item post user %}</div>
Lets say I want to put fav_item on my category model
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
slug = models.CharField(max_length=100, unique=True)
author = models.OneToOneField(settings.AUTH_USER_MODEL, unique=True)
def save(self, *args, **kwargs):
self.slug = uuslug(self.name,instance=self, max_length=100)
super(Category, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
Then doesn't it make sense to import
main.models import Category
and switch obj to Category. Unfortunately there's not much documentation here: https://bitbucket.org/last_partizan/django-favorites/overview
This problem is related to Django version. The method get_query_set has been removed from Django 1.8.x . Before that, this method was used by RenameManagerMethods. Check this github source: https://github.com/django/django/blob/stable/1.7.x/django/db/models/manager.py#L56
Also django-favorites was last updated in 2013, as I can see from the source. You should consider downgrading your Django version.

Get the django user in save while using django.form

I have a problem getting the user in django when I use django forms. My code looks something like this.
The view:
#login_required
def something(request):
item = ItemForm(request.POST)
item.save(user=request.user)
The form:
class ItemForm(ModelForm):
class Meta:
model = Item
fields = '__all__'
def save(self, *args, **kwargs):
user = kwargs['user']
super(ItemForm, self).save(user=user)
The model
class Item(models.Model):
field = models.CharField(max_length=100,)
field2 = models.CharField(max_length=100,)
def check_permissions(self, user):
return user.groups.filter(name='group').exists()
def save(self, *args, **kwargs):
if self.check_permissions(kwargs['user']):
super(Item, self).save()
My problem is that when I call the default save in ItemForm I get an error because the user param is unexpected. I need the user in the model to make the permission check but I dont know how to get it.
I finally solved the problem. The way I found was to save the form without the user but with the commit flag set to False and then calling the function save from the model with the user param.
The form save method now looks like this
def save(self, *args, **kwargs):
item = super(ItemForm, self).save(commit=False)
item.save(user=kwargs['user'])

Django: how to automatically also delete file from storage when Clear Checkbox ticked?

I have a user profile model with optional avatar that looks like
#models.py
class UserProfile(models.Model):
avatar = models.ImageField(upload_to=avatars null=True, blank=True)
.
.
.
Then a form like:
#forms.py
class UserProfileForm(forms.ModelForm):
avatar = forms.ImageField(required=False,....)
class Meta:
model = UserProfile
Finally, a view including
#views.py
def edit_profile(....)
profile_obj = request.user.userprofile
form = UserProfile(data=request.POST, files=request.FILES, instance=profile_obj)
if form.is_valid():
form.save()
Now when the template for editing the user's profile, and avatar, is rendered, it included a Clear checkbox, which when selected let's the user remove their avatar photo. It leaves myuser.avatar in the state <ImageFieldFile: None>, however it does not delete the file in the storage area of the site itself (i.e. the jpg,png or whatever). I've read that this is by design in Django 1.6, which is all well and good, but how do I override this feature, so that the file is indeed deleted to?
From the shell there exists no problem:
from myapp.models import UserProfile
user1==UserProfile.objects.all()[0]
user1.avatar.delete()
Removes the jpeg too.
EDIT:
I tried using a signal like:
#models.py
.
.
.
#receiver(post_delete, sender=UserProfile)
def avatar_post_delete_handler(sender, **kwargs):
print 'DEBUG: avatar delete triggered'
avatar = kwargs['instance']
storage, path = avatar.original_image.storage, avatar.original_image.path
storage.delete(path)
but this did not even trigger, I guess because I'm not deleting the UserProfile object
in its entirety when the user selects the clear checkbox, but rather just the avatar.
Extend the ImageField like this and use it instead:
class ImageField(models.ImageField):
def save_form_data(self, instance, data):
if data is not None:
file = getattr(instance, self.attname)
if file != data:
file.delete(save=False)
super(ImageField, self).save_form_data(instance, data)
This will delete the old file if you replace it with the new one, or mark it to clear.
Here is the explanation why.
Edit:
There is also an app django-smartfields that includes this functionality plus more, like automatic re-sizing, automatic conversion of images and videos, etc. It achieves it in a more complicated way though, using field descriptors and model customization. But it is very simple to use:
from smartfields import fields
class UserProfile(models.Model):
avatar = fields.ImageField(upload_to='avatars', blank=True)
It will also remove files whenever:
field value was replaced with a new one (either uploaded or set manually)
the model instance itself containing the field is deleted.
Not the best solution at all, but one hackish way might be to store the file for each user under just their username, then on catching a pre_save signal with an empty instance.avatar.name and where the user file exists on disk in the expected place, delete it. Yuck.
class UserProfile(models.Model):
avatar = models.ImageField(upload_to=avatars null=True, blank=True)
and
#save the avatar for each user as their username
def update_filename(instance, filename):
path = "avatars"
format = instance.user.username
return os.path.join(path, format)
and
#if current instance has empty avatar.name
#and the file exists on disk where expected for user
#deduce user has clicked clear, delete file for user.
#receiver(pre_save, sender=UserProfile)
def avatar_pre_save_handler(sender, instance, **kwargs):
avatar_filepath = settings.MEDIA_ROOT +'/avatars/'+ instance.user.username
if not instance.avatar.name and os.path.isfile(avatar_filepath):
os.remove(avatar_filepath)
One more possible way (that doesn't require special naming of files) is overriding form save method, then calling it with the old avatar as kwarg:
#forms.py
class UserProfileForm(forms.ModelForm):
def save(self, commit=True, *args, **kwargs):
instance = super(UserProfileForm, self).save(commit=False)
old_avatar_name = kwargs.pop('old_avatar_name', None)
new_avatar_name = None
if self.cleaned_data['avatar']:
new_avatar_name = self.cleaned_data['avatar'].name
if old_avatar_name != new_avatar_name:
old_avatar_filepath = settings.MEDIA_ROOT +'/'+ old_avatar_name
if os.path.isfile(old_avatar_filepath):
os.remove(old_avatar_filepath)
if commit:
instance.save()
return instance
Then in the view:
def edit_profile(request,....):
.
.
.
try:
profile_obj = request.user.userprofile
except ObjectDoesNotExist:
return HttpResponseRedirect(reverse('profiles_create_profile'))
if profile_obj.avatar.name:
avatar_kwargs={'old_avatar_name': profile_obj.avatar.name}
else:
avatar_kwargs={}
.
.
.
if form.is_valid():
form.save(**avatar_kwargs)
Use Mixin like below
class ImageDeleteMixin(object):
def delete(self, *args, **kwargs):
if self.avatar:
storage, path = self.avatar.storage, self.avatar.path
super(ImageDeleteMixin, self).delete(*args, **kwargs)
storage.delete(path)
else:
super(ImageDeleteMixin, self).delete(*args, **kwargs)
def save(self, *args, **kwargs):
if self.id:
old_instance = self.__class__._default_manager.get(pk=self.pk)
if (
old_instance.avatar != self.avatar and old_instance.avatar and
old_instance.avatar.path
):
storage, path = old_instance.avatar.storage, old_instance.avatar.path
super(ImageDeleteMixin, self).save(*args, **kwargs)
storage.delete(path)
return
return super(ImageDeleteMixin, self).save(*args, **kwargs)
class UserProfile(ImageDeleteMixin, models.Model):
avatar = models.ImageField(upload_to=avatars null=True, blank=True)
Depending on your use case, you could hook into Django's Singals:
https://docs.djangoproject.com/en/dev/ref/signals/#post-delete
Or, in a view, when the "checked" variable is sent back to the server, delete the avatar!

Categories