Delete a Picture & thumbnails with sorl.thumbnail (Django) - python

I am working with Python 2.7, Django 1.9 and sorl.thumbnail.
I cannot manage to create a view to delete in one time the original picture file, the Picture entry in the database, the thumbnail pictures generated by sorl.thulbnail, and the thumbnail_kvstore entry in the database.
Here is my Picture model:
class Picture(models.Model):
file = models.ImageField(upload_to="pictures")
slug = models.SlugField(max_length=100, blank=True)
user = models.ForeignKey(User, null=True, blank=True)
exiflnglat = models.PointField(dim=3, geography=True, blank=True, null=True)
objects = models.GeoManager()
def __str__(self):
return self.slug
#models.permalink
def get_absolute_url(self):
return ('upload-new', )
And here is my view:
from sorl.thumbnail import delete
def deletepicnthumbs(request, pk):
allpicfromuser = Picture.objects.filter(user=request.user)
pictodelete = allpicfromuser.get(id=pk)
delete(pictodelete)
return redirect(adddetails)
This view does not delete anything.. What I am doing wrong ?
Thanks a lot

It seems that you are trying to use the delete method from sorl to delete an instance of the Picture model instead of the picture itself.
This code should work :
from sorl.thumbnail import delete
def deletepicnthumbs(request, pk):
pictodelete = Picture.objects.get_object_or_404(id=pk, user=request.user)
# Delete the thumbnails, as well as the original image
# If you want to keep the original image, pass ```delete_file=False```
delete(pictodelete.file)
# We use Django's method to delete the Picture instance, if needed
pictodelete.delete()
return redirect(adddetails)
Hope this helps,
Adela

Related

What is the best way of creating a 'featured image' in Django?

I'm relatively new to Django and I'm looking for a way to create a featured image within an image category.
At the moment I have a Show class and a Photo class:
class Show(models.Model):
title = models.CharField(max_length=100, unique=True, db_index=True)
playwright = models.CharField(max_length=100)
description = models.TextField()
def __str__(self) -> str:
return self.title
class Photo(models.Model):
show = models.ForeignKey(Show, on_delete=models.CASCADE)
image = models.ImageField(upload_to='', default='images/default.png')
def __str__(self) -> str:
return self.image.name
And I'd like to be able to set one photo for each show as the 'featured' image. Of course, I could put a Boolean field in for this, but that runs the risk of having multiple Featured images when there should be only one.
What is the best structure for this? I've had a look around and can't find any tutorial or resource that shows this behavior. I should be able to set this 'featured' setting from the admin panel.
(Also, I'm aware there are gallery apps out there for Django, but this is part of my learning Django, so I'm trying to build this myself.)
EDIT: I'm afraid I was not as clear as I should have been, apologies for that! I mean specifically that one show can have many photos related to it, and that from that pool of photos, one 'featured' image can be selected.
Thanks!
It seems to me that you are looking for the OneToOneField!
class Photo(models.Model):
show = models.OneToOneField(Show, on_delete=models.CASCADE)
image = models.ImageField(upload_to='', default='images/default.png')
def __str__(self) -> str:
return self.image
Make sure you also create a Form in forms.py for it and then make sure to validate it in views.py when you register the Show!
in forms.py something like this:
Class ShowPhoto(forms.ModelForm):
class Meta:
model = Photo
fields =('show','image')
and in Views.py after importing the ShowPhoto form with:
from .forms import ShowPhoto
something like this in the function referring to the html page:
add the variable:
show_pic = ShowPhoto(request.Post)
and add the validation when you are validating the form for the show:
if form.is_valid() and show_pic.is_valid():
show = form.save()
show_picture = show_pic.save(commit=false)
show_picture.show = show
show_picture.save()
remember to pass it to the context when you render!
and don't forget to add the form to add the picture when you create the show in the html template like:
{{ show_pic.as_p }}

Why is VSCode intellisense type hints different for these 2 Django model objects?

In the views.py after importing the Video model I am testing how intellisense works in VScode.
This is the views.py
from django.shortcuts import render
from .models import Video
# Create your views here.
video1: Video = Video.objects.get(id=1)
video2: Video = Video.objects.filter(id=1).first()
this is the models.py:
class Video(models.Model):
'''
info about videos
'''
video_id = models.TextField(blank=True, null=True)
title = models.TextField()
description = models.TextField()
tags = models.TextField()
is_uploaded = models.BooleanField()
date_uploaded = models.DateField(null=True)
filename = models.TextField()
thumbnail_filename = models.TextField()
When I start typing I get this for video1 which is from video1: Video = Video.objects.get(id=1):
As you can see it offers model fields
but for video2 which is from video2: Video = Video.objects.filter(id=1).first():
it doesn't offer model fields.
Why is that and how can we fix it?
The first one knows that it is getting a single model instance as it is guaranteed by the QuerySet. I think the second one is not guaranteed to return a model instance.
In the django source code for this:
def first(self):
"""Return the first object of a query or None if no match is found."""
for obj in (self if self.ordered else self.order_by('pk'))[:1]:
return obj
so it is returning Optional[<instance>] whereas get() returns an instance.
Interestingly, earliest may work for your usecase, and it is guaranteed to return a model instance as it subcalls get.

