How can I upload mutlple images at same time in Django? - django

I need upload multiple images in my form but I have just one imagefield, i don't want to have more imagefields the main idea is do a for each in the save method or something like that, but I don't have any idea to do that, please help.
This is my model:
class Archivos(models.Model):
id_archivo = models.AutoField(primary_key=True)
id_unidad = models.IntegerField()
nombre_archivo = models.CharField(max_length=255, blank=True)
imagen = models.ImageField(upload_to='img', null=True, blank=True)
I EDITED my def post
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('imagen')
if form.is_valid():
a = 0
for imagen in files:
a = a+1
img = form.save(commit=False)
img.id_unidad = 1
img.nombre_archivo ='hhh'+ str(a)
img.save()
print(a);
return self.form_valid(form)
else:
return self.form_invalid(form)
The for cicle works but always save the last image. I think it is working like and update but I don't understand why?
this is part of my form in a template:
the multiple imagefield

One of the choices is to create one more model for example ArchivosGallery with ForeignKey on your product. For example, if your product model called Archivos, then you should create:
class ArchivosGallery(models.Model):
image = models.ImageField(u'Images', upload_to='countries/images/', blank=True)
countries = models.ForeignKey('Archivos', blank=True, null=True)
to add images in product admin you have to add this code in admin.py:
class ImagesInline(admin.TabularInline):
model = ArchivosGallery
class ArchivosAdmin(admin.ModelAdmin):
inlines = [
ImagesInline,
]
admin.site.register(Archivos, ArchivosAdmin)
Hope its helps you

As #Zagorodniy suggested you probably want an extra table to keep the collection of uploaded images related to each other, using a ForeignKey in your image table. (if you need such a thing).
Anyway, to save multiple images, create instances of Archivos model in for loop and save it one by one:
files = request.FILES.getlist('imagen')
if form.is_valid():
for image in files:
instance = Planfile(
nombre_archivo = ... ,
id_unidad = ... ,
imagen = image
)
instance.save()
...

You should be able to access multipart values with getlist:
Here's an example:
for bfile in request.FILES.getlist('files'):
File(file=bfile, files=testing).save()

Related

Django save multiple foreign keys to database with multiple upload