Cant Upload Images to Django Admin

I am working on e-commerce project and i am stuck at this. Whenever admin adds new product,it should also add image related to the product. So i added the the column with ImageField but i am getting error again and again. Here is my code for models
class Product(models.Model):
product_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=100, blank=True, null=True)
image = models.ImageField(db_column='image' , blank=True, null=True)
info = models.CharField(max_length=500, blank=True, null=True)
def image_tag(self):
if self.image:
return mark_safe('<img src="%s"/>' % self.image.url)
else:
return 'No Image Found'
image_tag.short_description = 'Image'
and in admin.py
class ProductAdmin(admin.ModelAdmin):
list_display = ('product_id', 'name','image_tag', 'info')
readonly_fields = ('image',)
admin.site.register(Product, ProductAdmin)
But every time i get this error
Exception Type: AttributeError
Exception Value:'bytes' object has no attribute 'url'
I tried using escape but it still not displaying images.I am using MySQL existing database. I could really use the help. Thanks
Currently you are storing image in your database as bytes, which Django does not prefers instead you should first specify MEDIA_ROOT this is folder where your image will be saved and only the URL will be saved in the database. Docs [SOURCE]
I assume you have already setup MEDIA settings and have installed Pillow.
Your ImageField will look like,
# No need to specify db_column as Django already stores by default field's name.
image = models.ImageField(upload_to='users/%Y/%m/%d/', blank=True, null=True)
Now in your templates you can get the image by,
<img src="http://127.0.0.1:8000/media/{{ profile.photo }}" id='profile-photo'>

Model Placement