I am a newbie in Django and I am looking for the best workaround on how to deal with this problem.
I have these 3 Models:
class Rigs(models.Model):
name = models.CharField(max_length=30)
featured_img = models.ImageField(upload_to='images/')
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Gallery(models.Model):
rigs = models.ForeignKey(Rigs, on_delete=models.CASCADE, related_name='galleries')
file = models.ImageField(upload_to='images/')
def __str__(self):
return self.name
class Specs(models.Model):
cpu = models.CharField(max_length=50)
motherboard = models.CharField(max_length=50)
rigs = models.ForeignKey(Rigs, on_delete=models.CASCADE)
def __str__(self):
return self.cpu
I am using this library for the multi-uploading of images (https://github.com/Chive/django-multiupload), so basically I structured my Forms similar to what the document states:
class SpecsForm(forms.ModelForm):
class Meta:
model = Specs
fields = ('rigs', 'motherboard')
class RigForm(forms.ModelForm):
class Meta:
model = Rigs
fields = ('name','featured_img')
gallery_image = MultiMediaField(
min_num=1,
max_num=3,
max_file_size=1024*1024*5,
media_type='image'
)
def save(self, commit=True):
instance = super(RigForm, self).save(commit)
for each in self.cleaned_data['gallery_image']:
Gallery.objects.create(file=each, rigs=instance)
return instance
As well as my Views:
class newrig(CreateView):
model = Rigs
form_class = RigForm
template_name = 'flexrigapp/newrig.html'
success_url = '?success'
My problem is that I couldn't figure out how to include the SpecsForm on my HTML Form as well as save it on my database with the correct Foreign Key.
I have already tried transferring this one on my views with some changes but still no good.
def save(self, commit=True):
instance = super(RigForm, self).save(commit)
for each in self.cleaned_data['gallery_image']:
Gallery.objects.create(file=each, rigs=instance)
return instance
Also, I tried a function based views instead of class based but the problem is that the multi-uploading validation doesn't work on this approach of mine or maybe my code is not correct.
The expected result is that data should be saved on its respective databases (Rigs, Specs, Gallery) with the proper Foreign key on the Specs and Gallery. Right now, I can only save the Rigs and Gallery.
This is Django 2.2.4, by the way.
UPDATE
I tried updating my Views to Function-Based View just like what #dirkgroten suggested.
def newrig(request):
if request.method == 'POST':
rig_form = RigForm(request.POST, request.FILES, prefix="rigs")
specs_form = SpecsForm(request.POST, prefix="specs")
if rig_form.is_valid() and specs_form.is_valid():
rigs = rig_form.save()
specs = specs_form.save(commit=False)
specs.rigs = rigs
specs.save()
return HttpResponseRedirect("?success")
else:
rig_form = RigForm(prefix="rigs")
specs_form = SpecsForm(prefix="specs")
return render(request, "flexrigapp/newrig.html", {'rig_form': rig_form,'specs_form': specs_form,})
No data is being saved on the database now. No error logs.

Changing an image in Django inlineformset_factory puts it to the end of the list

Supposing I am making a "How to" Django webapp where users make posts about how to- do different things like.
"How to" make a rope
"How to" make an earthen pot
"How to" learn to ride a bike
You get the idea. I have made the post create view for this.Now when members make the post.They add additional images to the post
Example: "How to" make a rope
This has Post Title = How to make a rope
Post description = "Some description"
Post Image = Main Image
Now they have to show images step by step how the rope is made
Image 1: Do this 1st
Image 2: Do this 2nd
I am using Django formsets along with my post model to achieve this. Everything is working absolutely fine in create view. no problems. But in update view things break.
The Problem
The problem is when a user wants to EDIT their post and switch image number 2. from their post to a different image. Even though they changed the 2nd image. That image now ends up at the very end of the list. Making the user to re-upload all the images. To bring back the Order. Making my app look buggy.
Example: Lets assume user has the below post
main post Title
" Some description "
Main Image = Post_image.jpg
1st Image = A.jpg
Image Title
Image description
2nd Image = B.jpg
Image Title
Image description
3rd Image = C.jpg
Image Title
Image description
4st Image = D.jpg
Image Title
Image description
5th Image = E.jpg
Image Title
Image description
6th Image = F.img
Image Title
Image description
Now if I changed 2nd image B.jpg to b.jpg b.jpg moves to the very end of the list and you have the order as A, C, D, E, F, b
Below are my models:
class Post(models.Model):
user = models.ForeignKey(User, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=250, unique=True)
slug = models.SlugField(allow_unicode=True, unique=True,max_length=500)
post_image = models.ImageField()
message = models.TextField()
class Prep (models.Model): #(Images)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='post_prep')
image = models.ImageField(upload_to='images/', blank=True, null=True, default='')
image_title = models.CharField(max_length=100, default='')
image_description = models.CharField(max_length=250, default='')
sequence = models.SmallIntegerField() ###########################ADDED THIS
class Meta: ###########################ADDED THIS
unique_together = (('post', 'sequence'),) ###########################ADDED THIS
ordering = ['sequence'] ###########################ADDED THIS
My post create view
def post_create(request):
ImageFormSet = modelformset_factory(Prep, fields=('image', 'image_title', 'image_description'), extra=12, max_num=12,
min_num=2)
if request.method == "POST":
form = PostForm(request.POST or None, request.FILES or None)
formset = ImageFormSet(request.POST or None, request.FILES or None)
if form.is_valid() and formset.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
post_user = request.user
for index, f in enumerate(formset.cleaned_data): #######CHANGED THIS
try: ##############CHANGED THIS
photo = Prep(sequence=index, post=instance, image=f['image'],
image_title=f['image_title'], image_description=f['image_description'])
photo.save()
except Exception as e:
break
return redirect('posts:single', username=instance.user.username, slug=instance.slug)
else:
form = PostForm()
formset = ImageFormSet(queryset=Prep.objects.none())
context = {
'form': form,
'formset': formset,
}
return render(request, 'posts/post_form.html', context)
My post Edit View:
class PostPrepUpdate(LoginRequiredMixin, UpdateView):
model = Post
fields = ('title', 'message', 'post_image')
template_name = 'posts/post_edit.html'
success_url = reverse_lazy('home')
def get_context_data(self, **kwargs):
data = super(PostPrepUpdate, self).get_context_data(**kwargs)
if self.request.POST:
data['prep'] = PrepFormSet(self.request.POST, self.request.FILES, instance=self.object)
else:
data['prep'] = PrepFormSet(instance=self.object)
return data
def form_valid(self, form):
context = self.get_context_data()
prep = context['prep']
with transaction.atomic():
self.object = form.save()
if prep.is_valid():
prep.instance = self.object
prep.save()
return super(PostPrepUpdate, self).form_valid(form)
My Forms.py
class PostEditForm(forms.ModelForm):
class Meta:
model = Post
fields = ('title', 'message', 'post_image', 'group', )
class PrepForm(forms.ModelForm): #####################CHANGED THIS
class Meta:
model = Prep
fields = ('image', 'image_title', 'image_description', 'sequence')
PrepFormSet = inlineformset_factory(Post, Prep, form=PrepForm, extra=5, max_num=7, min_num=2)
***Need help fixing this issue. Example if they change Image 2. Then it should stay at Number 2 position and not move to the end of the list
Currently you don't save the order of the images, relying on the fact that they are displayed in the same order as they are created. Adding a field in Prep containing the place of the image in the sequence of images would help:
class Prep (models.Model):
# ...
nr = SmallIntegerField()
#...
class Meta:
unique_together = (('post', 'nr'),)
The unique_together constraint ensures that every number is only used once per post. This also allows reordering of images within a post without deleting and recreating all Prep objects.
On displaying the post, you'd have to order the Prep objects by nr.
As for populating the new column, since there's no single default value that makes sense, the easiest approach might be:
Add the nr field without the unique_together constraint and with null=True first; migrate the changes.
After migration, loop over the existing Prep objects of each Post in the current order and assign them ascending numbers.
After that remove null=True, add unique_together and migrate again.
unique_together needs string parameters (it cannot access the fields in the outer class); thanks for catching that.
On the edit form, you'd want to include the new field so that users can swap the order of two images by swapping their indexes. You just have to provide them a meaningful error message if they use duplicate indexes.
When creating however, your users don't need to specify the order explicitly as it is implicit in the sequence of the images in the formset. So I'd suggest changing your for loop like this:
for index, f in enumerate(formset.cleaned_data):
# ...
photo = Prep(nr=index,
# add all other fields
)
Use nr = index + 1 for human-friendly indexes starting with 1. In fact, index or image_index might be a better name for the field than nr.

Django - uploading several images at the time

I need your help in such question:
This is my models.py:
class Location(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
name = models.CharField(max_length=100, verbose_name="Локация", default=u'')
photos = models.ImageField(upload_to='photos', null=True)
This is my forms.py:
class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = ['name', 'photos']
This is my views.py:
class AddLocationPageView(FormView):
template_name = 'add_location.html'
form_class = LocationForm
def form_valid(self, form):
form.save()
return super(AddLocationPageView, self).form_valid(form)
I need to have possibility of uploading several photos at the time.
How can I do that?
Thanks!
Since I'm assuming you're using a relational database and your model field name is 'photos' you'll want more than one photo per location.
You can do something like:
class Image(models.Model):
full_size = models.ImageField()
thumbnail = models.ImageField()
location = models.ForeignKey('app_label.Location', related_name='photos')
and remove the image field from the Location model.
To upload multiple photos, you'll want to use a formset. Depending on the interface you want, you'll probably want to use a model formset so the photos get their location_id set properly.
With your form, all you need to do is use the formset_factory function that you can call in your view (probably in get_context_data).
Handling the formset in your view involves some logic wrangling but there is a project called django-extra-views that implements the form with a formset logic, again, depending on the interface you're going for here.
If you want to just add photos to a pre-existing location, that is much simpler: just include the model_formset with a location object.

Django ORM m2m limit to 1

# models.py
class Gallery(models.Model):
images = models.ManyToManyField(Image, null=True, blank=True)
class Image(models.Model):
image = models.ImageField()
# views.py
class GalleryIndex(ListView):
model = Gallery
I need to get thumbnail for every gallery, which is it's very first/last/whatever image.
How can I LIMIT image by 1 for any gallery as a custom attribute (to not override Gallery.images) without calling second SQL query?
A many-to-many acts as a descriptor for a normal queryset, so you can do my_gallery.images.all()[0] to limit the query to 1.
I don't think I understand correctly what you want to do, but doesn't below code work for you?
class Gallery(models.Model):
images = models.ManyToManyField(Image, null=True, blank=True)
def get_thumb(self):
return self.images.all()[0]
Or maybe other concept:
class Gallery(models.Model):
images = models.ManyToManyField(Image, null=True, blank=True)
thumbnail = models.ImageField()
def save(self, *args, **kwargs):
self.thumbnail = self.images.all()[0].image
(some exception catching here needed though)
I should've read docs better. Standard QuerySet API can't handle such cases efficiently (annotate() generates GROUP BY clause for each of parent fields which is slow) so I come with extra() method and raw subquery.
class GalleryIndex(ListView):
queryset = Gallery.objects.extra(select={'thumb':
"""
SELECT "image"
FROM "app_image", "app_gallery_images"
WHERE (
"app_gallery"."id" = "app_gallery_images"."gallery_id"
AND "app_gallery_images"."image_id" = "app_image"."id"
)
AND "app_image"."image" IS NOT NULL
LIMIT 1
"""
})
This queryset do exactly what I wanted, since SorlImageField (and ImageField) needs only filename to represent thumbnail in templates.

Django - having trouble saving filtered object

For my first Django project I'm trying to make an app that lets users create lists of media (books, movies, etc.) with various fields describing each object (title, author, etc.), and I'm having trouble figuring out how to get it to save. That is to say that nothing happens when the form is submitted. Can someone tell me what I'm doing wrong? Sorry if this is a bit of a noob question; it seems like I'm missing something really basic here. (I'm using basic HTML forms instead of ModelForms because for some media types I want to ignore certain fields - e.g. "author" for movies - but if there is an easy way to do that using ModelForms, I'm all ears.)
from views.py:
def editbook(request,list_owner,pk):
book_list = Item.objects.all().filter(item_creator=list_owner).filter(category='book').order_by('type','name')
item_to_edit = Item.objects.get(pk=pk)
if request.method == 'POST':
item_to_edit.save()
return render_to_response('books.html', {'booklist': book_list, 'listowner': list_owner}, RequestContext(request))
else:
form=EditItem()
return render_to_response('editbook.html', {'listowner': list_owner, 'item_to_edit': item_to_edit}, RequestContext(request))
from models.py:
CATEGORY_CHOICES = (
('book','book'),
('tv','tv'),
('movie','movie'),
('game','game'),
('music','music'),
)
class Item(models.Model):
item_creator = models.CharField(max_length=30) # user name goes here
category = models.CharField(max_length=5, choices=CATEGORY_CHOICES)
name = models.CharField(max_length=70)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
artist = models.CharField(max_length=70, blank=True)
type = models.CharField(max_length=50, blank=True)
progress = models.CharField(max_length=10, blank=True)
finished = models.BooleanField(default=False)
rating = models.IntegerField(default=0, blank=True, null=True)
comment = models.CharField(max_length=140, blank=True)
def __unicode__(self):
return self.name
There is, of course, a way to only use some fields in a modelform: as fully documented in Using a subset of fields on the form, you can use the fields or exclude attributes in the form's Meta class.
However you'll still need, as szaman points out, to pass the POST data to the form and check for validity, and in addition you'll need to pass in the instance paramater as you're updating an existing instance.
What I see is that you get object from database and when form is submitted than just saving the object, but you don't update any field so you cannot see changes in db. Try to do:
if request.method == "POST":
form = MyForm(request.POST)
logging.info("form.is_valid() %s" % form.is_valid())
if form.is_valid():
item_to_edit.name = form.cleaned_data['name']
item_to_edit.save()
...