It is necessary to place posts on the site so that they can be created and edited, but in the site design the posts are separated by sliders and other elements, how is it possible to implement this in such a way that the other layout elements are, so to speak, ignored?
I tried to solve this problem by creating additional models, that is, the posts before the separating elements (sliders in this case) should be taken in one class, and the subsequent ones in another and further edited in such a way. Of course this is a crutch, but I don’t know how to configure it, because together these models are not displayed for me, that is, either one or the other. I also created a separate class for the Main Post, because it has a different design and, as a matter of fact, always hangs in the top, but of course it’s ideal to implement such that the model has such an attribute that you can indicate to a specific post that it is the main and most located at the beginning of the site.
Here is my code (let's say that we added Posts_one, Posts_two, if we talk about the case of using a crutch, which I also failed):
models.py:
from django.conf import settings
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Toppost(models.Model):
title = models.CharField(max_length=200)
caption = models.CharField(max_length=150, default='краткое описание поста')
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
class Post(models.Model):
title = models.CharField(max_length=200)
caption = models.CharField(max_length=150, default='краткое описание поста')
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
views.py:
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from .models import Post, Toppost
def posts_list(request):
posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
return render(request, 'blog/posts_list.html', {'posts': posts})
topposts = Toppost.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
return render(request, 'blog/posts_list.html', {'topposts': topposts})
I also attach a screenshot with a diagram of how it should look. Ideally, of course, it’s interesting to know how this can be implemented so that all this is one Post model and does not destroy the layout of the site and that you can select any post as Main_Post.
I'd simply add an attribute for a post to define whether it's a top post or not.
class Post(models.Model):
# ... all the other stuff ...
is_top_post = models.BooleanField(default=False)
# ... all the other stuff ...
# ...
def posts_list(request):
all_posts = Post.objects.filter(published_date__lte=timezone.now()).order_by("published_date")
non_top_posts = []
top_posts = []
for post in all_posts:
(top_posts if post.is_top_post else non_top_posts).append(post)
return render(request, "blog/posts_list.html", {"posts": non_top_posts, "top_posts": top_posts})
You can also ensure that there is exactly one top post by adding a save override like
class Post(models.Model):
# ... all the other stuff ...
is_top_post = models.BooleanField(default=False)
# ... all the other stuff ...
def save(self, **kwargs):
if self.is_top_post:
# Un-top every other post
Post.objects.exclude(id=self.id).update(is_top_post=False)
return super().save(**kwargs)

Django many to many relation not saving

Update:
For anyone curious, I figured out what and why and how to fix it.
In my view I had:
fields = ['html', 'tags', 'title', 'text', 'taken_date', 'image']
And am using {{ form.as_p }} in my template. Apparently once that gets posted from the form it really, really doesn't want anything else touching the form fields that wasn't already in the form.
So I took out the 'tags' field from my view and it works.
Thanks to everyone that responded.
Original Question:
Using Django 2.0.1 and PostgreSQL 9.2.18
I'm writing a simple photogallery application. In it I have a photo object and PhotoTag object. The photo can have many tags and the tags can be associated with many photos, thus it needing to be a ManyToManyField.
Upon save of the submitted photo, a post_save receiver calls functions to make thumbnails (which work fine) and a function to update tags.
The photo gets saved fine, update_tags gets called fine, tags get read from the photo fine, tags get saved into PhotoTag fine. But the manytomany table tying the two together does not get the new rows inserted. Unless the code exits abnormally during either the update_tags function or the post_save receiver function, thumbs after update_tags is called.
I've even tried using a connection.cursor to write directly into the m2m table and it has the same behavior.
If I try to call save() on the Photo object again, I just get into an infinite loop due to the post_save signal.
I'm baffled as to what is going on. Any clues?
# models.py
def update_tags(instance):
tags = get_tags(instance.image)
# Set initial values
pt = []
tagid = ''
photoid = instance.id
# Loop through tag list and insert into PhotoTag and m2m relation
for x in range(0, len(tags)):
# Make sure this tag doesn't already exist
if PhotoTag.objects.filter(tag_text=tags[x]).count() == 0:
pt = PhotoTag.objects.create(tag_text=tags[x])
tagid = PhotoTag.objects.latest('id').id
instance.tags.add(pt)
else:
# Only working with new tags right now
pass
return
class Photo(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
title = models.CharField(max_length=200, null=True, blank=True)
text = models.TextField(null=True, blank=True)
html = models.BooleanField(default=False)
filename = models.CharField(default='', max_length=100, blank=True,
null=True)
image = models.ImageField(upload_to=upload_path)
location = models.CharField(max_length=100, blank=True, null=True)
entry_date = models.DateTimeField(default=timezone.now)
taken_date = models.DateTimeField(blank=True, null=True)
tags = models.ManyToManyField(PhotoTag, blank=True)
#receiver(post_save, sender=Photo)
def thumbs(sender, instance, **kwargs):
"""
Upon photo save, create thumbnails and then
update PhotoTag and m2m with any Exif/XMP tags
in the photo.
"""
mk_thumb(instance.image, 'mid')
mk_thumb(instance.image, 'th')
mk_thumb(instance.image, 'sm')
update_tags(instance)
return
-------------
From views.py
-------------
class PhotoCreate(LoginRequiredMixin, CreateView):
model = Photo
template_name = 'photogallery/photo_edit.html'
fields = ['html', 'tags', 'title', 'text', 'taken_date', 'image']
def get_initial(self):
self.initial = {'entry_date': timezone.now()}
return self.initial
def form_valid(self, form):
form.instance.author = self.request.user
return super(PhotoCreate, self).form_valid(form)
Update:
def save(self, mkthumb='', *args, **kwargs):
super(Photo, self).save(*args, **kwargs)
if mkthumb != "thumbs":
self.mk_thumb(self.image, 'mid')
self.mk_thumb(self.image, 'th')
self.mk_thumb(self.image, 'sm')
self.update_tags()
mkthumb = "thumbs"
return
I had a similar issue where I was trying to add a group when a user instance was saved.
The anwer why this is happening is at the docs and more explicitly (using code) at this ticket.
When saving a ModelForm() (hitting save in the admin), first an instance of the object is saved, then all its signals are triggered etc. The third step is to save all m2m relations using ModelForm().cleaned_data. If ModelForm().cleaned_data['tags'] is None, all the relations created from your signal, will be deleted.
A hackish solution, is to use a post_save signal with transaction.on_commit() which will execute the relevant code after the existing transaction (which includes the procedure of saving all m2m relations) is committed to the database.
def on_transaction_commit(func):
''' Create the decorator '''
def inner(*args, **kwargs):
transaction.on_commit(lambda: func(*args, **kwargs))
return inner
#receiver(post_save, sender=Photo)
#on_transaction_commit
def tags(instance, raw, **kwargs):
"""
Create the relevant tags after the transaction
of instance is committed, unless the database is
populated with fixtures.
"""
if not raw:
update_tags(instance)
A more sound solution if your many to many relation has not blank=True is to use a m2m_changed() signal, as explained in this post or the before mentioned ticket.
The best of all, is to ditch the signals and override the ModelForm().clean() method for the case where a ModelForm() is used, and also override the Model().save() method in case the model is directly saved.
A ModelForm().instance.my_flag will be useful so you can check for an existing Model().my_flag in Model().save() to avoid accessing twice the database.
override your save method like
def save(self, *args, **kwargs):
tags = get_tags(self.image)
# Set initial values
pt = None
# Loop through tag list and insert into PhotoTag and m2m relation
for x in range(0, len(tags)):
# Make sure this tag doesn't already exist
if PhotoTag.objects.filter(tag_text=tags[x]).count() == 0:
pt = PhotoTag.objects.create(tag_text=tags[x])
self.tags.add(pt)
else:
# Only working with new tags right now
pass
super(Photo, self).save(*args, **kwargs)

Categories